diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 00000000000..e5b6d8d6a67 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 00000000000..98267c52238 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", + "changelog": ["@changesets/changelog-github", { "repo": "bluesky-social/atproto" }], + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.changeset/seven-schools-switch.md b/.changeset/seven-schools-switch.md new file mode 100644 index 00000000000..012cf392426 --- /dev/null +++ b/.changeset/seven-schools-switch.md @@ -0,0 +1,6 @@ +--- +'@atproto/api': patch +--- + +Adds a new method `app.bsky.graph.getSuggestedFollowsByActor`. This method +returns suggested follows for a given actor based on their likes and follows. diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000000..fc66834cbc0 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +packages/api/src/client +packages/bsky/src/lexicon +packages/pds/src/lexicon diff --git a/.eslintrc b/.eslintrc index 9d8f724f10a..8a278deb2c7 100644 --- a/.eslintrc +++ b/.eslintrc @@ -13,7 +13,7 @@ "plugin:prettier/recommended", "prettier" ], - "ignorePatterns":[ + "ignorePatterns": [ "dist", "node_modules", "jest.config.base.js", @@ -26,7 +26,11 @@ "rules": { "no-var": "error", "prefer-const": "warn", - "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], + "no-misleading-character-class": "warn", + "@typescript-eslint/no-unused-vars": [ + "warn", + { "argsIgnorePattern": "^_" } + ], "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/no-empty-interface": "off", "@typescript-eslint/explicit-module-boundary-types": "off", diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index f02d83fa7f7..e6f81a12d57 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,10 +4,10 @@ about: Create a report to help us improve title: '' labels: bug assignees: '' - --- **Describe the bug** + **To Reproduce** @@ -22,8 +22,8 @@ Steps to reproduce the behavior: **Details** - - Operating system: - - Node version: +- Operating system: +- Node version: **Additional context** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 0ac6b3c416b..0103c283eb2 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,6 @@ about: Suggest an idea for this project title: '' labels: feature-request assignees: '' - --- **Is your feature request related to a problem? Please describe.** diff --git a/.github/workflows/build-and-push-bsky-aws.yaml b/.github/workflows/build-and-push-bsky-aws.yaml index 88904771f62..80258f3a7df 100644 --- a/.github/workflows/build-and-push-bsky-aws.yaml +++ b/.github/workflows/build-and-push-bsky-aws.yaml @@ -12,6 +12,7 @@ env: jobs: bsky-container-aws: + if: github.repository == 'bluesky-social/atproto' runs-on: ubuntu-latest permissions: contents: read @@ -47,7 +48,7 @@ jobs: with: context: . push: ${{ github.event_name != 'pull_request' }} - file: ./packages/bsky/Dockerfile + file: ./services/bsky/Dockerfile tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha diff --git a/.github/workflows/build-and-push-bsky-ghcr.yaml b/.github/workflows/build-and-push-bsky-ghcr.yaml index d9338f67f1b..b9137db1a54 100644 --- a/.github/workflows/build-and-push-bsky-ghcr.yaml +++ b/.github/workflows/build-and-push-bsky-ghcr.yaml @@ -14,6 +14,7 @@ env: jobs: bsky-container-ghcr: + if: github.repository == 'bluesky-social/atproto' runs-on: ubuntu-latest permissions: contents: read @@ -49,7 +50,7 @@ jobs: with: context: . push: ${{ github.event_name != 'pull_request' }} - file: ./packages/bsky/Dockerfile + file: ./services/bsky/Dockerfile tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha diff --git a/.github/workflows/build-and-push-pds-aws.yaml b/.github/workflows/build-and-push-pds-aws.yaml index 2565b38e432..097f782d88e 100644 --- a/.github/workflows/build-and-push-pds-aws.yaml +++ b/.github/workflows/build-and-push-pds-aws.yaml @@ -11,6 +11,7 @@ env: jobs: pds-container-aws: + if: github.repository == 'bluesky-social/atproto' runs-on: ubuntu-latest permissions: contents: read @@ -46,7 +47,7 @@ jobs: with: context: . push: ${{ github.event_name != 'pull_request' }} - file: ./packages/pds/Dockerfile + file: ./services/pds/Dockerfile tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha diff --git a/.github/workflows/build-and-push-pds-ghcr.yaml b/.github/workflows/build-and-push-pds-ghcr.yaml index cc6bc8939c9..b11230ab531 100644 --- a/.github/workflows/build-and-push-pds-ghcr.yaml +++ b/.github/workflows/build-and-push-pds-ghcr.yaml @@ -13,6 +13,7 @@ env: jobs: pds-container-ghcr: + if: github.repository == 'bluesky-social/atproto' runs-on: ubuntu-latest permissions: contents: read @@ -48,7 +49,7 @@ jobs: with: context: . push: ${{ github.event_name != 'pull_request' }} - file: ./packages/pds/Dockerfile + file: ./services/pds/Dockerfile tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 00000000000..3b3b008a141 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,37 @@ +name: Publish + +on: + push: + branches: + - main + +env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + build: + name: Build & Publish + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v3 + - uses: pnpm/action-setup@v2 + with: + version: 8 + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'pnpm' + - run: pnpm i --frozen-lockfile + - run: pnpm verify + - name: Publish + id: changesets + uses: changesets/action@v1 + with: + publish: pnpm release + version: pnpm version-packages + commit: 'Version packages' + title: 'Version packages' diff --git a/.github/workflows/repo.yaml b/.github/workflows/repo.yaml index 80903117c4c..8380fff8a63 100644 --- a/.github/workflows/repo.yaml +++ b/.github/workflows/repo.yaml @@ -1,43 +1,57 @@ -name: repo +name: Test + on: pull_request: - push: branches: - - main + - '*' + concurrency: group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}' cancel-in-progress: true + jobs: build: + name: Build runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - uses: pnpm/action-setup@v2 + with: + version: 8 - uses: actions/setup-node@v3 with: node-version: 18 - cache: "yarn" - - run: yarn install --frozen-lockfile - - run: yarn build + cache: 'pnpm' + - run: pnpm i --frozen-lockfile + - run: pnpm build test: + name: Test strategy: matrix: - shard: [1/4, 2/4, 3/4, 4/4] + shard: [1/8, 2/8, 3/8, 4/8, 5/8, 6/8, 7/8, 8/8] runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - uses: pnpm/action-setup@v2 + with: + version: 8 - uses: actions/setup-node@v3 with: node-version: 18 - cache: "yarn" - - run: yarn install --frozen-lockfile - - run: yarn test:withFlags --maxWorkers=2 --shard=${{ matrix.shard }} --passWithNoTests + cache: 'pnpm' + - run: pnpm i --frozen-lockfile + - run: pnpm test:withFlags --maxWorkers=1 --shard=${{ matrix.shard }} --passWithNoTests verify: + name: Verify runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - uses: pnpm/action-setup@v2 + with: + version: 8 - uses: actions/setup-node@v3 with: node-version: 18 - cache: "yarn" - - run: yarn install --frozen-lockfile - - run: yarn verify + cache: 'pnpm' + - run: pnpm install --frozen-lockfile + - run: pnpm verify diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000000..8e012302ad8 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +enable-pre-post-scripts = true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000000..6340cc359c2 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,10 @@ +node_modules +interop-test-files +dist +build +.nyc_output +coverage +pnpm-lock.yaml +.pnpm* +.changeset +*.d.ts diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 059698053c4..f10293dc002 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -14,4 +14,4 @@ ATProto receives so many contributions that we could never list everyone who des #### [TowhidKashem](https://github.com/TowhidKashem), Security disclosure, May 2023 -#### [DavidBuchanan314](https://github.com/DavidBuchanan314), Security disclosure, May 2023 \ No newline at end of file +#### [DavidBuchanan314](https://github.com/DavidBuchanan314), Security disclosure, May 2023 diff --git a/Makefile b/Makefile index 138575cd3a1..f8c36ce2bb0 100644 --- a/Makefile +++ b/Makefile @@ -12,46 +12,46 @@ help: ## Print info about all commands .PHONY: build build: ## Compile all modules - yarn build + pnpm build .PHONY: test test: ## Run all tests - yarn test + pnpm test .PHONY: run-dev-env run-dev-env: ## Run a "development environment" shell - cd packages/dev-env; yarn run start + cd packages/dev-env; pnpm run start .PHONY: run-dev-pds run-dev-pds: ## Run PDS locally if [ ! -f "packages/pds/.dev.env" ]; then cp packages/pds/example.dev.env packages/pds/.dev.env; fi - cd packages/pds; ENV=dev yarn run start | yarn exec pino-pretty + cd packages/pds; ENV=dev pnpm run start | pnpm exec pino-pretty .PHONY: run-dev-bsky run-dev-bsky: ## Run appview ('bsky') locally if [ ! -f "packages/bsky/.dev.env" ]; then cp packages/bsky/example.dev.env packages/bsky/.dev.env; fi - cd packages/bsky; ENV=dev yarn run start | yarn exec pino-pretty + cd packages/bsky; ENV=dev pnpm run start | pnpm exec pino-pretty .PHONY: codegen codegen: ## Re-generate packages from lexicon/ files - cd packages/api; yarn run codegen - cd packages/pds; yarn run codegen - cd packages/bsky; yarn run codegen + cd packages/api; pnpm run codegen + cd packages/pds; pnpm run codegen + cd packages/bsky; pnpm run codegen .PHONY: lint lint: ## Run style checks and verify syntax - yarn verify + pnpm verify .PHONY: fmt fmt: ## Run syntax re-formatting - yarn prettier + pnpm format .PHONY: deps -deps: ## Installs dependent libs using 'yarn install' - yarn install --frozen-lockfile +deps: ## Installs dependent libs using 'pnpm install' + pnpm install --frozen-lockfile .PHONY: nvm-setup -nvm-setup: ## Use NVM to install and activate node+yarn +nvm-setup: ## Use NVM to install and activate node+pnpm nvm install 18 nvm use 18 - npm install --global yarn + npm install --global pnpm diff --git a/README.md b/README.md index d4a9fa55f0f..12c6e58b880 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # AT Protocol (Authenticated Transfer Protocol) -This is a working repository for the "AT Protocol," aka the Authenticated Transfer Protocol. +This is a working repository for the AT Protocol, aka the Authenticated Transfer Protocol. --- @@ -10,7 +10,7 @@ This is a working repository for the "AT Protocol," aka the Authenticated Transf ## ℹ️ About this project -To learn more about ATP, see: +To learn more about atproto, see: - [Protocol Documentation](https://atproto.com/docs) - [Overview Guide](https://atproto.com/guides/overview) 👈 Good place to start @@ -31,7 +31,7 @@ While we do accept contributions, we prioritize high-quality issues and pull req - Check for existing issues before filing a new one, please. - Open an issue and give some time for discussion before submitting a PR. - If submitting a PR that includes a lexicon change, please get sign off on the lexicon change _before_ doing the implementation. -- Issues are for bugs & feature requests related to the Typescript implementation of atproto and related services. For high-level discussions, please you the [Discussion Forum](https://github.com/bluesky-social/atproto/discussions). For client issues, please use the relevant [social-app](https://github.com/bluesky-social/social-app) repo +- Issues are for bugs & feature requests related to the TypeScript implementation of atproto and related services. For high-level discussions, please you the [Discussion Forum](https://github.com/bluesky-social/atproto/discussions). For client issues, please use the relevant [social-app](https://github.com/bluesky-social/social-app) repo - Stay away from PRs that: - Refactor large parts of the codebase - Add entirely new features without prior discussion @@ -40,6 +40,12 @@ While we do accept contributions, we prioritize high-quality issues and pull req Remember, we serve a wide community of users. Our day-to-day involves us constantly asking "which top priority is our top priority." If you submit well-written PRs that solve problems concisely, that's an awesome contribution. Otherwise, as much as we'd love to accept your ideas and contributions, we really don't have the bandwidth. +## Are you a developer interested in building on atproto? + +Bluesky is an open social network built on the AT Protocol, a flexible technology that will never lock developers out of the ecosystems that they help build. With atproto, third-party can be as seamless as first-party through custom feeds, federated services, clients, and more. + +If you're a developer interested in building on atproto, we'd love to email you a Bluesky invite code. Simply share your GitHub (or similar) profile with us via [this form](https://forms.gle/BF21oxVNZiDjDhXF9). + ## Security disclosures If you discover any security issues, please send an email to security@bsky.app. The email is automatically CCed to the entire team, and we'll respond promptly. See [SECURITY.md](https://github.com/bluesky-social/atproto/blob/main/SECURITY.md) for more info. diff --git a/SECURITY.md b/SECURITY.md index d1602659ff1..0d6086fb204 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,8 +2,8 @@ ## Reporting a Vulnerability -Please do NOT report possible security vulnerabilities in public channels such as GitHub Issues. If you believe you have found a security vulnerability, please email us at `security@bsky.app` with a description of the issue. +Please do NOT report possible security vulnerabilities in public channels such as GitHub Issues. If you believe you have found a security vulnerability, please email us at `security@bsky.app` with a description of the issue. We will acknowledge the vulnerability as soon as possible - within 3 business days - and follow up when a fix lands. Please avoid discussing the vulnerability until we do so. -With your consent, we will add you to the repository [CONTRIBUTORS](https://github.com/bluesky-social/atproto/blob/main/CONTRIBUTORS.md) file. \ No newline at end of file +With your consent, we will add you to the repository [CONTRIBUTORS](https://github.com/bluesky-social/atproto/blob/main/CONTRIBUTORS.md) file. diff --git a/interop-test-files/README.md b/interop-test-files/README.md new file mode 100644 index 00000000000..3a72400317c --- /dev/null +++ b/interop-test-files/README.md @@ -0,0 +1,9 @@ + +atproto Interop Test Files +========================== + +This directory contains reusable files for testing interoperability and specification compliance for atproto (AT Protocol). + +The protocol itself is documented at . If there are conflicts or ambiguity between these test files and the specs, the specs are the authority, and these test files should usually be corrected. + +These files are intended to be simple (JSON, text files, etc) and mostly self-documenting. diff --git a/interop-test-files/crypto/signature-fixtures.json b/interop-test-files/crypto/signature-fixtures.json new file mode 100644 index 00000000000..917c6d02455 --- /dev/null +++ b/interop-test-files/crypto/signature-fixtures.json @@ -0,0 +1,42 @@ +[ + { + "comment": "valid P-256 key and signature, with low-S signature", + "messageBase64": "oWVoZWxsb2V3b3JsZA", + "algorithm": "ES256", + "didDocSuite": "EcdsaSecp256r1VerificationKey2019", + "publicKeyDid": "did:key:zDnaembgSGUhZULN2Caob4HLJPaxBh92N7rtH21TErzqf8HQo", + "publicKeyMultibase": "zxdM8dSstjrpZaRUwBmDvjGXweKuEMVN95A9oJBFjkWMh", + "signatureBase64": "2vZNsG3UKvvO/CDlrdvyZRISOFylinBh0Jupc6KcWoJWExHptCfduPleDbG3rko3YZnn9Lw0IjpixVmexJDegg", + "validSignature": true + }, + { + "comment": "valid K-256 key and signature, with low-S signature", + "messageBase64": "oWVoZWxsb2V3b3JsZA", + "algorithm": "ES256K", + "didDocSuite": "EcdsaSecp256k1VerificationKey2019", + "publicKeyDid": "did:key:zQ3shqwJEJyMBsBXCWyCBpUBMqxcon9oHB7mCvx4sSpMdLJwc", + "publicKeyMultibase": "z25z9DTpsiYYJKGsWmSPJK2NFN8PcJtZig12K59UgW7q5t", + "signatureBase64": "5WpdIuEUUfVUYaozsi8G0B3cWO09cgZbIIwg1t2YKdUn/FEznOndsz/qgiYb89zwxYCbB71f7yQK5Lr7NasfoA", + "validSignature": true + }, + { + "comment": "P-256 key and signature, with non-low-S signature which is invalid in atproto", + "messageBase64": "oWVoZWxsb2V3b3JsZA", + "algorithm": "ES256", + "didDocSuite": "EcdsaSecp256r1VerificationKey2019", + "publicKeyDid": "did:key:zDnaembgSGUhZULN2Caob4HLJPaxBh92N7rtH21TErzqf8HQo", + "publicKeyMultibase": "zxdM8dSstjrpZaRUwBmDvjGXweKuEMVN95A9oJBFjkWMh", + "signatureBase64": "2vZNsG3UKvvO/CDlrdvyZRISOFylinBh0Jupc6KcWoKp7O4VS9giSAah8k5IUbXIW00SuOrjfEqQ9HEkN9JGzw", + "validSignature": false + }, + { + "comment": "K-256 key and signature, with non-low-S signature which is invalid in atproto", + "messageBase64": "oWVoZWxsb2V3b3JsZA", + "algorithm": "ES256K", + "didDocSuite": "EcdsaSecp256k1VerificationKey2019", + "publicKeyDid": "did:key:zQ3shqwJEJyMBsBXCWyCBpUBMqxcon9oHB7mCvx4sSpMdLJwc", + "publicKeyMultibase": "z25z9DTpsiYYJKGsWmSPJK2NFN8PcJtZig12K59UgW7q5t", + "signatureBase64": "5WpdIuEUUfVUYaozsi8G0B3cWO09cgZbIIwg1t2YKdXYA67MYxYiTMAVfdnkDCMN9S5B3vHosRe07aORmoshoQ", + "validSignature": false + } +] diff --git a/interop-test-files/crypto/w3c_didkey_K256.json b/interop-test-files/crypto/w3c_didkey_K256.json new file mode 100644 index 00000000000..9ba9844b3ed --- /dev/null +++ b/interop-test-files/crypto/w3c_didkey_K256.json @@ -0,0 +1,22 @@ +[ + { + "privateKeyBytesHex": "9085d2bef69286a6cbb51623c8fa258629945cd55ca705cc4e66700396894e0c", + "publicDidKey": "did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme" + }, + { + "privateKeyBytesHex": "f0f4df55a2b3ff13051ea814a8f24ad00f2e469af73c363ac7e9fb999a9072ed", + "publicDidKey": "did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2" + }, + { + "privateKeyBytesHex": "6b0b91287ae3348f8c2f2552d766f30e3604867e34adc37ccbb74a8e6b893e02", + "publicDidKey": "did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N" + }, + { + "privateKeyBytesHex": "c0a6a7c560d37d7ba81ecee9543721ff48fea3e0fb827d42c1868226540fac15", + "publicDidKey": "did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy" + }, + { + "privateKeyBytesHex": "175a232d440be1e0788f25488a73d9416c04b6f924bea6354bf05dd2f1a75133", + "publicDidKey": "did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj" + } +] diff --git a/interop-test-files/crypto/w3c_didkey_P256.json b/interop-test-files/crypto/w3c_didkey_P256.json new file mode 100644 index 00000000000..65d50933a5f --- /dev/null +++ b/interop-test-files/crypto/w3c_didkey_P256.json @@ -0,0 +1,6 @@ +[ + { + "privateKeyBytesBase58": "9p4VRzdmhsnq869vQjVCTrRry7u4TtfRxhvBFJTGU2Cp", + "publicDidKey": "did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb" + } +] diff --git a/interop-test-files/syntax/atidentifier_syntax_invalid.txt b/interop-test-files/syntax/atidentifier_syntax_invalid.txt new file mode 100644 index 00000000000..f0f84309b3e --- /dev/null +++ b/interop-test-files/syntax/atidentifier_syntax_invalid.txt @@ -0,0 +1,28 @@ + +# invalid handles +did:thing.test +did:thing +john-.test +john.0 +john.- +xn--bcher-.tld +john..test +jo_hn.test + +# invalid DIDs +did +didmethodval +method:did:val +did:method: +didmethod:val +did:methodval) +:did:method:val +did:method:val: +did:method:val% +DID:method:val + +# other invalid stuff +email@example.com +@handle@example.com +@handle +blah diff --git a/interop-test-files/syntax/atidentifier_syntax_valid.txt b/interop-test-files/syntax/atidentifier_syntax_valid.txt new file mode 100644 index 00000000000..cc4a42b0fa7 --- /dev/null +++ b/interop-test-files/syntax/atidentifier_syntax_valid.txt @@ -0,0 +1,15 @@ + +# allows valid handles +XX.LCS.MIT.EDU +john.test +jan.test +a234567890123456789.test +john2.test +john-john.test + +# allows valid DIDs +did:method:val +did:method:VAL +did:method:val123 +did:method:123 +did:method:val-two diff --git a/interop-test-files/syntax/aturi_syntax_invalid.txt b/interop-test-files/syntax/aturi_syntax_invalid.txt new file mode 100644 index 00000000000..2ac2eadadb3 --- /dev/null +++ b/interop-test-files/syntax/aturi_syntax_invalid.txt @@ -0,0 +1,89 @@ + +# enforces spec basics +a://did:plc:asdf123 +at//did:plc:asdf123 +at:/a/did:plc:asdf123 +at:/did:plc:asdf123 +AT://did:plc:asdf123 +http://did:plc:asdf123 +://did:plc:asdf123 +at:did:plc:asdf123 +at:/did:plc:asdf123 +at:///did:plc:asdf123 +at://:/did:plc:asdf123 +at:/ /did:plc:asdf123 +at://did:plc:asdf123 +at://did:plc:asdf123/ + at://did:plc:asdf123 +at://did:plc:asdf123/com.atproto.feed.post +at://did:plc:asdf123/com.atproto.feed.post# +at://did:plc:asdf123/com.atproto.feed.post#/ +at://did:plc:asdf123/com.atproto.feed.post#/frag +at://did:plc:asdf123/com.atproto.feed.post#fr ag +//did:plc:asdf123 +at://name +at://name.0 +at://diD:plc:asdf123 +at://did:plc:asdf123/com.atproto.feed.p@st +at://did:plc:asdf123/com.atproto.feed.p$st +at://did:plc:asdf123/com.atproto.feed.p%st +at://did:plc:asdf123/com.atproto.feed.p&st +at://did:plc:asdf123/com.atproto.feed.p()t +at://did:plc:asdf123/com.atproto.feed_post +at://did:plc:asdf123/-com.atproto.feed.post +at://did:plc:asdf@123/com.atproto.feed.post +at://DID:plc:asdf123 +at://user.bsky.123 +at://bsky +at://did:plc: +at://did:plc: +at://frag + +# too long: 'at://did:plc:asdf123/com.atproto.feed.post/' + 'o'.repeat(8200) +at://did:plc:asdf123/com.atproto.feed.post/oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo + +# has specified behavior on edge cases +at://user.bsky.social// +at://user.bsky.social//com.atproto.feed.post +at://user.bsky.social/com.atproto.feed.post// +at://did:plc:asdf123/com.atproto.feed.post/asdf123/more/more', +at://did:plc:asdf123/short/stuff +at://did:plc:asdf123/12345 + +# enforces no trailing slashes +at://did:plc:asdf123/ +at://user.bsky.social/ +at://did:plc:asdf123/com.atproto.feed.post/ +at://did:plc:asdf123/com.atproto.feed.post/record/ +at://did:plc:asdf123/com.atproto.feed.post/record/#/frag + +# enforces strict paths +at://did:plc:asdf123/com.atproto.feed.post/asdf123/asdf + +# is very permissive about fragments +at://did:plc:asdf123# +at://did:plc:asdf123## +#at://did:plc:asdf123 +at://did:plc:asdf123#/asdf#/asdf + +# new less permissive about record keys for Lexicon use (with recordkey more specified) +at://did:plc:asdf123/com.atproto.feed.post/%23 +at://did:plc:asdf123/com.atproto.feed.post/$@!*)(:,;~.sdf123 +at://did:plc:asdf123/com.atproto.feed.post/~'sdf123") +at://did:plc:asdf123/com.atproto.feed.post/$ +at://did:plc:asdf123/com.atproto.feed.post/@ +at://did:plc:asdf123/com.atproto.feed.post/! +at://did:plc:asdf123/com.atproto.feed.post/* +at://did:plc:asdf123/com.atproto.feed.post/( +at://did:plc:asdf123/com.atproto.feed.post/, +at://did:plc:asdf123/com.atproto.feed.post/; +at://did:plc:asdf123/com.atproto.feed.post/abc%30123 +at://did:plc:asdf123/com.atproto.feed.post/%30 +at://did:plc:asdf123/com.atproto.feed.post/%3 +at://did:plc:asdf123/com.atproto.feed.post/% +at://did:plc:asdf123/com.atproto.feed.post/%zz +at://did:plc:asdf123/com.atproto.feed.post/%%% + +# disallow dot / double-dot +at://did:plc:asdf123/com.atproto.feed.post/. +at://did:plc:asdf123/com.atproto.feed.post/.. diff --git a/interop-test-files/syntax/aturi_syntax_valid.txt b/interop-test-files/syntax/aturi_syntax_valid.txt new file mode 100644 index 00000000000..2552a964ce0 --- /dev/null +++ b/interop-test-files/syntax/aturi_syntax_valid.txt @@ -0,0 +1,26 @@ + +# enforces spec basics +at://did:plc:asdf123 +at://user.bsky.social +at://did:plc:asdf123/com.atproto.feed.post +at://did:plc:asdf123/com.atproto.feed.post/record + +# very long: 'at://did:plc:asdf123/com.atproto.feed.post/' + 'o'.repeat(512) +at://did:plc:asdf123/com.atproto.feed.post/oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo + +# enforces no trailing slashes +at://did:plc:asdf123 +at://user.bsky.social +at://did:plc:asdf123/com.atproto.feed.post +at://did:plc:asdf123/com.atproto.feed.post/record + +# enforces strict paths +at://did:plc:asdf123/com.atproto.feed.post/asdf123 + +# is very permissive about record keys +at://did:plc:asdf123/com.atproto.feed.post/asdf123 +at://did:plc:asdf123/com.atproto.feed.post/a + +at://did:plc:asdf123/com.atproto.feed.post/asdf-123 +at://did:abc:123 +at://did:abc:123/io.nsid.someFunc/record-key diff --git a/interop-test-files/syntax/did_syntax_invalid.txt b/interop-test-files/syntax/did_syntax_invalid.txt new file mode 100644 index 00000000000..9e724b3d7b5 --- /dev/null +++ b/interop-test-files/syntax/did_syntax_invalid.txt @@ -0,0 +1,19 @@ +did +didmethodval +method:did:val +did:method: +didmethod:val +did:methodval) +:did:method:val +did.method.val +did:method:val: +did:method:val% +DID:method:val +did:METHOD:val +did:m123:val +did:method:val/two +did:method:val?two +did:method:val#two +did:method:val% +did:method:vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + diff --git a/interop-test-files/syntax/did_syntax_valid.txt b/interop-test-files/syntax/did_syntax_valid.txt new file mode 100644 index 00000000000..5aa6c9e7e3b --- /dev/null +++ b/interop-test-files/syntax/did_syntax_valid.txt @@ -0,0 +1,26 @@ +did:method:val +did:method:VAL +did:method:val123 +did:method:123 +did:method:val-two +did:method:val_two +did:method:val.two +did:method:val:two +did:method:val%BB +did:method:vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv +did:m:v +did:method::::val +did:method:- +did:method:-:_:.:%ab +did:method:. +did:method:_ +did:method::. + +# allows some real DID values +did:onion:2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid +did:example:123456789abcdefghi +did:plc:7iza6de2dwap2sbkpav7c6c6 +did:web:example.com +did:web:localhost%3A1234 +did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N +did:ethr:0xb9c5714089478a327f09197987f16f9e5d936e8a diff --git a/interop-test-files/syntax/handle_syntax_invalid.txt b/interop-test-files/syntax/handle_syntax_invalid.txt new file mode 100644 index 00000000000..49275a390bf --- /dev/null +++ b/interop-test-files/syntax/handle_syntax_invalid.txt @@ -0,0 +1,61 @@ +# throws on invalid handles +did:thing.test +did:thing +john-.test +john.0 +john.- +xn--bcher-.tld +john..test +jo_hn.test +-john.test +.john.test +jo!hn.test +jo%hn.test +jo&hn.test +jo@hn.test +jo*hn.test +jo|hn.test +jo:hn.test +jo/hn.test +john💩.test +bücher.test +john .test +john.test. +john +john. +.john +john.test. +.john.test + john.test +john.test +joh-.test +john.-est +john.tes- + +# max over all handle: 'shoooort' + '.loooooooooooooooooooooooooong'.repeat(9) + '.test' +shoooort.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.test + +# max segment: 'short.' + 'o'.repeat(64) + '.test' +short.oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.test + +# throws on "dotless" TLD handles +org +ai +gg +io + +# correctly validates corner cases (modern vs. old RFCs) +cn.8 +thing.0aa +thing.0aa + +# does not allow IP addresses as handles +127.0.0.1 +192.168.0.142 +fe80::7325:8a97:c100:94b +2600:3c03::f03c:9100:feb0:af1f + +# examples from stackoverflow +-notvalid.at-all +-thing.com +www.masełkowski.pl.com diff --git a/interop-test-files/syntax/handle_syntax_valid.txt b/interop-test-files/syntax/handle_syntax_valid.txt new file mode 100644 index 00000000000..a23a9213839 --- /dev/null +++ b/interop-test-files/syntax/handle_syntax_valid.txt @@ -0,0 +1,90 @@ +# allows valid handles +A.ISI.EDU +XX.LCS.MIT.EDU +SRI-NIC.ARPA +john.test +jan.test +a234567890123456789.test +john2.test +john-john.test +john.bsky.app +jo.hn +a.co +a.org +joh.n +j0.h0 +jaymome-johnber123456.test +jay.mome-johnber123456.test +john.test.bsky.app + +# max over all handle: 'shoooort' + '.loooooooooooooooooooooooooong'.repeat(8) + '.test' +shoooort.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.test + +# max segment: 'short.' + 'o'.repeat(63) + '.test' +short.ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.test + +# NOTE: this probably isn't ever going to be a real domain, but my read of the RFC is that it would be possible +john.t + +# allows .local and .arpa handles (proto-level) +laptop.local +laptop.arpa + +# allows punycode handles +# 💩.test +xn--ls8h.test +# bücher.tld +xn--bcher-kva.tld +xn--3jk.com +xn--w3d.com +xn--vqb.com +xn--ppd.com +xn--cs9a.com +xn--8r9a.com +xn--cfd.com +xn--5jk.com +xn--2lb.com + +# allows onion (Tor) handles +expyuzz4wqqyqhjn.onion +friend.expyuzz4wqqyqhjn.onion +g2zyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion +friend.g2zyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion +friend.g2zyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion +2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion +friend.2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion + +# correctly validates corner cases (modern vs. old RFCs) +12345.test +8.cn +4chan.org +4chan.o-g +blah.4chan.org +thing.a01 +120.0.0.1.com +0john.test +9sta--ck.com +99stack.com +0ohn.test +john.t--t +thing.0aa.thing + +# examples from stackoverflow +stack.com +sta-ck.com +sta---ck.com +sta--ck9.com +stack99.com +sta99ck.com +google.com.uk +google.co.in +google.com +maselkowski.pl +m.maselkowski.pl +xn--masekowski-d0b.pl +xn--fiqa61au8b7zsevnm8ak20mc4a87e.xn--fiqs8s +xn--stackoverflow.com +stackoverflow.xn--com +stackoverflow.co.uk +xn--masekowski-d0b.pl +xn--fiqa61au8b7zsevnm8ak20mc4a87e.xn--fiqs8s diff --git a/interop-test-files/syntax/nsid_syntax_invalid.txt b/interop-test-files/syntax/nsid_syntax_invalid.txt new file mode 100644 index 00000000000..bc0bd2fdf86 --- /dev/null +++ b/interop-test-files/syntax/nsid_syntax_invalid.txt @@ -0,0 +1,32 @@ +# length checks +com.oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.foo +com.example.oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo +com.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.foo + +# invliad examples +com.example.foo.* +com.example.foo.blah* +com.example.foo.*blah +com.example.f00 +com.exa💩ple.thing +a-0.b-1.c-3 +a-0.b-1.c-o +a0.b1.c3 +1.0.0.127.record +0two.example.foo +example.com +com.example +a. +.one.two.three +one.two.three +one.two..three +one .two.three + one.two.three +com.exa💩ple.thing +com.atproto.feed.p@st +com.atproto.feed.p_st +com.atproto.feed.p*st +com.atproto.feed.po#t +com.atproto.feed.p!ot +com.example-.foo + diff --git a/interop-test-files/syntax/nsid_syntax_valid.txt b/interop-test-files/syntax/nsid_syntax_valid.txt new file mode 100644 index 00000000000..54ef351f077 --- /dev/null +++ b/interop-test-files/syntax/nsid_syntax_valid.txt @@ -0,0 +1,29 @@ +# length checks +com.ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.foo +com.example.ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo +com.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.middle.foo + +# valid examples +com.example.fooBar +net.users.bob.ping +a.b.c +m.xn--masekowski-d0b.pl +one.two.three +one.two.three.four-and.FiVe +one.2.three +a-0.b-1.c +a0.b1.cc +cn.8.lex.stuff +test.12345.record +a01.thing.record +a.0.c +xn--fiqs8s.xn--fiqa61au8b7zsevnm8ak20mc4a87e.record.two + +# allows onion (Tor) NSIDs +onion.expyuzz4wqqyqhjn.spec.getThing +onion.g2zyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.lex.deleteThing + +# allows starting-with-numeric segments (same as domains) +org.4chan.lex.getThing +cn.8.lex.stuff +onion.2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.lex.deleteThing diff --git a/interop-test-files/syntax/recordkey_syntax_invalid.txt b/interop-test-files/syntax/recordkey_syntax_invalid.txt new file mode 100644 index 00000000000..1da3d1e7dbc --- /dev/null +++ b/interop-test-files/syntax/recordkey_syntax_invalid.txt @@ -0,0 +1,14 @@ +# specs +literal:self +alpha/beta +. +.. +#extra +@handle +any space +any+space +number[3] +number(3) +"quote" +pre:fix +dHJ1ZQ== diff --git a/interop-test-files/syntax/recordkey_syntax_valid.txt b/interop-test-files/syntax/recordkey_syntax_valid.txt new file mode 100644 index 00000000000..8d77d04d2b7 --- /dev/null +++ b/interop-test-files/syntax/recordkey_syntax_valid.txt @@ -0,0 +1,8 @@ +# specs +self +example.com +~1.2-3_ +dHJ1ZQ + +# very long: 'o'.repeat(512) +oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo diff --git a/jest.config.base.js b/jest.config.base.js index 31a393e14f2..2b914a54e51 100644 --- a/jest.config.base.js +++ b/jest.config.base.js @@ -1,5 +1,5 @@ // Jest doesn't like ES modules, so we need to transpile them -// For each one, add them to this list, add them to +// For each one, add them to this list, add them to // "workspaces.nohoist" in the root package.json, and // make sure that a babel.config.js is in the package root const esModules = ['get-port', 'node-fetch'].join('|') @@ -8,12 +8,12 @@ const esModules = ['get-port', 'node-fetch'].join('|') module.exports = { roots: ['/src', '/tests'], transform: { - "^.+\\.(t|j)s?$": "@swc/jest", + '^.+\\.(t|j)s?$': '@swc/jest', }, transformIgnorePatterns: [`/node_modules/(?!${esModules})`], testRegex: '(/tests/.*.(test|spec)).(jsx?|tsx?)$', moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], - setupFiles: ["/../../test-setup.ts"], + setupFiles: ['/../../test-setup.ts'], verbose: true, - testTimeout: 60000 + testTimeout: 60000, } diff --git a/lerna.json b/lerna.json deleted file mode 100644 index 2e245ac66a8..00000000000 --- a/lerna.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "packages": ["packages/*"], - "npmClient": "yarn", - "useWorkspaces": true, - "version": "0.0.1" -} \ No newline at end of file diff --git a/lexicons/app/bsky/actor/defs.json b/lexicons/app/bsky/actor/defs.json index cf2cafe06f0..6b6017d049d 100644 --- a/lexicons/app/bsky/actor/defs.json +++ b/lexicons/app/bsky/actor/defs.json @@ -7,18 +7,18 @@ "type": "object", "required": ["did", "handle"], "properties": { - "did": {"type": "string", "format": "did"}, - "handle": {"type": "string", "format": "handle"}, + "did": { "type": "string", "format": "did" }, + "handle": { "type": "string", "format": "handle" }, "displayName": { "type": "string", "maxGraphemes": 64, "maxLength": 640 }, "avatar": { "type": "string" }, - "viewer": {"type": "ref", "ref": "#viewerState"}, + "viewer": { "type": "ref", "ref": "#viewerState" }, "labels": { "type": "array", - "items": {"type": "ref", "ref": "com.atproto.label.defs#label"} + "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } } } }, @@ -26,8 +26,8 @@ "type": "object", "required": ["did", "handle"], "properties": { - "did": {"type": "string", "format":"did"}, - "handle": {"type": "string", "format":"handle"}, + "did": { "type": "string", "format": "did" }, + "handle": { "type": "string", "format": "handle" }, "displayName": { "type": "string", "maxGraphemes": 64, @@ -39,11 +39,11 @@ "maxLength": 2560 }, "avatar": { "type": "string" }, - "indexedAt": {"type": "string", "format": "datetime"}, - "viewer": {"type": "ref", "ref": "#viewerState"}, + "indexedAt": { "type": "string", "format": "datetime" }, + "viewer": { "type": "ref", "ref": "#viewerState" }, "labels": { "type": "array", - "items": {"type": "ref", "ref": "com.atproto.label.defs#label"} + "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } } } }, @@ -51,8 +51,8 @@ "type": "object", "required": ["did", "handle"], "properties": { - "did": {"type": "string", "format": "did"}, - "handle": {"type": "string", "format": "handle"}, + "did": { "type": "string", "format": "did" }, + "handle": { "type": "string", "format": "handle" }, "displayName": { "type": "string", "maxGraphemes": 64, @@ -65,26 +65,29 @@ }, "avatar": { "type": "string" }, "banner": { "type": "string" }, - "followersCount": {"type": "integer"}, - "followsCount": {"type": "integer"}, - "postsCount": {"type": "integer"}, - "indexedAt": {"type": "string", "format": "datetime"}, - "viewer": {"type": "ref", "ref": "#viewerState"}, + "followersCount": { "type": "integer" }, + "followsCount": { "type": "integer" }, + "postsCount": { "type": "integer" }, + "indexedAt": { "type": "string", "format": "datetime" }, + "viewer": { "type": "ref", "ref": "#viewerState" }, "labels": { "type": "array", - "items": {"type": "ref", "ref": "com.atproto.label.defs#label"} + "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } } } }, "viewerState": { "type": "object", "properties": { - "muted": {"type": "boolean"}, - "mutedByList": {"type": "ref", "ref": "app.bsky.graph.defs#listViewBasic"}, - "blockedBy": {"type": "boolean"}, - "blocking": {"type": "string", "format": "at-uri"}, - "following": {"type": "string", "format": "at-uri"}, - "followedBy": {"type": "string", "format": "at-uri"} + "muted": { "type": "boolean" }, + "mutedByList": { + "type": "ref", + "ref": "app.bsky.graph.defs#listViewBasic" + }, + "blockedBy": { "type": "boolean" }, + "blocking": { "type": "string", "format": "at-uri" }, + "following": { "type": "string", "format": "at-uri" }, + "followedBy": { "type": "string", "format": "at-uri" } } }, "preferences": { @@ -94,7 +97,8 @@ "refs": [ "#adultContentPref", "#contentLabelPref", - "#savedFeedsPref" + "#savedFeedsPref", + "#personalDetailsPref" ] } }, @@ -102,15 +106,18 @@ "type": "object", "required": ["enabled"], "properties": { - "enabled": {"type": "boolean", "default": false} + "enabled": { "type": "boolean", "default": false } } }, "contentLabelPref": { "type": "object", "required": ["label", "visibility"], "properties": { - "label": {"type": "string"}, - "visibility": {"type": "string", "knownValues": ["show", "warn", "hide"]} + "label": { "type": "string" }, + "visibility": { + "type": "string", + "knownValues": ["show", "warn", "hide"] + } } }, "savedFeedsPref": { @@ -132,6 +139,16 @@ } } } + }, + "personalDetailsPref": { + "type": "object", + "properties": { + "birthDate": { + "type": "string", + "format": "datetime", + "description": "The birth date of the owner of the account." + } + } } } } diff --git a/lexicons/app/bsky/actor/getPreferences.json b/lexicons/app/bsky/actor/getPreferences.json index c6285cffb53..cbd6b60bd6a 100644 --- a/lexicons/app/bsky/actor/getPreferences.json +++ b/lexicons/app/bsky/actor/getPreferences.json @@ -7,8 +7,7 @@ "description": "Get private preferences attached to the account.", "parameters": { "type": "params", - "properties": { - } + "properties": {} }, "output": { "encoding": "application/json", diff --git a/lexicons/app/bsky/actor/getProfile.json b/lexicons/app/bsky/actor/getProfile.json index d5f44b62284..d04ed0e159b 100644 --- a/lexicons/app/bsky/actor/getProfile.json +++ b/lexicons/app/bsky/actor/getProfile.json @@ -8,12 +8,15 @@ "type": "params", "required": ["actor"], "properties": { - "actor": {"type": "string", "format": "at-identifier"} + "actor": { "type": "string", "format": "at-identifier" } } }, "output": { "encoding": "application/json", - "schema": {"type": "ref", "ref": "app.bsky.actor.defs#profileViewDetailed"} + "schema": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileViewDetailed" + } } } } diff --git a/lexicons/app/bsky/actor/getProfiles.json b/lexicons/app/bsky/actor/getProfiles.json index cf4aac6fccf..ded213b6671 100644 --- a/lexicons/app/bsky/actor/getProfiles.json +++ b/lexicons/app/bsky/actor/getProfiles.json @@ -10,7 +10,7 @@ "properties": { "actors": { "type": "array", - "items": {"type": "string", "format": "at-identifier"}, + "items": { "type": "string", "format": "at-identifier" }, "maxLength": 25 } } @@ -23,7 +23,10 @@ "properties": { "profiles": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.actor.defs#profileViewDetailed"} + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileViewDetailed" + } } } } diff --git a/lexicons/app/bsky/actor/getSuggestions.json b/lexicons/app/bsky/actor/getSuggestions.json index de16ff7e2ac..38c30c2c9a6 100644 --- a/lexicons/app/bsky/actor/getSuggestions.json +++ b/lexicons/app/bsky/actor/getSuggestions.json @@ -8,8 +8,13 @@ "parameters": { "type": "params", "properties": { - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"} + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } } }, "output": { @@ -18,10 +23,13 @@ "type": "object", "required": ["actors"], "properties": { - "cursor": {"type": "string"}, + "cursor": { "type": "string" }, "actors": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.actor.defs#profileView"} + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileView" + } } } } diff --git a/lexicons/app/bsky/actor/profile.json b/lexicons/app/bsky/actor/profile.json index 02aa13d50eb..e0f476deb73 100644 --- a/lexicons/app/bsky/actor/profile.json +++ b/lexicons/app/bsky/actor/profile.json @@ -27,6 +27,10 @@ "type": "blob", "accept": ["image/png", "image/jpeg"], "maxSize": 1000000 + }, + "labels": { + "type": "union", + "refs": ["com.atproto.label.defs#selfLabels"] } } } diff --git a/lexicons/app/bsky/actor/searchActors.json b/lexicons/app/bsky/actor/searchActors.json index 7ad71530135..dc76ad8fc39 100644 --- a/lexicons/app/bsky/actor/searchActors.json +++ b/lexicons/app/bsky/actor/searchActors.json @@ -8,9 +8,14 @@ "parameters": { "type": "params", "properties": { - "term": {"type": "string"}, - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"} + "term": { "type": "string" }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } } }, "output": { @@ -19,10 +24,13 @@ "type": "object", "required": ["actors"], "properties": { - "cursor": {"type": "string"}, + "cursor": { "type": "string" }, "actors": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.actor.defs#profileView"} + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileView" + } } } } diff --git a/lexicons/app/bsky/actor/searchActorsTypeahead.json b/lexicons/app/bsky/actor/searchActorsTypeahead.json index 3402b230fda..7065f3d7117 100644 --- a/lexicons/app/bsky/actor/searchActorsTypeahead.json +++ b/lexicons/app/bsky/actor/searchActorsTypeahead.json @@ -8,8 +8,13 @@ "parameters": { "type": "params", "properties": { - "term": {"type": "string"}, - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50} + "term": { "type": "string" }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + } } }, "output": { @@ -20,7 +25,10 @@ "properties": { "actors": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.actor.defs#profileViewBasic"} + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileViewBasic" + } } } } diff --git a/lexicons/app/bsky/embed/external.json b/lexicons/app/bsky/embed/external.json index 02b96b387a2..694787eb507 100644 --- a/lexicons/app/bsky/embed/external.json +++ b/lexicons/app/bsky/embed/external.json @@ -15,11 +15,11 @@ }, "external": { "type": "object", - "required": ["uri", "title", "description"], + "required": ["uri", "title", "description"], "properties": { - "uri": {"type": "string", "format": "uri"}, - "title": {"type": "string"}, - "description": {"type": "string"}, + "uri": { "type": "string", "format": "uri" }, + "title": { "type": "string" }, + "description": { "type": "string" }, "thumb": { "type": "blob", "accept": ["image/*"], @@ -39,12 +39,12 @@ }, "viewExternal": { "type": "object", - "required": ["uri", "title", "description"], + "required": ["uri", "title", "description"], "properties": { - "uri": {"type": "string", "format": "uri"}, - "title": {"type": "string"}, - "description": {"type": "string"}, - "thumb": {"type": "string"} + "uri": { "type": "string", "format": "uri" }, + "title": { "type": "string" }, + "description": { "type": "string" }, + "thumb": { "type": "string" } } } } diff --git a/lexicons/app/bsky/embed/images.json b/lexicons/app/bsky/embed/images.json index 148fd2493ae..48975a60ae2 100644 --- a/lexicons/app/bsky/embed/images.json +++ b/lexicons/app/bsky/embed/images.json @@ -9,7 +9,7 @@ "properties": { "images": { "type": "array", - "items": {"type": "ref", "ref": "#image"}, + "items": { "type": "ref", "ref": "#image" }, "maxLength": 4 } } @@ -23,7 +23,17 @@ "accept": ["image/*"], "maxSize": 1000000 }, - "alt": {"type": "string"} + "alt": { "type": "string" }, + "aspectRatio": { "type": "ref", "ref": "#aspectRatio" } + } + }, + "aspectRatio": { + "type": "object", + "description": "width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit.", + "required": ["width", "height"], + "properties": { + "width": { "type": "integer", "minimum": 1 }, + "height": { "type": "integer", "minimum": 1 } } }, "view": { @@ -32,7 +42,7 @@ "properties": { "images": { "type": "array", - "items": {"type": "ref", "ref": "#viewImage"}, + "items": { "type": "ref", "ref": "#viewImage" }, "maxLength": 4 } } @@ -41,9 +51,10 @@ "type": "object", "required": ["thumb", "fullsize", "alt"], "properties": { - "thumb": {"type": "string"}, - "fullsize": {"type": "string"}, - "alt": {"type": "string"} + "thumb": { "type": "string" }, + "fullsize": { "type": "string" }, + "alt": { "type": "string" }, + "aspectRatio": { "type": "ref", "ref": "#aspectRatio" } } } } diff --git a/lexicons/app/bsky/embed/record.json b/lexicons/app/bsky/embed/record.json index 0b55be0c9c2..0d7cb830ba8 100644 --- a/lexicons/app/bsky/embed/record.json +++ b/lexicons/app/bsky/embed/record.json @@ -7,12 +7,12 @@ "type": "object", "required": ["record"], "properties": { - "record": {"type": "ref", "ref": "com.atproto.repo.strongRef"} + "record": { "type": "ref", "ref": "com.atproto.repo.strongRef" } } }, "view": { "type": "object", - "required": ["record"], + "required": ["record"], "properties": { "record": { "type": "union", @@ -28,15 +28,18 @@ }, "viewRecord": { "type": "object", - "required": ["uri", "cid", "author", "value", "indexedAt"], + "required": ["uri", "cid", "author", "value", "indexedAt"], "properties": { - "uri": {"type": "string", "format": "at-uri"}, - "cid": {"type": "string", "format": "cid"}, - "author": {"type": "ref", "ref": "app.bsky.actor.defs#profileViewBasic"}, - "value": {"type": "unknown"}, + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "author": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileViewBasic" + }, + "value": { "type": "unknown" }, "labels": { "type": "array", - "items": {"type": "ref", "ref": "com.atproto.label.defs#label"} + "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } }, "embeds": { "type": "array", @@ -50,21 +53,24 @@ ] } }, - "indexedAt": {"type": "string", "format": "datetime"} + "indexedAt": { "type": "string", "format": "datetime" } } }, "viewNotFound": { "type": "object", - "required": ["uri"], + "required": ["uri", "notFound"], "properties": { - "uri": {"type": "string", "format": "at-uri"} + "uri": { "type": "string", "format": "at-uri" }, + "notFound": { "type": "boolean", "const": true } } }, "viewBlocked": { "type": "object", - "required": ["uri"], + "required": ["uri", "blocked", "author"], "properties": { - "uri": {"type": "string", "format": "at-uri"} + "uri": { "type": "string", "format": "at-uri" }, + "blocked": { "type": "boolean", "const": true }, + "author": { "type": "ref", "ref": "app.bsky.feed.defs#blockedAuthor" } } } } diff --git a/lexicons/app/bsky/embed/recordWithMedia.json b/lexicons/app/bsky/embed/recordWithMedia.json index 1e5346a8beb..296494d9ebc 100644 --- a/lexicons/app/bsky/embed/recordWithMedia.json +++ b/lexicons/app/bsky/embed/recordWithMedia.json @@ -13,10 +13,7 @@ }, "media": { "type": "union", - "refs": [ - "app.bsky.embed.images", - "app.bsky.embed.external" - ] + "refs": ["app.bsky.embed.images", "app.bsky.embed.external"] } } }, @@ -30,10 +27,7 @@ }, "media": { "type": "union", - "refs": [ - "app.bsky.embed.images#view", - "app.bsky.embed.external#view" - ] + "refs": ["app.bsky.embed.images#view", "app.bsky.embed.external#view"] } } } diff --git a/lexicons/app/bsky/feed/defs.json b/lexicons/app/bsky/feed/defs.json index a4fe08aec8b..7a9fcf5e68f 100644 --- a/lexicons/app/bsky/feed/defs.json +++ b/lexicons/app/bsky/feed/defs.json @@ -6,10 +6,13 @@ "type": "object", "required": ["uri", "cid", "author", "record", "indexedAt"], "properties": { - "uri": {"type": "string", "format": "at-uri"}, - "cid": {"type": "string", "format": "cid"}, - "author": {"type": "ref", "ref": "app.bsky.actor.defs#profileViewBasic"}, - "record": {"type": "unknown"}, + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "author": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileViewBasic" + }, + "record": { "type": "unknown" }, "embed": { "type": "union", "refs": [ @@ -19,58 +22,70 @@ "app.bsky.embed.recordWithMedia#view" ] }, - "replyCount": {"type": "integer"}, - "repostCount": {"type": "integer"}, - "likeCount": {"type": "integer"}, - "indexedAt": {"type": "string", "format": "datetime"}, - "viewer": {"type": "ref", "ref": "#viewerState"}, + "replyCount": { "type": "integer" }, + "repostCount": { "type": "integer" }, + "likeCount": { "type": "integer" }, + "indexedAt": { "type": "string", "format": "datetime" }, + "viewer": { "type": "ref", "ref": "#viewerState" }, "labels": { "type": "array", - "items": {"type": "ref", "ref": "com.atproto.label.defs#label"} + "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } } } }, "viewerState": { "type": "object", "properties": { - "repost": {"type": "string", "format": "at-uri"}, - "like": {"type": "string", "format": "at-uri"} + "repost": { "type": "string", "format": "at-uri" }, + "like": { "type": "string", "format": "at-uri" } } }, "feedViewPost": { "type": "object", "required": ["post"], "properties": { - "post": {"type": "ref", "ref": "#postView"}, - "reply": {"type": "ref", "ref": "#replyRef"}, - "reason": {"type": "union", "refs": ["#reasonRepost"]} + "post": { "type": "ref", "ref": "#postView" }, + "reply": { "type": "ref", "ref": "#replyRef" }, + "reason": { "type": "union", "refs": ["#reasonRepost"] } } }, "replyRef": { "type": "object", "required": ["root", "parent"], "properties": { - "root": {"type": "union", "refs": ["#postView", "#notFoundPost", "#blockedPost"]}, - "parent": {"type": "union", "refs": ["#postView", "#notFoundPost", "#blockedPost"]} + "root": { + "type": "union", + "refs": ["#postView", "#notFoundPost", "#blockedPost"] + }, + "parent": { + "type": "union", + "refs": ["#postView", "#notFoundPost", "#blockedPost"] + } } }, "reasonRepost": { "type": "object", "required": ["by", "indexedAt"], "properties": { - "by": {"type": "ref", "ref": "app.bsky.actor.defs#profileViewBasic"}, - "indexedAt": {"type": "string", "format": "datetime"} + "by": { "type": "ref", "ref": "app.bsky.actor.defs#profileViewBasic" }, + "indexedAt": { "type": "string", "format": "datetime" } } }, "threadViewPost": { "type": "object", "required": ["post"], "properties": { - "post": {"type": "ref", "ref": "#postView"}, - "parent": {"type": "union", "refs": ["#threadViewPost", "#notFoundPost", "#blockedPost"]}, + "post": { "type": "ref", "ref": "#postView" }, + "parent": { + "type": "union", + "refs": ["#threadViewPost", "#notFoundPost", "#blockedPost"] + }, "replies": { "type": "array", - "items": {"type": "union", "refs": ["#threadViewPost", "#notFoundPost", "#blockedPost"]} + "items": { + "type": "union", + "refs": ["#threadViewPost", "#notFoundPost", "#blockedPost"] + } } } }, @@ -78,57 +93,70 @@ "type": "object", "required": ["uri", "notFound"], "properties": { - "uri": {"type": "string", "format": "at-uri"}, - "notFound": {"type": "boolean", "const": true} + "uri": { "type": "string", "format": "at-uri" }, + "notFound": { "type": "boolean", "const": true } } }, "blockedPost": { "type": "object", - "required": ["uri", "blocked"], + "required": ["uri", "blocked", "author"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "blocked": { "type": "boolean", "const": true }, + "author": { "type": "ref", "ref": "#blockedAuthor" } + } + }, + "blockedAuthor": { + "type": "object", + "required": ["did"], "properties": { - "uri": {"type": "string", "format": "at-uri"}, - "blocked": {"type": "boolean", "const": true} + "did": { "type": "string", "format": "did" }, + "viewer": { "type": "ref", "ref": "app.bsky.actor.defs#viewerState" } } }, "generatorView": { "type": "object", "required": ["uri", "cid", "did", "creator", "displayName", "indexedAt"], "properties": { - "uri": {"type": "string", "format": "at-uri"}, - "cid": {"type": "string", "format": "cid"}, - "did": {"type": "string", "format": "did"}, - "creator": {"type": "ref", "ref": "app.bsky.actor.defs#profileView"}, - "displayName": {"type": "string"}, - "description": {"type": "string", "maxGraphemes": 300, "maxLength": 3000}, + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "did": { "type": "string", "format": "did" }, + "creator": { "type": "ref", "ref": "app.bsky.actor.defs#profileView" }, + "displayName": { "type": "string" }, + "description": { + "type": "string", + "maxGraphemes": 300, + "maxLength": 3000 + }, "descriptionFacets": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.richtext.facet"} + "items": { "type": "ref", "ref": "app.bsky.richtext.facet" } }, - "avatar": {"type": "string"}, - "likeCount": {"type": "integer", "minimum": 0 }, - "viewer": {"type": "ref", "ref": "#generatorViewerState"}, - "indexedAt": {"type": "string", "format": "datetime"} + "avatar": { "type": "string" }, + "likeCount": { "type": "integer", "minimum": 0 }, + "viewer": { "type": "ref", "ref": "#generatorViewerState" }, + "indexedAt": { "type": "string", "format": "datetime" } } }, "generatorViewerState": { "type": "object", "properties": { - "like": {"type": "string", "format": "at-uri"} + "like": { "type": "string", "format": "at-uri" } } }, "skeletonFeedPost": { "type": "object", "required": ["post"], "properties": { - "post": {"type": "string", "format": "at-uri"}, - "reason": {"type": "union", "refs": ["#skeletonReasonRepost"]} + "post": { "type": "string", "format": "at-uri" }, + "reason": { "type": "union", "refs": ["#skeletonReasonRepost"] } } }, "skeletonReasonRepost": { "type": "object", "required": ["repost"], "properties": { - "repost": {"type": "string", "format": "at-uri"} + "repost": { "type": "string", "format": "at-uri" } } } } diff --git a/lexicons/app/bsky/feed/describeFeedGenerator.json b/lexicons/app/bsky/feed/describeFeedGenerator.json index 265b72c8bb2..69c143e6016 100644 --- a/lexicons/app/bsky/feed/describeFeedGenerator.json +++ b/lexicons/app/bsky/feed/describeFeedGenerator.json @@ -11,12 +11,12 @@ "type": "object", "required": ["did", "feeds"], "properties": { - "did": {"type": "string", "format": "did"}, + "did": { "type": "string", "format": "did" }, "feeds": { "type": "array", - "items": {"type": "ref", "ref": "#feed"} + "items": { "type": "ref", "ref": "#feed" } }, - "links": {"type": "ref", "ref": "#links"} + "links": { "type": "ref", "ref": "#links" } } } } @@ -25,14 +25,14 @@ "type": "object", "required": ["uri"], "properties": { - "uri": {"type": "string", "format": "at-uri"} + "uri": { "type": "string", "format": "at-uri" } } }, "links": { "type": "object", "properties": { - "privacyPolicy": {"type": "string"}, - "termsOfService": {"type": "string"} + "privacyPolicy": { "type": "string" }, + "termsOfService": { "type": "string" } } } } diff --git a/lexicons/app/bsky/feed/generator.json b/lexicons/app/bsky/feed/generator.json index 558b6b7213f..e31bb4484e1 100644 --- a/lexicons/app/bsky/feed/generator.json +++ b/lexicons/app/bsky/feed/generator.json @@ -10,21 +10,33 @@ "type": "object", "required": ["did", "displayName", "createdAt"], "properties": { - "did": {"type": "string", "format": "did"}, - "displayName": {"type": "string", "maxGraphemes": 24, "maxLength": 240}, - "description": {"type": "string", "maxGraphemes": 300, "maxLength": 3000}, + "did": { "type": "string", "format": "did" }, + "displayName": { + "type": "string", + "maxGraphemes": 24, + "maxLength": 240 + }, + "description": { + "type": "string", + "maxGraphemes": 300, + "maxLength": 3000 + }, "descriptionFacets": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.richtext.facet"} + "items": { "type": "ref", "ref": "app.bsky.richtext.facet" } }, "avatar": { "type": "blob", "accept": ["image/png", "image/jpeg"], "maxSize": 1000000 }, - "createdAt": {"type": "string", "format": "datetime"} + "labels": { + "type": "union", + "refs": ["com.atproto.label.defs#selfLabels"] + }, + "createdAt": { "type": "string", "format": "datetime" } } } } } -} \ No newline at end of file +} diff --git a/lexicons/app/bsky/feed/getActorFeeds.json b/lexicons/app/bsky/feed/getActorFeeds.json index b47bd90e679..f34aece1609 100644 --- a/lexicons/app/bsky/feed/getActorFeeds.json +++ b/lexicons/app/bsky/feed/getActorFeeds.json @@ -9,9 +9,14 @@ "type": "params", "required": ["actor"], "properties": { - "actor": {"type": "string", "format": "at-identifier"}, - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"} + "actor": { "type": "string", "format": "at-identifier" }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } } }, "output": { @@ -20,10 +25,13 @@ "type": "object", "required": ["feeds"], "properties": { - "cursor": {"type": "string"}, + "cursor": { "type": "string" }, "feeds": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.feed.defs#generatorView"} + "items": { + "type": "ref", + "ref": "app.bsky.feed.defs#generatorView" + } } } } diff --git a/lexicons/app/bsky/feed/getActorLikes.json b/lexicons/app/bsky/feed/getActorLikes.json new file mode 100644 index 00000000000..df5e45a4295 --- /dev/null +++ b/lexicons/app/bsky/feed/getActorLikes.json @@ -0,0 +1,42 @@ +{ + "lexicon": 1, + "id": "app.bsky.feed.getActorLikes", + "defs": { + "main": { + "type": "query", + "description": "A view of the posts liked by an actor.", + "parameters": { + "type": "params", + "required": ["actor"], + "properties": { + "actor": { "type": "string", "format": "at-identifier" }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["feed"], + "properties": { + "cursor": { "type": "string" }, + "feed": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.feed.defs#feedViewPost" + } + } + } + } + }, + "errors": [{ "name": "BlockedActor" }, { "name": "BlockedByActor" }] + } + } +} diff --git a/lexicons/app/bsky/feed/getAuthorFeed.json b/lexicons/app/bsky/feed/getAuthorFeed.json index 3713cb67c63..c1edfb21330 100644 --- a/lexicons/app/bsky/feed/getAuthorFeed.json +++ b/lexicons/app/bsky/feed/getAuthorFeed.json @@ -9,9 +9,23 @@ "type": "params", "required": ["actor"], "properties": { - "actor": {"type": "string", "format": "at-identifier"}, - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"} + "actor": { "type": "string", "format": "at-identifier" }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" }, + "filter": { + "type": "string", + "knownValues": [ + "posts_with_replies", + "posts_no_replies", + "posts_with_media" + ], + "default": "posts_with_replies" + } } }, "output": { @@ -20,18 +34,18 @@ "type": "object", "required": ["feed"], "properties": { - "cursor": {"type": "string"}, + "cursor": { "type": "string" }, "feed": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.feed.defs#feedViewPost"} + "items": { + "type": "ref", + "ref": "app.bsky.feed.defs#feedViewPost" + } } } } }, - "errors": [ - {"name": "BlockedActor"}, - {"name": "BlockedByActor"} - ] + "errors": [{ "name": "BlockedActor" }, { "name": "BlockedByActor" }] } } } diff --git a/lexicons/app/bsky/feed/getFeed.json b/lexicons/app/bsky/feed/getFeed.json index 5ca96e11f55..9aaeb24c7db 100644 --- a/lexicons/app/bsky/feed/getFeed.json +++ b/lexicons/app/bsky/feed/getFeed.json @@ -9,9 +9,14 @@ "type": "params", "required": ["feed"], "properties": { - "feed": {"type": "string", "format": "at-uri"}, - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"} + "feed": { "type": "string", "format": "at-uri" }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } } }, "output": { @@ -20,17 +25,18 @@ "type": "object", "required": ["feed"], "properties": { - "cursor": {"type": "string"}, + "cursor": { "type": "string" }, "feed": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.feed.defs#feedViewPost"} + "items": { + "type": "ref", + "ref": "app.bsky.feed.defs#feedViewPost" + } } } } }, - "errors": [ - {"name": "UnknownFeed"} - ] + "errors": [{ "name": "UnknownFeed" }] } } } diff --git a/lexicons/app/bsky/feed/getFeedGenerator.json b/lexicons/app/bsky/feed/getFeedGenerator.json index 0bb588baeb0..5af09254f93 100644 --- a/lexicons/app/bsky/feed/getFeedGenerator.json +++ b/lexicons/app/bsky/feed/getFeedGenerator.json @@ -9,7 +9,7 @@ "type": "params", "required": ["feed"], "properties": { - "feed": {"type": "string", "format": "at-uri"} + "feed": { "type": "string", "format": "at-uri" } } }, "output": { @@ -18,9 +18,12 @@ "type": "object", "required": ["view", "isOnline", "isValid"], "properties": { - "view": {"type": "ref", "ref": "app.bsky.feed.defs#generatorView"}, - "isOnline": {"type": "boolean"}, - "isValid": {"type": "boolean"} + "view": { + "type": "ref", + "ref": "app.bsky.feed.defs#generatorView" + }, + "isOnline": { "type": "boolean" }, + "isValid": { "type": "boolean" } } } } diff --git a/lexicons/app/bsky/feed/getFeedGenerators.json b/lexicons/app/bsky/feed/getFeedGenerators.json index 595e4e96fcc..d0cae3b39d2 100644 --- a/lexicons/app/bsky/feed/getFeedGenerators.json +++ b/lexicons/app/bsky/feed/getFeedGenerators.json @@ -11,7 +11,7 @@ "properties": { "feeds": { "type": "array", - "items": {"type": "string", "format": "at-uri"} + "items": { "type": "string", "format": "at-uri" } } } }, @@ -23,7 +23,10 @@ "properties": { "feeds": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.feed.defs#generatorView"} + "items": { + "type": "ref", + "ref": "app.bsky.feed.defs#generatorView" + } } } } diff --git a/lexicons/app/bsky/feed/getFeedSkeleton.json b/lexicons/app/bsky/feed/getFeedSkeleton.json index 013e7bacb9c..d12b4bdc2b8 100644 --- a/lexicons/app/bsky/feed/getFeedSkeleton.json +++ b/lexicons/app/bsky/feed/getFeedSkeleton.json @@ -9,9 +9,14 @@ "type": "params", "required": ["feed"], "properties": { - "feed": {"type": "string", "format": "at-uri"}, - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"} + "feed": { "type": "string", "format": "at-uri" }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } } }, "output": { @@ -20,17 +25,18 @@ "type": "object", "required": ["feed"], "properties": { - "cursor": {"type": "string"}, + "cursor": { "type": "string" }, "feed": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.feed.defs#skeletonFeedPost"} + "items": { + "type": "ref", + "ref": "app.bsky.feed.defs#skeletonFeedPost" + } } } } }, - "errors": [ - {"name": "UnknownFeed"} - ] + "errors": [{ "name": "UnknownFeed" }] } } } diff --git a/lexicons/app/bsky/feed/getLikes.json b/lexicons/app/bsky/feed/getLikes.json index b10a75adaf1..e9b632684a8 100644 --- a/lexicons/app/bsky/feed/getLikes.json +++ b/lexicons/app/bsky/feed/getLikes.json @@ -8,10 +8,15 @@ "type": "params", "required": ["uri"], "properties": { - "uri": {"type": "string", "format": "at-uri"}, - "cid": {"type": "string", "format": "cid"}, - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"} + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } } }, "output": { @@ -20,12 +25,12 @@ "type": "object", "required": ["uri", "likes"], "properties": { - "uri": {"type": "string", "format": "at-uri"}, - "cid": {"type": "string", "format": "cid"}, - "cursor": {"type": "string"}, + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "cursor": { "type": "string" }, "likes": { "type": "array", - "items": {"type": "ref", "ref": "#like"} + "items": { "type": "ref", "ref": "#like" } } } } @@ -35,9 +40,9 @@ "type": "object", "required": ["indexedAt", "createdAt", "actor"], "properties": { - "indexedAt": {"type": "string", "format": "datetime"}, - "createdAt": {"type": "string", "format": "datetime"}, - "actor": {"type": "ref", "ref": "app.bsky.actor.defs#profileView"} + "indexedAt": { "type": "string", "format": "datetime" }, + "createdAt": { "type": "string", "format": "datetime" }, + "actor": { "type": "ref", "ref": "app.bsky.actor.defs#profileView" } } } } diff --git a/lexicons/app/bsky/feed/getPostThread.json b/lexicons/app/bsky/feed/getPostThread.json index f69a30c5f06..2d5c2e29a11 100644 --- a/lexicons/app/bsky/feed/getPostThread.json +++ b/lexicons/app/bsky/feed/getPostThread.json @@ -8,7 +8,7 @@ "type": "params", "required": ["uri"], "properties": { - "uri": {"type": "string", "format": "at-uri"}, + "uri": { "type": "string", "format": "at-uri" }, "depth": { "type": "integer", "default": 6, @@ -40,9 +40,7 @@ } } }, - "errors": [ - {"name": "NotFound"} - ] + "errors": [{ "name": "NotFound" }] } } } diff --git a/lexicons/app/bsky/feed/getPosts.json b/lexicons/app/bsky/feed/getPosts.json index 10d8d609a7c..37056417a27 100644 --- a/lexicons/app/bsky/feed/getPosts.json +++ b/lexicons/app/bsky/feed/getPosts.json @@ -11,7 +11,7 @@ "properties": { "uris": { "type": "array", - "items": {"type": "string", "format": "at-uri"}, + "items": { "type": "string", "format": "at-uri" }, "maxLength": 25 } } @@ -24,7 +24,7 @@ "properties": { "posts": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.feed.defs#postView"} + "items": { "type": "ref", "ref": "app.bsky.feed.defs#postView" } } } } diff --git a/lexicons/app/bsky/feed/getRepostedBy.json b/lexicons/app/bsky/feed/getRepostedBy.json index 4298b89666a..247ca305e67 100644 --- a/lexicons/app/bsky/feed/getRepostedBy.json +++ b/lexicons/app/bsky/feed/getRepostedBy.json @@ -8,10 +8,15 @@ "type": "params", "required": ["uri"], "properties": { - "uri": {"type": "string", "format": "at-uri"}, - "cid": {"type": "string", "format": "cid"}, - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"} + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } } }, "output": { @@ -20,12 +25,15 @@ "type": "object", "required": ["uri", "repostedBy"], "properties": { - "uri": {"type": "string", "format": "at-uri"}, - "cid": {"type": "string", "format": "cid"}, - "cursor": {"type": "string"}, + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "cursor": { "type": "string" }, "repostedBy": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.actor.defs#profileView"} + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileView" + } } } } diff --git a/lexicons/app/bsky/feed/getSuggestedFeeds.json b/lexicons/app/bsky/feed/getSuggestedFeeds.json new file mode 100644 index 00000000000..de7c4fef753 --- /dev/null +++ b/lexicons/app/bsky/feed/getSuggestedFeeds.json @@ -0,0 +1,39 @@ +{ + "lexicon": 1, + "id": "app.bsky.feed.getSuggestedFeeds", + "defs": { + "main": { + "type": "query", + "description": "Get a list of suggested feeds for the viewer.", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["feeds"], + "properties": { + "cursor": { "type": "string" }, + "feeds": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.feed.defs#generatorView" + } + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/feed/getTimeline.json b/lexicons/app/bsky/feed/getTimeline.json index e5e13c496ec..49e1ae84b37 100644 --- a/lexicons/app/bsky/feed/getTimeline.json +++ b/lexicons/app/bsky/feed/getTimeline.json @@ -8,9 +8,14 @@ "parameters": { "type": "params", "properties": { - "algorithm": {"type": "string"}, - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"} + "algorithm": { "type": "string" }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } } }, "output": { @@ -19,10 +24,13 @@ "type": "object", "required": ["feed"], "properties": { - "cursor": {"type": "string"}, + "cursor": { "type": "string" }, "feed": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.feed.defs#feedViewPost"} + "items": { + "type": "ref", + "ref": "app.bsky.feed.defs#feedViewPost" + } } } } diff --git a/lexicons/app/bsky/feed/like.json b/lexicons/app/bsky/feed/like.json index ea0a7fa5f28..01d9a08a76c 100644 --- a/lexicons/app/bsky/feed/like.json +++ b/lexicons/app/bsky/feed/like.json @@ -9,8 +9,8 @@ "type": "object", "required": ["subject", "createdAt"], "properties": { - "subject": {"type": "ref", "ref": "com.atproto.repo.strongRef"}, - "createdAt": {"type": "string", "format": "datetime"} + "subject": { "type": "ref", "ref": "com.atproto.repo.strongRef" }, + "createdAt": { "type": "string", "format": "datetime" } } } } diff --git a/lexicons/app/bsky/feed/post.json b/lexicons/app/bsky/feed/post.json index 5f4ee579bdc..5622b5cfd50 100644 --- a/lexicons/app/bsky/feed/post.json +++ b/lexicons/app/bsky/feed/post.json @@ -9,17 +9,17 @@ "type": "object", "required": ["text", "createdAt"], "properties": { - "text": {"type": "string", "maxLength": 3000, "maxGraphemes": 300}, + "text": { "type": "string", "maxLength": 3000, "maxGraphemes": 300 }, "entities": { "type": "array", "description": "Deprecated: replaced by app.bsky.richtext.facet.", - "items": {"type": "ref", "ref": "#entity"} + "items": { "type": "ref", "ref": "#entity" } }, "facets": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.richtext.facet"} + "items": { "type": "ref", "ref": "app.bsky.richtext.facet" } }, - "reply": {"type": "ref", "ref": "#replyRef"}, + "reply": { "type": "ref", "ref": "#replyRef" }, "embed": { "type": "union", "refs": [ @@ -32,18 +32,22 @@ "langs": { "type": "array", "maxLength": 3, - "items": {"type": "string", "format": "language"} + "items": { "type": "string", "format": "language" } }, - "createdAt": {"type": "string", "format": "datetime"} + "labels": { + "type": "union", + "refs": ["com.atproto.label.defs#selfLabels"] + }, + "createdAt": { "type": "string", "format": "datetime" } } } }, - "replyRef":{ + "replyRef": { "type": "object", "required": ["root", "parent"], "properties": { - "root": {"type": "ref", "ref": "com.atproto.repo.strongRef"}, - "parent": {"type": "ref", "ref": "com.atproto.repo.strongRef"} + "root": { "type": "ref", "ref": "com.atproto.repo.strongRef" }, + "parent": { "type": "ref", "ref": "com.atproto.repo.strongRef" } } }, "entity": { @@ -51,12 +55,12 @@ "description": "Deprecated: use facets instead.", "required": ["index", "type", "value"], "properties": { - "index": {"type": "ref", "ref": "#textSlice"}, + "index": { "type": "ref", "ref": "#textSlice" }, "type": { "type": "string", "description": "Expected values are 'mention' and 'link'." }, - "value": {"type": "string"} + "value": { "type": "string" } } }, "textSlice": { @@ -64,8 +68,8 @@ "description": "Deprecated. Use app.bsky.richtext instead -- A text segment. Start is inclusive, end is exclusive. Indices are for utf16-encoded strings.", "required": ["start", "end"], "properties": { - "start": {"type": "integer", "minimum": 0}, - "end": {"type": "integer", "minimum": 0} + "start": { "type": "integer", "minimum": 0 }, + "end": { "type": "integer", "minimum": 0 } } } } diff --git a/lexicons/app/bsky/feed/repost.json b/lexicons/app/bsky/feed/repost.json index 752a032ecee..3b809a53a7e 100644 --- a/lexicons/app/bsky/feed/repost.json +++ b/lexicons/app/bsky/feed/repost.json @@ -9,10 +9,10 @@ "type": "object", "required": ["subject", "createdAt"], "properties": { - "subject": {"type": "ref", "ref": "com.atproto.repo.strongRef"}, - "createdAt": {"type": "string", "format": "datetime"} + "subject": { "type": "ref", "ref": "com.atproto.repo.strongRef" }, + "createdAt": { "type": "string", "format": "datetime" } } } } } -} \ No newline at end of file +} diff --git a/lexicons/app/bsky/graph/block.json b/lexicons/app/bsky/graph/block.json index 919a9c75e93..67821908d6a 100644 --- a/lexicons/app/bsky/graph/block.json +++ b/lexicons/app/bsky/graph/block.json @@ -10,8 +10,8 @@ "type": "object", "required": ["subject", "createdAt"], "properties": { - "subject": {"type": "string", "format": "did"}, - "createdAt": {"type": "string", "format": "datetime"} + "subject": { "type": "string", "format": "did" }, + "createdAt": { "type": "string", "format": "datetime" } } } } diff --git a/lexicons/app/bsky/graph/defs.json b/lexicons/app/bsky/graph/defs.json index d5de7e5422c..44cf55875b4 100644 --- a/lexicons/app/bsky/graph/defs.json +++ b/lexicons/app/bsky/graph/defs.json @@ -6,46 +6,48 @@ "type": "object", "required": ["uri", "cid", "name", "purpose"], "properties": { - "uri": {"type": "string", "format": "at-uri"}, - "cid": {"type": "string", "format": "cid"}, - "name": {"type": "string", "maxLength": 64, "minLength": 1}, - "purpose": {"type": "ref", "ref": "#listPurpose"}, - "avatar": {"type": "string"}, - "viewer": {"type": "ref", "ref": "#listViewerState"}, - "indexedAt": {"type": "string", "format": "datetime"} + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "name": { "type": "string", "maxLength": 64, "minLength": 1 }, + "purpose": { "type": "ref", "ref": "#listPurpose" }, + "avatar": { "type": "string" }, + "viewer": { "type": "ref", "ref": "#listViewerState" }, + "indexedAt": { "type": "string", "format": "datetime" } } }, "listView": { "type": "object", "required": ["uri", "cid", "creator", "name", "purpose", "indexedAt"], "properties": { - "uri": {"type": "string", "format": "at-uri"}, - "cid": {"type": "string", "format": "cid"}, - "creator": {"type": "ref", "ref": "app.bsky.actor.defs#profileView"}, - "name": {"type": "string", "maxLength": 64, "minLength": 1}, - "purpose": {"type": "ref", "ref": "#listPurpose"}, - "description": {"type": "string", "maxGraphemes": 300, "maxLength": 3000}, + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "creator": { "type": "ref", "ref": "app.bsky.actor.defs#profileView" }, + "name": { "type": "string", "maxLength": 64, "minLength": 1 }, + "purpose": { "type": "ref", "ref": "#listPurpose" }, + "description": { + "type": "string", + "maxGraphemes": 300, + "maxLength": 3000 + }, "descriptionFacets": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.richtext.facet"} + "items": { "type": "ref", "ref": "app.bsky.richtext.facet" } }, - "avatar": {"type": "string"}, - "viewer": {"type": "ref", "ref": "#listViewerState"}, - "indexedAt": {"type": "string", "format": "datetime"} + "avatar": { "type": "string" }, + "viewer": { "type": "ref", "ref": "#listViewerState" }, + "indexedAt": { "type": "string", "format": "datetime" } } }, "listItemView": { "type": "object", "required": ["subject"], "properties": { - "subject": {"type": "ref", "ref": "app.bsky.actor.defs#profileView"} + "subject": { "type": "ref", "ref": "app.bsky.actor.defs#profileView" } } }, "listPurpose": { "type": "string", - "knownValues": [ - "app.bsky.graph.defs#modlist" - ] + "knownValues": ["app.bsky.graph.defs#modlist"] }, "modlist": { "type": "token", @@ -54,7 +56,8 @@ "listViewerState": { "type": "object", "properties": { - "muted": {"type": "boolean"} + "muted": { "type": "boolean" }, + "blocked": { "type": "string", "format": "at-uri" } } } } diff --git a/lexicons/app/bsky/graph/follow.json b/lexicons/app/bsky/graph/follow.json index 5c04a3501c6..64783ae1d1b 100644 --- a/lexicons/app/bsky/graph/follow.json +++ b/lexicons/app/bsky/graph/follow.json @@ -10,8 +10,8 @@ "type": "object", "required": ["subject", "createdAt"], "properties": { - "subject": {"type": "string", "format": "did"}, - "createdAt": {"type": "string", "format": "datetime"} + "subject": { "type": "string", "format": "did" }, + "createdAt": { "type": "string", "format": "datetime" } } } } diff --git a/lexicons/app/bsky/graph/getBlocks.json b/lexicons/app/bsky/graph/getBlocks.json index 5b1d28f4ecd..1573e57faa3 100644 --- a/lexicons/app/bsky/graph/getBlocks.json +++ b/lexicons/app/bsky/graph/getBlocks.json @@ -8,8 +8,13 @@ "parameters": { "type": "params", "properties": { - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"} + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } } }, "output": { @@ -18,10 +23,13 @@ "type": "object", "required": ["blocks"], "properties": { - "cursor": {"type": "string"}, + "cursor": { "type": "string" }, "blocks": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.actor.defs#profileView"} + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileView" + } } } } diff --git a/lexicons/app/bsky/graph/getFollowers.json b/lexicons/app/bsky/graph/getFollowers.json index f9990de7cc2..f3824bbd699 100644 --- a/lexicons/app/bsky/graph/getFollowers.json +++ b/lexicons/app/bsky/graph/getFollowers.json @@ -9,9 +9,14 @@ "type": "params", "required": ["actor"], "properties": { - "actor": {"type": "string", "format": "at-identifier"}, - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"} + "actor": { "type": "string", "format": "at-identifier" }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } } }, "output": { @@ -20,11 +25,17 @@ "type": "object", "required": ["subject", "followers"], "properties": { - "subject": {"type": "ref", "ref": "app.bsky.actor.defs#profileView"}, - "cursor": {"type": "string"}, + "subject": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileView" + }, + "cursor": { "type": "string" }, "followers": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.actor.defs#profileView"} + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileView" + } } } } diff --git a/lexicons/app/bsky/graph/getFollows.json b/lexicons/app/bsky/graph/getFollows.json index 3a20b841976..04ce9fef8bf 100644 --- a/lexicons/app/bsky/graph/getFollows.json +++ b/lexicons/app/bsky/graph/getFollows.json @@ -9,9 +9,14 @@ "type": "params", "required": ["actor"], "properties": { - "actor": {"type": "string", "format": "at-identifier"}, - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"} + "actor": { "type": "string", "format": "at-identifier" }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } } }, "output": { @@ -20,11 +25,17 @@ "type": "object", "required": ["subject", "follows"], "properties": { - "subject": {"type": "ref", "ref": "app.bsky.actor.defs#profileView"}, - "cursor": {"type": "string"}, + "subject": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileView" + }, + "cursor": { "type": "string" }, "follows": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.actor.defs#profileView"} + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileView" + } } } } diff --git a/lexicons/app/bsky/graph/getList.json b/lexicons/app/bsky/graph/getList.json index 47b7bf9d49f..0241ee1e7f1 100644 --- a/lexicons/app/bsky/graph/getList.json +++ b/lexicons/app/bsky/graph/getList.json @@ -9,9 +9,14 @@ "type": "params", "required": ["list"], "properties": { - "list": {"type": "string", "format": "at-uri"}, - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"} + "list": { "type": "string", "format": "at-uri" }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } } }, "output": { @@ -20,11 +25,14 @@ "type": "object", "required": ["list", "items"], "properties": { - "cursor": {"type": "string"}, - "list": {"type": "ref", "ref": "app.bsky.graph.defs#listView"}, + "cursor": { "type": "string" }, + "list": { "type": "ref", "ref": "app.bsky.graph.defs#listView" }, "items": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.graph.defs#listItemView"} + "items": { + "type": "ref", + "ref": "app.bsky.graph.defs#listItemView" + } } } } diff --git a/lexicons/app/bsky/graph/getListBlocks.json b/lexicons/app/bsky/graph/getListBlocks.json new file mode 100644 index 00000000000..709d77aa68b --- /dev/null +++ b/lexicons/app/bsky/graph/getListBlocks.json @@ -0,0 +1,36 @@ +{ + "lexicon": 1, + "id": "app.bsky.graph.getListBlocks", + "defs": { + "main": { + "type": "query", + "description": "Which lists is the requester's account blocking?", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["lists"], + "properties": { + "cursor": { "type": "string" }, + "lists": { + "type": "array", + "items": { "type": "ref", "ref": "app.bsky.graph.defs#listView" } + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/graph/getListMutes.json b/lexicons/app/bsky/graph/getListMutes.json index f1ec812e501..44b3b652fde 100644 --- a/lexicons/app/bsky/graph/getListMutes.json +++ b/lexicons/app/bsky/graph/getListMutes.json @@ -8,8 +8,13 @@ "parameters": { "type": "params", "properties": { - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"} + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } } }, "output": { @@ -18,10 +23,10 @@ "type": "object", "required": ["lists"], "properties": { - "cursor": {"type": "string"}, + "cursor": { "type": "string" }, "lists": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.graph.defs#listView"} + "items": { "type": "ref", "ref": "app.bsky.graph.defs#listView" } } } } diff --git a/lexicons/app/bsky/graph/getLists.json b/lexicons/app/bsky/graph/getLists.json index 09c6bcc9f95..de83a50d3eb 100644 --- a/lexicons/app/bsky/graph/getLists.json +++ b/lexicons/app/bsky/graph/getLists.json @@ -9,9 +9,14 @@ "type": "params", "required": ["actor"], "properties": { - "actor": {"type": "string", "format": "at-identifier"}, - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"} + "actor": { "type": "string", "format": "at-identifier" }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } } }, "output": { @@ -20,10 +25,10 @@ "type": "object", "required": ["lists"], "properties": { - "cursor": {"type": "string"}, + "cursor": { "type": "string" }, "lists": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.graph.defs#listView"} + "items": { "type": "ref", "ref": "app.bsky.graph.defs#listView" } } } } diff --git a/lexicons/app/bsky/graph/getMutes.json b/lexicons/app/bsky/graph/getMutes.json index 150d6858271..aba63ea3043 100644 --- a/lexicons/app/bsky/graph/getMutes.json +++ b/lexicons/app/bsky/graph/getMutes.json @@ -8,8 +8,13 @@ "parameters": { "type": "params", "properties": { - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"} + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } } }, "output": { @@ -18,10 +23,13 @@ "type": "object", "required": ["mutes"], "properties": { - "cursor": {"type": "string"}, + "cursor": { "type": "string" }, "mutes": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.actor.defs#profileView"} + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileView" + } } } } diff --git a/lexicons/app/bsky/graph/getSuggestedFollowsByActor.json b/lexicons/app/bsky/graph/getSuggestedFollowsByActor.json new file mode 100644 index 00000000000..32873a537c9 --- /dev/null +++ b/lexicons/app/bsky/graph/getSuggestedFollowsByActor.json @@ -0,0 +1,33 @@ +{ + "lexicon": 1, + "id": "app.bsky.graph.getSuggestedFollowsByActor", + "defs": { + "main": { + "type": "query", + "description": "Get suggested follows related to a given actor.", + "parameters": { + "type": "params", + "required": ["actor"], + "properties": { + "actor": { "type": "string", "format": "at-identifier" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["suggestions"], + "properties": { + "suggestions": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileView" + } + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/graph/list.json b/lexicons/app/bsky/graph/list.json index c17105a208e..ccc845a6926 100644 --- a/lexicons/app/bsky/graph/list.json +++ b/lexicons/app/bsky/graph/list.json @@ -10,19 +10,30 @@ "type": "object", "required": ["name", "purpose", "createdAt"], "properties": { - "purpose": {"type": "ref", "ref": "app.bsky.graph.defs#listPurpose"}, - "name": {"type": "string", "maxLength": 64, "minLength": 1}, - "description": {"type": "string", "maxGraphemes": 300, "maxLength": 3000}, + "purpose": { + "type": "ref", + "ref": "app.bsky.graph.defs#listPurpose" + }, + "name": { "type": "string", "maxLength": 64, "minLength": 1 }, + "description": { + "type": "string", + "maxGraphemes": 300, + "maxLength": 3000 + }, "descriptionFacets": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.richtext.facet"} + "items": { "type": "ref", "ref": "app.bsky.richtext.facet" } }, "avatar": { "type": "blob", "accept": ["image/png", "image/jpeg"], "maxSize": 1000000 }, - "createdAt": {"type": "string", "format": "datetime"} + "labels": { + "type": "union", + "refs": ["com.atproto.label.defs#selfLabels"] + }, + "createdAt": { "type": "string", "format": "datetime" } } } } diff --git a/lexicons/app/bsky/graph/listblock.json b/lexicons/app/bsky/graph/listblock.json new file mode 100644 index 00000000000..b3a839c5316 --- /dev/null +++ b/lexicons/app/bsky/graph/listblock.json @@ -0,0 +1,19 @@ +{ + "lexicon": 1, + "id": "app.bsky.graph.listblock", + "defs": { + "main": { + "type": "record", + "description": "A block of an entire list of actors.", + "key": "tid", + "record": { + "type": "object", + "required": ["subject", "createdAt"], + "properties": { + "subject": { "type": "string", "format": "at-uri" }, + "createdAt": { "type": "string", "format": "datetime" } + } + } + } + } +} diff --git a/lexicons/app/bsky/graph/listitem.json b/lexicons/app/bsky/graph/listitem.json index c8f82e7fdfc..f05a1641e6f 100644 --- a/lexicons/app/bsky/graph/listitem.json +++ b/lexicons/app/bsky/graph/listitem.json @@ -10,9 +10,9 @@ "type": "object", "required": ["subject", "list", "createdAt"], "properties": { - "subject": {"type": "string", "format": "did"}, - "list": {"type": "string", "format": "at-uri"}, - "createdAt": {"type": "string", "format": "datetime"} + "subject": { "type": "string", "format": "did" }, + "list": { "type": "string", "format": "at-uri" }, + "createdAt": { "type": "string", "format": "datetime" } } } } diff --git a/lexicons/app/bsky/notification/getUnreadCount.json b/lexicons/app/bsky/notification/getUnreadCount.json index ba213622872..8b0cdc3af46 100644 --- a/lexicons/app/bsky/notification/getUnreadCount.json +++ b/lexicons/app/bsky/notification/getUnreadCount.json @@ -7,7 +7,7 @@ "parameters": { "type": "params", "properties": { - "seenAt": { "type": "string", "format": "datetime"} + "seenAt": { "type": "string", "format": "datetime" } } }, "output": { @@ -16,7 +16,7 @@ "type": "object", "required": ["count"], "properties": { - "count": { "type": "integer"} + "count": { "type": "integer" } } } } diff --git a/lexicons/app/bsky/notification/listNotifications.json b/lexicons/app/bsky/notification/listNotifications.json index d3775e6177c..c5a9aee0fd4 100644 --- a/lexicons/app/bsky/notification/listNotifications.json +++ b/lexicons/app/bsky/notification/listNotifications.json @@ -7,9 +7,14 @@ "parameters": { "type": "params", "properties": { - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"}, - "seenAt": { "type": "string", "format": "datetime"} + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" }, + "seenAt": { "type": "string", "format": "datetime" } } }, "output": { @@ -18,10 +23,10 @@ "type": "object", "required": ["notifications"], "properties": { - "cursor": {"type": "string"}, + "cursor": { "type": "string" }, "notifications": { "type": "array", - "items": {"type": "ref", "ref": "#notification"} + "items": { "type": "ref", "ref": "#notification" } } } } @@ -29,23 +34,38 @@ }, "notification": { "type": "object", - "required": ["uri", "cid", "author", "reason", "record", "isRead", "indexedAt"], + "required": [ + "uri", + "cid", + "author", + "reason", + "record", + "isRead", + "indexedAt" + ], "properties": { - "uri": {"type": "string", "format": "at-uri"}, - "cid": {"type": "string", "format": "cid" }, - "author": {"type": "ref", "ref": "app.bsky.actor.defs#profileView"}, + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "author": { "type": "ref", "ref": "app.bsky.actor.defs#profileView" }, "reason": { "type": "string", "description": "Expected values are 'like', 'repost', 'follow', 'mention', 'reply', and 'quote'.", - "knownValues": ["like", "repost", "follow", "mention", "reply", "quote"] + "knownValues": [ + "like", + "repost", + "follow", + "mention", + "reply", + "quote" + ] }, - "reasonSubject": {"type": "string", "format": "at-uri"}, - "record": {"type": "unknown"}, - "isRead": {"type": "boolean"}, - "indexedAt": {"type": "string", "format": "datetime"}, + "reasonSubject": { "type": "string", "format": "at-uri" }, + "record": { "type": "unknown" }, + "isRead": { "type": "boolean" }, + "indexedAt": { "type": "string", "format": "datetime" }, "labels": { "type": "array", - "items": {"type": "ref", "ref": "com.atproto.label.defs#label"} + "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } } } } diff --git a/lexicons/app/bsky/notification/registerPush.json b/lexicons/app/bsky/notification/registerPush.json new file mode 100644 index 00000000000..159dd370049 --- /dev/null +++ b/lexicons/app/bsky/notification/registerPush.json @@ -0,0 +1,26 @@ +{ + "lexicon": 1, + "id": "app.bsky.notification.registerPush", + "defs": { + "main": { + "type": "procedure", + "description": "Register for push notifications with a service", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["serviceDid", "token", "platform", "appId"], + "properties": { + "serviceDid": { "type": "string", "format": "did" }, + "token": { "type": "string" }, + "platform": { + "type": "string", + "knownValues": ["ios", "android", "web"] + }, + "appId": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/notification/updateSeen.json b/lexicons/app/bsky/notification/updateSeen.json index 5f54d9ae110..33626343e51 100644 --- a/lexicons/app/bsky/notification/updateSeen.json +++ b/lexicons/app/bsky/notification/updateSeen.json @@ -11,7 +11,7 @@ "type": "object", "required": ["seenAt"], "properties": { - "seenAt": { "type": "string", "format": "datetime"} + "seenAt": { "type": "string", "format": "datetime" } } } } diff --git a/lexicons/app/bsky/richtext/facet.json b/lexicons/app/bsky/richtext/facet.json index 714fe8151d3..9addf2f34b7 100644 --- a/lexicons/app/bsky/richtext/facet.json +++ b/lexicons/app/bsky/richtext/facet.json @@ -6,10 +6,10 @@ "type": "object", "required": ["index", "features"], "properties": { - "index": {"type": "ref", "ref": "#byteSlice"}, + "index": { "type": "ref", "ref": "#byteSlice" }, "features": { "type": "array", - "items": {"type": "union", "refs": ["#mention", "#link"]} + "items": { "type": "union", "refs": ["#mention", "#link"] } } } }, @@ -18,7 +18,7 @@ "description": "A facet feature for actor mentions.", "required": ["did"], "properties": { - "did": {"type": "string", "format": "did"} + "did": { "type": "string", "format": "did" } } }, "link": { @@ -26,7 +26,7 @@ "description": "A facet feature for links.", "required": ["uri"], "properties": { - "uri": {"type": "string", "format": "uri"} + "uri": { "type": "string", "format": "uri" } } }, "byteSlice": { @@ -34,8 +34,8 @@ "description": "A text segment. Start is inclusive, end is exclusive. Indices are for utf8-encoded strings.", "required": ["byteStart", "byteEnd"], "properties": { - "byteStart": {"type": "integer", "minimum": 0}, - "byteEnd": {"type": "integer", "minimum": 0} + "byteStart": { "type": "integer", "minimum": 0 }, + "byteEnd": { "type": "integer", "minimum": 0 } } } } diff --git a/lexicons/app/bsky/unspecced/applyLabels.json b/lexicons/app/bsky/unspecced/applyLabels.json new file mode 100644 index 00000000000..24c9e716ad5 --- /dev/null +++ b/lexicons/app/bsky/unspecced/applyLabels.json @@ -0,0 +1,23 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.applyLabels", + "defs": { + "main": { + "type": "procedure", + "description": "Allow a labeler to apply labels directly.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["labels"], + "properties": { + "labels": { + "type": "array", + "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getPopular.json b/lexicons/app/bsky/unspecced/getPopular.json index c6fe559c56c..791968c5ef9 100644 --- a/lexicons/app/bsky/unspecced/getPopular.json +++ b/lexicons/app/bsky/unspecced/getPopular.json @@ -8,9 +8,14 @@ "parameters": { "type": "params", "properties": { - "includeNsfw": {"type": "boolean", "default": false}, - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"} + "includeNsfw": { "type": "boolean", "default": false }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } } }, "output": { @@ -19,10 +24,13 @@ "type": "object", "required": ["feed"], "properties": { - "cursor": {"type": "string"}, + "cursor": { "type": "string" }, "feed": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.feed.defs#feedViewPost"} + "items": { + "type": "ref", + "ref": "app.bsky.feed.defs#feedViewPost" + } } } } diff --git a/lexicons/app/bsky/unspecced/getPopularFeedGenerators.json b/lexicons/app/bsky/unspecced/getPopularFeedGenerators.json index 4bc46acbee6..ddeb7e7fab2 100644 --- a/lexicons/app/bsky/unspecced/getPopularFeedGenerators.json +++ b/lexicons/app/bsky/unspecced/getPopularFeedGenerators.json @@ -5,15 +5,32 @@ "main": { "type": "query", "description": "An unspecced view of globally popular feed generators", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" }, + "query": { "type": "string" } + } + }, "output": { "encoding": "application/json", "schema": { "type": "object", "required": ["feeds"], "properties": { + "cursor": { "type": "string" }, "feeds": { "type": "array", - "items": {"type": "ref", "ref": "app.bsky.feed.defs#generatorView"} + "items": { + "type": "ref", + "ref": "app.bsky.feed.defs#generatorView" + } } } } diff --git a/lexicons/app/bsky/unspecced/getTimelineSkeleton.json b/lexicons/app/bsky/unspecced/getTimelineSkeleton.json new file mode 100644 index 00000000000..c720b81832d --- /dev/null +++ b/lexicons/app/bsky/unspecced/getTimelineSkeleton.json @@ -0,0 +1,40 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getTimelineSkeleton", + "defs": { + "main": { + "type": "query", + "description": "A skeleton of a timeline - UNSPECCED & WILL GO AWAY SOON", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["feed"], + "properties": { + "cursor": { "type": "string" }, + "feed": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.feed.defs#skeletonFeedPost" + } + } + } + } + }, + "errors": [{ "name": "UnknownFeed" }] + } + } +} diff --git a/lexicons/com/atproto/admin/defs.json b/lexicons/com/atproto/admin/defs.json index 8626a004267..a04c77d68f8 100644 --- a/lexicons/com/atproto/admin/defs.json +++ b/lexicons/com/atproto/admin/defs.json @@ -17,6 +17,10 @@ "properties": { "id": { "type": "integer" }, "action": { "type": "ref", "ref": "#actionType" }, + "durationInHours": { + "type": "integer", + "description": "Indicates how long this action was meant to be in effect before automatically expiring." + }, "subject": { "type": "union", "refs": ["#repoRef", "com.atproto.repo.strongRef"] @@ -46,6 +50,10 @@ "properties": { "id": { "type": "integer" }, "action": { "type": "ref", "ref": "#actionType" }, + "durationInHours": { + "type": "integer", + "description": "Indicates how long this action was meant to be in effect before automatically expiring." + }, "subject": { "type": "union", "refs": [ @@ -76,7 +84,11 @@ "required": ["id", "action"], "properties": { "id": { "type": "integer" }, - "action": { "type": "ref", "ref": "#actionType" } + "action": { "type": "ref", "ref": "#actionType" }, + "durationInHours": { + "type": "integer", + "description": "Indicates how long this action was meant to be in effect before automatically expiring." + } } }, "actionReversal": { @@ -192,7 +204,8 @@ "type": "ref", "ref": "com.atproto.server.defs#inviteCode" }, - "invitesDisabled": { "type": "boolean" } + "invitesDisabled": { "type": "boolean" }, + "inviteNote": { "type": "string" } } }, "repoViewDetail": { @@ -226,7 +239,8 @@ "ref": "com.atproto.server.defs#inviteCode" } }, - "invitesDisabled": { "type": "boolean" } + "invitesDisabled": { "type": "boolean" }, + "inviteNote": { "type": "string" } } }, "repoViewNotFound": { @@ -304,7 +318,6 @@ }, "moderation": { "type": "object", - "required": [], "properties": { "currentAction": { "type": "ref", "ref": "#actionViewCurrent" } } diff --git a/lexicons/com/atproto/admin/disableAccountInvites.json b/lexicons/com/atproto/admin/disableAccountInvites.json index 58d72c11564..2006271d089 100644 --- a/lexicons/com/atproto/admin/disableAccountInvites.json +++ b/lexicons/com/atproto/admin/disableAccountInvites.json @@ -11,7 +11,11 @@ "type": "object", "required": ["account"], "properties": { - "account": {"type": "string", "format": "did"} + "account": { "type": "string", "format": "did" }, + "note": { + "type": "string", + "description": "Additionally add a note describing why the invites were disabled" + } } } } diff --git a/lexicons/com/atproto/admin/disableInviteCodes.json b/lexicons/com/atproto/admin/disableInviteCodes.json index bfab5479ac6..f84bc05f203 100644 --- a/lexicons/com/atproto/admin/disableInviteCodes.json +++ b/lexicons/com/atproto/admin/disableInviteCodes.json @@ -11,12 +11,12 @@ "type": "object", "properties": { "codes": { - "type": "array", - "items": {"type": "string"} + "type": "array", + "items": { "type": "string" } }, "accounts": { - "type": "array", - "items": {"type": "string"} + "type": "array", + "items": { "type": "string" } } } } diff --git a/lexicons/com/atproto/admin/enableAccountInvites.json b/lexicons/com/atproto/admin/enableAccountInvites.json index 9519a15a7c0..ac42d727879 100644 --- a/lexicons/com/atproto/admin/enableAccountInvites.json +++ b/lexicons/com/atproto/admin/enableAccountInvites.json @@ -11,7 +11,11 @@ "type": "object", "required": ["account"], "properties": { - "account": {"type": "string", "format": "did"} + "account": { "type": "string", "format": "did" }, + "note": { + "type": "string", + "description": "Additionally add a note describing why the invites were enabled" + } } } } diff --git a/lexicons/com/atproto/admin/getInviteCodes.json b/lexicons/com/atproto/admin/getInviteCodes.json index c74a6d09bab..895f1259d7c 100644 --- a/lexicons/com/atproto/admin/getInviteCodes.json +++ b/lexicons/com/atproto/admin/getInviteCodes.json @@ -10,14 +10,16 @@ "properties": { "sort": { "type": "string", - "knownValues": [ - "recent", - "usage" - ], + "knownValues": ["recent", "usage"], "default": "recent" }, - "limit": {"type": "integer", "minimum": 1, "maximum": 500, "default": 100}, - "cursor": {"type": "string"} + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 500, + "default": 100 + }, + "cursor": { "type": "string" } } }, "output": { @@ -26,10 +28,13 @@ "type": "object", "required": ["codes"], "properties": { - "cursor": {"type": "string"}, + "cursor": { "type": "string" }, "codes": { "type": "array", - "items": {"type": "ref", "ref": "com.atproto.server.defs#inviteCode"} + "items": { + "type": "ref", + "ref": "com.atproto.server.defs#inviteCode" + } } } } diff --git a/lexicons/com/atproto/admin/getModerationAction.json b/lexicons/com/atproto/admin/getModerationAction.json index b733ba670a8..b25ccc227e1 100644 --- a/lexicons/com/atproto/admin/getModerationAction.json +++ b/lexicons/com/atproto/admin/getModerationAction.json @@ -9,12 +9,15 @@ "type": "params", "required": ["id"], "properties": { - "id": {"type": "integer"} + "id": { "type": "integer" } } }, "output": { "encoding": "application/json", - "schema": {"type": "ref", "ref": "com.atproto.admin.defs#actionViewDetail"} + "schema": { + "type": "ref", + "ref": "com.atproto.admin.defs#actionViewDetail" + } } } } diff --git a/lexicons/com/atproto/admin/getModerationActions.json b/lexicons/com/atproto/admin/getModerationActions.json index 20ac9144a00..89e16fd6919 100644 --- a/lexicons/com/atproto/admin/getModerationActions.json +++ b/lexicons/com/atproto/admin/getModerationActions.json @@ -8,9 +8,14 @@ "parameters": { "type": "params", "properties": { - "subject": {"type": "string"}, - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"} + "subject": { "type": "string" }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } } }, "output": { @@ -19,8 +24,14 @@ "type": "object", "required": ["actions"], "properties": { - "cursor": {"type": "string"}, - "actions": {"type": "array", "items": {"type": "ref", "ref": "com.atproto.admin.defs#actionView"}} + "cursor": { "type": "string" }, + "actions": { + "type": "array", + "items": { + "type": "ref", + "ref": "com.atproto.admin.defs#actionView" + } + } } } } diff --git a/lexicons/com/atproto/admin/getModerationReport.json b/lexicons/com/atproto/admin/getModerationReport.json index 2f1eed8d920..3e84e13e676 100644 --- a/lexicons/com/atproto/admin/getModerationReport.json +++ b/lexicons/com/atproto/admin/getModerationReport.json @@ -9,12 +9,15 @@ "type": "params", "required": ["id"], "properties": { - "id": {"type": "integer"} + "id": { "type": "integer" } } }, "output": { "encoding": "application/json", - "schema": {"type": "ref", "ref": "com.atproto.admin.defs#reportViewDetail"} + "schema": { + "type": "ref", + "ref": "com.atproto.admin.defs#reportViewDetail" + } } } } diff --git a/lexicons/com/atproto/admin/getModerationReports.json b/lexicons/com/atproto/admin/getModerationReports.json index a7f7bf83c4a..ad930389147 100644 --- a/lexicons/com/atproto/admin/getModerationReports.json +++ b/lexicons/com/atproto/admin/getModerationReports.json @@ -10,6 +10,11 @@ "properties": { "subject": { "type": "string" }, "ignoreSubjects": { "type": "array", "items": { "type": "string" } }, + "actionedBy": { + "type": "string", + "format": "did", + "description": "Get all reports that were actioned by a specific moderator" + }, "reporters": { "type": "array", "items": { "type": "string" }, diff --git a/lexicons/com/atproto/admin/getRecord.json b/lexicons/com/atproto/admin/getRecord.json index b0790187b1e..a16fe484feb 100644 --- a/lexicons/com/atproto/admin/getRecord.json +++ b/lexicons/com/atproto/admin/getRecord.json @@ -9,17 +9,18 @@ "type": "params", "required": ["uri"], "properties": { - "uri": {"type": "string", "format": "at-uri"}, - "cid": {"type": "string", "format": "cid"} + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" } } }, "output": { "encoding": "application/json", - "schema": {"type": "ref", "ref": "com.atproto.admin.defs#recordViewDetail"} + "schema": { + "type": "ref", + "ref": "com.atproto.admin.defs#recordViewDetail" + } }, - "errors": [ - {"name": "RecordNotFound"} - ] + "errors": [{ "name": "RecordNotFound" }] } } } diff --git a/lexicons/com/atproto/admin/getRepo.json b/lexicons/com/atproto/admin/getRepo.json index 972c00ced89..d8cfd883f72 100644 --- a/lexicons/com/atproto/admin/getRepo.json +++ b/lexicons/com/atproto/admin/getRepo.json @@ -9,16 +9,17 @@ "type": "params", "required": ["did"], "properties": { - "did": {"type": "string", "format":"did"} + "did": { "type": "string", "format": "did" } } }, "output": { "encoding": "application/json", - "schema": {"type": "ref", "ref": "com.atproto.admin.defs#repoViewDetail"} + "schema": { + "type": "ref", + "ref": "com.atproto.admin.defs#repoViewDetail" + } }, - "errors": [ - {"name": "RepoNotFound"} - ] + "errors": [{ "name": "RepoNotFound" }] } } } diff --git a/lexicons/com/atproto/admin/rebaseRepo.json b/lexicons/com/atproto/admin/rebaseRepo.json deleted file mode 100644 index 86e75c59a52..00000000000 --- a/lexicons/com/atproto/admin/rebaseRepo.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "lexicon": 1, - "id": "com.atproto.admin.rebaseRepo", - "defs": { - "main": { - "type": "procedure", - "description": "Administrative action to rebase an account's repo", - "input": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["repo"], - "properties": { - "repo": { - "type": "string", - "format": "at-identifier", - "description": "The handle or DID of the repo." - }, - "swapCommit": { - "type": "string", - "format": "cid", - "description": "Compare and swap with the previous commit by cid." - } - } - } - }, - "errors": [ - {"name": "InvalidSwap"}, - {"name": "ConcurrentWrites"} - ] - } - } -} diff --git a/lexicons/com/atproto/admin/resolveModerationReports.json b/lexicons/com/atproto/admin/resolveModerationReports.json index 536379e195e..0cc5c1df2a2 100644 --- a/lexicons/com/atproto/admin/resolveModerationReports.json +++ b/lexicons/com/atproto/admin/resolveModerationReports.json @@ -11,9 +11,9 @@ "type": "object", "required": ["actionId", "reportIds", "createdBy"], "properties": { - "actionId": {"type": "integer"}, - "reportIds": {"type": "array", "items": {"type": "integer"}}, - "createdBy": {"type": "string", "format": "did"} + "actionId": { "type": "integer" }, + "reportIds": { "type": "array", "items": { "type": "integer" } }, + "createdBy": { "type": "string", "format": "did" } } } }, diff --git a/lexicons/com/atproto/admin/reverseModerationAction.json b/lexicons/com/atproto/admin/reverseModerationAction.json index 496be245c30..9b479dcc8e1 100644 --- a/lexicons/com/atproto/admin/reverseModerationAction.json +++ b/lexicons/com/atproto/admin/reverseModerationAction.json @@ -11,9 +11,9 @@ "type": "object", "required": ["id", "reason", "createdBy"], "properties": { - "id": {"type": "integer"}, - "reason": {"type": "string"}, - "createdBy": {"type": "string", "format": "did"} + "id": { "type": "integer" }, + "reason": { "type": "string" }, + "createdBy": { "type": "string", "format": "did" } } } }, diff --git a/lexicons/com/atproto/admin/searchRepos.json b/lexicons/com/atproto/admin/searchRepos.json index 8955866ff5f..fb9c90f343c 100644 --- a/lexicons/com/atproto/admin/searchRepos.json +++ b/lexicons/com/atproto/admin/searchRepos.json @@ -8,10 +8,15 @@ "parameters": { "type": "params", "properties": { - "term": {"type": "string"}, - "invitedBy": {"type": "string"}, - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}, - "cursor": {"type": "string"} + "term": { "type": "string" }, + "invitedBy": { "type": "string" }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } } }, "output": { @@ -20,8 +25,14 @@ "type": "object", "required": ["repos"], "properties": { - "cursor": {"type": "string"}, - "repos": {"type": "array", "items": {"type": "ref", "ref": "com.atproto.admin.defs#repoView"}} + "cursor": { "type": "string" }, + "repos": { + "type": "array", + "items": { + "type": "ref", + "ref": "com.atproto.admin.defs#repoView" + } + } } } } diff --git a/lexicons/com/atproto/admin/sendEmail.json b/lexicons/com/atproto/admin/sendEmail.json new file mode 100644 index 00000000000..8df082258dd --- /dev/null +++ b/lexicons/com/atproto/admin/sendEmail.json @@ -0,0 +1,32 @@ +{ + "lexicon": 1, + "id": "com.atproto.admin.sendEmail", + "defs": { + "main": { + "type": "procedure", + "description": "Send email to a user's primary email address", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["recipientDid", "content"], + "properties": { + "recipientDid": { "type": "string", "format": "did" }, + "content": { "type": "string" }, + "subject": { "type": "string" } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["sent"], + "properties": { + "sent": { "type": "boolean" } + } + } + } + } + } +} diff --git a/lexicons/com/atproto/admin/takeModerationAction.json b/lexicons/com/atproto/admin/takeModerationAction.json index d9f72c2eb84..76600735251 100644 --- a/lexicons/com/atproto/admin/takeModerationAction.json +++ b/lexicons/com/atproto/admin/takeModerationAction.json @@ -26,11 +26,24 @@ "com.atproto.repo.strongRef" ] }, - "subjectBlobCids": {"type": "array", "items": {"type": "string", "format": "cid"}}, - "createLabelVals": {"type": "array", "items": {"type": "string"}}, - "negateLabelVals": {"type": "array", "items": {"type": "string"}}, - "reason": {"type": "string"}, - "createdBy": {"type": "string", "format": "did"} + "subjectBlobCids": { + "type": "array", + "items": { "type": "string", "format": "cid" } + }, + "createLabelVals": { + "type": "array", + "items": { "type": "string" } + }, + "negateLabelVals": { + "type": "array", + "items": { "type": "string" } + }, + "reason": { "type": "string" }, + "durationInHours": { + "type": "integer", + "description": "Indicates how long this action was meant to be in effect before automatically expiring." + }, + "createdBy": { "type": "string", "format": "did" } } } }, diff --git a/lexicons/com/atproto/admin/updateAccountEmail.json b/lexicons/com/atproto/admin/updateAccountEmail.json index 98a66e843d1..a4cf711dee9 100644 --- a/lexicons/com/atproto/admin/updateAccountEmail.json +++ b/lexicons/com/atproto/admin/updateAccountEmail.json @@ -16,10 +16,10 @@ "format": "at-identifier", "description": "The handle or DID of the repo." }, - "email": {"type": "string"} + "email": { "type": "string" } } } } } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/admin/updateAccountHandle.json b/lexicons/com/atproto/admin/updateAccountHandle.json index c9809b97af2..15442e98beb 100644 --- a/lexicons/com/atproto/admin/updateAccountHandle.json +++ b/lexicons/com/atproto/admin/updateAccountHandle.json @@ -11,11 +11,11 @@ "type": "object", "required": ["did", "handle"], "properties": { - "did": {"type": "string", "format": "did"}, - "handle": {"type": "string", "format": "handle"} + "did": { "type": "string", "format": "did" }, + "handle": { "type": "string", "format": "handle" } } } } } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/identity/resolveHandle.json b/lexicons/com/atproto/identity/resolveHandle.json index 5c22f78d6b3..ae5aab8f8fc 100644 --- a/lexicons/com/atproto/identity/resolveHandle.json +++ b/lexicons/com/atproto/identity/resolveHandle.json @@ -22,10 +22,10 @@ "type": "object", "required": ["did"], "properties": { - "did": {"type": "string", "format": "did"} + "did": { "type": "string", "format": "did" } } } } } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/identity/updateHandle.json b/lexicons/com/atproto/identity/updateHandle.json index 53789d539cb..2b595d189f5 100644 --- a/lexicons/com/atproto/identity/updateHandle.json +++ b/lexicons/com/atproto/identity/updateHandle.json @@ -11,10 +11,10 @@ "type": "object", "required": ["handle"], "properties": { - "handle": {"type": "string", "format": "handle"} + "handle": { "type": "string", "format": "handle" } } } } } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/label/defs.json b/lexicons/com/atproto/label/defs.json index bdfdeec6b67..88a262b11c8 100644 --- a/lexicons/com/atproto/label/defs.json +++ b/lexicons/com/atproto/label/defs.json @@ -37,6 +37,30 @@ "description": "timestamp when this label was created" } } + }, + "selfLabels": { + "type": "object", + "description": "Metadata tags on an atproto record, published by the author within the record.", + "required": ["values"], + "properties": { + "values": { + "type": "array", + "items": { "type": "ref", "ref": "#selfLabel" }, + "maxLength": 10 + } + } + }, + "selfLabel": { + "type": "object", + "description": "Metadata tag on an atproto record, published by the author within the record. Note -- schemas should use #selfLabels, not #selfLabel.", + "required": ["val"], + "properties": { + "val": { + "type": "string", + "maxLength": 128, + "description": "the short string name of the value or type of this label" + } + } } } } diff --git a/lexicons/com/atproto/label/queryLabels.json b/lexicons/com/atproto/label/queryLabels.json index c51597ceb8d..f4773f255e3 100644 --- a/lexicons/com/atproto/label/queryLabels.json +++ b/lexicons/com/atproto/label/queryLabels.json @@ -10,17 +10,22 @@ "required": ["uriPatterns"], "properties": { "uriPatterns": { - "type": "array", - "items": {"type": "string"}, - "description": "List of AT URI patterns to match (boolean 'OR'). Each may be a prefix (ending with '*'; will match inclusive of the string leading to '*'), or a full URI" + "type": "array", + "items": { "type": "string" }, + "description": "List of AT URI patterns to match (boolean 'OR'). Each may be a prefix (ending with '*'; will match inclusive of the string leading to '*'), or a full URI" }, "sources": { - "type": "array", - "items": {"type": "string", "format": "did"}, - "description": "Optional list of label sources (DIDs) to filter on" + "type": "array", + "items": { "type": "string", "format": "did" }, + "description": "Optional list of label sources (DIDs) to filter on" + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 250, + "default": 50 }, - "limit": {"type": "integer", "minimum": 1, "maximum": 250, "default": 50}, - "cursor": {"type": "string"} + "cursor": { "type": "string" } } }, "output": { @@ -29,10 +34,10 @@ "type": "object", "required": ["labels"], "properties": { - "cursor": {"type": "string"}, + "cursor": { "type": "string" }, "labels": { "type": "array", - "items": {"type": "ref", "ref": "com.atproto.label.defs#label"} + "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } } } } diff --git a/lexicons/com/atproto/label/subscribeLabels.json b/lexicons/com/atproto/label/subscribeLabels.json index 65a9e1a4a3c..044036cfad4 100644 --- a/lexicons/com/atproto/label/subscribeLabels.json +++ b/lexicons/com/atproto/label/subscribeLabels.json @@ -17,21 +17,16 @@ "message": { "schema": { "type": "union", - "refs": [ - "#labels", - "#info" - ] + "refs": ["#labels", "#info"] } }, - "errors": [ - {"name": "FutureCursor"} - ] + "errors": [{ "name": "FutureCursor" }] }, "labels": { "type": "object", "required": ["seq", "labels"], "properties": { - "seq": {"type": "integer"}, + "seq": { "type": "integer" }, "labels": { "type": "array", "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } diff --git a/lexicons/com/atproto/moderation/createReport.json b/lexicons/com/atproto/moderation/createReport.json index c0c8d3b2594..0f34ed4329b 100644 --- a/lexicons/com/atproto/moderation/createReport.json +++ b/lexicons/com/atproto/moderation/createReport.json @@ -11,8 +11,11 @@ "type": "object", "required": ["reasonType", "subject"], "properties": { - "reasonType": {"type": "ref", "ref": "com.atproto.moderation.defs#reasonType"}, - "reason": {"type": "string"}, + "reasonType": { + "type": "ref", + "ref": "com.atproto.moderation.defs#reasonType" + }, + "reason": { "type": "string" }, "subject": { "type": "union", "refs": [ @@ -27,11 +30,20 @@ "encoding": "application/json", "schema": { "type": "object", - "required": ["id", "reasonType", "subject", "reportedBy", "createdAt"], + "required": [ + "id", + "reasonType", + "subject", + "reportedBy", + "createdAt" + ], "properties": { - "id": {"type": "integer"}, - "reasonType": {"type": "ref", "ref": "com.atproto.moderation.defs#reasonType"}, - "reason": {"type": "string"}, + "id": { "type": "integer" }, + "reasonType": { + "type": "ref", + "ref": "com.atproto.moderation.defs#reasonType" + }, + "reason": { "type": "string" }, "subject": { "type": "union", "refs": [ @@ -39,8 +51,8 @@ "com.atproto.repo.strongRef" ] }, - "reportedBy": {"type": "string", "format": "did"}, - "createdAt": {"type": "string", "format": "datetime"} + "reportedBy": { "type": "string", "format": "did" }, + "createdAt": { "type": "string", "format": "datetime" } } } } diff --git a/lexicons/com/atproto/moderation/defs.json b/lexicons/com/atproto/moderation/defs.json index 987771f0ca5..a06579a502e 100644 --- a/lexicons/com/atproto/moderation/defs.json +++ b/lexicons/com/atproto/moderation/defs.json @@ -1,41 +1,41 @@ { - "lexicon": 1, - "id": "com.atproto.moderation.defs", - "defs": { - "reasonType": { - "type": "string", - "knownValues": [ - "com.atproto.moderation.defs#reasonSpam", - "com.atproto.moderation.defs#reasonViolation", - "com.atproto.moderation.defs#reasonMisleading", - "com.atproto.moderation.defs#reasonSexual", - "com.atproto.moderation.defs#reasonRude", - "com.atproto.moderation.defs#reasonOther" - ] - }, - "reasonSpam": { - "type": "token", - "description": "Spam: frequent unwanted promotion, replies, mentions" - }, - "reasonViolation": { - "type": "token", - "description": "Direct violation of server rules, laws, terms of service" - }, - "reasonMisleading": { - "type": "token", - "description": "Misleading identity, affiliation, or content" - }, - "reasonSexual": { - "type": "token", - "description": "Unwanted or mis-labeled sexual content" - }, - "reasonRude": { - "type": "token", - "description": "Rude, harassing, explicit, or otherwise unwelcoming behavior" - }, - "reasonOther": { - "type": "token", - "description": "Other: reports not falling under another report category" - } + "lexicon": 1, + "id": "com.atproto.moderation.defs", + "defs": { + "reasonType": { + "type": "string", + "knownValues": [ + "com.atproto.moderation.defs#reasonSpam", + "com.atproto.moderation.defs#reasonViolation", + "com.atproto.moderation.defs#reasonMisleading", + "com.atproto.moderation.defs#reasonSexual", + "com.atproto.moderation.defs#reasonRude", + "com.atproto.moderation.defs#reasonOther" + ] + }, + "reasonSpam": { + "type": "token", + "description": "Spam: frequent unwanted promotion, replies, mentions" + }, + "reasonViolation": { + "type": "token", + "description": "Direct violation of server rules, laws, terms of service" + }, + "reasonMisleading": { + "type": "token", + "description": "Misleading identity, affiliation, or content" + }, + "reasonSexual": { + "type": "token", + "description": "Unwanted or mislabeled sexual content" + }, + "reasonRude": { + "type": "token", + "description": "Rude, harassing, explicit, or otherwise unwelcoming behavior" + }, + "reasonOther": { + "type": "token", + "description": "Other: reports not falling under another report category" } + } } diff --git a/lexicons/com/atproto/repo/applyWrites.json b/lexicons/com/atproto/repo/applyWrites.json index 6d3a375b29d..5c37340cf3f 100644 --- a/lexicons/com/atproto/repo/applyWrites.json +++ b/lexicons/com/atproto/repo/applyWrites.json @@ -23,7 +23,11 @@ }, "writes": { "type": "array", - "items": {"type": "union", "refs": ["#create", "#update", "#delete"], "closed": true} + "items": { + "type": "union", + "refs": ["#create", "#update", "#delete"], + "closed": true + } }, "swapCommit": { "type": "string", @@ -32,18 +36,16 @@ } } }, - "errors": [ - {"name": "InvalidSwap"} - ] + "errors": [{ "name": "InvalidSwap" }] }, "create": { "type": "object", "description": "Create a new record.", "required": ["collection", "value"], "properties": { - "collection": {"type": "string", "format": "nsid"}, - "rkey": {"type": "string", "maxLength": 15}, - "value": {"type": "unknown"} + "collection": { "type": "string", "format": "nsid" }, + "rkey": { "type": "string", "maxLength": 15 }, + "value": { "type": "unknown" } } }, "update": { @@ -51,9 +53,9 @@ "description": "Update an existing record.", "required": ["collection", "rkey", "value"], "properties": { - "collection": {"type": "string", "format": "nsid"}, - "rkey": {"type": "string"}, - "value": {"type": "unknown"} + "collection": { "type": "string", "format": "nsid" }, + "rkey": { "type": "string" }, + "value": { "type": "unknown" } } }, "delete": { @@ -61,8 +63,8 @@ "description": "Delete an existing record.", "required": ["collection", "rkey"], "properties": { - "collection": {"type": "string", "format": "nsid"}, - "rkey": {"type": "string"} + "collection": { "type": "string", "format": "nsid" }, + "rkey": { "type": "string" } } } } diff --git a/lexicons/com/atproto/repo/createRecord.json b/lexicons/com/atproto/repo/createRecord.json index 3988de24b00..e594db58d51 100644 --- a/lexicons/com/atproto/repo/createRecord.json +++ b/lexicons/com/atproto/repo/createRecord.json @@ -49,14 +49,12 @@ "type": "object", "required": ["uri", "cid"], "properties": { - "uri": {"type": "string", "format": "at-uri"}, - "cid": {"type": "string", "format": "cid"} + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" } } } }, - "errors": [ - {"name": "InvalidSwap"} - ] + "errors": [{ "name": "InvalidSwap" }] } } } diff --git a/lexicons/com/atproto/repo/deleteRecord.json b/lexicons/com/atproto/repo/deleteRecord.json index 3735b02c5a0..53ef72b190e 100644 --- a/lexicons/com/atproto/repo/deleteRecord.json +++ b/lexicons/com/atproto/repo/deleteRecord.json @@ -38,9 +38,7 @@ } } }, - "errors": [ - {"name": "InvalidSwap"} - ] + "errors": [{ "name": "InvalidSwap" }] } } } diff --git a/lexicons/com/atproto/repo/describeRepo.json b/lexicons/com/atproto/repo/describeRepo.json index 51ec1138b25..b7f283bff70 100644 --- a/lexicons/com/atproto/repo/describeRepo.json +++ b/lexicons/com/atproto/repo/describeRepo.json @@ -20,16 +20,25 @@ "encoding": "application/json", "schema": { "type": "object", - "required": ["handle", "did", "didDoc", "collections", "handleIsCorrect"], + "required": [ + "handle", + "did", + "didDoc", + "collections", + "handleIsCorrect" + ], "properties": { - "handle": {"type": "string", "format": "handle"}, - "did": {"type": "string", "format": "did"}, - "didDoc": {"type": "unknown"}, - "collections": {"type": "array", "items": {"type": "string", "format": "nsid"}}, - "handleIsCorrect": {"type": "boolean"} + "handle": { "type": "string", "format": "handle" }, + "did": { "type": "string", "format": "did" }, + "didDoc": { "type": "unknown" }, + "collections": { + "type": "array", + "items": { "type": "string", "format": "nsid" } + }, + "handleIsCorrect": { "type": "boolean" } } } } } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/repo/getRecord.json b/lexicons/com/atproto/repo/getRecord.json index 25032a522de..ec4d17e4260 100644 --- a/lexicons/com/atproto/repo/getRecord.json +++ b/lexicons/com/atproto/repo/getRecord.json @@ -19,7 +19,7 @@ "format": "nsid", "description": "The NSID of the record collection." }, - "rkey": {"type": "string", "description": "The key of the record."}, + "rkey": { "type": "string", "description": "The key of the record." }, "cid": { "type": "string", "format": "cid", @@ -33,9 +33,9 @@ "type": "object", "required": ["uri", "value"], "properties": { - "uri": {"type": "string", "format": "at-uri"}, - "cid": {"type": "string", "format": "cid"}, - "value": {"type": "unknown"} + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "value": { "type": "unknown" } } } } diff --git a/lexicons/com/atproto/repo/listRecords.json b/lexicons/com/atproto/repo/listRecords.json index 81c0d5a4270..8bcee4fcb58 100644 --- a/lexicons/com/atproto/repo/listRecords.json +++ b/lexicons/com/atproto/repo/listRecords.json @@ -9,13 +9,36 @@ "type": "params", "required": ["repo", "collection"], "properties": { - "repo": {"type": "string", "format": "at-identifier", "description": "The handle or DID of the repo."}, - "collection": {"type": "string", "format": "nsid", "description": "The NSID of the record type."}, - "limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50, "description": "The number of records to return."}, - "cursor": {"type": "string"}, - "rkeyStart": {"type": "string", "description": "DEPRECATED: The lowest sort-ordered rkey to start from (exclusive)"}, - "rkeyEnd": {"type": "string", "description": "DEPRECATED: The highest sort-ordered rkey to stop at (exclusive)"}, - "reverse": {"type": "boolean", "description": "Reverse the order of the returned records?"} + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo." + }, + "collection": { + "type": "string", + "format": "nsid", + "description": "The NSID of the record type." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50, + "description": "The number of records to return." + }, + "cursor": { "type": "string" }, + "rkeyStart": { + "type": "string", + "description": "DEPRECATED: The lowest sort-ordered rkey to start from (exclusive)" + }, + "rkeyEnd": { + "type": "string", + "description": "DEPRECATED: The highest sort-ordered rkey to stop at (exclusive)" + }, + "reverse": { + "type": "boolean", + "description": "Reverse the order of the returned records?" + } } }, "output": { @@ -24,10 +47,10 @@ "type": "object", "required": ["records"], "properties": { - "cursor": {"type": "string"}, + "cursor": { "type": "string" }, "records": { "type": "array", - "items": {"type": "ref", "ref": "#record"} + "items": { "type": "ref", "ref": "#record" } } } } @@ -37,10 +60,10 @@ "type": "object", "required": ["uri", "cid", "value"], "properties": { - "uri": {"type": "string", "format": "at-uri"}, - "cid": {"type": "string", "format": "cid"}, - "value": {"type": "unknown"} + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "value": { "type": "unknown" } } } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/repo/putRecord.json b/lexicons/com/atproto/repo/putRecord.json index d0067110fd5..118b41dc49c 100644 --- a/lexicons/com/atproto/repo/putRecord.json +++ b/lexicons/com/atproto/repo/putRecord.json @@ -55,14 +55,12 @@ "type": "object", "required": ["uri", "cid"], "properties": { - "uri": {"type": "string", "format": "at-uri"}, - "cid": {"type": "string", "format": "cid"} + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" } } } }, - "errors": [ - {"name": "InvalidSwap"} - ] + "errors": [{ "name": "InvalidSwap" }] } } } diff --git a/lexicons/com/atproto/repo/rebaseRepo.json b/lexicons/com/atproto/repo/rebaseRepo.json deleted file mode 100644 index ff278c0db3b..00000000000 --- a/lexicons/com/atproto/repo/rebaseRepo.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "lexicon": 1, - "id": "com.atproto.repo.rebaseRepo", - "defs": { - "main": { - "type": "procedure", - "description": "Simple rebase of repo that deletes history", - "input": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["repo"], - "properties": { - "repo": { - "type": "string", - "format": "at-identifier", - "description": "The handle or DID of the repo." - }, - "swapCommit": { - "type": "string", - "format": "cid", - "description": "Compare and swap with the previous commit by cid." - } - } - } - }, - "errors": [ - {"name": "InvalidSwap"}, - {"name": "ConcurrentWrites"} - ] - } - } -} diff --git a/lexicons/com/atproto/repo/strongRef.json b/lexicons/com/atproto/repo/strongRef.json index 3bac786b4f4..cb79625165c 100644 --- a/lexicons/com/atproto/repo/strongRef.json +++ b/lexicons/com/atproto/repo/strongRef.json @@ -7,9 +7,9 @@ "type": "object", "required": ["uri", "cid"], "properties": { - "uri": {"type": "string", "format": "at-uri"}, - "cid": {"type": "string", "format": "cid"} + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" } } } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/repo/uploadBlob.json b/lexicons/com/atproto/repo/uploadBlob.json index ab04c87e076..63d1671bd3e 100644 --- a/lexicons/com/atproto/repo/uploadBlob.json +++ b/lexicons/com/atproto/repo/uploadBlob.json @@ -14,7 +14,7 @@ "type": "object", "required": ["blob"], "properties": { - "blob": {"type": "blob"} + "blob": { "type": "blob" } } } } diff --git a/lexicons/com/atproto/server/createAccount.json b/lexicons/com/atproto/server/createAccount.json index 4aadb7b5b5f..9fd09740fda 100644 --- a/lexicons/com/atproto/server/createAccount.json +++ b/lexicons/com/atproto/server/createAccount.json @@ -11,12 +11,12 @@ "type": "object", "required": ["handle", "email", "password"], "properties": { - "email": {"type": "string"}, - "handle": {"type": "string", "format": "handle"}, - "did": {"type": "string", "format": "did"}, - "inviteCode": {"type": "string"}, - "password": {"type": "string"}, - "recoveryKey": {"type": "string"} + "email": { "type": "string" }, + "handle": { "type": "string", "format": "handle" }, + "did": { "type": "string", "format": "did" }, + "inviteCode": { "type": "string" }, + "password": { "type": "string" }, + "recoveryKey": { "type": "string" } } } }, @@ -34,14 +34,14 @@ } }, "errors": [ - {"name": "InvalidHandle"}, - {"name": "InvalidPassword"}, - {"name": "InvalidInviteCode"}, - {"name": "HandleNotAvailable"}, - {"name": "UnsupportedDomain"}, - {"name": "UnresolvableDid"}, - {"name": "IncompatibleDidDoc"} + { "name": "InvalidHandle" }, + { "name": "InvalidPassword" }, + { "name": "InvalidInviteCode" }, + { "name": "HandleNotAvailable" }, + { "name": "UnsupportedDomain" }, + { "name": "UnresolvableDid" }, + { "name": "IncompatibleDidDoc" } ] } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/server/createAppPassword.json b/lexicons/com/atproto/server/createAppPassword.json index ae7810e7e0e..4dfdcca1f6b 100644 --- a/lexicons/com/atproto/server/createAppPassword.json +++ b/lexicons/com/atproto/server/createAppPassword.json @@ -11,7 +11,7 @@ "type": "object", "required": ["name"], "properties": { - "name": {"type": "string"} + "name": { "type": "string" } } } }, @@ -22,17 +22,15 @@ "ref": "#appPassword" } }, - "errors": [ - {"name": "AccountTakedown"} - ] + "errors": [{ "name": "AccountTakedown" }] }, "appPassword": { "type": "object", "required": ["name", "password", "createdAt"], "properties": { - "name": {"type": "string"}, - "password": {"type": "string"}, - "createdAt": {"type": "string", "format": "datetime"} + "name": { "type": "string" }, + "password": { "type": "string" }, + "createdAt": { "type": "string", "format": "datetime" } } } } diff --git a/lexicons/com/atproto/server/createInviteCode.json b/lexicons/com/atproto/server/createInviteCode.json index 81a967d8a30..5ce5dbe39f9 100644 --- a/lexicons/com/atproto/server/createInviteCode.json +++ b/lexicons/com/atproto/server/createInviteCode.json @@ -11,8 +11,8 @@ "type": "object", "required": ["useCount"], "properties": { - "useCount": {"type": "integer"}, - "forAccount": {"type": "string", "format": "did"} + "useCount": { "type": "integer" }, + "forAccount": { "type": "string", "format": "did" } } } }, diff --git a/lexicons/com/atproto/server/createInviteCodes.json b/lexicons/com/atproto/server/createInviteCodes.json index b967ca2f0d3..827d798a088 100644 --- a/lexicons/com/atproto/server/createInviteCodes.json +++ b/lexicons/com/atproto/server/createInviteCodes.json @@ -11,11 +11,11 @@ "type": "object", "required": ["codeCount", "useCount"], "properties": { - "codeCount": {"type": "integer", "default": 1}, - "useCount": {"type": "integer"}, + "codeCount": { "type": "integer", "default": 1 }, + "useCount": { "type": "integer" }, "forAccounts": { "type": "array", - "items": {"type": "string", "format": "did"} + "items": { "type": "string", "format": "did" } } } } @@ -26,9 +26,9 @@ "type": "object", "required": ["codes"], "properties": { - "codes": { + "codes": { "type": "array", - "items": {"type": "ref", "ref": "#accountCodes"} + "items": { "type": "ref", "ref": "#accountCodes" } } } } @@ -38,10 +38,10 @@ "type": "object", "required": ["account", "codes"], "properties": { - "account": {"type": "string"}, - "codes": { + "account": { "type": "string" }, + "codes": { "type": "array", - "items": {"type": "string"} + "items": { "type": "string" } } } } diff --git a/lexicons/com/atproto/server/createSession.json b/lexicons/com/atproto/server/createSession.json index 0494d67a828..fc416ddabae 100644 --- a/lexicons/com/atproto/server/createSession.json +++ b/lexicons/com/atproto/server/createSession.json @@ -15,7 +15,7 @@ "type": "string", "description": "Handle or other identifier supported by the server for the authenticating user." }, - "password": {"type": "string"} + "password": { "type": "string" } } } }, @@ -25,17 +25,15 @@ "type": "object", "required": ["accessJwt", "refreshJwt", "handle", "did"], "properties": { - "accessJwt": {"type": "string"}, - "refreshJwt": {"type": "string"}, - "handle": {"type": "string", "format": "handle"}, - "did": {"type": "string", "format": "did"}, - "email": {"type": "string"} + "accessJwt": { "type": "string" }, + "refreshJwt": { "type": "string" }, + "handle": { "type": "string", "format": "handle" }, + "did": { "type": "string", "format": "did" }, + "email": { "type": "string" } } } }, - "errors": [ - {"name": "AccountTakedown"} - ] + "errors": [{ "name": "AccountTakedown" }] } } } diff --git a/lexicons/com/atproto/server/defs.json b/lexicons/com/atproto/server/defs.json index beb9954bb5b..8686d4d410c 100644 --- a/lexicons/com/atproto/server/defs.json +++ b/lexicons/com/atproto/server/defs.json @@ -4,17 +4,25 @@ "defs": { "inviteCode": { "type": "object", - "required": ["code", "available", "disabled", "forAccount", "createdBy", "createdAt", "uses"], + "required": [ + "code", + "available", + "disabled", + "forAccount", + "createdBy", + "createdAt", + "uses" + ], "properties": { - "code": {"type": "string"}, - "available": {"type": "integer"}, - "disabled": {"type": "boolean"}, - "forAccount": {"type": "string"}, - "createdBy": {"type": "string"}, - "createdAt": {"type": "string", "format": "datetime"}, + "code": { "type": "string" }, + "available": { "type": "integer" }, + "disabled": { "type": "boolean" }, + "forAccount": { "type": "string" }, + "createdBy": { "type": "string" }, + "createdAt": { "type": "string", "format": "datetime" }, "uses": { "type": "array", - "items": {"type": "ref", "ref": "#inviteCodeUse"} + "items": { "type": "ref", "ref": "#inviteCodeUse" } } } }, @@ -22,8 +30,8 @@ "type": "object", "required": ["usedBy", "usedAt"], "properties": { - "usedBy": {"type": "string", "format": "did"}, - "usedAt": {"type": "string", "format": "datetime"} + "usedBy": { "type": "string", "format": "did" }, + "usedAt": { "type": "string", "format": "datetime" } } } } diff --git a/lexicons/com/atproto/server/deleteAccount.json b/lexicons/com/atproto/server/deleteAccount.json index f273ebb5fdf..2cef799aefe 100644 --- a/lexicons/com/atproto/server/deleteAccount.json +++ b/lexicons/com/atproto/server/deleteAccount.json @@ -9,7 +9,7 @@ "encoding": "application/json", "schema": { "type": "object", - "required": ["did", "password", "token"], + "required": ["did", "password", "token"], "properties": { "did": { "type": "string", "format": "did" }, "password": { "type": "string" }, @@ -20,4 +20,4 @@ "errors": [{ "name": "ExpiredToken" }, { "name": "InvalidToken" }] } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/server/deleteSession.json b/lexicons/com/atproto/server/deleteSession.json index de044bbdb6d..e05d019024a 100644 --- a/lexicons/com/atproto/server/deleteSession.json +++ b/lexicons/com/atproto/server/deleteSession.json @@ -7,4 +7,4 @@ "description": "Delete the current session." } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/server/describeServer.json b/lexicons/com/atproto/server/describeServer.json index dc291e5fc95..b19b1504020 100644 --- a/lexicons/com/atproto/server/describeServer.json +++ b/lexicons/com/atproto/server/describeServer.json @@ -11,9 +11,12 @@ "type": "object", "required": ["availableUserDomains"], "properties": { - "inviteCodeRequired": {"type": "boolean"}, - "availableUserDomains": {"type": "array", "items": {"type": "string"}}, - "links": {"type": "ref", "ref": "#links"} + "inviteCodeRequired": { "type": "boolean" }, + "availableUserDomains": { + "type": "array", + "items": { "type": "string" } + }, + "links": { "type": "ref", "ref": "#links" } } } } @@ -21,9 +24,9 @@ "links": { "type": "object", "properties": { - "privacyPolicy": {"type": "string"}, - "termsOfService": {"type": "string"} + "privacyPolicy": { "type": "string" }, + "termsOfService": { "type": "string" } } } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/server/getAccountInviteCodes.json b/lexicons/com/atproto/server/getAccountInviteCodes.json index ff6e4d6d1cb..a99f78e0d7b 100644 --- a/lexicons/com/atproto/server/getAccountInviteCodes.json +++ b/lexicons/com/atproto/server/getAccountInviteCodes.json @@ -28,9 +28,7 @@ } } }, - "errors": [ - {"name": "DuplicateCreate"} - ] + "errors": [{ "name": "DuplicateCreate" }] } } } diff --git a/lexicons/com/atproto/server/getSession.json b/lexicons/com/atproto/server/getSession.json index db790f39e05..55b129be3df 100644 --- a/lexicons/com/atproto/server/getSession.json +++ b/lexicons/com/atproto/server/getSession.json @@ -11,12 +11,12 @@ "type": "object", "required": ["handle", "did"], "properties": { - "handle": {"type": "string", "format": "handle"}, - "did": {"type": "string", "format": "did"}, - "email": {"type": "string"} + "handle": { "type": "string", "format": "handle" }, + "did": { "type": "string", "format": "did" }, + "email": { "type": "string" } } } } } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/server/listAppPasswords.json b/lexicons/com/atproto/server/listAppPasswords.json index 613ca17ae43..f50a13d6b82 100644 --- a/lexicons/com/atproto/server/listAppPasswords.json +++ b/lexicons/com/atproto/server/listAppPasswords.json @@ -13,21 +13,19 @@ "properties": { "passwords": { "type": "array", - "items": {"type": "ref", "ref": "#appPassword"} + "items": { "type": "ref", "ref": "#appPassword" } } } } }, - "errors": [ - {"name": "AccountTakedown"} - ] + "errors": [{ "name": "AccountTakedown" }] }, "appPassword": { "type": "object", "required": ["name", "createdAt"], "properties": { - "name": {"type": "string"}, - "createdAt": {"type": "string", "format": "datetime"} + "name": { "type": "string" }, + "createdAt": { "type": "string", "format": "datetime" } } } } diff --git a/lexicons/com/atproto/server/refreshSession.json b/lexicons/com/atproto/server/refreshSession.json index 18ab3ed4777..ab895a34c94 100644 --- a/lexicons/com/atproto/server/refreshSession.json +++ b/lexicons/com/atproto/server/refreshSession.json @@ -11,16 +11,14 @@ "type": "object", "required": ["accessJwt", "refreshJwt", "handle", "did"], "properties": { - "accessJwt": {"type": "string"}, - "refreshJwt": {"type": "string"}, - "handle": {"type": "string", "format": "handle"}, - "did": {"type": "string", "format": "did"} + "accessJwt": { "type": "string" }, + "refreshJwt": { "type": "string" }, + "handle": { "type": "string", "format": "handle" }, + "did": { "type": "string", "format": "did" } } } }, - "errors": [ - {"name": "AccountTakedown"} - ] + "errors": [{ "name": "AccountTakedown" }] } } } diff --git a/lexicons/com/atproto/server/requestAccountDelete.json b/lexicons/com/atproto/server/requestAccountDelete.json index fcb9a869d5d..aaac0b70ec9 100644 --- a/lexicons/com/atproto/server/requestAccountDelete.json +++ b/lexicons/com/atproto/server/requestAccountDelete.json @@ -7,4 +7,4 @@ "description": "Initiate a user account deletion via email." } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/server/revokeAppPassword.json b/lexicons/com/atproto/server/revokeAppPassword.json index 265f89544e7..52094c4459c 100644 --- a/lexicons/com/atproto/server/revokeAppPassword.json +++ b/lexicons/com/atproto/server/revokeAppPassword.json @@ -11,7 +11,7 @@ "type": "object", "required": ["name"], "properties": { - "name": {"type": "string"} + "name": { "type": "string" } } } } diff --git a/lexicons/com/atproto/sync/getBlob.json b/lexicons/com/atproto/sync/getBlob.json index 9b29b16a6e8..23e18a4f3b5 100644 --- a/lexicons/com/atproto/sync/getBlob.json +++ b/lexicons/com/atproto/sync/getBlob.json @@ -9,8 +9,16 @@ "type": "params", "required": ["did", "cid"], "properties": { - "did": {"type": "string", "format": "did", "description": "The DID of the repo."}, - "cid": {"type": "string", "format": "cid", "description": "The CID of the blob to fetch"} + "did": { + "type": "string", + "format": "did", + "description": "The DID of the repo." + }, + "cid": { + "type": "string", + "format": "cid", + "description": "The CID of the blob to fetch" + } } }, "output": { diff --git a/lexicons/com/atproto/sync/getBlocks.json b/lexicons/com/atproto/sync/getBlocks.json index cddacb56ad5..0b6c25f8252 100644 --- a/lexicons/com/atproto/sync/getBlocks.json +++ b/lexicons/com/atproto/sync/getBlocks.json @@ -15,8 +15,8 @@ "description": "The DID of the repo." }, "cids": { - "type": "array", - "items": {"type": "string", "format": "cid"} + "type": "array", + "items": { "type": "string", "format": "cid" } } } }, @@ -25,4 +25,4 @@ } } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/sync/getCheckout.json b/lexicons/com/atproto/sync/getCheckout.json index 16b2330c6fc..b3fb375e5db 100644 --- a/lexicons/com/atproto/sync/getCheckout.json +++ b/lexicons/com/atproto/sync/getCheckout.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Gets the repo state.", + "description": "DEPRECATED - please use com.atproto.sync.getRepo instead", "parameters": { "type": "params", "required": ["did"], @@ -13,11 +13,6 @@ "type": "string", "format": "did", "description": "The DID of the repo." - }, - "commit": { - "type": "string", - "format": "cid", - "description": "The commit to get the checkout from. Defaults to current HEAD." } } }, @@ -26,4 +21,4 @@ } } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/sync/getCommitPath.json b/lexicons/com/atproto/sync/getCommitPath.json deleted file mode 100644 index d011595393c..00000000000 --- a/lexicons/com/atproto/sync/getCommitPath.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "lexicon": 1, - "id": "com.atproto.sync.getCommitPath", - "defs": { - "main": { - "type": "query", - "description": "Gets the path of repo commits", - "parameters": { - "type": "params", - "required": ["did"], - "properties": { - "did": { - "type": "string", - "format": "did", - "description": "The DID of the repo." - }, - "latest": { - "type": "string", - "format": "cid", - "description": "The most recent commit" - }, - "earliest": { - "type": "string", - "format": "cid", - "description": "The earliest commit to start from" - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["commits"], - "properties": { - "commits": { - "type": "array", - "items": { "type": "string", "format": "cid" } - } - } - } - } - } - } -} \ No newline at end of file diff --git a/lexicons/com/atproto/sync/getHead.json b/lexicons/com/atproto/sync/getHead.json index 38bfe22bce9..31ed72cbae1 100644 --- a/lexicons/com/atproto/sync/getHead.json +++ b/lexicons/com/atproto/sync/getHead.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Gets the current HEAD CID of a repo.", + "description": "DEPRECATED - please use com.atproto.sync.getLatestCommit instead", "parameters": { "type": "params", "required": ["did"], @@ -22,13 +22,11 @@ "type": "object", "required": ["root"], "properties": { - "root": {"type": "string", "format": "cid"} + "root": { "type": "string", "format": "cid" } } } }, - "errors": [ - {"name": "HeadNotFound"} - ] + "errors": [{ "name": "HeadNotFound" }] } } } diff --git a/lexicons/com/atproto/sync/getLatestCommit.json b/lexicons/com/atproto/sync/getLatestCommit.json new file mode 100644 index 00000000000..602cc2dac59 --- /dev/null +++ b/lexicons/com/atproto/sync/getLatestCommit.json @@ -0,0 +1,33 @@ +{ + "lexicon": 1, + "id": "com.atproto.sync.getLatestCommit", + "defs": { + "main": { + "type": "query", + "description": "Gets the current commit CID & revision of the repo.", + "parameters": { + "type": "params", + "required": ["did"], + "properties": { + "did": { + "type": "string", + "format": "did", + "description": "The DID of the repo." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["cid", "rev"], + "properties": { + "cid": { "type": "string", "format": "cid" }, + "rev": { "type": "string" } + } + } + }, + "errors": [{ "name": "RepoNotFound" }] + } + } +} diff --git a/lexicons/com/atproto/sync/getRecord.json b/lexicons/com/atproto/sync/getRecord.json index 6ff65cfbc25..ea14ba0f75e 100644 --- a/lexicons/com/atproto/sync/getRecord.json +++ b/lexicons/com/atproto/sync/getRecord.json @@ -14,8 +14,8 @@ "format": "did", "description": "The DID of the repo." }, - "collection": {"type": "string", "format": "nsid"}, - "rkey": {"type": "string" }, + "collection": { "type": "string", "format": "nsid" }, + "rkey": { "type": "string" }, "commit": { "type": "string", "format": "cid", @@ -28,4 +28,4 @@ } } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/sync/getRepo.json b/lexicons/com/atproto/sync/getRepo.json index bf0991db2b4..6bb6de2b1e4 100644 --- a/lexicons/com/atproto/sync/getRepo.json +++ b/lexicons/com/atproto/sync/getRepo.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Gets the repo state.", + "description": "Gets the did's repo, optionally catching up from a specific revision.", "parameters": { "type": "params", "required": ["did"], @@ -14,15 +14,9 @@ "format": "did", "description": "The DID of the repo." }, - "earliest": { + "since": { "type": "string", - "format": "cid", - "description": "The earliest commit in the commit range (not inclusive)" - }, - "latest": { - "type": "string", - "format": "cid", - "description": "The latest commit in the commit range (inclusive)" + "description": "The revision of the repo to catch up from." } } }, diff --git a/lexicons/com/atproto/sync/listBlobs.json b/lexicons/com/atproto/sync/listBlobs.json index 8939a5ee074..9f25b1330a6 100644 --- a/lexicons/com/atproto/sync/listBlobs.json +++ b/lexicons/com/atproto/sync/listBlobs.json @@ -4,14 +4,27 @@ "defs": { "main": { "type": "query", - "description": "List blob cids for some range of commits", + "description": "List blob cids since some revision", "parameters": { "type": "params", "required": ["did"], "properties": { - "did": {"type": "string", "format": "did", "description": "The DID of the repo."}, - "latest": { "type": "string", "format": "cid", "description": "The most recent commit"}, - "earliest": { "type": "string", "format": "cid", "description": "The earliest commit to start from"} + "did": { + "type": "string", + "format": "did", + "description": "The DID of the repo." + }, + "since": { + "type": "string", + "description": "Optional revision of the repo to list blobs since" + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 500 + }, + "cursor": { "type": "string" } } }, "output": { @@ -20,6 +33,7 @@ "type": "object", "required": ["cids"], "properties": { + "cursor": { "type": "string" }, "cids": { "type": "array", "items": { "type": "string", "format": "cid" } @@ -29,4 +43,4 @@ } } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/sync/listRepos.json b/lexicons/com/atproto/sync/listRepos.json index e54f74e50a1..9fdd57a55cd 100644 --- a/lexicons/com/atproto/sync/listRepos.json +++ b/lexicons/com/atproto/sync/listRepos.json @@ -8,8 +8,13 @@ "parameters": { "type": "params", "properties": { - "limit": {"type": "integer", "minimum": 1, "maximum": 1000, "default": 500}, - "cursor": {"type": "string"} + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 500 + }, + "cursor": { "type": "string" } } }, "output": { @@ -18,10 +23,10 @@ "type": "object", "required": ["repos"], "properties": { - "cursor": {"type": "string"}, + "cursor": { "type": "string" }, "repos": { "type": "array", - "items": {"type": "ref", "ref": "#repo"} + "items": { "type": "ref", "ref": "#repo" } } } } @@ -36,4 +41,4 @@ } } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/sync/notifyOfUpdate.json b/lexicons/com/atproto/sync/notifyOfUpdate.json index a5449b152fe..caca6fe9f30 100644 --- a/lexicons/com/atproto/sync/notifyOfUpdate.json +++ b/lexicons/com/atproto/sync/notifyOfUpdate.json @@ -11,10 +11,13 @@ "type": "object", "required": ["hostname"], "properties": { - "hostname": {"type": "string", "description": "Hostname of the service that is notifying of update."} + "hostname": { + "type": "string", + "description": "Hostname of the service that is notifying of update." + } } } } } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/sync/requestCrawl.json b/lexicons/com/atproto/sync/requestCrawl.json index 1bd09829002..a3520a33180 100644 --- a/lexicons/com/atproto/sync/requestCrawl.json +++ b/lexicons/com/atproto/sync/requestCrawl.json @@ -11,10 +11,13 @@ "type": "object", "required": ["hostname"], "properties": { - "hostname": {"type": "string", "description": "Hostname of the service that is requesting to be crawled."} + "hostname": { + "type": "string", + "description": "Hostname of the service that is requesting to be crawled." + } } } } } } -} \ No newline at end of file +} diff --git a/lexicons/com/atproto/sync/subscribeRepos.json b/lexicons/com/atproto/sync/subscribeRepos.json index 04a633447ca..b8feecad5b8 100644 --- a/lexicons/com/atproto/sync/subscribeRepos.json +++ b/lexicons/com/atproto/sync/subscribeRepos.json @@ -17,56 +17,67 @@ "message": { "schema": { "type": "union", - "refs": [ - "#commit", - "#handle", - "#migrate", - "#tombstone", - "#info" - ] + "refs": ["#commit", "#handle", "#migrate", "#tombstone", "#info"] } }, - "errors": [ - {"name": "FutureCursor"}, - {"name": "ConsumerTooSlow"} - ] + "errors": [{ "name": "FutureCursor" }, { "name": "ConsumerTooSlow" }] }, "commit": { "type": "object", - "required": ["seq", "rebase", "tooBig", "repo", "commit", "prev", "blocks", "ops", "blobs", "time"], - "nullable": ["prev"], + "required": [ + "seq", + "rebase", + "tooBig", + "repo", + "commit", + "rev", + "since", + "blocks", + "ops", + "blobs", + "time" + ], + "nullable": ["prev", "since"], "properties": { - "seq": {"type": "integer"}, - "rebase": {"type": "boolean"}, - "tooBig": {"type": "boolean"}, - "repo": {"type": "string", "format": "did"}, - "commit": {"type": "cid-link"}, - "prev": {"type": "cid-link"}, + "seq": { "type": "integer" }, + "rebase": { "type": "boolean" }, + "tooBig": { "type": "boolean" }, + "repo": { "type": "string", "format": "did" }, + "commit": { "type": "cid-link" }, + "prev": { "type": "cid-link" }, + "rev": { + "type": "string", + "description": "The rev of the emitted commit" + }, + "since": { + "type": "string", + "description": "The rev of the last emitted commit from this repo" + }, "blocks": { "type": "bytes", "description": "CAR file containing relevant blocks", "maxLength": 1000000 }, "ops": { - "type": "array", - "items": { "type": "ref", "ref": "#repoOp"}, + "type": "array", + "items": { "type": "ref", "ref": "#repoOp" }, "maxLength": 200 }, "blobs": { "type": "array", - "items": {"type": "cid-link"} + "items": { "type": "cid-link" } }, - "time": {"type": "string", "format": "datetime"} + "time": { "type": "string", "format": "datetime" } } }, "handle": { "type": "object", "required": ["seq", "did", "handle", "time"], "properties": { - "seq": {"type": "integer"}, - "did": {"type": "string", "format": "did"}, - "handle": {"type": "string", "format": "handle"}, - "time": {"type": "string", "format": "datetime"} + "seq": { "type": "integer" }, + "did": { "type": "string", "format": "did" }, + "handle": { "type": "string", "format": "handle" }, + "time": { "type": "string", "format": "datetime" } } }, "migrate": { @@ -74,19 +85,19 @@ "required": ["seq", "did", "migrateTo", "time"], "nullable": ["migrateTo"], "properties": { - "seq": {"type": "integer"}, - "did": {"type": "string", "format": "did"}, - "migrateTo": {"type": "string"}, - "time": {"type": "string", "format": "datetime"} + "seq": { "type": "integer" }, + "did": { "type": "string", "format": "did" }, + "migrateTo": { "type": "string" }, + "time": { "type": "string", "format": "datetime" } } }, "tombstone": { "type": "object", "required": ["seq", "did", "time"], "properties": { - "seq": {"type": "integer"}, - "did": {"type": "string", "format": "did"}, - "time": {"type": "string", "format": "datetime"} + "seq": { "type": "integer" }, + "did": { "type": "string", "format": "did" }, + "time": { "type": "string", "format": "datetime" } } }, "info": { @@ -104,20 +115,17 @@ }, "repoOp": { "type": "object", + "description": "A repo operation, ie a write of a single record. For creates and updates, cid is the record's CID as of this operation. For deletes, it's null.", "required": ["action", "path", "cid"], "nullable": ["cid"], "properties": { "action": { "type": "string", - "knownValues": [ - "create", - "update", - "delete" - ] + "knownValues": ["create", "update", "delete"] }, - "path": {"type": "string"}, - "cid": {"type": "cid-link"} + "path": { "type": "string" }, + "cid": { "type": "cid-link" } } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 2c1337435b3..71b802b991f 100644 --- a/package.json +++ b/package.json @@ -9,16 +9,23 @@ "node": ">=18" }, "scripts": { - "prepublish": "yarn build", - "verify": "lerna run verify --stream", - "prettier": "lerna run prettier", - "build": "lerna run build", - "test": "LOG_ENABLED=false NODE_ENV=development ./packages/pg/with-test-db.sh lerna run test --stream", - "test:withFlags": "LOG_ENABLED=false NODE_ENV=development ./packages/pg/with-test-db.sh lerna run test --stream --" + "lint:fix": "pnpm lint --fix", + "lint": "eslint . --ext .ts,.tsx", + "verify": "prettier --check . && pnpm lint", + "format": "prettier --write .", + "build": "pnpm -r --stream build", + "update-main-to-dist": "pnpm -r --stream update-main-to-dist", + "test": "LOG_ENABLED=false NODE_ENV=development ./packages/dev-infra/with-test-redis-and-db.sh pnpm --stream -r test", + "test:withFlags": "LOG_ENABLED=false NODE_ENV=development ./packages/dev-infra/with-test-redis-and-db.sh pnpm --stream -r test --", + "changeset": "changeset", + "release": "pnpm build && changeset publish", + "version-packages": "changeset version && git add ." }, "devDependencies": { "@babel/core": "^7.18.6", "@babel/preset-env": "^7.18.6", + "@changesets/changelog-github": "^0.4.8", + "@changesets/cli": "^2.26.2", "@npmcli/package-json": "^3.0.0", "@swc/core": "^1.3.42", "@swc/jest": "^0.2.24", @@ -34,10 +41,9 @@ "eslint": "^8.24.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", + "get-port": "^6.1.2", "jest": "^28.1.2", - "lerna": "^4.0.0", "node-gyp": "^9.3.1", - "npm-run-all": "^4.1.5", "pino-pretty": "^9.1.0", "prettier": "^2.7.1", "prettier-config-standard": "^5.0.0", diff --git a/packages/README.md b/packages/README.md index 4f2a8bdd972..68835de45f7 100644 --- a/packages/README.md +++ b/packages/README.md @@ -11,11 +11,9 @@ - [API](./api): A library for communicating with atproto servers. - [Common](./common): A library containing code which is shared between atproto packages. - [Crypto](./crypto): Atproto's common cryptographic operations. -- [Identity](./identity): A library for resolving atproto DIDs and handles. +- [Syntax](./syntax): A library for identifier syntax: NSID, AT URI, handles, etc. - [Lexicon](./lexicon): A library for validating data using atproto's schema system. -- [NSID](./nsid): A parser and generator of NSIDs. - [Repo](./repo): The "atproto repository" core implementation (a Merkle Search Tree). -- [URI](./uri): A parser and generator of `at://` uris. - [XRPC](./xrpc): An XRPC client implementation. - [XRPC Server](./xrpc-server): An XRPC server implementation. @@ -23,12 +21,12 @@ Only applicable to packages which contain benchmarks(`jest.bench.config.js`). -You can run benchmarks with `yarn bench`. +You can run benchmarks with `pnpm bench`. ### Attaching a profiler -Running `yarn bench:profile` will launch `bench` with `--inspect-brk` flag. -Execution will be paused until a debugger is attached, you can read more +Running `pnpm bench:profile` will launch `bench` with `--inspect-brk` flag. +Execution will be paused until a debugger is attached, you can read more about node debuggers [here](https://nodejs.org/en/docs/guides/debugging-getting-started#inspector-clients) An easy way to profile is: diff --git a/packages/api/README.md b/packages/api/README.md index 62fa8db924f..fdfcbc48b73 100644 --- a/packages/api/README.md +++ b/packages/api/README.md @@ -34,16 +34,16 @@ import { BskyAgent, AtpSessionEvent, AtpSessionData } from '@atproto/api' const agent = new BskyAgent({ service: 'https://example.com', persistSession: (evt: AtpSessionEvent, sess?: AtpSessionData) => { - // store the session-data for reuse - } + // store the session-data for reuse + }, }) -await agent.login({identifier: 'alice@mail.com', password: 'hunter2'}) +await agent.login({ identifier: 'alice@mail.com', password: 'hunter2' }) await agent.resumeSession(savedSessionData) await agent.createAccount({ email: 'alice@mail.com', password: 'hunter2', - handle: 'alice.example.com' + handle: 'alice.example.com', }) ``` @@ -127,16 +127,18 @@ Some records (ie posts) use the `app.bsky.richtext` lexicon. At the moment richt ℹ️ It is **strongly** recommended to use this package's `RichText` library. Javascript encodes strings in utf16 while the protocol (and most other programming environments) use utf8. Converting between the two is challenging, but `RichText` handles that for you. ```typescript -import {RichText} from '@atproto/api' +import { RichText } from '@atproto/api' // creating richtext -const rt = new RichText({text: 'Hello @alice.com, check out this link: https://example.com'}) +const rt = new RichText({ + text: 'Hello @alice.com, check out this link: https://example.com', +}) await rt.detectFacets(agent) // automatically detects mentions and links const postRecord = { $type: 'app.bsky.feed.post', text: rt.text, facets: rt.facets, - createdAt: new Date().toISOString() + createdAt: new Date().toISOString(), } // rendering as markdown @@ -152,14 +154,103 @@ for (const segment of rt.segments()) { } // calculating string lengths -const rt2 = new RichText({text: 'Hello'}) +const rt2 = new RichText({ text: 'Hello' }) console.log(rt2.length) // => 5 console.log(rt2.graphemeLength) // => 5 -const rt3 = new RichText({text: '👨‍👩‍👧‍👧'}) +const rt3 = new RichText({ text: '👨‍👩‍👧‍👧' }) console.log(rt3.length) // => 25 console.log(rt3.graphemeLength) // => 1 ``` +### Moderation + +Applying the moderation system is a challenging task, but we've done our best to simplify it for you. The Moderation API helps handle a wide range of tasks, including: + +- User muting (including mutelists) +- User blocking +- Moderator labeling + +For more information, see the [Moderation Documentation](./docs/moderation.md) or the associated [Labels Reference](./docs/labels.md). + +```typescript +import { moderatePost, moderateProfile } from '@atproto/api' + +// We call the appropriate moderation function for the content +// = + +const postMod = moderatePost(postView, getOpts()) +const profileMod = moderateProfile(profileView, getOpts()) + +// We then use the output to decide how to affect rendering +// = + +if (postMod.content.filter) { + // dont render in feeds or similar + // in contexts where this is disruptive (eg threads) you should ignore this and instead check blur +} +if (postMod.content.blur) { + // render the whole object behind a cover (use postMod.content.cause to explain) + if (postMod.content.noOverride) { + // do not allow the cover the be removed + } +} +if (postMod.content.alert) { + // render a warning on the content (use postMod.content.cause to explain) +} +if (postMod.embed.blur) { + // render the embedded media behind a cover (use postMod.embed.cause to explain) + if (postMod.embed.noOverride) { + // do not allow the cover the be removed + } +} +if (postMod.embed.alert) { + // render a warning on the embedded media (use postMod.embed.cause to explain) +} +if (postMod.avatar.blur) { + // render the avatar behind a cover +} +if (postMod.avatar.alert) { + // render an alert on the avatar +} + +// The options passed into `apply()` supply the user's preferences +// = + +function getOpts() { + return { + // the logged-in user's DID + userDid: 'did:plc:1234...', + + // is adult content allowed? + adultContentEnabled: true, + + // the global label settings (used on self-labels) + labels: { + porn: 'hide', + sexual: 'warn', + nudity: 'ignore', + // ... + }, + + // the per-labeler settings + labelers: [ + { + labeler: { + did: '...', + displayName: 'My mod service', + }, + labels: { + porn: 'hide', + sexual: 'warn', + nudity: 'ignore', + // ... + }, + }, + ], + } +} +``` + ## Advanced ### Advanced API calls @@ -169,24 +260,28 @@ The methods above are convenience wrappers. It covers most but not all available The AT Protocol identifies methods and records with reverse-DNS names. You can use them on the agent as well: ```typescript -const res1 = await agent.com.atproto.repo.createRecord( +const res1 = await agent.com.atproto.repo.createRecord({ + did: alice.did, + collection: 'app.bsky.feed.post', + record: { + $type: 'app.bsky.feed.post', + text: 'Hello, world!', + createdAt: new Date().toISOString(), + }, +}) +const res2 = await agent.com.atproto.repo.listRecords({ + repo: alice.did, + collection: 'app.bsky.feed.post', +}) + +const res3 = await agent.app.bsky.feed.post.create( + { repo: alice.did }, { - did: alice.did, - collection: 'app.bsky.feed.post', - record: { - $type: 'app.bsky.feed.post', - text: 'Hello, world!', - createdAt: new Date().toISOString() - } - } + text: 'Hello, world!', + createdAt: new Date().toISOString(), + }, ) -const res2 = await agent.com.atproto.repo.listRecords({repo: alice.did, collection: 'app.bsky.feed.post'}) - -const res3 = await agent.app.bsky.feed.post.create({repo: alice.did}, { - text: 'Hello, world!', - createdAt: new Date().toISOString() -}) -const res4 = await agent.app.bsky.feed.post.list({repo: alice.did}) +const res4 = await agent.app.bsky.feed.post.list({ repo: alice.did }) ``` ### Generic agent @@ -196,7 +291,7 @@ If you want a generic AT Protocol agent without methods related to the Bluesky s ```typescript import { AtpAgent } from '@atproto/api' -const agent = new AtpAgent({service: 'https://example.com'}) +const agent = new AtpAgent({ service: 'https://example.com' }) ``` ### Non-browser configuration @@ -206,10 +301,13 @@ In non-browser environments you'll need to specify a fetch polyfill. [See the ex ```typescript import { BskyAgent } from '@atproto/api' -const agent = new BskyAgent({service: 'https://example.com'}) +const agent = new BskyAgent({ service: 'https://example.com' }) // provide a custom fetch implementation (shouldnt be needed in node or the browser) -import {AtpAgentFetchHeaders, AtpAgentFetchHandlerResponse} from '@atproto/api' +import { + AtpAgentFetchHeaders, + AtpAgentFetchHandlerResponse, +} from '@atproto/api' BskyAgent.configure({ async fetch( httpUri: string, @@ -218,8 +316,8 @@ BskyAgent.configure({ httpReqBody: any, ): Promise { // insert definition here... - return {status: 200, /*...*/} - } + return { status: 200 /*...*/ } + }, }) ``` diff --git a/packages/api/bench/agent.bench.ts b/packages/api/bench/agent.bench.ts index 22905e19f06..333fbd49a20 100644 --- a/packages/api/bench/agent.bench.ts +++ b/packages/api/bench/agent.bench.ts @@ -1,9 +1,9 @@ -import { BskyAgent } from "@atproto/api"; +import { BskyAgent } from '@atproto/api' describe('Agent Benchmarks', () => { it('Creates new Agent instance 10 times', () => { for (let i = 0; i < 10; i++) { - new BskyAgent({ service: 'https://bsky.social' }); + new BskyAgent({ service: 'https://bsky.social' }) } }) }) diff --git a/packages/api/build.js b/packages/api/build.js index 5570ef4ee72..30fbe7cea56 100644 --- a/packages/api/build.js +++ b/packages/api/build.js @@ -1,16 +1,8 @@ -const pkgJson = require('@npmcli/package-json') const { nodeExternalsPlugin } = require('esbuild-node-externals') const buildShallow = process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true' -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} - require('esbuild').build({ logLevel: 'info', entryPoints: ['src/index.ts'], diff --git a/packages/api/definitions/labels.json b/packages/api/definitions/labels.json new file mode 100644 index 00000000000..91b5dd43cba --- /dev/null +++ b/packages/api/definitions/labels.json @@ -0,0 +1,218 @@ +[ + { + "id": "system", + "configurable": false, + "labels": [ + { + "id": "!hide", + "preferences": ["hide"], + "flags": ["no-override"], + "onwarn": "blur" + }, + { + "id": "!no-promote", + "preferences": ["hide"], + "flags": [], + "onwarn": null + }, + { + "id": "!warn", + "preferences": ["warn"], + "flags": [], + "onwarn": "blur" + } + ] + }, + { + "id": "legal", + "configurable": false, + "labels": [ + { + "id": "dmca-violation", + "preferences": ["hide"], + "flags": ["no-override"], + "onwarn": "blur" + }, + { + "id": "doxxing", + "preferences": ["hide"], + "flags": ["no-override"], + "onwarn": "blur" + } + ] + }, + { + "id": "sexual", + "configurable": true, + "labels": [ + { + "id": "porn", + "preferences": ["ignore", "warn", "hide"], + "flags": ["adult"], + "onwarn": "blur-media" + }, + { + "id": "sexual", + "preferences": ["ignore", "warn", "hide"], + "flags": ["adult"], + "onwarn": "blur-media" + }, + { + "id": "nudity", + "preferences": ["ignore", "warn", "hide"], + "flags": ["adult"], + "onwarn": "blur-media" + } + ] + }, + { + "id": "violence", + "configurable": true, + "labels": [ + { + "id": "nsfl", + "preferences": ["ignore", "warn", "hide"], + "flags": ["adult"], + "onwarn": "blur-media" + }, + { + "id": "corpse", + "preferences": ["ignore", "warn", "hide"], + "flags": ["adult"], + "onwarn": "blur-media" + }, + { + "id": "gore", + "preferences": ["ignore", "warn", "hide"], + "flags": ["adult"], + "onwarn": "blur-media" + }, + { + "id": "torture", + "preferences": ["ignore", "warn", "hide"], + "flags": ["adult"], + "onwarn": "blur" + }, + { + "id": "self-harm", + "preferences": ["ignore", "warn", "hide"], + "flags": ["adult"], + "onwarn": "blur-media" + } + ] + }, + { + "id": "intolerance", + "configurable": true, + "labels": [ + { + "id": "intolerant-race", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "intolerant-gender", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "intolerant-sexual-orientation", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "intolerant-religion", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "intolerant", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "icon-intolerant", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur-media" + } + ] + }, + { + "id": "rude", + "configurable": true, + "labels": [ + { + "id": "threat", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + } + ] + }, + { + "id": "curation", + "configurable": true, + "labels": [ + { + "id": "spoiler", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + } + ] + }, + { + "id": "spam", + "configurable": true, + "labels": [ + { + "id": "spam", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + } + ] + }, + { + "id": "misinfo", + "configurable": true, + "labels": [ + { + "id": "account-security", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "net-abuse", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "impersonation", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "alert" + }, + { + "id": "scam", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "alert" + }, + { + "id": "misleading", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "alert" + } + ] + } +] diff --git a/packages/api/definitions/locale/en/label-groups.json b/packages/api/definitions/locale/en/label-groups.json new file mode 100644 index 00000000000..06cc6699a7b --- /dev/null +++ b/packages/api/definitions/locale/en/label-groups.json @@ -0,0 +1,38 @@ +{ + "system": { + "name": "System", + "description": "Moderator overrides for special cases." + }, + "legal": { + "name": "Legal", + "description": "Content removed for legal reasons." + }, + "sexual": { + "name": "Adult Content", + "description": "Content which is sexual in nature." + }, + "violence": { + "name": "Violence", + "description": "Content which is violent or deeply disturbing." + }, + "intolerance": { + "name": "Intolerance", + "description": "Content or behavior which is hateful or intolerant toward a group of people." + }, + "rude": { + "name": "Rude", + "description": "Behavior which is rude toward other users." + }, + "curation": { + "name": "Curational", + "description": "Subjective moderation geared towards curating a more positive environment." + }, + "spam": { + "name": "Spam", + "description": "Content which doesn't add to the conversation." + }, + "misinfo": { + "name": "Misinformation", + "description": "Content which misleads or defrauds users." + } +} diff --git a/packages/api/definitions/locale/en/labels.json b/packages/api/definitions/locale/en/labels.json new file mode 100644 index 00000000000..9a29b4b44d5 --- /dev/null +++ b/packages/api/definitions/locale/en/labels.json @@ -0,0 +1,380 @@ +{ + "!hide": { + "settings": { + "name": "Moderator Hide", + "description": "Moderator has chosen to hide the content." + }, + "account": { + "name": "Content Blocked", + "description": "This account has been hidden by the moderators." + }, + "content": { + "name": "Content Blocked", + "description": "This content has been hidden by the moderators." + } + }, + "!no-promote": { + "settings": { + "name": "Moderator Filter", + "description": "Moderator has chosen to filter the content from feeds." + }, + "account": { + "name": "N/A", + "description": "N/A" + }, + "content": { + "name": "N/A", + "description": "N/A" + } + }, + "!warn": { + "settings": { + "name": "Moderator Warn", + "description": "Moderator has chosen to set a general warning on the content." + }, + "account": { + "name": "Content Warning", + "description": "This account has received a general warning from moderators." + }, + "content": { + "name": "Content Warning", + "description": "This content has received a general warning from moderators." + } + }, + "dmca-violation": { + "settings": { + "name": "Copyright Violation", + "description": "The content has received a DMCA takedown request." + }, + "account": { + "name": "Copyright Violation", + "description": "This account has received a DMCA takedown request. It will be restored if the concerns can be resolved." + }, + "content": { + "name": "Copyright Violation", + "description": "This content has received a DMCA takedown request. It will be restored if the concerns can be resolved." + } + }, + "doxxing": { + "settings": { + "name": "Doxxing", + "description": "Information that reveals private information about someone which has been shared without the consent of the subject." + }, + "account": { + "name": "Doxxing", + "description": "This account has been reported to publish private information about someone without their consent. This report is currently under review." + }, + "content": { + "name": "Doxxing", + "description": "This content has been reported to include private information about someone without their consent." + } + }, + "porn": { + "settings": { + "name": "Pornography", + "description": "Images of full-frontal nudity (genitalia) in any sexualized context, or explicit sexual activity (meaning contact with genitalia or breasts) even if partially covered. Includes graphic sexual cartoons (often jokes/memes)." + }, + "account": { + "name": "Adult Content", + "description": "This account contains imagery of full-frontal nudity or explicit sexual activity." + }, + "content": { + "name": "Adult Content", + "description": "This content contains imagery of full-frontal nudity or explicit sexual activity." + } + }, + "sexual": { + "settings": { + "name": "Sexually Suggestive", + "description": "Content that does not meet the level of \"pornography\", but is still sexual. Some common examples have been selfies and \"hornyposting\" with underwear on, or partially naked (naked but covered, eg with hands or from side perspective). Sheer/see-through nipples may end up in this category." + }, + "account": { + "name": "Suggestive Content", + "description": "This account contains imagery which is sexually suggestive. Common examples include selfies in underwear or in partial undress." + }, + "content": { + "name": "Suggestive Content", + "description": "This content contains imagery which is sexually suggestive. Common examples include selfies in underwear or in partial undress." + } + }, + "nudity": { + "settings": { + "name": "Nudity", + "description": "Nudity which is not sexual, or that is primarily \"artistic\" in nature. For example: breastfeeding; classic art paintings and sculptures; newspaper images with some nudity; fashion modeling. \"Erotic photography\" is likely to end up in sexual or porn." + }, + "account": { + "name": "Adult Content", + "description": "This account contains imagery which portrays nudity in a non-sexual or artistic setting." + }, + "content": { + "name": "Adult Content", + "description": "This content contains imagery which portrays nudity in a non-sexual or artistic setting." + } + }, + "nsfl": { + "settings": { + "name": "NSFL", + "description": "\"Not Suitable For Life.\" This includes graphic images like the infamous \"goatse\" (don't look it up)." + }, + "account": { + "name": "Graphic Imagery (NSFL)", + "description": "This account contains graphic images which are often referred to as \"Not Suitable For Life.\"" + }, + "content": { + "name": "Graphic Imagery (NSFL)", + "description": "This content contains graphic images which are often referred to as \"Not Suitable For Life.\"" + } + }, + "corpse": { + "settings": { + "name": "Corpse", + "description": "Visual image of a dead human body in any context. Includes war images, hanging, funeral caskets. Does not include all figurative cases (cartoons), but can include realistic figurative images or renderings." + }, + "account": { + "name": "Graphic Imagery (Corpse)", + "description": "This account contains images of a dead human body in any context. Includes war images, hanging, funeral caskets." + }, + "content": { + "name": "Graphic Imagery (Corpse)", + "description": "This content contains images of a dead human body in any context. Includes war images, hanging, funeral caskets." + } + }, + "gore": { + "settings": { + "name": "Gore", + "description": "Intended for shocking images, typically involving blood or visible wounds." + }, + "account": { + "name": "Graphic Imagery (Gore)", + "description": "This account contains shocking images involving blood or visible wounds." + }, + "content": { + "name": "Graphic Imagery (Gore)", + "description": "This content contains shocking images involving blood or visible wounds." + } + }, + "torture": { + "settings": { + "name": "Torture", + "description": "Depictions of torture of a human or animal (animal cruelty)." + }, + "account": { + "name": "Graphic Imagery (Torture)", + "description": "This account contains depictions of torture of a human or animal." + }, + "content": { + "name": "Graphic Imagery (Torture)", + "description": "This content contains depictions of torture of a human or animal." + } + }, + "self-harm": { + "settings": { + "name": "Self-Harm", + "description": "A visual depiction (photo or figurative) of cutting, suicide, or similar." + }, + "account": { + "name": "Graphic Imagery (Self-Harm)", + "description": "This account includes depictions of cutting, suicide, or other forms of self-harm." + }, + "content": { + "name": "Graphic Imagery (Self-Harm)", + "description": "This content includes depictions of cutting, suicide, or other forms of self-harm." + } + }, + "intolerant-race": { + "settings": { + "name": "Racial Intolerance", + "description": "Hateful or intolerant content related to race." + }, + "account": { + "name": "Intolerance (Racial)", + "description": "This account includes hateful or intolerant content related to race." + }, + "content": { + "name": "Intolerance (Racial)", + "description": "This content includes hateful or intolerant views related to race." + } + }, + "intolerant-gender": { + "settings": { + "name": "Gender Intolerance", + "description": "Hateful or intolerant content related to gender or gender identity." + }, + "account": { + "name": "Intolerance (Gender)", + "description": "This account includes hateful or intolerant content related to gender or gender identity." + }, + "content": { + "name": "Intolerance (Gender)", + "description": "This content includes hateful or intolerant views related to gender or gender identity." + } + }, + "intolerant-sexual-orientation": { + "settings": { + "name": "Sexual Orientation Intolerance", + "description": "Hateful or intolerant content related to sexual preferences." + }, + "account": { + "name": "Intolerance (Orientation)", + "description": "This account includes hateful or intolerant content related to sexual preferences." + }, + "content": { + "name": "Intolerance (Orientation)", + "description": "This content includes hateful or intolerant views related to sexual preferences." + } + }, + "intolerant-religion": { + "settings": { + "name": "Religious Intolerance", + "description": "Hateful or intolerant content related to religious views or practices." + }, + "account": { + "name": "Intolerance (Religious)", + "description": "This account includes hateful or intolerant content related to religious views or practices." + }, + "content": { + "name": "Intolerance (Religious)", + "description": "This content includes hateful or intolerant views related to religious views or practices." + } + }, + "intolerant": { + "settings": { + "name": "Intolerance", + "description": "A catchall for hateful or intolerant content which is not covered elsewhere." + }, + "account": { + "name": "Intolerance", + "description": "This account includes hateful or intolerant content." + }, + "content": { + "name": "Intolerance", + "description": "This content includes hateful or intolerant views." + } + }, + "icon-intolerant": { + "settings": { + "name": "Intolerant Iconography", + "description": "Visual imagery associated with a hate group, such as the KKK or Nazi, in any context (supportive, critical, documentary, etc)." + }, + "account": { + "name": "Intolerant Iconography", + "description": "This account includes imagery associated with a hate group such as the KKK or Nazis. This warning may apply to content any context, including critical or documentary purposes." + }, + "content": { + "name": "Intolerant Iconography", + "description": "This content includes imagery associated with a hate group such as the KKK or Nazis. This warning may apply to content any context, including critical or documentary purposes." + } + }, + "threat": { + "settings": { + "name": "Threats", + "description": "Statements or imagery published with the intent to threaten, intimidate, or harm." + }, + "account": { + "name": "Threats", + "description": "The moderators believe this account has published statements or imagery with the intent to threaten, intimidate, or harm others." + }, + "content": { + "name": "Threats", + "description": "The moderators believe this content was published with the intent to threaten, intimidate, or harm others." + } + }, + "spoiler": { + "settings": { + "name": "Spoiler", + "description": "Discussion about film, TV, etc which gives away plot points." + }, + "account": { + "name": "Spoiler Warning", + "description": "This account contains discussion about film, TV, etc which gives away plot points." + }, + "content": { + "name": "Spoiler Warning", + "description": "This content contains discussion about film, TV, etc which gives away plot points." + } + }, + "spam": { + "settings": { + "name": "Spam", + "description": "Repeat, low-quality messages which are clearly not designed to add to a conversation or space." + }, + "account": { + "name": "Spam", + "description": "This account publishes repeat, low-quality messages which are clearly not designed to add to a conversation or space." + }, + "content": { + "name": "Spam", + "description": "This content is a part of repeat, low-quality messages which are clearly not designed to add to a conversation or space." + } + }, + "account-security": { + "settings": { + "name": "Security Concerns", + "description": "Content designed to hijack user accounts such as a phishing attack." + }, + "account": { + "name": "Security Warning", + "description": "This account has published content designed to hijack user accounts such as a phishing attack." + }, + "content": { + "name": "Security Warning", + "description": "This content is designed to hijack user accounts such as a phishing attack." + } + }, + "net-abuse": { + "settings": { + "name": "Network Attacks", + "description": "Content designed to attack network systems such as denial-of-service attacks." + }, + "account": { + "name": "Network Attack Warning", + "description": "This account has published content designed to attack network systems such as denial-of-service attacks." + }, + "content": { + "name": "Network Attack Warning", + "description": "This content is designed to attack network systems such as denial-of-service attacks." + } + }, + "impersonation": { + "settings": { + "name": "Impersonation", + "description": "Accounts which falsely assert some identity." + }, + "account": { + "name": "Impersonation Warning", + "description": "The moderators believe this account is lying about their identity." + }, + "content": { + "name": "Impersonation Warning", + "description": "The moderators believe this account is lying about their identity." + } + }, + "scam": { + "settings": { + "name": "Scam", + "description": "Fraudulent content." + }, + "account": { + "name": "Scam Warning", + "description": "The moderators believe this account publishes fraudulent content." + }, + "content": { + "name": "Scam Warning", + "description": "The moderators believe this is fraudulent content." + } + }, + "misleading": { + "settings": { + "name": "Misleading", + "description": "Accounts which share misleading information." + }, + "account": { + "name": "Misleading", + "description": "The moderators believe this account is spreading misleading information." + }, + "content": { + "name": "Misleading", + "description": "The moderators believe this account is spreading misleading information." + } + } +} diff --git a/packages/api/definitions/locale/en/proposed-label-groups.json b/packages/api/definitions/locale/en/proposed-label-groups.json new file mode 100644 index 00000000000..06cc6699a7b --- /dev/null +++ b/packages/api/definitions/locale/en/proposed-label-groups.json @@ -0,0 +1,38 @@ +{ + "system": { + "name": "System", + "description": "Moderator overrides for special cases." + }, + "legal": { + "name": "Legal", + "description": "Content removed for legal reasons." + }, + "sexual": { + "name": "Adult Content", + "description": "Content which is sexual in nature." + }, + "violence": { + "name": "Violence", + "description": "Content which is violent or deeply disturbing." + }, + "intolerance": { + "name": "Intolerance", + "description": "Content or behavior which is hateful or intolerant toward a group of people." + }, + "rude": { + "name": "Rude", + "description": "Behavior which is rude toward other users." + }, + "curation": { + "name": "Curational", + "description": "Subjective moderation geared towards curating a more positive environment." + }, + "spam": { + "name": "Spam", + "description": "Content which doesn't add to the conversation." + }, + "misinfo": { + "name": "Misinformation", + "description": "Content which misleads or defrauds users." + } +} diff --git a/packages/api/definitions/locale/en/proposed-labels.json b/packages/api/definitions/locale/en/proposed-labels.json new file mode 100644 index 00000000000..e789103dfc4 --- /dev/null +++ b/packages/api/definitions/locale/en/proposed-labels.json @@ -0,0 +1,632 @@ +{ + "!hide": { + "settings": { + "name": "Moderator Hide", + "description": "Moderator has chosen to hide the content." + }, + "account": { + "name": "Content Blocked", + "description": "This account has been hidden by the moderators." + }, + "content": { + "name": "Content Blocked", + "description": "This content has been hidden by the moderators." + } + }, + "!no-promote": { + "settings": { + "name": "Moderator Filter", + "description": "Moderator has chosen to filter the content from feeds." + }, + "account": { + "name": "N/A", + "description": "N/A" + }, + "content": { + "name": "N/A", + "description": "N/A" + } + }, + "!warn": { + "settings": { + "name": "Moderator Warn", + "description": "Moderator has chosen to set a general warning on the content." + }, + "account": { + "name": "Content Warning", + "description": "This account has received a general warning from moderators." + }, + "content": { + "name": "Content Warning", + "description": "This content has received a general warning from moderators." + } + }, + "nudity-nonconsensual": { + "settings": { + "name": "Nonconsensual Nudity", + "description": "Nudity or sexual material which has been identified as being shared without the consent of the subjects." + }, + "account": { + "name": "Nonconsensual Nudity", + "description": "This account has triggered the Nonconsensual Nudity Review systems. This may be in error, so please do not jump to conclusions while the account is under review. This warning will be lifted if the review was triggered incorrectly. Otherwise, the account will be removed from the network." + }, + "content": { + "name": "Nonconsensual Nudity", + "description": "This content has triggered the Nonconsensual Nudity Review systems. This may be in error, so please do not jump to conclusions while the account is under review. This warning will be lifted if the review was triggered incorrectly. Otherwise, the account will be removed from the network." + } + }, + "dmca-violation": { + "settings": { + "name": "Copyright Violation", + "description": "The content has received a DMCA takedown request." + }, + "account": { + "name": "Copyright Violation", + "description": "This account has received a DMCA takedown request. It will be restored if the concerns can be resolved." + }, + "content": { + "name": "Copyright Violation", + "description": "This content has received a DMCA takedown request. It will be restored if the concerns can be resolved." + } + }, + "doxxing": { + "settings": { + "name": "Doxxing", + "description": "Information that reveals private information about someone which has been shared without the consent of the subject." + }, + "account": { + "name": "Doxxing", + "description": "This account has been reported to publish private information about someone without their consent. This report is currently under review." + }, + "content": { + "name": "Doxxing", + "description": "This content has been reported to include private information about someone without their consent." + } + }, + "porn": { + "settings": { + "name": "Pornography", + "description": "Images of full-frontal nudity (genitalia) in any sexualized context, or explicit sexual activity (meaning contact with genitalia or breasts) even if partially covered. Includes graphic sexual cartoons (often jokes/memes)." + }, + "account": { + "name": "Pornography", + "description": "This account contains imagery of full-frontal nudity or explicit sexual activity." + }, + "content": { + "name": "Pornography", + "description": "This content contains imagery of full-frontal nudity or explicit sexual activity." + } + }, + "sexual": { + "settings": { + "name": "Sexually Suggestive", + "description": "Content that does not meet the level of \"pornography\", but is still sexual. Some common examples have been selfies and \"hornyposting\" with underwear on, or partially naked (naked but covered, eg with hands or from side perspective). Sheer/see-through nipples may end up in this category." + }, + "account": { + "name": "Sexually Suggestive", + "description": "This account contains imagery which is sexually suggestive. Common examples include selfies in underwear or in partial undress." + }, + "content": { + "name": "Sexually Suggestive", + "description": "This content contains imagery which is sexually suggestive. Common examples include selfies in underwear or in partial undress." + } + }, + "nudity": { + "settings": { + "name": "Nudity", + "description": "Nudity which is not sexual, or that is primarily \"artistic\" in nature. For example: breastfeeding; classic art paintings and sculptures; newspaper images with some nudity; fashion modeling. \"Erotic photography\" is likely to end up in sexual or porn." + }, + "account": { + "name": "Nudity", + "description": "This account contains imagery which portrays nudity in a non-sexual or artistic setting." + }, + "content": { + "name": "Nudity", + "description": "This content contains imagery which portrays nudity in a non-sexual or artistic setting." + } + }, + "nsfl": { + "settings": { + "name": "NSFL", + "description": "\"Not Suitable For Life.\" This includes graphic images like the infamous \"goatse\" (don't look it up)." + }, + "account": { + "name": "Graphic Imagery (NSFL)", + "description": "This account contains graphic images which are often referred to as \"Not Suitable For Life.\"" + }, + "content": { + "name": "Graphic Imagery (NSFL)", + "description": "This content contains graphic images which are often referred to as \"Not Suitable For Life.\"" + } + }, + "corpse": { + "settings": { + "name": "Corpse", + "description": "Visual image of a dead human body in any context. Includes war images, hanging, funeral caskets. Does not include all figurative cases (cartoons), but can include realistic figurative images or renderings." + }, + "account": { + "name": "Graphic Imagery (Corpse)", + "description": "This account contains images of a dead human body in any context. Includes war images, hanging, funeral caskets." + }, + "content": { + "name": "Graphic Imagery (Corpse)", + "description": "This content contains images of a dead human body in any context. Includes war images, hanging, funeral caskets." + } + }, + "gore": { + "settings": { + "name": "Gore", + "description": "Intended for shocking images, typically involving blood or visible wounds." + }, + "account": { + "name": "Graphic Imagery (Gore)", + "description": "This account contains shocking images involving blood or visible wounds." + }, + "content": { + "name": "Graphic Imagery (Gore)", + "description": "This content contains shocking images involving blood or visible wounds." + } + }, + "torture": { + "settings": { + "name": "Torture", + "description": "Depictions of torture of a human or animal (animal cruelty)." + }, + "account": { + "name": "Graphic Imagery (Torture)", + "description": "This account contains depictions of torture of a human or animal." + }, + "content": { + "name": "Graphic Imagery (Torture)", + "description": "This content contains depictions of torture of a human or animal." + } + }, + "self-harm": { + "settings": { + "name": "Self-Harm", + "description": "A visual depiction (photo or figurative) of cutting, suicide, or similar." + }, + "account": { + "name": "Graphic Imagery (Self-Harm)", + "description": "This account includes depictions of cutting, suicide, or other forms of self-harm." + }, + "content": { + "name": "Graphic Imagery (Self-Harm)", + "description": "This content includes depictions of cutting, suicide, or other forms of self-harm." + } + }, + "intolerant-race": { + "settings": { + "name": "Racial Intolerance", + "description": "Hateful or intolerant content related to race." + }, + "account": { + "name": "Intolerance (Racial)", + "description": "This account includes hateful or intolerant content related to race." + }, + "content": { + "name": "Intolerance (Racial)", + "description": "This content includes hateful or intolerant views related to race." + } + }, + "intolerant-gender": { + "settings": { + "name": "Gender Intolerance", + "description": "Hateful or intolerant content related to gender or gender identity." + }, + "account": { + "name": "Intolerance (Gender)", + "description": "This account includes hateful or intolerant content related to gender or gender identity." + }, + "content": { + "name": "Intolerance (Gender)", + "description": "This content includes hateful or intolerant views related to gender or gender identity." + } + }, + "intolerant-sexual-orientation": { + "settings": { + "name": "Sexual Orientation Intolerance", + "description": "Hateful or intolerant content related to sexual preferences." + }, + "account": { + "name": "Intolerance (Orientation)", + "description": "This account includes hateful or intolerant content related to sexual preferences." + }, + "content": { + "name": "Intolerance (Orientation)", + "description": "This content includes hateful or intolerant views related to sexual preferences." + } + }, + "intolerant-religion": { + "settings": { + "name": "Religious Intolerance", + "description": "Hateful or intolerant content related to religious views or practices." + }, + "account": { + "name": "Intolerance (Religious)", + "description": "This account includes hateful or intolerant content related to religious views or practices." + }, + "content": { + "name": "Intolerance (Religious)", + "description": "This content includes hateful or intolerant views related to religious views or practices." + } + }, + "intolerant": { + "settings": { + "name": "Intolerance", + "description": "A catchall for hateful or intolerant content which is not covered elsewhere." + }, + "account": { + "name": "Intolerance", + "description": "This account includes hateful or intolerant content." + }, + "content": { + "name": "Intolerance", + "description": "This content includes hateful or intolerant views." + } + }, + "icon-intolerant": { + "settings": { + "name": "Intolerant Iconography", + "description": "Visual imagery associated with a hate group, such as the KKK or Nazi, in any context (supportive, critical, documentary, etc)." + }, + "account": { + "name": "Intolerant Iconography", + "description": "This account includes imagery associated with a hate group such as the KKK or Nazis. This warning may apply to content any context, including critical or documentary purposes." + }, + "content": { + "name": "Intolerant Iconography", + "description": "This content includes imagery associated with a hate group such as the KKK or Nazis. This warning may apply to content any context, including critical or documentary purposes." + } + }, + "trolling": { + "settings": { + "name": "Trolling", + "description": "Content which is intended to produce a negative reaction from other users." + }, + "account": { + "name": "Trolling", + "description": "The moderators believe this account has published content intended to inflame users." + }, + "content": { + "name": "Trolling", + "description": "The moderators believe this content is intended to inflame users." + } + }, + "harassment": { + "settings": { + "name": "Harassment", + "description": "Repeated posts directed at a user or a group of users with the intent to produce a negative reaction." + }, + "account": { + "name": "Harassment", + "description": "The moderators believe this account has published content directed at a user or a group of users with the intent to inflame." + }, + "content": { + "name": "Harassment", + "description": "The moderators believe this content is directed at a user or a group of users with the intent to inflame." + } + }, + "bullying": { + "settings": { + "name": "Bullying", + "description": "Statements or imagery published with the intent to bully, humiliate, or degrade." + }, + "account": { + "name": "Bullying", + "description": "The moderators believe this account has published statements or imagery published with the intent to bully, humiliate, or degrade others." + }, + "content": { + "name": "Bullying", + "description": "The moderators believe this content was published with the intent to bully, humiliate, or degrade others." + } + }, + "threat": { + "settings": { + "name": "Threats", + "description": "Statements or imagery published with the intent to threaten, intimidate, or harm." + }, + "account": { + "name": "Threats", + "description": "The moderators believe this account has published statements or imagery with the intent to threaten, intimidate, or harm others." + }, + "content": { + "name": "Threats", + "description": "The moderators believe this content was published with the intent to threaten, intimidate, or harm others." + } + }, + "disgusting": { + "settings": { + "name": "Disgusting", + "description": "Content which is gross, like an image of poop." + }, + "account": { + "name": "Warning: Disgusting", + "description": "The moderators believe this account contains content which users may find disgusting." + }, + "content": { + "name": "Warning: Disgusting", + "description": "The moderators believe users may find this content disgusting." + } + }, + "upsetting": { + "settings": { + "name": "Upsetting", + "description": "Content which is upsetting, like a video of an accident." + }, + "account": { + "name": "Warning: Upsetting", + "description": "The moderators believe this account contains content which users may find upsetting." + }, + "content": { + "name": "Warning: Upsetting", + "description": "The moderators believe users may find this content upsetting." + } + }, + "profane": { + "settings": { + "name": "Profane", + "description": "Content which includes excessive swearing or violates common sensibilities." + }, + "account": { + "name": "Warning: Profane", + "description": "The moderators believe this account contains content which users may find profane." + }, + "content": { + "name": "Warning: Profane", + "description": "The moderators believe users may find this content profane." + } + }, + "politics": { + "settings": { + "name": "Politics", + "description": "Anything that discusses politics or political discourse." + }, + "account": { + "name": "Warning: Politics", + "description": "This is not a violation. The moderators believe this account discusses politics or political discourse. This warning is only provided for users who wish to reduce the amount of politics in their experience." + }, + "content": { + "name": "Warning: Politics", + "description": "This is not a violation. The moderators believe this content discusses politics or political discourse. This warning is only provided for users who wish to reduce the amount of politics in their experience." + } + }, + "troubling": { + "settings": { + "name": "Troubling", + "description": "Content which can be difficult to process such as bad news." + }, + "account": { + "name": "Warning: Troubling", + "description": "This is not a violation. The moderators believe this account discusses topics which can be difficult to process. This warning is only provided for users who wish to reduce the amount of troubling discussion in their experience." + }, + "content": { + "name": "Warning: Troubling", + "description": "This is not a violation. The moderators believe this content discusses topics which can be difficult to process. This warning is only provided for users who wish to reduce the amount of troubling discussion in their experience." + } + }, + "negative": { + "settings": { + "name": "Negative", + "description": "Statements which are critical, pessimistic, or generally negative." + }, + "account": { + "name": "Warning: Negative", + "description": "This is not a violation. The moderators believe this account publishes statements which are critical, pessimistic, or generally negative. This warning is only provided for users who wish to reduce the amount of negativity in their experience." + }, + "content": { + "name": "Warning: Negative", + "description": "This is not a violation. The moderators believe this content is critical, pessimistic, or generally negative. This warning is only provided for users who wish to reduce the amount of negativity in their experience." + } + }, + "discourse": { + "settings": { + "name": "Discourse", + "description": "Drama, typically about some topic which is currently active in the network." + }, + "account": { + "name": "Warning: Discourse", + "description": "This is not a violation. The moderators believe this account publishes statements regarding in-network drama or disputes (aka \"discourse\"). This warning is only provided for users who wish to reduce the amount of negativity in their experience." + }, + "content": { + "name": "Warning: Discourse", + "description": "This is not a violation. The moderators believe this content relates to in-network drama or disputes (aka \"discourse\"). This warning is only provided for users who wish to reduce the amount of negativity in their experience." + } + }, + "spoiler": { + "settings": { + "name": "Spoiler", + "description": "Discussion about film, TV, etc which gives away plot points." + }, + "account": { + "name": "Spoiler Warning", + "description": "This account contains discussion about film, TV, etc which gives away plot points." + }, + "content": { + "name": "Spoiler Warning", + "description": "This content contains discussion about film, TV, etc which gives away plot points." + } + }, + "spam": { + "settings": { + "name": "Spam", + "description": "Repeat, low-quality messages which are clearly not designed to add to a conversation or space." + }, + "account": { + "name": "Spam", + "description": "This account publishes repeat, low-quality messages which are clearly not designed to add to a conversation or space." + }, + "content": { + "name": "Spam", + "description": "This content is a part of repeat, low-quality messages which are clearly not designed to add to a conversation or space." + } + }, + "clickbait": { + "settings": { + "name": "Clickbait", + "description": "Low-quality content that's designed to get users to open an external link by appearing more engaging than it is." + }, + "account": { + "name": "Clickbait", + "description": "The moderators believe this account publishes low-quality content that's designed to get users to open an external link by appearing more engaging than it is." + }, + "content": { + "name": "Clickbait", + "description": "The moderators believe this is low-quality content that's designed to get users to open an external link by appearing more engaging than it is." + } + }, + "shill": { + "settings": { + "name": "Shilling", + "description": "Over-enthusiastic promotion of a technology, product, or service, especially when there is a financial conflict of interest." + }, + "account": { + "name": "Shill", + "description": "The moderators believe this account participates in over-enthusiastic promotion of a technology, product, or service." + }, + "content": { + "name": "Shilling", + "description": "The moderators believe this content is in over-enthusiastic promotion of a technology, product, or service." + } + }, + "promotion": { + "settings": { + "name": "Promotion", + "description": "Advertising or blunt marketing of a commercial service or product." + }, + "account": { + "name": "Promotion", + "description": "The moderators believe this account engages in advertising or blunt marketing of a commercial service or product." + }, + "content": { + "name": "Promotion", + "description": "The moderators believe this content is advertising or blunt marketing of a commercial service or product." + } + }, + "account-security": { + "settings": { + "name": "Security Concerns", + "description": "Content designed to hijack user accounts such as a phishing attack." + }, + "account": { + "name": "Security Warning", + "description": "This account has published content designed to hijack user accounts such as a phishing attack." + }, + "content": { + "name": "Security Warning", + "description": "This content is designed to hijack user accounts such as a phishing attack." + } + }, + "net-abuse": { + "settings": { + "name": "Network Attacks", + "description": "Content designed to attack network systems such as denial-of-service attacks." + }, + "account": { + "name": "Network Attack Warning", + "description": "This account has published content designed to attack network systems such as denial-of-service attacks." + }, + "content": { + "name": "Network Attack Warning", + "description": "This content is designed to attack network systems such as denial-of-service attacks." + } + }, + "impersonation": { + "settings": { + "name": "Impersonation", + "description": "Accounts which falsely assert some identity." + }, + "account": { + "name": "Impersonation Warning", + "description": "The moderators believe this account is lying about their identity." + }, + "content": { + "name": "Impersonation Warning", + "description": "The moderators believe this account is lying about their identity." + } + }, + "scam": { + "settings": { + "name": "Scam", + "description": "Fraudulent content." + }, + "account": { + "name": "Scam Warning", + "description": "The moderators believe this account publishes fraudulent content." + }, + "content": { + "name": "Scam Warning", + "description": "The moderators believe this is fraudulent content." + } + }, + "misinformation": { + "settings": { + "name": "Misinformation", + "description": "Lies with the intent to deceive." + }, + "account": { + "name": "Misinformation Warning", + "description": "The moderators believe this account has published lies with the intent to deceive." + }, + "content": { + "name": "Misinformation Warning", + "description": "The moderators believe this content contains lies with the intent to deceive." + } + }, + "unverified": { + "settings": { + "name": "Unverified Claims", + "description": "Assertions which have not been verified by a trusted source." + }, + "account": { + "name": "Unverified Claims Warning", + "description": "The moderators believe this account has published claims which have not been verified by a trusted source." + }, + "content": { + "name": "Unverified Claims Warning", + "description": "The moderators believe this content contains claims which have not been verified by a trusted source." + } + }, + "manipulated": { + "settings": { + "name": "Manipulated Media", + "description": "Content which misrepresents a person or event by modifying the source material." + }, + "account": { + "name": "Manipulated Media Warning", + "description": "The moderators believe this account has published content which misrepresents a person or event by modifying the source material." + }, + "content": { + "name": "Manipulated Media Warning", + "description": "The moderators believe this content contains misrepresentations of a person or event by modifying the source material." + } + }, + "fringe": { + "settings": { + "name": "Conspiracy Theories", + "description": "Fringe views which lack evidence." + }, + "account": { + "name": "Conspiracy Theories Warning", + "description": "The moderators believe this account has published fringe views which lack evidence." + }, + "content": { + "name": "Conspiracy Theories Warning", + "description": "The moderators believe this content contains fringe views which lack evidence." + } + }, + "bullshit": { + "settings": { + "name": "Bullshit", + "description": "Content which is not technically wrong or lying, but misleading through omission or re-contextualization." + }, + "account": { + "name": "Bullshit Warning", + "description": "The moderators believe this account has published content which is not technically wrong or lying, but misleading through omission or re-contextualization." + }, + "content": { + "name": "Bullshit Warning", + "description": "The moderators believe this content includes statements which are not technically wrong or lying, but are misleading through omission or re-contextualization." + } + } +} diff --git a/packages/api/definitions/moderation-behaviors.d.ts b/packages/api/definitions/moderation-behaviors.d.ts new file mode 100644 index 00000000000..5f6e2df81ca --- /dev/null +++ b/packages/api/definitions/moderation-behaviors.d.ts @@ -0,0 +1,48 @@ +import type { LabelPreference } from '../src' + +export interface ModerationBehaviorResult { + cause?: string + filter?: boolean + blur?: boolean + alert?: boolean + noOverride?: boolean +} + +export interface ModerationBehaviorScenario { + cfg: string + subject: 'post' | 'profile' | 'userlist' | 'feedgen' + author: string + quoteAuthor?: string + labels: { + post?: string[] + profile?: string[] + account?: string[] + quotedPost?: string[] + quotedAccount?: string[] + } + behaviors: { + content?: ModerationBehaviorResult + avatar?: ModerationBehaviorResult + embed?: ModerationBehaviorResult + } +} + +export interface ModerationBehaviors { + users: Record< + string, + { + blocking: boolean + blockedBy: boolean + muted: boolean + mutedByList: boolean + } + > + configurations: Record< + string, + { + adultContentEnabled: boolean + settings: Record + } + > + scenarios: Record +} diff --git a/packages/api/definitions/post-moderation-behaviors.json b/packages/api/definitions/post-moderation-behaviors.json new file mode 100644 index 00000000000..a82ae5c80d9 --- /dev/null +++ b/packages/api/definitions/post-moderation-behaviors.json @@ -0,0 +1,1098 @@ +{ + "users": { + "self": { + "blocking": false, + "blockedBy": false, + "muted": false, + "mutedByList": false + }, + "alice": { + "blocking": false, + "blockedBy": false, + "muted": false, + "mutedByList": false + }, + "bob": { + "blocking": true, + "blockedBy": false, + "muted": false, + "mutedByList": false + }, + "carla": { + "blocking": false, + "blockedBy": true, + "muted": false, + "mutedByList": false + }, + "dan": { + "blocking": false, + "blockedBy": false, + "muted": true, + "mutedByList": false + }, + "elise": { + "blocking": false, + "blockedBy": false, + "muted": false, + "mutedByList": true + }, + "fern": { + "blocking": true, + "blockedBy": true, + "muted": false, + "mutedByList": false + } + }, + "configurations": { + "none": {}, + "adult-disabled": { + "adultContentEnabled": false + }, + "intolerant-hide": { + "settings": { "intolerant": "hide" } + }, + "intolerant-warn": { + "settings": { "intolerant": "warn" } + }, + "intolerant-ignore": { + "settings": { "intolerant": "ignore" } + }, + "porn-hide": { + "adultContentEnabled": true, + "settings": { "porn": "hide" } + }, + "porn-warn": { + "adultContentEnabled": true, + "settings": { "porn": "warn" } + }, + "porn-ignore": { + "adultContentEnabled": true, + "settings": { "porn": "ignore" } + }, + "scam-hide": { + "settings": { "scam": "hide" } + }, + "scam-warn": { + "settings": { "scam": "warn" } + }, + "scam-ignore": { + "settings": { "scam": "ignore" } + }, + "porn-hide-intolerant-hide": { + "adultContentEnabled": true, + "settings": { "porn": "hide", "intolerant": "hide" } + }, + "porn-hide-intolerant-warn": { + "adultContentEnabled": true, + "settings": { "porn": "hide", "intolerant": "warn" } + }, + "porn-warn-intolerant-hide": { + "adultContentEnabled": true, + "settings": { "porn": "warn", "intolerant": "hide" } + } + }, + "scenarios": { + "Imperative label ('!hide') on post": { + "cfg": "none", + "subject": "post", + "author": "alice", + "labels": { "post": ["!hide"] }, + "behaviors": { + "content": { + "cause": "label:!hide", + "filter": true, + "blur": true, + "noOverride": true + } + } + }, + "Imperative label ('!hide') on author profile": { + "cfg": "none", + "subject": "post", + "author": "alice", + "labels": { "profile": ["!hide"] }, + "behaviors": { + "avatar": { "cause": "label:!hide", "blur": true, "noOverride": true } + } + }, + "Imperative label ('!hide') on author account": { + "cfg": "none", + "subject": "post", + "author": "alice", + "labels": { "account": ["!hide"] }, + "behaviors": { + "content": { + "cause": "label:!hide", + "filter": true, + "blur": true, + "noOverride": true + }, + "avatar": { "cause": "label:!hide", "blur": true, "noOverride": true } + } + }, + "Imperative label ('!hide') on quoted post": { + "cfg": "none", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedPost": ["!hide"] }, + "behaviors": { + "content": { "cause": "label:!hide", "filter": true }, + "embed": { "cause": "label:!hide", "blur": true, "noOverride": true } + } + }, + "Imperative label ('!hide') on quoted author account": { + "cfg": "none", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedAccount": ["!hide"] }, + "behaviors": { + "content": { "cause": "label:!hide", "filter": true }, + "embed": { "cause": "label:!hide", "blur": true, "noOverride": true } + } + }, + + "Imperative label ('!no-promote') on post": { + "cfg": "none", + "subject": "post", + "author": "alice", + "labels": { "post": ["!no-promote"] }, + "behaviors": { + "content": { "cause": "label:!no-promote", "filter": true } + } + }, + "Imperative label ('!no-promote') on author profile": { + "cfg": "none", + "subject": "post", + "author": "alice", + "labels": { "profile": ["!no-promote"] }, + "behaviors": {} + }, + "Imperative label ('!no-promote') on author account": { + "cfg": "none", + "subject": "post", + "author": "alice", + "labels": { "account": ["!no-promote"] }, + "behaviors": { + "content": { "cause": "label:!no-promote", "filter": true } + } + }, + "Imperative label ('!no-promote') on quoted post": { + "cfg": "none", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedPost": ["!no-promote"] }, + "behaviors": { + "content": { "cause": "label:!no-promote", "filter": true } + } + }, + "Imperative label ('!no-promote') on quoted author account": { + "cfg": "none", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedAccount": ["!no-promote"] }, + "behaviors": { + "content": { "cause": "label:!no-promote", "filter": true } + } + }, + + "Imperative label ('!warn') on post": { + "cfg": "none", + "subject": "post", + "author": "alice", + "labels": { "post": ["!warn"] }, + "behaviors": { + "content": { "cause": "label:!warn", "blur": true } + } + }, + "Imperative label ('!warn') on author profile": { + "cfg": "none", + "subject": "post", + "author": "alice", + "labels": { "profile": ["!warn"] }, + "behaviors": { + "avatar": { "blur": true } + } + }, + "Imperative label ('!warn') on author account": { + "cfg": "none", + "subject": "post", + "author": "alice", + "labels": { "account": ["!warn"] }, + "behaviors": { + "content": { "cause": "label:!warn", "blur": true }, + "avatar": { "blur": true } + } + }, + "Imperative label ('!warn') on quoted post": { + "cfg": "none", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedPost": ["!warn"] }, + "behaviors": { + "embed": { "cause": "label:!warn", "blur": true } + } + }, + "Imperative label ('!warn') on quoted author account": { + "cfg": "none", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedAccount": ["!warn"] }, + "behaviors": { + "embed": { "cause": "label:!warn", "blur": true } + } + }, + + "Blur label ('intolerant') on post (hide)": { + "cfg": "intolerant-hide", + "subject": "post", + "author": "alice", + "labels": { "post": ["intolerant"] }, + "behaviors": { + "content": { "cause": "label:intolerant", "filter": true, "blur": true } + } + }, + "Blur label ('intolerant') on author profile (hide)": { + "cfg": "intolerant-hide", + "subject": "post", + "author": "alice", + "labels": { "profile": ["intolerant"] }, + "behaviors": { + "avatar": { "blur": true } + } + }, + "Blur label ('intolerant') on author account (hide)": { + "cfg": "intolerant-hide", + "subject": "post", + "author": "alice", + "labels": { "account": ["intolerant"] }, + "behaviors": { + "content": { + "cause": "label:intolerant", + "filter": true, + "blur": true + }, + "avatar": { "blur": true } + } + }, + "Blur label ('intolerant') on quoted post (hide)": { + "cfg": "intolerant-hide", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedPost": ["intolerant"] }, + "behaviors": { + "content": { "cause": "label:intolerant", "filter": true }, + "embed": { "cause": "label:intolerant", "blur": true } + } + }, + "Blur label ('intolerant') on quoted author account (hide)": { + "cfg": "intolerant-hide", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedAccount": ["intolerant"] }, + "behaviors": { + "content": { "cause": "label:intolerant", "filter": true }, + "embed": { "cause": "label:intolerant", "blur": true } + } + }, + + "Blur label ('intolerant') on post (warn)": { + "cfg": "intolerant-warn", + "subject": "post", + "author": "alice", + "labels": { "post": ["intolerant"] }, + "behaviors": { + "content": { "cause": "label:intolerant", "blur": true } + } + }, + "Blur label ('intolerant') on author profile (warn)": { + "cfg": "intolerant-warn", + "subject": "post", + "author": "alice", + "labels": { "profile": ["intolerant"] }, + "behaviors": { + "avatar": { "blur": true } + } + }, + "Blur label ('intolerant') on author account (warn)": { + "cfg": "intolerant-warn", + "subject": "post", + "author": "alice", + "labels": { "account": ["intolerant"] }, + "behaviors": { + "content": { "cause": "label:intolerant", "blur": true }, + "avatar": { "blur": true } + } + }, + "Blur label ('intolerant') on quoted post (warn)": { + "cfg": "intolerant-warn", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedPost": ["intolerant"] }, + "behaviors": { + "embed": { "cause": "label:intolerant", "blur": true } + } + }, + "Blur label ('intolerant') on quoted author account (warn)": { + "cfg": "intolerant-warn", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedPost": ["intolerant"] }, + "behaviors": { + "embed": { "cause": "label:intolerant", "blur": true } + } + }, + + "Blur label ('intolerant') on post (ignore)": { + "cfg": "intolerant-ignore", + "subject": "post", + "author": "alice", + "labels": { "post": ["intolerant"] }, + "behaviors": {} + }, + "Blur label ('intolerant') on author profile (ignore)": { + "cfg": "intolerant-ignore", + "subject": "post", + "author": "alice", + "labels": { "profile": ["intolerant"] }, + "behaviors": {} + }, + "Blur label ('intolerant') on author account (ignore)": { + "cfg": "intolerant-ignore", + "subject": "post", + "author": "alice", + "labels": { "account": ["intolerant"] }, + "behaviors": {} + }, + "Blur label ('intolerant') on quoted post (ignore)": { + "cfg": "intolerant-ignore", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedPost": ["intolerant"] }, + "behaviors": {} + }, + "Blur label ('intolerant') on quoted author account (ignore)": { + "cfg": "intolerant-ignore", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedAccount": ["intolerant"] }, + "behaviors": {} + }, + + "Blur-media label ('porn') on post (hide)": { + "cfg": "porn-hide", + "subject": "post", + "author": "alice", + "labels": { "post": ["porn"] }, + "behaviors": { + "content": { "cause": "label:porn", "filter": true }, + "embed": { "cause": "label:porn", "blur": true } + } + }, + "Blur-media label ('porn') on author profile (hide)": { + "cfg": "porn-hide", + "subject": "post", + "author": "alice", + "labels": { "profile": ["porn"] }, + "behaviors": { + "avatar": { "blur": true } + } + }, + "Blur-media label ('porn') on author account (hide)": { + "cfg": "porn-hide", + "subject": "post", + "author": "alice", + "labels": { "account": ["porn"] }, + "behaviors": { + "content": { "cause": "label:porn", "filter": true }, + "avatar": { "blur": true }, + "embed": { "cause": "label:porn", "blur": true } + } + }, + "Blur-media label ('porn') on quoted post (hide)": { + "cfg": "porn-hide", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedPost": ["porn"] }, + "behaviors": { + "content": { "cause": "label:porn", "filter": true }, + "embed": { "cause": "label:porn", "blur": true } + } + }, + "Blur-media label ('porn') on quoted author account (hide)": { + "cfg": "porn-hide", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedAccount": ["porn"] }, + "behaviors": { + "content": { "cause": "label:porn", "filter": true } + } + }, + + "Blur-media label ('porn') on post (warn)": { + "cfg": "porn-warn", + "subject": "post", + "author": "alice", + "labels": { "post": ["porn"] }, + "behaviors": { + "embed": { "cause": "label:porn", "blur": true } + } + }, + "Blur-media label ('porn') on author profile (warn)": { + "cfg": "porn-warn", + "subject": "post", + "author": "alice", + "labels": { "profile": ["porn"] }, + "behaviors": { + "avatar": { "blur": true } + } + }, + "Blur-media label ('porn') on author account (warn)": { + "cfg": "porn-warn", + "subject": "post", + "author": "alice", + "labels": { "account": ["porn"] }, + "behaviors": { + "avatar": { "blur": true }, + "embed": { "cause": "label:porn", "blur": true } + } + }, + "Blur-media label ('porn') on quoted post (warn)": { + "cfg": "porn-warn", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedPost": ["porn"] }, + "behaviors": { + "embed": { "cause": "label:porn", "blur": true } + } + }, + "Blur-media label ('porn') on quoted author account (warn)": { + "cfg": "porn-warn", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedAccount": ["porn"] }, + "behaviors": {} + }, + + "Blur-media label ('porn') on post (ignore)": { + "cfg": "porn-ignore", + "subject": "post", + "author": "alice", + "labels": { "post": ["porn"] }, + "behaviors": {} + }, + "Blur-media label ('porn') on author profile (ignore)": { + "cfg": "porn-ignore", + "subject": "post", + "author": "alice", + "labels": { "profile": ["porn"] }, + "behaviors": {} + }, + "Blur-media label ('porn') on author account (ignore)": { + "cfg": "porn-ignore", + "subject": "post", + "author": "alice", + "labels": { "account": ["porn"] }, + "behaviors": {} + }, + "Blur-media label ('porn') on quoted post (ignore)": { + "cfg": "porn-ignore", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedPost": ["porn"] }, + "behaviors": {} + }, + "Blur-media label ('porn') on quoted author account (ignore)": { + "cfg": "porn-ignore", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedAccount": ["porn"] }, + "behaviors": {} + }, + + "Notice label ('scam') on post (hide)": { + "cfg": "scam-hide", + "subject": "post", + "author": "alice", + "labels": { "post": ["scam"] }, + "behaviors": { + "content": { "cause": "label:scam", "filter": true, "alert": true } + } + }, + "Notice label ('scam') on author profile (hide)": { + "cfg": "scam-hide", + "subject": "post", + "author": "alice", + "labels": { "profile": ["scam"] }, + "behaviors": { + "avatar": { "alert": true } + } + }, + "Notice label ('scam') on author account (hide)": { + "cfg": "scam-hide", + "subject": "post", + "author": "alice", + "labels": { "account": ["scam"] }, + "behaviors": { + "content": { "cause": "label:scam", "filter": true, "alert": true }, + "avatar": { "alert": true } + } + }, + "Notice label ('scam') on quoted post (hide)": { + "cfg": "scam-hide", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedPost": ["scam"] }, + "behaviors": { + "content": { "cause": "label:scam", "filter": true }, + "embed": { "cause": "label:scam", "alert": true } + } + }, + "Notice label ('scam') on quoted author account (hide)": { + "cfg": "scam-hide", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedAccount": ["scam"] }, + "behaviors": { + "content": { "cause": "label:scam", "filter": true }, + "embed": { "cause": "label:scam", "alert": true } + } + }, + + "Notice label ('scam') on post (warn)": { + "cfg": "scam-warn", + "subject": "post", + "author": "alice", + "labels": { "post": ["scam"] }, + "behaviors": { + "content": { "cause": "label:scam", "alert": true } + } + }, + "Notice label ('scam') on author profile (warn)": { + "cfg": "scam-warn", + "subject": "post", + "author": "alice", + "labels": { "profile": ["scam"] }, + "behaviors": { + "avatar": { "alert": true } + } + }, + "Notice label ('scam') on author account (warn)": { + "cfg": "scam-warn", + "subject": "post", + "author": "alice", + "labels": { "account": ["scam"] }, + "behaviors": { + "content": { "cause": "label:scam", "alert": true }, + "avatar": { "alert": true } + } + }, + "Notice label ('scam') on quoted post (warn)": { + "cfg": "scam-warn", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedPost": ["scam"] }, + "behaviors": { + "embed": { "cause": "label:scam", "alert": true } + } + }, + "Notice label ('scam') on quoted author account (warn)": { + "cfg": "scam-warn", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedAccount": ["scam"] }, + "behaviors": { + "embed": { "cause": "label:scam", "alert": true } + } + }, + + "Notice label ('scam') on post (ignore)": { + "cfg": "scam-ignore", + "subject": "post", + "author": "alice", + "labels": { "post": ["scam"] }, + "behaviors": {} + }, + "Notice label ('scam') on author profile (ignore)": { + "cfg": "scam-ignore", + "subject": "post", + "author": "alice", + "labels": { "profile": ["scam"] }, + "behaviors": {} + }, + "Notice label ('scam') on author account (ignore)": { + "cfg": "scam-ignore", + "subject": "post", + "author": "alice", + "labels": { "account": ["scam"] }, + "behaviors": {} + }, + "Notice label ('scam') on quoted post (ignore)": { + "cfg": "scam-ignore", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedPost": ["scam"] }, + "behaviors": {} + }, + "Notice label ('scam') on quoted author account (ignore)": { + "cfg": "scam-ignore", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedAccount": ["scam"] }, + "behaviors": {} + }, + + "Adult-only label on post when adult content is disabled": { + "cfg": "adult-disabled", + "subject": "post", + "author": "alice", + "labels": { "post": ["porn"] }, + "behaviors": { + "content": { + "cause": "label:porn", + "filter": true, + "noOverride": true + }, + "embed": { "cause": "label:porn", "blur": true, "noOverride": true } + } + }, + "Adult-only label on author profile when adult content is disabled": { + "cfg": "adult-disabled", + "subject": "post", + "author": "alice", + "labels": { "profile": ["porn"] }, + "behaviors": { + "avatar": { "cause": "label:porn", "blur": true, "noOverride": true } + } + }, + "Adult-only label on author account when adult content is disabled": { + "cfg": "adult-disabled", + "subject": "post", + "author": "alice", + "labels": { "account": ["porn"] }, + "behaviors": { + "content": { + "cause": "label:porn", + "filter": true, + "noOverride": true + }, + "avatar": { "cause": "label:porn", "blur": true, "noOverride": true }, + "embed": { "cause": "label:porn", "blur": true, "noOverride": true } + } + }, + "Adult-only label on quoted post when adult content is disabled": { + "cfg": "adult-disabled", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedPost": ["porn"] }, + "behaviors": { + "content": { "cause": "label:porn", "filter": true }, + "embed": { "cause": "label:porn", "blur": true, "noOverride": true } + } + }, + "Adult-only label on quoted author account when adult content is disabled": { + "cfg": "adult-disabled", + "subject": "post", + "author": "alice", + "quoteAuthor": "alice", + "labels": { "quotedAccount": ["porn"] }, + "behaviors": { + "content": { "cause": "label:porn", "filter": true } + } + }, + + "Self-post: Imperative label ('!hide') on post": { + "cfg": "none", + "subject": "post", + "author": "self", + "labels": { "post": ["!hide"] }, + "behaviors": { + "content": { "cause": "label:!hide", "blur": true } + } + }, + "Self-post: Imperative label ('!hide') on author profile": { + "cfg": "none", + "subject": "post", + "author": "self", + "labels": { "profile": ["!hide"] }, + "behaviors": {} + }, + "Self-post: Imperative label ('!hide') on author account": { + "cfg": "none", + "subject": "post", + "author": "self", + "labels": { "account": ["!hide"] }, + "behaviors": {} + }, + "Self-post: Imperative label ('!hide') on quoted post": { + "cfg": "none", + "subject": "post", + "author": "self", + "quoteAuthor": "self", + "labels": { "quotedPost": ["!hide"] }, + "behaviors": { + "embed": { "cause": "label:!hide", "blur": true } + } + }, + "Self-post: Imperative label ('!hide') on quoted author account": { + "cfg": "none", + "subject": "post", + "author": "self", + "quoteAuthor": "self", + "labels": { "quotedAccount": ["!hide"] }, + "behaviors": {} + }, + + "Self-post: Imperative label ('!warn') on post": { + "cfg": "none", + "subject": "post", + "author": "self", + "labels": { "post": ["!warn"] }, + "behaviors": { + "content": { "cause": "label:!warn", "blur": true } + } + }, + "Self-post: Imperative label ('!warn') on author profile": { + "cfg": "none", + "subject": "post", + "author": "self", + "labels": { "profile": ["!warn"] }, + "behaviors": {} + }, + "Self-post: Imperative label ('!warn') on author account": { + "cfg": "none", + "subject": "post", + "author": "self", + "labels": { "account": ["!warn"] }, + "behaviors": {} + }, + "Self-post: Imperative label ('!warn') on quoted post": { + "cfg": "none", + "subject": "post", + "author": "self", + "quoteAuthor": "self", + "labels": { "quotedPost": ["!warn"] }, + "behaviors": { + "embed": { "cause": "label:!warn", "blur": true } + } + }, + "Self-post: Imperative label ('!warn') on quoted author account": { + "cfg": "none", + "subject": "post", + "author": "self", + "quoteAuthor": "self", + "labels": { "quotedAccount": ["!warn"] }, + "behaviors": {} + }, + + "Self-post: Blur-media label ('porn') on post (hide)": { + "cfg": "porn-hide", + "subject": "post", + "author": "self", + "labels": { "post": ["porn"] }, + "behaviors": { + "embed": { "cause": "label:porn", "blur": true } + } + }, + "Self-post: Blur-media label ('porn') on author profile (hide)": { + "cfg": "porn-hide", + "subject": "post", + "author": "self", + "labels": { "profile": ["porn"] }, + "behaviors": {} + }, + "Self-post: Blur-media label ('porn') on author account (hide)": { + "cfg": "porn-hide", + "subject": "post", + "author": "self", + "labels": { "account": ["porn"] }, + "behaviors": {} + }, + "Self-post: Blur-media label ('porn') on quoted post (hide)": { + "cfg": "porn-hide", + "subject": "post", + "author": "self", + "quoteAuthor": "self", + "labels": { "quotedPost": ["porn"] }, + "behaviors": { + "embed": { "cause": "label:porn", "blur": true } + } + }, + "Self-post: Blur-media label ('porn') on quoted author account (hide)": { + "cfg": "porn-hide", + "subject": "post", + "author": "self", + "quoteAuthor": "self", + "labels": { "quotedAccount": ["porn"] }, + "behaviors": {} + }, + + "Self-post: Blur-media label ('porn') on post (warn)": { + "cfg": "porn-warn", + "subject": "post", + "author": "self", + "labels": { "post": ["porn"] }, + "behaviors": { + "embed": { "cause": "label:porn", "blur": true } + } + }, + "Self-post: Blur-media label ('porn') on author profile (warn)": { + "cfg": "porn-warn", + "subject": "post", + "author": "self", + "labels": { "profile": ["porn"] }, + "behaviors": {} + }, + "Self-post: Blur-media label ('porn') on author account (warn)": { + "cfg": "porn-warn", + "subject": "post", + "author": "self", + "labels": { "account": ["porn"] }, + "behaviors": {} + }, + "Self-post: Blur-media label ('porn') on quoted post (warn)": { + "cfg": "porn-warn", + "subject": "post", + "author": "self", + "quoteAuthor": "self", + "labels": { "quotedPost": ["porn"] }, + "behaviors": { + "embed": { "cause": "label:porn", "blur": true } + } + }, + "Self-post: Blur-media label ('porn') on quoted author account (warn)": { + "cfg": "porn-warn", + "subject": "post", + "author": "self", + "quoteAuthor": "self", + "labels": { "quotedAccount": ["porn"] }, + "behaviors": {} + }, + + "Post with blocked author": { + "cfg": "none", + "subject": "post", + "author": "bob", + "labels": {}, + "behaviors": { + "content": { + "cause": "blocking", + "filter": true, + "blur": true, + "noOverride": true + }, + "avatar": { "blur": true, "noOverride": true } + } + }, + "Post with blocked quoted author": { + "cfg": "none", + "subject": "post", + "author": "alice", + "quoteAuthor": "bob", + "labels": {}, + "behaviors": { + "content": { "cause": "blocking", "filter": true }, + "embed": { "cause": "blocking", "blur": true, "noOverride": true } + } + }, + + "Post with author blocking user": { + "cfg": "none", + "subject": "post", + "author": "carla", + "labels": {}, + "behaviors": { + "content": { + "cause": "blocked-by", + "filter": true, + "blur": true, + "noOverride": true + }, + "avatar": { "blur": true, "noOverride": true } + } + }, + "Post with quoted author blocking user": { + "cfg": "none", + "subject": "post", + "author": "alice", + "quoteAuthor": "carla", + "labels": {}, + "behaviors": { + "content": { "cause": "blocked-by", "filter": true }, + "embed": { "cause": "blocked-by", "blur": true, "noOverride": true } + } + }, + + "Post with muted author": { + "cfg": "none", + "subject": "post", + "author": "dan", + "labels": {}, + "behaviors": { + "content": { "cause": "muted", "filter": true, "blur": true } + } + }, + "Post with muted quoted author": { + "cfg": "none", + "subject": "post", + "author": "alice", + "quoteAuthor": "dan", + "labels": {}, + "behaviors": { + "content": { "cause": "muted", "filter": true }, + "embed": { "cause": "muted", "blur": true } + } + }, + + "Post with muted-by-list author": { + "cfg": "none", + "subject": "post", + "author": "elise", + "labels": {}, + "behaviors": { + "content": { "cause": "muted-by-list", "filter": true, "blur": true } + } + }, + "Post with muted-by-list quoted author": { + "cfg": "none", + "subject": "post", + "author": "alice", + "quoteAuthor": "elise", + "labels": {}, + "behaviors": { + "content": { "cause": "muted-by-list", "filter": true }, + "embed": { "cause": "muted-by-list", "blur": true } + } + }, + + "Prioritization: post with blocking & blocked-by author": { + "cfg": "none", + "subject": "post", + "author": "fern", + "labels": {}, + "behaviors": { + "content": { + "cause": "blocking", + "filter": true, + "blur": true, + "noOverride": true + }, + "avatar": { "blur": true, "noOverride": true } + } + }, + "Prioritization: post with blocking & blocked-by quoted author": { + "cfg": "none", + "subject": "post", + "author": "alice", + "quoteAuthor": "fern", + "labels": {}, + "behaviors": { + "content": { "cause": "blocking", "filter": true }, + "embed": { "cause": "blocking", "blur": true, "noOverride": true } + } + }, + "Prioritization: '!hide' label on post by blocked user": { + "cfg": "none", + "subject": "post", + "author": "bob", + "labels": { "post": ["!hide"] }, + "behaviors": { + "content": { + "cause": "label:!hide", + "filter": true, + "blur": true, + "noOverride": true + }, + "avatar": { "blur": true, "noOverride": true } + } + }, + "Prioritization: '!hide' label on quoted post, post by blocked user": { + "cfg": "none", + "subject": "post", + "author": "bob", + "quoteAuthor": "alice", + "labels": { "quotedPost": ["!hide"] }, + "behaviors": { + "content": { + "cause": "blocking", + "filter": true, + "blur": true, + "noOverride": true + }, + "avatar": { "blur": true, "noOverride": true }, + "embed": { "cause": "label:!hide", "blur": true, "noOverride": true } + } + }, + "Prioritization: '!hide' and 'intolerant' labels on post (hide)": { + "cfg": "intolerant-hide", + "subject": "post", + "author": "alice", + "labels": { "post": ["!hide", "intolerant"] }, + "behaviors": { + "content": { + "cause": "label:!hide", + "filter": true, + "blur": true, + "noOverride": true + } + } + }, + "Prioritization: '!warn' and 'intolerant' labels on post (hide)": { + "cfg": "intolerant-hide", + "subject": "post", + "author": "alice", + "labels": { "post": ["!warn", "intolerant"] }, + "behaviors": { + "content": { "cause": "label:intolerant", "filter": true, "blur": true } + } + }, + "Prioritization: '!hide' and 'porn' labels on post (hide)": { + "cfg": "porn-hide", + "subject": "post", + "author": "alice", + "labels": { "post": ["!hide", "porn"] }, + "behaviors": { + "content": { + "cause": "label:!hide", + "filter": true, + "blur": true, + "noOverride": true + } + } + }, + "Prioritization: '!warn' and 'porn' labels on post (hide)": { + "cfg": "porn-hide", + "subject": "post", + "author": "alice", + "labels": { "post": ["!warn", "porn"] }, + "behaviors": { + "content": { "cause": "label:porn", "filter": true }, + "embed": { "cause": "label:porn", "blur": true } + } + } + } +} diff --git a/packages/api/definitions/profile-moderation-behaviors.json b/packages/api/definitions/profile-moderation-behaviors.json new file mode 100644 index 00000000000..52e04761618 --- /dev/null +++ b/packages/api/definitions/profile-moderation-behaviors.json @@ -0,0 +1,518 @@ +{ + "users": { + "self": { + "blocking": false, + "blockedBy": false, + "muted": false, + "mutedByList": false + }, + "alice": { + "blocking": false, + "blockedBy": false, + "muted": false, + "mutedByList": false + }, + "bob": { + "blocking": true, + "blockedBy": false, + "muted": false, + "mutedByList": false + }, + "carla": { + "blocking": false, + "blockedBy": true, + "muted": false, + "mutedByList": false + }, + "dan": { + "blocking": false, + "blockedBy": false, + "muted": true, + "mutedByList": false + }, + "elise": { + "blocking": false, + "blockedBy": false, + "muted": false, + "mutedByList": true + }, + "fern": { + "blocking": true, + "blockedBy": true, + "muted": false, + "mutedByList": false + } + }, + "configurations": { + "none": {}, + "adult-disabled": { + "adultContentEnabled": false + }, + "intolerant-hide": { + "settings": { "intolerant": "hide" } + }, + "intolerant-warn": { + "settings": { "intolerant": "warn" } + }, + "intolerant-ignore": { + "settings": { "intolerant": "ignore" } + }, + "porn-hide": { + "adultContentEnabled": true, + "settings": { "porn": "hide" } + }, + "porn-warn": { + "adultContentEnabled": true, + "settings": { "porn": "warn" } + }, + "porn-ignore": { + "adultContentEnabled": true, + "settings": { "porn": "ignore" } + }, + "scam-hide": { + "settings": { "scam": "hide" } + }, + "scam-warn": { + "settings": { "scam": "warn" } + }, + "scam-ignore": { + "settings": { "scam": "ignore" } + }, + "intolerant-hide-scam-warn": { + "settings": { "intolerant": "hide", "scam": "hide" } + } + }, + "scenarios": { + "Imperative label ('!hide') on account": { + "cfg": "none", + "subject": "profile", + "author": "alice", + "labels": { "account": ["!hide"] }, + "behaviors": { + "account": { + "cause": "label:!hide", + "filter": true, + "blur": true, + "noOverride": true + }, + "avatar": { "blur": true, "noOverride": true } + } + }, + "Imperative label ('!hide') on profile": { + "cfg": "none", + "subject": "profile", + "author": "alice", + "labels": { "profile": ["!hide"] }, + "behaviors": { + "profile": { "cause": "label:!hide", "blur": true, "noOverride": true }, + "avatar": { "blur": true, "noOverride": true } + } + }, + + "Imperative label ('!no-promote') on account": { + "cfg": "none", + "subject": "profile", + "author": "alice", + "labels": { "account": ["!no-promote"] }, + "behaviors": { + "account": { "cause": "label:!no-promote", "filter": true } + } + }, + "Imperative label ('!no-promote') on profile": { + "cfg": "none", + "subject": "profile", + "author": "alice", + "labels": { "profile": ["!no-promote"] }, + "behaviors": {} + }, + + "Imperative label ('!warn') on account": { + "cfg": "none", + "subject": "profile", + "author": "alice", + "labels": { "account": ["!warn"] }, + "behaviors": { + "account": { "cause": "label:!warn", "blur": true }, + "avatar": { "blur": true } + } + }, + "Imperative label ('!warn') on profile": { + "cfg": "none", + "subject": "profile", + "author": "alice", + "labels": { "profile": ["!warn"] }, + "behaviors": { + "profile": { "cause": "label:!warn", "blur": true }, + "avatar": { "blur": true } + } + }, + + "Blur label ('intolerant') on account (hide)": { + "cfg": "intolerant-hide", + "subject": "profile", + "author": "alice", + "labels": { "account": ["intolerant"] }, + "behaviors": { + "account": { + "cause": "label:intolerant", + "filter": true, + "blur": true + }, + "avatar": { "blur": true } + } + }, + "Blur label ('intolerant') on profile (hide)": { + "cfg": "intolerant-hide", + "subject": "profile", + "author": "alice", + "labels": { "profile": ["intolerant"] }, + "behaviors": { + "profile": { "cause": "label:intolerant", "blur": true }, + "avatar": { "blur": true } + } + }, + + "Blur label ('intolerant') on account (warn)": { + "cfg": "intolerant-warn", + "subject": "profile", + "author": "alice", + "labels": { "account": ["intolerant"] }, + "behaviors": { + "account": { "cause": "label:intolerant", "blur": true }, + "avatar": { "blur": true } + } + }, + "Blur label ('intolerant') on profile (warn)": { + "cfg": "intolerant-warn", + "subject": "profile", + "author": "alice", + "labels": { "profile": ["intolerant"] }, + "behaviors": { + "profile": { "cause": "label:intolerant", "blur": true }, + "avatar": { "blur": true } + } + }, + + "Blur label ('intolerant') on account (ignore)": { + "cfg": "intolerant-ignore", + "subject": "profile", + "author": "alice", + "labels": { "account": ["intolerant"] }, + "behaviors": {} + }, + "Blur label ('intolerant') on profile (ignore)": { + "cfg": "intolerant-ignore", + "subject": "profile", + "author": "alice", + "labels": { "profile": ["intolerant"] }, + "behaviors": {} + }, + + "Blur-media label ('porn') on account (hide)": { + "cfg": "porn-hide", + "subject": "profile", + "author": "alice", + "labels": { "account": ["porn"] }, + "behaviors": { + "account": { "cause": "label:porn", "filter": true, "blur": true }, + "avatar": { "blur": true } + } + }, + "Blur-media label ('porn') on profile (hide)": { + "cfg": "porn-hide", + "subject": "profile", + "author": "alice", + "labels": { "profile": ["porn"] }, + "behaviors": { + "avatar": { "blur": true } + } + }, + + "Blur-media label ('porn') on account (warn)": { + "cfg": "porn-warn", + "subject": "profile", + "author": "alice", + "labels": { "account": ["porn"] }, + "behaviors": { + "account": { "cause": "label:porn", "blur": true }, + "avatar": { "blur": true } + } + }, + "Blur-media label ('porn') on profile (warn)": { + "cfg": "porn-warn", + "subject": "profile", + "author": "alice", + "labels": { "profile": ["porn"] }, + "behaviors": { + "avatar": { "blur": true } + } + }, + + "Blur-media label ('porn') on account (ignore)": { + "cfg": "porn-ignore", + "subject": "profile", + "author": "alice", + "labels": { "account": ["porn"] }, + "behaviors": {} + }, + "Blur-media label ('porn') on profile (ignore)": { + "cfg": "porn-ignore", + "subject": "profile", + "author": "alice", + "labels": { "profile": ["porn"] }, + "behaviors": {} + }, + + "Notice label ('scam') on account (hide)": { + "cfg": "scam-hide", + "subject": "profile", + "author": "alice", + "labels": { "account": ["scam"] }, + "behaviors": { + "account": { "cause": "label:scam", "filter": true, "alert": true }, + "avatar": { "alert": true } + } + }, + "Notice label ('scam') on profile (hide)": { + "cfg": "scam-hide", + "subject": "profile", + "author": "alice", + "labels": { "profile": ["scam"] }, + "behaviors": { + "profile": { "cause": "label:scam", "alert": true }, + "avatar": { "alert": true } + } + }, + + "Notice label ('scam') on account (warn)": { + "cfg": "scam-warn", + "subject": "profile", + "author": "alice", + "labels": { "account": ["scam"] }, + "behaviors": { + "account": { "cause": "label:scam", "alert": true }, + "avatar": { "alert": true } + } + }, + "Notice label ('scam') on profile (warn)": { + "cfg": "scam-warn", + "subject": "profile", + "author": "alice", + "labels": { "profile": ["scam"] }, + "behaviors": { + "profile": { "cause": "label:scam", "alert": true }, + "avatar": { "alert": true } + } + }, + + "Notice label ('scam') on account (ignore)": { + "cfg": "scam-ignore", + "subject": "profile", + "author": "alice", + "labels": { "account": ["scam"] }, + "behaviors": {} + }, + "Notice label ('scam') on profile (ignore)": { + "cfg": "scam-ignore", + "subject": "profile", + "author": "alice", + "labels": { "profile": ["scam"] }, + "behaviors": {} + }, + + "Adult-only label on account when adult content is disabled": { + "cfg": "adult-disabled", + "subject": "profile", + "author": "alice", + "labels": { "account": ["porn"] }, + "behaviors": { + "account": { + "cause": "label:porn", + "filter": true, + "blur": true, + "noOverride": true + }, + "avatar": { "blur": true, "noOverride": true } + } + }, + "Adult-only label on profile when adult content is disabled": { + "cfg": "adult-disabled", + "subject": "profile", + "author": "alice", + "labels": { "profile": ["porn"] }, + "behaviors": { + "avatar": { "blur": true, "noOverride": true } + } + }, + + "Self-profile: !hide on account": { + "cfg": "none", + "subject": "profile", + "author": "self", + "labels": { "account": ["!hide"] }, + "behaviors": { + "account": { "cause": "label:!hide", "alert": true }, + "avatar": { "alert": true } + } + }, + "Self-profile: !hide on profile": { + "cfg": "none", + "subject": "profile", + "author": "self", + "labels": { "profile": ["!hide"] }, + "behaviors": { + "profile": { "cause": "label:!hide", "alert": true }, + "avatar": { "alert": true } + } + }, + + "Mute/block: Blocking user": { + "cfg": "none", + "subject": "profile", + "author": "bob", + "labels": {}, + "behaviors": { + "account": { "cause": "blocking", "filter": true }, + "avatar": { "blur": true, "noOverride": true } + } + }, + + "Mute/block: Blocked by user": { + "cfg": "none", + "subject": "profile", + "author": "carla", + "labels": {}, + "behaviors": { + "account": { "cause": "blocked-by", "filter": true }, + "avatar": { "blur": true, "noOverride": true } + } + }, + + "Mute/block: Muted user": { + "cfg": "none", + "subject": "profile", + "author": "dan", + "labels": {}, + "behaviors": { + "account": { "cause": "muted", "filter": true } + } + }, + + "Mute/block: Muted-by-list user": { + "cfg": "none", + "subject": "profile", + "author": "elise", + "labels": {}, + "behaviors": { + "account": { "cause": "muted-by-list", "filter": true } + } + }, + + "Prioritization: blocking & blocked-by user": { + "cfg": "none", + "subject": "profile", + "author": "fern", + "labels": {}, + "behaviors": { + "account": { "cause": "blocking", "filter": true, "blur": false }, + "avatar": { "blur": true, "noOverride": true } + } + }, + "Prioritization: '!hide' label on account of blocked user": { + "cfg": "none", + "subject": "profile", + "author": "bob", + "labels": { "account": ["!hide"] }, + "behaviors": { + "account": { + "cause": "label:!hide", + "filter": true, + "blur": true, + "noOverride": true + }, + "avatar": { "blur": true, "noOverride": true } + } + }, + "Prioritization: '!hide' and 'intolerant' labels on account (hide)": { + "cfg": "intolerant-hide", + "subject": "profile", + "author": "alice", + "labels": { "account": ["!hide", "intolerant"] }, + "behaviors": { + "account": { + "cause": "label:!hide", + "filter": true, + "blur": true, + "noOverride": true + }, + "avatar": { "blur": true, "noOverride": true } + } + }, + "Prioritization: '!warn' and 'intolerant' labels on account (hide)": { + "cfg": "intolerant-hide", + "subject": "profile", + "author": "alice", + "labels": { "account": ["!warn", "intolerant"] }, + "behaviors": { + "account": { + "cause": "label:intolerant", + "filter": true, + "blur": true + }, + "avatar": { "blur": true } + } + }, + "Prioritization: '!warn' and 'porn' labels on account (hide)": { + "cfg": "porn-hide", + "subject": "profile", + "author": "alice", + "labels": { "account": ["!warn", "porn"] }, + "behaviors": { + "account": { "cause": "label:porn", "filter": true, "blur": true }, + "avatar": { "blur": true } + } + }, + "Prioritization: intolerant label on account (hide) and scam label on profile (warn)": { + "cfg": "intolerant-hide-scam-warn", + "subject": "profile", + "author": "alice", + "labels": { "account": ["intolerant"], "profile": ["scam"] }, + "behaviors": { + "account": { + "cause": "label:intolerant", + "filter": true, + "blur": true + }, + "profile": { "cause": "label:scam", "alert": true }, + "avatar": { "blur": true, "alert": true } + } + }, + "Prioritization: !hide on account, !warn on profile": { + "cfg": "none", + "subject": "profile", + "author": "alice", + "labels": { "account": ["!hide"], "profile": ["!warn"] }, + "behaviors": { + "account": { + "cause": "label:!hide", + "filter": true, + "blur": true, + "noOverride": true + }, + "profile": { "cause": "label:!warn", "blur": true }, + "avatar": { "blur": true, "noOverride": true } + } + }, + "Prioritization: !warn on account, !hide on profile": { + "cfg": "none", + "subject": "profile", + "author": "alice", + "labels": { "account": ["!warn"], "profile": ["!hide"] }, + "behaviors": { + "account": { "cause": "label:!warn", "blur": true }, + "profile": { "cause": "label:!hide", "blur": true, "noOverride": true }, + "avatar": { "blur": true, "noOverride": true } + } + } + } +} diff --git a/packages/api/definitions/proposed-labels.json b/packages/api/definitions/proposed-labels.json new file mode 100644 index 00000000000..ad9b8924c8a --- /dev/null +++ b/packages/api/definitions/proposed-labels.json @@ -0,0 +1,326 @@ +[ + { + "id": "system", + "configurable": false, + "labels": [ + { + "id": "!hide", + "preferences": ["hide"], + "flags": ["no-override"], + "onwarn": "blur" + }, + { + "id": "!no-promote", + "preferences": ["hide"], + "flags": [], + "onwarn": null + }, + { + "id": "!warn", + "preferences": ["warn"], + "flags": [], + "onwarn": "blur" + } + ] + }, + { + "id": "legal", + "configurable": false, + "labels": [ + { + "id": "nudity-nonconsensual", + "preferences": ["hide"], + "flags": ["no-override"], + "onwarn": "blur" + }, + { + "id": "dmca-violation", + "preferences": ["hide"], + "flags": ["no-override"], + "onwarn": "blur" + }, + { + "id": "doxxing", + "preferences": ["hide"], + "flags": ["no-override"], + "onwarn": "blur" + } + ] + }, + { + "id": "sexual", + "configurable": true, + "labels": [ + { + "id": "porn", + "preferences": ["ignore", "warn", "hide"], + "flags": ["adult"], + "onwarn": "blur-media" + }, + { + "id": "sexual", + "preferences": ["ignore", "warn", "hide"], + "flags": ["adult"], + "onwarn": "blur-media" + }, + { + "id": "nudity", + "preferences": ["ignore", "warn", "hide"], + "flags": ["adult"], + "onwarn": "blur-media" + } + ] + }, + { + "id": "violence", + "configurable": true, + "labels": [ + { + "id": "nsfl", + "preferences": ["ignore", "warn", "hide"], + "flags": ["adult"], + "onwarn": "blur-media" + }, + { + "id": "corpse", + "preferences": ["ignore", "warn", "hide"], + "flags": ["adult"], + "onwarn": "blur-media" + }, + { + "id": "gore", + "preferences": ["ignore", "warn", "hide"], + "flags": ["adult"], + "onwarn": "blur-media" + }, + { + "id": "torture", + "preferences": ["ignore", "warn", "hide"], + "flags": ["adult"], + "onwarn": "blur" + }, + { + "id": "self-harm", + "preferences": ["ignore", "warn", "hide"], + "flags": ["adult"], + "onwarn": "blur-media" + } + ] + }, + { + "id": "intolerance", + "configurable": true, + "labels": [ + { + "id": "intolerant-race", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "intolerant-gender", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "intolerant-sexual-orientation", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "intolerant-religion", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "intolerant", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "icon-intolerant", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur-media" + } + ] + }, + { + "id": "rude", + "configurable": true, + "labels": [ + { + "id": "trolling", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "harassment", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "bullying", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "threat", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + } + ] + }, + { + "id": "curation", + "configurable": true, + "labels": [ + { + "id": "disgusting", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "upsetting", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "profane", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "politics", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "troubling", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "negative", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "discourse", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "spoiler", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + } + ] + }, + { + "id": "spam", + "configurable": true, + "labels": [ + { + "id": "spam", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "clickbait", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "shill", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "promotion", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + } + ] + }, + { + "id": "misinfo", + "configurable": true, + "labels": [ + { + "id": "account-security", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "net-abuse", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "blur" + }, + { + "id": "impersonation", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "alert" + }, + { + "id": "scam", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "alert" + }, + { + "id": "misinformation", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "alert" + }, + { + "id": "unverified", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "alert" + }, + { + "id": "manipulated", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "alert" + }, + { + "id": "fringe", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "alert" + }, + { + "id": "bullshit", + "preferences": ["ignore", "warn", "hide"], + "flags": [], + "onwarn": "alert" + } + ] + } +] diff --git a/packages/api/docs/labels.md b/packages/api/docs/labels.md new file mode 100644 index 00000000000..a2d8806b566 --- /dev/null +++ b/packages/api/docs/labels.md @@ -0,0 +1,538 @@ + + +# Labels + +This document is a reference for the labels used in the SDK. + +**⚠️ Note**: These labels are still in development and may change over time. Not all are currently in use. + +## Key + +### Label Preferences + +The possible client interpretations for a label. + +- ignore Do nothing with the label. +- warn Provide some form of warning on the content (see "On Warn" behavior). +- hide Remove the content from feeds and apply the warning when directly viewed. + +Each label specifies which preferences it can support. If a label is not configurable, it must have only own supported preference. + +### Configurable? + +Non-configurable labels cannot have their preference changed by the user. + +### Flags + +Additional behaviors which a label can adopt. + +- no-override The user cannot click through any covering of content created by the label. +- adult The user must have adult content enabled to configure the label. If adult content is not enabled, the label must adopt the strictest preference. + +### On Warn + +The kind of UI behavior used when a warning must be applied. + +- blur Hide all of the content behind an interstitial. +- blur-media Hide only the media within the content (ie images) behind an interstitial. +- alert Display a descriptive warning but do not hide the content. +- null Do nothing. + +## Label Behaviors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDGroupPreferencesConfigurableFlagsOn Warn
!hidesystemhideno-overrideblur
!no-promotesystemhidenull
!warnsystemwarnblur
dmca-violationlegalhideno-overrideblur
doxxinglegalhideno-overrideblur
pornsexualignore, warn, hideadultblur-media
sexualsexualignore, warn, hideadultblur-media
nuditysexualignore, warn, hideadultblur-media
nsflviolenceignore, warn, hideadultblur-media
corpseviolenceignore, warn, hideadultblur-media
goreviolenceignore, warn, hideadultblur-media
tortureviolenceignore, warn, hideadultblur
self-harmviolenceignore, warn, hideadultblur-media
intolerant-raceintoleranceignore, warn, hideblur
intolerant-genderintoleranceignore, warn, hideblur
intolerant-sexual-orientationintoleranceignore, warn, hideblur
intolerant-religionintoleranceignore, warn, hideblur
intolerantintoleranceignore, warn, hideblur
icon-intolerantintoleranceignore, warn, hideblur-media
threatrudeignore, warn, hideblur
spoilercurationignore, warn, hideblur
spamspamignore, warn, hideblur
account-securitymisinfoignore, warn, hideblur
net-abusemisinfoignore, warn, hideblur
impersonationmisinfoignore, warn, hidealert
scammisinfoignore, warn, hidealert
misleadingmisinfoignore, warn, hidealert
+ +## Label Group Descriptions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDDescription
systemgeneral
System
Moderator overrides for special cases.
legalgeneral
Legal
Content removed for legal reasons.
sexualgeneral
Adult Content
Content which is sexual in nature.
violencegeneral
Violence
Content which is violent or deeply disturbing.
intolerancegeneral
Intolerance
Content or behavior which is hateful or intolerant toward a group of people.
rudegeneral
Rude
Behavior which is rude toward other users.
curationgeneral
Curational
Subjective moderation geared towards curating a more positive environment.
spamgeneral
Spam
Content which doesn't add to the conversation.
misinfogeneral
Misinformation
Content which misleads or defrauds users.
+ +## Label Descriptions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDDescription
!hide + general
Moderator Hide
Moderator has chosen to hide the content.

+ on an account
Content Blocked
This account has been hidden by the moderators.

+ on content
Content Blocked
This content has been hidden by the moderators.

+
!no-promote + general
Moderator Filter
Moderator has chosen to filter the content from feeds.

+ on an account
N/A
N/A

+ on content
N/A
N/A

+
!warn + general
Moderator Warn
Moderator has chosen to set a general warning on the content.

+ on an account
Content Warning
This account has received a general warning from moderators.

+ on content
Content Warning
This content has received a general warning from moderators.

+
dmca-violation + general
Copyright Violation
The content has received a DMCA takedown request.

+ on an account
Copyright Violation
This account has received a DMCA takedown request. It will be restored if the concerns can be resolved.

+ on content
Copyright Violation
This content has received a DMCA takedown request. It will be restored if the concerns can be resolved.

+
doxxing + general
Doxxing
Information that reveals private information about someone which has been shared without the consent of the subject.

+ on an account
Doxxing
This account has been reported to publish private information about someone without their consent. This report is currently under review.

+ on content
Doxxing
This content has been reported to include private information about someone without their consent.

+
porn + general
Pornography
Images of full-frontal nudity (genitalia) in any sexualized context, or explicit sexual activity (meaning contact with genitalia or breasts) even if partially covered. Includes graphic sexual cartoons (often jokes/memes).

+ on an account
Adult Content
This account contains imagery of full-frontal nudity or explicit sexual activity.

+ on content
Adult Content
This content contains imagery of full-frontal nudity or explicit sexual activity.

+
sexual + general
Sexually Suggestive
Content that does not meet the level of "pornography", but is still sexual. Some common examples have been selfies and "hornyposting" with underwear on, or partially naked (naked but covered, eg with hands or from side perspective). Sheer/see-through nipples may end up in this category.

+ on an account
Suggestive Content
This account contains imagery which is sexually suggestive. Common examples include selfies in underwear or in partial undress.

+ on content
Suggestive Content
This content contains imagery which is sexually suggestive. Common examples include selfies in underwear or in partial undress.

+
nudity + general
Nudity
Nudity which is not sexual, or that is primarily "artistic" in nature. For example: breastfeeding; classic art paintings and sculptures; newspaper images with some nudity; fashion modeling. "Erotic photography" is likely to end up in sexual or porn.

+ on an account
Adult Content
This account contains imagery which portrays nudity in a non-sexual or artistic setting.

+ on content
Adult Content
This content contains imagery which portrays nudity in a non-sexual or artistic setting.

+
nsfl + general
NSFL
"Not Suitable For Life." This includes graphic images like the infamous "goatse" (don't look it up).

+ on an account
Graphic Imagery (NSFL)
This account contains graphic images which are often referred to as "Not Suitable For Life."

+ on content
Graphic Imagery (NSFL)
This content contains graphic images which are often referred to as "Not Suitable For Life."

+
corpse + general
Corpse
Visual image of a dead human body in any context. Includes war images, hanging, funeral caskets. Does not include all figurative cases (cartoons), but can include realistic figurative images or renderings.

+ on an account
Graphic Imagery (Corpse)
This account contains images of a dead human body in any context. Includes war images, hanging, funeral caskets.

+ on content
Graphic Imagery (Corpse)
This content contains images of a dead human body in any context. Includes war images, hanging, funeral caskets.

+
gore + general
Gore
Intended for shocking images, typically involving blood or visible wounds.

+ on an account
Graphic Imagery (Gore)
This account contains shocking images involving blood or visible wounds.

+ on content
Graphic Imagery (Gore)
This content contains shocking images involving blood or visible wounds.

+
torture + general
Torture
Depictions of torture of a human or animal (animal cruelty).

+ on an account
Graphic Imagery (Torture)
This account contains depictions of torture of a human or animal.

+ on content
Graphic Imagery (Torture)
This content contains depictions of torture of a human or animal.

+
self-harm + general
Self-Harm
A visual depiction (photo or figurative) of cutting, suicide, or similar.

+ on an account
Graphic Imagery (Self-Harm)
This account includes depictions of cutting, suicide, or other forms of self-harm.

+ on content
Graphic Imagery (Self-Harm)
This content includes depictions of cutting, suicide, or other forms of self-harm.

+
intolerant-race + general
Racial Intolerance
Hateful or intolerant content related to race.

+ on an account
Intolerance (Racial)
This account includes hateful or intolerant content related to race.

+ on content
Intolerance (Racial)
This content includes hateful or intolerant views related to race.

+
intolerant-gender + general
Gender Intolerance
Hateful or intolerant content related to gender or gender identity.

+ on an account
Intolerance (Gender)
This account includes hateful or intolerant content related to gender or gender identity.

+ on content
Intolerance (Gender)
This content includes hateful or intolerant views related to gender or gender identity.

+
intolerant-sexual-orientation + general
Sexual Orientation Intolerance
Hateful or intolerant content related to sexual preferences.

+ on an account
Intolerance (Orientation)
This account includes hateful or intolerant content related to sexual preferences.

+ on content
Intolerance (Orientation)
This content includes hateful or intolerant views related to sexual preferences.

+
intolerant-religion + general
Religious Intolerance
Hateful or intolerant content related to religious views or practices.

+ on an account
Intolerance (Religious)
This account includes hateful or intolerant content related to religious views or practices.

+ on content
Intolerance (Religious)
This content includes hateful or intolerant views related to religious views or practices.

+
intolerant + general
Intolerance
A catchall for hateful or intolerant content which is not covered elsewhere.

+ on an account
Intolerance
This account includes hateful or intolerant content.

+ on content
Intolerance
This content includes hateful or intolerant views.

+
icon-intolerant + general
Intolerant Iconography
Visual imagery associated with a hate group, such as the KKK or Nazi, in any context (supportive, critical, documentary, etc).

+ on an account
Intolerant Iconography
This account includes imagery associated with a hate group such as the KKK or Nazis. This warning may apply to content any context, including critical or documentary purposes.

+ on content
Intolerant Iconography
This content includes imagery associated with a hate group such as the KKK or Nazis. This warning may apply to content any context, including critical or documentary purposes.

+
threat + general
Threats
Statements or imagery published with the intent to threaten, intimidate, or harm.

+ on an account
Threats
The moderators believe this account has published statements or imagery with the intent to threaten, intimidate, or harm others.

+ on content
Threats
The moderators believe this content was published with the intent to threaten, intimidate, or harm others.

+
spoiler + general
Spoiler
Discussion about film, TV, etc which gives away plot points.

+ on an account
Spoiler Warning
This account contains discussion about film, TV, etc which gives away plot points.

+ on content
Spoiler Warning
This content contains discussion about film, TV, etc which gives away plot points.

+
spam + general
Spam
Repeat, low-quality messages which are clearly not designed to add to a conversation or space.

+ on an account
Spam
This account publishes repeat, low-quality messages which are clearly not designed to add to a conversation or space.

+ on content
Spam
This content is a part of repeat, low-quality messages which are clearly not designed to add to a conversation or space.

+
account-security + general
Security Concerns
Content designed to hijack user accounts such as a phishing attack.

+ on an account
Security Warning
This account has published content designed to hijack user accounts such as a phishing attack.

+ on content
Security Warning
This content is designed to hijack user accounts such as a phishing attack.

+
net-abuse + general
Network Attacks
Content designed to attack network systems such as denial-of-service attacks.

+ on an account
Network Attack Warning
This account has published content designed to attack network systems such as denial-of-service attacks.

+ on content
Network Attack Warning
This content is designed to attack network systems such as denial-of-service attacks.

+
impersonation + general
Impersonation
Accounts which falsely assert some identity.

+ on an account
Impersonation Warning
The moderators believe this account is lying about their identity.

+ on content
Impersonation Warning
The moderators believe this account is lying about their identity.

+
scam + general
Scam
Fraudulent content.

+ on an account
Scam Warning
The moderators believe this account publishes fraudulent content.

+ on content
Scam Warning
The moderators believe this is fraudulent content.

+
misleading + general
Misleading
Accounts which share misleading information.

+ on an account
Misleading
The moderators believe this account is spreading misleading information.

+ on content
Misleading
The moderators believe this account is spreading misleading information.

+
diff --git a/packages/api/docs/moderation-behaviors/posts.md b/packages/api/docs/moderation-behaviors/posts.md new file mode 100644 index 00000000000..5ddcf9ff602 --- /dev/null +++ b/packages/api/docs/moderation-behaviors/posts.md @@ -0,0 +1,1746 @@ + + +# Post moderation behaviors + +This document is a reference for the expected behaviors for a post in the application based on some given scenarios. The moderatePost() command condense down to the following yes or no decisions: + +- res.content.filter Do not show the post in feeds. +- res.content.blur Put the post behind a warning cover. +- res.content.noOverride Do not allow the post's blur cover to be lifted. +- res.content.alert Add a warning to the post but do not cover it. +- res.avatar.blur Put the avatar behind a cover. +- res.avatar.noOverride Do not allow the avatars's blur cover to be lifted. +- res.avatar.alert Put a warning icon on the avatar. +- res.embed.blur Put the embed content (media, quote post) behind a warning cover. +- res.embed.noOverride Do not allow the embed's blur cover to be lifted. +- res.embed.alert Put a warning on the embed content (media, quote post). + +Key: + +- ❌ = Filter Content +- 🚫 = Blur (no-override) +- ✋ = Blur +- 🪧 = Alert + +## Scenarios + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ScenarioFilterContentAvatarEmbed
Imperative label ('!hide') on post +❌ + +🚫 + + + + + +
Imperative label ('!hide') on author profile + + + + +🚫 + + + +
Imperative label ('!hide') on author account +❌ + +🚫 + + +🚫 + + + +
Imperative label ('!hide') on quoted post +❌ + + + + + +🚫 + +
Imperative label ('!hide') on quoted author account +❌ + + + + + +🚫 + +
Imperative label ('!no-promote') on post +❌ + + + + + + +
Imperative label ('!no-promote') on author profile + + + + + + + +
Imperative label ('!no-promote') on author account +❌ + + + + + + +
Imperative label ('!no-promote') on quoted post +❌ + + + + + + +
Imperative label ('!no-promote') on quoted author account +❌ + + + + + + +
Imperative label ('!warn') on post + + +✋ + + + + + +
Imperative label ('!warn') on author profile + + + + +✋ + + + +
Imperative label ('!warn') on author account + + +✋ + + +✋ + + + +
Imperative label ('!warn') on quoted post + + + + + + +✋ + +
Imperative label ('!warn') on quoted author account + + + + + + +✋ + +
ScenarioFilterContentAvatarEmbed
Blur label ('intolerant') on post (hide) +❌ + +✋ + + + + + +
Blur label ('intolerant') on author profile (hide) + + + + +✋ + + + +
Blur label ('intolerant') on author account (hide) +❌ + +✋ + + +✋ + + + +
Blur label ('intolerant') on quoted post (hide) +❌ + + + + + +✋ + +
Blur label ('intolerant') on quoted author account (hide) +❌ + + + + + +✋ + +
Blur label ('intolerant') on post (warn) + + +✋ + + + + + +
Blur label ('intolerant') on author profile (warn) + + + + +✋ + + + +
Blur label ('intolerant') on author account (warn) + + +✋ + + +✋ + + + +
Blur label ('intolerant') on quoted post (warn) + + + + + + +✋ + +
Blur label ('intolerant') on quoted author account (warn) + + + + + + +✋ + +
Blur label ('intolerant') on post (ignore) + + + + + + + +
Blur label ('intolerant') on author profile (ignore) + + + + + + + +
Blur label ('intolerant') on author account (ignore) + + + + + + + +
Blur label ('intolerant') on quoted post (ignore) + + + + + + + +
Blur label ('intolerant') on quoted author account (ignore) + + + + + + + +
ScenarioFilterContentAvatarEmbed
Blur-media label ('porn') on post (hide) +❌ + + + + + +✋ + +
Blur-media label ('porn') on author profile (hide) + + + + +✋ + + + +
Blur-media label ('porn') on author account (hide) +❌ + + + +✋ + + +✋ + +
Blur-media label ('porn') on quoted post (hide) +❌ + + + + + +✋ + +
Blur-media label ('porn') on quoted author account (hide) +❌ + + + + + + +
Blur-media label ('porn') on post (warn) + + + + + + +✋ + +
Blur-media label ('porn') on author profile (warn) + + + + +✋ + + + +
Blur-media label ('porn') on author account (warn) + + + + +✋ + + +✋ + +
Blur-media label ('porn') on quoted post (warn) + + + + + + +✋ + +
Blur-media label ('porn') on quoted author account (warn) + + + + + + + +
Blur-media label ('porn') on post (ignore) + + + + + + + +
Blur-media label ('porn') on author profile (ignore) + + + + + + + +
Blur-media label ('porn') on author account (ignore) + + + + + + + +
Blur-media label ('porn') on quoted post (ignore) + + + + + + + +
Blur-media label ('porn') on quoted author account (ignore) + + + + + + + +
ScenarioFilterContentAvatarEmbed
Notice label ('scam') on post (hide) +❌ + + +🪧 + + + + + +
Notice label ('scam') on author profile (hide) + + + + + +🪧 + + + +
Notice label ('scam') on author account (hide) +❌ + + +🪧 + + + +🪧 + + + +
Notice label ('scam') on quoted post (hide) +❌ + + + + + + +🪧 + +
Notice label ('scam') on quoted author account (hide) +❌ + + + + + + +🪧 + +
Notice label ('scam') on post (warn) + + + +🪧 + + + + + +
Notice label ('scam') on author profile (warn) + + + + + +🪧 + + + +
Notice label ('scam') on author account (warn) + + + +🪧 + + + +🪧 + + + +
Notice label ('scam') on quoted post (warn) + + + + + + + +🪧 + +
Notice label ('scam') on quoted author account (warn) + + + + + + + +🪧 + +
Notice label ('scam') on post (ignore) + + + + + + + +
Notice label ('scam') on author profile (ignore) + + + + + + + +
Notice label ('scam') on author account (ignore) + + + + + + + +
Notice label ('scam') on quoted post (ignore) + + + + + + + +
Notice label ('scam') on quoted author account (ignore) + + + + + + + +
ScenarioFilterContentAvatarEmbed
Adult-only label on post when adult content is disabled +❌ + + + + + +🚫 + +
Adult-only label on author profile when adult content is disabled + + + + +🚫 + + + +
Adult-only label on author account when adult content is disabled +❌ + + + +🚫 + + +🚫 + +
Adult-only label on quoted post when adult content is disabled +❌ + + + + + +🚫 + +
Adult-only label on quoted author account when adult content is disabled +❌ + + + + + + +
ScenarioFilterContentAvatarEmbed
Self-post: Imperative label ('!hide') on post + + +✋ + + + + + +
Self-post: Imperative label ('!hide') on author profile + + + + + + + +
Self-post: Imperative label ('!hide') on author account + + + + + + + +
Self-post: Imperative label ('!hide') on quoted post + + + + + + +✋ + +
Self-post: Imperative label ('!hide') on quoted author account + + + + + + + +
Self-post: Imperative label ('!warn') on post + + +✋ + + + + + +
Self-post: Imperative label ('!warn') on author profile + + + + + + + +
Self-post: Imperative label ('!warn') on author account + + + + + + + +
Self-post: Imperative label ('!warn') on quoted post + + + + + + +✋ + +
Self-post: Imperative label ('!warn') on quoted author account + + + + + + + +
Self-post: Blur-media label ('porn') on post (hide) + + + + + + +✋ + +
Self-post: Blur-media label ('porn') on author profile (hide) + + + + + + + +
Self-post: Blur-media label ('porn') on author account (hide) + + + + + + + +
Self-post: Blur-media label ('porn') on quoted post (hide) + + + + + + +✋ + +
Self-post: Blur-media label ('porn') on quoted author account (hide) + + + + + + + +
Self-post: Blur-media label ('porn') on post (warn) + + + + + + +✋ + +
Self-post: Blur-media label ('porn') on author profile (warn) + + + + + + + +
Self-post: Blur-media label ('porn') on author account (warn) + + + + + + + +
Self-post: Blur-media label ('porn') on quoted post (warn) + + + + + + +✋ + +
Self-post: Blur-media label ('porn') on quoted author account (warn) + + + + + + + +
ScenarioFilterContentAvatarEmbed
Post with blocked author +❌ + +🚫 + + +🚫 + + + +
Post with blocked quoted author +❌ + + + + + +🚫 + +
Post with author blocking user +❌ + +🚫 + + +🚫 + + + +
Post with quoted author blocking user +❌ + + + + + +🚫 + +
Post with muted author +❌ + +✋ + + + + + +
Post with muted quoted author +❌ + + + + + +✋ + +
Post with muted-by-list author +❌ + +✋ + + + + + +
Post with muted-by-list quoted author +❌ + + + + + +✋ + +
ScenarioFilterContentAvatarEmbed
Prioritization: post with blocking & blocked-by author +❌ + +🚫 + + +🚫 + + + +
Prioritization: post with blocking & blocked-by quoted author +❌ + + + + + +🚫 + +
Prioritization: '!hide' label on post by blocked user +❌ + +🚫 + + +🚫 + + + +
Prioritization: '!hide' label on quoted post, post by blocked user +❌ + +🚫 + + +🚫 + + +🚫 + +
Prioritization: '!hide' and 'intolerant' labels on post (hide) +❌ + +🚫 + + + + + +
Prioritization: '!warn' and 'intolerant' labels on post (hide) +❌ + +✋ + + + + + +
Prioritization: '!hide' and 'porn' labels on post (hide) +❌ + +🚫 + + + + + +
Prioritization: '!warn' and 'porn' labels on post (hide) +❌ + + + + + +✋ + +
diff --git a/packages/api/docs/moderation-behaviors/profiles.md b/packages/api/docs/moderation-behaviors/profiles.md new file mode 100644 index 00000000000..b8d7c94ce91 --- /dev/null +++ b/packages/api/docs/moderation-behaviors/profiles.md @@ -0,0 +1,747 @@ + + +# Profile moderation behaviors + +This document is a reference for the expected behaviors for a profile in the application based on some given scenarios. The moderateProfile() command condense down to the following yes or no decisions: + +- res.account.filter Do not show the account in feeds. +- res.account.blur Put the account (in listings, when viewing) behind a warning cover. +- res.account.noOverride Do not allow the account's blur cover to be lifted. +- res.account.alert Add a warning to the account but do not cover it. +- res.profile.blur Put the profile details (handle, display name, bio) behind a warning cover. +- res.profile.noOverride Do not allow the profile's blur cover to be lifted. +- res.profile.alert Add a warning to the profile but do not cover it. +- res.avatar.blur Put the avatar behind a cover. +- res.avatar.noOverride Do not allow the avatars's blur cover to be lifted. +- res.avatar.alert Put a warning icon on the avatar. + +Key: + +- ❌ = Filter Content +- 🚫 = Blur (no-override) +- ✋ = Blur +- 🪧 = Alert + +## Scenarios + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ScenarioFilterAccountProfileAvatar
Imperative label ('!hide') on account +❌ + +🚫 + + + + +🚫 + +
Imperative label ('!hide') on profile + + + + +🚫 + + +🚫 + +
Imperative label ('!no-promote') on account +❌ + + + + + + +
Imperative label ('!no-promote') on profile + + + + + + + +
Imperative label ('!warn') on account + + +✋ + + + + +✋ + +
Imperative label ('!warn') on profile + + + + +✋ + + +✋ + +
ScenarioFilterAccountProfileAvatar
Blur label ('intolerant') on account (hide) +❌ + +✋ + + + + +✋ + +
Blur label ('intolerant') on profile (hide) + + + + +✋ + + +✋ + +
Blur label ('intolerant') on account (warn) + + +✋ + + + + +✋ + +
Blur label ('intolerant') on profile (warn) + + + + +✋ + + +✋ + +
Blur label ('intolerant') on account (ignore) + + + + + + + +
Blur label ('intolerant') on profile (ignore) + + + + + + + +
ScenarioFilterAccountProfileAvatar
Blur-media label ('porn') on account (hide) +❌ + +✋ + + + + +✋ + +
Blur-media label ('porn') on profile (hide) + + + + + + +✋ + +
Blur-media label ('porn') on account (warn) + + +✋ + + + + +✋ + +
Blur-media label ('porn') on profile (warn) + + + + + + +✋ + +
Blur-media label ('porn') on account (ignore) + + + + + + + +
Blur-media label ('porn') on profile (ignore) + + + + + + + +
ScenarioFilterAccountProfileAvatar
Notice label ('scam') on account (hide) +❌ + + +🪧 + + + + + +🪧 + +
Notice label ('scam') on profile (hide) + + + + + +🪧 + + + +🪧 + +
Notice label ('scam') on account (warn) + + + +🪧 + + + + + +🪧 + +
Notice label ('scam') on profile (warn) + + + + + +🪧 + + + +🪧 + +
Notice label ('scam') on account (ignore) + + + + + + + +
Notice label ('scam') on profile (ignore) + + + + + + + +
ScenarioFilterAccountProfileAvatar
Adult-only label on account when adult content is disabled +❌ + +🚫 + + + + +🚫 + +
Adult-only label on profile when adult content is disabled + + + + + + +🚫 + +
ScenarioFilterAccountProfileAvatar
Self-profile: !hide on account + + + +🪧 + + + + + +🪧 + +
Self-profile: !hide on profile + + + + + +🪧 + + + +🪧 + +
ScenarioFilterAccountProfileAvatar
Mute/block: Blocking user +❌ + + + + + +🚫 + +
Mute/block: Blocked by user +❌ + + + + + +🚫 + +
Mute/block: Muted user +❌ + + + + + + +
Mute/block: Muted-by-list user +❌ + + + + + + +
ScenarioFilterAccountProfileAvatar
Prioritization: blocking & blocked-by user +❌ + + + + + +🚫 + +
Prioritization: '!hide' label on account of blocked user +❌ + +🚫 + + + + +🚫 + +
Prioritization: '!hide' and 'intolerant' labels on account (hide) +❌ + +🚫 + + + + +🚫 + +
Prioritization: '!warn' and 'intolerant' labels on account (hide) +❌ + +✋ + + + + +✋ + +
Prioritization: '!warn' and 'porn' labels on account (hide) +❌ + +✋ + + + + +✋ + +
Prioritization: intolerant label on account (hide) and scam label on profile (warn) +❌ + +✋ + + + +🪧 + + +✋ +🪧 +
Prioritization: !hide on account, !warn on profile +❌ + +🚫 + + +✋ + + +🚫 + +
Prioritization: !warn on account, !hide on profile + + +✋ + + +🚫 + + +🚫 + +
diff --git a/packages/api/docs/moderation.md b/packages/api/docs/moderation.md new file mode 100644 index 00000000000..23446f11b54 --- /dev/null +++ b/packages/api/docs/moderation.md @@ -0,0 +1,153 @@ +# Moderation API + +Applying the moderation system is a challenging task, but we've done our best to simplify it for you. The Moderation API helps handle a wide range of tasks, including: + +- User muting (including mutelists) +- User blocking +- Moderator labeling + +For more information, see the [Moderation Documentation](./docs/moderation.md) or the associated [Labels Reference](./docs/labels.md). + +Additional docs: + +- [Labels Reference](./labels.md) +- [Post Moderation Behaviors](./moderation-behaviors/posts.md) +- [Profile Moderation Behaviors](./moderation-behaviors/profiles.md) + +## Configuration + +Every moderation function takes a set of options which look like this: + +```typescript +{ + // the logged-in user's DID + userDid: 'did:plc:1234...', + + // is adult content allowed? + adultContentEnabled: true, + + // the global label settings (used on self-labels) + labels: { + porn: 'hide', + sexual: 'warn', + nudity: 'ignore', + // ... + }, + + // the per-labeler settings + labelers: [ + { + labeler: { + did: '...', + displayName: 'My mod service' + }, + labels: { + porn: 'hide', + sexual: 'warn', + nudity: 'ignore', + // ... + } + } + ] +} +``` + +This should match the following interfaces: + +```typescript +interface ModerationOpts { + userDid: string + adultContentEnabled: boolean + labels: Record + labelers: LabelerSettings[] +} + +interface Labeler { + did: string + displayName: string +} + +type LabelPreference = 'ignore' | 'warn' | 'hide' + +interface LabelerSettings { + labeler: Labeler + labels: Record +} +``` + +## Posts + +Applications need to produce the [Post Moderation Behaviors](./moderation-behaviors/posts.md) using the `moderatePost()` API. + +```typescript +import { moderatePost } from '@atproto/api' + +const postMod = moderatePost(postView, getOpts()) + +if (postMod.content.filter) { + // dont render in feeds or similar + // in contexts where this is disruptive (eg threads) you should ignore this and instead check blur +} +if (postMod.content.blur) { + // render the whole object behind a cover (use postMod.content.cause to explain) + if (postMod.content.noOverride) { + // do not allow the cover the be removed + } +} +if (postMod.content.alert) { + // render a warning on the content (use postMod.content.cause to explain) +} +if (postMod.embed.blur) { + // render the embedded media behind a cover (use postMod.embed.cause to explain) + if (postMod.embed.noOverride) { + // do not allow the cover the be removed + } +} +if (postMod.embed.alert) { + // render a warning on the embedded media (use postMod.embed.cause to explain) +} +if (postMod.avatar.blur) { + // render the avatar behind a cover +} +if (postMod.avatar.alert) { + // render an alert on the avatar +} +``` + +## Profiles + +Applications need to produce the [Profile Moderation Behaviors](./moderation-behaviors/profiles.md) using the `moderateProfile()` API. + +```typescript +import { moderateProfile } from '@atproto/api' + +const profileMod = moderateProfile(profileView, getOpts()) + +if (profileMod.acount.filter) { + // dont render in discovery +} +if (profileMod.account.blur) { + // render the whole account behind a cover (use profileMod.account.cause to explain) + if (profileMod.account.noOverride) { + // do not allow the cover the be removed + } +} +if (profileMod.account.alert) { + // render a warning on the account (use profileMod.account.cause to explain) +} +if (profileMod.profile.blur) { + // render the profile information (display name, bio) behind a cover + if (profileMod.profile.noOverride) { + // do not allow the cover the be removed + } +} +if (profileMod.profile.alert) { + // render a warning on the profile (use profileMod.profile.cause to explain) +} +if (profileMod.avatar.blur) { + // render the avatar behind a cover +} +if (profileMod.avatar.alert) { + // render an alert on the avatar +} +``` diff --git a/packages/api/package.json b/packages/api/package.json index f34a2bb5726..3f4f0be06d8 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,15 +1,17 @@ { "name": "@atproto/api", - "version": "0.3.13", + "version": "0.6.12", "main": "src/index.ts", + "publishConfig": { + "main": "dist/index.js", + "types": "dist/index.d.ts" + }, "scripts": { - "codegen": "lex gen-api ./src/client ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/*", + "codegen": "pnpm docgen && node ./scripts/generate-code.mjs && lex gen-api ./src/client ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/*", + "docgen": "node ./scripts/generate-docs.mjs", "build": "node ./build.js", "postbuild": "tsc --build tsconfig.build.json", - "update-main-to-dist": "node ./update-pkg.js --update-main-to-dist", - "update-main-to-src": "node ./update-pkg.js --update-main-to-src", - "prepublish": "npm run update-main-to-dist", - "postpublish": "npm run update-main-to-src", + "update-main-to-dist": "node ../../update-main-to-dist.js packages/api", "test": "jest", "bench": "jest --config jest.bench.config.js", "bench:profile": "node --inspect-brk ../../node_modules/.bin/jest --config jest.bench.config.js" @@ -21,14 +23,17 @@ "directory": "packages/api" }, "dependencies": { - "@atproto/common-web": "*", - "@atproto/uri": "*", - "@atproto/xrpc": "*", + "@atproto/common-web": "workspace:^", + "@atproto/lexicon": "workspace:^", + "@atproto/syntax": "workspace:^", + "@atproto/xrpc": "workspace:^", + "multiformats": "^9.9.0", "tlds": "^1.234.0", "typed-emitter": "^2.1.0" }, "devDependencies": { - "@atproto/lex-cli": "*", - "@atproto/pds": "* | >=0.2.0-beta.0" + "@atproto/lex-cli": "workspace:^", + "@atproto/pds": "workspace:^", + "common-tags": "^1.8.2" } } diff --git a/packages/api/scripts/code/label-groups.mjs b/packages/api/scripts/code/label-groups.mjs new file mode 100644 index 00000000000..2c62cbb10b7 --- /dev/null +++ b/packages/api/scripts/code/label-groups.mjs @@ -0,0 +1,68 @@ +import * as url from 'url' +import { readFileSync, writeFileSync } from 'fs' +import { join } from 'path' +import * as prettier from 'prettier' + +const __dirname = url.fileURLToPath(new URL('.', import.meta.url)) + +const labelsDef = JSON.parse( + readFileSync( + join(__dirname, '..', '..', 'definitions', 'labels.json'), + 'utf8', + ), +) +const labelGroupsEn = JSON.parse( + readFileSync( + join( + __dirname, + '..', + '..', + 'definitions', + 'locale', + 'en', + 'label-groups.json', + ), + 'utf8', + ), +) + +writeFileSync( + join(__dirname, '..', '..', 'src', 'moderation', 'const', 'label-groups.ts'), + await gen(), + 'utf8', +) + +async function gen() { + return prettier.format( + `/** this doc is generated by ./scripts/code/labels.mjs **/ + import {LabelGroupDefinitionMap} from '../types' + import {LABELS} from './labels' + + export const LABEL_GROUPS: LabelGroupDefinitionMap = { + ${genDefMap()} + } + `, + { semi: false, parser: 'babel', singleQuote: true }, + ) +} + +function genDefMap() { + const lines = [] + for (const group of labelsDef) { + lines.push(`"${group.id}": {`) + lines.push(` id: "${group.id}",`) + lines.push(` configurable: ${group.configurable ? true : false},`) + lines.push( + ` labels: [${group.labels + .map((label) => `LABELS["${label.id}"]`) + .join(', ')}],`, + ) + lines.push( + ` strings: {settings: {en: ${JSON.stringify(labelGroupsEn[group.id])}}}`, + ) + lines.push(`},`) + } + return lines.join('\n') +} + +export {} diff --git a/packages/api/scripts/code/labels.mjs b/packages/api/scripts/code/labels.mjs new file mode 100644 index 00000000000..9880afab1ad --- /dev/null +++ b/packages/api/scripts/code/labels.mjs @@ -0,0 +1,68 @@ +import * as url from 'url' +import { readFileSync, writeFileSync } from 'fs' +import { join } from 'path' +import * as prettier from 'prettier' + +const __dirname = url.fileURLToPath(new URL('.', import.meta.url)) + +const labelsDef = JSON.parse( + readFileSync( + join(__dirname, '..', '..', 'definitions', 'labels.json'), + 'utf8', + ), +) +const labelsEn = JSON.parse( + readFileSync( + join(__dirname, '..', '..', 'definitions', 'locale', 'en', 'labels.json'), + 'utf8', + ), +) + +writeFileSync( + join(__dirname, '..', '..', 'src', 'moderation', 'const', 'labels.ts'), + await gen(), + 'utf8', +) + +async function gen() { + return prettier.format( + `/** this doc is generated by ./scripts/code/labels.mjs **/ + import {LabelDefinitionMap} from '../types' + + export const LABELS: LabelDefinitionMap = ${JSON.stringify( + genDefMap(), + null, + 2, + )} + `, + { semi: false, parser: 'babel', singleQuote: true }, + ) +} + +function genDefMap() { + const labels = {} + for (const group of labelsDef) { + for (const label of group.labels) { + labels[label.id] = { + ...label, + groupId: group.id, + configurable: group.configurable, + strings: { + settings: getLabelStrings(label.id, 'settings'), + account: getLabelStrings(label.id, 'account'), + content: getLabelStrings(label.id, 'content'), + }, + } + } + } + return labels +} + +function getLabelStrings(id, type) { + if (labelsEn[id] && labelsEn[id][type]) { + return { en: labelsEn[id][type] } + } + throw new Error('Label strings not found for ' + id) +} + +export {} diff --git a/packages/api/scripts/docs/labels.mjs b/packages/api/scripts/docs/labels.mjs new file mode 100644 index 00000000000..979b23738e1 --- /dev/null +++ b/packages/api/scripts/docs/labels.mjs @@ -0,0 +1,164 @@ +import * as url from 'url' +import { readFileSync, writeFileSync } from 'fs' +import { join } from 'path' +import { stripIndent } from 'common-tags' + +const __dirname = url.fileURLToPath(new URL('.', import.meta.url)) + +const labelsDef = JSON.parse( + readFileSync( + join(__dirname, '..', '..', 'definitions', 'labels.json'), + 'utf8', + ), +) +const labelGroupsEn = JSON.parse( + readFileSync( + join( + __dirname, + '..', + '..', + 'definitions', + 'locale', + 'en', + 'label-groups.json', + ), + 'utf8', + ), +) +const labelsEn = JSON.parse( + readFileSync( + join(__dirname, '..', '..', 'definitions', 'locale', 'en', 'labels.json'), + 'utf8', + ), +) + +writeFileSync(join(__dirname, '..', '..', 'docs', 'labels.md'), doc(), 'utf8') + +function doc() { + return stripIndent` + + + # Labels + + This document is a reference for the labels used in the SDK. + + **⚠️ Note**: These labels are still in development and may change over time. Not all are currently in use. + + ## Key + + ### Label Preferences + + The possible client interpretations for a label. + + - ignore Do nothing with the label. + - warn Provide some form of warning on the content (see "On Warn" behavior). + - hide Remove the content from feeds and apply the warning when directly viewed. + + Each label specifies which preferences it can support. If a label is not configurable, it must have only own supported preference. + + ### Configurable? + + Non-configurable labels cannot have their preference changed by the user. + + ### Flags + + Additional behaviors which a label can adopt. + + - no-override The user cannot click through any covering of content created by the label. + - adult The user must have adult content enabled to configure the label. If adult content is not enabled, the label must adopt the strictest preference. + + ### On Warn + + The kind of UI behavior used when a warning must be applied. + + - blur Hide all of the content behind an interstitial. + - blur-media Hide only the media within the content (ie images) behind an interstitial. + - alert Display a descriptive warning but do not hide the content. + - null Do nothing. + + ## Label Behaviors + + + + + + + + + + + ${labelsRef()} +
IDGroupPreferencesConfigurableFlagsOn Warn
+ + ## Label Group Descriptions + + + + + + + ${labelGroupsDesc()} +
IDDescription
+ + ## Label Descriptions + + + + + + + ${labelsDesc()} +
IDDescription
+ ` +} + +function labelsRef() { + const lines = [] + for (const group of labelsDef) { + for (const label of group.labels) { + lines.push(stripIndent` + + ${label.id} + ${group.id} + ${label.preferences.join(', ')} + ${group.configurable ? '✅' : '❌'} + ${label.flags.join(', ')} + ${label.onwarn} + + `) + } + } + return lines.join('\n') +} + +function labelGroupsDesc() { + const lines = [] + for (const id in labelGroupsEn) { + lines.push(stripIndent` + + ${id} + general
${labelGroupsEn[id].name}
${labelGroupsEn[id].description} + + `) + } + return lines.join('\n') +} + +function labelsDesc() { + const lines = [] + for (const id in labelsEn) { + lines.push(stripIndent` + + ${id} + + general
${labelsEn[id].settings.name}
${labelsEn[id].settings.description}

+ on an account
${labelsEn[id].account.name}
${labelsEn[id].account.description}

+ on content
${labelsEn[id].content.name}
${labelsEn[id].content.description}

+ + + `) + } + return lines.join('\n') +} + +export {} diff --git a/packages/api/scripts/docs/post-moderation-behaviors.mjs b/packages/api/scripts/docs/post-moderation-behaviors.mjs new file mode 100644 index 00000000000..e90809fb000 --- /dev/null +++ b/packages/api/scripts/docs/post-moderation-behaviors.mjs @@ -0,0 +1,122 @@ +import * as url from 'url' +import { readFileSync, writeFileSync } from 'fs' +import { join } from 'path' +import { stripIndents } from 'common-tags' + +const __dirname = url.fileURLToPath(new URL('.', import.meta.url)) + +const postModerationBehaviorsDef = JSON.parse( + readFileSync( + join( + __dirname, + '..', + '..', + 'definitions', + 'post-moderation-behaviors.json', + ), + 'utf8', + ), +) + +writeFileSync( + join(__dirname, '..', '..', 'docs', 'moderation-behaviors', 'posts.md'), + posts(), + 'utf8', +) + +function posts() { + let lastTitle = 'NULL' + return stripIndents` + + + # Post moderation behaviors + + This document is a reference for the expected behaviors for a post in the application based on some given scenarios. The moderatePost() command condense down to the following yes or no decisions: + + - res.content.filter Do not show the post in feeds. + - res.content.blur Put the post behind a warning cover. + - res.content.noOverride Do not allow the post's blur cover to be lifted. + - res.content.alert Add a warning to the post but do not cover it. + - res.avatar.blur Put the avatar behind a cover. + - res.avatar.noOverride Do not allow the avatars's blur cover to be lifted. + - res.avatar.alert Put a warning icon on the avatar. + - res.embed.blur Put the embed content (media, quote post) behind a warning cover. + - res.embed.noOverride Do not allow the embed's blur cover to be lifted. + - res.embed.alert Put a warning on the embed content (media, quote post). + + Key: + + - ❌ = Filter Content + - 🚫 = Blur (no-override) + - ✋ = Blur + - 🪧 = Alert + + ## Scenarios + + + ${Array.from(Object.entries(postModerationBehaviorsDef.scenarios)) + .map(([title, scenario], i) => { + const str = ` + ${title.indexOf(lastTitle) === -1 ? postTableHead() : ''} + ${scenarioSection(title, scenario)} + ` + lastTitle = title.slice(0, 10) + return str + }) + .join('\n\n')} +
+ ` +} + +function postTableHead() { + return `ScenarioFilterContentAvatarEmbed` +} + +function scenarioSection(title, scenario) { + return stripIndents` + + ${title} + + ${filter(scenario.behaviors.content?.filter)} + + + ${blur( + scenario.behaviors.content?.blur, + scenario.behaviors.content?.noOverride, + )} + ${alert(scenario.behaviors.content?.alert)} + + + ${blur( + scenario.behaviors.avatar?.blur, + scenario.behaviors.avatar?.noOverride, + )} + ${alert(scenario.behaviors.avatar?.alert)} + + + ${blur( + scenario.behaviors.embed?.blur, + scenario.behaviors.embed?.noOverride, + )} + ${alert(scenario.behaviors.embed?.alert)} + + + ` +} + +function filter(val) { + return val ? '❌' : '' +} + +function blur(val, noOverride) { + if (val) { + return noOverride ? '🚫' : '✋' + } + return '' +} + +function alert(val) { + return val ? '🪧' : '' +} + +export {} diff --git a/packages/api/scripts/docs/profile-moderation-behaviors.mjs b/packages/api/scripts/docs/profile-moderation-behaviors.mjs new file mode 100644 index 00000000000..413d2593011 --- /dev/null +++ b/packages/api/scripts/docs/profile-moderation-behaviors.mjs @@ -0,0 +1,122 @@ +import * as url from 'url' +import { readFileSync, writeFileSync } from 'fs' +import { join } from 'path' +import { stripIndents } from 'common-tags' + +const __dirname = url.fileURLToPath(new URL('.', import.meta.url)) + +const profileModerationBehaviorsDef = JSON.parse( + readFileSync( + join( + __dirname, + '..', + '..', + 'definitions', + 'profile-moderation-behaviors.json', + ), + 'utf8', + ), +) + +writeFileSync( + join(__dirname, '..', '..', 'docs', 'moderation-behaviors', 'profiles.md'), + profiles(), + 'utf8', +) + +function profiles() { + let lastTitle = 'NULL' + return stripIndents` + + + # Profile moderation behaviors + + This document is a reference for the expected behaviors for a profile in the application based on some given scenarios. The moderateProfile() command condense down to the following yes or no decisions: + + - res.account.filter Do not show the account in feeds. + - res.account.blur Put the account (in listings, when viewing) behind a warning cover. + - res.account.noOverride Do not allow the account's blur cover to be lifted. + - res.account.alert Add a warning to the account but do not cover it. + - res.profile.blur Put the profile details (handle, display name, bio) behind a warning cover. + - res.profile.noOverride Do not allow the profile's blur cover to be lifted. + - res.profile.alert Add a warning to the profile but do not cover it. + - res.avatar.blur Put the avatar behind a cover. + - res.avatar.noOverride Do not allow the avatars's blur cover to be lifted. + - res.avatar.alert Put a warning icon on the avatar. + + Key: + + - ❌ = Filter Content + - 🚫 = Blur (no-override) + - ✋ = Blur + - 🪧 = Alert + + ## Scenarios + + + ${Array.from(Object.entries(profileModerationBehaviorsDef.scenarios)) + .map(([title, scenario], i) => { + const str = ` + ${title.indexOf(lastTitle) === -1 ? postTableHead() : ''} + ${scenarioSection(title, scenario)} + ` + lastTitle = title.slice(0, 10) + return str + }) + .join('\n\n')} +
+ ` +} + +function postTableHead() { + return `ScenarioFilterAccountProfileAvatar` +} + +function scenarioSection(title, scenario) { + return stripIndents` + + ${title} + + ${filter(scenario.behaviors.account?.filter)} + + + ${blur( + scenario.behaviors.account?.blur, + scenario.behaviors.account?.noOverride, + )} + ${alert(scenario.behaviors.account?.alert)} + + + ${blur( + scenario.behaviors.profile?.blur, + scenario.behaviors.profile?.noOverride, + )} + ${alert(scenario.behaviors.profile?.alert)} + + + ${blur( + scenario.behaviors.avatar?.blur, + scenario.behaviors.avatar?.noOverride, + )} + ${alert(scenario.behaviors.avatar?.alert)} + + + ` +} + +function filter(val) { + return val ? '❌' : '' +} + +function blur(val, noOverride) { + if (val) { + return noOverride ? '🚫' : '✋' + } + return '' +} + +function alert(val) { + return val ? '🪧' : '' +} + +export {} diff --git a/packages/api/scripts/generate-code.mjs b/packages/api/scripts/generate-code.mjs new file mode 100644 index 00000000000..287d9beb393 --- /dev/null +++ b/packages/api/scripts/generate-code.mjs @@ -0,0 +1,4 @@ +import './code/labels.mjs' +import './code/label-groups.mjs' + +export {} diff --git a/packages/api/scripts/generate-docs.mjs b/packages/api/scripts/generate-docs.mjs new file mode 100644 index 00000000000..f7d4a1b2424 --- /dev/null +++ b/packages/api/scripts/generate-docs.mjs @@ -0,0 +1,5 @@ +import './docs/labels.mjs' +import './docs/post-moderation-behaviors.mjs' +import './docs/profile-moderation-behaviors.mjs' + +export {} diff --git a/packages/api/src/bsky-agent.ts b/packages/api/src/bsky-agent.ts index 4d623c08bae..b7dd1bc1931 100644 --- a/packages/api/src/bsky-agent.ts +++ b/packages/api/src/bsky-agent.ts @@ -1,10 +1,21 @@ -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { AtpAgent } from './agent' import { AppBskyFeedPost, AppBskyActorProfile, + AppBskyActorDefs, ComAtprotoRepoPutRecord, } from './client' +import { BskyPreferences, BskyLabelPreference } from './types' + +declare global { + interface Array { + findLast( + predicate: (value: T, index: number, obj: T[]) => unknown, + thisArg?: any, + ): T + } +} export class BskyAgent extends AtpAgent { get app() { @@ -17,6 +28,9 @@ export class BskyAgent extends AtpAgent { getAuthorFeed: typeof this.api.app.bsky.feed.getAuthorFeed = (params, opts) => this.api.app.bsky.feed.getAuthorFeed(params, opts) + getActorLikes: typeof this.api.app.bsky.feed.getActorLikes = (params, opts) => + this.api.app.bsky.feed.getActorLikes(params, opts) + getPostThread: typeof this.api.app.bsky.feed.getPostThread = (params, opts) => this.api.app.bsky.feed.getPostThread(params, opts) @@ -233,4 +247,223 @@ export class BskyAgent extends AtpAgent { seenAt, }) } + + async getPreferences(): Promise { + const prefs: BskyPreferences = { + feeds: { + saved: undefined, + pinned: undefined, + }, + adultContentEnabled: false, + contentLabels: {}, + birthDate: undefined, + } + const res = await this.app.bsky.actor.getPreferences({}) + for (const pref of res.data.preferences) { + if ( + AppBskyActorDefs.isAdultContentPref(pref) && + AppBskyActorDefs.validateAdultContentPref(pref).success + ) { + prefs.adultContentEnabled = pref.enabled + } else if ( + AppBskyActorDefs.isContentLabelPref(pref) && + AppBskyActorDefs.validateAdultContentPref(pref).success + ) { + let value = pref.visibility + if (value === 'show') { + value = 'ignore' + } + if (value === 'ignore' || value === 'warn' || value === 'hide') { + prefs.contentLabels[pref.label] = value as BskyLabelPreference + } + } else if ( + AppBskyActorDefs.isSavedFeedsPref(pref) && + AppBskyActorDefs.validateSavedFeedsPref(pref).success + ) { + prefs.feeds.saved = pref.saved + prefs.feeds.pinned = pref.pinned + } else if ( + AppBskyActorDefs.isPersonalDetailsPref(pref) && + AppBskyActorDefs.validatePersonalDetailsPref(pref).success + ) { + if (pref.birthDate) { + prefs.birthDate = new Date(pref.birthDate) + } + } + } + return prefs + } + + async setSavedFeeds(saved: string[], pinned: string[]) { + return updateFeedPreferences(this, () => ({ + saved, + pinned, + })) + } + + async addSavedFeed(v: string) { + return updateFeedPreferences(this, (saved: string[], pinned: string[]) => ({ + saved: [...saved.filter((uri) => uri !== v), v], + pinned, + })) + } + + async removeSavedFeed(v: string) { + return updateFeedPreferences(this, (saved: string[], pinned: string[]) => ({ + saved: saved.filter((uri) => uri !== v), + pinned: pinned.filter((uri) => uri !== v), + })) + } + + async addPinnedFeed(v: string) { + return updateFeedPreferences(this, (saved: string[], pinned: string[]) => ({ + saved: [...saved.filter((uri) => uri !== v), v], + pinned: [...pinned.filter((uri) => uri !== v), v], + })) + } + + async removePinnedFeed(v: string) { + return updateFeedPreferences(this, (saved: string[], pinned: string[]) => ({ + saved, + pinned: pinned.filter((uri) => uri !== v), + })) + } + + async setAdultContentEnabled(v: boolean) { + await updatePreferences(this, (prefs: AppBskyActorDefs.Preferences) => { + let adultContentPref = prefs.findLast( + (pref) => + AppBskyActorDefs.isAdultContentPref(pref) && + AppBskyActorDefs.validateAdultContentPref(pref).success, + ) + if (adultContentPref) { + adultContentPref.enabled = v + } else { + adultContentPref = { + $type: 'app.bsky.actor.defs#adultContentPref', + enabled: v, + } + } + return prefs + .filter((pref) => !AppBskyActorDefs.isAdultContentPref(pref)) + .concat([adultContentPref]) + }) + } + + async setContentLabelPref(key: string, value: BskyLabelPreference) { + // TEMP update old value + if (value === 'show') { + value = 'ignore' + } + + await updatePreferences(this, (prefs: AppBskyActorDefs.Preferences) => { + let labelPref = prefs.findLast( + (pref) => + AppBskyActorDefs.isContentLabelPref(pref) && + AppBskyActorDefs.validateAdultContentPref(pref).success && + pref.label === key, + ) + if (labelPref) { + labelPref.visibility = value + } else { + labelPref = { + $type: 'app.bsky.actor.defs#contentLabelPref', + label: key, + visibility: value, + } + } + return prefs + .filter( + (pref) => + !AppBskyActorDefs.isContentLabelPref(pref) || pref.label !== key, + ) + .concat([labelPref]) + }) + } + + async setPersonalDetails({ + birthDate, + }: { + birthDate: string | Date | undefined + }) { + birthDate = birthDate instanceof Date ? birthDate.toISOString() : birthDate + await updatePreferences(this, (prefs: AppBskyActorDefs.Preferences) => { + let personalDetailsPref = prefs.findLast( + (pref) => + AppBskyActorDefs.isPersonalDetailsPref(pref) && + AppBskyActorDefs.validatePersonalDetailsPref(pref).success, + ) + if (personalDetailsPref) { + personalDetailsPref.birthDate = birthDate + } else { + personalDetailsPref = { + $type: 'app.bsky.actor.defs#personalDetailsPref', + birthDate, + } + } + return prefs + .filter((pref) => !AppBskyActorDefs.isPersonalDetailsPref(pref)) + .concat([personalDetailsPref]) + }) + } +} + +/** + * This function updates the preferences of a user and allows for a callback function to be executed + * before the update. + * @param cb - cb is a callback function that takes in a single parameter of type + * AppBskyActorDefs.Preferences and returns either a boolean or void. This callback function is used to + * update the preferences of the user. The function is called with the current preferences as an + * argument and if the callback returns false, the preferences are not updated. + */ +async function updatePreferences( + agent: BskyAgent, + cb: ( + prefs: AppBskyActorDefs.Preferences, + ) => AppBskyActorDefs.Preferences | false, +) { + const res = await agent.app.bsky.actor.getPreferences({}) + const newPrefs = cb(res.data.preferences) + if (newPrefs === false) { + return + } + await agent.app.bsky.actor.putPreferences({ + preferences: newPrefs, + }) +} + +/** + * A helper specifically for updating feed preferences + */ +async function updateFeedPreferences( + agent: BskyAgent, + cb: ( + saved: string[], + pinned: string[], + ) => { saved: string[]; pinned: string[] }, +): Promise<{ saved: string[]; pinned: string[] }> { + let res + await updatePreferences(agent, (prefs: AppBskyActorDefs.Preferences) => { + let feedsPref = prefs.findLast( + (pref) => + AppBskyActorDefs.isSavedFeedsPref(pref) && + AppBskyActorDefs.validateSavedFeedsPref(pref).success, + ) as AppBskyActorDefs.SavedFeedsPref | undefined + if (feedsPref) { + res = cb(feedsPref.saved, feedsPref.pinned) + feedsPref.saved = res.saved + feedsPref.pinned = res.pinned + } else { + res = cb([], []) + feedsPref = { + $type: 'app.bsky.actor.defs#savedFeedsPref', + saved: res.saved, + pinned: res.pinned, + } + } + return prefs + .filter((pref) => !AppBskyActorDefs.isSavedFeedsPref(pref)) + .concat([feedsPref]) + }) + return res } diff --git a/packages/api/src/client/index.ts b/packages/api/src/client/index.ts index e74bee840e0..761097aad7c 100644 --- a/packages/api/src/client/index.ts +++ b/packages/api/src/client/index.ts @@ -18,10 +18,10 @@ import * as ComAtprotoAdminGetModerationReport from './types/com/atproto/admin/g import * as ComAtprotoAdminGetModerationReports from './types/com/atproto/admin/getModerationReports' import * as ComAtprotoAdminGetRecord from './types/com/atproto/admin/getRecord' import * as ComAtprotoAdminGetRepo from './types/com/atproto/admin/getRepo' -import * as ComAtprotoAdminRebaseRepo from './types/com/atproto/admin/rebaseRepo' import * as ComAtprotoAdminResolveModerationReports from './types/com/atproto/admin/resolveModerationReports' import * as ComAtprotoAdminReverseModerationAction from './types/com/atproto/admin/reverseModerationAction' import * as ComAtprotoAdminSearchRepos from './types/com/atproto/admin/searchRepos' +import * as ComAtprotoAdminSendEmail from './types/com/atproto/admin/sendEmail' import * as ComAtprotoAdminTakeModerationAction from './types/com/atproto/admin/takeModerationAction' import * as ComAtprotoAdminUpdateAccountEmail from './types/com/atproto/admin/updateAccountEmail' import * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/updateAccountHandle' @@ -39,7 +39,6 @@ import * as ComAtprotoRepoDescribeRepo from './types/com/atproto/repo/describeRe import * as ComAtprotoRepoGetRecord from './types/com/atproto/repo/getRecord' import * as ComAtprotoRepoListRecords from './types/com/atproto/repo/listRecords' import * as ComAtprotoRepoPutRecord from './types/com/atproto/repo/putRecord' -import * as ComAtprotoRepoRebaseRepo from './types/com/atproto/repo/rebaseRepo' import * as ComAtprotoRepoStrongRef from './types/com/atproto/repo/strongRef' import * as ComAtprotoRepoUploadBlob from './types/com/atproto/repo/uploadBlob' import * as ComAtprotoServerCreateAccount from './types/com/atproto/server/createAccount' @@ -62,8 +61,8 @@ import * as ComAtprotoServerRevokeAppPassword from './types/com/atproto/server/r import * as ComAtprotoSyncGetBlob from './types/com/atproto/sync/getBlob' import * as ComAtprotoSyncGetBlocks from './types/com/atproto/sync/getBlocks' import * as ComAtprotoSyncGetCheckout from './types/com/atproto/sync/getCheckout' -import * as ComAtprotoSyncGetCommitPath from './types/com/atproto/sync/getCommitPath' import * as ComAtprotoSyncGetHead from './types/com/atproto/sync/getHead' +import * as ComAtprotoSyncGetLatestCommit from './types/com/atproto/sync/getLatestCommit' import * as ComAtprotoSyncGetRecord from './types/com/atproto/sync/getRecord' import * as ComAtprotoSyncGetRepo from './types/com/atproto/sync/getRepo' import * as ComAtprotoSyncListBlobs from './types/com/atproto/sync/listBlobs' @@ -88,6 +87,7 @@ import * as AppBskyFeedDefs from './types/app/bsky/feed/defs' import * as AppBskyFeedDescribeFeedGenerator from './types/app/bsky/feed/describeFeedGenerator' import * as AppBskyFeedGenerator from './types/app/bsky/feed/generator' import * as AppBskyFeedGetActorFeeds from './types/app/bsky/feed/getActorFeeds' +import * as AppBskyFeedGetActorLikes from './types/app/bsky/feed/getActorLikes' import * as AppBskyFeedGetAuthorFeed from './types/app/bsky/feed/getAuthorFeed' import * as AppBskyFeedGetFeed from './types/app/bsky/feed/getFeed' import * as AppBskyFeedGetFeedGenerator from './types/app/bsky/feed/getFeedGenerator' @@ -97,6 +97,7 @@ import * as AppBskyFeedGetLikes from './types/app/bsky/feed/getLikes' import * as AppBskyFeedGetPostThread from './types/app/bsky/feed/getPostThread' import * as AppBskyFeedGetPosts from './types/app/bsky/feed/getPosts' import * as AppBskyFeedGetRepostedBy from './types/app/bsky/feed/getRepostedBy' +import * as AppBskyFeedGetSuggestedFeeds from './types/app/bsky/feed/getSuggestedFeeds' import * as AppBskyFeedGetTimeline from './types/app/bsky/feed/getTimeline' import * as AppBskyFeedLike from './types/app/bsky/feed/like' import * as AppBskyFeedPost from './types/app/bsky/feed/post' @@ -108,10 +109,13 @@ import * as AppBskyGraphGetBlocks from './types/app/bsky/graph/getBlocks' import * as AppBskyGraphGetFollowers from './types/app/bsky/graph/getFollowers' import * as AppBskyGraphGetFollows from './types/app/bsky/graph/getFollows' import * as AppBskyGraphGetList from './types/app/bsky/graph/getList' +import * as AppBskyGraphGetListBlocks from './types/app/bsky/graph/getListBlocks' import * as AppBskyGraphGetListMutes from './types/app/bsky/graph/getListMutes' import * as AppBskyGraphGetLists from './types/app/bsky/graph/getLists' import * as AppBskyGraphGetMutes from './types/app/bsky/graph/getMutes' +import * as AppBskyGraphGetSuggestedFollowsByActor from './types/app/bsky/graph/getSuggestedFollowsByActor' import * as AppBskyGraphList from './types/app/bsky/graph/list' +import * as AppBskyGraphListblock from './types/app/bsky/graph/listblock' import * as AppBskyGraphListitem from './types/app/bsky/graph/listitem' import * as AppBskyGraphMuteActor from './types/app/bsky/graph/muteActor' import * as AppBskyGraphMuteActorList from './types/app/bsky/graph/muteActorList' @@ -119,10 +123,13 @@ import * as AppBskyGraphUnmuteActor from './types/app/bsky/graph/unmuteActor' import * as AppBskyGraphUnmuteActorList from './types/app/bsky/graph/unmuteActorList' import * as AppBskyNotificationGetUnreadCount from './types/app/bsky/notification/getUnreadCount' import * as AppBskyNotificationListNotifications from './types/app/bsky/notification/listNotifications' +import * as AppBskyNotificationRegisterPush from './types/app/bsky/notification/registerPush' import * as AppBskyNotificationUpdateSeen from './types/app/bsky/notification/updateSeen' import * as AppBskyRichtextFacet from './types/app/bsky/richtext/facet' +import * as AppBskyUnspeccedApplyLabels from './types/app/bsky/unspecced/applyLabels' import * as AppBskyUnspeccedGetPopular from './types/app/bsky/unspecced/getPopular' import * as AppBskyUnspeccedGetPopularFeedGenerators from './types/app/bsky/unspecced/getPopularFeedGenerators' +import * as AppBskyUnspeccedGetTimelineSkeleton from './types/app/bsky/unspecced/getTimelineSkeleton' export * as ComAtprotoAdminDefs from './types/com/atproto/admin/defs' export * as ComAtprotoAdminDisableAccountInvites from './types/com/atproto/admin/disableAccountInvites' @@ -135,10 +142,10 @@ export * as ComAtprotoAdminGetModerationReport from './types/com/atproto/admin/g export * as ComAtprotoAdminGetModerationReports from './types/com/atproto/admin/getModerationReports' export * as ComAtprotoAdminGetRecord from './types/com/atproto/admin/getRecord' export * as ComAtprotoAdminGetRepo from './types/com/atproto/admin/getRepo' -export * as ComAtprotoAdminRebaseRepo from './types/com/atproto/admin/rebaseRepo' export * as ComAtprotoAdminResolveModerationReports from './types/com/atproto/admin/resolveModerationReports' export * as ComAtprotoAdminReverseModerationAction from './types/com/atproto/admin/reverseModerationAction' export * as ComAtprotoAdminSearchRepos from './types/com/atproto/admin/searchRepos' +export * as ComAtprotoAdminSendEmail from './types/com/atproto/admin/sendEmail' export * as ComAtprotoAdminTakeModerationAction from './types/com/atproto/admin/takeModerationAction' export * as ComAtprotoAdminUpdateAccountEmail from './types/com/atproto/admin/updateAccountEmail' export * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/updateAccountHandle' @@ -156,7 +163,6 @@ export * as ComAtprotoRepoDescribeRepo from './types/com/atproto/repo/describeRe export * as ComAtprotoRepoGetRecord from './types/com/atproto/repo/getRecord' export * as ComAtprotoRepoListRecords from './types/com/atproto/repo/listRecords' export * as ComAtprotoRepoPutRecord from './types/com/atproto/repo/putRecord' -export * as ComAtprotoRepoRebaseRepo from './types/com/atproto/repo/rebaseRepo' export * as ComAtprotoRepoStrongRef from './types/com/atproto/repo/strongRef' export * as ComAtprotoRepoUploadBlob from './types/com/atproto/repo/uploadBlob' export * as ComAtprotoServerCreateAccount from './types/com/atproto/server/createAccount' @@ -179,8 +185,8 @@ export * as ComAtprotoServerRevokeAppPassword from './types/com/atproto/server/r export * as ComAtprotoSyncGetBlob from './types/com/atproto/sync/getBlob' export * as ComAtprotoSyncGetBlocks from './types/com/atproto/sync/getBlocks' export * as ComAtprotoSyncGetCheckout from './types/com/atproto/sync/getCheckout' -export * as ComAtprotoSyncGetCommitPath from './types/com/atproto/sync/getCommitPath' export * as ComAtprotoSyncGetHead from './types/com/atproto/sync/getHead' +export * as ComAtprotoSyncGetLatestCommit from './types/com/atproto/sync/getLatestCommit' export * as ComAtprotoSyncGetRecord from './types/com/atproto/sync/getRecord' export * as ComAtprotoSyncGetRepo from './types/com/atproto/sync/getRepo' export * as ComAtprotoSyncListBlobs from './types/com/atproto/sync/listBlobs' @@ -205,6 +211,7 @@ export * as AppBskyFeedDefs from './types/app/bsky/feed/defs' export * as AppBskyFeedDescribeFeedGenerator from './types/app/bsky/feed/describeFeedGenerator' export * as AppBskyFeedGenerator from './types/app/bsky/feed/generator' export * as AppBskyFeedGetActorFeeds from './types/app/bsky/feed/getActorFeeds' +export * as AppBskyFeedGetActorLikes from './types/app/bsky/feed/getActorLikes' export * as AppBskyFeedGetAuthorFeed from './types/app/bsky/feed/getAuthorFeed' export * as AppBskyFeedGetFeed from './types/app/bsky/feed/getFeed' export * as AppBskyFeedGetFeedGenerator from './types/app/bsky/feed/getFeedGenerator' @@ -214,6 +221,7 @@ export * as AppBskyFeedGetLikes from './types/app/bsky/feed/getLikes' export * as AppBskyFeedGetPostThread from './types/app/bsky/feed/getPostThread' export * as AppBskyFeedGetPosts from './types/app/bsky/feed/getPosts' export * as AppBskyFeedGetRepostedBy from './types/app/bsky/feed/getRepostedBy' +export * as AppBskyFeedGetSuggestedFeeds from './types/app/bsky/feed/getSuggestedFeeds' export * as AppBskyFeedGetTimeline from './types/app/bsky/feed/getTimeline' export * as AppBskyFeedLike from './types/app/bsky/feed/like' export * as AppBskyFeedPost from './types/app/bsky/feed/post' @@ -225,10 +233,13 @@ export * as AppBskyGraphGetBlocks from './types/app/bsky/graph/getBlocks' export * as AppBskyGraphGetFollowers from './types/app/bsky/graph/getFollowers' export * as AppBskyGraphGetFollows from './types/app/bsky/graph/getFollows' export * as AppBskyGraphGetList from './types/app/bsky/graph/getList' +export * as AppBskyGraphGetListBlocks from './types/app/bsky/graph/getListBlocks' export * as AppBskyGraphGetListMutes from './types/app/bsky/graph/getListMutes' export * as AppBskyGraphGetLists from './types/app/bsky/graph/getLists' export * as AppBskyGraphGetMutes from './types/app/bsky/graph/getMutes' +export * as AppBskyGraphGetSuggestedFollowsByActor from './types/app/bsky/graph/getSuggestedFollowsByActor' export * as AppBskyGraphList from './types/app/bsky/graph/list' +export * as AppBskyGraphListblock from './types/app/bsky/graph/listblock' export * as AppBskyGraphListitem from './types/app/bsky/graph/listitem' export * as AppBskyGraphMuteActor from './types/app/bsky/graph/muteActor' export * as AppBskyGraphMuteActorList from './types/app/bsky/graph/muteActorList' @@ -236,10 +247,13 @@ export * as AppBskyGraphUnmuteActor from './types/app/bsky/graph/unmuteActor' export * as AppBskyGraphUnmuteActorList from './types/app/bsky/graph/unmuteActorList' export * as AppBskyNotificationGetUnreadCount from './types/app/bsky/notification/getUnreadCount' export * as AppBskyNotificationListNotifications from './types/app/bsky/notification/listNotifications' +export * as AppBskyNotificationRegisterPush from './types/app/bsky/notification/registerPush' export * as AppBskyNotificationUpdateSeen from './types/app/bsky/notification/updateSeen' export * as AppBskyRichtextFacet from './types/app/bsky/richtext/facet' +export * as AppBskyUnspeccedApplyLabels from './types/app/bsky/unspecced/applyLabels' export * as AppBskyUnspeccedGetPopular from './types/app/bsky/unspecced/getPopular' export * as AppBskyUnspeccedGetPopularFeedGenerators from './types/app/bsky/unspecced/getPopularFeedGenerators' +export * as AppBskyUnspeccedGetTimelineSkeleton from './types/app/bsky/unspecced/getTimelineSkeleton' export const COM_ATPROTO_ADMIN = { DefsTakedown: 'com.atproto.admin.defs#takedown', @@ -438,17 +452,6 @@ export class AdminNS { }) } - rebaseRepo( - data?: ComAtprotoAdminRebaseRepo.InputSchema, - opts?: ComAtprotoAdminRebaseRepo.CallOptions, - ): Promise { - return this._service.xrpc - .call('com.atproto.admin.rebaseRepo', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoAdminRebaseRepo.toKnownErr(e) - }) - } - resolveModerationReports( data?: ComAtprotoAdminResolveModerationReports.InputSchema, opts?: ComAtprotoAdminResolveModerationReports.CallOptions, @@ -482,6 +485,17 @@ export class AdminNS { }) } + sendEmail( + data?: ComAtprotoAdminSendEmail.InputSchema, + opts?: ComAtprotoAdminSendEmail.CallOptions, + ): Promise { + return this._service.xrpc + .call('com.atproto.admin.sendEmail', opts?.qp, data, opts) + .catch((e) => { + throw ComAtprotoAdminSendEmail.toKnownErr(e) + }) + } + takeModerationAction( data?: ComAtprotoAdminTakeModerationAction.InputSchema, opts?: ComAtprotoAdminTakeModerationAction.CallOptions, @@ -668,17 +682,6 @@ export class RepoNS { }) } - rebaseRepo( - data?: ComAtprotoRepoRebaseRepo.InputSchema, - opts?: ComAtprotoRepoRebaseRepo.CallOptions, - ): Promise { - return this._service.xrpc - .call('com.atproto.repo.rebaseRepo', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoRepoRebaseRepo.toKnownErr(e) - }) - } - uploadBlob( data?: ComAtprotoRepoUploadBlob.InputSchema, opts?: ComAtprotoRepoUploadBlob.CallOptions, @@ -915,17 +918,6 @@ export class SyncNS { }) } - getCommitPath( - params?: ComAtprotoSyncGetCommitPath.QueryParams, - opts?: ComAtprotoSyncGetCommitPath.CallOptions, - ): Promise { - return this._service.xrpc - .call('com.atproto.sync.getCommitPath', params, undefined, opts) - .catch((e) => { - throw ComAtprotoSyncGetCommitPath.toKnownErr(e) - }) - } - getHead( params?: ComAtprotoSyncGetHead.QueryParams, opts?: ComAtprotoSyncGetHead.CallOptions, @@ -937,6 +929,17 @@ export class SyncNS { }) } + getLatestCommit( + params?: ComAtprotoSyncGetLatestCommit.QueryParams, + opts?: ComAtprotoSyncGetLatestCommit.CallOptions, + ): Promise { + return this._service.xrpc + .call('com.atproto.sync.getLatestCommit', params, undefined, opts) + .catch((e) => { + throw ComAtprotoSyncGetLatestCommit.toKnownErr(e) + }) + } + getRecord( params?: ComAtprotoSyncGetRecord.QueryParams, opts?: ComAtprotoSyncGetRecord.CallOptions, @@ -1229,6 +1232,17 @@ export class FeedNS { }) } + getActorLikes( + params?: AppBskyFeedGetActorLikes.QueryParams, + opts?: AppBskyFeedGetActorLikes.CallOptions, + ): Promise { + return this._service.xrpc + .call('app.bsky.feed.getActorLikes', params, undefined, opts) + .catch((e) => { + throw AppBskyFeedGetActorLikes.toKnownErr(e) + }) + } + getAuthorFeed( params?: AppBskyFeedGetAuthorFeed.QueryParams, opts?: AppBskyFeedGetAuthorFeed.CallOptions, @@ -1328,6 +1342,17 @@ export class FeedNS { }) } + getSuggestedFeeds( + params?: AppBskyFeedGetSuggestedFeeds.QueryParams, + opts?: AppBskyFeedGetSuggestedFeeds.CallOptions, + ): Promise { + return this._service.xrpc + .call('app.bsky.feed.getSuggestedFeeds', params, undefined, opts) + .catch((e) => { + throw AppBskyFeedGetSuggestedFeeds.toKnownErr(e) + }) + } + getTimeline( params?: AppBskyFeedGetTimeline.QueryParams, opts?: AppBskyFeedGetTimeline.CallOptions, @@ -1589,6 +1614,7 @@ export class GraphNS { block: BlockRecord follow: FollowRecord list: ListRecord + listblock: ListblockRecord listitem: ListitemRecord constructor(service: AtpServiceClient) { @@ -1596,6 +1622,7 @@ export class GraphNS { this.block = new BlockRecord(service) this.follow = new FollowRecord(service) this.list = new ListRecord(service) + this.listblock = new ListblockRecord(service) this.listitem = new ListitemRecord(service) } @@ -1643,6 +1670,17 @@ export class GraphNS { }) } + getListBlocks( + params?: AppBskyGraphGetListBlocks.QueryParams, + opts?: AppBskyGraphGetListBlocks.CallOptions, + ): Promise { + return this._service.xrpc + .call('app.bsky.graph.getListBlocks', params, undefined, opts) + .catch((e) => { + throw AppBskyGraphGetListBlocks.toKnownErr(e) + }) + } + getListMutes( params?: AppBskyGraphGetListMutes.QueryParams, opts?: AppBskyGraphGetListMutes.CallOptions, @@ -1676,6 +1714,22 @@ export class GraphNS { }) } + getSuggestedFollowsByActor( + params?: AppBskyGraphGetSuggestedFollowsByActor.QueryParams, + opts?: AppBskyGraphGetSuggestedFollowsByActor.CallOptions, + ): Promise { + return this._service.xrpc + .call( + 'app.bsky.graph.getSuggestedFollowsByActor', + params, + undefined, + opts, + ) + .catch((e) => { + throw AppBskyGraphGetSuggestedFollowsByActor.toKnownErr(e) + }) + } + muteActor( data?: AppBskyGraphMuteActor.InputSchema, opts?: AppBskyGraphMuteActor.CallOptions, @@ -1904,6 +1958,71 @@ export class ListRecord { } } +export class ListblockRecord { + _service: AtpServiceClient + + constructor(service: AtpServiceClient) { + this._service = service + } + + async list( + params: Omit, + ): Promise<{ + cursor?: string + records: { uri: string; value: AppBskyGraphListblock.Record }[] + }> { + const res = await this._service.xrpc.call('com.atproto.repo.listRecords', { + collection: 'app.bsky.graph.listblock', + ...params, + }) + return res.data + } + + async get( + params: Omit, + ): Promise<{ + uri: string + cid: string + value: AppBskyGraphListblock.Record + }> { + const res = await this._service.xrpc.call('com.atproto.repo.getRecord', { + collection: 'app.bsky.graph.listblock', + ...params, + }) + return res.data + } + + async create( + params: Omit< + ComAtprotoRepoCreateRecord.InputSchema, + 'collection' | 'record' + >, + record: AppBskyGraphListblock.Record, + headers?: Record, + ): Promise<{ uri: string; cid: string }> { + record.$type = 'app.bsky.graph.listblock' + const res = await this._service.xrpc.call( + 'com.atproto.repo.createRecord', + undefined, + { collection: 'app.bsky.graph.listblock', ...params, record }, + { encoding: 'application/json', headers }, + ) + return res.data + } + + async delete( + params: Omit, + headers?: Record, + ): Promise { + await this._service.xrpc.call( + 'com.atproto.repo.deleteRecord', + undefined, + { collection: 'app.bsky.graph.listblock', ...params }, + { headers }, + ) + } +} + export class ListitemRecord { _service: AtpServiceClient @@ -1994,6 +2113,17 @@ export class NotificationNS { }) } + registerPush( + data?: AppBskyNotificationRegisterPush.InputSchema, + opts?: AppBskyNotificationRegisterPush.CallOptions, + ): Promise { + return this._service.xrpc + .call('app.bsky.notification.registerPush', opts?.qp, data, opts) + .catch((e) => { + throw AppBskyNotificationRegisterPush.toKnownErr(e) + }) + } + updateSeen( data?: AppBskyNotificationUpdateSeen.InputSchema, opts?: AppBskyNotificationUpdateSeen.CallOptions, @@ -2021,6 +2151,17 @@ export class UnspeccedNS { this._service = service } + applyLabels( + data?: AppBskyUnspeccedApplyLabels.InputSchema, + opts?: AppBskyUnspeccedApplyLabels.CallOptions, + ): Promise { + return this._service.xrpc + .call('app.bsky.unspecced.applyLabels', opts?.qp, data, opts) + .catch((e) => { + throw AppBskyUnspeccedApplyLabels.toKnownErr(e) + }) + } + getPopular( params?: AppBskyUnspeccedGetPopular.QueryParams, opts?: AppBskyUnspeccedGetPopular.CallOptions, @@ -2047,4 +2188,15 @@ export class UnspeccedNS { throw AppBskyUnspeccedGetPopularFeedGenerators.toKnownErr(e) }) } + + getTimelineSkeleton( + params?: AppBskyUnspeccedGetTimelineSkeleton.QueryParams, + opts?: AppBskyUnspeccedGetTimelineSkeleton.CallOptions, + ): Promise { + return this._service.xrpc + .call('app.bsky.unspecced.getTimelineSkeleton', params, undefined, opts) + .catch((e) => { + throw AppBskyUnspeccedGetTimelineSkeleton.toKnownErr(e) + }) + } } diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index e7a5195328e..f3c93c5e805 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -28,6 +28,11 @@ export const schemaDict = { type: 'ref', ref: 'lex:com.atproto.admin.defs#actionType', }, + durationInHours: { + type: 'integer', + description: + 'Indicates how long this action was meant to be in effect before automatically expiring.', + }, subject: { type: 'union', refs: [ @@ -96,6 +101,11 @@ export const schemaDict = { type: 'ref', ref: 'lex:com.atproto.admin.defs#actionType', }, + durationInHours: { + type: 'integer', + description: + 'Indicates how long this action was meant to be in effect before automatically expiring.', + }, subject: { type: 'union', refs: [ @@ -159,6 +169,11 @@ export const schemaDict = { type: 'ref', ref: 'lex:com.atproto.admin.defs#actionType', }, + durationInHours: { + type: 'integer', + description: + 'Indicates how long this action was meant to be in effect before automatically expiring.', + }, }, }, actionReversal: { @@ -343,6 +358,9 @@ export const schemaDict = { invitesDisabled: { type: 'boolean', }, + inviteNote: { + type: 'string', + }, }, }, repoViewDetail: { @@ -401,6 +419,9 @@ export const schemaDict = { invitesDisabled: { type: 'boolean', }, + inviteNote: { + type: 'string', + }, }, }, repoViewNotFound: { @@ -530,7 +551,6 @@ export const schemaDict = { }, moderation: { type: 'object', - required: [], properties: { currentAction: { type: 'ref', @@ -640,6 +660,11 @@ export const schemaDict = { type: 'string', format: 'did', }, + note: { + type: 'string', + description: + 'Additionally add a note describing why the invites were disabled', + }, }, }, }, @@ -694,6 +719,11 @@ export const schemaDict = { type: 'string', format: 'did', }, + note: { + type: 'string', + description: + 'Additionally add a note describing why the invites were enabled', + }, }, }, }, @@ -865,6 +895,12 @@ export const schemaDict = { type: 'string', }, }, + actionedBy: { + type: 'string', + format: 'did', + description: + 'Get all reports that were actioned by a specific moderator', + }, reporters: { type: 'array', items: { @@ -990,44 +1026,6 @@ export const schemaDict = { }, }, }, - ComAtprotoAdminRebaseRepo: { - lexicon: 1, - id: 'com.atproto.admin.rebaseRepo', - defs: { - main: { - type: 'procedure', - description: "Administrative action to rebase an account's repo", - input: { - encoding: 'application/json', - schema: { - type: 'object', - required: ['repo'], - properties: { - repo: { - type: 'string', - format: 'at-identifier', - description: 'The handle or DID of the repo.', - }, - swapCommit: { - type: 'string', - format: 'cid', - description: - 'Compare and swap with the previous commit by cid.', - }, - }, - }, - }, - errors: [ - { - name: 'InvalidSwap', - }, - { - name: 'ConcurrentWrites', - }, - ], - }, - }, - }, ComAtprotoAdminResolveModerationReports: { lexicon: 1, id: 'com.atproto.admin.resolveModerationReports', @@ -1152,6 +1150,47 @@ export const schemaDict = { }, }, }, + ComAtprotoAdminSendEmail: { + lexicon: 1, + id: 'com.atproto.admin.sendEmail', + defs: { + main: { + type: 'procedure', + description: "Send email to a user's primary email address", + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['recipientDid', 'content'], + properties: { + recipientDid: { + type: 'string', + format: 'did', + }, + content: { + type: 'string', + }, + subject: { + type: 'string', + }, + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['sent'], + properties: { + sent: { + type: 'boolean', + }, + }, + }, + }, + }, + }, + }, ComAtprotoAdminTakeModerationAction: { lexicon: 1, id: 'com.atproto.admin.takeModerationAction', @@ -1202,6 +1241,11 @@ export const schemaDict = { reason: { type: 'string', }, + durationInHours: { + type: 'integer', + description: + 'Indicates how long this action was meant to be in effect before automatically expiring.', + }, createdBy: { type: 'string', format: 'did', @@ -1379,6 +1423,36 @@ export const schemaDict = { }, }, }, + selfLabels: { + type: 'object', + description: + 'Metadata tags on an atproto record, published by the author within the record.', + required: ['values'], + properties: { + values: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:com.atproto.label.defs#selfLabel', + }, + maxLength: 10, + }, + }, + }, + selfLabel: { + type: 'object', + description: + 'Metadata tag on an atproto record, published by the author within the record. Note -- schemas should use #selfLabels, not #selfLabel.', + required: ['val'], + properties: { + val: { + type: 'string', + maxLength: 128, + description: + 'the short string name of the value or type of this label', + }, + }, + }, }, }, ComAtprotoLabelQueryLabels: { @@ -1605,7 +1679,7 @@ export const schemaDict = { }, reasonSexual: { type: 'token', - description: 'Unwanted or mis-labeled sexual content', + description: 'Unwanted or mislabeled sexual content', }, reasonRude: { type: 'token', @@ -2117,44 +2191,6 @@ export const schemaDict = { }, }, }, - ComAtprotoRepoRebaseRepo: { - lexicon: 1, - id: 'com.atproto.repo.rebaseRepo', - defs: { - main: { - type: 'procedure', - description: 'Simple rebase of repo that deletes history', - input: { - encoding: 'application/json', - schema: { - type: 'object', - required: ['repo'], - properties: { - repo: { - type: 'string', - format: 'at-identifier', - description: 'The handle or DID of the repo.', - }, - swapCommit: { - type: 'string', - format: 'cid', - description: - 'Compare and swap with the previous commit by cid.', - }, - }, - }, - }, - errors: [ - { - name: 'InvalidSwap', - }, - { - name: 'ConcurrentWrites', - }, - ], - }, - }, - }, ComAtprotoRepoStrongRef: { lexicon: 1, id: 'com.atproto.repo.strongRef', @@ -2957,7 +2993,7 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Gets the repo state.', + description: 'DEPRECATED - please use com.atproto.sync.getRepo instead', parameters: { type: 'params', required: ['did'], @@ -2967,12 +3003,6 @@ export const schemaDict = { format: 'did', description: 'The DID of the repo.', }, - commit: { - type: 'string', - format: 'cid', - description: - 'The commit to get the checkout from. Defaults to current HEAD.', - }, }, }, output: { @@ -2981,13 +3011,14 @@ export const schemaDict = { }, }, }, - ComAtprotoSyncGetCommitPath: { + ComAtprotoSyncGetHead: { lexicon: 1, - id: 'com.atproto.sync.getCommitPath', + id: 'com.atproto.sync.getHead', defs: { main: { type: 'query', - description: 'Gets the path of repo commits', + description: + 'DEPRECATED - please use com.atproto.sync.getLatestCommit instead', parameters: { type: 'params', required: ['did'], @@ -2997,44 +3028,36 @@ export const schemaDict = { format: 'did', description: 'The DID of the repo.', }, - latest: { - type: 'string', - format: 'cid', - description: 'The most recent commit', - }, - earliest: { - type: 'string', - format: 'cid', - description: 'The earliest commit to start from', - }, }, }, output: { encoding: 'application/json', schema: { type: 'object', - required: ['commits'], + required: ['root'], properties: { - commits: { - type: 'array', - items: { - type: 'string', - format: 'cid', - }, + root: { + type: 'string', + format: 'cid', }, }, }, }, + errors: [ + { + name: 'HeadNotFound', + }, + ], }, }, }, - ComAtprotoSyncGetHead: { + ComAtprotoSyncGetLatestCommit: { lexicon: 1, - id: 'com.atproto.sync.getHead', + id: 'com.atproto.sync.getLatestCommit', defs: { main: { type: 'query', - description: 'Gets the current HEAD CID of a repo.', + description: 'Gets the current commit CID & revision of the repo.', parameters: { type: 'params', required: ['did'], @@ -3050,18 +3073,21 @@ export const schemaDict = { encoding: 'application/json', schema: { type: 'object', - required: ['root'], + required: ['cid', 'rev'], properties: { - root: { + cid: { type: 'string', format: 'cid', }, + rev: { + type: 'string', + }, }, }, }, errors: [ { - name: 'HeadNotFound', + name: 'RepoNotFound', }, ], }, @@ -3110,7 +3136,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Gets the repo state.', + description: + "Gets the did's repo, optionally catching up from a specific revision.", parameters: { type: 'params', required: ['did'], @@ -3120,16 +3147,9 @@ export const schemaDict = { format: 'did', description: 'The DID of the repo.', }, - earliest: { + since: { type: 'string', - format: 'cid', - description: - 'The earliest commit in the commit range (not inclusive)', - }, - latest: { - type: 'string', - format: 'cid', - description: 'The latest commit in the commit range (inclusive)', + description: 'The revision of the repo to catch up from.', }, }, }, @@ -3145,7 +3165,7 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'List blob cids for some range of commits', + description: 'List blob cids since some revision', parameters: { type: 'params', required: ['did'], @@ -3155,15 +3175,18 @@ export const schemaDict = { format: 'did', description: 'The DID of the repo.', }, - latest: { + since: { type: 'string', - format: 'cid', - description: 'The most recent commit', + description: 'Optional revision of the repo to list blobs since', + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 1000, + default: 500, }, - earliest: { + cursor: { type: 'string', - format: 'cid', - description: 'The earliest commit to start from', }, }, }, @@ -3173,6 +3196,9 @@ export const schemaDict = { type: 'object', required: ['cids'], properties: { + cursor: { + type: 'string', + }, cids: { type: 'array', items: { @@ -3337,13 +3363,14 @@ export const schemaDict = { 'tooBig', 'repo', 'commit', - 'prev', + 'rev', + 'since', 'blocks', 'ops', 'blobs', 'time', ], - nullable: ['prev'], + nullable: ['prev', 'since'], properties: { seq: { type: 'integer', @@ -3364,6 +3391,14 @@ export const schemaDict = { prev: { type: 'cid-link', }, + rev: { + type: 'string', + description: 'The rev of the emitted commit', + }, + since: { + type: 'string', + description: 'The rev of the last emitted commit from this repo', + }, blocks: { type: 'bytes', description: 'CAR file containing relevant blocks', @@ -3463,6 +3498,8 @@ export const schemaDict = { }, repoOp: { type: 'object', + description: + "A repo operation, ie a write of a single record. For creates and updates, cid is the record's CID as of this operation. For deletes, it's null.", required: ['action', 'path', 'cid'], nullable: ['cid'], properties: { @@ -3649,6 +3686,7 @@ export const schemaDict = { 'lex:app.bsky.actor.defs#adultContentPref', 'lex:app.bsky.actor.defs#contentLabelPref', 'lex:app.bsky.actor.defs#savedFeedsPref', + 'lex:app.bsky.actor.defs#personalDetailsPref', ], }, }, @@ -3695,6 +3733,16 @@ export const schemaDict = { }, }, }, + personalDetailsPref: { + type: 'object', + properties: { + birthDate: { + type: 'string', + format: 'datetime', + description: 'The birth date of the owner of the account.', + }, + }, + }, }, }, AppBskyActorGetPreferences: { @@ -3863,6 +3911,10 @@ export const schemaDict = { accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, + labels: { + type: 'union', + refs: ['lex:com.atproto.label.defs#selfLabels'], + }, }, }, }, @@ -4076,6 +4128,26 @@ export const schemaDict = { alt: { type: 'string', }, + aspectRatio: { + type: 'ref', + ref: 'lex:app.bsky.embed.images#aspectRatio', + }, + }, + }, + aspectRatio: { + type: 'object', + description: + 'width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit.', + required: ['width', 'height'], + properties: { + width: { + type: 'integer', + minimum: 1, + }, + height: { + type: 'integer', + minimum: 1, + }, }, }, view: { @@ -4105,6 +4177,10 @@ export const schemaDict = { alt: { type: 'string', }, + aspectRatio: { + type: 'ref', + ref: 'lex:app.bsky.embed.images#aspectRatio', + }, }, }, }, @@ -4187,22 +4263,34 @@ export const schemaDict = { }, viewNotFound: { type: 'object', - required: ['uri'], + required: ['uri', 'notFound'], properties: { uri: { type: 'string', format: 'at-uri', }, + notFound: { + type: 'boolean', + const: true, + }, }, }, viewBlocked: { type: 'object', - required: ['uri'], + required: ['uri', 'blocked', 'author'], properties: { uri: { type: 'string', format: 'at-uri', }, + blocked: { + type: 'boolean', + const: true, + }, + author: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#blockedAuthor', + }, }, }, }, @@ -4416,7 +4504,7 @@ export const schemaDict = { }, blockedPost: { type: 'object', - required: ['uri', 'blocked'], + required: ['uri', 'blocked', 'author'], properties: { uri: { type: 'string', @@ -4426,17 +4514,35 @@ export const schemaDict = { type: 'boolean', const: true, }, + author: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#blockedAuthor', + }, }, }, - generatorView: { + blockedAuthor: { type: 'object', - required: ['uri', 'cid', 'did', 'creator', 'displayName', 'indexedAt'], + required: ['did'], properties: { - uri: { + did: { type: 'string', - format: 'at-uri', + format: 'did', }, - cid: { + viewer: { + type: 'ref', + ref: 'lex:app.bsky.actor.defs#viewerState', + }, + }, + }, + generatorView: { + type: 'object', + required: ['uri', 'cid', 'did', 'creator', 'displayName', 'indexedAt'], + properties: { + uri: { + type: 'string', + format: 'at-uri', + }, + cid: { type: 'string', format: 'cid', }, @@ -4609,6 +4715,10 @@ export const schemaDict = { accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, + labels: { + type: 'union', + refs: ['lex:com.atproto.label.defs#selfLabels'], + }, createdAt: { type: 'string', format: 'datetime', @@ -4666,6 +4776,62 @@ export const schemaDict = { }, }, }, + AppBskyFeedGetActorLikes: { + lexicon: 1, + id: 'app.bsky.feed.getActorLikes', + defs: { + main: { + type: 'query', + description: 'A view of the posts liked by an actor.', + parameters: { + type: 'params', + required: ['actor'], + properties: { + actor: { + type: 'string', + format: 'at-identifier', + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['feed'], + properties: { + cursor: { + type: 'string', + }, + feed: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#feedViewPost', + }, + }, + }, + }, + }, + errors: [ + { + name: 'BlockedActor', + }, + { + name: 'BlockedByActor', + }, + ], + }, + }, + }, AppBskyFeedGetAuthorFeed: { lexicon: 1, id: 'app.bsky.feed.getAuthorFeed', @@ -4690,6 +4856,15 @@ export const schemaDict = { cursor: { type: 'string', }, + filter: { + type: 'string', + knownValues: [ + 'posts_with_replies', + 'posts_no_replies', + 'posts_with_media', + ], + default: 'posts_with_replies', + }, }, }, output: { @@ -5137,6 +5312,49 @@ export const schemaDict = { }, }, }, + AppBskyFeedGetSuggestedFeeds: { + lexicon: 1, + id: 'app.bsky.feed.getSuggestedFeeds', + defs: { + main: { + type: 'query', + description: 'Get a list of suggested feeds for the viewer.', + parameters: { + type: 'params', + properties: { + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['feeds'], + properties: { + cursor: { + type: 'string', + }, + feeds: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#generatorView', + }, + }, + }, + }, + }, + }, + }, + }, AppBskyFeedGetTimeline: { lexicon: 1, id: 'app.bsky.feed.getTimeline', @@ -5259,6 +5477,10 @@ export const schemaDict = { format: 'language', }, }, + labels: { + type: 'union', + refs: ['lex:com.atproto.label.defs#selfLabels'], + }, createdAt: { type: 'string', format: 'datetime', @@ -5478,6 +5700,10 @@ export const schemaDict = { muted: { type: 'boolean', }, + blocked: { + type: 'string', + format: 'at-uri', + }, }, }, }, @@ -5706,6 +5932,49 @@ export const schemaDict = { }, }, }, + AppBskyGraphGetListBlocks: { + lexicon: 1, + id: 'app.bsky.graph.getListBlocks', + defs: { + main: { + type: 'query', + description: "Which lists is the requester's account blocking?", + parameters: { + type: 'params', + properties: { + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['lists'], + properties: { + cursor: { + type: 'string', + }, + lists: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.graph.defs#listView', + }, + }, + }, + }, + }, + }, + }, + }, AppBskyGraphGetListMutes: { lexicon: 1, id: 'app.bsky.graph.getListMutes', @@ -5840,6 +6109,42 @@ export const schemaDict = { }, }, }, + AppBskyGraphGetSuggestedFollowsByActor: { + lexicon: 1, + id: 'app.bsky.graph.getSuggestedFollowsByActor', + defs: { + main: { + type: 'query', + description: 'Get suggested follows related to a given actor.', + parameters: { + type: 'params', + required: ['actor'], + properties: { + actor: { + type: 'string', + format: 'at-identifier', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['suggestions'], + properties: { + suggestions: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.actor.defs#profileView', + }, + }, + }, + }, + }, + }, + }, + }, AppBskyGraphList: { lexicon: 1, id: 'app.bsky.graph.list', @@ -5878,6 +6183,35 @@ export const schemaDict = { accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, + labels: { + type: 'union', + refs: ['lex:com.atproto.label.defs#selfLabels'], + }, + createdAt: { + type: 'string', + format: 'datetime', + }, + }, + }, + }, + }, + }, + AppBskyGraphListblock: { + lexicon: 1, + id: 'app.bsky.graph.listblock', + defs: { + main: { + type: 'record', + description: 'A block of an entire list of actors.', + key: 'tid', + record: { + type: 'object', + required: ['subject', 'createdAt'], + properties: { + subject: { + type: 'string', + format: 'at-uri', + }, createdAt: { type: 'string', format: 'datetime', @@ -6144,6 +6478,39 @@ export const schemaDict = { }, }, }, + AppBskyNotificationRegisterPush: { + lexicon: 1, + id: 'app.bsky.notification.registerPush', + defs: { + main: { + type: 'procedure', + description: 'Register for push notifications with a service', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['serviceDid', 'token', 'platform', 'appId'], + properties: { + serviceDid: { + type: 'string', + format: 'did', + }, + token: { + type: 'string', + }, + platform: { + type: 'string', + knownValues: ['ios', 'android', 'web'], + }, + appId: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, AppBskyNotificationUpdateSeen: { lexicon: 1, id: 'app.bsky.notification.updateSeen', @@ -6231,6 +6598,32 @@ export const schemaDict = { }, }, }, + AppBskyUnspeccedApplyLabels: { + lexicon: 1, + id: 'app.bsky.unspecced.applyLabels', + defs: { + main: { + type: 'procedure', + description: 'Allow a labeler to apply labels directly.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['labels'], + properties: { + labels: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:com.atproto.label.defs#label', + }, + }, + }, + }, + }, + }, + }, + }, AppBskyUnspeccedGetPopular: { lexicon: 1, id: 'app.bsky.unspecced.getPopular', @@ -6285,12 +6678,32 @@ export const schemaDict = { main: { type: 'query', description: 'An unspecced view of globally popular feed generators', + parameters: { + type: 'params', + properties: { + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + query: { + type: 'string', + }, + }, + }, output: { encoding: 'application/json', schema: { type: 'object', required: ['feeds'], properties: { + cursor: { + type: 'string', + }, feeds: { type: 'array', items: { @@ -6304,6 +6717,54 @@ export const schemaDict = { }, }, }, + AppBskyUnspeccedGetTimelineSkeleton: { + lexicon: 1, + id: 'app.bsky.unspecced.getTimelineSkeleton', + defs: { + main: { + type: 'query', + description: 'A skeleton of a timeline - UNSPECCED & WILL GO AWAY SOON', + parameters: { + type: 'params', + properties: { + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['feed'], + properties: { + cursor: { + type: 'string', + }, + feed: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#skeletonFeedPost', + }, + }, + }, + }, + }, + errors: [ + { + name: 'UnknownFeed', + }, + ], + }, + }, + }, } export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[] export const lexicons: Lexicons = new Lexicons(schemas) @@ -6320,12 +6781,12 @@ export const ids = { ComAtprotoAdminGetModerationReports: 'com.atproto.admin.getModerationReports', ComAtprotoAdminGetRecord: 'com.atproto.admin.getRecord', ComAtprotoAdminGetRepo: 'com.atproto.admin.getRepo', - ComAtprotoAdminRebaseRepo: 'com.atproto.admin.rebaseRepo', ComAtprotoAdminResolveModerationReports: 'com.atproto.admin.resolveModerationReports', ComAtprotoAdminReverseModerationAction: 'com.atproto.admin.reverseModerationAction', ComAtprotoAdminSearchRepos: 'com.atproto.admin.searchRepos', + ComAtprotoAdminSendEmail: 'com.atproto.admin.sendEmail', ComAtprotoAdminTakeModerationAction: 'com.atproto.admin.takeModerationAction', ComAtprotoAdminUpdateAccountEmail: 'com.atproto.admin.updateAccountEmail', ComAtprotoAdminUpdateAccountHandle: 'com.atproto.admin.updateAccountHandle', @@ -6343,7 +6804,6 @@ export const ids = { ComAtprotoRepoGetRecord: 'com.atproto.repo.getRecord', ComAtprotoRepoListRecords: 'com.atproto.repo.listRecords', ComAtprotoRepoPutRecord: 'com.atproto.repo.putRecord', - ComAtprotoRepoRebaseRepo: 'com.atproto.repo.rebaseRepo', ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef', ComAtprotoRepoUploadBlob: 'com.atproto.repo.uploadBlob', ComAtprotoServerCreateAccount: 'com.atproto.server.createAccount', @@ -6369,8 +6829,8 @@ export const ids = { ComAtprotoSyncGetBlob: 'com.atproto.sync.getBlob', ComAtprotoSyncGetBlocks: 'com.atproto.sync.getBlocks', ComAtprotoSyncGetCheckout: 'com.atproto.sync.getCheckout', - ComAtprotoSyncGetCommitPath: 'com.atproto.sync.getCommitPath', ComAtprotoSyncGetHead: 'com.atproto.sync.getHead', + ComAtprotoSyncGetLatestCommit: 'com.atproto.sync.getLatestCommit', ComAtprotoSyncGetRecord: 'com.atproto.sync.getRecord', ComAtprotoSyncGetRepo: 'com.atproto.sync.getRepo', ComAtprotoSyncListBlobs: 'com.atproto.sync.listBlobs', @@ -6395,6 +6855,7 @@ export const ids = { AppBskyFeedDescribeFeedGenerator: 'app.bsky.feed.describeFeedGenerator', AppBskyFeedGenerator: 'app.bsky.feed.generator', AppBskyFeedGetActorFeeds: 'app.bsky.feed.getActorFeeds', + AppBskyFeedGetActorLikes: 'app.bsky.feed.getActorLikes', AppBskyFeedGetAuthorFeed: 'app.bsky.feed.getAuthorFeed', AppBskyFeedGetFeed: 'app.bsky.feed.getFeed', AppBskyFeedGetFeedGenerator: 'app.bsky.feed.getFeedGenerator', @@ -6404,6 +6865,7 @@ export const ids = { AppBskyFeedGetPostThread: 'app.bsky.feed.getPostThread', AppBskyFeedGetPosts: 'app.bsky.feed.getPosts', AppBskyFeedGetRepostedBy: 'app.bsky.feed.getRepostedBy', + AppBskyFeedGetSuggestedFeeds: 'app.bsky.feed.getSuggestedFeeds', AppBskyFeedGetTimeline: 'app.bsky.feed.getTimeline', AppBskyFeedLike: 'app.bsky.feed.like', AppBskyFeedPost: 'app.bsky.feed.post', @@ -6415,10 +6877,14 @@ export const ids = { AppBskyGraphGetFollowers: 'app.bsky.graph.getFollowers', AppBskyGraphGetFollows: 'app.bsky.graph.getFollows', AppBskyGraphGetList: 'app.bsky.graph.getList', + AppBskyGraphGetListBlocks: 'app.bsky.graph.getListBlocks', AppBskyGraphGetListMutes: 'app.bsky.graph.getListMutes', AppBskyGraphGetLists: 'app.bsky.graph.getLists', AppBskyGraphGetMutes: 'app.bsky.graph.getMutes', + AppBskyGraphGetSuggestedFollowsByActor: + 'app.bsky.graph.getSuggestedFollowsByActor', AppBskyGraphList: 'app.bsky.graph.list', + AppBskyGraphListblock: 'app.bsky.graph.listblock', AppBskyGraphListitem: 'app.bsky.graph.listitem', AppBskyGraphMuteActor: 'app.bsky.graph.muteActor', AppBskyGraphMuteActorList: 'app.bsky.graph.muteActorList', @@ -6427,9 +6893,12 @@ export const ids = { AppBskyNotificationGetUnreadCount: 'app.bsky.notification.getUnreadCount', AppBskyNotificationListNotifications: 'app.bsky.notification.listNotifications', + AppBskyNotificationRegisterPush: 'app.bsky.notification.registerPush', AppBskyNotificationUpdateSeen: 'app.bsky.notification.updateSeen', AppBskyRichtextFacet: 'app.bsky.richtext.facet', + AppBskyUnspeccedApplyLabels: 'app.bsky.unspecced.applyLabels', AppBskyUnspeccedGetPopular: 'app.bsky.unspecced.getPopular', AppBskyUnspeccedGetPopularFeedGenerators: 'app.bsky.unspecced.getPopularFeedGenerators', + AppBskyUnspeccedGetTimelineSkeleton: 'app.bsky.unspecced.getTimelineSkeleton', } diff --git a/packages/api/src/client/types/app/bsky/actor/defs.ts b/packages/api/src/client/types/app/bsky/actor/defs.ts index f9ab03dcb81..7d3c9fcaac6 100644 --- a/packages/api/src/client/types/app/bsky/actor/defs.ts +++ b/packages/api/src/client/types/app/bsky/actor/defs.ts @@ -108,6 +108,7 @@ export type Preferences = ( | AdultContentPref | ContentLabelPref | SavedFeedsPref + | PersonalDetailsPref | { $type: string; [k: string]: unknown } )[] @@ -163,3 +164,21 @@ export function isSavedFeedsPref(v: unknown): v is SavedFeedsPref { export function validateSavedFeedsPref(v: unknown): ValidationResult { return lexicons.validate('app.bsky.actor.defs#savedFeedsPref', v) } + +export interface PersonalDetailsPref { + /** The birth date of the owner of the account. */ + birthDate?: string + [k: string]: unknown +} + +export function isPersonalDetailsPref(v: unknown): v is PersonalDetailsPref { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.actor.defs#personalDetailsPref' + ) +} + +export function validatePersonalDetailsPref(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.actor.defs#personalDetailsPref', v) +} diff --git a/packages/api/src/client/types/app/bsky/actor/profile.ts b/packages/api/src/client/types/app/bsky/actor/profile.ts index 749a2e192b3..fa36f4298f1 100644 --- a/packages/api/src/client/types/app/bsky/actor/profile.ts +++ b/packages/api/src/client/types/app/bsky/actor/profile.ts @@ -5,12 +5,16 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' export interface Record { displayName?: string description?: string avatar?: BlobRef banner?: BlobRef + labels?: + | ComAtprotoLabelDefs.SelfLabels + | { $type: string; [k: string]: unknown } [k: string]: unknown } diff --git a/packages/api/src/client/types/app/bsky/embed/images.ts b/packages/api/src/client/types/app/bsky/embed/images.ts index f98dc3f64db..77909a4b3b0 100644 --- a/packages/api/src/client/types/app/bsky/embed/images.ts +++ b/packages/api/src/client/types/app/bsky/embed/images.ts @@ -27,6 +27,7 @@ export function validateMain(v: unknown): ValidationResult { export interface Image { image: BlobRef alt: string + aspectRatio?: AspectRatio [k: string]: unknown } @@ -40,6 +41,25 @@ export function validateImage(v: unknown): ValidationResult { return lexicons.validate('app.bsky.embed.images#image', v) } +/** width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit. */ +export interface AspectRatio { + width: number + height: number + [k: string]: unknown +} + +export function isAspectRatio(v: unknown): v is AspectRatio { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.embed.images#aspectRatio' + ) +} + +export function validateAspectRatio(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.embed.images#aspectRatio', v) +} + export interface View { images: ViewImage[] [k: string]: unknown @@ -59,6 +79,7 @@ export interface ViewImage { thumb: string fullsize: string alt: string + aspectRatio?: AspectRatio [k: string]: unknown } diff --git a/packages/api/src/client/types/app/bsky/embed/record.ts b/packages/api/src/client/types/app/bsky/embed/record.ts index 223669e03b5..caee8f08cdd 100644 --- a/packages/api/src/client/types/app/bsky/embed/record.ts +++ b/packages/api/src/client/types/app/bsky/embed/record.ts @@ -84,6 +84,7 @@ export function validateViewRecord(v: unknown): ValidationResult { export interface ViewNotFound { uri: string + notFound: true [k: string]: unknown } @@ -101,6 +102,8 @@ export function validateViewNotFound(v: unknown): ValidationResult { export interface ViewBlocked { uri: string + blocked: true + author: AppBskyFeedDefs.BlockedAuthor [k: string]: unknown } diff --git a/packages/api/src/client/types/app/bsky/feed/defs.ts b/packages/api/src/client/types/app/bsky/feed/defs.ts index 5aa51290fed..1270dab250b 100644 --- a/packages/api/src/client/types/app/bsky/feed/defs.ts +++ b/packages/api/src/client/types/app/bsky/feed/defs.ts @@ -171,6 +171,7 @@ export function validateNotFoundPost(v: unknown): ValidationResult { export interface BlockedPost { uri: string blocked: true + author: BlockedAuthor [k: string]: unknown } @@ -186,6 +187,24 @@ export function validateBlockedPost(v: unknown): ValidationResult { return lexicons.validate('app.bsky.feed.defs#blockedPost', v) } +export interface BlockedAuthor { + did: string + viewer?: AppBskyActorDefs.ViewerState + [k: string]: unknown +} + +export function isBlockedAuthor(v: unknown): v is BlockedAuthor { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.feed.defs#blockedAuthor' + ) +} + +export function validateBlockedAuthor(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.feed.defs#blockedAuthor', v) +} + export interface GeneratorView { uri: string cid: string diff --git a/packages/api/src/client/types/app/bsky/feed/generator.ts b/packages/api/src/client/types/app/bsky/feed/generator.ts index 980c8fdf6fe..d9df6464f94 100644 --- a/packages/api/src/client/types/app/bsky/feed/generator.ts +++ b/packages/api/src/client/types/app/bsky/feed/generator.ts @@ -6,6 +6,7 @@ import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' import * as AppBskyRichtextFacet from '../richtext/facet' +import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' export interface Record { did: string @@ -13,6 +14,9 @@ export interface Record { description?: string descriptionFacets?: AppBskyRichtextFacet.Main[] avatar?: BlobRef + labels?: + | ComAtprotoLabelDefs.SelfLabels + | { $type: string; [k: string]: unknown } createdAt: string [k: string]: unknown } diff --git a/packages/api/src/client/types/app/bsky/feed/getActorLikes.ts b/packages/api/src/client/types/app/bsky/feed/getActorLikes.ts new file mode 100644 index 00000000000..0f101ca4c3b --- /dev/null +++ b/packages/api/src/client/types/app/bsky/feed/getActorLikes.ts @@ -0,0 +1,53 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { Headers, XRPCError } from '@atproto/xrpc' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' +import * as AppBskyFeedDefs from './defs' + +export interface QueryParams { + actor: string + limit?: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + feed: AppBskyFeedDefs.FeedViewPost[] + [k: string]: unknown +} + +export interface CallOptions { + headers?: Headers +} + +export interface Response { + success: boolean + headers: Headers + data: OutputSchema +} + +export class BlockedActorError extends XRPCError { + constructor(src: XRPCError) { + super(src.status, src.error, src.message, src.headers) + } +} + +export class BlockedByActorError extends XRPCError { + constructor(src: XRPCError) { + super(src.status, src.error, src.message, src.headers) + } +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + if (e.error === 'BlockedActor') return new BlockedActorError(e) + if (e.error === 'BlockedByActor') return new BlockedByActorError(e) + } + return e +} diff --git a/packages/api/src/client/types/app/bsky/feed/getAuthorFeed.ts b/packages/api/src/client/types/app/bsky/feed/getAuthorFeed.ts index 442a7efcc08..3f3abc9933f 100644 --- a/packages/api/src/client/types/app/bsky/feed/getAuthorFeed.ts +++ b/packages/api/src/client/types/app/bsky/feed/getAuthorFeed.ts @@ -12,6 +12,11 @@ export interface QueryParams { actor: string limit?: number cursor?: string + filter?: + | 'posts_with_replies' + | 'posts_no_replies' + | 'posts_with_media' + | (string & {}) } export type InputSchema = undefined @@ -34,13 +39,13 @@ export interface Response { export class BlockedActorError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } export class BlockedByActorError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/app/bsky/feed/getFeed.ts b/packages/api/src/client/types/app/bsky/feed/getFeed.ts index dfc05bd2481..65ede1fd2a4 100644 --- a/packages/api/src/client/types/app/bsky/feed/getFeed.ts +++ b/packages/api/src/client/types/app/bsky/feed/getFeed.ts @@ -34,7 +34,7 @@ export interface Response { export class UnknownFeedError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/app/bsky/feed/getFeedSkeleton.ts b/packages/api/src/client/types/app/bsky/feed/getFeedSkeleton.ts index 434010b1c3f..0aa325d7fec 100644 --- a/packages/api/src/client/types/app/bsky/feed/getFeedSkeleton.ts +++ b/packages/api/src/client/types/app/bsky/feed/getFeedSkeleton.ts @@ -34,7 +34,7 @@ export interface Response { export class UnknownFeedError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/app/bsky/feed/getPostThread.ts b/packages/api/src/client/types/app/bsky/feed/getPostThread.ts index 44ab27fcefe..d3865db9ee2 100644 --- a/packages/api/src/client/types/app/bsky/feed/getPostThread.ts +++ b/packages/api/src/client/types/app/bsky/feed/getPostThread.ts @@ -37,7 +37,7 @@ export interface Response { export class NotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/com/atproto/sync/getCommitPath.ts b/packages/api/src/client/types/app/bsky/feed/getSuggestedFeeds.ts similarity index 78% rename from packages/api/src/client/types/com/atproto/sync/getCommitPath.ts rename to packages/api/src/client/types/app/bsky/feed/getSuggestedFeeds.ts index 4caa728a9f2..ef4fe1cfefe 100644 --- a/packages/api/src/client/types/com/atproto/sync/getCommitPath.ts +++ b/packages/api/src/client/types/app/bsky/feed/getSuggestedFeeds.ts @@ -6,20 +6,18 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import * as AppBskyFeedDefs from './defs' export interface QueryParams { - /** The DID of the repo. */ - did: string - /** The most recent commit */ - latest?: string - /** The earliest commit to start from */ - earliest?: string + limit?: number + cursor?: string } export type InputSchema = undefined export interface OutputSchema { - commits: string[] + cursor?: string + feeds: AppBskyFeedDefs.GeneratorView[] [k: string]: unknown } diff --git a/packages/api/src/client/types/app/bsky/feed/post.ts b/packages/api/src/client/types/app/bsky/feed/post.ts index f85797acaf8..1e326692640 100644 --- a/packages/api/src/client/types/app/bsky/feed/post.ts +++ b/packages/api/src/client/types/app/bsky/feed/post.ts @@ -10,6 +10,7 @@ import * as AppBskyEmbedImages from '../embed/images' import * as AppBskyEmbedExternal from '../embed/external' import * as AppBskyEmbedRecord from '../embed/record' import * as AppBskyEmbedRecordWithMedia from '../embed/recordWithMedia' +import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' export interface Record { @@ -25,6 +26,9 @@ export interface Record { | AppBskyEmbedRecordWithMedia.Main | { $type: string; [k: string]: unknown } langs?: string[] + labels?: + | ComAtprotoLabelDefs.SelfLabels + | { $type: string; [k: string]: unknown } createdAt: string [k: string]: unknown } diff --git a/packages/api/src/client/types/app/bsky/graph/defs.ts b/packages/api/src/client/types/app/bsky/graph/defs.ts index 2e70bef750e..566ea2446d8 100644 --- a/packages/api/src/client/types/app/bsky/graph/defs.ts +++ b/packages/api/src/client/types/app/bsky/graph/defs.ts @@ -81,6 +81,7 @@ export const MODLIST = 'app.bsky.graph.defs#modlist' export interface ListViewerState { muted?: boolean + blocked?: string [k: string]: unknown } diff --git a/packages/api/src/client/types/app/bsky/graph/getListBlocks.ts b/packages/api/src/client/types/app/bsky/graph/getListBlocks.ts new file mode 100644 index 00000000000..052587c603e --- /dev/null +++ b/packages/api/src/client/types/app/bsky/graph/getListBlocks.ts @@ -0,0 +1,38 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { Headers, XRPCError } from '@atproto/xrpc' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' +import * as AppBskyGraphDefs from './defs' + +export interface QueryParams { + limit?: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + lists: AppBskyGraphDefs.ListView[] + [k: string]: unknown +} + +export interface CallOptions { + headers?: Headers +} + +export interface Response { + success: boolean + headers: Headers + data: OutputSchema +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + } + return e +} diff --git a/packages/api/src/client/types/app/bsky/graph/getSuggestedFollowsByActor.ts b/packages/api/src/client/types/app/bsky/graph/getSuggestedFollowsByActor.ts new file mode 100644 index 00000000000..8ff7ed414cb --- /dev/null +++ b/packages/api/src/client/types/app/bsky/graph/getSuggestedFollowsByActor.ts @@ -0,0 +1,36 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { Headers, XRPCError } from '@atproto/xrpc' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' +import * as AppBskyActorDefs from '../actor/defs' + +export interface QueryParams { + actor: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + suggestions: AppBskyActorDefs.ProfileView[] + [k: string]: unknown +} + +export interface CallOptions { + headers?: Headers +} + +export interface Response { + success: boolean + headers: Headers + data: OutputSchema +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + } + return e +} diff --git a/packages/api/src/client/types/app/bsky/graph/list.ts b/packages/api/src/client/types/app/bsky/graph/list.ts index 0b162046b6f..4fe6dd8ed8b 100644 --- a/packages/api/src/client/types/app/bsky/graph/list.ts +++ b/packages/api/src/client/types/app/bsky/graph/list.ts @@ -7,6 +7,7 @@ import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' import * as AppBskyGraphDefs from './defs' import * as AppBskyRichtextFacet from '../richtext/facet' +import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' export interface Record { purpose: AppBskyGraphDefs.ListPurpose @@ -14,6 +15,9 @@ export interface Record { description?: string descriptionFacets?: AppBskyRichtextFacet.Main[] avatar?: BlobRef + labels?: + | ComAtprotoLabelDefs.SelfLabels + | { $type: string; [k: string]: unknown } createdAt: string [k: string]: unknown } diff --git a/packages/api/src/client/types/app/bsky/graph/listblock.ts b/packages/api/src/client/types/app/bsky/graph/listblock.ts new file mode 100644 index 00000000000..770dfbb0775 --- /dev/null +++ b/packages/api/src/client/types/app/bsky/graph/listblock.ts @@ -0,0 +1,26 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' + +export interface Record { + subject: string + createdAt: string + [k: string]: unknown +} + +export function isRecord(v: unknown): v is Record { + return ( + isObj(v) && + hasProp(v, '$type') && + (v.$type === 'app.bsky.graph.listblock#main' || + v.$type === 'app.bsky.graph.listblock') + ) +} + +export function validateRecord(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.graph.listblock#main', v) +} diff --git a/packages/api/src/client/types/com/atproto/repo/rebaseRepo.ts b/packages/api/src/client/types/app/bsky/notification/registerPush.ts similarity index 53% rename from packages/api/src/client/types/com/atproto/repo/rebaseRepo.ts rename to packages/api/src/client/types/app/bsky/notification/registerPush.ts index fc91da19bf7..9354ef76766 100644 --- a/packages/api/src/client/types/com/atproto/repo/rebaseRepo.ts +++ b/packages/api/src/client/types/app/bsky/notification/registerPush.ts @@ -10,10 +10,10 @@ import { CID } from 'multiformats/cid' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ - repo: string - /** Compare and swap with the previous commit by cid. */ - swapCommit?: string + serviceDid: string + token: string + platform: 'ios' | 'android' | 'web' | (string & {}) + appId: string [k: string]: unknown } @@ -28,22 +28,8 @@ export interface Response { headers: Headers } -export class InvalidSwapError extends XRPCError { - constructor(src: XRPCError) { - super(src.status, src.error, src.message) - } -} - -export class ConcurrentWritesError extends XRPCError { - constructor(src: XRPCError) { - super(src.status, src.error, src.message) - } -} - export function toKnownErr(e: any) { if (e instanceof XRPCError) { - if (e.error === 'InvalidSwap') return new InvalidSwapError(e) - if (e.error === 'ConcurrentWrites') return new ConcurrentWritesError(e) } return e } diff --git a/packages/api/src/client/types/app/bsky/unspecced/applyLabels.ts b/packages/api/src/client/types/app/bsky/unspecced/applyLabels.ts new file mode 100644 index 00000000000..c8e72746a42 --- /dev/null +++ b/packages/api/src/client/types/app/bsky/unspecced/applyLabels.ts @@ -0,0 +1,33 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { Headers, XRPCError } from '@atproto/xrpc' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' +import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' + +export interface QueryParams {} + +export interface InputSchema { + labels: ComAtprotoLabelDefs.Label[] + [k: string]: unknown +} + +export interface CallOptions { + headers?: Headers + qp?: QueryParams + encoding: 'application/json' +} + +export interface Response { + success: boolean + headers: Headers +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + } + return e +} diff --git a/packages/api/src/client/types/app/bsky/unspecced/getPopularFeedGenerators.ts b/packages/api/src/client/types/app/bsky/unspecced/getPopularFeedGenerators.ts index e6d15b062f4..19ca5cbc597 100644 --- a/packages/api/src/client/types/app/bsky/unspecced/getPopularFeedGenerators.ts +++ b/packages/api/src/client/types/app/bsky/unspecced/getPopularFeedGenerators.ts @@ -8,11 +8,16 @@ import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' import * as AppBskyFeedDefs from '../feed/defs' -export interface QueryParams {} +export interface QueryParams { + limit?: number + cursor?: string + query?: string +} export type InputSchema = undefined export interface OutputSchema { + cursor?: string feeds: AppBskyFeedDefs.GeneratorView[] [k: string]: unknown } diff --git a/packages/api/src/client/types/app/bsky/unspecced/getTimelineSkeleton.ts b/packages/api/src/client/types/app/bsky/unspecced/getTimelineSkeleton.ts new file mode 100644 index 00000000000..91acf9d5e3d --- /dev/null +++ b/packages/api/src/client/types/app/bsky/unspecced/getTimelineSkeleton.ts @@ -0,0 +1,45 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { Headers, XRPCError } from '@atproto/xrpc' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' +import * as AppBskyFeedDefs from '../feed/defs' + +export interface QueryParams { + limit?: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + feed: AppBskyFeedDefs.SkeletonFeedPost[] + [k: string]: unknown +} + +export interface CallOptions { + headers?: Headers +} + +export interface Response { + success: boolean + headers: Headers + data: OutputSchema +} + +export class UnknownFeedError extends XRPCError { + constructor(src: XRPCError) { + super(src.status, src.error, src.message, src.headers) + } +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + if (e.error === 'UnknownFeed') return new UnknownFeedError(e) + } + return e +} diff --git a/packages/api/src/client/types/com/atproto/admin/defs.ts b/packages/api/src/client/types/com/atproto/admin/defs.ts index 7da21d1178e..f98814ca8e2 100644 --- a/packages/api/src/client/types/com/atproto/admin/defs.ts +++ b/packages/api/src/client/types/com/atproto/admin/defs.ts @@ -13,6 +13,8 @@ import * as ComAtprotoLabelDefs from '../label/defs' export interface ActionView { id: number action: ActionType + /** Indicates how long this action was meant to be in effect before automatically expiring. */ + durationInHours?: number subject: | RepoRef | ComAtprotoRepoStrongRef.Main @@ -43,6 +45,8 @@ export function validateActionView(v: unknown): ValidationResult { export interface ActionViewDetail { id: number action: ActionType + /** Indicates how long this action was meant to be in effect before automatically expiring. */ + durationInHours?: number subject: | RepoView | RepoViewNotFound @@ -75,6 +79,8 @@ export function validateActionViewDetail(v: unknown): ValidationResult { export interface ActionViewCurrent { id: number action: ActionType + /** Indicates how long this action was meant to be in effect before automatically expiring. */ + durationInHours?: number [k: string]: unknown } @@ -189,6 +195,7 @@ export interface RepoView { moderation: Moderation invitedBy?: ComAtprotoServerDefs.InviteCode invitesDisabled?: boolean + inviteNote?: string [k: string]: unknown } @@ -215,6 +222,7 @@ export interface RepoViewDetail { invitedBy?: ComAtprotoServerDefs.InviteCode invites?: ComAtprotoServerDefs.InviteCode[] invitesDisabled?: boolean + inviteNote?: string [k: string]: unknown } diff --git a/packages/api/src/client/types/com/atproto/admin/disableAccountInvites.ts b/packages/api/src/client/types/com/atproto/admin/disableAccountInvites.ts index 3e224c0aa1a..cf61d3026d4 100644 --- a/packages/api/src/client/types/com/atproto/admin/disableAccountInvites.ts +++ b/packages/api/src/client/types/com/atproto/admin/disableAccountInvites.ts @@ -11,6 +11,8 @@ export interface QueryParams {} export interface InputSchema { account: string + /** Additionally add a note describing why the invites were disabled */ + note?: string [k: string]: unknown } diff --git a/packages/api/src/client/types/com/atproto/admin/enableAccountInvites.ts b/packages/api/src/client/types/com/atproto/admin/enableAccountInvites.ts index 3e224c0aa1a..e20c3f14d49 100644 --- a/packages/api/src/client/types/com/atproto/admin/enableAccountInvites.ts +++ b/packages/api/src/client/types/com/atproto/admin/enableAccountInvites.ts @@ -11,6 +11,8 @@ export interface QueryParams {} export interface InputSchema { account: string + /** Additionally add a note describing why the invites were enabled */ + note?: string [k: string]: unknown } diff --git a/packages/api/src/client/types/com/atproto/admin/getModerationReports.ts b/packages/api/src/client/types/com/atproto/admin/getModerationReports.ts index 6897a374e38..cc6c6f00f3c 100644 --- a/packages/api/src/client/types/com/atproto/admin/getModerationReports.ts +++ b/packages/api/src/client/types/com/atproto/admin/getModerationReports.ts @@ -11,6 +11,8 @@ import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { subject?: string ignoreSubjects?: string[] + /** Get all reports that were actioned by a specific moderator */ + actionedBy?: string /** Filter reports made by one or more DIDs */ reporters?: string[] resolved?: boolean diff --git a/packages/api/src/client/types/com/atproto/admin/getRecord.ts b/packages/api/src/client/types/com/atproto/admin/getRecord.ts index 201e1934c49..453e94c39d7 100644 --- a/packages/api/src/client/types/com/atproto/admin/getRecord.ts +++ b/packages/api/src/client/types/com/atproto/admin/getRecord.ts @@ -28,7 +28,7 @@ export interface Response { export class RecordNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/com/atproto/admin/getRepo.ts b/packages/api/src/client/types/com/atproto/admin/getRepo.ts index f880dd86ac0..5391bada281 100644 --- a/packages/api/src/client/types/com/atproto/admin/getRepo.ts +++ b/packages/api/src/client/types/com/atproto/admin/getRepo.ts @@ -27,7 +27,7 @@ export interface Response { export class RepoNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/com/atproto/admin/rebaseRepo.ts b/packages/api/src/client/types/com/atproto/admin/sendEmail.ts similarity index 53% rename from packages/api/src/client/types/com/atproto/admin/rebaseRepo.ts rename to packages/api/src/client/types/com/atproto/admin/sendEmail.ts index fc91da19bf7..d2d8b0fecbf 100644 --- a/packages/api/src/client/types/com/atproto/admin/rebaseRepo.ts +++ b/packages/api/src/client/types/com/atproto/admin/sendEmail.ts @@ -10,10 +10,14 @@ import { CID } from 'multiformats/cid' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ - repo: string - /** Compare and swap with the previous commit by cid. */ - swapCommit?: string + recipientDid: string + content: string + subject?: string + [k: string]: unknown +} + +export interface OutputSchema { + sent: boolean [k: string]: unknown } @@ -26,24 +30,11 @@ export interface CallOptions { export interface Response { success: boolean headers: Headers -} - -export class InvalidSwapError extends XRPCError { - constructor(src: XRPCError) { - super(src.status, src.error, src.message) - } -} - -export class ConcurrentWritesError extends XRPCError { - constructor(src: XRPCError) { - super(src.status, src.error, src.message) - } + data: OutputSchema } export function toKnownErr(e: any) { if (e instanceof XRPCError) { - if (e.error === 'InvalidSwap') return new InvalidSwapError(e) - if (e.error === 'ConcurrentWrites') return new ConcurrentWritesError(e) } return e } diff --git a/packages/api/src/client/types/com/atproto/admin/takeModerationAction.ts b/packages/api/src/client/types/com/atproto/admin/takeModerationAction.ts index c1f0de18433..6e253d6b0ef 100644 --- a/packages/api/src/client/types/com/atproto/admin/takeModerationAction.ts +++ b/packages/api/src/client/types/com/atproto/admin/takeModerationAction.ts @@ -25,6 +25,8 @@ export interface InputSchema { createLabelVals?: string[] negateLabelVals?: string[] reason: string + /** Indicates how long this action was meant to be in effect before automatically expiring. */ + durationInHours?: number createdBy: string [k: string]: unknown } @@ -45,7 +47,7 @@ export interface Response { export class SubjectHasActionError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/com/atproto/label/defs.ts b/packages/api/src/client/types/com/atproto/label/defs.ts index 7f76a03d3c0..13f90cd80c5 100644 --- a/packages/api/src/client/types/com/atproto/label/defs.ts +++ b/packages/api/src/client/types/com/atproto/label/defs.ts @@ -34,3 +34,40 @@ export function isLabel(v: unknown): v is Label { export function validateLabel(v: unknown): ValidationResult { return lexicons.validate('com.atproto.label.defs#label', v) } + +/** Metadata tags on an atproto record, published by the author within the record. */ +export interface SelfLabels { + values: SelfLabel[] + [k: string]: unknown +} + +export function isSelfLabels(v: unknown): v is SelfLabels { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'com.atproto.label.defs#selfLabels' + ) +} + +export function validateSelfLabels(v: unknown): ValidationResult { + return lexicons.validate('com.atproto.label.defs#selfLabels', v) +} + +/** Metadata tag on an atproto record, published by the author within the record. Note -- schemas should use #selfLabels, not #selfLabel. */ +export interface SelfLabel { + /** the short string name of the value or type of this label */ + val: string + [k: string]: unknown +} + +export function isSelfLabel(v: unknown): v is SelfLabel { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'com.atproto.label.defs#selfLabel' + ) +} + +export function validateSelfLabel(v: unknown): ValidationResult { + return lexicons.validate('com.atproto.label.defs#selfLabel', v) +} diff --git a/packages/api/src/client/types/com/atproto/moderation/defs.ts b/packages/api/src/client/types/com/atproto/moderation/defs.ts index c8613faf0fb..b6463993614 100644 --- a/packages/api/src/client/types/com/atproto/moderation/defs.ts +++ b/packages/api/src/client/types/com/atproto/moderation/defs.ts @@ -21,7 +21,7 @@ export const REASONSPAM = 'com.atproto.moderation.defs#reasonSpam' export const REASONVIOLATION = 'com.atproto.moderation.defs#reasonViolation' /** Misleading identity, affiliation, or content */ export const REASONMISLEADING = 'com.atproto.moderation.defs#reasonMisleading' -/** Unwanted or mis-labeled sexual content */ +/** Unwanted or mislabeled sexual content */ export const REASONSEXUAL = 'com.atproto.moderation.defs#reasonSexual' /** Rude, harassing, explicit, or otherwise unwelcoming behavior */ export const REASONRUDE = 'com.atproto.moderation.defs#reasonRude' diff --git a/packages/api/src/client/types/com/atproto/repo/applyWrites.ts b/packages/api/src/client/types/com/atproto/repo/applyWrites.ts index a56b082d331..b25c3716e1b 100644 --- a/packages/api/src/client/types/com/atproto/repo/applyWrites.ts +++ b/packages/api/src/client/types/com/atproto/repo/applyWrites.ts @@ -32,7 +32,7 @@ export interface Response { export class InvalidSwapError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/com/atproto/repo/createRecord.ts b/packages/api/src/client/types/com/atproto/repo/createRecord.ts index 84d1d49a863..27662fc4929 100644 --- a/packages/api/src/client/types/com/atproto/repo/createRecord.ts +++ b/packages/api/src/client/types/com/atproto/repo/createRecord.ts @@ -45,7 +45,7 @@ export interface Response { export class InvalidSwapError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/com/atproto/repo/deleteRecord.ts b/packages/api/src/client/types/com/atproto/repo/deleteRecord.ts index 5363a974da0..4bbe1fd2341 100644 --- a/packages/api/src/client/types/com/atproto/repo/deleteRecord.ts +++ b/packages/api/src/client/types/com/atproto/repo/deleteRecord.ts @@ -36,7 +36,7 @@ export interface Response { export class InvalidSwapError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/com/atproto/repo/putRecord.ts b/packages/api/src/client/types/com/atproto/repo/putRecord.ts index 62cdb7b63ca..7fbf2630b81 100644 --- a/packages/api/src/client/types/com/atproto/repo/putRecord.ts +++ b/packages/api/src/client/types/com/atproto/repo/putRecord.ts @@ -47,7 +47,7 @@ export interface Response { export class InvalidSwapError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/com/atproto/server/createAccount.ts b/packages/api/src/client/types/com/atproto/server/createAccount.ts index 52ee12599c7..3eeaab250b4 100644 --- a/packages/api/src/client/types/com/atproto/server/createAccount.ts +++ b/packages/api/src/client/types/com/atproto/server/createAccount.ts @@ -41,43 +41,43 @@ export interface Response { export class InvalidHandleError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } export class InvalidPasswordError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } export class InvalidInviteCodeError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } export class HandleNotAvailableError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } export class UnsupportedDomainError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } export class UnresolvableDidError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } export class IncompatibleDidDocError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/com/atproto/server/createAppPassword.ts b/packages/api/src/client/types/com/atproto/server/createAppPassword.ts index 2dc001146b0..d6e9ce3ddf5 100644 --- a/packages/api/src/client/types/com/atproto/server/createAppPassword.ts +++ b/packages/api/src/client/types/com/atproto/server/createAppPassword.ts @@ -30,7 +30,7 @@ export interface Response { export class AccountTakedownError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/com/atproto/server/createSession.ts b/packages/api/src/client/types/com/atproto/server/createSession.ts index 4df0341dd20..d86f2aef1d4 100644 --- a/packages/api/src/client/types/com/atproto/server/createSession.ts +++ b/packages/api/src/client/types/com/atproto/server/createSession.ts @@ -39,7 +39,7 @@ export interface Response { export class AccountTakedownError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/com/atproto/server/deleteAccount.ts b/packages/api/src/client/types/com/atproto/server/deleteAccount.ts index 0b664474552..403de18a345 100644 --- a/packages/api/src/client/types/com/atproto/server/deleteAccount.ts +++ b/packages/api/src/client/types/com/atproto/server/deleteAccount.ts @@ -29,13 +29,13 @@ export interface Response { export class ExpiredTokenError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } export class InvalidTokenError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/com/atproto/server/getAccountInviteCodes.ts b/packages/api/src/client/types/com/atproto/server/getAccountInviteCodes.ts index 451d7800f21..d019ed3fa23 100644 --- a/packages/api/src/client/types/com/atproto/server/getAccountInviteCodes.ts +++ b/packages/api/src/client/types/com/atproto/server/getAccountInviteCodes.ts @@ -32,7 +32,7 @@ export interface Response { export class DuplicateCreateError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/com/atproto/server/listAppPasswords.ts b/packages/api/src/client/types/com/atproto/server/listAppPasswords.ts index fbdecbb1918..ee5f1c12c82 100644 --- a/packages/api/src/client/types/com/atproto/server/listAppPasswords.ts +++ b/packages/api/src/client/types/com/atproto/server/listAppPasswords.ts @@ -28,7 +28,7 @@ export interface Response { export class AccountTakedownError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/com/atproto/server/refreshSession.ts b/packages/api/src/client/types/com/atproto/server/refreshSession.ts index ae566ce88ec..5b531b19e9d 100644 --- a/packages/api/src/client/types/com/atproto/server/refreshSession.ts +++ b/packages/api/src/client/types/com/atproto/server/refreshSession.ts @@ -32,7 +32,7 @@ export interface Response { export class AccountTakedownError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/com/atproto/server/resetPassword.ts b/packages/api/src/client/types/com/atproto/server/resetPassword.ts index c7b1fbb93d2..f0a3a68e50b 100644 --- a/packages/api/src/client/types/com/atproto/server/resetPassword.ts +++ b/packages/api/src/client/types/com/atproto/server/resetPassword.ts @@ -28,13 +28,13 @@ export interface Response { export class ExpiredTokenError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } export class InvalidTokenError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/com/atproto/sync/getCheckout.ts b/packages/api/src/client/types/com/atproto/sync/getCheckout.ts index 663918d029b..4210840ec1c 100644 --- a/packages/api/src/client/types/com/atproto/sync/getCheckout.ts +++ b/packages/api/src/client/types/com/atproto/sync/getCheckout.ts @@ -10,8 +10,6 @@ import { CID } from 'multiformats/cid' export interface QueryParams { /** The DID of the repo. */ did: string - /** The commit to get the checkout from. Defaults to current HEAD. */ - commit?: string } export type InputSchema = undefined diff --git a/packages/api/src/client/types/com/atproto/sync/getHead.ts b/packages/api/src/client/types/com/atproto/sync/getHead.ts index 717ef7b5a8c..c71de962b01 100644 --- a/packages/api/src/client/types/com/atproto/sync/getHead.ts +++ b/packages/api/src/client/types/com/atproto/sync/getHead.ts @@ -31,7 +31,7 @@ export interface Response { export class HeadNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message) + super(src.status, src.error, src.message, src.headers) } } diff --git a/packages/api/src/client/types/com/atproto/sync/getLatestCommit.ts b/packages/api/src/client/types/com/atproto/sync/getLatestCommit.ts new file mode 100644 index 00000000000..8d98adeec39 --- /dev/null +++ b/packages/api/src/client/types/com/atproto/sync/getLatestCommit.ts @@ -0,0 +1,44 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { Headers, XRPCError } from '@atproto/xrpc' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' + +export interface QueryParams { + /** The DID of the repo. */ + did: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cid: string + rev: string + [k: string]: unknown +} + +export interface CallOptions { + headers?: Headers +} + +export interface Response { + success: boolean + headers: Headers + data: OutputSchema +} + +export class RepoNotFoundError extends XRPCError { + constructor(src: XRPCError) { + super(src.status, src.error, src.message, src.headers) + } +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + if (e.error === 'RepoNotFound') return new RepoNotFoundError(e) + } + return e +} diff --git a/packages/api/src/client/types/com/atproto/sync/getRepo.ts b/packages/api/src/client/types/com/atproto/sync/getRepo.ts index e01970240d5..0a45536779e 100644 --- a/packages/api/src/client/types/com/atproto/sync/getRepo.ts +++ b/packages/api/src/client/types/com/atproto/sync/getRepo.ts @@ -10,10 +10,8 @@ import { CID } from 'multiformats/cid' export interface QueryParams { /** The DID of the repo. */ did: string - /** The earliest commit in the commit range (not inclusive) */ - earliest?: string - /** The latest commit in the commit range (inclusive) */ - latest?: string + /** The revision of the repo to catch up from. */ + since?: string } export type InputSchema = undefined diff --git a/packages/api/src/client/types/com/atproto/sync/listBlobs.ts b/packages/api/src/client/types/com/atproto/sync/listBlobs.ts index b630d4cf930..72ddd99cb2d 100644 --- a/packages/api/src/client/types/com/atproto/sync/listBlobs.ts +++ b/packages/api/src/client/types/com/atproto/sync/listBlobs.ts @@ -10,15 +10,16 @@ import { CID } from 'multiformats/cid' export interface QueryParams { /** The DID of the repo. */ did: string - /** The most recent commit */ - latest?: string - /** The earliest commit to start from */ - earliest?: string + /** Optional revision of the repo to list blobs since */ + since?: string + limit?: number + cursor?: string } export type InputSchema = undefined export interface OutputSchema { + cursor?: string cids: string[] [k: string]: unknown } diff --git a/packages/api/src/client/types/com/atproto/sync/subscribeRepos.ts b/packages/api/src/client/types/com/atproto/sync/subscribeRepos.ts index 7e41515badb..f54c8d45631 100644 --- a/packages/api/src/client/types/com/atproto/sync/subscribeRepos.ts +++ b/packages/api/src/client/types/com/atproto/sync/subscribeRepos.ts @@ -13,7 +13,11 @@ export interface Commit { tooBig: boolean repo: string commit: CID - prev: CID | null + prev?: CID | null + /** The rev of the emitted commit */ + rev: string + /** The rev of the last emitted commit from this repo */ + since: string | null /** CAR file containing relevant blocks */ blocks: Uint8Array ops: RepoOp[] @@ -111,6 +115,7 @@ export function validateInfo(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#info', v) } +/** A repo operation, ie a write of a single record. For creates and updates, cid is the record's CID as of this operation. For deletes, it's null. */ export interface RepoOp { action: 'create' | 'update' | 'delete' | (string & {}) path: string diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 81297f246b4..958e8930603 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1,4 +1,4 @@ -export { AtUri } from '@atproto/uri' +export { AtUri } from '@atproto/syntax' export { BlobRef, lexToJson, @@ -13,5 +13,9 @@ export * from './agent' export * from './rich-text/rich-text' export * from './rich-text/sanitization' export * from './rich-text/unicode' +export * from './moderation' +export * from './moderation/types' +export { LABELS } from './moderation/const/labels' +export { LABEL_GROUPS } from './moderation/const/label-groups' export { BskyAgent } from './bsky-agent' export { AtpAgent as default } from './agent' diff --git a/packages/api/src/moderation/accumulator.ts b/packages/api/src/moderation/accumulator.ts new file mode 100644 index 00000000000..eebdfd59399 --- /dev/null +++ b/packages/api/src/moderation/accumulator.ts @@ -0,0 +1,200 @@ +import { AppBskyGraphDefs } from '../client/index' +import { + Label, + LabelPreference, + ModerationCause, + ModerationOpts, + ModerationDecision, +} from './types' +import { LABELS } from './const/labels' + +export class ModerationCauseAccumulator { + did = '' + causes: ModerationCause[] = [] + + constructor() {} + + setDid(did: string) { + this.did = did + } + + addBlocking(blocking: string | undefined) { + if (blocking) { + this.causes.push({ + type: 'blocking', + source: { type: 'user' }, + priority: 3, + }) + } + } + + addBlockedBy(blockedBy: boolean | undefined) { + if (blockedBy) { + this.causes.push({ + type: 'blocked-by', + source: { type: 'user' }, + priority: 4, + }) + } + } + + addBlockOther(blockOther: boolean | undefined) { + if (blockOther) { + this.causes.push({ + type: 'block-other', + source: { type: 'user' }, + priority: 4, + }) + } + } + + addLabel(label: Label, opts: ModerationOpts) { + // look up the label definition + const labelDef = LABELS[label.val] + if (!labelDef) { + // ignore labels we don't understand + return + } + + // look up the label preference + const isSelf = label.src === this.did + const labeler = isSelf + ? undefined + : opts.labelers.find((s) => s.labeler.did === label.src) + + /* TODO when 3P labelers are supported + if (!isSelf && !labeler) { + return // skip labelers not configured by the user + }*/ + + // establish the label preference for interpretation + let labelPref: LabelPreference = 'ignore' + if (!labelDef.configurable) { + labelPref = labelDef.preferences[0] + } else if (labelDef.flags.includes('adult') && !opts.adultContentEnabled) { + labelPref = 'hide' + } else if (labeler?.labels[label.val]) { + labelPref = labeler.labels[label.val] + } else if (opts.labels[label.val]) { + labelPref = opts.labels[label.val] + } + + // ignore labels the user has asked to ignore + if (labelPref === 'ignore') { + return + } + + // establish the priority of the label + let priority: 1 | 2 | 5 | 7 | 8 + if (labelDef.flags.includes('no-override')) { + priority = 1 + } else if (labelPref === 'hide') { + priority = 2 + } else if (labelDef.onwarn === 'blur') { + priority = 5 + } else if (labelDef.onwarn === 'blur-media') { + priority = 7 + } else { + priority = 8 + } + + this.causes.push({ + type: 'label', + source: + isSelf || !labeler + ? { type: 'user' } + : { type: 'labeler', labeler: labeler.labeler }, + label, + labelDef, + setting: labelPref, + priority, + }) + } + + addMuted(muted: boolean | undefined) { + if (muted) { + this.causes.push({ + type: 'muted', + source: { type: 'user' }, + priority: 6, + }) + } + } + + addMutedByList(mutedByList: AppBskyGraphDefs.ListViewBasic | undefined) { + if (mutedByList) { + this.causes.push({ + type: 'muted', + source: { type: 'list', list: mutedByList }, + priority: 6, + }) + } + } + + finalizeDecision(opts: ModerationOpts): ModerationDecision { + const mod = new ModerationDecision() + mod.did = this.did + if (!this.causes.length) { + return mod + } + + // sort the causes by priority and then choose the top one + this.causes.sort((a, b) => a.priority - b.priority) + mod.cause = this.causes[0] + mod.additionalCauses = this.causes.slice(1) + + // blocked user + if ( + mod.cause.type === 'blocking' || + mod.cause.type === 'blocked-by' || + mod.cause.type === 'block-other' + ) { + // filter and blur, dont allow override + mod.filter = true + mod.blur = true + mod.noOverride = true + } + // muted user + else if (mod.cause.type === 'muted') { + // filter and blur + mod.filter = true + mod.blur = true + } + // labeled subject + else if (mod.cause.type === 'label') { + // 'hide' setting + if (mod.cause.setting === 'hide') { + // filter + mod.filter = true + } + + // 'hide' and 'warn' setting, apply onwarn + switch (mod.cause.labelDef.onwarn) { + case 'alert': + mod.alert = true + break + case 'blur': + mod.blur = true + break + case 'blur-media': + mod.blurMedia = true + break + case null: + // do nothing + break + } + + // apply noOverride as needed + if (mod.cause.labelDef.flags.includes('no-override')) { + mod.noOverride = true + } else if ( + mod.cause.labelDef.flags.includes('adult') && + !opts.adultContentEnabled + ) { + mod.noOverride = true + } + } + + return mod + } +} diff --git a/packages/api/src/moderation/const/label-groups.ts b/packages/api/src/moderation/const/label-groups.ts new file mode 100644 index 00000000000..108727ef7a6 --- /dev/null +++ b/packages/api/src/moderation/const/label-groups.ts @@ -0,0 +1,144 @@ +/** this doc is generated by ./scripts/code/labels.mjs **/ +import { LabelGroupDefinitionMap } from '../types' +import { LABELS } from './labels' + +export const LABEL_GROUPS: LabelGroupDefinitionMap = { + system: { + id: 'system', + configurable: false, + labels: [LABELS['!hide'], LABELS['!no-promote'], LABELS['!warn']], + strings: { + settings: { + en: { + name: 'System', + description: 'Moderator overrides for special cases.', + }, + }, + }, + }, + legal: { + id: 'legal', + configurable: false, + labels: [LABELS['dmca-violation'], LABELS['doxxing']], + strings: { + settings: { + en: { + name: 'Legal', + description: 'Content removed for legal reasons.', + }, + }, + }, + }, + sexual: { + id: 'sexual', + configurable: true, + labels: [LABELS['porn'], LABELS['sexual'], LABELS['nudity']], + strings: { + settings: { + en: { + name: 'Adult Content', + description: 'Content which is sexual in nature.', + }, + }, + }, + }, + violence: { + id: 'violence', + configurable: true, + labels: [ + LABELS['nsfl'], + LABELS['corpse'], + LABELS['gore'], + LABELS['torture'], + LABELS['self-harm'], + ], + strings: { + settings: { + en: { + name: 'Violence', + description: 'Content which is violent or deeply disturbing.', + }, + }, + }, + }, + intolerance: { + id: 'intolerance', + configurable: true, + labels: [ + LABELS['intolerant-race'], + LABELS['intolerant-gender'], + LABELS['intolerant-sexual-orientation'], + LABELS['intolerant-religion'], + LABELS['intolerant'], + LABELS['icon-intolerant'], + ], + strings: { + settings: { + en: { + name: 'Intolerance', + description: + 'Content or behavior which is hateful or intolerant toward a group of people.', + }, + }, + }, + }, + rude: { + id: 'rude', + configurable: true, + labels: [LABELS['threat']], + strings: { + settings: { + en: { + name: 'Rude', + description: 'Behavior which is rude toward other users.', + }, + }, + }, + }, + curation: { + id: 'curation', + configurable: true, + labels: [LABELS['spoiler']], + strings: { + settings: { + en: { + name: 'Curational', + description: + 'Subjective moderation geared towards curating a more positive environment.', + }, + }, + }, + }, + spam: { + id: 'spam', + configurable: true, + labels: [LABELS['spam']], + strings: { + settings: { + en: { + name: 'Spam', + description: "Content which doesn't add to the conversation.", + }, + }, + }, + }, + misinfo: { + id: 'misinfo', + configurable: true, + labels: [ + LABELS['account-security'], + LABELS['net-abuse'], + LABELS['impersonation'], + LABELS['scam'], + LABELS['misleading'], + ], + strings: { + settings: { + en: { + name: 'Misinformation', + description: 'Content which misleads or defrauds users.', + }, + }, + }, + }, +} diff --git a/packages/api/src/moderation/const/labels.ts b/packages/api/src/moderation/const/labels.ts new file mode 100644 index 00000000000..7e4c91777e7 --- /dev/null +++ b/packages/api/src/moderation/const/labels.ts @@ -0,0 +1,828 @@ +/** this doc is generated by ./scripts/code/labels.mjs **/ +import { LabelDefinitionMap } from '../types' + +export const LABELS: LabelDefinitionMap = { + '!hide': { + id: '!hide', + preferences: ['hide'], + flags: ['no-override'], + onwarn: 'blur', + groupId: 'system', + configurable: false, + strings: { + settings: { + en: { + name: 'Moderator Hide', + description: 'Moderator has chosen to hide the content.', + }, + }, + account: { + en: { + name: 'Content Blocked', + description: 'This account has been hidden by the moderators.', + }, + }, + content: { + en: { + name: 'Content Blocked', + description: 'This content has been hidden by the moderators.', + }, + }, + }, + }, + '!no-promote': { + id: '!no-promote', + preferences: ['hide'], + flags: [], + onwarn: null, + groupId: 'system', + configurable: false, + strings: { + settings: { + en: { + name: 'Moderator Filter', + description: 'Moderator has chosen to filter the content from feeds.', + }, + }, + account: { + en: { + name: 'N/A', + description: 'N/A', + }, + }, + content: { + en: { + name: 'N/A', + description: 'N/A', + }, + }, + }, + }, + '!warn': { + id: '!warn', + preferences: ['warn'], + flags: [], + onwarn: 'blur', + groupId: 'system', + configurable: false, + strings: { + settings: { + en: { + name: 'Moderator Warn', + description: + 'Moderator has chosen to set a general warning on the content.', + }, + }, + account: { + en: { + name: 'Content Warning', + description: + 'This account has received a general warning from moderators.', + }, + }, + content: { + en: { + name: 'Content Warning', + description: + 'This content has received a general warning from moderators.', + }, + }, + }, + }, + 'dmca-violation': { + id: 'dmca-violation', + preferences: ['hide'], + flags: ['no-override'], + onwarn: 'blur', + groupId: 'legal', + configurable: false, + strings: { + settings: { + en: { + name: 'Copyright Violation', + description: 'The content has received a DMCA takedown request.', + }, + }, + account: { + en: { + name: 'Copyright Violation', + description: + 'This account has received a DMCA takedown request. It will be restored if the concerns can be resolved.', + }, + }, + content: { + en: { + name: 'Copyright Violation', + description: + 'This content has received a DMCA takedown request. It will be restored if the concerns can be resolved.', + }, + }, + }, + }, + doxxing: { + id: 'doxxing', + preferences: ['hide'], + flags: ['no-override'], + onwarn: 'blur', + groupId: 'legal', + configurable: false, + strings: { + settings: { + en: { + name: 'Doxxing', + description: + 'Information that reveals private information about someone which has been shared without the consent of the subject.', + }, + }, + account: { + en: { + name: 'Doxxing', + description: + 'This account has been reported to publish private information about someone without their consent. This report is currently under review.', + }, + }, + content: { + en: { + name: 'Doxxing', + description: + 'This content has been reported to include private information about someone without their consent.', + }, + }, + }, + }, + porn: { + id: 'porn', + preferences: ['ignore', 'warn', 'hide'], + flags: ['adult'], + onwarn: 'blur-media', + groupId: 'sexual', + configurable: true, + strings: { + settings: { + en: { + name: 'Pornography', + description: + 'Images of full-frontal nudity (genitalia) in any sexualized context, or explicit sexual activity (meaning contact with genitalia or breasts) even if partially covered. Includes graphic sexual cartoons (often jokes/memes).', + }, + }, + account: { + en: { + name: 'Adult Content', + description: + 'This account contains imagery of full-frontal nudity or explicit sexual activity.', + }, + }, + content: { + en: { + name: 'Adult Content', + description: + 'This content contains imagery of full-frontal nudity or explicit sexual activity.', + }, + }, + }, + }, + sexual: { + id: 'sexual', + preferences: ['ignore', 'warn', 'hide'], + flags: ['adult'], + onwarn: 'blur-media', + groupId: 'sexual', + configurable: true, + strings: { + settings: { + en: { + name: 'Sexually Suggestive', + description: + 'Content that does not meet the level of "pornography", but is still sexual. Some common examples have been selfies and "hornyposting" with underwear on, or partially naked (naked but covered, eg with hands or from side perspective). Sheer/see-through nipples may end up in this category.', + }, + }, + account: { + en: { + name: 'Suggestive Content', + description: + 'This account contains imagery which is sexually suggestive. Common examples include selfies in underwear or in partial undress.', + }, + }, + content: { + en: { + name: 'Suggestive Content', + description: + 'This content contains imagery which is sexually suggestive. Common examples include selfies in underwear or in partial undress.', + }, + }, + }, + }, + nudity: { + id: 'nudity', + preferences: ['ignore', 'warn', 'hide'], + flags: ['adult'], + onwarn: 'blur-media', + groupId: 'sexual', + configurable: true, + strings: { + settings: { + en: { + name: 'Nudity', + description: + 'Nudity which is not sexual, or that is primarily "artistic" in nature. For example: breastfeeding; classic art paintings and sculptures; newspaper images with some nudity; fashion modeling. "Erotic photography" is likely to end up in sexual or porn.', + }, + }, + account: { + en: { + name: 'Adult Content', + description: + 'This account contains imagery which portrays nudity in a non-sexual or artistic setting.', + }, + }, + content: { + en: { + name: 'Adult Content', + description: + 'This content contains imagery which portrays nudity in a non-sexual or artistic setting.', + }, + }, + }, + }, + nsfl: { + id: 'nsfl', + preferences: ['ignore', 'warn', 'hide'], + flags: ['adult'], + onwarn: 'blur-media', + groupId: 'violence', + configurable: true, + strings: { + settings: { + en: { + name: 'NSFL', + description: + '"Not Suitable For Life." This includes graphic images like the infamous "goatse" (don\'t look it up).', + }, + }, + account: { + en: { + name: 'Graphic Imagery (NSFL)', + description: + 'This account contains graphic images which are often referred to as "Not Suitable For Life."', + }, + }, + content: { + en: { + name: 'Graphic Imagery (NSFL)', + description: + 'This content contains graphic images which are often referred to as "Not Suitable For Life."', + }, + }, + }, + }, + corpse: { + id: 'corpse', + preferences: ['ignore', 'warn', 'hide'], + flags: ['adult'], + onwarn: 'blur-media', + groupId: 'violence', + configurable: true, + strings: { + settings: { + en: { + name: 'Corpse', + description: + 'Visual image of a dead human body in any context. Includes war images, hanging, funeral caskets. Does not include all figurative cases (cartoons), but can include realistic figurative images or renderings.', + }, + }, + account: { + en: { + name: 'Graphic Imagery (Corpse)', + description: + 'This account contains images of a dead human body in any context. Includes war images, hanging, funeral caskets.', + }, + }, + content: { + en: { + name: 'Graphic Imagery (Corpse)', + description: + 'This content contains images of a dead human body in any context. Includes war images, hanging, funeral caskets.', + }, + }, + }, + }, + gore: { + id: 'gore', + preferences: ['ignore', 'warn', 'hide'], + flags: ['adult'], + onwarn: 'blur-media', + groupId: 'violence', + configurable: true, + strings: { + settings: { + en: { + name: 'Gore', + description: + 'Intended for shocking images, typically involving blood or visible wounds.', + }, + }, + account: { + en: { + name: 'Graphic Imagery (Gore)', + description: + 'This account contains shocking images involving blood or visible wounds.', + }, + }, + content: { + en: { + name: 'Graphic Imagery (Gore)', + description: + 'This content contains shocking images involving blood or visible wounds.', + }, + }, + }, + }, + torture: { + id: 'torture', + preferences: ['ignore', 'warn', 'hide'], + flags: ['adult'], + onwarn: 'blur', + groupId: 'violence', + configurable: true, + strings: { + settings: { + en: { + name: 'Torture', + description: + 'Depictions of torture of a human or animal (animal cruelty).', + }, + }, + account: { + en: { + name: 'Graphic Imagery (Torture)', + description: + 'This account contains depictions of torture of a human or animal.', + }, + }, + content: { + en: { + name: 'Graphic Imagery (Torture)', + description: + 'This content contains depictions of torture of a human or animal.', + }, + }, + }, + }, + 'self-harm': { + id: 'self-harm', + preferences: ['ignore', 'warn', 'hide'], + flags: ['adult'], + onwarn: 'blur-media', + groupId: 'violence', + configurable: true, + strings: { + settings: { + en: { + name: 'Self-Harm', + description: + 'A visual depiction (photo or figurative) of cutting, suicide, or similar.', + }, + }, + account: { + en: { + name: 'Graphic Imagery (Self-Harm)', + description: + 'This account includes depictions of cutting, suicide, or other forms of self-harm.', + }, + }, + content: { + en: { + name: 'Graphic Imagery (Self-Harm)', + description: + 'This content includes depictions of cutting, suicide, or other forms of self-harm.', + }, + }, + }, + }, + 'intolerant-race': { + id: 'intolerant-race', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'intolerance', + configurable: true, + strings: { + settings: { + en: { + name: 'Racial Intolerance', + description: 'Hateful or intolerant content related to race.', + }, + }, + account: { + en: { + name: 'Intolerance (Racial)', + description: + 'This account includes hateful or intolerant content related to race.', + }, + }, + content: { + en: { + name: 'Intolerance (Racial)', + description: + 'This content includes hateful or intolerant views related to race.', + }, + }, + }, + }, + 'intolerant-gender': { + id: 'intolerant-gender', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'intolerance', + configurable: true, + strings: { + settings: { + en: { + name: 'Gender Intolerance', + description: + 'Hateful or intolerant content related to gender or gender identity.', + }, + }, + account: { + en: { + name: 'Intolerance (Gender)', + description: + 'This account includes hateful or intolerant content related to gender or gender identity.', + }, + }, + content: { + en: { + name: 'Intolerance (Gender)', + description: + 'This content includes hateful or intolerant views related to gender or gender identity.', + }, + }, + }, + }, + 'intolerant-sexual-orientation': { + id: 'intolerant-sexual-orientation', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'intolerance', + configurable: true, + strings: { + settings: { + en: { + name: 'Sexual Orientation Intolerance', + description: + 'Hateful or intolerant content related to sexual preferences.', + }, + }, + account: { + en: { + name: 'Intolerance (Orientation)', + description: + 'This account includes hateful or intolerant content related to sexual preferences.', + }, + }, + content: { + en: { + name: 'Intolerance (Orientation)', + description: + 'This content includes hateful or intolerant views related to sexual preferences.', + }, + }, + }, + }, + 'intolerant-religion': { + id: 'intolerant-religion', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'intolerance', + configurable: true, + strings: { + settings: { + en: { + name: 'Religious Intolerance', + description: + 'Hateful or intolerant content related to religious views or practices.', + }, + }, + account: { + en: { + name: 'Intolerance (Religious)', + description: + 'This account includes hateful or intolerant content related to religious views or practices.', + }, + }, + content: { + en: { + name: 'Intolerance (Religious)', + description: + 'This content includes hateful or intolerant views related to religious views or practices.', + }, + }, + }, + }, + intolerant: { + id: 'intolerant', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'intolerance', + configurable: true, + strings: { + settings: { + en: { + name: 'Intolerance', + description: + 'A catchall for hateful or intolerant content which is not covered elsewhere.', + }, + }, + account: { + en: { + name: 'Intolerance', + description: 'This account includes hateful or intolerant content.', + }, + }, + content: { + en: { + name: 'Intolerance', + description: 'This content includes hateful or intolerant views.', + }, + }, + }, + }, + 'icon-intolerant': { + id: 'icon-intolerant', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur-media', + groupId: 'intolerance', + configurable: true, + strings: { + settings: { + en: { + name: 'Intolerant Iconography', + description: + 'Visual imagery associated with a hate group, such as the KKK or Nazi, in any context (supportive, critical, documentary, etc).', + }, + }, + account: { + en: { + name: 'Intolerant Iconography', + description: + 'This account includes imagery associated with a hate group such as the KKK or Nazis. This warning may apply to content any context, including critical or documentary purposes.', + }, + }, + content: { + en: { + name: 'Intolerant Iconography', + description: + 'This content includes imagery associated with a hate group such as the KKK or Nazis. This warning may apply to content any context, including critical or documentary purposes.', + }, + }, + }, + }, + threat: { + id: 'threat', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'rude', + configurable: true, + strings: { + settings: { + en: { + name: 'Threats', + description: + 'Statements or imagery published with the intent to threaten, intimidate, or harm.', + }, + }, + account: { + en: { + name: 'Threats', + description: + 'The moderators believe this account has published statements or imagery with the intent to threaten, intimidate, or harm others.', + }, + }, + content: { + en: { + name: 'Threats', + description: + 'The moderators believe this content was published with the intent to threaten, intimidate, or harm others.', + }, + }, + }, + }, + spoiler: { + id: 'spoiler', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'curation', + configurable: true, + strings: { + settings: { + en: { + name: 'Spoiler', + description: + 'Discussion about film, TV, etc which gives away plot points.', + }, + }, + account: { + en: { + name: 'Spoiler Warning', + description: + 'This account contains discussion about film, TV, etc which gives away plot points.', + }, + }, + content: { + en: { + name: 'Spoiler Warning', + description: + 'This content contains discussion about film, TV, etc which gives away plot points.', + }, + }, + }, + }, + spam: { + id: 'spam', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'spam', + configurable: true, + strings: { + settings: { + en: { + name: 'Spam', + description: + 'Repeat, low-quality messages which are clearly not designed to add to a conversation or space.', + }, + }, + account: { + en: { + name: 'Spam', + description: + 'This account publishes repeat, low-quality messages which are clearly not designed to add to a conversation or space.', + }, + }, + content: { + en: { + name: 'Spam', + description: + 'This content is a part of repeat, low-quality messages which are clearly not designed to add to a conversation or space.', + }, + }, + }, + }, + 'account-security': { + id: 'account-security', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'misinfo', + configurable: true, + strings: { + settings: { + en: { + name: 'Security Concerns', + description: + 'Content designed to hijack user accounts such as a phishing attack.', + }, + }, + account: { + en: { + name: 'Security Warning', + description: + 'This account has published content designed to hijack user accounts such as a phishing attack.', + }, + }, + content: { + en: { + name: 'Security Warning', + description: + 'This content is designed to hijack user accounts such as a phishing attack.', + }, + }, + }, + }, + 'net-abuse': { + id: 'net-abuse', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'misinfo', + configurable: true, + strings: { + settings: { + en: { + name: 'Network Attacks', + description: + 'Content designed to attack network systems such as denial-of-service attacks.', + }, + }, + account: { + en: { + name: 'Network Attack Warning', + description: + 'This account has published content designed to attack network systems such as denial-of-service attacks.', + }, + }, + content: { + en: { + name: 'Network Attack Warning', + description: + 'This content is designed to attack network systems such as denial-of-service attacks.', + }, + }, + }, + }, + impersonation: { + id: 'impersonation', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'alert', + groupId: 'misinfo', + configurable: true, + strings: { + settings: { + en: { + name: 'Impersonation', + description: 'Accounts which falsely assert some identity.', + }, + }, + account: { + en: { + name: 'Impersonation Warning', + description: + 'The moderators believe this account is lying about their identity.', + }, + }, + content: { + en: { + name: 'Impersonation Warning', + description: + 'The moderators believe this account is lying about their identity.', + }, + }, + }, + }, + scam: { + id: 'scam', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'alert', + groupId: 'misinfo', + configurable: true, + strings: { + settings: { + en: { + name: 'Scam', + description: 'Fraudulent content.', + }, + }, + account: { + en: { + name: 'Scam Warning', + description: + 'The moderators believe this account publishes fraudulent content.', + }, + }, + content: { + en: { + name: 'Scam Warning', + description: 'The moderators believe this is fraudulent content.', + }, + }, + }, + }, + misleading: { + id: 'misleading', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'alert', + groupId: 'misinfo', + configurable: true, + strings: { + settings: { + en: { + name: 'Misleading', + description: 'Accounts which share misleading information.', + }, + }, + account: { + en: { + name: 'Misleading', + description: + 'The moderators believe this account is spreading misleading information.', + }, + }, + content: { + en: { + name: 'Misleading', + description: + 'The moderators believe this account is spreading misleading information.', + }, + }, + }, + }, +} diff --git a/packages/api/src/moderation/index.ts b/packages/api/src/moderation/index.ts new file mode 100644 index 00000000000..a5f62ab33e4 --- /dev/null +++ b/packages/api/src/moderation/index.ts @@ -0,0 +1,346 @@ +import { AppBskyActorDefs } from '../client/index' +import { + ModerationSubjectProfile, + ModerationSubjectPost, + ModerationSubjectFeedGenerator, + ModerationSubjectUserList, + ModerationOpts, + ModerationDecision, + ModerationUI, +} from './types' +import { decideAccount } from './subjects/account' +import { decideProfile } from './subjects/profile' +import { decidePost } from './subjects/post' +import { + decideQuotedPost, + decideQuotedPostAccount, + decideQuotedPostWithMedia, + decideQuotedPostWithMediaAccount, +} from './subjects/quoted-post' +import { decideFeedGenerator } from './subjects/feed-generator' +import { decideUserList } from './subjects/user-list' +import { + takeHighestPriorityDecision, + downgradeDecision, + isModerationDecisionNoop, + isQuotedPost, + isQuotedPostWithMedia, + toModerationUI, +} from './util' + +// profiles +// = + +export interface ProfileModeration { + decisions: { + account: ModerationDecision + profile: ModerationDecision + } + account: ModerationUI + profile: ModerationUI + avatar: ModerationUI +} + +export function moderateProfile( + subject: ModerationSubjectProfile, + opts: ModerationOpts, +): ProfileModeration { + // decide the moderation the account and the profile + const account = decideAccount(subject, opts) + const profile = decideProfile(subject, opts) + + // if the decision is supposed to blur media, + // - have it apply to the view if it's on the account + // - otherwise ignore it + if (account.blurMedia) { + account.blur = true + } + + // dont give profile.filter because that is meaningless + profile.filter = false + + // downgrade based on authorship + if (!isModerationDecisionNoop(account) && account.did === opts.userDid) { + downgradeDecision(account, 'alert') + } + if (!isModerationDecisionNoop(profile) && profile.did === opts.userDid) { + downgradeDecision(profile, 'alert') + } + + // derive avatar blurring from account & profile, but override for mutes because that shouldnt blur + let avatarBlur = false + let avatarNoOverride = false + if ((account.blur || account.blurMedia) && account.cause?.type !== 'muted') { + avatarBlur = true + avatarNoOverride = account.noOverride || profile.noOverride + } else if (profile.blur || profile.blurMedia) { + avatarBlur = true + avatarNoOverride = account.noOverride || profile.noOverride + } + + // dont blur the account for blocking & muting + if ( + account.cause?.type === 'blocking' || + account.cause?.type === 'blocked-by' || + account.cause?.type === 'muted' + ) { + account.blur = false + account.noOverride = false + } + + return { + decisions: { account, profile }, + + // moderate all content based on account + account: + account.filter || account.blur || account.alert + ? toModerationUI(account) + : {}, + + // moderate the profile details based on the profile + profile: + profile.filter || profile.blur || profile.alert + ? toModerationUI(profile) + : {}, + + // blur or alert the avatar based on the account and profile decisions + avatar: { + blur: avatarBlur, + alert: account.alert || profile.alert, + noOverride: avatarNoOverride, + }, + } +} + +// posts +// = + +export interface PostModeration { + decisions: { + post: ModerationDecision + account: ModerationDecision + profile: ModerationDecision + quote?: ModerationDecision + quotedAccount?: ModerationDecision + } + content: ModerationUI + avatar: ModerationUI + embed: ModerationUI +} + +export function moderatePost( + subject: ModerationSubjectPost, + opts: ModerationOpts, +): PostModeration { + // decide the moderation for the post, the post author's account, + // and the post author's profile + const post = decidePost(subject, opts) + const account = decideAccount(subject.author, opts) + const profile = decideProfile(subject.author, opts) + + // decide the moderation for any quoted posts + let quote: ModerationDecision | undefined + let quotedAccount: ModerationDecision | undefined + if (isQuotedPost(subject.embed)) { + quote = decideQuotedPost(subject.embed, opts) + quotedAccount = decideQuotedPostAccount(subject.embed, opts) + } else if (isQuotedPostWithMedia(subject.embed)) { + quote = decideQuotedPostWithMedia(subject.embed, opts) + quotedAccount = decideQuotedPostWithMediaAccount(subject.embed, opts) + } + if (quote?.blurMedia) { + quote.blur = true // treat blurMedia of quote as blur of quote + } + + // downgrade based on authorship + if (!isModerationDecisionNoop(post) && post.did === opts.userDid) { + downgradeDecision(post, 'blur') + } + if (account.cause && account.did === opts.userDid) { + downgradeDecision(account, 'noop') + } + if (profile.cause && profile.did === opts.userDid) { + downgradeDecision(profile, 'noop') + } + if (quote && !isModerationDecisionNoop(quote) && quote.did === opts.userDid) { + downgradeDecision(quote, 'blur') + } + if ( + quotedAccount && + !isModerationDecisionNoop(quotedAccount) && + quotedAccount.did === opts.userDid + ) { + downgradeDecision(quotedAccount, 'noop') + } + + // derive filtering from feeds from the post, post author's account, + // quoted post, and quoted post author's account + const mergedForFeed = takeHighestPriorityDecision( + post, + account, + quote, + quotedAccount, + ) + + // derive view blurring from the post and the post author's account + const mergedForView = takeHighestPriorityDecision(post, account) + + // derive embed blurring from the quoted post and the quoted post author's account + const mergedQuote = takeHighestPriorityDecision(quote, quotedAccount) + + // derive avatar blurring from account & profile, but override for mutes because that shouldnt blur + let blurAvatar = false + if ((account.blur || account.blurMedia) && account.cause?.type !== 'muted') { + blurAvatar = true + } else if ( + (profile.blur || profile.blurMedia) && + profile.cause?.type !== 'muted' + ) { + blurAvatar = true + } + + return { + decisions: { post, account, profile, quote, quotedAccount }, + + // content behaviors are pulled from feed and view derivations above + content: { + cause: !isModerationDecisionNoop(mergedForView) + ? mergedForView.cause + : mergedForFeed.filter + ? mergedForFeed.cause + : undefined, + filter: mergedForFeed.filter, + blur: mergedForView.blur, + alert: mergedForView.alert, + noOverride: mergedForView.noOverride, + }, + + // blur or alert the avatar based on the account and profile decisions + avatar: { + blur: blurAvatar, + alert: account.alert || profile.alert, + noOverride: account.noOverride || profile.noOverride, + }, + + // blur the embed if the quoted post required it, + // or else if the account or post decision was to blur media + embed: !isModerationDecisionNoop(mergedQuote, { ignoreFilter: true }) + ? { + cause: mergedQuote.cause, + blur: mergedQuote.blur, + alert: mergedQuote.alert, + noOverride: mergedQuote.noOverride, + } + : account.blurMedia + ? { + cause: account.cause, + blur: true, + noOverride: account.noOverride, + } + : post.blurMedia + ? { + cause: post.cause, + blur: true, + noOverride: post.noOverride, + } + : {}, + } +} + +// feed generators +// = + +export interface FeedGeneratorModeration { + decisions: { + feedGenerator: ModerationDecision + account: ModerationDecision + profile: ModerationDecision + } + content: ModerationUI + avatar: ModerationUI +} + +export function moderateFeedGenerator( + subject: ModerationSubjectFeedGenerator, + opts: ModerationOpts, +): FeedGeneratorModeration { + // decide the moderation for the generator, the generator creator's account, + // and the generator creator's profile + const feedGenerator = decideFeedGenerator(subject, opts) + const account = decideAccount(subject.creator, opts) + const profile = decideProfile(subject.creator, opts) + + // derive behaviors from feeds from the generator and the generator's account + const merged = takeHighestPriorityDecision(feedGenerator, account) + + return { + decisions: { feedGenerator, account, profile }, + + // content behaviors are pulled from merged decisions + content: { + cause: isModerationDecisionNoop(merged) ? undefined : merged.cause, + filter: merged.filter, + blur: merged.blur, + alert: merged.alert, + noOverride: merged.noOverride, + }, + + // blur or alert the avatar based on the account and profile decisions + avatar: { + blur: account.blurMedia || profile.blurMedia, + alert: account.alert, + noOverride: account.noOverride || profile.noOverride, + }, + } +} + +// user lists +// = + +export interface UserListModeration { + decisions: { + userList: ModerationDecision + account: ModerationDecision + profile: ModerationDecision + } + content: ModerationUI + avatar: ModerationUI +} + +export function moderateUserList( + subject: ModerationSubjectUserList, + opts: ModerationOpts, +): UserListModeration { + // decide the moderation for the list, the list creator's account, + // and the list creator's profile + const userList = decideUserList(subject, opts) + const account = AppBskyActorDefs.isProfileViewBasic(subject.creator) + ? decideAccount(subject.creator, opts) + : ModerationDecision.noop() + const profile = AppBskyActorDefs.isProfileViewBasic(subject.creator) + ? decideProfile(subject.creator, opts) + : ModerationDecision.noop() + + // derive behaviors from feeds from the list and the list's account + const merged = takeHighestPriorityDecision(userList, account) + + return { + decisions: { userList, account, profile }, + + // content behaviors are pulled from merged decisions + content: { + cause: isModerationDecisionNoop(merged) ? undefined : merged.cause, + filter: merged.filter, + blur: merged.blur, + alert: merged.alert, + noOverride: merged.noOverride, + }, + + // blur or alert the avatar based on the account and profile decisions + avatar: { + blur: account.blurMedia || profile.blurMedia, + alert: account.alert, + noOverride: account.noOverride || profile.noOverride, + }, + } +} diff --git a/packages/api/src/moderation/subjects/account.ts b/packages/api/src/moderation/subjects/account.ts new file mode 100644 index 00000000000..5b6d74369e2 --- /dev/null +++ b/packages/api/src/moderation/subjects/account.ts @@ -0,0 +1,40 @@ +import { ModerationCauseAccumulator } from '../accumulator' +import { + Label, + ModerationSubjectProfile, + ModerationOpts, + ModerationDecision, +} from '../types' + +export function decideAccount( + subject: ModerationSubjectProfile, + opts: ModerationOpts, +): ModerationDecision { + const acc = new ModerationCauseAccumulator() + + acc.setDid(subject.did) + if (subject.viewer?.muted) { + if (subject.viewer?.mutedByList) { + acc.addMutedByList(subject.viewer?.mutedByList) + } else { + acc.addMuted(subject.viewer?.muted) + } + } + acc.addBlocking(subject.viewer?.blocking) + acc.addBlockedBy(subject.viewer?.blockedBy) + + for (const label of filterAccountLabels(subject.labels)) { + acc.addLabel(label, opts) + } + + return acc.finalizeDecision(opts) +} + +export function filterAccountLabels(labels?: Label[]): Label[] { + if (!labels) { + return [] + } + return labels.filter( + (label) => !label.uri.endsWith('/app.bsky.actor.profile/self'), + ) +} diff --git a/packages/api/src/moderation/subjects/feed-generator.ts b/packages/api/src/moderation/subjects/feed-generator.ts new file mode 100644 index 00000000000..1b75d502810 --- /dev/null +++ b/packages/api/src/moderation/subjects/feed-generator.ts @@ -0,0 +1,13 @@ +import { + ModerationSubjectFeedGenerator, + ModerationDecision, + ModerationOpts, +} from '../types' + +export function decideFeedGenerator( + subject: ModerationSubjectFeedGenerator, + opts: ModerationOpts, +): ModerationDecision { + // TODO handle labels applied on the feed generator itself + return ModerationDecision.noop() +} diff --git a/packages/api/src/moderation/subjects/post.ts b/packages/api/src/moderation/subjects/post.ts new file mode 100644 index 00000000000..577b5374df1 --- /dev/null +++ b/packages/api/src/moderation/subjects/post.ts @@ -0,0 +1,23 @@ +import { ModerationCauseAccumulator } from '../accumulator' +import { + ModerationSubjectPost, + ModerationOpts, + ModerationDecision, +} from '../types' + +export function decidePost( + subject: ModerationSubjectPost, + opts: ModerationOpts, +): ModerationDecision { + const acc = new ModerationCauseAccumulator() + + acc.setDid(subject.author.did) + + if (subject.labels?.length) { + for (const label of subject.labels) { + acc.addLabel(label, opts) + } + } + + return acc.finalizeDecision(opts) +} diff --git a/packages/api/src/moderation/subjects/profile.ts b/packages/api/src/moderation/subjects/profile.ts new file mode 100644 index 00000000000..0025bf690e5 --- /dev/null +++ b/packages/api/src/moderation/subjects/profile.ts @@ -0,0 +1,31 @@ +import { ModerationCauseAccumulator } from '../accumulator' +import { + Label, + ModerationSubjectProfile, + ModerationOpts, + ModerationDecision, +} from '../types' + +export function decideProfile( + subject: ModerationSubjectProfile, + opts: ModerationOpts, +): ModerationDecision { + const acc = new ModerationCauseAccumulator() + + acc.setDid(subject.did) + + for (const label of filterProfileLabels(subject.labels)) { + acc.addLabel(label, opts) + } + + return acc.finalizeDecision(opts) +} + +export function filterProfileLabels(labels?: Label[]): Label[] { + if (!labels) { + return [] + } + return labels.filter((label) => + label.uri.endsWith('/app.bsky.actor.profile/self'), + ) +} diff --git a/packages/api/src/moderation/subjects/quoted-post.ts b/packages/api/src/moderation/subjects/quoted-post.ts new file mode 100644 index 00000000000..6d0f9eb9d52 --- /dev/null +++ b/packages/api/src/moderation/subjects/quoted-post.ts @@ -0,0 +1,80 @@ +import { AppBskyEmbedRecord, AppBskyEmbedRecordWithMedia } from '../../client' +import { ModerationCauseAccumulator } from '../accumulator' +import { ModerationOpts, ModerationDecision } from '../types' +import { decideAccount } from './account' + +export function decideQuotedPost( + subject: AppBskyEmbedRecord.View, + opts: ModerationOpts, +): ModerationDecision { + const acc = new ModerationCauseAccumulator() + + if (AppBskyEmbedRecord.isViewRecord(subject.record)) { + acc.setDid(subject.record.author.did) + + if (subject.record.labels?.length) { + for (const label of subject.record.labels) { + acc.addLabel(label, opts) + } + } + } else if (AppBskyEmbedRecord.isViewBlocked(subject.record)) { + acc.setDid(subject.record.author.did) + if (subject.record.author.viewer?.blocking) { + acc.addBlocking(subject.record.author.viewer?.blocking) + } else if (subject.record.author.viewer?.blockedBy) { + acc.addBlockedBy(subject.record.author.viewer?.blockedBy) + } else { + acc.addBlockOther(true) + } + } + + return acc.finalizeDecision(opts) +} + +export function decideQuotedPostAccount( + subject: AppBskyEmbedRecord.View, + opts: ModerationOpts, +): ModerationDecision { + if (AppBskyEmbedRecord.isViewRecord(subject.record)) { + return decideAccount(subject.record.author, opts) + } + return ModerationDecision.noop() +} + +export function decideQuotedPostWithMedia( + subject: AppBskyEmbedRecordWithMedia.View, + opts: ModerationOpts, +): ModerationDecision { + const acc = new ModerationCauseAccumulator() + + if (AppBskyEmbedRecord.isViewRecord(subject.record.record)) { + acc.setDid(subject.record.record.author.did) + + if (subject.record.record.labels?.length) { + for (const label of subject.record.record.labels) { + acc.addLabel(label, opts) + } + } + } else if (AppBskyEmbedRecord.isViewBlocked(subject.record.record)) { + acc.setDid(subject.record.record.author.did) + if (subject.record.record.author.viewer?.blocking) { + acc.addBlocking(subject.record.record.author.viewer?.blocking) + } else if (subject.record.record.author.viewer?.blockedBy) { + acc.addBlockedBy(subject.record.record.author.viewer?.blockedBy) + } else { + acc.addBlockOther(true) + } + } + + return acc.finalizeDecision(opts) +} + +export function decideQuotedPostWithMediaAccount( + subject: AppBskyEmbedRecordWithMedia.View, + opts: ModerationOpts, +): ModerationDecision { + if (AppBskyEmbedRecord.isViewRecord(subject.record.record)) { + return decideAccount(subject.record.record.author, opts) + } + return ModerationDecision.noop() +} diff --git a/packages/api/src/moderation/subjects/user-list.ts b/packages/api/src/moderation/subjects/user-list.ts new file mode 100644 index 00000000000..20c48ae523f --- /dev/null +++ b/packages/api/src/moderation/subjects/user-list.ts @@ -0,0 +1,13 @@ +import { + ModerationSubjectUserList, + ModerationOpts, + ModerationDecision, +} from '../types' + +export function decideUserList( + subject: ModerationSubjectUserList, + opts: ModerationOpts, +): ModerationDecision { + // TODO handle labels applied on the list itself + return ModerationDecision.noop() +} diff --git a/packages/api/src/moderation/types.ts b/packages/api/src/moderation/types.ts new file mode 100644 index 00000000000..e3cb6200a00 --- /dev/null +++ b/packages/api/src/moderation/types.ts @@ -0,0 +1,144 @@ +import { + AppBskyActorDefs, + AppBskyFeedDefs, + AppBskyGraphDefs, + ComAtprotoLabelDefs, +} from '../client/index' + +// labels +// = + +export type Label = ComAtprotoLabelDefs.Label + +export type LabelPreference = 'ignore' | 'warn' | 'hide' +export type LabelDefinitionFlag = 'no-override' | 'adult' +export type LabelDefinitionOnWarnBehavior = + | 'blur' + | 'blur-media' + | 'alert' + | null + +export interface LabelDefinitionLocalizedStrings { + name: string + description: string +} + +export type LabelDefinitionLocalizedStringsMap = Record< + string, + LabelDefinitionLocalizedStrings +> + +export interface LabelDefinition { + id: string + groupId: string + configurable: boolean + preferences: LabelPreference[] + flags: LabelDefinitionFlag[] + onwarn: LabelDefinitionOnWarnBehavior + strings: { + settings: LabelDefinitionLocalizedStringsMap + account: LabelDefinitionLocalizedStringsMap + content: LabelDefinitionLocalizedStringsMap + } +} + +export interface LabelGroupDefinition { + id: string + configurable: boolean + labels: LabelDefinition[] + strings: { + settings: LabelDefinitionLocalizedStringsMap + } +} + +export type LabelDefinitionMap = Record +export type LabelGroupDefinitionMap = Record + +// labelers +// = + +interface Labeler { + did: string + displayName: string +} + +export interface LabelerSettings { + labeler: Labeler + labels: Record +} + +// subjects +// = + +export type ModerationSubjectProfile = + | AppBskyActorDefs.ProfileViewBasic + | AppBskyActorDefs.ProfileView + | AppBskyActorDefs.ProfileViewDetailed + +export type ModerationSubjectPost = AppBskyFeedDefs.PostView + +export type ModerationSubjectFeedGenerator = AppBskyFeedDefs.GeneratorView + +export type ModerationSubjectUserList = + | AppBskyGraphDefs.ListViewBasic + | AppBskyGraphDefs.ListView + +export type ModerationSubject = + | ModerationSubjectProfile + | ModerationSubjectPost + | ModerationSubjectFeedGenerator + | ModerationSubjectUserList + +// behaviors +// = + +export type ModerationCauseSource = + | { type: 'user' } + | { type: 'list'; list: AppBskyGraphDefs.ListViewBasic } + | { type: 'labeler'; labeler: Labeler } + +export type ModerationCause = + | { type: 'blocking'; source: ModerationCauseSource; priority: 3 } + | { type: 'blocked-by'; source: ModerationCauseSource; priority: 4 } + | { type: 'block-other'; source: ModerationCauseSource; priority: 4 } + | { + type: 'label' + source: ModerationCauseSource + label: Label + labelDef: LabelDefinition + setting: LabelPreference + priority: 1 | 2 | 5 | 7 | 8 + } + | { type: 'muted'; source: ModerationCauseSource; priority: 6 } + +export interface ModerationOpts { + userDid: string + adultContentEnabled: boolean + labels: Record + labelers: LabelerSettings[] +} + +export class ModerationDecision { + static noop() { + return new ModerationDecision() + } + + constructor( + public cause: ModerationCause | undefined = undefined, + public alert: boolean = false, + public blur: boolean = false, + public blurMedia: boolean = false, + public filter: boolean = false, + public noOverride: boolean = false, + public additionalCauses: ModerationCause[] = [], + public did: string = '', + ) {} +} + +export interface ModerationUI { + filter?: boolean + blur?: boolean + alert?: boolean + cause?: ModerationCause + noOverride?: boolean +} diff --git a/packages/api/src/moderation/util.ts b/packages/api/src/moderation/util.ts new file mode 100644 index 00000000000..7b42f4dfffe --- /dev/null +++ b/packages/api/src/moderation/util.ts @@ -0,0 +1,90 @@ +import { + AppBskyEmbedRecord, + AppBskyEmbedRecordWithMedia, + AppBskyFeedPost, +} from '../client' +import { ModerationDecision, ModerationUI } from './types' + +export function takeHighestPriorityDecision( + ...decisions: (ModerationDecision | undefined)[] +): ModerationDecision { + // remove undefined decisions + const filtered = decisions.filter((d) => !!d) as ModerationDecision[] + if (filtered.length === 0) { + return ModerationDecision.noop() + } + + // sort by highest priority + filtered.sort((a, b) => { + if (a.cause && b.cause) { + return a.cause.priority - b.cause.priority + } + if (a.cause) { + return -1 + } + if (b.cause) { + return 1 + } + return 0 + }) + + // use the top priority + return filtered[0] +} + +export function downgradeDecision( + decision: ModerationDecision, + to: 'blur' | 'alert' | 'noop', +) { + decision.filter = false + decision.noOverride = false + if (to === 'noop') { + decision.blur = false + decision.blurMedia = false + decision.alert = false + delete decision.cause + } else if (to === 'alert') { + decision.blur = false + decision.blurMedia = false + decision.alert = true + } +} + +export function isModerationDecisionNoop( + decision: ModerationDecision | undefined, + { ignoreFilter }: { ignoreFilter: boolean } = { ignoreFilter: false }, +): boolean { + if (!decision) { + return true + } + if (decision.alert) { + return false + } + if (decision.blur) { + return false + } + if (decision.filter && !ignoreFilter) { + return false + } + return true +} + +export function isQuotedPost(embed: unknown): embed is AppBskyEmbedRecord.View { + return Boolean(embed && AppBskyEmbedRecord.isView(embed)) +} + +export function isQuotedPostWithMedia( + embed: unknown, +): embed is AppBskyEmbedRecordWithMedia.View { + return Boolean(embed && AppBskyEmbedRecordWithMedia.isView(embed)) +} + +export function toModerationUI(decision: ModerationDecision): ModerationUI { + return { + cause: decision.cause, + filter: decision.filter, + blur: decision.blur, + alert: decision.alert, + noOverride: decision.noOverride, + } +} diff --git a/packages/api/src/types.ts b/packages/api/src/types.ts index 38ad34388d0..0310d6743b8 100644 --- a/packages/api/src/types.ts +++ b/packages/api/src/types.ts @@ -1,3 +1,5 @@ +import { LabelPreference } from './moderation/types' + /** * Used by the PersistSessionHandler to indicate what change occurred */ @@ -70,3 +72,22 @@ export type AtpAgentFetchHandler = ( export interface AtpAgentGlobalOpts { fetch: AtpAgentFetchHandler } + +/** + * Content-label preference + */ +export type BskyLabelPreference = LabelPreference | 'show' +// TEMP we need to permanently convert 'show' to 'ignore', for now we manually convert -prf + +/** + * Bluesky preferences object + */ +export interface BskyPreferences { + feeds: { + saved?: string[] + pinned?: string[] + } + adultContentEnabled: boolean + contentLabels: Record + birthDate: Date | undefined +} diff --git a/packages/api/tests/_util.ts b/packages/api/tests/_util.ts deleted file mode 100644 index e7848d50ea4..00000000000 --- a/packages/api/tests/_util.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { AtpAgentFetchHandlerResponse } from '..' - -export async function fetchHandler( - httpUri: string, - httpMethod: string, - httpHeaders: Record, - httpReqBody: unknown, -): Promise { - // The duplex field is now required for streaming bodies, but not yet reflected - // anywhere in docs or types. See whatwg/fetch#1438, nodejs/node#46221. - const reqInit: RequestInit & { duplex: string } = { - method: httpMethod, - headers: httpHeaders, - body: httpReqBody - ? new TextEncoder().encode(JSON.stringify(httpReqBody)) - : undefined, - duplex: 'half', - } - const res = await fetch(httpUri, reqInit) - const resBody = await res.arrayBuffer() - return { - status: res.status, - headers: Object.fromEntries(res.headers.entries()), - body: resBody ? JSON.parse(new TextDecoder().decode(resBody)) : undefined, - } -} diff --git a/packages/api/tests/bsky-agent.test.ts b/packages/api/tests/bsky-agent.test.ts index c65abeb6013..96082e92f4c 100644 --- a/packages/api/tests/bsky-agent.test.ts +++ b/packages/api/tests/bsky-agent.test.ts @@ -214,4 +214,335 @@ describe('agent', () => { await expect(agent.deleteFollow('foo')).rejects.toThrow('Not logged in') }) }) + + describe('preferences methods', () => { + it('gets and sets preferences correctly', async () => { + const agent = new BskyAgent({ service: server.url }) + + await agent.createAccount({ + handle: 'user5.test', + email: 'user5@test.com', + password: 'password', + }) + + await expect(agent.getPreferences()).resolves.toStrictEqual({ + feeds: { pinned: undefined, saved: undefined }, + adultContentEnabled: false, + contentLabels: {}, + birthDate: undefined, + }) + + await agent.setAdultContentEnabled(true) + await expect(agent.getPreferences()).resolves.toStrictEqual({ + feeds: { pinned: undefined, saved: undefined }, + adultContentEnabled: true, + contentLabels: {}, + birthDate: undefined, + }) + + await agent.setAdultContentEnabled(false) + await expect(agent.getPreferences()).resolves.toStrictEqual({ + feeds: { pinned: undefined, saved: undefined }, + adultContentEnabled: false, + contentLabels: {}, + birthDate: undefined, + }) + + await agent.setContentLabelPref('impersonation', 'warn') + await expect(agent.getPreferences()).resolves.toStrictEqual({ + feeds: { pinned: undefined, saved: undefined }, + adultContentEnabled: false, + contentLabels: { + impersonation: 'warn', + }, + birthDate: undefined, + }) + + await agent.setContentLabelPref('spam', 'show') // will convert to 'ignore' + await agent.setContentLabelPref('impersonation', 'hide') + await expect(agent.getPreferences()).resolves.toStrictEqual({ + feeds: { pinned: undefined, saved: undefined }, + adultContentEnabled: false, + contentLabels: { + impersonation: 'hide', + spam: 'ignore', + }, + birthDate: undefined, + }) + + await agent.addSavedFeed('at://bob.com/app.bsky.feed.generator/fake') + await expect(agent.getPreferences()).resolves.toStrictEqual({ + feeds: { + pinned: [], + saved: ['at://bob.com/app.bsky.feed.generator/fake'], + }, + adultContentEnabled: false, + contentLabels: { + impersonation: 'hide', + spam: 'ignore', + }, + birthDate: undefined, + }) + + await agent.addPinnedFeed('at://bob.com/app.bsky.feed.generator/fake') + await expect(agent.getPreferences()).resolves.toStrictEqual({ + feeds: { + pinned: ['at://bob.com/app.bsky.feed.generator/fake'], + saved: ['at://bob.com/app.bsky.feed.generator/fake'], + }, + adultContentEnabled: false, + contentLabels: { + impersonation: 'hide', + spam: 'ignore', + }, + birthDate: undefined, + }) + + await agent.removePinnedFeed('at://bob.com/app.bsky.feed.generator/fake') + await expect(agent.getPreferences()).resolves.toStrictEqual({ + feeds: { + pinned: [], + saved: ['at://bob.com/app.bsky.feed.generator/fake'], + }, + adultContentEnabled: false, + contentLabels: { + impersonation: 'hide', + spam: 'ignore', + }, + birthDate: undefined, + }) + + await agent.removeSavedFeed('at://bob.com/app.bsky.feed.generator/fake') + await expect(agent.getPreferences()).resolves.toStrictEqual({ + feeds: { + pinned: [], + saved: [], + }, + adultContentEnabled: false, + contentLabels: { + impersonation: 'hide', + spam: 'ignore', + }, + birthDate: undefined, + }) + + await agent.addPinnedFeed('at://bob.com/app.bsky.feed.generator/fake') + await expect(agent.getPreferences()).resolves.toStrictEqual({ + feeds: { + pinned: ['at://bob.com/app.bsky.feed.generator/fake'], + saved: ['at://bob.com/app.bsky.feed.generator/fake'], + }, + adultContentEnabled: false, + contentLabels: { + impersonation: 'hide', + spam: 'ignore', + }, + birthDate: undefined, + }) + + await agent.addPinnedFeed('at://bob.com/app.bsky.feed.generator/fake2') + await expect(agent.getPreferences()).resolves.toStrictEqual({ + feeds: { + pinned: [ + 'at://bob.com/app.bsky.feed.generator/fake', + 'at://bob.com/app.bsky.feed.generator/fake2', + ], + saved: [ + 'at://bob.com/app.bsky.feed.generator/fake', + 'at://bob.com/app.bsky.feed.generator/fake2', + ], + }, + adultContentEnabled: false, + contentLabels: { + impersonation: 'hide', + spam: 'ignore', + }, + birthDate: undefined, + }) + + await agent.removeSavedFeed('at://bob.com/app.bsky.feed.generator/fake') + await expect(agent.getPreferences()).resolves.toStrictEqual({ + feeds: { + pinned: ['at://bob.com/app.bsky.feed.generator/fake2'], + saved: ['at://bob.com/app.bsky.feed.generator/fake2'], + }, + adultContentEnabled: false, + contentLabels: { + impersonation: 'hide', + spam: 'ignore', + }, + birthDate: undefined, + }) + + await agent.setPersonalDetails({ birthDate: '2023-09-11T18:05:42.556Z' }) + await expect(agent.getPreferences()).resolves.toStrictEqual({ + feeds: { + pinned: ['at://bob.com/app.bsky.feed.generator/fake2'], + saved: ['at://bob.com/app.bsky.feed.generator/fake2'], + }, + adultContentEnabled: false, + contentLabels: { + impersonation: 'hide', + spam: 'ignore', + }, + birthDate: new Date('2023-09-11T18:05:42.556Z'), + }) + }) + + it('resolves duplicates correctly', async () => { + const agent = new BskyAgent({ service: server.url }) + + await agent.createAccount({ + handle: 'user6.test', + email: 'user6@test.com', + password: 'password', + }) + + await agent.app.bsky.actor.putPreferences({ + preferences: [ + { + $type: 'app.bsky.actor.defs#contentLabelPref', + label: 'nsfw', + visibility: 'show', + }, + { + $type: 'app.bsky.actor.defs#contentLabelPref', + label: 'nsfw', + visibility: 'hide', + }, + { + $type: 'app.bsky.actor.defs#contentLabelPref', + label: 'nsfw', + visibility: 'show', + }, + { + $type: 'app.bsky.actor.defs#contentLabelPref', + label: 'nsfw', + visibility: 'warn', + }, + { + $type: 'app.bsky.actor.defs#adultContentPref', + enabled: true, + }, + { + $type: 'app.bsky.actor.defs#adultContentPref', + enabled: false, + }, + { + $type: 'app.bsky.actor.defs#adultContentPref', + enabled: true, + }, + { + $type: 'app.bsky.actor.defs#savedFeedsPref', + pinned: [ + 'at://bob.com/app.bsky.feed.generator/fake', + 'at://bob.com/app.bsky.feed.generator/fake2', + ], + saved: [ + 'at://bob.com/app.bsky.feed.generator/fake', + 'at://bob.com/app.bsky.feed.generator/fake2', + ], + }, + { + $type: 'app.bsky.actor.defs#savedFeedsPref', + pinned: [], + saved: [], + }, + { + $type: 'app.bsky.actor.defs#personalDetailsPref', + birthDate: '2023-09-11T18:05:42.556Z', + }, + { + $type: 'app.bsky.actor.defs#personalDetailsPref', + birthDate: '2021-09-11T18:05:42.556Z', + }, + ], + }) + await expect(agent.getPreferences()).resolves.toStrictEqual({ + feeds: { + pinned: [], + saved: [], + }, + adultContentEnabled: true, + contentLabels: { + nsfw: 'warn', + }, + birthDate: new Date('2021-09-11T18:05:42.556Z'), + }) + + await agent.setAdultContentEnabled(false) + await expect(agent.getPreferences()).resolves.toStrictEqual({ + feeds: { + pinned: [], + saved: [], + }, + adultContentEnabled: false, + contentLabels: { + nsfw: 'warn', + }, + birthDate: new Date('2021-09-11T18:05:42.556Z'), + }) + + await agent.setContentLabelPref('nsfw', 'hide') + await expect(agent.getPreferences()).resolves.toStrictEqual({ + feeds: { + pinned: [], + saved: [], + }, + adultContentEnabled: false, + contentLabels: { + nsfw: 'hide', + }, + birthDate: new Date('2021-09-11T18:05:42.556Z'), + }) + + await agent.addPinnedFeed('at://bob.com/app.bsky.feed.generator/fake') + await expect(agent.getPreferences()).resolves.toStrictEqual({ + feeds: { + pinned: ['at://bob.com/app.bsky.feed.generator/fake'], + saved: ['at://bob.com/app.bsky.feed.generator/fake'], + }, + adultContentEnabled: false, + contentLabels: { + nsfw: 'hide', + }, + birthDate: new Date('2021-09-11T18:05:42.556Z'), + }) + + await agent.setPersonalDetails({ birthDate: '2023-09-11T18:05:42.556Z' }) + await expect(agent.getPreferences()).resolves.toStrictEqual({ + feeds: { + pinned: ['at://bob.com/app.bsky.feed.generator/fake'], + saved: ['at://bob.com/app.bsky.feed.generator/fake'], + }, + adultContentEnabled: false, + contentLabels: { + nsfw: 'hide', + }, + birthDate: new Date('2023-09-11T18:05:42.556Z'), + }) + + const res = await agent.app.bsky.actor.getPreferences() + await expect(res.data.preferences).toStrictEqual([ + { + $type: 'app.bsky.actor.defs#adultContentPref', + enabled: false, + }, + { + $type: 'app.bsky.actor.defs#contentLabelPref', + label: 'nsfw', + visibility: 'hide', + }, + { + $type: 'app.bsky.actor.defs#savedFeedsPref', + pinned: ['at://bob.com/app.bsky.feed.generator/fake'], + saved: ['at://bob.com/app.bsky.feed.generator/fake'], + }, + { + $type: 'app.bsky.actor.defs#personalDetailsPref', + birthDate: '2023-09-11T18:05:42.556Z', + }, + ]) + }) + }) }) diff --git a/packages/api/tests/moderation.test.ts b/packages/api/tests/moderation.test.ts new file mode 100644 index 00000000000..dba625b7cfe --- /dev/null +++ b/packages/api/tests/moderation.test.ts @@ -0,0 +1,334 @@ +import { moderateProfile, moderatePost } from '../src' +import { mock } from './util' +import './util/moderation-behavior' + +describe('Moderation', () => { + it('Applies self-labels on profiles according to the global preferences', () => { + // porn (hide) + const res1 = moderateProfile( + mock.profileViewBasic({ + handle: 'bob.test', + displayName: 'Bob', + labels: [ + { + src: 'did:web:bob.test', + uri: 'at://did:web:bob.test/app.bsky.actor.profile/self', + val: 'porn', + cts: new Date().toISOString(), + }, + ], + }), + { + userDid: 'did:web:alice.test', + adultContentEnabled: true, + labels: { + porn: 'hide', + }, + labelers: [], + }, + ) + expect(res1.account).toBeModerationResult( + {}, + 'post content', + JSON.stringify(res1, null, 2), + ) + expect(res1.profile).toBeModerationResult( + {}, + 'post content', + JSON.stringify(res1, null, 2), + ) + expect(res1.avatar).toBeModerationResult( + { blur: true }, + 'post avatar', + JSON.stringify(res1, null, 2), + true, + ) + + // porn (ignore) + const res2 = moderateProfile( + mock.profileViewBasic({ + handle: 'bob.test', + displayName: 'Bob', + labels: [ + { + src: 'did:web:bob.test', + uri: 'at://did:web:bob.test/app.bsky.actor.profile/self', + val: 'porn', + cts: new Date().toISOString(), + }, + ], + }), + { + userDid: 'did:web:alice.test', + adultContentEnabled: true, + labels: { + porn: 'ignore', + }, + labelers: [], + }, + ) + expect(res2.account).toBeModerationResult( + {}, + 'post content', + JSON.stringify(res2, null, 2), + ) + expect(res2.profile).toBeModerationResult( + {}, + 'post content', + JSON.stringify(res2, null, 2), + ) + expect(res2.avatar).toBeModerationResult( + {}, + 'post avatar', + JSON.stringify(res2, null, 2), + true, + ) + }) + + it('Applies self-labels on posts according to the global preferences', () => { + // porn (hide) + const res1 = moderatePost( + mock.postView({ + record: { + text: 'Hello', + createdAt: new Date().toISOString(), + }, + author: mock.profileViewBasic({ + handle: 'bob.test', + displayName: 'Bob', + }), + labels: [ + { + src: 'did:web:bob.test', + uri: 'at://did:web:bob.test/app.bsky.actor.profile/self', + val: 'porn', + cts: new Date().toISOString(), + }, + ], + }), + { + userDid: 'did:web:alice.test', + adultContentEnabled: true, + labels: { + porn: 'hide', + }, + labelers: [], + }, + ) + expect(res1.content).toBeModerationResult( + { cause: 'label:porn', filter: true }, + 'post content', + JSON.stringify(res1, null, 2), + ) + expect(res1.embed).toBeModerationResult( + { cause: 'label:porn', blur: true }, + 'post content', + JSON.stringify(res1, null, 2), + ) + expect(res1.avatar).toBeModerationResult( + {}, + 'post avatar', + JSON.stringify(res1, null, 2), + true, + ) + + // porn (ignore) + const res2 = moderatePost( + mock.postView({ + record: { + text: 'Hello', + createdAt: new Date().toISOString(), + }, + author: mock.profileViewBasic({ + handle: 'bob.test', + displayName: 'Bob', + }), + labels: [ + { + src: 'did:web:bob.test', + uri: 'at://did:web:bob.test/app.bsky.actor.profile/self', + val: 'porn', + cts: new Date().toISOString(), + }, + ], + }), + { + userDid: 'did:web:alice.test', + adultContentEnabled: true, + labels: { + porn: 'ignore', + }, + labelers: [], + }, + ) + expect(res2.content).toBeModerationResult( + {}, + 'post content', + JSON.stringify(res2, null, 2), + ) + expect(res2.embed).toBeModerationResult( + {}, + 'post content', + JSON.stringify(res2, null, 2), + ) + expect(res2.avatar).toBeModerationResult( + {}, + 'post avatar', + JSON.stringify(res2, null, 2), + true, + ) + }) + + it('Applies labeler labels according to the per-labeler then global preferences', () => { + // porn (ignore for labeler, hide for global) + const res1 = moderateProfile( + mock.profileViewBasic({ + handle: 'bob.test', + displayName: 'Bob', + labels: [ + { + src: 'did:web:labeler.test', + uri: 'at://did:web:bob.test/app.bsky.actor.profile/self', + val: 'porn', + cts: new Date().toISOString(), + }, + ], + }), + { + userDid: 'did:web:alice.test', + adultContentEnabled: true, + labels: { + porn: 'hide', + }, + labelers: [ + { + labeler: { + did: 'did:web:labeler.test', + displayName: 'Labeler', + }, + labels: { + porn: 'ignore', + }, + }, + ], + }, + ) + expect(res1.avatar).toBeModerationResult( + {}, + 'post avatar', + JSON.stringify(res1, null, 2), + true, + ) + + // porn (hide for labeler, ignore for global) + const res2 = moderateProfile( + mock.profileViewBasic({ + handle: 'bob.test', + displayName: 'Bob', + labels: [ + { + src: 'did:web:labeler.test', + uri: 'at://did:web:bob.test/app.bsky.actor.profile/self', + val: 'porn', + cts: new Date().toISOString(), + }, + ], + }), + { + userDid: 'did:web:alice.test', + adultContentEnabled: true, + labels: { + porn: 'ignore', + }, + labelers: [ + { + labeler: { + did: 'did:web:labeler.test', + displayName: 'Labeler', + }, + labels: { + porn: 'hide', + }, + }, + ], + }, + ) + expect(res2.avatar).toBeModerationResult( + { blur: true }, + 'post avatar', + JSON.stringify(res2, null, 2), + true, + ) + + // porn (unspecified for labeler, hide for global) + const res3 = moderateProfile( + mock.profileViewBasic({ + handle: 'bob.test', + displayName: 'Bob', + labels: [ + { + src: 'did:web:labeler.test', + uri: 'at://did:web:bob.test/app.bsky.actor.profile/self', + val: 'porn', + cts: new Date().toISOString(), + }, + ], + }), + { + userDid: 'did:web:alice.test', + adultContentEnabled: true, + labels: { + porn: 'hide', + }, + labelers: [ + { + labeler: { + did: 'did:web:labeler.test', + displayName: 'Labeler', + }, + labels: {}, + }, + ], + }, + ) + expect(res3.avatar).toBeModerationResult( + { blur: true }, + 'post avatar', + JSON.stringify(res3, null, 2), + true, + ) + }) + + /* + TODO enable when 3P labeler support is addded + it('Ignores labels from unknown labelers', () => { + const res1 = moderateProfile( + mock.profileViewBasic({ + handle: 'bob.test', + displayName: 'Bob', + labels: [ + { + src: 'did:web:rando.test', + uri: 'at://did:web:bob.test/app.bsky.actor.profile/self', + val: 'porn', + cts: new Date().toISOString(), + }, + ], + }), + { + userDid: 'did:web:alice.test', + adultContentEnabled: true, + labels: { + porn: 'hide', + }, + labelers: [], + }, + ) + expect(res1.avatar).toBeModerationResult( + {}, + 'post avatar', + JSON.stringify(res1, null, 2), + true, + ) + })*/ +}) diff --git a/packages/api/tests/post-moderation.test.ts b/packages/api/tests/post-moderation.test.ts new file mode 100644 index 00000000000..3d62a720507 --- /dev/null +++ b/packages/api/tests/post-moderation.test.ts @@ -0,0 +1,46 @@ +import { moderatePost } from '../src' +import type { + ModerationBehaviors, + ModerationBehaviorScenario, +} from '../definitions/moderation-behaviors' +import { ModerationBehaviorSuiteRunner } from './util/moderation-behavior' +import { readFileSync } from 'fs' +import { join } from 'path' + +const suite: ModerationBehaviors = JSON.parse( + readFileSync( + join(__dirname, '..', 'definitions', 'post-moderation-behaviors.json'), + 'utf8', + ), +) + +const suiteRunner = new ModerationBehaviorSuiteRunner(suite) + +describe('Post moderation behaviors', () => { + const scenarios = Array.from(Object.entries(suite.scenarios)) + it.each(scenarios)( + '%s', + (_name: string, scenario: ModerationBehaviorScenario) => { + const res = moderatePost( + suiteRunner.postScenario(scenario), + suiteRunner.moderationOpts(scenario), + ) + expect(res.content).toBeModerationResult( + scenario.behaviors.content, + 'post content', + JSON.stringify(res, null, 2), + ) + expect(res.avatar).toBeModerationResult( + scenario.behaviors.avatar, + 'post avatar', + JSON.stringify(res, null, 2), + true, + ) + expect(res.embed).toBeModerationResult( + scenario.behaviors.embed, + 'post embed', + JSON.stringify(res, null, 2), + ) + }, + ) +}) diff --git a/packages/api/tests/profile-moderation.test.ts b/packages/api/tests/profile-moderation.test.ts new file mode 100644 index 00000000000..bca63857c30 --- /dev/null +++ b/packages/api/tests/profile-moderation.test.ts @@ -0,0 +1,46 @@ +import { moderateProfile } from '../src' +import type { + ModerationBehaviors, + ModerationBehaviorScenario, +} from '../definitions/moderation-behaviors' +import { ModerationBehaviorSuiteRunner } from './util/moderation-behavior' +import { readFileSync } from 'fs' +import { join } from 'path' + +const suite: ModerationBehaviors = JSON.parse( + readFileSync( + join(__dirname, '..', 'definitions', 'profile-moderation-behaviors.json'), + 'utf8', + ), +) + +const suiteRunner = new ModerationBehaviorSuiteRunner(suite) + +describe('Post moderation behaviors', () => { + const scenarios = Array.from(Object.entries(suite.scenarios)) + it.each(scenarios)( + '%s', + (_name: string, scenario: ModerationBehaviorScenario) => { + const res = moderateProfile( + suiteRunner.profileScenario(scenario), + suiteRunner.moderationOpts(scenario), + ) + expect(res.account).toBeModerationResult( + scenario.behaviors.account, + 'account', + JSON.stringify(res, null, 2), + ) + expect(res.profile).toBeModerationResult( + scenario.behaviors.profile, + 'profile content', + JSON.stringify(res, null, 2), + ) + expect(res.avatar).toBeModerationResult( + scenario.behaviors.avatar, + 'profile avatar', + JSON.stringify(res, null, 2), + true, + ) + }, + ) +}) diff --git a/packages/api/tests/util/index.ts b/packages/api/tests/util/index.ts new file mode 100644 index 00000000000..d9cc5e90780 --- /dev/null +++ b/packages/api/tests/util/index.ts @@ -0,0 +1,176 @@ +import { + AtpAgentFetchHandlerResponse, + ComAtprotoLabelDefs, + AppBskyFeedDefs, + AppBskyActorDefs, + AppBskyFeedPost, + AppBskyEmbedRecord, + AppBskyGraphDefs, +} from '../../src' + +export async function fetchHandler( + httpUri: string, + httpMethod: string, + httpHeaders: Record, + httpReqBody: unknown, +): Promise { + // The duplex field is now required for streaming bodies, but not yet reflected + // anywhere in docs or types. See whatwg/fetch#1438, nodejs/node#46221. + const reqInit: RequestInit & { duplex: string } = { + method: httpMethod, + headers: httpHeaders, + body: httpReqBody + ? new TextEncoder().encode(JSON.stringify(httpReqBody)) + : undefined, + duplex: 'half', + } + const res = await fetch(httpUri, reqInit) + const resBody = await res.arrayBuffer() + return { + status: res.status, + headers: Object.fromEntries(res.headers.entries()), + body: resBody ? JSON.parse(new TextDecoder().decode(resBody)) : undefined, + } +} + +export const mock = { + post({ + text, + reply, + embed, + }: { + text: string + reply?: AppBskyFeedPost.ReplyRef + embed?: AppBskyFeedPost.Record['embed'] + }): AppBskyFeedPost.Record { + return { + $type: 'app.bsky.feed.post', + text, + reply, + embed, + langs: ['en'], + createdAt: new Date().toISOString(), + } + }, + + postView({ + record, + author, + embed, + replyCount, + repostCount, + likeCount, + viewer, + labels, + }: { + record: AppBskyFeedPost.Record + author: AppBskyActorDefs.ProfileViewBasic + embed?: AppBskyFeedDefs.PostView['embed'] + replyCount?: number + repostCount?: number + likeCount?: number + viewer?: AppBskyFeedDefs.ViewerState + labels?: ComAtprotoLabelDefs.Label[] + }): AppBskyFeedDefs.PostView { + return { + uri: `at://${author.did}/app.bsky.post/fake`, + cid: 'fake', + author, + record, + embed, + replyCount, + repostCount, + likeCount, + indexedAt: new Date().toISOString(), + viewer, + labels, + } + }, + + embedRecordView({ + record, + author, + labels, + }: { + record: AppBskyFeedPost.Record + author: AppBskyActorDefs.ProfileViewBasic + labels?: ComAtprotoLabelDefs.Label[] + }): AppBskyEmbedRecord.View { + return { + $type: 'app.bsky.embed.record#view', + record: { + $type: 'app.bsky.embed.record#viewRecord', + uri: `at://${author.did}/app.bsky.post/fake`, + cid: 'fake', + author, + value: record, + labels, + indexedAt: new Date().toISOString(), + }, + } + }, + + profileViewBasic({ + handle, + displayName, + viewer, + labels, + }: { + handle: string + displayName?: string + viewer?: AppBskyActorDefs.ViewerState + labels?: ComAtprotoLabelDefs.Label[] + }): AppBskyActorDefs.ProfileViewBasic { + return { + did: `did:web:${handle}`, + handle, + displayName, + viewer, + labels, + } + }, + + actorViewerState({ + muted, + mutedByList, + blockedBy, + blocking, + following, + followedBy, + }: { + muted?: boolean + mutedByList?: AppBskyGraphDefs.ListViewBasic + blockedBy?: boolean + blocking?: string + following?: string + followedBy?: string + }): AppBskyActorDefs.ViewerState { + return { + muted, + mutedByList, + blockedBy, + blocking, + following, + followedBy, + } + }, + + listViewBasic({ name }: { name: string }): AppBskyGraphDefs.ListViewBasic { + return { + uri: 'at://did:plc:fake/app.bsky.graph.list/fake', + cid: 'fake', + name, + purpose: 'app.bsky.graph.defs#modlist', + indexedAt: new Date().toISOString(), + } + }, + + label({ val, uri }: { val: string; uri: string }): ComAtprotoLabelDefs.Label { + return { + src: 'did:plc:fake-labeler', + uri, + val, + cts: new Date().toISOString(), + } + }, +} diff --git a/packages/api/tests/util/moderation-behavior.ts b/packages/api/tests/util/moderation-behavior.ts new file mode 100644 index 00000000000..4eda83a7b41 --- /dev/null +++ b/packages/api/tests/util/moderation-behavior.ts @@ -0,0 +1,181 @@ +import { ModerationUI, ModerationOpts, ComAtprotoLabelDefs } from '../../src' +import type { + ModerationBehaviors, + ModerationBehaviorScenario, + ModerationBehaviorResult, +} from '../../definitions/moderation-behaviors' +import { mock as m } from './index' + +expect.extend({ + toBeModerationResult( + actual: ModerationUI, + expected: ModerationBehaviorResult | undefined, + context: string, + stringifiedResult: string, + ignoreCause = false, + ) { + const fail = (msg: string) => ({ + pass: false, + message: () => `${msg}. Full result: ${stringifiedResult}`, + }) + let cause = actual.cause?.type as string + if (actual.cause?.type === 'label') { + cause = `label:${actual.cause.labelDef.id}` + } else if (actual.cause?.type === 'muted') { + if (actual.cause.source.type === 'list') { + cause = 'muted-by-list' + } + } + if (!expected) { + if (!ignoreCause && actual.cause) { + return fail(`${context} expected to be a no-op, got ${cause}`) + } + if (actual.alert) { + return fail(`${context} expected to be a no-op, got alert=true`) + } + if (actual.blur) { + return fail(`${context} expected to be a no-op, got blur=true`) + } + if (actual.filter) { + return fail(`${context} expected to be a no-op, got filter=true`) + } + if (actual.noOverride) { + return fail(`${context} expected to be a no-op, got noOverride=true`) + } + } else { + if (!ignoreCause && cause !== expected.cause) { + return fail(`${context} expected to be ${expected.cause}, got ${cause}`) + } + if (!!actual.alert !== !!expected.alert) { + return fail( + `${context} expected to be alert=${expected.alert || false}, got ${ + actual.alert || false + }`, + ) + } + if (!!actual.blur !== !!expected.blur) { + return fail( + `${context} expected to be blur=${expected.blur || false}, got ${ + actual.blur || false + }`, + ) + } + if (!!actual.filter !== !!expected.filter) { + return fail( + `${context} expected to be filter=${expected.filter || false}, got ${ + actual.filter || false + }`, + ) + } + if (!!actual.noOverride !== !!expected.noOverride) { + return fail( + `${context} expected to be noOverride=${ + expected.noOverride || false + }, got ${actual.noOverride || false}`, + ) + } + } + return { pass: true, message: () => '' } + }, +}) + +export class ModerationBehaviorSuiteRunner { + constructor(public suite: ModerationBehaviors) {} + + postScenario(scenario: ModerationBehaviorScenario) { + if (scenario.subject !== 'post') { + throw new Error('Scenario subject must be "post"') + } + const author = this.profileViewBasic(scenario.author, scenario.labels) + return m.postView({ + record: m.post({ + text: 'Post text', + }), + author, + labels: (scenario.labels.post || []).map((val) => + m.label({ val, uri: `at://${author.did}/app.bsky.feed.post/fake` }), + ), + embed: scenario.quoteAuthor + ? m.embedRecordView({ + record: m.post({ + text: 'Quoted post text', + }), + labels: (scenario.labels.quotedPost || []).map((val) => + m.label({ + val, + uri: `at://${author.did}/app.bsky.feed.post/fake`, + }), + ), + author: this.profileViewBasic(scenario.quoteAuthor, { + account: scenario.labels.quotedAccount, + }), + }) + : undefined, + }) + } + + profileScenario(scenario: ModerationBehaviorScenario) { + if (scenario.subject !== 'profile') { + throw new Error('Scenario subject must be "profile"') + } + return this.profileViewBasic(scenario.author, scenario.labels) + } + + profileViewBasic( + name: string, + scenarioLabels: ModerationBehaviorScenario['labels'], + ) { + const def = this.suite.users[name] + + const labels: ComAtprotoLabelDefs.Label[] = [] + if (scenarioLabels.account) { + for (const l of scenarioLabels.account) { + labels.push(m.label({ val: l, uri: `did:web:${name}` })) + } + } + if (scenarioLabels.profile) { + for (const l of scenarioLabels.profile) { + labels.push( + m.label({ + val: l, + uri: `at://did:web:${name}/app.bsky.actor.profile/self`, + }), + ) + } + } + + return m.profileViewBasic({ + handle: `${name}.test`, + labels, + viewer: m.actorViewerState({ + muted: def.muted || def.mutedByList, + mutedByList: def.mutedByList + ? m.listViewBasic({ name: 'Fake List' }) + : undefined, + blockedBy: def.blockedBy, + blocking: def.blocking + ? 'at://did:web:self.test/app.bsky.graph.block/fake' + : undefined, + }), + }) + } + + moderationOpts(scenario: ModerationBehaviorScenario): ModerationOpts { + return { + userDid: 'did:web:self.test', + adultContentEnabled: Boolean( + this.suite.configurations[scenario.cfg].adultContentEnabled, + ), + labels: this.suite.configurations[scenario.cfg].settings, + labelers: [ + { + labeler: { + did: 'did:plc:fake-labeler', + displayName: 'Fake Labeler', + }, + labels: this.suite.configurations[scenario.cfg].settings, + }, + ], + } + } +} diff --git a/packages/api/tsconfig.build.json b/packages/api/tsconfig.build.json index 27df65b89e2..02a84823b65 100644 --- a/packages/api/tsconfig.build.json +++ b/packages/api/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", "exclude": ["**/*.spec.ts", "**/*.test.ts"] -} \ No newline at end of file +} diff --git a/packages/api/update-pkg.js b/packages/api/update-pkg.js deleted file mode 100644 index 15d70bdab33..00000000000 --- a/packages/api/update-pkg.js +++ /dev/null @@ -1,14 +0,0 @@ -const pkgJson = require('@npmcli/package-json') - -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} -if (process.argv.includes('--update-main-to-src')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'src/index.ts' })) - .then((pkg) => pkg.save()) -} diff --git a/packages/aws/README.md b/packages/aws/README.md index 19e36786da6..9a32e52b36d 100644 --- a/packages/aws/README.md +++ b/packages/aws/README.md @@ -1,3 +1,3 @@ # AWS KMS -A Keypair-compatible wrapper for AWS KMS. \ No newline at end of file +A Keypair-compatible wrapper for AWS KMS. diff --git a/packages/aws/build.js b/packages/aws/build.js index 5628aa4f4eb..e880ae9930b 100644 --- a/packages/aws/build.js +++ b/packages/aws/build.js @@ -1,16 +1,8 @@ -const pkgJson = require('@npmcli/package-json') const { nodeExternalsPlugin } = require('esbuild-node-externals') const buildShallow = process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true' -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} - require('esbuild').build({ logLevel: 'info', entryPoints: ['src/index.ts'], diff --git a/packages/aws/package.json b/packages/aws/package.json index faedeb69c2f..70fe9f72698 100644 --- a/packages/aws/package.json +++ b/packages/aws/package.json @@ -1,7 +1,11 @@ { "name": "@atproto/aws", - "version": "0.0.1", + "version": "0.1.0", "main": "src/index.ts", + "publishConfig": { + "main": "dist/index.js", + "types": "dist/src/index.d.ts" + }, "license": "MIT", "repository": { "type": "git", @@ -9,24 +13,20 @@ "directory": "packages/aws" }, "scripts": { - "prettier": "prettier --check src/", - "prettier:fix": "prettier --write src/", - "lint": "eslint . --ext .ts,.tsx", - "lint:fix": "yarn lint --fix", - "verify": "run-p prettier lint", - "verify:fix": "yarn prettier:fix && yarn lint:fix", "build": "node ./build.js", - "postbuild": "tsc --build tsconfig.build.json" + "postbuild": "tsc --build tsconfig.build.json", + "update-main-to-dist": "node ../../update-main-to-dist.js packages/aws" }, "dependencies": { - "@atproto/crypto": "*", + "@atproto/crypto": "workspace:^", + "@atproto/repo": "workspace:^", "@aws-sdk/client-cloudfront": "^3.261.0", "@aws-sdk/client-kms": "^3.196.0", "@aws-sdk/client-s3": "^3.224.0", "@aws-sdk/lib-storage": "^3.226.0", "@noble/curves": "^1.1.0", "key-encoder": "^2.0.3", - "multiformats": "^9.6.4", + "multiformats": "^9.9.0", "uint8arrays": "3.0.0" } } diff --git a/packages/aws/tsconfig.build.json b/packages/aws/tsconfig.build.json index 27df65b89e2..02a84823b65 100644 --- a/packages/aws/tsconfig.build.json +++ b/packages/aws/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", "exclude": ["**/*.spec.ts", "**/*.test.ts"] -} \ No newline at end of file +} diff --git a/packages/aws/tsconfig.json b/packages/aws/tsconfig.json index 624cc32a35f..fee83b7f23b 100644 --- a/packages/aws/tsconfig.json +++ b/packages/aws/tsconfig.json @@ -4,5 +4,5 @@ "outDir": "./dist", // Your outDir, "emitDeclarationOnly": true }, - "include": ["./src","__tests__/**/**.ts"] -} \ No newline at end of file + "include": ["./src", "__tests__/**/**.ts"] +} diff --git a/packages/bsky/.eslintignore b/packages/bsky/.eslintignore deleted file mode 100644 index 18dd0f354fc..00000000000 --- a/packages/bsky/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -lexicon \ No newline at end of file diff --git a/packages/bsky/.prettierignore b/packages/bsky/.prettierignore deleted file mode 100644 index dd7966f095f..00000000000 --- a/packages/bsky/.prettierignore +++ /dev/null @@ -1 +0,0 @@ -src/lexicon/**/* diff --git a/packages/bsky/build.js b/packages/bsky/build.js index ba40c492406..3822d9bc98f 100644 --- a/packages/bsky/build.js +++ b/packages/bsky/build.js @@ -1,19 +1,11 @@ -const pkgJson = require('@npmcli/package-json') const { nodeExternalsPlugin } = require('esbuild-node-externals') const buildShallow = process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true' -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} - require('esbuild').build({ logLevel: 'info', - entryPoints: ['src/index.ts', 'src/bin.ts', 'src/db/index.ts'], + entryPoints: ['src/index.ts', 'src/db/index.ts'], bundle: true, sourcemap: true, outdir: 'dist', diff --git a/packages/bsky/package.json b/packages/bsky/package.json index 1a873caf272..508d9ef569f 100644 --- a/packages/bsky/package.json +++ b/packages/bsky/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/bsky", - "version": "0.0.2", + "version": "0.0.3", "license": "MIT", "repository": { "type": "git", @@ -8,64 +8,65 @@ "directory": "packages/bsky" }, "main": "src/index.ts", - "bin": "dist/bin.ts", + "publishConfig": { + "main": "dist/index.js", + "types": "dist/index.d.ts" + }, + "bin": "dist/bin.js", "scripts": { "codegen": "lex gen-server ./src/lexicon ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/*", "build": "node ./build.js", "postbuild": "tsc --build tsconfig.build.json", + "update-main-to-dist": "node ../../update-main-to-dist.js packages/bsky", "start": "node --enable-source-maps dist/bin.js", - "test": "../pg/with-test-db.sh jest", + "test": "../dev-infra/with-test-redis-and-db.sh jest", "test:log": "tail -50 test.log | pino-pretty", "test:updateSnapshot": "jest --updateSnapshot", - "prettier": "prettier --check src/ tests/", - "prettier:fix": "prettier --write src/ tests/", - "lint": "eslint . --ext .ts,.tsx", - "lint:fix": "yarn lint --fix", - "verify": "run-p prettier lint", - "verify:fix": "yarn prettier:fix && yarn lint:fix", - "migration:create": "ts-node ./bin/migration-create.ts", - "update-main-to-dist": "node ./update-pkg.js --update-main-to-dist", - "update-main-to-src": "node ./update-pkg.js --update-main-to-src", - "prepublish": "npm run update-main-to-dist", - "postpublish": "npm run update-main-to-src" + "migration:create": "ts-node ./bin/migration-create.ts" }, "dependencies": { - "@atproto/api": "*", - "@atproto/common": "*", - "@atproto/crypto": "*", - "@atproto/identifier": "*", - "@atproto/identity": "*", - "@atproto/lexicon": "*", - "@atproto/repo": "*", - "@atproto/uri": "*", - "@atproto/xrpc-server": "*", + "@atproto/api": "workspace:^", + "@atproto/common": "workspace:^", + "@atproto/crypto": "workspace:^", + "@atproto/syntax": "workspace:^", + "@atproto/identity": "workspace:^", + "@atproto/lexicon": "workspace:^", + "@atproto/repo": "workspace:^", + "@atproto/xrpc-server": "workspace:^", "@did-plc/lib": "^0.0.1", + "@isaacs/ttlcache": "^1.4.1", + "compression": "^1.7.4", "cors": "^2.8.5", "dotenv": "^16.0.0", "express": "^4.17.2", "express-async-errors": "^3.1.1", + "form-data": "^4.0.0", "http-errors": "^2.0.0", "http-terminator": "^3.2.0", + "ioredis": "^5.3.2", + "iso-datestring-validator": "^2.2.2", "kysely": "^0.22.0", - "multiformats": "^9.6.4", + "multiformats": "^9.9.0", "p-queue": "^6.6.2", "pg": "^8.10.0", - "pino": "^8.6.1", + "pino": "^8.15.0", "pino-http": "^8.2.1", "sharp": "^0.31.2", "typed-emitter": "^2.1.0", "uint8arrays": "3.0.0" }, "devDependencies": { - "@atproto/api": "*", - "@atproto/dev-env": "*", - "@atproto/lex-cli": "*", - "@atproto/pds": "* | >=0.2.0-beta.0", - "@atproto/xrpc": "*", + "@atproto/api": "workspace:^", + "@atproto/dev-env": "workspace:^", + "@atproto/lex-cli": "workspace:^", + "@atproto/pds": "workspace:^", + "@atproto/xrpc": "workspace:^", "@did-plc/server": "^0.0.1", "@types/cors": "^2.8.12", "@types/express": "^4.17.13", + "@types/express-serve-static-core": "^4.17.36", "@types/pg": "^8.6.6", + "@types/qs": "^6.9.7", "@types/sharp": "^0.31.0", "axios": "^0.27.2" } diff --git a/packages/bsky/service/package.json b/packages/bsky/service/package.json deleted file mode 100644 index 52fa4decbd1..00000000000 --- a/packages/bsky/service/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "bsky-app-view-service", - "private": true, - "dependencies": { - "dd-trace": "^3.8.0" - } -} diff --git a/packages/bsky/service/yarn.lock b/packages/bsky/service/yarn.lock deleted file mode 100644 index c1090549f70..00000000000 --- a/packages/bsky/service/yarn.lock +++ /dev/null @@ -1,320 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@datadog/native-appsec@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@datadog/native-appsec/-/native-appsec-2.0.0.tgz#ad65ba19bfd68e6b6c6cf64bb8ef55d099af8edc" - integrity sha512-XHARZ6MVgbnfOUO6/F3ZoZ7poXHJCNYFlgcyS2Xetuk9ITA5bfcooX2B2F7tReVB+RLJ+j8bsm0t55SyF04KDw== - dependencies: - node-gyp-build "^3.9.0" - -"@datadog/native-iast-rewriter@1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@datadog/native-iast-rewriter/-/native-iast-rewriter-1.1.2.tgz#793cbf92d218ec80d645be0830023656b81018ea" - integrity sha512-pigRfRtAjZjMjqIXyXb98S4aDnuHz/EmqpoxAajFZsNjBLM87YonwSY5zoBdCsOyA46ddKOJRoCQd5ZalpOFMQ== - dependencies: - node-gyp-build "^4.5.0" - -"@datadog/native-iast-taint-tracking@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@datadog/native-iast-taint-tracking/-/native-iast-taint-tracking-1.1.0.tgz#8f7d0016157b32dbf5c01b15b8afb1c4286b4a18" - integrity sha512-TOrngpt6Qh52zWFOz1CkFXw0g43rnuUziFBtIMUsOLGzSHr9wdnTnE6HAyuvKy3f3ecAoZESlMfilGRKP93hXQ== - dependencies: - node-gyp-build "^3.9.0" - -"@datadog/native-metrics@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@datadog/native-metrics/-/native-metrics-1.5.0.tgz#e71b6b6d65f4bd58dfdffab2737890e8eef34584" - integrity sha512-K63XMDx74RLhOpM8I9GGZR9ft0CNNB/RkjYPLHcVGvVnBR47zmWE2KFa7Yrtzjbk73+88PXI4nzqLyR3PJsaIQ== - dependencies: - node-gyp-build "^3.9.0" - -"@datadog/pprof@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@datadog/pprof/-/pprof-1.1.1.tgz#17e86035140523ac3a96f3662e5dd29822042d61" - integrity sha512-5lYXUpikQhrJwzODtJ7aFM0oKmPccISnTCecuWhjxIj4/7UJv0DamkLak634bgEW+kiChgkKFDapHSesuXRDXQ== - dependencies: - delay "^5.0.0" - findit2 "^2.2.3" - node-gyp-build "^3.9.0" - p-limit "^3.1.0" - pify "^5.0.0" - protobufjs "^7.0.0" - source-map "^0.7.3" - split "^1.0.1" - -"@datadog/sketches-js@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@datadog/sketches-js/-/sketches-js-2.1.0.tgz#8c7e8028a5fc22ad102fa542b0a446c956830455" - integrity sha512-smLocSfrt3s53H/XSVP3/1kP42oqvrkjUPtyaFd1F79ux24oE31BKt+q0c6lsa6hOYrFzsIwyc5GXAI5JmfOew== - -"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== - -"@protobufjs/base64@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" - integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== - -"@protobufjs/codegen@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" - integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== - -"@protobufjs/eventemitter@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== - -"@protobufjs/fetch@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== - dependencies: - "@protobufjs/aspromise" "^1.1.1" - "@protobufjs/inquire" "^1.1.0" - -"@protobufjs/float@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== - -"@protobufjs/inquire@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== - -"@protobufjs/path@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== - -"@protobufjs/pool@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== - -"@protobufjs/utf8@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== - -"@types/node@>=13.7.0": - version "18.13.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.13.0.tgz#0400d1e6ce87e9d3032c19eb6c58205b0d3f7850" - integrity sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg== - -crypto-randomuuid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/crypto-randomuuid/-/crypto-randomuuid-1.0.0.tgz#acf583e5e085e867ae23e107ff70279024f9e9e7" - integrity sha512-/RC5F4l1SCqD/jazwUF6+t34Cd8zTSAGZ7rvvZu1whZUhD2a5MOGKjSGowoGcpj/cbVZk1ZODIooJEQQq3nNAA== - -dd-trace@^3.8.0: - version "3.13.2" - resolved "https://registry.yarnpkg.com/dd-trace/-/dd-trace-3.13.2.tgz#95b1ec480ab9ac406e1da7591a8c6f678d3799fd" - integrity sha512-POO9nEcAufe5pgp2xV1X3PfWip6wh+6TpEcRSlSgZJCIIMvWVCkcIVL/J2a6KAZq6V3Yjbkl8Ktfe+MOzQf5kw== - dependencies: - "@datadog/native-appsec" "2.0.0" - "@datadog/native-iast-rewriter" "1.1.2" - "@datadog/native-iast-taint-tracking" "1.1.0" - "@datadog/native-metrics" "^1.5.0" - "@datadog/pprof" "^1.1.1" - "@datadog/sketches-js" "^2.1.0" - crypto-randomuuid "^1.0.0" - diagnostics_channel "^1.1.0" - ignore "^5.2.0" - import-in-the-middle "^1.3.4" - ipaddr.js "^2.0.1" - istanbul-lib-coverage "3.2.0" - koalas "^1.0.2" - limiter "^1.1.4" - lodash.kebabcase "^4.1.1" - lodash.pick "^4.4.0" - lodash.sortby "^4.7.0" - lodash.uniq "^4.5.0" - lru-cache "^7.14.0" - methods "^1.1.2" - module-details-from-path "^1.0.3" - node-abort-controller "^3.0.1" - opentracing ">=0.12.1" - path-to-regexp "^0.1.2" - protobufjs "^7.1.2" - retry "^0.10.1" - semver "^5.5.0" - -delay@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" - integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== - -diagnostics_channel@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/diagnostics_channel/-/diagnostics_channel-1.1.0.tgz#bd66c49124ce3bac697dff57466464487f57cea5" - integrity sha512-OE1ngLDjSBPG6Tx0YATELzYzy3RKHC+7veQ8gLa8yS7AAgw65mFbVdcsu3501abqOZCEZqZyAIemB0zXlqDSuw== - -findit2@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/findit2/-/findit2-2.2.3.tgz#58a466697df8a6205cdfdbf395536b8bd777a5f6" - integrity sha512-lg/Moejf4qXovVutL0Lz4IsaPoNYMuxt4PA0nGqFxnJ1CTTGGlEO2wKgoDpwknhvZ8k4Q2F+eesgkLbG2Mxfog== - -ignore@^5.2.0: - version "5.2.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" - integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== - -import-in-the-middle@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.3.4.tgz#7074bbd4e84e8cdafd1eae400b04e6fe252a0768" - integrity sha512-TUXqqEFacJ2DWAeYOhHwGZTMJtFxFVw0C1pYA+AXmuWXZGnBqUhHdtVrSkSbW5D7k2yriBG45j23iH9TRtI+bQ== - dependencies: - module-details-from-path "^1.0.3" - -ipaddr.js@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" - integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== - -istanbul-lib-coverage@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" - integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== - -koalas@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/koalas/-/koalas-1.0.2.tgz#318433f074235db78fae5661a02a8ca53ee295cd" - integrity sha512-RYhBbYaTTTHId3l6fnMZc3eGQNW6FVCqMG6AMwA5I1Mafr6AflaXeoi6x3xQuATRotGYRLk6+1ELZH4dstFNOA== - -limiter@^1.1.4: - version "1.1.5" - resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.5.tgz#8f92a25b3b16c6131293a0cc834b4a838a2aa7c2" - integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA== - -lodash.kebabcase@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" - integrity sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g== - -lodash.pick@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" - integrity sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q== - -lodash.sortby@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" - integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== - -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== - -long@^5.0.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/long/-/long-5.2.1.tgz#e27595d0083d103d2fa2c20c7699f8e0c92b897f" - integrity sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A== - -lru-cache@^7.14.0: - version "7.16.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.16.0.tgz#b1b946cff368d3f3c569cc3d6a5ba8f90435160f" - integrity sha512-VJBdeMa9Bz27NNlx+DI/YXGQtXdjUU+9gdfN1rYfra7vtTjhodl5tVNmR42bo+ORHuDqDT+lGAUAb+lzvY42Bw== - -methods@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -module-details-from-path@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b" - integrity sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A== - -node-abort-controller@^3.0.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" - integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== - -node-gyp-build@^3.9.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.9.0.tgz#53a350187dd4d5276750da21605d1cb681d09e25" - integrity sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A== - -node-gyp-build@^4.5.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" - integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== - -opentracing@>=0.12.1: - version "0.14.7" - resolved "https://registry.yarnpkg.com/opentracing/-/opentracing-0.14.7.tgz#25d472bd0296dc0b64d7b94cbc995219031428f5" - integrity sha512-vz9iS7MJ5+Bp1URw8Khvdyw1H/hGvzHWlKQ7eRrQojSCDL1/SrWfrY9QebLw97n2deyRtzHRC3MkQfVNUCo91Q== - -p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -path-to-regexp@^0.1.2: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== - -pify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" - integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== - -protobufjs@^7.0.0, protobufjs@^7.1.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.2.tgz#2af401d8c547b9476fb37ffc65782cf302342ca3" - integrity sha512-++PrQIjrom+bFDPpfmqXfAGSQs40116JRrqqyf53dymUMvvb5d/LMRyicRoF1AUKoXVS1/IgJXlEgcpr4gTF3Q== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/node" ">=13.7.0" - long "^5.0.0" - -retry@^0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" - integrity sha512-ZXUSQYTHdl3uS7IuCehYfMzKyIDBNoAuUblvy5oGO5UJSUTmStUUVPXbA9Qxd173Bgre53yCQczQuHgRWAdvJQ== - -semver@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -source-map@^0.7.3: - version "0.7.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" - integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== - -split@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" - integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== - dependencies: - through "2" - -through@2: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/packages/bsky/src/api/app/bsky/actor/getProfile.ts b/packages/bsky/src/api/app/bsky/actor/getProfile.ts index 51cbd313eaf..09699b8914b 100644 --- a/packages/bsky/src/api/app/bsky/actor/getProfile.ts +++ b/packages/bsky/src/api/app/bsky/actor/getProfile.ts @@ -1,33 +1,107 @@ import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' +import { QueryParams } from '../../../../lexicon/types/app/bsky/actor/getProfile' import { softDeleted } from '../../../../db/util' import AppContext from '../../../../context' +import { Database } from '../../../../db' +import { Actor } from '../../../../db/tables/actor' +import { + ActorService, + ProfileDetailHydrationState, +} from '../../../../services/actor' +import { setRepoRev } from '../../../util' +import { createPipeline, noRules } from '../../../../pipeline' export default function (server: Server, ctx: AppContext) { + const getProfile = createPipeline(skeleton, hydration, noRules, presentation) server.app.bsky.actor.getProfile({ - auth: ctx.authOptionalVerifier, - handler: async ({ auth, params }) => { - const { actor } = params - const requester = auth.credentials.did - const { db, services } = ctx - const actorService = services.actor(db) + auth: ctx.authOptionalAccessOrRoleVerifier, + handler: async ({ auth, params, res }) => { + const db = ctx.db.getReplica() + const actorService = ctx.services.actor(db) + const viewer = 'did' in auth.credentials ? auth.credentials.did : null + const canViewTakendownProfile = + auth.credentials.type === 'role' && auth.credentials.triage - const actorRes = await actorService.getActor(actor, true) + const [result, repoRev] = await Promise.allSettled([ + getProfile( + { ...params, viewer, canViewTakendownProfile }, + { db, actorService }, + ), + actorService.getRepoRev(viewer), + ]) - if (!actorRes) { - throw new InvalidRequestError('Profile not found') + if (repoRev.status === 'fulfilled') { + setRepoRev(res, repoRev.value) } - if (softDeleted(actorRes)) { - throw new InvalidRequestError( - 'Account has been taken down', - 'AccountTakedown', - ) + if (result.status === 'rejected') { + throw result.reason } return { encoding: 'application/json', - body: await actorService.views.profileDetailed(actorRes, requester), + body: result.value, } }, }) } + +const skeleton = async ( + params: Params, + ctx: Context, +): Promise => { + const { actorService } = ctx + const { canViewTakendownProfile } = params + const actor = await actorService.getActor(params.actor, true) + if (!actor) { + throw new InvalidRequestError('Profile not found') + } + if (!canViewTakendownProfile && softDeleted(actor)) { + throw new InvalidRequestError( + 'Account has been taken down', + 'AccountTakedown', + ) + } + return { params, actor } +} + +const hydration = async (state: SkeletonState, ctx: Context) => { + const { actorService } = ctx + const { params, actor } = state + const { viewer, canViewTakendownProfile } = params + const hydration = await actorService.views.profileDetailHydration( + [actor.did], + { viewer, includeSoftDeleted: canViewTakendownProfile }, + ) + return { ...state, ...hydration } +} + +const presentation = (state: HydrationState, ctx: Context) => { + const { actorService } = ctx + const { params, actor } = state + const { viewer } = params + const profiles = actorService.views.profileDetailPresentation( + [actor.did], + state, + { viewer }, + ) + const profile = profiles[actor.did] + if (!profile) { + throw new InvalidRequestError('Profile not found') + } + return profile +} + +type Context = { + db: Database + actorService: ActorService +} + +type Params = QueryParams & { + viewer: string | null + canViewTakendownProfile: boolean +} + +type SkeletonState = { params: Params; actor: Actor } + +type HydrationState = SkeletonState & ProfileDetailHydrationState diff --git a/packages/bsky/src/api/app/bsky/actor/getProfiles.ts b/packages/bsky/src/api/app/bsky/actor/getProfiles.ts index ad676f01959..f2e0eb3fd50 100644 --- a/packages/bsky/src/api/app/bsky/actor/getProfiles.ts +++ b/packages/bsky/src/api/app/bsky/actor/getProfiles.ts @@ -1,26 +1,78 @@ +import { mapDefined } from '@atproto/common' import { Server } from '../../../../lexicon' +import { QueryParams } from '../../../../lexicon/types/app/bsky/actor/getProfiles' import AppContext from '../../../../context' +import { Database } from '../../../../db' +import { + ActorService, + ProfileDetailHydrationState, +} from '../../../../services/actor' +import { setRepoRev } from '../../../util' +import { createPipeline, noRules } from '../../../../pipeline' export default function (server: Server, ctx: AppContext) { + const getProfile = createPipeline(skeleton, hydration, noRules, presentation) server.app.bsky.actor.getProfiles({ auth: ctx.authOptionalVerifier, - handler: async ({ auth, params }) => { - const { actors } = params - const requester = auth.credentials.did - const { db, services } = ctx - const actorService = services.actor(db) + handler: async ({ auth, params, res }) => { + const db = ctx.db.getReplica() + const actorService = ctx.services.actor(db) + const viewer = auth.credentials.did - const actorsRes = await actorService.getActors(actors) + const [result, repoRev] = await Promise.all([ + getProfile({ ...params, viewer }, { db, actorService }), + actorService.getRepoRev(viewer), + ]) + + setRepoRev(res, repoRev) return { encoding: 'application/json', - body: { - profiles: await actorService.views.profileDetailed( - actorsRes, - requester, - ), - }, + body: result, } }, }) } + +const skeleton = async ( + params: Params, + ctx: Context, +): Promise => { + const { actorService } = ctx + const actors = await actorService.getActors(params.actors) + return { params, dids: actors.map((a) => a.did) } +} + +const hydration = async (state: SkeletonState, ctx: Context) => { + const { actorService } = ctx + const { params, dids } = state + const { viewer } = params + const hydration = await actorService.views.profileDetailHydration(dids, { + viewer, + }) + return { ...state, ...hydration } +} + +const presentation = (state: HydrationState, ctx: Context) => { + const { actorService } = ctx + const { params, dids } = state + const { viewer } = params + const profiles = actorService.views.profileDetailPresentation(dids, state, { + viewer, + }) + const profileViews = mapDefined(dids, (did) => profiles[did]) + return { profiles: profileViews } +} + +type Context = { + db: Database + actorService: ActorService +} + +type Params = QueryParams & { + viewer: string | null +} + +type SkeletonState = { params: Params; dids: string[] } + +type HydrationState = SkeletonState & ProfileDetailHydrationState diff --git a/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts b/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts index 37448cbf0d3..18ab99debe2 100644 --- a/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts +++ b/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts @@ -1,63 +1,126 @@ +import { mapDefined } from '@atproto/common' import AppContext from '../../../../context' +import { Database } from '../../../../db' +import { Actor } from '../../../../db/tables/actor' import { notSoftDeletedClause } from '../../../../db/util' import { Server } from '../../../../lexicon' +import { QueryParams } from '../../../../lexicon/types/app/bsky/actor/getSuggestions' +import { createPipeline } from '../../../../pipeline' +import { ActorInfoMap, ActorService } from '../../../../services/actor' +import { BlockAndMuteState, GraphService } from '../../../../services/graph' export default function (server: Server, ctx: AppContext) { + const getSuggestions = createPipeline( + skeleton, + hydration, + noBlocksOrMutes, + presentation, + ) server.app.bsky.actor.getSuggestions({ auth: ctx.authOptionalVerifier, handler: async ({ params, auth }) => { - const { limit, cursor } = params + const db = ctx.db.getReplica() + const actorService = ctx.services.actor(db) + const graphService = ctx.services.graph(db) const viewer = auth.credentials.did - const actorService = ctx.services.actor(ctx.db) - const graphService = ctx.services.graph(ctx.db) - - const db = ctx.db.db - const { ref } = db.dynamic - - let suggestionsQb = db - .selectFrom('suggested_follow') - .innerJoin('actor', 'actor.did', 'suggested_follow.did') - .innerJoin('profile_agg', 'profile_agg.did', 'actor.did') - .where(notSoftDeletedClause(ref('actor'))) - .where('suggested_follow.did', '!=', viewer ?? '') - .whereNotExists((qb) => - qb - .selectFrom('follow') - .selectAll() - .where('creator', '=', viewer ?? '') - .whereRef('subjectDid', '=', ref('actor.did')), - ) - .whereNotExists(graphService.blockQb(viewer, [ref('actor.did')])) - .selectAll() - .select('profile_agg.postsCount as postsCount') - .limit(limit) - .orderBy('suggested_follow.order', 'asc') - - if (cursor) { - const cursorRow = await db - .selectFrom('suggested_follow') - .where('did', '=', cursor) - .selectAll() - .executeTakeFirst() - if (cursorRow) { - suggestionsQb = suggestionsQb.where( - 'suggested_follow.order', - '>', - cursorRow.order, - ) - } - } - - const suggestionsRes = await suggestionsQb.execute() + const result = await getSuggestions( + { ...params, viewer }, + { db, actorService, graphService }, + ) return { encoding: 'application/json', - body: { - cursor: suggestionsRes.at(-1)?.did, - actors: await actorService.views.profile(suggestionsRes, viewer), - }, + body: result, } }, }) } + +const skeleton = async ( + params: Params, + ctx: Context, +): Promise => { + const { db } = ctx + const { limit, cursor, viewer } = params + const { ref } = db.db.dynamic + let suggestionsQb = db.db + .selectFrom('suggested_follow') + .innerJoin('actor', 'actor.did', 'suggested_follow.did') + .innerJoin('profile_agg', 'profile_agg.did', 'actor.did') + .where(notSoftDeletedClause(ref('actor'))) + .where('suggested_follow.did', '!=', viewer ?? '') + .whereNotExists((qb) => + qb + .selectFrom('follow') + .selectAll() + .where('creator', '=', viewer ?? '') + .whereRef('subjectDid', '=', ref('actor.did')), + ) + .selectAll() + .select('profile_agg.postsCount as postsCount') + .limit(limit) + .orderBy('suggested_follow.order', 'asc') + + if (cursor) { + const cursorRow = await db.db + .selectFrom('suggested_follow') + .where('did', '=', cursor) + .selectAll() + .executeTakeFirst() + if (cursorRow) { + suggestionsQb = suggestionsQb.where( + 'suggested_follow.order', + '>', + cursorRow.order, + ) + } + } + const suggestions = await suggestionsQb.execute() + return { params, suggestions, cursor: suggestions.at(-1)?.did } +} + +const hydration = async (state: SkeletonState, ctx: Context) => { + const { graphService, actorService } = ctx + const { params, suggestions } = state + const { viewer } = params + const [actors, bam] = await Promise.all([ + actorService.views.profiles(suggestions, viewer), + graphService.getBlockAndMuteState( + viewer ? suggestions.map((sug) => [viewer, sug.did]) : [], + ), + ]) + return { ...state, bam, actors } +} + +const noBlocksOrMutes = (state: HydrationState) => { + const { viewer } = state.params + if (!viewer) return state + state.suggestions = state.suggestions.filter( + (item) => + !state.bam.block([viewer, item.did]) && + !state.bam.mute([viewer, item.did]), + ) + return state +} + +const presentation = (state: HydrationState) => { + const { suggestions, actors, cursor } = state + const suggestedActors = mapDefined(suggestions, (sug) => actors[sug.did]) + return { actors: suggestedActors, cursor } +} + +type Context = { + db: Database + actorService: ActorService + graphService: GraphService +} + +type Params = QueryParams & { viewer: string | null } + +type SkeletonState = { params: Params; suggestions: Actor[]; cursor?: string } + +type HydrationState = SkeletonState & { + bam: BlockAndMuteState + actors: ActorInfoMap +} diff --git a/packages/bsky/src/api/app/bsky/actor/searchActors.ts b/packages/bsky/src/api/app/bsky/actor/searchActors.ts index d29114432c5..df5821a03f9 100644 --- a/packages/bsky/src/api/app/bsky/actor/searchActors.ts +++ b/packages/bsky/src/api/app/bsky/actor/searchActors.ts @@ -11,11 +11,12 @@ export default function (server: Server, ctx: AppContext) { server.app.bsky.actor.searchActors({ auth: ctx.authOptionalVerifier, handler: async ({ auth, params }) => { - const { services, db } = ctx const { cursor, limit, term: rawTerm } = params const requester = auth.credentials.did const term = cleanTerm(rawTerm || '') + const db = ctx.db.getReplica('search') + const results = term ? await getUserSearchQuery(db, { term, limit, cursor }) .select('distance') @@ -24,7 +25,9 @@ export default function (server: Server, ctx: AppContext) { : [] const keyset = new SearchKeyset(sql``, sql``) - const actors = await services.actor(db).views.profile(results, requester) + const actors = await ctx.services + .actor(db) + .views.profilesList(results, requester) const filtered = actors.filter( (actor) => !actor.viewer?.blocking && !actor.viewer?.blockedBy, ) diff --git a/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts b/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts index 9148634fa19..64bcd811d02 100644 --- a/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts +++ b/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts @@ -9,24 +9,28 @@ export default function (server: Server, ctx: AppContext) { server.app.bsky.actor.searchActorsTypeahead({ auth: ctx.authOptionalVerifier, handler: async ({ params, auth }) => { - const { services, db } = ctx const { limit, term: rawTerm } = params const requester = auth.credentials.did const term = cleanTerm(rawTerm || '') + const db = ctx.db.getReplica('search') + const results = term ? await getUserSearchQuerySimple(db, { term, limit }) .selectAll('actor') .execute() : [] - const actors = await services + const actors = await ctx.services .actor(db) - .views.profileBasic(results, requester) + .views.profilesBasic(results, requester, { omitLabels: true }) - const filtered = actors.filter( - (actor) => !actor.viewer?.blocking && !actor.viewer?.blockedBy, - ) + const SKIP = [] + const filtered = results.flatMap((res) => { + const actor = actors[res.did] + if (actor.viewer?.blocking || actor.viewer?.blockedBy) return SKIP + return actor + }) return { encoding: 'application/json', diff --git a/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts b/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts index 0a18d5b155a..deb5c3a5a1b 100644 --- a/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts +++ b/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts @@ -10,15 +10,16 @@ export default function (server: Server, ctx: AppContext) { const { actor, limit, cursor } = params const viewer = auth.credentials.did - const actorService = ctx.services.actor(ctx.db) - const feedService = ctx.services.feed(ctx.db) + const db = ctx.db.getReplica() + const actorService = ctx.services.actor(db) + const feedService = ctx.services.feed(db) const creatorRes = await actorService.getActor(actor) if (!creatorRes) { throw new InvalidRequestError(`Actor not found: ${actor}`) } - const { ref } = ctx.db.db.dynamic + const { ref } = db.db.dynamic let feedsQb = feedService .selectFeedGeneratorQb(viewer) .where('feed_generator.creator', '=', creatorRes.did) @@ -33,15 +34,21 @@ export default function (server: Server, ctx: AppContext) { keyset, }) - const [feedsRes, creatorProfile] = await Promise.all([ + const [feedsRes, profiles] = await Promise.all([ feedsQb.execute(), - actorService.views.profile(creatorRes, viewer), + actorService.views.profiles([creatorRes], viewer), ]) - const profiles = { [creatorProfile.did]: creatorProfile } + if (!profiles[creatorRes.did]) { + throw new InvalidRequestError(`Actor not found: ${actor}`) + } - const feeds = feedsRes.map((row) => - feedService.views.formatFeedGeneratorView(row, profiles), - ) + const feeds = feedsRes.map((row) => { + const feed = { + ...row, + viewer: viewer ? { like: row.viewerLike } : undefined, + } + return feedService.views.formatFeedGeneratorView(feed, profiles) + }) return { encoding: 'application/json', diff --git a/packages/bsky/src/api/app/bsky/feed/getActorLikes.ts b/packages/bsky/src/api/app/bsky/feed/getActorLikes.ts new file mode 100644 index 00000000000..73b8b070262 --- /dev/null +++ b/packages/bsky/src/api/app/bsky/feed/getActorLikes.ts @@ -0,0 +1,127 @@ +import { InvalidRequestError } from '@atproto/xrpc-server' +import { Server } from '../../../../lexicon' +import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getActorLikes' +import { FeedKeyset } from '../util/feed' +import { paginate } from '../../../../db/pagination' +import AppContext from '../../../../context' +import { setRepoRev } from '../../../util' +import { + FeedHydrationState, + FeedRow, + FeedService, +} from '../../../../services/feed' +import { Database } from '../../../../db' +import { ActorService } from '../../../../services/actor' +import { GraphService } from '../../../../services/graph' +import { createPipeline } from '../../../../pipeline' + +export default function (server: Server, ctx: AppContext) { + const getActorLikes = createPipeline( + skeleton, + hydration, + noPostBlocks, + presentation, + ) + server.app.bsky.feed.getActorLikes({ + auth: ctx.authOptionalVerifier, + handler: async ({ params, auth, res }) => { + const viewer = auth.credentials.did + const db = ctx.db.getReplica() + const actorService = ctx.services.actor(db) + const feedService = ctx.services.feed(db) + const graphService = ctx.services.graph(db) + + const [result, repoRev] = await Promise.all([ + getActorLikes( + { ...params, viewer }, + { db, actorService, feedService, graphService }, + ), + actorService.getRepoRev(viewer), + ]) + + setRepoRev(res, repoRev) + + return { + encoding: 'application/json', + body: result, + } + }, + }) +} + +const skeleton = async ( + params: Params, + ctx: Context, +): Promise => { + const { db, actorService, feedService } = ctx + const { actor, limit, cursor, viewer } = params + const { ref } = db.db.dynamic + + const actorRes = await actorService.getActor(actor) + if (!actorRes) { + throw new InvalidRequestError('Profile not found') + } + const actorDid = actorRes.did + + if (!viewer || viewer !== actorDid) { + throw new InvalidRequestError('Profile not found') + } + + let feedItemsQb = feedService + .selectFeedItemQb() + .innerJoin('like', 'like.subject', 'feed_item.uri') + .where('like.creator', '=', actorDid) + + const keyset = new FeedKeyset(ref('feed_item.sortAt'), ref('feed_item.cid')) + + feedItemsQb = paginate(feedItemsQb, { + limit, + cursor, + keyset, + }) + + const feedItems = await feedItemsQb.execute() + + return { params, feedItems, cursor: keyset.packFromResult(feedItems) } +} + +const hydration = async (state: SkeletonState, ctx: Context) => { + const { feedService } = ctx + const { params, feedItems } = state + const refs = feedService.feedItemRefs(feedItems) + const hydrated = await feedService.feedHydration({ + ...refs, + viewer: params.viewer, + }) + return { ...state, ...hydrated } +} + +const noPostBlocks = (state: HydrationState) => { + const { viewer } = state.params + state.feedItems = state.feedItems.filter( + (item) => !viewer || !state.bam.block([viewer, item.postAuthorDid]), + ) + return state +} + +const presentation = (state: HydrationState, ctx: Context) => { + const { feedService } = ctx + const { feedItems, cursor, params } = state + const feed = feedService.views.formatFeed(feedItems, state, { + viewer: params.viewer, + }) + return { feed, cursor } +} + +type Context = { + db: Database + feedService: FeedService + actorService: ActorService + graphService: GraphService +} + +type Params = QueryParams & { viewer: string | null } + +type SkeletonState = { params: Params; feedItems: FeedRow[]; cursor?: string } + +type HydrationState = SkeletonState & FeedHydrationState diff --git a/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts b/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts index 5fbabd9f1e5..9f25eb131e1 100644 --- a/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts +++ b/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts @@ -1,87 +1,171 @@ +import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' +import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getAuthorFeed' import { FeedKeyset } from '../util/feed' import { paginate } from '../../../../db/pagination' import AppContext from '../../../../context' -import { InvalidRequestError } from '@atproto/xrpc-server' +import { setRepoRev } from '../../../util' +import { Database } from '../../../../db' +import { + FeedHydrationState, + FeedRow, + FeedService, +} from '../../../../services/feed' +import { ActorService } from '../../../../services/actor' +import { GraphService } from '../../../../services/graph' +import { createPipeline } from '../../../../pipeline' export default function (server: Server, ctx: AppContext) { + const getAuthorFeed = createPipeline( + skeleton, + hydration, + noBlocksOrMutedReposts, + presentation, + ) server.app.bsky.feed.getAuthorFeed({ - auth: ctx.authOptionalVerifier, - handler: async ({ params, auth }) => { - const { actor, limit, cursor } = params - const viewer = auth.credentials.did - const db = ctx.db.db - const { ref } = db.dynamic - - // first verify there is not a block between requester & subject - if (viewer !== null) { - const blocks = await ctx.services.graph(ctx.db).getBlocks(viewer, actor) - if (blocks.blocking) { - throw new InvalidRequestError( - `Requester has blocked actor: ${actor}`, - 'BlockedActor', - ) - } else if (blocks.blockedBy) { - throw new InvalidRequestError( - `Requester is blocked by actor: $${actor}`, - 'BlockedByActor', - ) - } - } + auth: ctx.authOptionalAccessOrRoleVerifier, + handler: async ({ params, auth, res }) => { + const db = ctx.db.getReplica() + const actorService = ctx.services.actor(db) + const feedService = ctx.services.feed(db) + const graphService = ctx.services.graph(db) + const viewer = + auth.credentials.type === 'access' ? auth.credentials.did : null - const feedService = ctx.services.feed(ctx.db) - const graphService = ctx.services.graph(ctx.db) - - let did = '' - if (actor.startsWith('did:')) { - did = actor - } else { - const actorRes = await db - .selectFrom('actor') - .select('did') - .where('handle', '=', actor) - .executeTakeFirst() - if (actorRes) { - did = actorRes?.did - } - } + const [result, repoRev] = await Promise.all([ + getAuthorFeed( + { ...params, viewer }, + { db, actorService, feedService, graphService }, + ), + actorService.getRepoRev(viewer), + ]) + + setRepoRev(res, repoRev) - let feedItemsQb = feedService - .selectFeedItemQb() - .where('originatorDid', '=', did) - - if (viewer !== null) { - feedItemsQb = feedItemsQb.where((qb) => - // Hide reposts of muted content - qb - .where('type', '=', 'post') - .orWhere((qb) => - graphService.whereNotMuted(qb, viewer, [ref('post.creator')]), - ), - ) + return { + encoding: 'application/json', + body: result, } + }, + }) +} + +export const skeleton = async ( + params: Params, + ctx: Context, +): Promise => { + const { cursor, limit, actor, filter, viewer } = params + const { db, actorService, feedService, graphService } = ctx + const { ref } = db.db.dynamic + + // maybe resolve did first + const actorRes = await actorService.getActor(actor) + if (!actorRes) { + throw new InvalidRequestError('Profile not found') + } + const actorDid = actorRes.did - const keyset = new FeedKeyset( - ref('feed_item.sortAt'), - ref('feed_item.cid'), + // verify there is not a block between requester & subject + if (viewer !== null) { + const blocks = await graphService.getBlockState([[viewer, actorDid]]) + if (blocks.blocking([viewer, actorDid])) { + throw new InvalidRequestError( + `Requester has blocked actor: ${actor}`, + 'BlockedActor', ) + } + if (blocks.blockedBy([viewer, actorDid])) { + throw new InvalidRequestError( + `Requester is blocked by actor: $${actor}`, + 'BlockedByActor', + ) + } + } - feedItemsQb = paginate(feedItemsQb, { - limit, - cursor, - keyset, - }) + // defaults to posts, reposts, and replies + let feedItemsQb = feedService + .selectFeedItemQb() + .where('originatorDid', '=', actorDid) - const feedItems = await feedItemsQb.execute() - const feed = await feedService.hydrateFeed(feedItems, viewer) + if (filter === 'posts_with_media') { + feedItemsQb = feedItemsQb + // and only your own posts/reposts + .where('post.creator', '=', actorDid) + // only posts with media + .whereExists((qb) => + qb + .selectFrom('post_embed_image') + .select('post_embed_image.postUri') + .whereRef('post_embed_image.postUri', '=', 'feed_item.postUri'), + ) + } else if (filter === 'posts_no_replies') { + feedItemsQb = feedItemsQb.where((qb) => + qb.where('post.replyParent', 'is', null).orWhere('type', '=', 'repost'), + ) + } - return { - encoding: 'application/json', - body: { - feed, - cursor: keyset.packFromResult(feedItems), - }, - } - }, + const keyset = new FeedKeyset(ref('feed_item.sortAt'), ref('feed_item.cid')) + + feedItemsQb = paginate(feedItemsQb, { + limit, + cursor, + keyset, + }) + + const feedItems = await feedItemsQb.execute() + + return { + params, + feedItems, + cursor: keyset.packFromResult(feedItems), + } +} + +const hydration = async (state: SkeletonState, ctx: Context) => { + const { feedService } = ctx + const { params, feedItems } = state + const refs = feedService.feedItemRefs(feedItems) + const hydrated = await feedService.feedHydration({ + ...refs, + viewer: params.viewer, + }) + return { ...state, ...hydrated } +} + +const noBlocksOrMutedReposts = (state: HydrationState) => { + const { viewer } = state.params + state.feedItems = state.feedItems.filter((item) => { + if (!viewer) return true + return ( + !state.bam.block([viewer, item.postAuthorDid]) && + (item.type === 'post' || !state.bam.mute([viewer, item.postAuthorDid])) + ) }) + return state } + +const presentation = (state: HydrationState, ctx: Context) => { + const { feedService } = ctx + const { feedItems, cursor, params } = state + const feed = feedService.views.formatFeed(feedItems, state, { + viewer: params.viewer, + }) + return { feed, cursor } +} + +type Context = { + db: Database + actorService: ActorService + feedService: FeedService + graphService: GraphService +} + +type Params = QueryParams & { viewer: string | null } + +type SkeletonState = { + params: Params + feedItems: FeedRow[] + cursor?: string +} + +type HydrationState = SkeletonState & FeedHydrationState diff --git a/packages/bsky/src/api/app/bsky/feed/getFeed.ts b/packages/bsky/src/api/app/bsky/feed/getFeed.ts index a2d49203e3b..8af159decd3 100644 --- a/packages/bsky/src/api/app/bsky/feed/getFeed.ts +++ b/packages/bsky/src/api/app/bsky/feed/getFeed.ts @@ -11,45 +11,47 @@ import { getFeedGen, } from '@atproto/identity' import { AtpAgent, AppBskyFeedGetFeedSkeleton } from '@atproto/api' -import { SkeletonFeedPost } from '../../../../lexicon/types/app/bsky/feed/defs' import { QueryParams as GetFeedParams } from '../../../../lexicon/types/app/bsky/feed/getFeed' import { OutputSchema as SkeletonOutput } from '../../../../lexicon/types/app/bsky/feed/getFeedSkeleton' +import { SkeletonFeedPost } from '../../../../lexicon/types/app/bsky/feed/defs' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { FeedRow } from '../../../../services/types' import { AlgoResponse } from '../../../../feed-gen/types' +import { Database } from '../../../../db' +import { + FeedHydrationState, + FeedRow, + FeedService, +} from '../../../../services/feed' +import { createPipeline } from '../../../../pipeline' export default function (server: Server, ctx: AppContext) { + const getFeed = createPipeline( + skeleton, + hydration, + noBlocksOrMutes, + presentation, + ) server.app.bsky.feed.getFeed({ auth: ctx.authVerifierAnyAudience, handler: async ({ params, auth, req }) => { - const { feed } = params + const db = ctx.db.getReplica() + const feedService = ctx.services.feed(db) const viewer = auth.credentials.did - const feedService = ctx.services.feed(ctx.db) - const localAlgo = ctx.algos[feed] - - const timerSkele = new ServerTimer('skele').start() - const { feedItems, ...rest } = - localAlgo !== undefined - ? await localAlgo(ctx, params, viewer) - : await skeletonFromFeedGen( - ctx, - params, - viewer, - req.headers['authorization'], - ) - timerSkele.stop() - - const timerHydr = new ServerTimer('hydr').start() - const hydrated = await feedService.hydrateFeed(feedItems, viewer) - timerHydr.stop() + + const { timerSkele, timerHydr, ...result } = await getFeed( + { ...params, viewer }, + { + db, + feedService, + appCtx: ctx, + authorization: req.headers['authorization'], + }, + ) return { encoding: 'application/json', - body: { - ...rest, - feed: hydrated, - }, + body: result, headers: { 'server-timing': serverTimingHeader([timerSkele, timerHydr]), }, @@ -58,15 +60,97 @@ export default function (server: Server, ctx: AppContext) { }) } -async function skeletonFromFeedGen( - ctx: AppContext, +const skeleton = async ( + params: Params, + ctx: Context, +): Promise => { + const timerSkele = new ServerTimer('skele').start() + const localAlgo = ctx.appCtx.algos[params.feed] + const feedParams: GetFeedParams = { + feed: params.feed, + limit: params.limit, + cursor: params.cursor, + } + const { feedItems, cursor, ...passthrough } = + localAlgo !== undefined + ? await localAlgo(ctx.appCtx, params, params.viewer) + : await skeletonFromFeedGen(ctx, feedParams) + return { + params, + cursor, + feedItems, + timerSkele: timerSkele.stop(), + passthrough, + } +} + +const hydration = async (state: SkeletonState, ctx: Context) => { + const timerHydr = new ServerTimer('hydr').start() + const { feedService } = ctx + const { params, feedItems } = state + const refs = feedService.feedItemRefs(feedItems) + const hydrated = await feedService.feedHydration({ + ...refs, + viewer: params.viewer, + }) + return { ...state, ...hydrated, timerHydr: timerHydr.stop() } +} + +const noBlocksOrMutes = (state: HydrationState) => { + const { viewer } = state.params + state.feedItems = state.feedItems.filter( + (item) => + !state.bam.block([viewer, item.postAuthorDid]) && + !state.bam.block([viewer, item.originatorDid]) && + !state.bam.mute([viewer, item.postAuthorDid]) && + !state.bam.mute([viewer, item.originatorDid]), + ) + return state +} + +const presentation = (state: HydrationState, ctx: Context) => { + const { feedService } = ctx + const { feedItems, cursor, passthrough, params } = state + const feed = feedService.views.formatFeed(feedItems, state, { + viewer: params.viewer, + }) + return { + feed, + cursor, + timerSkele: state.timerSkele, + timerHydr: state.timerHydr, + ...passthrough, + } +} + +type Context = { + db: Database + feedService: FeedService + appCtx: AppContext + authorization?: string +} + +type Params = GetFeedParams & { viewer: string } + +type SkeletonState = { + params: Params + feedItems: FeedRow[] + passthrough: Record // pass through additional items in feedgen response + cursor?: string + timerSkele: ServerTimer +} + +type HydrationState = SkeletonState & + FeedHydrationState & { feedItems: FeedRow[]; timerHydr: ServerTimer } + +const skeletonFromFeedGen = async ( + ctx: Context, params: GetFeedParams, - viewer: string, - authorization?: string, -): Promise { +): Promise => { + const { db, appCtx, authorization } = ctx const { feed } = params // Resolve and fetch feed skeleton - const found = await ctx.db.db + const found = await db.db .selectFrom('feed_generator') .where('uri', '=', feed) .select('feedDid') @@ -78,7 +162,7 @@ async function skeletonFromFeedGen( let resolved: DidDocument | null try { - resolved = await ctx.idResolver.did.resolve(feedDid) + resolved = await appCtx.idResolver.did.resolve(feedDid) } catch (err) { if (err instanceof PoorlyFormattedDidDocumentError) { throw new InvalidRequestError(`invalid did document: ${feedDid}`) @@ -102,7 +186,7 @@ async function skeletonFromFeedGen( try { // @TODO currently passthrough auth headers from pds const headers: Record = authorization - ? { authorization } + ? { authorization: authorization } : {} const result = await agent.api.app.bsky.feed.getFeedSkeleton(params, { headers, @@ -126,69 +210,34 @@ async function skeletonFromFeedGen( throw err } - const { feed: skeletonFeed, ...rest } = skeleton - - // Hydrate feed skeleton - const { ref } = ctx.db.db.dynamic - const feedService = ctx.services.feed(ctx.db) - const graphService = ctx.services.graph(ctx.db) - const feedItemUris = skeletonFeed.map(getSkeleFeedItemUri) - - // @TODO apply mutes and blocks - const feedItems = feedItemUris.length - ? await feedService - .selectFeedItemQb() - .where('feed_item.uri', 'in', feedItemUris) - .where((qb) => - // Hide posts and reposts of or by muted actors - graphService.whereNotMuted(qb, viewer, [ - ref('post.creator'), - ref('originatorDid'), - ]), - ) - .whereNotExists( - graphService.blockQb(viewer, [ - ref('post.creator'), - ref('originatorDid'), - ]), - ) - .execute() - : [] + const { feed: feedSkele, ...skele } = skeleton + const feedItems = await skeletonToFeedItems( + feedSkele.slice(0, params.limit), + ctx, + ) - const orderedItems = getOrderedFeedItems(skeletonFeed, feedItems, params) - return { - ...rest, - feedItems: orderedItems, - } + return { ...skele, feedItems } } -function getSkeleFeedItemUri(item: SkeletonFeedPost) { - if (typeof item.reason?.repost === 'string') { - return item.reason.repost +const skeletonToFeedItems = async ( + skeleton: SkeletonFeedPost[], + ctx: Context, +): Promise => { + const { feedService } = ctx + const feedItemUris = skeleton.map(getSkeleFeedItemUri) + const feedItemsRaw = await feedService.getFeedItems(feedItemUris) + const results: FeedRow[] = [] + for (const skeleItem of skeleton) { + const feedItem = feedItemsRaw[getSkeleFeedItemUri(skeleItem)] + if (feedItem && feedItem.postUri === skeleItem.post) { + results.push(feedItem) + } } - return item.post + return results } -function getOrderedFeedItems( - skeletonItems: SkeletonFeedPost[], - feedItems: FeedRow[], - params: GetFeedParams, -) { - const SKIP = [] - const feedItemsByUri = feedItems.reduce((acc, item) => { - return Object.assign(acc, { [item.uri]: item }) - }, {} as Record) - // enforce limit param in the case that the feedgen does not - if (skeletonItems.length > params.limit) { - skeletonItems = skeletonItems.slice(0, params.limit) - } - return skeletonItems.flatMap((item) => { - const uri = getSkeleFeedItemUri(item) - const feedItem = feedItemsByUri[uri] - if (!feedItem || item.post !== feedItem.postUri) { - // Couldn't find the record, or skeleton repost referenced the wrong post - return SKIP - } - return feedItem - }) +const getSkeleFeedItemUri = (item: SkeletonFeedPost) => { + return typeof item.reason?.repost === 'string' + ? item.reason.repost + : item.post } diff --git a/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts b/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts index 01f14b9349a..6207ba1e1aa 100644 --- a/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts +++ b/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts @@ -14,9 +14,11 @@ export default function (server: Server, ctx: AppContext) { const { feed } = params const viewer = auth.credentials.did - const feedService = ctx.services.feed(ctx.db) + const db = ctx.db.getReplica() + const feedService = ctx.services.feed(db) + const actorService = ctx.services.actor(db) - const got = await feedService.getFeedGeneratorViews([feed], viewer) + const got = await feedService.getFeedGeneratorInfos([feed], viewer) const feedInfo = got[feed] if (!feedInfo) { throw new InvalidRequestError('could not find feed') @@ -45,7 +47,7 @@ export default function (server: Server, ctx: AppContext) { ) } - const profiles = await feedService.getActorViews( + const profiles = await actorService.views.profilesBasic( [feedInfo.creator], viewer, ) diff --git a/packages/bsky/src/api/app/bsky/feed/getFeedGenerators.ts b/packages/bsky/src/api/app/bsky/feed/getFeedGenerators.ts index ee19a1050d2..a973ee6c2fb 100644 --- a/packages/bsky/src/api/app/bsky/feed/getFeedGenerators.ts +++ b/packages/bsky/src/api/app/bsky/feed/getFeedGenerators.ts @@ -1,31 +1,79 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { FeedGenInfo, FeedService } from '../../../../services/feed' +import { createPipeline, noRules } from '../../../../pipeline' +import { ActorInfoMap, ActorService } from '../../../../services/actor' +import { Database } from '../../../../db' export default function (server: Server, ctx: AppContext) { + const getFeedGenerators = createPipeline( + skeleton, + hydration, + noRules, + presentation, + ) server.app.bsky.feed.getFeedGenerators({ auth: ctx.authOptionalVerifier, handler: async ({ params, auth }) => { const { feeds } = params - const requester = auth.credentials.did + const viewer = auth.credentials.did + const db = ctx.db.getReplica() + const feedService = ctx.services.feed(db) + const actorService = ctx.services.actor(db) - const feedService = ctx.services.feed(ctx.db) - - const genViews = await feedService.getFeedGeneratorViews(feeds, requester) - const genList = Object.values(genViews) - - const creators = genList.map((gen) => gen.creator) - const profiles = await feedService.getActorViews(creators, requester) - - const feedViews = genList.map((gen) => - feedService.views.formatFeedGeneratorView(gen, profiles), + const view = await getFeedGenerators( + { feeds, viewer }, + { db, feedService, actorService }, ) return { encoding: 'application/json', - body: { - feeds: feedViews, - }, + body: view, } }, }) } + +const skeleton = async (params: Params, ctx: Context) => { + const { feedService } = ctx + const genInfos = await feedService.getFeedGeneratorInfos( + params.feeds, + params.viewer, + ) + return { + params, + generators: Object.values(genInfos), + } +} + +const hydration = async (state: SkeletonState, ctx: Context) => { + const { actorService } = ctx + const profiles = await actorService.views.profilesBasic( + state.generators.map((gen) => gen.creator), + state.params.viewer, + ) + return { + ...state, + profiles, + } +} + +const presentation = (state: HydrationState, ctx: Context) => { + const { feedService } = ctx + const feeds = state.generators.map((gen) => + feedService.views.formatFeedGeneratorView(gen, state.profiles), + ) + return { feeds } +} + +type Context = { + db: Database + feedService: FeedService + actorService: ActorService +} + +type Params = { viewer: string | null; feeds: string[] } + +type SkeletonState = { params: Params; generators: FeedGenInfo[] } + +type HydrationState = SkeletonState & { profiles: ActorInfoMap } diff --git a/packages/bsky/src/api/app/bsky/feed/getFeedSkeleton.ts b/packages/bsky/src/api/app/bsky/feed/getFeedSkeleton.ts new file mode 100644 index 00000000000..5d65044f86f --- /dev/null +++ b/packages/bsky/src/api/app/bsky/feed/getFeedSkeleton.ts @@ -0,0 +1,30 @@ +import { InvalidRequestError } from '@atproto/xrpc-server' +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { toSkeletonItem } from '../../../../feed-gen/types' + +export default function (server: Server, ctx: AppContext) { + server.app.bsky.feed.getFeedSkeleton({ + auth: ctx.authVerifierAnyAudience, + handler: async ({ params, auth }) => { + const { feed } = params + const viewer = auth.credentials.did + const localAlgo = ctx.algos[feed] + + if (!localAlgo) { + throw new InvalidRequestError('Unknown feed', 'UnknownFeed') + } + + const result = await localAlgo(ctx, params, viewer) + + return { + encoding: 'application/json', + body: { + // @TODO should we proactively filter blocks/mutes from the skeleton, or treat this similar to other custom feeds? + feed: result.feedItems.map(toSkeletonItem), + cursor: result.cursor, + }, + } + }, + }) +} diff --git a/packages/bsky/src/api/app/bsky/feed/getLikes.ts b/packages/bsky/src/api/app/bsky/feed/getLikes.ts index 4476b3bbd2c..893617f6bb0 100644 --- a/packages/bsky/src/api/app/bsky/feed/getLikes.ts +++ b/packages/bsky/src/api/app/bsky/feed/getLikes.ts @@ -1,57 +1,127 @@ +import { mapDefined } from '@atproto/common' import { Server } from '../../../../lexicon' +import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getLikes' import { paginate, TimeCidKeyset } from '../../../../db/pagination' import AppContext from '../../../../context' import { notSoftDeletedClause } from '../../../../db/util' +import { BlockAndMuteState, GraphService } from '../../../../services/graph' +import { ActorInfoMap, ActorService } from '../../../../services/actor' +import { Actor } from '../../../../db/tables/actor' +import { Database } from '../../../../db' +import { createPipeline } from '../../../../pipeline' export default function (server: Server, ctx: AppContext) { + const getLikes = createPipeline(skeleton, hydration, noBlocks, presentation) server.app.bsky.feed.getLikes({ auth: ctx.authOptionalVerifier, handler: async ({ params, auth }) => { - const { uri, limit, cursor, cid } = params - const requester = auth.credentials.did - const { services, db } = ctx - const { ref } = db.db.dynamic - - let builder = db.db - .selectFrom('like') - .where('like.subject', '=', uri) - .innerJoin('actor as creator', 'creator.did', 'like.creator') - .where(notSoftDeletedClause(ref('creator'))) - .selectAll('creator') - .select([ - 'like.cid as cid', - 'like.createdAt as createdAt', - 'like.indexedAt as indexedAt', - 'like.sortAt as sortAt', - ]) - - if (cid) { - builder = builder.where('like.subjectCid', '=', cid) - } - - const keyset = new TimeCidKeyset(ref('like.sortAt'), ref('like.cid')) - builder = paginate(builder, { - limit, - cursor, - keyset, - }) + const db = ctx.db.getReplica() + const actorService = ctx.services.actor(db) + const graphService = ctx.services.graph(db) + const viewer = auth.credentials.did - const likesRes = await builder.execute() - const actors = await services.actor(db).views.profile(likesRes, requester) + const result = await getLikes( + { ...params, viewer }, + { db, actorService, graphService }, + ) return { encoding: 'application/json', - body: { - uri, - cid, - cursor: keyset.packFromResult(likesRes), - likes: likesRes.map((row, i) => ({ - createdAt: row.createdAt, - indexedAt: row.indexedAt, - actor: actors[i], - })), - }, + body: result, } }, }) } + +const skeleton = async ( + params: Params, + ctx: Context, +): Promise => { + const { db } = ctx + const { uri, cid, limit, cursor } = params + const { ref } = db.db.dynamic + + let builder = db.db + .selectFrom('like') + .where('like.subject', '=', uri) + .innerJoin('actor as creator', 'creator.did', 'like.creator') + .where(notSoftDeletedClause(ref('creator'))) + .selectAll('creator') + .select([ + 'like.cid as cid', + 'like.createdAt as createdAt', + 'like.indexedAt as indexedAt', + 'like.sortAt as sortAt', + ]) + + if (cid) { + builder = builder.where('like.subjectCid', '=', cid) + } + + const keyset = new TimeCidKeyset(ref('like.sortAt'), ref('like.cid')) + builder = paginate(builder, { + limit, + cursor, + keyset, + }) + + const likes = await builder.execute() + + return { params, likes, cursor: keyset.packFromResult(likes) } +} + +const hydration = async (state: SkeletonState, ctx: Context) => { + const { graphService, actorService } = ctx + const { params, likes } = state + const { viewer } = params + const [actors, bam] = await Promise.all([ + actorService.views.profiles(likes, viewer), + graphService.getBlockAndMuteState( + viewer ? likes.map((like) => [viewer, like.did]) : [], + ), + ]) + return { ...state, bam, actors } +} + +const noBlocks = (state: HydrationState) => { + const { viewer } = state.params + if (!viewer) return state + state.likes = state.likes.filter( + (item) => !state.bam.block([viewer, item.did]), + ) + return state +} + +const presentation = (state: HydrationState) => { + const { params, likes, actors, cursor } = state + const { uri, cid } = params + const likesView = mapDefined(likes, (like) => + actors[like.did] + ? { + createdAt: like.createdAt, + indexedAt: like.indexedAt, + actor: actors[like.did], + } + : undefined, + ) + return { likes: likesView, cursor, uri, cid } +} + +type Context = { + db: Database + actorService: ActorService + graphService: GraphService +} + +type Params = QueryParams & { viewer: string | null } + +type SkeletonState = { + params: Params + likes: (Actor & { createdAt: string })[] + cursor?: string +} + +type HydrationState = SkeletonState & { + bam: BlockAndMuteState + actors: ActorInfoMap +} diff --git a/packages/bsky/src/api/app/bsky/feed/getPostThread.ts b/packages/bsky/src/api/app/bsky/feed/getPostThread.ts index ce019bef047..2d10ff98006 100644 --- a/packages/bsky/src/api/app/bsky/feed/getPostThread.ts +++ b/packages/bsky/src/api/app/bsky/feed/getPostThread.ts @@ -1,86 +1,106 @@ import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' -import AppContext from '../../../../context' -import { - ActorViewMap, - FeedEmbeds, - FeedRow, - PostInfoMap, -} from '../../../../services/types' -import { FeedService } from '../../../../services/feed' -import { Labels } from '../../../../services/label' import { BlockedPost, NotFoundPost, ThreadViewPost, isNotFoundPost, } from '../../../../lexicon/types/app/bsky/feed/defs' - -export type PostThread = { - post: FeedRow - parent?: PostThread | ParentNotFoundError - replies?: PostThread[] -} +import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getPostThread' +import AppContext from '../../../../context' +import { + FeedService, + FeedRow, + FeedHydrationState, +} from '../../../../services/feed' +import { + getAncestorsAndSelfQb, + getDescendentsQb, +} from '../../../../services/util/post' +import { Database } from '../../../../db' +import { setRepoRev } from '../../../util' +import { createPipeline, noRules } from '../../../../pipeline' +import { ActorInfoMap, ActorService } from '../../../../services/actor' export default function (server: Server, ctx: AppContext) { + const getPostThread = createPipeline( + skeleton, + hydration, + noRules, // handled in presentation: 3p block-violating replies are turned to #blockedPost, viewer blocks turned to #notFoundPost. + presentation, + ) server.app.bsky.feed.getPostThread({ auth: ctx.authOptionalVerifier, - handler: async ({ params, auth }) => { - const { uri, depth, parentHeight } = params - const requester = auth.credentials.did - - const feedService = ctx.services.feed(ctx.db) - const labelService = ctx.services.label(ctx.db) - - const threadData = await getThreadData( - feedService, - uri, - depth, - parentHeight, - ) - if (!threadData) { - throw new InvalidRequestError(`Post not found: ${uri}`, 'NotFound') - } - const relevant = getRelevantIds(threadData) - const [actors, posts, embeds, labels] = await Promise.all([ - feedService.getActorViews(Array.from(relevant.dids), requester, { - skipLabels: true, - }), - feedService.getPostViews(Array.from(relevant.uris), requester), - feedService.embedsForPosts(Array.from(relevant.uris), requester), - labelService.getLabelsForSubjects([...relevant.uris, ...relevant.dids]), - ]) + handler: async ({ params, auth, res }) => { + const viewer = auth.credentials.did + const db = ctx.db.getReplica('thread') + const feedService = ctx.services.feed(db) + const actorService = ctx.services.actor(db) - const thread = composeThread( - threadData, - feedService, - posts, - actors, - embeds, - labels, - ) + const [result, repoRev] = await Promise.allSettled([ + getPostThread({ ...params, viewer }, { db, feedService, actorService }), + actorService.getRepoRev(viewer), + ]) - if (isNotFoundPost(thread)) { - // @TODO technically this could be returned as a NotFoundPost based on lexicon - throw new InvalidRequestError(`Post not found: ${uri}`, 'NotFound') + if (repoRev.status === 'fulfilled') { + setRepoRev(res, repoRev.value) + } + if (result.status === 'rejected') { + throw result.reason } return { encoding: 'application/json', - body: { thread }, + body: result.value, } }, }) } +const skeleton = async (params: Params, ctx: Context) => { + const threadData = await getThreadData(params, ctx) + if (!threadData) { + throw new InvalidRequestError(`Post not found: ${params.uri}`, 'NotFound') + } + return { params, threadData } +} + +const hydration = async (state: SkeletonState, ctx: Context) => { + const { feedService } = ctx + const { + threadData, + params: { viewer }, + } = state + const relevant = getRelevantIds(threadData) + const hydrated = await feedService.feedHydration({ ...relevant, viewer }) + return { ...state, ...hydrated } +} + +const presentation = (state: HydrationState, ctx: Context) => { + const { params, profiles } = state + const { actorService } = ctx + const actors = actorService.views.profileBasicPresentation( + Object.keys(profiles), + state, + { viewer: params.viewer }, + ) + const thread = composeThread(state.threadData, actors, state, ctx) + if (isNotFoundPost(thread)) { + // @TODO technically this could be returned as a NotFoundPost based on lexicon + throw new InvalidRequestError(`Post not found: ${params.uri}`, 'NotFound') + } + return { thread } +} + const composeThread = ( threadData: PostThread, - feedService: FeedService, - posts: PostInfoMap, - actors: ActorViewMap, - embeds: FeedEmbeds, - labels: Labels, + actors: ActorInfoMap, + state: HydrationState, + ctx: Context, ) => { + const { feedService } = ctx + const { posts, embeds, blocks, labels } = state + const post = feedService.views.formatPostView( threadData.post.postUri, actors, @@ -89,7 +109,7 @@ const composeThread = ( labels, ) - if (!post) { + if (!post || blocks[post.uri]?.reply) { return { $type: 'app.bsky.feed.defs#notFoundPost', uri: threadData.post.postUri, @@ -102,6 +122,15 @@ const composeThread = ( $type: 'app.bsky.feed.defs#blockedPost', uri: threadData.post.postUri, blocked: true, + author: { + did: post.author.did, + viewer: post.author.viewer + ? { + blockedBy: post.author.viewer?.blockedBy, + blocking: post.author.viewer?.blocking, + } + : undefined, + }, } } @@ -114,28 +143,14 @@ const composeThread = ( notFound: true, } } else { - parent = composeThread( - threadData.parent, - feedService, - posts, - actors, - embeds, - labels, - ) + parent = composeThread(threadData.parent, actors, state, ctx) } } let replies: (ThreadViewPost | NotFoundPost | BlockedPost)[] | undefined if (threadData.replies) { replies = threadData.replies.flatMap((reply) => { - const thread = composeThread( - reply, - feedService, - posts, - actors, - embeds, - labels, - ) + const thread = composeThread(reply, actors, state, ctx) // e.g. don't bother including #postNotFound reply placeholders for takedowns. either way matches api contract. const skip = [] return isNotFoundPost(thread) ? skip : thread @@ -173,24 +188,31 @@ const getRelevantIds = ( } const getThreadData = async ( - feedService: FeedService, - uri: string, - depth: number, - parentHeight: number, + params: Params, + ctx: Context, ): Promise => { + const { db, feedService } = ctx + const { uri, depth, parentHeight } = params + const [parents, children] = await Promise.all([ - feedService - .selectPostQb() - .innerJoin('post_hierarchy', 'post_hierarchy.ancestorUri', 'post.uri') - .where('post_hierarchy.uri', '=', uri) + getAncestorsAndSelfQb(db.db, { uri, parentHeight }) + .selectFrom('ancestor') + .innerJoin( + feedService.selectPostQb().as('post'), + 'post.uri', + 'ancestor.uri', + ) + .selectAll('post') .execute(), - feedService - .selectPostQb() - .innerJoin('post_hierarchy', 'post_hierarchy.uri', 'post.uri') - .where('post_hierarchy.uri', '!=', uri) - .where('post_hierarchy.ancestorUri', '=', uri) - .where('depth', '<=', depth) - .orderBy('post.createdAt', 'desc') + getDescendentsQb(db.db, { uri, depth }) + .selectFrom('descendent') + .innerJoin( + feedService.selectPostQb().as('post'), + 'post.uri', + 'descendent.uri', + ) + .selectAll('post') + .orderBy('sortAt', 'desc') .execute(), ]) const parentsByUri = parents.reduce((acc, parent) => { @@ -248,3 +270,24 @@ class ParentNotFoundError extends Error { super(`Parent not found: ${uri}`) } } + +type PostThread = { + post: FeedRow + parent?: PostThread | ParentNotFoundError + replies?: PostThread[] +} + +type Context = { + db: Database + feedService: FeedService + actorService: ActorService +} + +type Params = QueryParams & { viewer: string | null } + +type SkeletonState = { + params: Params + threadData: PostThread +} + +type HydrationState = SkeletonState & FeedHydrationState diff --git a/packages/bsky/src/api/app/bsky/feed/getPosts.ts b/packages/bsky/src/api/app/bsky/feed/getPosts.ts index 16dcbb1f3d9..fc35b203034 100644 --- a/packages/bsky/src/api/app/bsky/feed/getPosts.ts +++ b/packages/bsky/src/api/app/bsky/feed/getPosts.ts @@ -1,50 +1,96 @@ -import * as common from '@atproto/common' +import { dedupeStrs } from '@atproto/common' +import { AtUri } from '@atproto/syntax' import { Server } from '../../../../lexicon' +import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getPosts' import AppContext from '../../../../context' -import { AtUri } from '@atproto/uri' -import { PostView } from '@atproto/api/src/client/types/app/bsky/feed/defs' +import { Database } from '../../../../db' +import { FeedHydrationState, FeedService } from '../../../../services/feed' +import { createPipeline } from '../../../../pipeline' +import { ActorService } from '../../../../services/actor' export default function (server: Server, ctx: AppContext) { + const getPosts = createPipeline(skeleton, hydration, noBlocks, presentation) server.app.bsky.feed.getPosts({ auth: ctx.authOptionalVerifier, handler: async ({ params, auth }) => { - const requester = auth.credentials.did + const db = ctx.db.getReplica() + const feedService = ctx.services.feed(db) + const actorService = ctx.services.actor(db) + const viewer = auth.credentials.did - const feedService = ctx.services.feed(ctx.db) - const labelService = ctx.services.label(ctx.db) - - const uris = common.dedupeStrs(params.uris) - const dids = common.dedupeStrs( - params.uris.map((uri) => new AtUri(uri).hostname), + const results = await getPosts( + { ...params, viewer }, + { db, feedService, actorService }, ) - const [actors, postViews, embeds, labels] = await Promise.all([ - feedService.getActorViews(Array.from(dids), requester, { - skipLabels: true, - }), - feedService.getPostViews(Array.from(uris), requester), - feedService.embedsForPosts(Array.from(uris), requester), - labelService.getLabelsForSubjects([...uris, ...dids]), - ]) - - const posts: PostView[] = [] - for (const uri of uris) { - const post = feedService.views.formatPostView( - uri, - actors, - postViews, - embeds, - labels, - ) - if (post) { - posts.push(post) - } - } - return { encoding: 'application/json', - body: { posts }, + body: results, } }, }) } + +const skeleton = async (params: Params) => { + return { params, postUris: dedupeStrs(params.uris) } +} + +const hydration = async (state: SkeletonState, ctx: Context) => { + const { feedService } = ctx + const { params, postUris } = state + const uris = new Set(postUris) + const dids = new Set(postUris.map((uri) => new AtUri(uri).hostname)) + const hydrated = await feedService.feedHydration({ + uris, + dids, + viewer: params.viewer, + }) + return { ...state, ...hydrated } +} + +const noBlocks = (state: HydrationState) => { + const { viewer } = state.params + state.postUris = state.postUris.filter((uri) => { + const post = state.posts[uri] + if (!viewer || !post) return true + return !state.bam.block([viewer, post.creator]) + }) + return state +} + +const presentation = (state: HydrationState, ctx: Context) => { + const { feedService, actorService } = ctx + const { postUris, profiles, params } = state + const SKIP = [] + const actors = actorService.views.profileBasicPresentation( + Object.keys(profiles), + state, + { viewer: params.viewer }, + ) + const postViews = postUris.flatMap((uri) => { + const postView = feedService.views.formatPostView( + uri, + actors, + state.posts, + state.embeds, + state.labels, + ) + return postView ?? SKIP + }) + return { posts: postViews } +} + +type Context = { + db: Database + feedService: FeedService + actorService: ActorService +} + +type Params = QueryParams & { viewer: string | null } + +type SkeletonState = { + params: Params + postUris: string[] +} + +type HydrationState = SkeletonState & FeedHydrationState diff --git a/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts b/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts index c76e19b32b7..5ca5c452b63 100644 --- a/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts +++ b/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts @@ -1,50 +1,118 @@ +import { mapDefined } from '@atproto/common' import { Server } from '../../../../lexicon' +import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getRepostedBy' import { paginate, TimeCidKeyset } from '../../../../db/pagination' import AppContext from '../../../../context' import { notSoftDeletedClause } from '../../../../db/util' +import { Database } from '../../../../db' +import { ActorInfoMap, ActorService } from '../../../../services/actor' +import { BlockAndMuteState, GraphService } from '../../../../services/graph' +import { Actor } from '../../../../db/tables/actor' +import { createPipeline } from '../../../../pipeline' export default function (server: Server, ctx: AppContext) { + const getRepostedBy = createPipeline( + skeleton, + hydration, + noBlocks, + presentation, + ) server.app.bsky.feed.getRepostedBy({ auth: ctx.authOptionalVerifier, handler: async ({ params, auth }) => { - const { uri, limit, cursor, cid } = params - const requester = auth.credentials.did - const { services, db } = ctx - const { ref } = db.db.dynamic - - let builder = db.db - .selectFrom('repost') - .where('repost.subject', '=', uri) - .innerJoin('actor as creator', 'creator.did', 'repost.creator') - .where(notSoftDeletedClause(ref('creator'))) - .selectAll('creator') - .select(['repost.cid as cid', 'repost.sortAt as sortAt']) - - if (cid) { - builder = builder.where('repost.subjectCid', '=', cid) - } - - const keyset = new TimeCidKeyset(ref('repost.sortAt'), ref('repost.cid')) - builder = paginate(builder, { - limit, - cursor, - keyset, - }) + const db = ctx.db.getReplica() + const actorService = ctx.services.actor(db) + const graphService = ctx.services.graph(db) + const viewer = auth.credentials.did - const repostedByRes = await builder.execute() - const repostedBy = await services - .actor(db) - .views.profile(repostedByRes, requester) + const result = await getRepostedBy( + { ...params, viewer }, + { db, actorService, graphService }, + ) return { encoding: 'application/json', - body: { - uri, - cid, - repostedBy, - cursor: keyset.packFromResult(repostedByRes), - }, + body: result, } }, }) } + +const skeleton = async ( + params: Params, + ctx: Context, +): Promise => { + const { db } = ctx + const { limit, cursor, uri, cid } = params + const { ref } = db.db.dynamic + + let builder = db.db + .selectFrom('repost') + .where('repost.subject', '=', uri) + .innerJoin('actor as creator', 'creator.did', 'repost.creator') + .where(notSoftDeletedClause(ref('creator'))) + .selectAll('creator') + .select(['repost.cid as cid', 'repost.sortAt as sortAt']) + + if (cid) { + builder = builder.where('repost.subjectCid', '=', cid) + } + + const keyset = new TimeCidKeyset(ref('repost.sortAt'), ref('repost.cid')) + builder = paginate(builder, { + limit, + cursor, + keyset, + }) + + const repostedBy = await builder.execute() + return { params, repostedBy, cursor: keyset.packFromResult(repostedBy) } +} + +const hydration = async (state: SkeletonState, ctx: Context) => { + const { graphService, actorService } = ctx + const { params, repostedBy } = state + const { viewer } = params + const [actors, bam] = await Promise.all([ + actorService.views.profiles(repostedBy, viewer), + graphService.getBlockAndMuteState( + viewer ? repostedBy.map((item) => [viewer, item.did]) : [], + ), + ]) + return { ...state, bam, actors } +} + +const noBlocks = (state: HydrationState) => { + const { viewer } = state.params + if (!viewer) return state + state.repostedBy = state.repostedBy.filter( + (item) => !state.bam.block([viewer, item.did]), + ) + return state +} + +const presentation = (state: HydrationState) => { + const { params, repostedBy, actors, cursor } = state + const { uri, cid } = params + const repostedByView = mapDefined(repostedBy, (item) => actors[item.did]) + return { repostedBy: repostedByView, cursor, uri, cid } +} + +type Context = { + db: Database + actorService: ActorService + graphService: GraphService +} + +type Params = QueryParams & { viewer: string | null } + +type SkeletonState = { + params: Params + repostedBy: Actor[] + cursor?: string +} + +type HydrationState = SkeletonState & { + bam: BlockAndMuteState + actors: ActorInfoMap +} diff --git a/packages/bsky/src/api/app/bsky/feed/getSuggestedFeeds.ts b/packages/bsky/src/api/app/bsky/feed/getSuggestedFeeds.ts new file mode 100644 index 00000000000..1ad65cdf756 --- /dev/null +++ b/packages/bsky/src/api/app/bsky/feed/getSuggestedFeeds.ts @@ -0,0 +1,38 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' + +export default function (server: Server, ctx: AppContext) { + server.app.bsky.feed.getSuggestedFeeds({ + auth: ctx.authOptionalVerifier, + handler: async ({ auth }) => { + const viewer = auth.credentials.did + + const db = ctx.db.getReplica() + const feedService = ctx.services.feed(db) + const actorService = ctx.services.actor(db) + const feedsRes = await db.db + .selectFrom('suggested_feed') + .orderBy('suggested_feed.order', 'asc') + .selectAll() + .execute() + const genInfos = await feedService.getFeedGeneratorInfos( + feedsRes.map((r) => r.uri), + viewer, + ) + const genList = feedsRes.map((r) => genInfos[r.uri]).filter(Boolean) + const creators = genList.map((gen) => gen.creator) + const profiles = await actorService.views.profilesBasic(creators, viewer) + + const feedViews = genList.map((gen) => + feedService.views.formatFeedGeneratorView(gen, profiles), + ) + + return { + encoding: 'application/json', + body: { + feeds: feedViews, + }, + } + }, + }) +} diff --git a/packages/bsky/src/api/app/bsky/feed/getTimeline.ts b/packages/bsky/src/api/app/bsky/feed/getTimeline.ts index 142283aacf3..9609ed6db42 100644 --- a/packages/bsky/src/api/app/bsky/feed/getTimeline.ts +++ b/packages/bsky/src/api/app/bsky/feed/getTimeline.ts @@ -3,73 +3,166 @@ import { Server } from '../../../../lexicon' import { FeedAlgorithm, FeedKeyset, getFeedDateThreshold } from '../util/feed' import { paginate } from '../../../../db/pagination' import AppContext from '../../../../context' +import { Database } from '../../../../db' +import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getTimeline' +import { setRepoRev } from '../../../util' +import { + FeedHydrationState, + FeedRow, + FeedService, +} from '../../../../services/feed' +import { createPipeline } from '../../../../pipeline' export default function (server: Server, ctx: AppContext) { + const getTimeline = createPipeline( + skeleton, + hydration, + noBlocksOrMutes, + presentation, + ) server.app.bsky.feed.getTimeline({ auth: ctx.authVerifier, - handler: async ({ params, auth }) => { - const { algorithm, limit, cursor } = params - const db = ctx.db.db - const { ref } = db.dynamic + handler: async ({ params, auth, res }) => { const viewer = auth.credentials.did + const db = ctx.db.getReplica('timeline') + const feedService = ctx.services.feed(db) + const actorService = ctx.services.actor(db) - if (algorithm && algorithm !== FeedAlgorithm.ReverseChronological) { - throw new InvalidRequestError(`Unsupported algorithm: ${algorithm}`) - } + const [result, repoRev] = await Promise.all([ + getTimeline({ ...params, viewer }, { db, feedService }), + actorService.getRepoRev(viewer), + ]) - const feedService = ctx.services.feed(ctx.db) - const graphService = ctx.services.graph(ctx.db) - - const followingIdsSubquery = db - .selectFrom('follow') - .select('follow.subjectDid') - .where('follow.creator', '=', viewer) - - const keyset = new FeedKeyset( - ref('feed_item.sortAt'), - ref('feed_item.cid'), - ) - const sortFrom = keyset.unpack(cursor)?.primary - - let feedItemsQb = feedService - .selectFeedItemQb() - .where((qb) => - qb - .where('originatorDid', '=', viewer) - .orWhere('originatorDid', 'in', followingIdsSubquery), - ) - .where((qb) => - // Hide posts and reposts of or by muted actors - graphService.whereNotMuted(qb, viewer, [ - ref('post.creator'), - ref('originatorDid'), - ]), - ) - .whereNotExists( - graphService.blockQb(viewer, [ - ref('post.creator'), - ref('originatorDid'), - ]), - ) - .where('feed_item.sortAt', '>', getFeedDateThreshold(sortFrom)) - - feedItemsQb = paginate(feedItemsQb, { - limit, - cursor, - keyset, - tryIndex: true, - }) - - const feedItems = await feedItemsQb.execute() - const feed = await feedService.hydrateFeed(feedItems, viewer) + setRepoRev(res, repoRev) return { encoding: 'application/json', - body: { - feed, - cursor: keyset.packFromResult(feedItems), - }, + body: result, } }, }) } + +export const skeleton = async ( + params: Params, + ctx: Context, +): Promise => { + const { cursor, limit, algorithm, viewer } = params + const { db } = ctx + const { ref } = db.db.dynamic + + if (algorithm && algorithm !== FeedAlgorithm.ReverseChronological) { + throw new InvalidRequestError(`Unsupported algorithm: ${algorithm}`) + } + + const keyset = new FeedKeyset(ref('feed_item.sortAt'), ref('feed_item.cid')) + const sortFrom = keyset.unpack(cursor)?.primary + + let followQb = db.db + .selectFrom('feed_item') + .innerJoin('follow', 'follow.subjectDid', 'feed_item.originatorDid') + .where('follow.creator', '=', viewer) + .innerJoin('post', 'post.uri', 'feed_item.postUri') + .where('feed_item.sortAt', '>', getFeedDateThreshold(sortFrom, 2)) + .selectAll('feed_item') + .select([ + 'post.replyRoot', + 'post.replyParent', + 'post.creator as postAuthorDid', + ]) + + followQb = paginate(followQb, { + limit, + cursor, + keyset, + tryIndex: true, + }) + + let selfQb = db.db + .selectFrom('feed_item') + .innerJoin('post', 'post.uri', 'feed_item.postUri') + .where('feed_item.originatorDid', '=', viewer) + .where('feed_item.sortAt', '>', getFeedDateThreshold(sortFrom, 2)) + .selectAll('feed_item') + .select([ + 'post.replyRoot', + 'post.replyParent', + 'post.creator as postAuthorDid', + ]) + + selfQb = paginate(selfQb, { + limit: Math.min(limit, 10), + cursor, + keyset, + tryIndex: true, + }) + + const [followRes, selfRes] = await Promise.all([ + followQb.execute(), + selfQb.execute(), + ]) + + const feedItems: FeedRow[] = [...followRes, ...selfRes] + .sort((a, b) => { + if (a.sortAt > b.sortAt) return -1 + if (a.sortAt < b.sortAt) return 1 + return a.cid > b.cid ? -1 : 1 + }) + .slice(0, limit) + + return { + params, + feedItems, + cursor: keyset.packFromResult(feedItems), + } +} + +const hydration = async ( + state: SkeletonState, + ctx: Context, +): Promise => { + const { feedService } = ctx + const { params, feedItems } = state + const refs = feedService.feedItemRefs(feedItems) + const hydrated = await feedService.feedHydration({ + ...refs, + viewer: params.viewer, + }) + return { ...state, ...hydrated } +} + +const noBlocksOrMutes = (state: HydrationState): HydrationState => { + const { viewer } = state.params + state.feedItems = state.feedItems.filter( + (item) => + !state.bam.block([viewer, item.postAuthorDid]) && + !state.bam.block([viewer, item.originatorDid]) && + !state.bam.mute([viewer, item.postAuthorDid]) && + !state.bam.mute([viewer, item.originatorDid]), + ) + return state +} + +const presentation = (state: HydrationState, ctx: Context) => { + const { feedService } = ctx + const { feedItems, cursor, params } = state + const feed = feedService.views.formatFeed(feedItems, state, { + viewer: params.viewer, + }) + return { feed, cursor } +} + +type Context = { + db: Database + feedService: FeedService +} + +type Params = QueryParams & { viewer: string } + +type SkeletonState = { + params: Params + feedItems: FeedRow[] + cursor?: string +} + +type HydrationState = SkeletonState & FeedHydrationState diff --git a/packages/bsky/src/api/app/bsky/graph/getBlocks.ts b/packages/bsky/src/api/app/bsky/graph/getBlocks.ts index e2142debfa5..66b809d70ce 100644 --- a/packages/bsky/src/api/app/bsky/graph/getBlocks.ts +++ b/packages/bsky/src/api/app/bsky/graph/getBlocks.ts @@ -9,10 +9,10 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth }) => { const { limit, cursor } = params const requester = auth.credentials.did - const { services, db } = ctx + const db = ctx.db.getReplica() const { ref } = db.db.dynamic - let blocksReq = ctx.db.db + let blocksReq = db.db .selectFrom('actor_block') .where('actor_block.creator', '=', requester) .innerJoin('actor as subject', 'subject.did', 'actor_block.subjectDid') @@ -32,8 +32,8 @@ export default function (server: Server, ctx: AppContext) { const blocksRes = await blocksReq.execute() - const actorService = services.actor(db) - const blocks = await actorService.views.profile(blocksRes, requester) + const actorService = ctx.services.actor(db) + const blocks = await actorService.views.profilesList(blocksRes, requester) return { encoding: 'application/json', diff --git a/packages/bsky/src/api/app/bsky/graph/getFollowers.ts b/packages/bsky/src/api/app/bsky/graph/getFollowers.ts index 6908d989af0..1382c1f87c7 100644 --- a/packages/bsky/src/api/app/bsky/graph/getFollowers.ts +++ b/packages/bsky/src/api/app/bsky/graph/getFollowers.ts @@ -1,54 +1,146 @@ +import { mapDefined } from '@atproto/common' import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' -import { paginate, TimeCidKeyset } from '../../../../db/pagination' +import { QueryParams } from '../../../../lexicon/types/app/bsky/graph/getFollowers' import AppContext from '../../../../context' +import { Database } from '../../../../db' import { notSoftDeletedClause } from '../../../../db/util' +import { paginate, TimeCidKeyset } from '../../../../db/pagination' +import { Actor } from '../../../../db/tables/actor' +import { ActorInfoMap, ActorService } from '../../../../services/actor' +import { BlockAndMuteState, GraphService } from '../../../../services/graph' +import { createPipeline } from '../../../../pipeline' export default function (server: Server, ctx: AppContext) { + const getFollowers = createPipeline( + skeleton, + hydration, + noBlocksInclInvalid, + presentation, + ) server.app.bsky.graph.getFollowers({ - auth: ctx.authOptionalVerifier, + auth: ctx.authOptionalAccessOrRoleVerifier, handler: async ({ params, auth }) => { - const { actor, limit, cursor } = params - const requester = auth.credentials.did - const { services, db } = ctx - const { ref } = db.db.dynamic + const db = ctx.db.getReplica() + const actorService = ctx.services.actor(db) + const graphService = ctx.services.graph(db) + const viewer = 'did' in auth.credentials ? auth.credentials.did : null + const canViewTakendownProfile = + auth.credentials.type === 'role' && auth.credentials.triage - const actorService = services.actor(db) - - const subjectRes = await actorService.getActor(actor) - if (!subjectRes) { - throw new InvalidRequestError(`Actor not found: ${actor}`) - } - - let followersReq = ctx.db.db - .selectFrom('follow') - .where('follow.subjectDid', '=', subjectRes.did) - .innerJoin('actor as creator', 'creator.did', 'follow.creator') - .where(notSoftDeletedClause(ref('creator'))) - .selectAll('creator') - .select(['follow.cid as cid', 'follow.sortAt as sortAt']) - - const keyset = new TimeCidKeyset(ref('follow.sortAt'), ref('follow.cid')) - followersReq = paginate(followersReq, { - limit, - cursor, - keyset, - }) - - const followersRes = await followersReq.execute() - const [followers, subject] = await Promise.all([ - actorService.views.profile(followersRes, requester), - actorService.views.profile(subjectRes, requester), - ]) + const result = await getFollowers( + { ...params, viewer, canViewTakendownProfile }, + { db, actorService, graphService }, + ) return { encoding: 'application/json', - body: { - subject, - followers, - cursor: keyset.packFromResult(followersRes), - }, + body: result, } }, }) } + +const skeleton = async ( + params: Params, + ctx: Context, +): Promise => { + const { db, actorService } = ctx + const { limit, cursor, actor, canViewTakendownProfile } = params + const { ref } = db.db.dynamic + + const subject = await actorService.getActor(actor, canViewTakendownProfile) + if (!subject) { + throw new InvalidRequestError(`Actor not found: ${actor}`) + } + + let followersReq = db.db + .selectFrom('follow') + .where('follow.subjectDid', '=', subject.did) + .innerJoin('actor as creator', 'creator.did', 'follow.creator') + .if(!canViewTakendownProfile, (qb) => + qb.where(notSoftDeletedClause(ref('creator'))), + ) + .selectAll('creator') + .select(['follow.cid as cid', 'follow.sortAt as sortAt']) + + const keyset = new TimeCidKeyset(ref('follow.sortAt'), ref('follow.cid')) + followersReq = paginate(followersReq, { + limit, + cursor, + keyset, + }) + + const followers = await followersReq.execute() + return { + params, + followers, + subject, + cursor: keyset.packFromResult(followers), + } +} + +const hydration = async (state: SkeletonState, ctx: Context) => { + const { graphService, actorService } = ctx + const { params, followers, subject } = state + const { viewer } = params + const [actors, bam] = await Promise.all([ + actorService.views.profiles([subject, ...followers], viewer), + graphService.getBlockAndMuteState( + followers.flatMap((item) => { + if (viewer) { + return [ + [viewer, item.did], + [subject.did, item.did], + ] + } + return [[subject.did, item.did]] + }), + ), + ]) + return { ...state, bam, actors } +} + +const noBlocksInclInvalid = (state: HydrationState) => { + const { subject } = state + const { viewer } = state.params + state.followers = state.followers.filter( + (item) => + !state.bam.block([subject.did, item.did]) && + (!viewer || !state.bam.block([viewer, item.did])), + ) + return state +} + +const presentation = (state: HydrationState) => { + const { params, followers, subject, actors, cursor } = state + const subjectView = actors[subject.did] + const followersView = mapDefined(followers, (item) => actors[item.did]) + if (!subjectView) { + throw new InvalidRequestError(`Actor not found: ${params.actor}`) + } + return { followers: followersView, subject: subjectView, cursor } +} + +type Context = { + db: Database + actorService: ActorService + graphService: GraphService +} + +type Params = QueryParams & { + viewer: string | null + canViewTakendownProfile: boolean +} + +type SkeletonState = { + params: Params + followers: Actor[] + subject: Actor + cursor?: string +} + +type HydrationState = SkeletonState & { + bam: BlockAndMuteState + actors: ActorInfoMap +} diff --git a/packages/bsky/src/api/app/bsky/graph/getFollows.ts b/packages/bsky/src/api/app/bsky/graph/getFollows.ts index 2db016da037..34b5d72a605 100644 --- a/packages/bsky/src/api/app/bsky/graph/getFollows.ts +++ b/packages/bsky/src/api/app/bsky/graph/getFollows.ts @@ -1,54 +1,147 @@ +import { mapDefined } from '@atproto/common' import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' -import { paginate, TimeCidKeyset } from '../../../../db/pagination' +import { QueryParams } from '../../../../lexicon/types/app/bsky/graph/getFollows' import AppContext from '../../../../context' +import { Database } from '../../../../db' import { notSoftDeletedClause } from '../../../../db/util' +import { paginate, TimeCidKeyset } from '../../../../db/pagination' +import { Actor } from '../../../../db/tables/actor' +import { ActorInfoMap, ActorService } from '../../../../services/actor' +import { BlockAndMuteState, GraphService } from '../../../../services/graph' +import { createPipeline } from '../../../../pipeline' export default function (server: Server, ctx: AppContext) { + const getFollows = createPipeline( + skeleton, + hydration, + noBlocksInclInvalid, + presentation, + ) server.app.bsky.graph.getFollows({ - auth: ctx.authOptionalVerifier, + auth: ctx.authOptionalAccessOrRoleVerifier, handler: async ({ params, auth }) => { - const { actor, limit, cursor } = params - const requester = auth.credentials.did - const { services, db } = ctx - const { ref } = db.db.dynamic + const db = ctx.db.getReplica() + const actorService = ctx.services.actor(db) + const graphService = ctx.services.graph(db) + const viewer = 'did' in auth.credentials ? auth.credentials.did : null + const canViewTakendownProfile = + auth.credentials.type === 'role' && auth.credentials.triage - const actorService = services.actor(db) - - const creatorRes = await actorService.getActor(actor) - if (!creatorRes) { - throw new InvalidRequestError(`Actor not found: ${actor}`) - } - - let followsReq = ctx.db.db - .selectFrom('follow') - .where('follow.creator', '=', creatorRes.did) - .innerJoin('actor as subject', 'subject.did', 'follow.subjectDid') - .where(notSoftDeletedClause(ref('subject'))) - .selectAll('subject') - .select(['follow.cid as cid', 'follow.sortAt as sortAt']) - - const keyset = new TimeCidKeyset(ref('follow.sortAt'), ref('follow.cid')) - followsReq = paginate(followsReq, { - limit, - cursor, - keyset, - }) - - const followsRes = await followsReq.execute() - const [follows, subject] = await Promise.all([ - actorService.views.profile(followsRes, requester), - actorService.views.profile(creatorRes, requester), - ]) + const result = await getFollows( + { ...params, viewer, canViewTakendownProfile }, + { db, actorService, graphService }, + ) return { encoding: 'application/json', - body: { - subject, - follows, - cursor: keyset.packFromResult(followsRes), - }, + body: result, } }, }) } + +const skeleton = async ( + params: Params, + ctx: Context, +): Promise => { + const { db, actorService } = ctx + const { limit, cursor, actor, canViewTakendownProfile } = params + const { ref } = db.db.dynamic + + const creator = await actorService.getActor(actor, canViewTakendownProfile) + if (!creator) { + throw new InvalidRequestError(`Actor not found: ${actor}`) + } + + let followsReq = db.db + .selectFrom('follow') + .where('follow.creator', '=', creator.did) + .innerJoin('actor as subject', 'subject.did', 'follow.subjectDid') + .if(!canViewTakendownProfile, (qb) => + qb.where(notSoftDeletedClause(ref('subject'))), + ) + .selectAll('subject') + .select(['follow.cid as cid', 'follow.sortAt as sortAt']) + + const keyset = new TimeCidKeyset(ref('follow.sortAt'), ref('follow.cid')) + followsReq = paginate(followsReq, { + limit, + cursor, + keyset, + }) + + const follows = await followsReq.execute() + + return { + params, + follows, + creator, + cursor: keyset.packFromResult(follows), + } +} + +const hydration = async (state: SkeletonState, ctx: Context) => { + const { graphService, actorService } = ctx + const { params, follows, creator } = state + const { viewer } = params + const [actors, bam] = await Promise.all([ + actorService.views.profiles([creator, ...follows], viewer), + graphService.getBlockAndMuteState( + follows.flatMap((item) => { + if (viewer) { + return [ + [viewer, item.did], + [creator.did, item.did], + ] + } + return [[creator.did, item.did]] + }), + ), + ]) + return { ...state, bam, actors } +} + +const noBlocksInclInvalid = (state: HydrationState) => { + const { creator } = state + const { viewer } = state.params + state.follows = state.follows.filter( + (item) => + !state.bam.block([creator.did, item.did]) && + (!viewer || !state.bam.block([viewer, item.did])), + ) + return state +} + +const presentation = (state: HydrationState) => { + const { params, follows, creator, actors, cursor } = state + const creatorView = actors[creator.did] + const followsView = mapDefined(follows, (item) => actors[item.did]) + if (!creatorView) { + throw new InvalidRequestError(`Actor not found: ${params.actor}`) + } + return { follows: followsView, subject: creatorView, cursor } +} + +type Context = { + db: Database + actorService: ActorService + graphService: GraphService +} + +type Params = QueryParams & { + viewer: string | null + canViewTakendownProfile: boolean +} + +type SkeletonState = { + params: Params + follows: Actor[] + creator: Actor + cursor?: string +} + +type HydrationState = SkeletonState & { + bam: BlockAndMuteState + actors: ActorInfoMap +} diff --git a/packages/bsky/src/api/app/bsky/graph/getList.ts b/packages/bsky/src/api/app/bsky/graph/getList.ts index 2814cf99e4e..1e6775d01cb 100644 --- a/packages/bsky/src/api/app/bsky/graph/getList.ts +++ b/packages/bsky/src/api/app/bsky/graph/getList.ts @@ -1,91 +1,126 @@ +import { mapDefined } from '@atproto/common' import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' -import { paginate, TimeCidKeyset } from '../../../../db/pagination' +import { QueryParams } from '../../../../lexicon/types/app/bsky/graph/getList' import AppContext from '../../../../context' -import { ProfileView } from '../../../../lexicon/types/app/bsky/actor/defs' +import { Database } from '../../../../db' +import { paginate, TimeCidKeyset } from '../../../../db/pagination' +import { Actor } from '../../../../db/tables/actor' +import { GraphService, ListInfo } from '../../../../services/graph' +import { ActorService, ProfileHydrationState } from '../../../../services/actor' +import { createPipeline, noRules } from '../../../../pipeline' export default function (server: Server, ctx: AppContext) { + const getList = createPipeline(skeleton, hydration, noRules, presentation) server.app.bsky.graph.getList({ auth: ctx.authOptionalVerifier, handler: async ({ params, auth }) => { - const { list, limit, cursor } = params - const requester = auth.credentials.did - const { services, db } = ctx - const { ref } = db.db.dynamic - - const graphService = ctx.services.graph(ctx.db) - - const listRes = await graphService - .getListsQb(requester) - .where('list.uri', '=', list) - .executeTakeFirst() - if (!listRes) { - throw new InvalidRequestError(`List not found: ${list}`) - } - - let itemsReq = graphService - .getListItemsQb() - .where('list_item.listUri', '=', list) - .where('list_item.creator', '=', listRes.creator) + const db = ctx.db.getReplica() + const graphService = ctx.services.graph(db) + const actorService = ctx.services.actor(db) + const viewer = auth.credentials.did - const keyset = new TimeCidKeyset( - ref('list_item.sortAt'), - ref('list_item.cid'), + const result = await getList( + { ...params, viewer }, + { db, graphService, actorService }, ) - itemsReq = paginate(itemsReq, { - limit, - cursor, - keyset, - }) - const itemsRes = await itemsReq.execute() - - const actorService = services.actor(db) - const profiles = await actorService.views.profile(itemsRes, requester) - const profilesMap = profiles.reduce( - (acc, cur) => ({ - ...acc, - [cur.did]: cur, - }), - {} as Record, - ) - - const items = itemsRes.map((item) => ({ - subject: profilesMap[item.did], - })) - - const creator = await actorService.views.profile(listRes, requester) - - const subject = { - uri: listRes.uri, - cid: listRes.cid, - creator, - name: listRes.name, - purpose: listRes.purpose, - description: listRes.description ?? undefined, - descriptionFacets: listRes.descriptionFacets - ? JSON.parse(listRes.descriptionFacets) - : undefined, - avatar: listRes.avatarCid - ? ctx.imgUriBuilder.getCommonSignedUri( - 'avatar', - listRes.creator, - listRes.avatarCid, - ) - : undefined, - indexedAt: listRes.indexedAt, - viewer: { - muted: !!listRes.viewerMuted, - }, - } return { encoding: 'application/json', - body: { - items, - list: subject, - cursor: keyset.packFromResult(itemsRes), - }, + body: result, } }, }) } + +const skeleton = async ( + params: Params, + ctx: Context, +): Promise => { + const { db, graphService } = ctx + const { list, limit, cursor, viewer } = params + const { ref } = db.db.dynamic + + const listRes = await graphService + .getListsQb(viewer) + .where('list.uri', '=', list) + .executeTakeFirst() + if (!listRes) { + throw new InvalidRequestError(`List not found: ${list}`) + } + + let itemsReq = graphService + .getListItemsQb() + .where('list_item.listUri', '=', list) + .where('list_item.creator', '=', listRes.creator) + + const keyset = new TimeCidKeyset( + ref('list_item.sortAt'), + ref('list_item.cid'), + ) + + itemsReq = paginate(itemsReq, { + limit, + cursor, + keyset, + }) + + const listItems = await itemsReq.execute() + + return { + params, + list: listRes, + listItems, + cursor: keyset.packFromResult(listItems), + } +} + +const hydration = async (state: SkeletonState, ctx: Context) => { + const { actorService } = ctx + const { params, list, listItems } = state + const profileState = await actorService.views.profileHydration( + [list, ...listItems].map((x) => x.did), + { viewer: params.viewer }, + ) + return { ...state, ...profileState } +} + +const presentation = (state: HydrationState, ctx: Context) => { + const { actorService, graphService } = ctx + const { params, list, listItems, cursor, ...profileState } = state + const actors = actorService.views.profilePresentation( + Object.keys(profileState.profiles), + profileState, + { viewer: params.viewer }, + ) + const creator = actors[list.creator] + if (!creator) { + throw new InvalidRequestError(`Actor not found: ${list.handle}`) + } + const listView = graphService.formatListView(list, actors) + const items = mapDefined(listItems, (item) => { + const subject = actors[item.did] + if (!subject) return + return { subject } + }) + return { list: listView, items, cursor } +} + +type Context = { + db: Database + actorService: ActorService + graphService: GraphService +} + +type Params = QueryParams & { + viewer: string | null +} + +type SkeletonState = { + params: Params + list: Actor & ListInfo + listItems: (Actor & { cid: string; sortAt: string })[] + cursor?: string +} + +type HydrationState = SkeletonState & ProfileHydrationState diff --git a/packages/bsky/src/api/app/bsky/graph/getListBlocks.ts b/packages/bsky/src/api/app/bsky/graph/getListBlocks.ts new file mode 100644 index 00000000000..0884005b244 --- /dev/null +++ b/packages/bsky/src/api/app/bsky/graph/getListBlocks.ts @@ -0,0 +1,114 @@ +import { Server } from '../../../../lexicon' +import { QueryParams } from '../../../../lexicon/types/app/bsky/graph/getListBlocks' +import { paginate, TimeCidKeyset } from '../../../../db/pagination' +import AppContext from '../../../../context' +import { Database } from '../../../../db' +import { Actor } from '../../../../db/tables/actor' +import { GraphService, ListInfo } from '../../../../services/graph' +import { ActorService, ProfileHydrationState } from '../../../../services/actor' +import { createPipeline, noRules } from '../../../../pipeline' + +export default function (server: Server, ctx: AppContext) { + const getListBlocks = createPipeline( + skeleton, + hydration, + noRules, + presentation, + ) + server.app.bsky.graph.getListBlocks({ + auth: ctx.authVerifier, + handler: async ({ params, auth }) => { + const db = ctx.db.getReplica() + const graphService = ctx.services.graph(db) + const actorService = ctx.services.actor(db) + const viewer = auth.credentials.did + + const result = await getListBlocks( + { ...params, viewer }, + { db, actorService, graphService }, + ) + + return { + encoding: 'application/json', + body: result, + } + }, + }) +} + +const skeleton = async ( + params: Params, + ctx: Context, +): Promise => { + const { db, graphService } = ctx + const { limit, cursor, viewer } = params + const { ref } = db.db.dynamic + + let listsReq = graphService + .getListsQb(viewer) + .whereExists( + db.db + .selectFrom('list_block') + .where('list_block.creator', '=', viewer) + .whereRef('list_block.subjectUri', '=', ref('list.uri')) + .selectAll(), + ) + + const keyset = new TimeCidKeyset(ref('list.createdAt'), ref('list.cid')) + + listsReq = paginate(listsReq, { + limit, + cursor, + keyset, + }) + + const listInfos = await listsReq.execute() + + return { + params, + listInfos, + cursor: keyset.packFromResult(listInfos), + } +} + +const hydration = async (state: SkeletonState, ctx: Context) => { + const { actorService } = ctx + const { params, listInfos } = state + const profileState = await actorService.views.profileHydration( + listInfos.map((list) => list.creator), + { viewer: params.viewer }, + ) + return { ...state, ...profileState } +} + +const presentation = (state: HydrationState, ctx: Context) => { + const { actorService, graphService } = ctx + const { params, listInfos, cursor, ...profileState } = state + const actors = actorService.views.profilePresentation( + Object.keys(profileState.profiles), + profileState, + { viewer: params.viewer }, + ) + const lists = listInfos.map((list) => + graphService.formatListView(list, actors), + ) + return { lists, cursor } +} + +type Context = { + db: Database + actorService: ActorService + graphService: GraphService +} + +type Params = QueryParams & { + viewer: string +} + +type SkeletonState = { + params: Params + listInfos: (Actor & ListInfo)[] + cursor?: string +} + +type HydrationState = SkeletonState & ProfileHydrationState diff --git a/packages/bsky/src/api/app/bsky/graph/getListMutes.ts b/packages/bsky/src/api/app/bsky/graph/getListMutes.ts index f8f353bcaf6..49d04b3233f 100644 --- a/packages/bsky/src/api/app/bsky/graph/getListMutes.ts +++ b/packages/bsky/src/api/app/bsky/graph/getListMutes.ts @@ -1,7 +1,6 @@ import { Server } from '../../../../lexicon' import { paginate, TimeCidKeyset } from '../../../../db/pagination' import AppContext from '../../../../context' -import { ProfileView } from '../../../../lexicon/types/app/bsky/actor/defs' export default function (server: Server, ctx: AppContext) { server.app.bsky.graph.getListMutes({ @@ -9,15 +8,15 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth }) => { const { limit, cursor } = params const requester = auth.credentials.did - const { db } = ctx + const db = ctx.db.getReplica() const { ref } = db.db.dynamic - const graphService = ctx.services.graph(ctx.db) + const graphService = ctx.services.graph(db) let listsReq = graphService .getListsQb(requester) .whereExists( - ctx.db.db + db.db .selectFrom('list_mute') .where('list_mute.mutedByDid', '=', requester) .whereRef('list_mute.listUri', '=', ref('list.uri')) @@ -32,18 +31,11 @@ export default function (server: Server, ctx: AppContext) { }) const listsRes = await listsReq.execute() - const actorService = ctx.services.actor(ctx.db) - const profiles = await actorService.views.profile(listsRes, requester) - const profilesMap = profiles.reduce( - (acc, cur) => ({ - ...acc, - [cur.did]: cur, - }), - {} as Record, - ) + const actorService = ctx.services.actor(db) + const profiles = await actorService.views.profiles(listsRes, requester) const lists = listsRes.map((row) => - graphService.formatListView(row, profilesMap), + graphService.formatListView(row, profiles), ) return { diff --git a/packages/bsky/src/api/app/bsky/graph/getLists.ts b/packages/bsky/src/api/app/bsky/graph/getLists.ts index b325a8a435b..e6ca61fa9c7 100644 --- a/packages/bsky/src/api/app/bsky/graph/getLists.ts +++ b/packages/bsky/src/api/app/bsky/graph/getLists.ts @@ -9,11 +9,11 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth }) => { const { actor, limit, cursor } = params const requester = auth.credentials.did - const { services, db } = ctx + const db = ctx.db.getReplica() const { ref } = db.db.dynamic - const actorService = services.actor(db) - const graphService = services.graph(db) + const actorService = ctx.services.actor(db) + const graphService = ctx.services.graph(db) const creatorRes = await actorService.getActor(actor) if (!creatorRes) { @@ -31,16 +31,16 @@ export default function (server: Server, ctx: AppContext) { keyset, }) - const [listsRes, creator] = await Promise.all([ + const [listsRes, profiles] = await Promise.all([ listsReq.execute(), - actorService.views.profile(creatorRes, requester), + actorService.views.profiles([creatorRes], requester), ]) - const profileMap = { - [creator.did]: creator, + if (!profiles[creatorRes.did]) { + throw new InvalidRequestError(`Actor not found: ${actor}`) } const lists = listsRes.map((row) => - graphService.formatListView(row, profileMap), + graphService.formatListView(row, profiles), ) return { diff --git a/packages/bsky/src/api/app/bsky/graph/getMutes.ts b/packages/bsky/src/api/app/bsky/graph/getMutes.ts index fa8b53f09d4..e69803d144a 100644 --- a/packages/bsky/src/api/app/bsky/graph/getMutes.ts +++ b/packages/bsky/src/api/app/bsky/graph/getMutes.ts @@ -9,10 +9,10 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth }) => { const { limit, cursor } = params const requester = auth.credentials.did - const { services, db } = ctx - const { ref } = ctx.db.db.dynamic + const db = ctx.db.getReplica() + const { ref } = db.db.dynamic - let mutesReq = ctx.db.db + let mutesReq = db.db .selectFrom('mute') .innerJoin('actor', 'actor.did', 'mute.subjectDid') .where(notSoftDeletedClause(ref('actor'))) @@ -32,13 +32,13 @@ export default function (server: Server, ctx: AppContext) { const mutesRes = await mutesReq.execute() - const actorService = services.actor(db) + const actorService = ctx.services.actor(db) return { encoding: 'application/json', body: { cursor: keyset.packFromResult(mutesRes), - mutes: await actorService.views.profile(mutesRes, requester), + mutes: await actorService.views.profilesList(mutesRes, requester), }, } }, diff --git a/packages/bsky/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts b/packages/bsky/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts new file mode 100644 index 00000000000..be42ce2b959 --- /dev/null +++ b/packages/bsky/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts @@ -0,0 +1,138 @@ +import { sql } from 'kysely' +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { InvalidRequestError } from '@atproto/xrpc-server' +import { Database } from '../../../../db' +import { ActorService } from '../../../../services/actor' + +const RESULT_LENGTH = 10 + +export default function (server: Server, ctx: AppContext) { + server.app.bsky.graph.getSuggestedFollowsByActor({ + auth: ctx.authVerifier, + handler: async ({ auth, params }) => { + const { actor } = params + const viewer = auth.credentials.did + + const db = ctx.db.getReplica() + const actorService = ctx.services.actor(db) + const actorDid = await actorService.getActorDid(actor) + + if (!actorDid) { + throw new InvalidRequestError('Actor not found') + } + + const skeleton = await getSkeleton( + { + actor: actorDid, + viewer, + }, + { + db, + actorService, + }, + ) + const hydrationState = await actorService.views.profileDetailHydration( + skeleton.map((a) => a.did), + { viewer }, + ) + const presentationState = actorService.views.profileDetailPresentation( + skeleton.map((a) => a.did), + hydrationState, + { viewer }, + ) + const suggestions = Object.values(presentationState).filter((profile) => { + return ( + !profile.viewer?.muted && + !profile.viewer?.mutedByList && + !profile.viewer?.blocking && + !profile.viewer?.blockedBy + ) + }) + + return { + encoding: 'application/json', + body: { suggestions }, + } + }, + }) +} + +async function getSkeleton( + params: { + actor: string + viewer: string + }, + ctx: { + db: Database + actorService: ActorService + }, +): Promise<{ did: string }[]> { + const actorsViewerFollows = ctx.db.db + .selectFrom('follow') + .where('creator', '=', params.viewer) + .select('subjectDid') + const mostLikedAccounts = await ctx.db.db + .selectFrom( + ctx.db.db + .selectFrom('like') + .where('creator', '=', params.actor) + .select(sql`split_part(subject, '/', 3)`.as('subjectDid')) + .limit(1000) // limit to 1000 + .as('likes'), + ) + .select('likes.subjectDid as did') + .select((qb) => qb.fn.count('likes.subjectDid').as('count')) + .where('likes.subjectDid', 'not in', actorsViewerFollows) + .where('likes.subjectDid', 'not in', [params.actor, params.viewer]) + .groupBy('likes.subjectDid') + .orderBy('count', 'desc') + .limit(RESULT_LENGTH) + .execute() + const resultDids = mostLikedAccounts.map((a) => ({ did: a.did })) as { + did: string + }[] + + if (resultDids.length < RESULT_LENGTH) { + // backfill with popular accounts followed by actor + const mostPopularAccountsActorFollows = await ctx.db.db + .selectFrom('follow') + .innerJoin('profile_agg', 'follow.subjectDid', 'profile_agg.did') + .select('follow.subjectDid as did') + .where('follow.creator', '=', params.actor) + .where('follow.subjectDid', '!=', params.viewer) + .where('follow.subjectDid', 'not in', actorsViewerFollows) + .if(resultDids.length > 0, (qb) => + qb.where( + 'subjectDid', + 'not in', + resultDids.map((a) => a.did), + ), + ) + .orderBy('profile_agg.followersCount', 'desc') + .limit(RESULT_LENGTH) + .execute() + + resultDids.push(...mostPopularAccountsActorFollows) + } + + if (resultDids.length < RESULT_LENGTH) { + // backfill with suggested_follow table + const additional = await ctx.db.db + .selectFrom('suggested_follow') + .where( + 'did', + 'not in', + // exclude any we already have + resultDids.map((a) => a.did).concat([params.actor, params.viewer]), + ) + // and aren't already followed by viewer + .where('did', 'not in', actorsViewerFollows) + .selectAll() + .execute() + + resultDids.push(...additional) + } + + return resultDids +} diff --git a/packages/bsky/src/api/app/bsky/graph/muteActor.ts b/packages/bsky/src/api/app/bsky/graph/muteActor.ts index 9489c7a6151..50a3723db6e 100644 --- a/packages/bsky/src/api/app/bsky/graph/muteActor.ts +++ b/packages/bsky/src/api/app/bsky/graph/muteActor.ts @@ -8,9 +8,9 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ auth, input }) => { const { actor } = input.body const requester = auth.credentials.did - const { db, services } = ctx + const db = ctx.db.getPrimary() - const subjectDid = await services.actor(db).getActorDid(actor) + const subjectDid = await ctx.services.actor(db).getActorDid(actor) if (!subjectDid) { throw new InvalidRequestError(`Actor not found: ${actor}`) } @@ -18,7 +18,7 @@ export default function (server: Server, ctx: AppContext) { throw new InvalidRequestError('Cannot mute oneself') } - await services.graph(db).muteActor({ + await ctx.services.graph(db).muteActor({ subjectDid, mutedByDid: requester, }) diff --git a/packages/bsky/src/api/app/bsky/graph/muteActorList.ts b/packages/bsky/src/api/app/bsky/graph/muteActorList.ts index 3e35ef55bd9..b6b29796c5c 100644 --- a/packages/bsky/src/api/app/bsky/graph/muteActorList.ts +++ b/packages/bsky/src/api/app/bsky/graph/muteActorList.ts @@ -2,7 +2,7 @@ import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import * as lex from '../../../../lexicon/lexicons' import AppContext from '../../../../context' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' export default function (server: Server, ctx: AppContext) { server.app.bsky.graph.muteActorList({ @@ -11,13 +11,15 @@ export default function (server: Server, ctx: AppContext) { const { list } = input.body const requester = auth.credentials.did + const db = ctx.db.getPrimary() + const listUri = new AtUri(list) const collId = lex.ids.AppBskyGraphList if (listUri.collection !== collId) { throw new InvalidRequestError(`Invalid collection: expected: ${collId}`) } - await ctx.services.graph(ctx.db).muteActorList({ + await ctx.services.graph(db).muteActorList({ list, mutedByDid: requester, }) diff --git a/packages/bsky/src/api/app/bsky/graph/unmuteActor.ts b/packages/bsky/src/api/app/bsky/graph/unmuteActor.ts index 93d485b1892..11af919126f 100644 --- a/packages/bsky/src/api/app/bsky/graph/unmuteActor.ts +++ b/packages/bsky/src/api/app/bsky/graph/unmuteActor.ts @@ -8,9 +8,9 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ auth, input }) => { const { actor } = input.body const requester = auth.credentials.did - const { db, services } = ctx + const db = ctx.db.getPrimary() - const subjectDid = await services.actor(db).getActorDid(actor) + const subjectDid = await ctx.services.actor(db).getActorDid(actor) if (!subjectDid) { throw new InvalidRequestError(`Actor not found: ${actor}`) } @@ -18,7 +18,7 @@ export default function (server: Server, ctx: AppContext) { throw new InvalidRequestError('Cannot mute oneself') } - await services.graph(db).unmuteActor({ + await ctx.services.graph(db).unmuteActor({ subjectDid, mutedByDid: requester, }) diff --git a/packages/bsky/src/api/app/bsky/graph/unmuteActorList.ts b/packages/bsky/src/api/app/bsky/graph/unmuteActorList.ts index da63978fd23..8b97530c216 100644 --- a/packages/bsky/src/api/app/bsky/graph/unmuteActorList.ts +++ b/packages/bsky/src/api/app/bsky/graph/unmuteActorList.ts @@ -7,8 +7,9 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ auth, input }) => { const { list } = input.body const requester = auth.credentials.did + const db = ctx.db.getPrimary() - await ctx.services.graph(ctx.db).unmuteActorList({ + await ctx.services.graph(db).unmuteActorList({ list, mutedByDid: requester, }) diff --git a/packages/bsky/src/api/app/bsky/notification/getUnreadCount.ts b/packages/bsky/src/api/app/bsky/notification/getUnreadCount.ts index 3c6b26e9a71..c23d7683abe 100644 --- a/packages/bsky/src/api/app/bsky/notification/getUnreadCount.ts +++ b/packages/bsky/src/api/app/bsky/notification/getUnreadCount.ts @@ -1,3 +1,4 @@ +import { sql } from 'kysely' import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import { countAll, notSoftDeletedClause } from '../../../../db/util' @@ -12,8 +13,9 @@ export default function (server: Server, ctx: AppContext) { throw new InvalidRequestError('The seenAt parameter is unsupported') } - const { ref } = ctx.db.db.dynamic - const result = await ctx.db.db + const db = ctx.db.getReplica() + const { ref } = db.db.dynamic + const result = await db.db .selectFrom('notification') .select(countAll.as('count')) .innerJoin('actor', 'actor.did', 'notification.did') @@ -21,15 +23,12 @@ export default function (server: Server, ctx: AppContext) { .innerJoin('record', 'record.uri', 'notification.recordUri') .where(notSoftDeletedClause(ref('actor'))) .where(notSoftDeletedClause(ref('record'))) + // Ensure to hit notification_did_sortat_idx, handling case where lastSeenNotifs is null. .where('notification.did', '=', requester) - .where((inner) => - inner - .where('actor_state.lastSeenNotifs', 'is', null) - .orWhereRef( - 'notification.sortAt', - '>', - 'actor_state.lastSeenNotifs', - ), + .where( + 'notification.sortAt', + '>', + sql`coalesce(${ref('actor_state.lastSeenNotifs')}, ${''})`, ) .executeTakeFirst() diff --git a/packages/bsky/src/api/app/bsky/notification/listNotifications.ts b/packages/bsky/src/api/app/bsky/notification/listNotifications.ts index 9aec05ed8aa..7bcc88f12d3 100644 --- a/packages/bsky/src/api/app/bsky/notification/listNotifications.ts +++ b/packages/bsky/src/api/app/bsky/notification/listNotifications.ts @@ -1,119 +1,199 @@ import { InvalidRequestError } from '@atproto/xrpc-server' import { jsonStringToLex } from '@atproto/lexicon' +import { mapDefined } from '@atproto/common' import { Server } from '../../../../lexicon' -import { paginate, TimeCidKeyset } from '../../../../db/pagination' +import { QueryParams } from '../../../../lexicon/types/app/bsky/notification/listNotifications' import AppContext from '../../../../context' +import { Database } from '../../../../db' import { notSoftDeletedClause } from '../../../../db/util' +import { paginate, TimeCidKeyset } from '../../../../db/pagination' +import { BlockAndMuteState, GraphService } from '../../../../services/graph' +import { ActorInfoMap, ActorService } from '../../../../services/actor' +import { getSelfLabels, Labels, LabelService } from '../../../../services/label' +import { createPipeline } from '../../../../pipeline' export default function (server: Server, ctx: AppContext) { + const listNotifications = createPipeline( + skeleton, + hydration, + noBlockOrMutes, + presentation, + ) server.app.bsky.notification.listNotifications({ auth: ctx.authVerifier, handler: async ({ params, auth }) => { - const { limit, cursor } = params - const requester = auth.credentials.did - if (params.seenAt) { - throw new InvalidRequestError('The seenAt parameter is unsupported') - } + const db = ctx.db.getReplica() + const actorService = ctx.services.actor(db) + const graphService = ctx.services.graph(db) + const labelService = ctx.services.label(db) + const viewer = auth.credentials.did - const graphService = ctx.services.graph(ctx.db) - - const { ref } = ctx.db.db.dynamic - let notifBuilder = ctx.db.db - .selectFrom('notification as notif') - .innerJoin('record', 'record.uri', 'notif.recordUri') - .innerJoin('actor as author', 'author.did', 'notif.author') - .where(notSoftDeletedClause(ref('record'))) - .where(notSoftDeletedClause(ref('author'))) - .where('notif.did', '=', requester) - .where((qb) => - graphService.whereNotMuted(qb, requester, [ref('notif.author')]), - ) - .whereNotExists(graphService.blockQb(requester, [ref('notif.author')])) - .where((clause) => - clause - .where('reasonSubject', 'is', null) - .orWhereExists( - ctx.db.db - .selectFrom('record as subject') - .selectAll() - .whereRef('subject.uri', '=', ref('notif.reasonSubject')), - ), - ) - .select([ - 'notif.recordUri as uri', - 'notif.recordCid as cid', - 'author.did as authorDid', - 'author.handle as authorHandle', - 'author.indexedAt as authorIndexedAt', - 'author.takedownId as authorTakedownId', - 'notif.reason as reason', - 'notif.reasonSubject as reasonSubject', - 'notif.sortAt as indexedAt', - 'record.json as recordJson', - ]) - - const keyset = new NotifsKeyset( - ref('notif.sortAt'), - ref('notif.recordCid'), + const result = await listNotifications( + { ...params, viewer }, + { db, actorService, graphService, labelService }, ) - notifBuilder = paginate(notifBuilder, { - cursor, - limit, - keyset, - }) - - const actorStateQuery = ctx.db.db - .selectFrom('actor_state') - .selectAll() - .where('did', '=', requester) - - const [actorState, notifs] = await Promise.all([ - actorStateQuery.executeTakeFirst(), - notifBuilder.execute(), - ]) - - const seenAt = actorState?.lastSeenNotifs - - const actorService = ctx.services.actor(ctx.db) - const labelService = ctx.services.label(ctx.db) - const recordUris = notifs.map((notif) => notif.uri) - const [authors, labels] = await Promise.all([ - actorService.views.profile( - notifs.map((notif) => ({ - did: notif.authorDid, - handle: notif.authorHandle, - indexedAt: notif.authorIndexedAt, - takedownId: notif.authorTakedownId, - })), - requester, - ), - labelService.getLabelsForUris(recordUris), - ]) - - const notifications = notifs.map((notif, i) => ({ - uri: notif.uri, - cid: notif.cid, - author: authors[i], - reason: notif.reason, - reasonSubject: notif.reasonSubject || undefined, - record: jsonStringToLex(notif.recordJson) as Record, - isRead: seenAt ? notif.indexedAt <= seenAt : false, - indexedAt: notif.indexedAt, - labels: labels[notif.uri] ?? [], - })) return { encoding: 'application/json', - body: { - notifications, - cursor: keyset.packFromResult(notifs), - }, + body: result, } }, }) } -type NotifRow = { indexedAt: string; cid: string } +const skeleton = async ( + params: Params, + ctx: Context, +): Promise => { + const { db } = ctx + const { limit, cursor, viewer } = params + const { ref } = db.db.dynamic + if (params.seenAt) { + throw new InvalidRequestError('The seenAt parameter is unsupported') + } + let notifBuilder = db.db + .selectFrom('notification as notif') + .innerJoin('record', 'record.uri', 'notif.recordUri') + .innerJoin('actor as author', 'author.did', 'notif.author') + .where(notSoftDeletedClause(ref('record'))) + .where(notSoftDeletedClause(ref('author'))) + .where('notif.did', '=', viewer) + .where((clause) => + clause + .where('reasonSubject', 'is', null) + .orWhereExists( + db.db + .selectFrom('record as subject') + .selectAll() + .whereRef('subject.uri', '=', ref('notif.reasonSubject')), + ), + ) + .select([ + 'notif.recordUri as uri', + 'notif.recordCid as cid', + 'author.did as authorDid', + 'author.handle as authorHandle', + 'author.indexedAt as authorIndexedAt', + 'author.takedownId as authorTakedownId', + 'notif.reason as reason', + 'notif.reasonSubject as reasonSubject', + 'notif.sortAt as indexedAt', + 'record.json as recordJson', + ]) + + const keyset = new NotifsKeyset(ref('notif.sortAt'), ref('notif.recordCid')) + notifBuilder = paginate(notifBuilder, { + cursor, + limit, + keyset, + }) + + const actorStateQuery = db.db + .selectFrom('actor_state') + .selectAll() + .where('did', '=', viewer) + + const [notifs, actorState] = await Promise.all([ + notifBuilder.execute(), + actorStateQuery.executeTakeFirst(), + ]) + + return { + params, + notifs, + cursor: keyset.packFromResult(notifs), + lastSeenNotifs: actorState?.lastSeenNotifs, + } +} + +const hydration = async (state: SkeletonState, ctx: Context) => { + const { graphService, actorService, labelService } = ctx + const { params, notifs } = state + const { viewer } = params + const dids = notifs.map((notif) => notif.authorDid) + const uris = notifs.map((notif) => notif.uri) + const [actors, labels, bam] = await Promise.all([ + actorService.views.profiles(dids, viewer), + labelService.getLabelsForUris(uris), + graphService.getBlockAndMuteState(dids.map((did) => [viewer, did])), + ]) + return { ...state, actors, labels, bam } +} + +const noBlockOrMutes = (state: HydrationState) => { + const { viewer } = state.params + state.notifs = state.notifs.filter( + (item) => + !state.bam.block([viewer, item.authorDid]) && + !state.bam.mute([viewer, item.authorDid]), + ) + return state +} + +const presentation = (state: HydrationState) => { + const { notifs, cursor, actors, labels, lastSeenNotifs } = state + const notifications = mapDefined(notifs, (notif) => { + const author = actors[notif.authorDid] + if (!author) return undefined + const record = jsonStringToLex(notif.recordJson) as Record + const recordLabels = labels[notif.uri] ?? [] + const recordSelfLabels = getSelfLabels({ + uri: notif.uri, + cid: notif.cid, + record, + }) + return { + uri: notif.uri, + cid: notif.cid, + author, + reason: notif.reason, + reasonSubject: notif.reasonSubject || undefined, + record, + isRead: lastSeenNotifs ? notif.indexedAt <= lastSeenNotifs : false, + indexedAt: notif.indexedAt, + labels: [...recordLabels, ...recordSelfLabels], + } + }) + return { notifications, cursor } +} + +type Context = { + db: Database + actorService: ActorService + graphService: GraphService + labelService: LabelService +} + +type Params = QueryParams & { + viewer: string +} + +type SkeletonState = { + params: Params + notifs: NotifRow[] + lastSeenNotifs?: string + cursor?: string +} + +type HydrationState = SkeletonState & { + bam: BlockAndMuteState + actors: ActorInfoMap + labels: Labels +} + +type NotifRow = { + indexedAt: string + cid: string + uri: string + authorDid: string + authorHandle: string | null + authorIndexedAt: string + authorTakedownId: number | null + reason: string + reasonSubject: string | null + recordJson: string +} + class NotifsKeyset extends TimeCidKeyset { labelResult(result: NotifRow) { return { primary: result.indexedAt, secondary: result.cid } diff --git a/packages/bsky/src/api/app/bsky/notification/registerPush.ts b/packages/bsky/src/api/app/bsky/notification/registerPush.ts new file mode 100644 index 00000000000..be7d373bcd4 --- /dev/null +++ b/packages/bsky/src/api/app/bsky/notification/registerPush.ts @@ -0,0 +1,31 @@ +import { InvalidRequestError } from '@atproto/xrpc-server' +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { Platform } from '../../../../notifications' + +export default function (server: Server, ctx: AppContext) { + server.app.bsky.notification.registerPush({ + auth: ctx.authVerifier, + handler: async ({ auth, input }) => { + const { token, platform, serviceDid, appId } = input.body + const { + credentials: { did }, + } = auth + if (serviceDid !== auth.artifacts.aud) { + throw new InvalidRequestError('Invalid serviceDid.') + } + const { notifServer } = ctx + if (platform !== 'ios' && platform !== 'android' && platform !== 'web') { + throw new InvalidRequestError( + 'Unsupported platform: must be "ios", "android", or "web".', + ) + } + await notifServer.registerDeviceForPushNotifications( + did, + token, + platform as Platform, + appId, + ) + }, + }) +} diff --git a/packages/bsky/src/api/app/bsky/notification/updateSeen.ts b/packages/bsky/src/api/app/bsky/notification/updateSeen.ts index 98a53a8a585..b7c705c0889 100644 --- a/packages/bsky/src/api/app/bsky/notification/updateSeen.ts +++ b/packages/bsky/src/api/app/bsky/notification/updateSeen.ts @@ -17,12 +17,14 @@ export default function (server: Server, ctx: AppContext) { throw new InvalidRequestError('Invalid date') } - await ctx.db.db + const db = ctx.db.getPrimary() + + await db.db .insertInto('actor_state') .values({ did: viewer, lastSeenNotifs: parsed }) .onConflict((oc) => oc.column('did').doUpdateSet({ - lastSeenNotifs: excluded(ctx.db.db, 'lastSeenNotifs'), + lastSeenNotifs: excluded(db.db, 'lastSeenNotifs'), }), ) .executeTakeFirst() diff --git a/packages/bsky/src/api/app/bsky/unspecced.ts b/packages/bsky/src/api/app/bsky/unspecced.ts deleted file mode 100644 index 1f4e4bf7f41..00000000000 --- a/packages/bsky/src/api/app/bsky/unspecced.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Server } from '../../../lexicon' -import AppContext from '../../../context' -import { countAll } from '../../../db/util' - -// THIS IS A TEMPORARY UNSPECCED ROUTE -export default function (server: Server, ctx: AppContext) { - server.app.bsky.unspecced.getPopularFeedGenerators({ - auth: ctx.authOptionalVerifier, - handler: async ({ auth }) => { - const requester = auth.credentials.did - const db = ctx.db.db - const { ref } = db.dynamic - const feedService = ctx.services.feed(ctx.db) - - const mostPopularFeeds = await ctx.db.db - .selectFrom('feed_generator') - .select([ - 'uri', - ctx.db.db - .selectFrom('like') - .whereRef('like.subject', '=', ref('feed_generator.uri')) - .select(countAll.as('count')) - .as('likeCount'), - ]) - .orderBy('likeCount', 'desc') - .orderBy('cid', 'desc') - .limit(50) - .execute() - - const genViews = await feedService.getFeedGeneratorViews( - mostPopularFeeds.map((feed) => feed.uri), - requester, - ) - - const genList = Object.values(genViews) - const creators = genList.map((gen) => gen.creator) - const profiles = await feedService.getActorViews(creators, requester) - - const feedViews = genList.map((gen) => - feedService.views.formatFeedGeneratorView(gen, profiles), - ) - - return { - encoding: 'application/json', - body: { - feeds: feedViews.sort((feedA, feedB) => { - const likeA = feedA.likeCount ?? 0 - const likeB = feedB.likeCount ?? 0 - const likeDiff = likeB - likeA - if (likeDiff !== 0) return likeDiff - return feedB.cid.localeCompare(feedA.cid) - }), - }, - } - }, - }) -} diff --git a/packages/bsky/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts b/packages/bsky/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts new file mode 100644 index 00000000000..2971beba381 --- /dev/null +++ b/packages/bsky/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts @@ -0,0 +1,99 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { countAll } from '../../../../db/util' +import { GenericKeyset, paginate } from '../../../../db/pagination' +import { InvalidRequestError } from '@atproto/xrpc-server' +import { GeneratorView } from '../../../../lexicon/types/app/bsky/feed/defs' + +// THIS IS A TEMPORARY UNSPECCED ROUTE +export default function (server: Server, ctx: AppContext) { + server.app.bsky.unspecced.getPopularFeedGenerators({ + auth: ctx.authOptionalVerifier, + handler: async ({ auth, params }) => { + const { limit, cursor, query } = params + const requester = auth.credentials.did + const db = ctx.db.getReplica() + const { ref } = db.db.dynamic + const feedService = ctx.services.feed(db) + const actorService = ctx.services.actor(db) + + let inner = db.db + .selectFrom('feed_generator') + .select([ + 'uri', + 'cid', + db.db + .selectFrom('like') + .whereRef('like.subject', '=', ref('feed_generator.uri')) + .select(countAll.as('count')) + .as('likeCount'), + ]) + + if (query) { + inner = inner.where((qb) => + qb + .where('feed_generator.displayName', 'ilike', `%${query}%`) + .orWhere('feed_generator.description', 'ilike', `%${query}%`), + ) + } + + let builder = db.db.selectFrom(inner.as('feed_gens')).selectAll() + + const keyset = new LikeCountKeyset(ref('likeCount'), ref('cid')) + builder = paginate(builder, { limit, cursor, keyset, direction: 'desc' }) + + const res = await builder.execute() + + const genInfos = await feedService.getFeedGeneratorInfos( + res.map((feed) => feed.uri), + requester, + ) + + const creators = Object.values(genInfos).map((gen) => gen.creator) + const profiles = await actorService.views.profiles(creators, requester) + + const genViews: GeneratorView[] = [] + for (const row of res) { + const gen = genInfos[row.uri] + if (!gen) continue + const view = feedService.views.formatFeedGeneratorView(gen, profiles) + genViews.push(view) + } + + return { + encoding: 'application/json', + body: { + cursor: keyset.packFromResult(res), + feeds: genViews, + }, + } + }, + }) +} + +type Result = { likeCount: number; cid: string } +type LabeledResult = { primary: number; secondary: string } +export class LikeCountKeyset extends GenericKeyset { + labelResult(result: Result) { + return { + primary: result.likeCount, + secondary: result.cid, + } + } + labeledResultToCursor(labeled: LabeledResult) { + return { + primary: labeled.primary.toString(), + secondary: labeled.secondary, + } + } + cursorToLabeledResult(cursor: { primary: string; secondary: string }) { + const likes = parseInt(cursor.primary, 10) + if (isNaN(likes)) { + throw new InvalidRequestError('Malformed cursor') + } + return { + primary: likes, + secondary: cursor.secondary, + } + } +} diff --git a/packages/bsky/src/api/app/bsky/unspecced/getTimelineSkeleton.ts b/packages/bsky/src/api/app/bsky/unspecced/getTimelineSkeleton.ts new file mode 100644 index 00000000000..821eeda655f --- /dev/null +++ b/packages/bsky/src/api/app/bsky/unspecced/getTimelineSkeleton.ts @@ -0,0 +1,26 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { skeleton } from '../feed/getTimeline' +import { toSkeletonItem } from '../../../../feed-gen/types' + +// THIS IS A TEMPORARY UNSPECCED ROUTE +export default function (server: Server, ctx: AppContext) { + server.app.bsky.unspecced.getTimelineSkeleton({ + auth: ctx.authVerifier, + handler: async ({ auth, params }) => { + const db = ctx.db.getReplica('timeline') + const feedService = ctx.services.feed(db) + const viewer = auth.credentials.did + + const result = await skeleton({ ...params, viewer }, { db, feedService }) + + return { + encoding: 'application/json', + body: { + feed: result.feedItems.map(toSkeletonItem), + cursor: result.cursor, + }, + } + }, + }) +} diff --git a/packages/bsky/src/api/app/bsky/util/feed.ts b/packages/bsky/src/api/app/bsky/util/feed.ts index 9e85f4e974f..769b2d7e833 100644 --- a/packages/bsky/src/api/app/bsky/util/feed.ts +++ b/packages/bsky/src/api/app/bsky/util/feed.ts @@ -1,5 +1,5 @@ import { TimeCidKeyset } from '../../../../db/pagination' -import { FeedRow } from '../../../../services/types' +import { FeedRow } from '../../../../services/feed/types' export enum FeedAlgorithm { ReverseChronological = 'reverse-chronological', @@ -12,7 +12,7 @@ export class FeedKeyset extends TimeCidKeyset { } // For users with sparse feeds, avoid scanning more than one week for a single page -export const getFeedDateThreshold = (from: string | undefined, days = 3) => { +export const getFeedDateThreshold = (from: string | undefined, days = 1) => { const timelineDateThreshold = from ? new Date(from) : new Date() timelineDateThreshold.setDate(timelineDateThreshold.getDate() - days) return timelineDateThreshold.toISOString() diff --git a/packages/bsky/src/api/auth.ts b/packages/bsky/src/api/auth.ts deleted file mode 100644 index ec9b8112168..00000000000 --- a/packages/bsky/src/api/auth.ts +++ /dev/null @@ -1,65 +0,0 @@ -import express from 'express' -import * as uint8arrays from 'uint8arrays' -import { AuthRequiredError } from '@atproto/xrpc-server' - -const BASIC = 'Basic ' -const BEARER = 'Bearer ' - -// @TODO(bsky) treating did as a bearer, just a placeholder for now. -export const authVerifier = (ctx: { - req: express.Request - res: express.Response -}) => { - const { authorization = '' } = ctx.req.headers - if (!authorization.startsWith(BEARER)) { - throw new AuthRequiredError() - } - const did = authorization.replace(BEARER, '').trim() - if (!did.startsWith('did:')) { - throw new AuthRequiredError() - } - return { credentials: { did } } -} - -export const authOptionalVerifier = (ctx: { - req: express.Request - res: express.Response -}) => { - if (!ctx.req.headers.authorization) { - return { credentials: { did: null } } - } - return authVerifier(ctx) -} - -export const adminVerifier = - (adminPassword: string) => - (ctx: { req: express.Request; res: express.Response }) => { - const { authorization = '' } = ctx.req.headers - const parsed = parseBasicAuth(authorization) - if (!parsed) { - throw new AuthRequiredError() - } - const { username, password } = parsed - if (username !== 'admin' || password !== adminPassword) { - throw new AuthRequiredError() - } - return { credentials: { admin: true } } - } - -export const parseBasicAuth = ( - token: string, -): { username: string; password: string } | null => { - if (!token.startsWith(BASIC)) return null - const b64 = token.slice(BASIC.length) - let parsed: string[] - try { - parsed = uint8arrays - .toString(uint8arrays.fromString(b64, 'base64pad'), 'utf8') - .split(':') - } catch (err) { - return null - } - const [username, password] = parsed - if (!username || !password) return null - return { username, password } -} diff --git a/packages/bsky/src/api/blob-resolver.ts b/packages/bsky/src/api/blob-resolver.ts index c19f3d34f3e..c366583c246 100644 --- a/packages/bsky/src/api/blob-resolver.ts +++ b/packages/bsky/src/api/blob-resolver.ts @@ -3,14 +3,14 @@ import express from 'express' import createError from 'http-errors' import axios, { AxiosError } from 'axios' import { CID } from 'multiformats/cid' -import { ensureValidDid } from '@atproto/identifier' +import { ensureValidDid } from '@atproto/syntax' import { forwardStreamErrors, VerifyCidTransform } from '@atproto/common' import { IdResolver, DidNotFoundError } from '@atproto/identity' import { TAKEDOWN } from '../lexicon/types/com/atproto/admin/defs' import AppContext from '../context' import { httpLogger as log } from '../logger' import { retryHttp } from '../util/retry' -import Database from '../db' +import { Database } from '../db' // Resolve and verify blob from its origin host @@ -32,7 +32,8 @@ export const createRouter = (ctx: AppContext): express.Router => { return next(createError(400, 'Invalid cid')) } - const verifiedImage = await resolveBlob(did, cid, ctx) + const db = ctx.db.getReplica() + const verifiedImage = await resolveBlob(did, cid, db, ctx.idResolver) // Send chunked response, destroying stream early (before // closing chunk) if the bytes don't match the expected cid. @@ -79,15 +80,13 @@ export const createRouter = (ctx: AppContext): express.Router => { export async function resolveBlob( did: string, cid: CID, - ctx: { - db: Database - idResolver: IdResolver - }, + db: Database, + idResolver: IdResolver, ) { const cidStr = cid.toString() const [{ pds }, takedown] = await Promise.all([ - ctx.idResolver.did.resolveAtprotoData(did), // @TODO cache did info - ctx.db.db + idResolver.did.resolveAtprotoData(did), // @TODO cache did info + db.db .selectFrom('moderation_action_subject_blob') .select('actionId') .innerJoin( @@ -123,6 +122,6 @@ async function getBlob(opts: { pds: string; did: string; cid: string }) { params: { did, cid }, decompress: true, responseType: 'stream', - timeout: 2000, // 2sec of inactivity on the connection + timeout: 5000, // 5sec of inactivity on the connection }) } diff --git a/packages/bsky/src/api/com/atproto/admin/getModerationAction.ts b/packages/bsky/src/api/com/atproto/admin/getModerationAction.ts index 86fa2c94eec..55ff9b9ccf8 100644 --- a/packages/bsky/src/api/com/atproto/admin/getModerationAction.ts +++ b/packages/bsky/src/api/com/atproto/admin/getModerationAction.ts @@ -1,14 +1,13 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { adminVerifier } from '../../../auth' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.getModerationAction({ - auth: adminVerifier(ctx.cfg.adminPassword), + auth: ctx.roleVerifier, handler: async ({ params }) => { - const { db, services } = ctx const { id } = params - const moderationService = services.moderation(db) + const db = ctx.db.getPrimary() + const moderationService = ctx.services.moderation(db) const result = await moderationService.getActionOrThrow(id) return { encoding: 'application/json', diff --git a/packages/bsky/src/api/com/atproto/admin/getModerationActions.ts b/packages/bsky/src/api/com/atproto/admin/getModerationActions.ts index f677906ebff..ef28ef10b7a 100644 --- a/packages/bsky/src/api/com/atproto/admin/getModerationActions.ts +++ b/packages/bsky/src/api/com/atproto/admin/getModerationActions.ts @@ -1,14 +1,13 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { adminVerifier } from '../../../auth' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.getModerationActions({ - auth: adminVerifier(ctx.cfg.adminPassword), + auth: ctx.roleVerifier, handler: async ({ params }) => { - const { db, services } = ctx const { subject, limit = 50, cursor } = params - const moderationService = services.moderation(db) + const db = ctx.db.getPrimary() + const moderationService = ctx.services.moderation(db) const results = await moderationService.getActions({ subject, limit, diff --git a/packages/bsky/src/api/com/atproto/admin/getModerationReport.ts b/packages/bsky/src/api/com/atproto/admin/getModerationReport.ts index 25cfbb4acb9..e3faaa04436 100644 --- a/packages/bsky/src/api/com/atproto/admin/getModerationReport.ts +++ b/packages/bsky/src/api/com/atproto/admin/getModerationReport.ts @@ -1,14 +1,13 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { adminVerifier } from '../../../auth' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.getModerationReport({ - auth: adminVerifier(ctx.cfg.adminPassword), + auth: ctx.roleVerifier, handler: async ({ params }) => { - const { db, services } = ctx const { id } = params - const moderationService = services.moderation(db) + const db = ctx.db.getPrimary() + const moderationService = ctx.services.moderation(db) const result = await moderationService.getReportOrThrow(id) return { encoding: 'application/json', diff --git a/packages/bsky/src/api/com/atproto/admin/getModerationReports.ts b/packages/bsky/src/api/com/atproto/admin/getModerationReports.ts index c344539eaa6..d3956973f37 100644 --- a/packages/bsky/src/api/com/atproto/admin/getModerationReports.ts +++ b/packages/bsky/src/api/com/atproto/admin/getModerationReports.ts @@ -1,12 +1,10 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { adminVerifier } from '../../../auth' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.getModerationReports({ - auth: adminVerifier(ctx.cfg.adminPassword), + auth: ctx.roleVerifier, handler: async ({ params }) => { - const { db, services } = ctx const { subject, resolved, @@ -16,8 +14,10 @@ export default function (server: Server, ctx: AppContext) { ignoreSubjects, reverse = false, reporters = [], + actionedBy, } = params - const moderationService = services.moderation(db) + const db = ctx.db.getPrimary() + const moderationService = ctx.services.moderation(db) const results = await moderationService.getReports({ subject, resolved, @@ -27,6 +27,7 @@ export default function (server: Server, ctx: AppContext) { ignoreSubjects, reverse, reporters, + actionedBy, }) return { encoding: 'application/json', diff --git a/packages/bsky/src/api/com/atproto/admin/getRecord.ts b/packages/bsky/src/api/com/atproto/admin/getRecord.ts index 3081c2cf4e4..80e79fd94a2 100644 --- a/packages/bsky/src/api/com/atproto/admin/getRecord.ts +++ b/packages/bsky/src/api/com/atproto/admin/getRecord.ts @@ -1,14 +1,13 @@ import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { adminVerifier } from '../../../auth' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.getRecord({ - auth: adminVerifier(ctx.cfg.adminPassword), + auth: ctx.roleVerifier, handler: async ({ params }) => { - const { db, services } = ctx const { uri, cid } = params + const db = ctx.db.getPrimary() const result = await db.db .selectFrom('record') .selectAll() @@ -20,7 +19,7 @@ export default function (server: Server, ctx: AppContext) { } return { encoding: 'application/json', - body: await services.moderation(db).views.recordDetail(result), + body: await ctx.services.moderation(db).views.recordDetail(result), } }, }) diff --git a/packages/bsky/src/api/com/atproto/admin/getRepo.ts b/packages/bsky/src/api/com/atproto/admin/getRepo.ts index 08f7770e5f4..5febdfcdd0c 100644 --- a/packages/bsky/src/api/com/atproto/admin/getRepo.ts +++ b/packages/bsky/src/api/com/atproto/admin/getRepo.ts @@ -1,21 +1,20 @@ import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { adminVerifier } from '../../../auth' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.getRepo({ - auth: adminVerifier(ctx.cfg.adminPassword), + auth: ctx.roleVerifier, handler: async ({ params }) => { - const { db, services } = ctx const { did } = params - const result = await services.actor(db).getActor(did, true) + const db = ctx.db.getPrimary() + const result = await ctx.services.actor(db).getActor(did, true) if (!result) { throw new InvalidRequestError('Repo not found', 'RepoNotFound') } return { encoding: 'application/json', - body: await services.moderation(db).views.repoDetail(result), + body: await ctx.services.moderation(db).views.repoDetail(result), } }, }) diff --git a/packages/bsky/src/api/com/atproto/admin/resolveModerationReports.ts b/packages/bsky/src/api/com/atproto/admin/resolveModerationReports.ts index 63b1e837fc9..ed420e7d820 100644 --- a/packages/bsky/src/api/com/atproto/admin/resolveModerationReports.ts +++ b/packages/bsky/src/api/com/atproto/admin/resolveModerationReports.ts @@ -1,17 +1,16 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { adminVerifier } from '../../../auth' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.resolveModerationReports({ - auth: adminVerifier(ctx.cfg.adminPassword), + auth: ctx.roleVerifier, handler: async ({ input }) => { - const { db, services } = ctx - const moderationService = services.moderation(db) + const db = ctx.db.getPrimary() + const moderationService = ctx.services.moderation(db) const { actionId, reportIds, createdBy } = input.body const moderationAction = await db.transaction(async (dbTxn) => { - const moderationTxn = services.moderation(dbTxn) + const moderationTxn = ctx.services.moderation(dbTxn) await moderationTxn.resolveReports({ reportIds, actionId, createdBy }) return await moderationTxn.getActionOrThrow(actionId) }) diff --git a/packages/bsky/src/api/com/atproto/admin/reverseModerationAction.ts b/packages/bsky/src/api/com/atproto/admin/reverseModerationAction.ts index 259e0a69c3f..e0c70103359 100644 --- a/packages/bsky/src/api/com/atproto/admin/reverseModerationAction.ts +++ b/packages/bsky/src/api/com/atproto/admin/reverseModerationAction.ts @@ -1,21 +1,25 @@ -import { AtUri } from '@atproto/uri' -import { InvalidRequestError } from '@atproto/xrpc-server' +import { AtUri } from '@atproto/syntax' +import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server' +import { + ACKNOWLEDGE, + ESCALATE, + TAKEDOWN, +} from '../../../../lexicon/types/com/atproto/admin/defs' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { TAKEDOWN } from '../../../../lexicon/types/com/atproto/admin/defs' -import { adminVerifier } from '../../../auth' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.reverseModerationAction({ - auth: adminVerifier(ctx.cfg.adminPassword), - handler: async ({ input }) => { - const { db, services } = ctx - const moderationService = services.moderation(db) + auth: ctx.roleVerifier, + handler: async ({ input, auth }) => { + const access = auth.credentials + const db = ctx.db.getPrimary() + const moderationService = ctx.services.moderation(db) const { id, createdBy, reason } = input.body const moderationAction = await db.transaction(async (dbTxn) => { - const moderationTxn = services.moderation(dbTxn) - const labelTxn = services.label(dbTxn) + const moderationTxn = ctx.services.moderation(dbTxn) + const labelTxn = ctx.services.label(dbTxn) const now = new Date() const existing = await moderationTxn.getAction(id) @@ -28,33 +32,35 @@ export default function (server: Server, ctx: AppContext) { ) } - const result = await moderationTxn.logReverseAction({ - id, - createdAt: now, - createdBy, - reason, - }) + // apply access rules + // if less than moderator access then can only reverse ack and escalation actions if ( - result.action === TAKEDOWN && - result.subjectType === 'com.atproto.admin.defs#repoRef' && - result.subjectDid + !access.moderator && + ![ACKNOWLEDGE, ESCALATE].includes(existing.action) ) { - await moderationTxn.reverseTakedownRepo({ - did: result.subjectDid, - }) + throw new AuthRequiredError( + 'Must be a full moderator to reverse this type of action', + ) } - + // if less than moderator access then cannot reverse takedown on an account if ( - result.action === TAKEDOWN && - result.subjectType === 'com.atproto.repo.strongRef' && - result.subjectUri + !access.moderator && + existing.action === TAKEDOWN && + existing.subjectType === 'com.atproto.admin.defs#repoRef' ) { - await moderationTxn.reverseTakedownRecord({ - uri: new AtUri(result.subjectUri), - }) + throw new AuthRequiredError( + 'Must be a full moderator to reverse an account takedown', + ) } + const result = await moderationTxn.revertAction({ + id, + createdAt: now, + createdBy, + reason, + }) + // invert creates & negates const { createLabelVals, negateLabelVals } = result const negate = diff --git a/packages/bsky/src/api/com/atproto/admin/searchRepos.ts b/packages/bsky/src/api/com/atproto/admin/searchRepos.ts index 2e7bbcd3f47..9945b27fcb4 100644 --- a/packages/bsky/src/api/com/atproto/admin/searchRepos.ts +++ b/packages/bsky/src/api/com/atproto/admin/searchRepos.ts @@ -1,34 +1,25 @@ import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { adminVerifier } from '../../../auth' -import { paginate } from '../../../../db/pagination' -import { ListKeyset } from '../../../../services/actor' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.searchRepos({ - auth: adminVerifier(ctx.cfg.adminPassword), + auth: ctx.roleVerifier, handler: async ({ params }) => { - const { db, services } = ctx - const moderationService = services.moderation(db) - const { term = '', limit, cursor, invitedBy } = params + const db = ctx.db.getPrimary() + const moderationService = ctx.services.moderation(db) + const { invitedBy } = params if (invitedBy) { throw new InvalidRequestError('The invitedBy parameter is unsupported') } - const searchField = term.startsWith('did:') ? 'did' : 'handle' - - const { ref } = db.db.dynamic - const keyset = new ListKeyset(ref('indexedAt'), ref('handle')) - let resultQb = services.actor(db).searchQb(searchField, term).selectAll() - resultQb = paginate(resultQb, { keyset, cursor, limit }) - - const results = await resultQb.execute() - + const { results, cursor } = await ctx.services + .actor(db) + .getSearchResults({ ...params, includeSoftDeleted: true }) return { encoding: 'application/json', body: { - cursor: keyset.packFromResult(results), + cursor, repos: await moderationService.views.repo(results), }, } diff --git a/packages/bsky/src/api/com/atproto/admin/takeModerationAction.ts b/packages/bsky/src/api/com/atproto/admin/takeModerationAction.ts index 773b7f74eb5..fc49a9c14ff 100644 --- a/packages/bsky/src/api/com/atproto/admin/takeModerationAction.ts +++ b/packages/bsky/src/api/com/atproto/admin/takeModerationAction.ts @@ -1,18 +1,22 @@ import { CID } from 'multiformats/cid' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' +import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { TAKEDOWN } from '../../../../lexicon/types/com/atproto/admin/defs' +import { + ACKNOWLEDGE, + ESCALATE, + TAKEDOWN, +} from '../../../../lexicon/types/com/atproto/admin/defs' import { getSubject, getAction } from '../moderation/util' -import { InvalidRequestError } from '@atproto/xrpc-server' -import { adminVerifier } from '../../../auth' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.takeModerationAction({ - auth: adminVerifier(ctx.cfg.adminPassword), - handler: async ({ input }) => { - const { db, services } = ctx - const moderationService = services.moderation(db) + auth: ctx.roleVerifier, + handler: async ({ input, auth }) => { + const access = auth.credentials + const db = ctx.db.getPrimary() + const moderationService = ctx.services.moderation(db) const { action, subject, @@ -21,13 +25,36 @@ export default function (server: Server, ctx: AppContext) { createLabelVals, negateLabelVals, subjectBlobCids, + durationInHours, } = input.body + // apply access rules + + // if less than admin access then can not takedown an account + if (!access.moderator && action === TAKEDOWN && 'did' in subject) { + throw new AuthRequiredError( + 'Must be a full moderator to perform an account takedown', + ) + } + // if less than moderator access then can only take ack and escalation actions + if (!access.moderator && ![ACKNOWLEDGE, ESCALATE].includes(action)) { + throw new AuthRequiredError( + 'Must be a full moderator to take this type of action', + ) + } + // if less than moderator access then can not apply labels + if ( + !access.moderator && + (createLabelVals?.length || negateLabelVals?.length) + ) { + throw new AuthRequiredError('Must be a full moderator to label content') + } + validateLabels([...(createLabelVals ?? []), ...(negateLabelVals ?? [])]) const moderationAction = await db.transaction(async (dbTxn) => { - const moderationTxn = services.moderation(dbTxn) - const labelTxn = services.label(dbTxn) + const moderationTxn = ctx.services.moderation(dbTxn) + const labelTxn = ctx.services.label(dbTxn) const result = await moderationTxn.logAction({ action: getAction(action), @@ -37,6 +64,7 @@ export default function (server: Server, ctx: AppContext) { negateLabelVals, createdBy, reason, + durationInHours, }) if ( diff --git a/packages/bsky/src/api/com/atproto/identity/resolveHandle.ts b/packages/bsky/src/api/com/atproto/identity/resolveHandle.ts index c1e8f88a4c4..30c1d7f8a6f 100644 --- a/packages/bsky/src/api/com/atproto/identity/resolveHandle.ts +++ b/packages/bsky/src/api/com/atproto/identity/resolveHandle.ts @@ -1,5 +1,5 @@ import { InvalidRequestError } from '@atproto/xrpc-server' -import * as ident from '@atproto/identifier' +import * as ident from '@atproto/syntax' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' @@ -7,8 +7,9 @@ export default function (server: Server, ctx: AppContext) { server.com.atproto.identity.resolveHandle(async ({ req, params }) => { const handle = ident.normalizeHandle(params.handle || req.hostname) + const db = ctx.db.getReplica() let did: string | undefined - const user = await ctx.services.actor(ctx.db).getActor(handle, true) + const user = await ctx.services.actor(db).getActor(handle, true) if (user) { did = user.did } else { diff --git a/packages/bsky/src/api/com/atproto/moderation/createReport.ts b/packages/bsky/src/api/com/atproto/moderation/createReport.ts index 35bcde5a5e9..4cef67f1c65 100644 --- a/packages/bsky/src/api/com/atproto/moderation/createReport.ts +++ b/packages/bsky/src/api/com/atproto/moderation/createReport.ts @@ -9,19 +9,20 @@ export default function (server: Server, ctx: AppContext) { // @TODO anonymous reports w/ optional auth are a temporary measure auth: ctx.authOptionalVerifier, handler: async ({ input, auth }) => { - const { db, services } = ctx const { reasonType, reason, subject } = input.body const requester = auth.credentials.did + const db = ctx.db.getPrimary() + if (requester) { // Don't accept reports from users that are fully taken-down - const actor = await services.actor(db).getActor(requester, true) + const actor = await ctx.services.actor(db).getActor(requester, true) if (actor && softDeleted(actor)) { throw new AuthRequiredError() } } - const moderationService = services.moderation(db) + const moderationService = ctx.services.moderation(db) const report = await moderationService.report({ reasonType: getReasonType(reasonType), diff --git a/packages/bsky/src/api/com/atproto/moderation/util.ts b/packages/bsky/src/api/com/atproto/moderation/util.ts index 6aca2271506..d856148ee08 100644 --- a/packages/bsky/src/api/com/atproto/moderation/util.ts +++ b/packages/bsky/src/api/com/atproto/moderation/util.ts @@ -1,6 +1,6 @@ import { CID } from 'multiformats/cid' import { InvalidRequestError } from '@atproto/xrpc-server' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { ModerationAction } from '../../../../db/tables/moderation' import { ModerationReport } from '../../../../db/tables/moderation' import { InputSchema as ReportInput } from '../../../../lexicon/types/com/atproto/moderation/createReport' diff --git a/packages/bsky/src/api/com/atproto/repo/getRecord.ts b/packages/bsky/src/api/com/atproto/repo/getRecord.ts index 9903d171b7f..c42c1fd6b4c 100644 --- a/packages/bsky/src/api/com/atproto/repo/getRecord.ts +++ b/packages/bsky/src/api/com/atproto/repo/getRecord.ts @@ -1,5 +1,5 @@ import { InvalidRequestError } from '@atproto/xrpc-server' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import { jsonStringToLex } from '@atproto/lexicon' @@ -7,14 +7,15 @@ import { jsonStringToLex } from '@atproto/lexicon' export default function (server: Server, ctx: AppContext) { server.com.atproto.repo.getRecord(async ({ params }) => { const { repo, collection, rkey, cid } = params - const did = await ctx.services.actor(ctx.db).getActorDid(repo) + const db = ctx.db.getReplica() + const did = await ctx.services.actor(db).getActorDid(repo) if (!did) { throw new InvalidRequestError(`Could not find repo: ${repo}`) } const uri = AtUri.make(did, collection, rkey) - let builder = ctx.db.db + let builder = db.db .selectFrom('record') .selectAll() .where('uri', '=', uri.toString()) diff --git a/packages/bsky/src/api/health.ts b/packages/bsky/src/api/health.ts index ef14882625d..bdcdeefcb4b 100644 --- a/packages/bsky/src/api/health.ts +++ b/packages/bsky/src/api/health.ts @@ -7,8 +7,9 @@ export const createRouter = (ctx: AppContext): express.Router => { router.get('/xrpc/_health', async function (req, res) { const { version } = ctx.cfg + const db = ctx.db.getPrimary() try { - await sql`select 1`.execute(ctx.db.db) + await sql`select 1`.execute(db.db) } catch (err) { req.log.error(err, 'failed health check') return res.status(503).send({ version, error: 'Service Unavailable' }) diff --git a/packages/bsky/src/api/index.ts b/packages/bsky/src/api/index.ts index c56765c8d56..1928cda01d2 100644 --- a/packages/bsky/src/api/index.ts +++ b/packages/bsky/src/api/index.ts @@ -3,17 +3,21 @@ import AppContext from '../context' import describeFeedGenerator from './app/bsky/feed/describeFeedGenerator' import getTimeline from './app/bsky/feed/getTimeline' import getActorFeeds from './app/bsky/feed/getActorFeeds' +import getSuggestedFeeds from './app/bsky/feed/getSuggestedFeeds' import getAuthorFeed from './app/bsky/feed/getAuthorFeed' import getFeed from './app/bsky/feed/getFeed' import getFeedGenerator from './app/bsky/feed/getFeedGenerator' import getFeedGenerators from './app/bsky/feed/getFeedGenerators' +import getFeedSkeleton from './app/bsky/feed/getFeedSkeleton' import getLikes from './app/bsky/feed/getLikes' import getPostThread from './app/bsky/feed/getPostThread' import getPosts from './app/bsky/feed/getPosts' +import getActorLikes from './app/bsky/feed/getActorLikes' import getProfile from './app/bsky/actor/getProfile' import getProfiles from './app/bsky/actor/getProfiles' import getRepostedBy from './app/bsky/feed/getRepostedBy' import getBlocks from './app/bsky/graph/getBlocks' +import getListBlocks from './app/bsky/graph/getListBlocks' import getFollowers from './app/bsky/graph/getFollowers' import getFollows from './app/bsky/graph/getFollows' import getList from './app/bsky/graph/getList' @@ -24,13 +28,16 @@ import muteActor from './app/bsky/graph/muteActor' import unmuteActor from './app/bsky/graph/unmuteActor' import muteActorList from './app/bsky/graph/muteActorList' import unmuteActorList from './app/bsky/graph/unmuteActorList' +import getSuggestedFollowsByActor from './app/bsky/graph/getSuggestedFollowsByActor' import searchActors from './app/bsky/actor/searchActors' import searchActorsTypeahead from './app/bsky/actor/searchActorsTypeahead' import getSuggestions from './app/bsky/actor/getSuggestions' import getUnreadCount from './app/bsky/notification/getUnreadCount' import listNotifications from './app/bsky/notification/listNotifications' import updateSeen from './app/bsky/notification/updateSeen' -import unspecced from './app/bsky/unspecced' +import registerPush from './app/bsky/notification/registerPush' +import getPopularFeedGenerators from './app/bsky/unspecced/getPopularFeedGenerators' +import getTimelineSkeleton from './app/bsky/unspecced/getTimelineSkeleton' import createReport from './com/atproto/moderation/createReport' import resolveModerationReports from './com/atproto/admin/resolveModerationReports' import reverseModerationAction from './com/atproto/admin/reverseModerationAction' @@ -47,6 +54,8 @@ import getRecord from './com/atproto/repo/getRecord' export * as health from './health' +export * as wellKnown from './well-known' + export * as blobResolver from './blob-resolver' export default function (server: Server, ctx: AppContext) { @@ -54,17 +63,21 @@ export default function (server: Server, ctx: AppContext) { describeFeedGenerator(server, ctx) getTimeline(server, ctx) getActorFeeds(server, ctx) + getSuggestedFeeds(server, ctx) getAuthorFeed(server, ctx) getFeed(server, ctx) getFeedGenerator(server, ctx) getFeedGenerators(server, ctx) + getFeedSkeleton(server, ctx) getLikes(server, ctx) getPostThread(server, ctx) getPosts(server, ctx) + getActorLikes(server, ctx) getProfile(server, ctx) getProfiles(server, ctx) getRepostedBy(server, ctx) getBlocks(server, ctx) + getListBlocks(server, ctx) getFollowers(server, ctx) getFollows(server, ctx) getList(server, ctx) @@ -75,13 +88,16 @@ export default function (server: Server, ctx: AppContext) { unmuteActor(server, ctx) muteActorList(server, ctx) unmuteActorList(server, ctx) + getSuggestedFollowsByActor(server, ctx) searchActors(server, ctx) searchActorsTypeahead(server, ctx) getSuggestions(server, ctx) getUnreadCount(server, ctx) listNotifications(server, ctx) updateSeen(server, ctx) - unspecced(server, ctx) + registerPush(server, ctx) + getPopularFeedGenerators(server, ctx) + getTimelineSkeleton(server, ctx) // com.atproto createReport(server, ctx) resolveModerationReports(server, ctx) diff --git a/packages/bsky/src/api/util.ts b/packages/bsky/src/api/util.ts new file mode 100644 index 00000000000..ef7e51bc95e --- /dev/null +++ b/packages/bsky/src/api/util.ts @@ -0,0 +1,7 @@ +import express from 'express' + +export const setRepoRev = (res: express.Response, rev: string | null) => { + if (rev !== null) { + res.setHeader('Atproto-Repo-Rev', rev) + } +} diff --git a/packages/bsky/src/api/well-known.ts b/packages/bsky/src/api/well-known.ts new file mode 100644 index 00000000000..b6813751605 --- /dev/null +++ b/packages/bsky/src/api/well-known.ts @@ -0,0 +1,26 @@ +import express from 'express' +import AppContext from '../context' + +export const createRouter = (ctx: AppContext): express.Router => { + const router = express.Router() + + router.get('/.well-known/did.json', (_req, res) => { + const hostname = ctx.cfg.publicUrl && new URL(ctx.cfg.publicUrl).hostname + if (!hostname || ctx.cfg.serverDid !== `did:web:${hostname}`) { + return res.sendStatus(404) + } + res.json({ + '@context': ['https://www.w3.org/ns/did/v1'], + id: ctx.cfg.serverDid, + service: [ + { + id: '#bsky_notif', + type: 'BskyNotificationService', + serviceEndpoint: `https://${hostname}`, + }, + ], + }) + }) + + return router +} diff --git a/packages/bsky/src/auth.ts b/packages/bsky/src/auth.ts index 2a2a960f7f9..a92023d55f5 100644 --- a/packages/bsky/src/auth.ts +++ b/packages/bsky/src/auth.ts @@ -1,6 +1,11 @@ import express from 'express' +import * as uint8arrays from 'uint8arrays' import { AuthRequiredError, verifyJwt } from '@atproto/xrpc-server' import { IdResolver } from '@atproto/identity' +import { ServerConfig } from './config' + +const BASIC = 'Basic ' +const BEARER = 'Bearer ' export const authVerifier = (idResolver: IdResolver, opts: { aud: string | null }) => @@ -13,7 +18,7 @@ export const authVerifier = const atprotoData = await idResolver.did.resolveAtprotoData(did) return atprotoData.signingKey }) - return { credentials: { did } } + return { credentials: { did }, artifacts: { aud: opts.aud } } } export const authOptionalVerifier = @@ -25,10 +30,99 @@ export const authOptionalVerifier = return authVerifier(idResolver, opts)(reqCtx) } +export const authOptionalAccessOrRoleVerifier = ( + idResolver: IdResolver, + cfg: ServerConfig, +) => { + const verifyAccess = authVerifier(idResolver, { aud: cfg.serverDid }) + const verifyRole = roleVerifier(cfg) + return async (ctx: { req: express.Request; res: express.Response }) => { + const defaultUnAuthorizedCredentials = { + credentials: { did: null, type: 'unauthed' as const }, + } + if (!ctx.req.headers.authorization) { + return defaultUnAuthorizedCredentials + } + // For non-admin tokens, we don't want to consider alternative verifiers and let it fail if it fails + const isRoleAuthToken = ctx.req.headers.authorization?.startsWith(BASIC) + if (isRoleAuthToken) { + const result = await verifyRole(ctx) + return { + ...result, + credentials: { + type: 'role' as const, + ...result.credentials, + }, + } + } + const result = await verifyAccess(ctx) + return { + ...result, + credentials: { + type: 'access' as const, + ...result.credentials, + }, + } + } +} + +export const roleVerifier = + (cfg: ServerConfig) => + async (reqCtx: { req: express.Request; res: express.Response }) => { + const credentials = getRoleCredentials(cfg, reqCtx.req) + if (!credentials.valid) { + throw new AuthRequiredError() + } + return { credentials } + } + +export const getRoleCredentials = (cfg: ServerConfig, req: express.Request) => { + const parsed = parseBasicAuth(req.headers.authorization || '') + const { username, password } = parsed ?? {} + if (username === 'admin' && password === cfg.triagePassword) { + return { valid: true, admin: false, moderator: false, triage: true } + } + if (username === 'admin' && password === cfg.moderatorPassword) { + return { valid: true, admin: false, moderator: true, triage: true } + } + if (username === 'admin' && password === cfg.adminPassword) { + return { valid: true, admin: true, moderator: true, triage: true } + } + return { valid: false, admin: false, moderator: false, triage: false } +} + +export const parseBasicAuth = ( + token: string, +): { username: string; password: string } | null => { + if (!token.startsWith(BASIC)) return null + const b64 = token.slice(BASIC.length) + let parsed: string[] + try { + parsed = uint8arrays + .toString(uint8arrays.fromString(b64, 'base64pad'), 'utf8') + .split(':') + } catch (err) { + return null + } + const [username, password] = parsed + if (!username || !password) return null + return { username, password } +} + +export const buildBasicAuth = (username: string, password: string): string => { + return ( + BASIC + + uint8arrays.toString( + uint8arrays.fromString(`${username}:${password}`, 'utf8'), + 'base64pad', + ) + ) +} + export const getJwtStrFromReq = (req: express.Request): string | null => { const { authorization = '' } = req.headers - if (!authorization.startsWith('Bearer ')) { + if (!authorization.startsWith(BEARER)) { return null } - return authorization.replace('Bearer ', '').trim() + return authorization.replace(BEARER, '').trim() } diff --git a/packages/bsky/src/auto-moderator/abyss.ts b/packages/bsky/src/auto-moderator/abyss.ts new file mode 100644 index 00000000000..fb9ee2c4e98 --- /dev/null +++ b/packages/bsky/src/auto-moderator/abyss.ts @@ -0,0 +1,106 @@ +import axios from 'axios' +import { CID } from 'multiformats/cid' +import * as ui8 from 'uint8arrays' +import { resolveBlob } from '../api/blob-resolver' +import { retryHttp } from '../util/retry' +import { PrimaryDatabase } from '../db' +import { IdResolver } from '@atproto/identity' +import { labelerLogger as log } from '../logger' + +export interface ImageFlagger { + scanImage(did: string, cid: CID): Promise +} + +export class Abyss implements ImageFlagger { + protected auth: string + + constructor( + public endpoint: string, + protected password: string, + public ctx: { db: PrimaryDatabase; idResolver: IdResolver }, + ) { + this.auth = basicAuth(this.password) + } + + async scanImage(did: string, cid: CID): Promise { + const start = Date.now() + const res = await retryHttp(async () => { + try { + return await this.makeReq(did, cid) + } catch (err) { + log.warn({ err, did, cid: cid.toString() }, 'abyss request failed') + throw err + } + }) + log.info( + { res, did, cid: cid.toString(), duration: Date.now() - start }, + 'abyss response', + ) + return this.parseRes(res) + } + + async makeReq(did: string, cid: CID): Promise { + const { stream, contentType } = await resolveBlob( + did, + cid, + this.ctx.db, + this.ctx.idResolver, + ) + const { data } = await axios.post(this.getReqUrl({ did }), stream, { + headers: { + 'Content-Type': contentType, + authorization: this.auth, + }, + timeout: 10000, + }) + return data + } + + parseRes(res: ScannerResp): string[] { + if (!res.match || res.match.status !== 'success') { + return [] + } + const labels: string[] = [] + for (const hit of res.match.hits) { + if (TAKEDOWN_LABELS.includes(hit.label)) { + labels.push(hit.label) + } + } + return labels + } + + getReqUrl(params: { did: string }) { + return `${this.endpoint}/xrpc/com.atproto.unspecced.scanBlob?did=${params.did}` + } +} + +const TAKEDOWN_LABELS = ['csam', 'csem'] + +type ScannerResp = { + blob: unknown + match?: { + status: string + hits: ScannerHit[] + } + classify?: { + hits?: unknown[] + } + review?: { + state?: string + ticketId?: string + } +} + +type ScannerHit = { + hashType: string + hashValue: string + label: string + corpus: string +} + +const basicAuth = (password: string) => { + return ( + 'Basic ' + + ui8.toString(ui8.fromString(`admin:${password}`, 'utf8'), 'base64pad') + ) +} diff --git a/packages/bsky/src/auto-moderator/fuzzy-matcher.ts b/packages/bsky/src/auto-moderator/fuzzy-matcher.ts new file mode 100644 index 00000000000..07b5fb9a85e --- /dev/null +++ b/packages/bsky/src/auto-moderator/fuzzy-matcher.ts @@ -0,0 +1,126 @@ +import { dedupeStrs } from '@atproto/common' +import * as ui8 from 'uint8arrays' + +export interface TextFlagger { + getMatches(string: string): string[] +} + +export class FuzzyMatcher implements TextFlagger { + private bannedWords: Set + private falsePositives: Set + + constructor(bannedWords: string[], falsePositives: string[] = []) { + this.bannedWords = new Set(bannedWords.map((word) => word.toLowerCase())) + this.falsePositives = new Set( + falsePositives.map((word) => word.toLowerCase()), + ) + } + + static fromB64(bannedB64: string, falsePositivesB64?: string) { + return new FuzzyMatcher( + decode(bannedB64), + falsePositivesB64 ? decode(falsePositivesB64) : undefined, + ) + } + + private normalize(domain: string): string[] { + const withoutSymbols = domain.replace(/[\W_]+/g, '') // Remove non-alphanumeric characters + const lowercase = withoutSymbols.toLowerCase() + + // Replace common leetspeak characters + const leetSpeakReplacements: { [key: string]: string[] } = { + '0': ['o'], + '8': ['b'], + '3': ['e'], + '4': ['a'], + '6': ['g'], + '1': ['i', 'l'], + '5': ['s'], + '7': ['t'], + } + + return this.generatePermutations(lowercase, leetSpeakReplacements) + } + + private generatePermutations( + domain: string, + leetSpeakReplacements: { [key: string]: string[] }, + ): string[] { + const results: string[] = [] + + const leetChars = Object.keys(leetSpeakReplacements) + const firstLeetCharIndex = [...domain].findIndex((char) => + leetChars.includes(char), + ) + + if (firstLeetCharIndex === -1) { + // No leetspeak characters left in the string + results.push(domain) + } else { + const char = domain[firstLeetCharIndex] + const beforeChar = domain.slice(0, firstLeetCharIndex) + const afterChar = domain.slice(firstLeetCharIndex + 1) + + // For each replacement, generate all possible combinations + for (const replacement of leetSpeakReplacements[char]) { + const replaced = beforeChar + replacement + afterChar + + // Recursively generate all permutations for the rest of the string + const otherPermutations = this.generatePermutations( + replaced, + leetSpeakReplacements, + ) + + // Add these permutations to the results + results.push(...otherPermutations) + } + } + + return dedupeStrs(results) + } + + public getMatches(domain: string): string[] { + const normalizedDomains = this.normalize(domain) + + const foundUnacceptableWords: string[] = [] + + for (const normalizedDomain of normalizedDomains) { + for (const word of this.bannedWords) { + const match = normalizedDomain.indexOf(word) + if (match > -1) { + let isFalsePositive = false + for (const falsePositive of this.falsePositives) { + const s_fp = falsePositive.indexOf(word) + const s_nd = match - s_fp + const wordToMatch = normalizedDomain.slice( + s_nd, + s_nd + falsePositive.length, + ) + if (wordToMatch === falsePositive) { + isFalsePositive = true + break + } + } + + if (!isFalsePositive) { + foundUnacceptableWords.push(word) + } + } + } + } + + if (foundUnacceptableWords.length > 0) { + return foundUnacceptableWords + } + + return [] + } +} + +export const decode = (encoded: string): string[] => { + return ui8.toString(ui8.fromString(encoded, 'base64'), 'utf8').split(',') +} + +export const encode = (words: string[]): string => { + return ui8.toString(ui8.fromString(words.join(','), 'utf8'), 'base64') +} diff --git a/packages/bsky/src/auto-moderator/hive.ts b/packages/bsky/src/auto-moderator/hive.ts new file mode 100644 index 00000000000..51d67c1c783 --- /dev/null +++ b/packages/bsky/src/auto-moderator/hive.ts @@ -0,0 +1,187 @@ +import axios from 'axios' +import FormData from 'form-data' +import { CID } from 'multiformats/cid' +import { IdResolver } from '@atproto/identity' +import { PrimaryDatabase } from '../db' +import { retryHttp } from '../util/retry' +import { resolveBlob } from '../api/blob-resolver' +import { labelerLogger as log } from '../logger' + +const HIVE_ENDPOINT = 'https://api.thehive.ai/api/v2/task/sync' + +export interface ImgLabeler { + labelImg(did: string, cid: CID): Promise +} + +export class HiveLabeler implements ImgLabeler { + constructor( + public hiveApiKey: string, + protected ctx: { + db: PrimaryDatabase + idResolver: IdResolver + }, + ) {} + + async labelImg(did: string, cid: CID): Promise { + const hiveRes = await retryHttp(async () => { + try { + return await this.makeHiveReq(did, cid) + } catch (err) { + log.warn({ err, did, cid: cid.toString() }, 'hive request failed') + throw err + } + }) + log.info({ hiveRes, did, cid: cid.toString() }, 'hive response') + const classes = respToClasses(hiveRes) + return summarizeLabels(classes) + } + + async makeHiveReq(did: string, cid: CID): Promise { + const { stream } = await resolveBlob( + did, + cid, + this.ctx.db, + this.ctx.idResolver, + ) + const form = new FormData() + form.append('media', stream) + const { data } = await axios.post(HIVE_ENDPOINT, form, { + headers: { + 'Content-Type': 'multipart/form-data', + authorization: `token ${this.hiveApiKey}`, + accept: 'application/json', + }, + }) + return data + } +} + +export const respToClasses = (res: HiveResp): HiveRespClass[] => { + const classes: HiveRespClass[] = [] + for (const status of res.status) { + for (const out of status.response.output) { + for (const cls of out.classes) { + classes.push(cls) + } + } + } + return classes +} + +// Matches only one (or none) of: porn, sexual, nudity +// +// porn: sexual and nudity. including both explicit activity or full-frontal and suggestive/intent +// sexual: sexually suggestive, not explicit; may include some forms of nudity +// nudity: non-sexual nudity (eg, artistic, possibly some photographic) +// +// hive docs/definitions: https://docs.thehive.ai/docs/sexual-content +export const sexualLabels = (classes: HiveRespClass[]): string[] => { + const scores = {} + + for (const cls of classes) { + scores[cls.class] = cls.score + } + + // first check if porn... + for (const pornClass of [ + 'yes_sexual_activity', + 'animal_genitalia_and_human', + 'yes_realistic_nsfw', + ]) { + if (scores[pornClass] >= 0.9) { + return ['porn'] + } + } + if (scores['general_nsfw'] >= 0.9) { + // special case for some anime examples + if (scores['animated_animal_genitalia'] >= 0.5) { + return ['porn'] + } + // special case for some pornographic/explicit classic drawings + if (scores['yes_undressed'] >= 0.9 && scores['yes_sexual_activity'] > 0.9) { + return ['porn'] + } + } + + // then check for sexual suggestive (which may include nudity)... + for (const sexualClass of ['yes_sexual_intent', 'yes_sex_toy']) { + if (scores[sexualClass] >= 0.9) { + return ['sexual'] + } + } + if (scores['yes_undressed'] >= 0.9) { + // special case for bondage examples + if (scores['yes_sex_toy'] > 0.75) { + return ['sexual'] + } + } + + // then non-sexual nudity... + for (const nudityClass of [ + 'yes_male_nudity', + 'yes_female_nudity', + 'yes_undressed', + ]) { + if (scores[nudityClass] >= 0.9) { + return ['nudity'] + } + } + + // then finally flag remaining "underwear" images in to sexually suggestive + // (after non-sexual content already labeled above) + for (const nudityClass of ['yes_male_underwear', 'yes_female_underwear']) { + if (scores[nudityClass] >= 0.9) { + // TODO: retaining 'underwear' label for a short time to help understand + // the impact of labeling all "underwear" as "sexual". This *will* be + // pulling in somewhat non-sexual content in to "sexual" label. + return ['sexual'] + } + } + + return [] +} + +// gore and violence: https://docs.thehive.ai/docs/class-descriptions-violence-gore +const labelForClass = { + very_bloody: 'gore', + human_corpse: 'corpse', + hanging: 'corpse', +} +const labelForClassLessSensitive = { + yes_self_harm: 'self-harm', +} + +export const summarizeLabels = (classes: HiveRespClass[]): string[] => { + const labels: string[] = sexualLabels(classes) + for (const cls of classes) { + if (labelForClass[cls.class] && cls.score >= 0.9) { + labels.push(labelForClass[cls.class]) + } + } + for (const cls of classes) { + if (labelForClassLessSensitive[cls.class] && cls.score >= 0.96) { + labels.push(labelForClassLessSensitive[cls.class]) + } + } + return labels +} + +type HiveResp = { + status: HiveRespStatus[] +} + +type HiveRespStatus = { + response: { + output: HiveRespOutput[] + } +} + +type HiveRespOutput = { + time: number + classes: HiveRespClass[] +} + +type HiveRespClass = { + class: string + score: number +} diff --git a/packages/bsky/src/auto-moderator/index.ts b/packages/bsky/src/auto-moderator/index.ts new file mode 100644 index 00000000000..85cc529bce1 --- /dev/null +++ b/packages/bsky/src/auto-moderator/index.ts @@ -0,0 +1,275 @@ +import { AtUri } from '@atproto/syntax' +import { AtpAgent } from '@atproto/api' +import { dedupe, getFieldsFromRecord } from './util' +import { labelerLogger as log } from '../logger' +import { PrimaryDatabase } from '../db' +import { IdResolver } from '@atproto/identity' +import { BackgroundQueue } from '../background' +import { IndexerConfig } from '../indexer/config' +import { buildBasicAuth } from '../auth' +import { CID } from 'multiformats/cid' +import { LabelService } from '../services/label' +import { ModerationService } from '../services/moderation' +import { ImageFlagger } from './abyss' +import { HiveLabeler, ImgLabeler } from './hive' +import { KeywordLabeler, TextLabeler } from './keyword' +import { ids } from '../lexicon/lexicons' +import { ImageUriBuilder } from '../image/uri' +import { ImageInvalidator } from '../image/invalidator' +import { Abyss } from './abyss' +import { FuzzyMatcher, TextFlagger } from './fuzzy-matcher' +import { REASONOTHER } from '../lexicon/types/com/atproto/moderation/defs' + +export class AutoModerator { + public pushAgent?: AtpAgent + public imageFlagger?: ImageFlagger + public textFlagger?: TextFlagger + public imgLabeler?: ImgLabeler + public textLabeler?: TextLabeler + + services: { + label: (db: PrimaryDatabase) => LabelService + moderation?: (db: PrimaryDatabase) => ModerationService + } + + constructor( + public ctx: { + db: PrimaryDatabase + idResolver: IdResolver + cfg: IndexerConfig + backgroundQueue: BackgroundQueue + imgUriBuilder?: ImageUriBuilder + imgInvalidator?: ImageInvalidator + }, + ) { + const { imgUriBuilder, imgInvalidator } = ctx + const { hiveApiKey, abyssEndpoint, abyssPassword } = ctx.cfg + this.services = { + label: LabelService.creator(null), + } + if (imgUriBuilder && imgInvalidator) { + this.services.moderation = ModerationService.creator( + imgUriBuilder, + imgInvalidator, + ) + } else { + log.error( + { imgUriBuilder, imgInvalidator }, + 'moderation service not properly configured', + ) + } + + this.imgLabeler = hiveApiKey ? new HiveLabeler(hiveApiKey, ctx) : undefined + this.textLabeler = new KeywordLabeler(ctx.cfg.labelerKeywords) + if (abyssEndpoint && abyssPassword) { + this.imageFlagger = new Abyss(abyssEndpoint, abyssPassword, ctx) + } else { + log.error( + { abyssEndpoint, abyssPassword }, + 'abyss not properly configured', + ) + } + + if (ctx.cfg.fuzzyMatchB64) { + this.textFlagger = FuzzyMatcher.fromB64( + ctx.cfg.fuzzyMatchB64, + ctx.cfg.fuzzyFalsePositiveB64, + ) + } + + if (ctx.cfg.moderationPushUrl) { + const url = new URL(ctx.cfg.moderationPushUrl) + this.pushAgent = new AtpAgent({ service: url.origin }) + this.pushAgent.api.setHeader( + 'authorization', + buildBasicAuth(url.username, url.password), + ) + } + } + + processRecord(uri: AtUri, cid: CID, obj: unknown) { + this.ctx.backgroundQueue.add(async () => { + const { text, imgs } = getFieldsFromRecord(obj, uri) + await Promise.all([ + this.labelRecord(uri, cid, text, imgs).catch((err) => { + log.error( + { err, uri: uri.toString(), record: obj }, + 'failed to label record', + ) + }), + this.flagRecordText(uri, cid, text).catch((err) => { + log.error( + { err, uri: uri.toString(), record: obj }, + 'failed to check record for text flagging', + ) + }), + this.checkImgForTakedown(uri, cid, imgs).catch((err) => { + log.error( + { err, uri: uri.toString(), record: obj }, + 'failed to check img for takedown', + ) + }), + ]) + }) + } + + processHandle(handle: string, did: string) { + this.ctx.backgroundQueue.add(async () => { + await this.flagSubjectText(handle, { did }).catch((err) => { + log.error({ err, handle, did }, 'failed to label handle') + }) + }) + } + + async labelRecord(uri: AtUri, recordCid: CID, text: string[], imgs: CID[]) { + if (uri.collection !== ids.AppBskyFeedPost) { + // @TODO label profiles + return + } + const allLabels = await Promise.all([ + this.textLabeler?.labelText(text.join(' ')), + ...imgs.map((cid) => this.imgLabeler?.labelImg(uri.host, cid)), + ]) + const labels = dedupe(allLabels.flat()) + await this.storeLabels(uri, recordCid, labels) + } + + async flagRecordText(uri: AtUri, cid: CID, text: string[]) { + if ( + ![ + ids.AppBskyActorProfile, + ids.AppBskyGraphList, + ids.AppBskyFeedGenerator, + ].includes(uri.collection) + ) { + return + } + return this.flagSubjectText(text.join(' '), { uri, cid }) + } + + async flagSubjectText( + text: string, + subject: { did: string } | { uri: AtUri; cid: CID }, + ) { + if (!this.textFlagger) return + const matches = this.textFlagger.getMatches(text) + if (matches.length < 1) return + if (!this.services.moderation) { + log.error( + { subject, text, matches }, + 'no moderation service setup to flag record text', + ) + return + } + await this.services.moderation(this.ctx.db).report({ + reasonType: REASONOTHER, + reason: `Automatically flagged for possible slurs: ${matches.join(', ')}`, + subject, + reportedBy: this.ctx.cfg.labelerDid, + }) + } + + async checkImgForTakedown(uri: AtUri, recordCid: CID, imgCids: CID[]) { + if (imgCids.length < 0) return + const results = await Promise.all( + imgCids.map((cid) => this.imageFlagger?.scanImage(uri.host, cid)), + ) + const takedownCids: CID[] = [] + for (let i = 0; i < results.length; i++) { + if (results.at(i)?.length) { + takedownCids.push(imgCids[i]) + } + } + if (takedownCids.length === 0) return + try { + await this.persistTakedown( + uri, + recordCid, + takedownCids, + dedupe(results.flat()), + ) + } catch (err) { + log.error( + { + err, + uri: uri.toString(), + imgCids: imgCids.map((c) => c.toString()), + results, + }, + 'failed to persist takedown', + ) + } + } + + async persistTakedown( + uri: AtUri, + recordCid: CID, + takedownCids: CID[], + labels: string[], + ) { + const reason = `automated takedown for labels: ${labels.join(', ')}` + if (this.pushAgent) { + await this.pushAgent.com.atproto.admin.takeModerationAction({ + action: 'com.atproto.admin.defs#takedown', + subject: { + $type: 'com.atproto.repo.strongRef', + uri: uri.toString(), + cid: recordCid.toString(), + }, + subjectBlobCids: takedownCids.map((c) => c.toString()), + reason, + createdBy: this.ctx.cfg.labelerDid, + }) + } else { + await this.ctx.db.transaction(async (dbTxn) => { + if (!this.services.moderation) { + throw new Error('no mod push agent or uri invalidator setup') + } + const modSrvc = this.services.moderation(dbTxn) + const action = await modSrvc.logAction({ + action: 'com.atproto.admin.defs#takedown', + subject: { uri, cid: recordCid }, + subjectBlobCids: takedownCids, + reason, + createdBy: this.ctx.cfg.labelerDid, + }) + await modSrvc.takedownRecord({ + takedownId: action.id, + uri: uri, + blobCids: takedownCids, + }) + }) + } + } + + async storeLabels(uri: AtUri, cid: CID, labels: string[]): Promise { + if (labels.length < 1) return + const labelSrvc = this.services.label(this.ctx.db) + const formatted = await labelSrvc.formatAndCreate( + this.ctx.cfg.labelerDid, + uri.toString(), + cid.toString(), + { create: labels }, + ) + if (this.pushAgent) { + const agent = this.pushAgent + try { + await agent.api.app.bsky.unspecced.applyLabels({ labels: formatted }) + } catch (err) { + log.error( + { + err, + uri: uri.toString(), + labels, + receiver: agent.service.toString(), + }, + 'failed to push labels', + ) + } + } + } + + async processAll() { + await this.ctx.backgroundQueue.processAll() + } +} diff --git a/packages/bsky/src/auto-moderator/keyword.ts b/packages/bsky/src/auto-moderator/keyword.ts new file mode 100644 index 00000000000..6bc504aa142 --- /dev/null +++ b/packages/bsky/src/auto-moderator/keyword.ts @@ -0,0 +1,25 @@ +export interface TextLabeler { + labelText(text: string): Promise +} + +export class KeywordLabeler implements TextLabeler { + constructor(public keywords: Record) {} + + async labelText(text: string): Promise { + return keywordLabeling(this.keywords, text) + } +} + +export const keywordLabeling = ( + keywords: Record, + text: string, +): string[] => { + const lowerText = text.toLowerCase() + const labels: string[] = [] + for (const word of Object.keys(keywords)) { + if (lowerText.includes(word)) { + labels.push(keywords[word]) + } + } + return labels +} diff --git a/packages/bsky/src/labeler/util.ts b/packages/bsky/src/auto-moderator/util.ts similarity index 52% rename from packages/bsky/src/labeler/util.ts rename to packages/bsky/src/auto-moderator/util.ts index 4175886a542..ab1467a07f2 100644 --- a/packages/bsky/src/labeler/util.ts +++ b/packages/bsky/src/auto-moderator/util.ts @@ -1,7 +1,22 @@ import { CID } from 'multiformats/cid' +import { AtUri } from '@atproto/syntax' import * as lex from '../lexicon/lexicons' -import { Record as PostRecord } from '../lexicon/types/app/bsky/feed/post' -import { Record as ProfileRecord } from '../lexicon/types/app/bsky/actor/profile' +import { + isRecord as isPost, + Record as PostRecord, +} from '../lexicon/types/app/bsky/feed/post' +import { + isRecord as isProfile, + Record as ProfileRecord, +} from '../lexicon/types/app/bsky/actor/profile' +import { + isRecord as isList, + Record as ListRecord, +} from '../lexicon/types/app/bsky/graph/list' +import { + isRecord as isGenerator, + Record as GeneratorRecord, +} from '../lexicon/types/app/bsky/feed/generator' import { isMain as isEmbedImage } from '../lexicon/types/app/bsky/embed/images' import { isMain as isEmbedExternal } from '../lexicon/types/app/bsky/embed/external' import { isMain as isEmbedRecordWithMedia } from '../lexicon/types/app/bsky/embed/recordWithMedia' @@ -11,12 +26,18 @@ type RecordFields = { imgs: CID[] } -export const getFieldsFromRecord = (record: unknown): RecordFields => { +export const getFieldsFromRecord = ( + record: unknown, + uri: AtUri, +): RecordFields => { if (isPost(record)) { return getFieldsFromPost(record) - // @TODO add back in profile labeling - // } else if (isProfile(record)) { - // return getFieldsFromProfile(record) + } else if (isProfile(record)) { + return getFieldsFromProfile(record) + } else if (isList(record)) { + return getFieldsFromList(record) + } else if (isGenerator(record)) { + return getFieldsFromGenerator(record, uri) } else { return { text: [], imgs: [] } } @@ -62,40 +83,48 @@ export const getFieldsFromProfile = (record: ProfileRecord): RecordFields => { return { text, imgs } } -export const dedupe = (str: string[]): string[] => { - const set = new Set(str) - return [...set] -} - -export const isPost = (obj: unknown): obj is PostRecord => { - return isRecordType(obj, 'app.bsky.feed.post') -} - -export const isProfile = (obj: unknown): obj is ProfileRecord => { - return isRecordType(obj, 'app.bsky.actor.profile') +export const getFieldsFromList = (record: ListRecord): RecordFields => { + const text: string[] = [] + const imgs: CID[] = [] + if (record.name) { + text.push(record.name) + } + if (record.description) { + text.push(record.description) + } + if (record.avatar) { + imgs.push(record.avatar.ref) + } + return { text, imgs } } -export const isRecordType = (obj: unknown, lexId: string): boolean => { - try { - lex.lexicons.assertValidRecord(lexId, obj) - return true - } catch { - return false +export const getFieldsFromGenerator = ( + record: GeneratorRecord, + uri: AtUri, +): RecordFields => { + const text: string[] = [] + const imgs: CID[] = [] + text.push(uri.rkey) + if (record.displayName) { + text.push(record.displayName) } + if (record.description) { + text.push(record.description) + } + if (record.avatar) { + imgs.push(record.avatar.ref) + } + return { text, imgs } } -export const keywordLabeling = ( - keywords: Record, - text: string, -): string[] => { - const lowerText = text.toLowerCase() - const labels: string[] = [] - for (const word of Object.keys(keywords)) { - if (lowerText.includes(word)) { - labels.push(keywords[word]) +export const dedupe = (strs: (string | undefined)[]): string[] => { + const set = new Set() + for (const str of strs) { + if (str !== undefined) { + set.add(str) } } - return labels + return [...set] } const separateEmbeds = (embed: PostRecord['embed']) => { diff --git a/packages/bsky/src/background.ts b/packages/bsky/src/background.ts index a66ecf887b8..466bad80a51 100644 --- a/packages/bsky/src/background.ts +++ b/packages/bsky/src/background.ts @@ -1,13 +1,13 @@ import PQueue from 'p-queue' -import Database from './db' +import { PrimaryDatabase } from './db' import { dbLogger } from './logger' // A simple queue for in-process, out-of-band/backgrounded work export class BackgroundQueue { - queue = new PQueue({ concurrency: 10 }) + queue = new PQueue({ concurrency: 20 }) destroyed = false - constructor(public db: Database) {} + constructor(public db: PrimaryDatabase) {} add(task: Task) { if (this.destroyed) { @@ -32,4 +32,4 @@ export class BackgroundQueue { } } -type Task = (db: Database) => Promise +type Task = (db: PrimaryDatabase) => Promise diff --git a/packages/bsky/src/bin.ts b/packages/bsky/src/bin.ts deleted file mode 100644 index 97494c59d07..00000000000 --- a/packages/bsky/src/bin.ts +++ /dev/null @@ -1,25 +0,0 @@ -import './env' -import { ServerConfig } from './config' -import Database from './db' -import BskyAppView from './index' -import { AddressInfo } from 'net' - -const run = async () => { - const cfg = ServerConfig.readEnv() - const db = Database.postgres({ - url: cfg.dbPostgresUrl, - schema: cfg.dbPostgresSchema, - }) - - await db.migrateToLatestOrThrow() - - const bsky = BskyAppView.create({ db, config: cfg }) - await bsky.start() - - const { address, port, family } = bsky.server?.address() as AddressInfo - const location = - family === 'IPv6' ? `[${address}]:${port}` : `${address}:${port}` - console.log(`🌞 Bsky App View is running at ${location}`) -} - -run() diff --git a/packages/bsky/src/config.ts b/packages/bsky/src/config.ts index de88cbe98a7..2ef7e1edf6c 100644 --- a/packages/bsky/src/config.ts +++ b/packages/bsky/src/config.ts @@ -8,21 +8,21 @@ export interface ServerConfigValues { publicUrl?: string serverDid: string feedGenDid?: string - dbPostgresUrl: string + dbPrimaryPostgresUrl: string + dbReplicaPostgresUrls?: string[] + dbReplicaTags?: Record // E.g. { timeline: [0], thread: [1] } dbPostgresSchema?: string didPlcUrl: string didCacheStaleTTL: number didCacheMaxTTL: number - imgUriSalt: string - imgUriKey: string + handleResolveNameservers?: string[] imgUriEndpoint?: string blobCacheLocation?: string - repoProvider?: string - repoSubLockId?: number labelerDid: string - hiveApiKey?: string adminPassword: string - labelerKeywords: Record + moderatorPassword?: string + triagePassword?: string + moderationActionReverseUrl?: string } export class ServerConfig { @@ -46,22 +46,40 @@ export class ServerConfig { process.env.DID_CACHE_MAX_TTL, DAY, ) - const imgUriSalt = - process.env.IMG_URI_SALT || '9dd04221f5755bce5f55f47464c27e1e' - const imgUriKey = - process.env.IMG_URI_KEY || - 'f23ecd142835025f42c3db2cf25dd813956c178392760256211f9d315f8ab4d8' + const handleResolveNameservers = process.env.HANDLE_RESOLVE_NAMESERVERS + ? process.env.HANDLE_RESOLVE_NAMESERVERS.split(',') + : [] const imgUriEndpoint = process.env.IMG_URI_ENDPOINT const blobCacheLocation = process.env.BLOB_CACHE_LOC - const dbPostgresUrl = - overrides?.dbPostgresUrl || process.env.DB_POSTGRES_URL - assert(dbPostgresUrl) + const dbPrimaryPostgresUrl = + overrides?.dbPrimaryPostgresUrl || process.env.DB_PRIMARY_POSTGRES_URL + let dbReplicaPostgresUrls = overrides?.dbReplicaPostgresUrls + if (!dbReplicaPostgresUrls && process.env.DB_REPLICA_POSTGRES_URLS) { + dbReplicaPostgresUrls = process.env.DB_REPLICA_POSTGRES_URLS.split(',') + } + const dbReplicaTags = overrides?.dbReplicaTags ?? { + '*': getTagIdxs(process.env.DB_REPLICA_TAGS_ANY), // e.g. DB_REPLICA_TAGS_ANY=0,1 + timeline: getTagIdxs(process.env.DB_REPLICA_TAGS_TIMELINE), + feed: getTagIdxs(process.env.DB_REPLICA_TAGS_FEED), + search: getTagIdxs(process.env.DB_REPLICA_TAGS_SEARCH), + thread: getTagIdxs(process.env.DB_REPLICA_TAGS_THREAD), + } + assert( + Object.values(dbReplicaTags) + .flat() + .every((idx) => idx < (dbReplicaPostgresUrls?.length ?? 0)), + 'out of range index in replica tags', + ) const dbPostgresSchema = process.env.DB_POSTGRES_SCHEMA - const repoProvider = process.env.REPO_PROVIDER // E.g. ws://abc.com:4000 + assert(dbPrimaryPostgresUrl) const adminPassword = process.env.ADMIN_PASSWORD || 'admin' + const moderatorPassword = process.env.MODERATOR_PASSWORD || undefined + const triagePassword = process.env.TRIAGE_PASSWORD || undefined const labelerDid = process.env.LABELER_DID || 'did:example:labeler' - const hiveApiKey = process.env.HIVE_API_KEY || undefined - const labelerKeywords = {} + const moderationActionReverseUrl = + overrides?.moderationActionReverseUrl || + process.env.MODERATION_PUSH_URL || + undefined return new ServerConfig({ version, debugMode, @@ -69,20 +87,21 @@ export class ServerConfig { publicUrl, serverDid, feedGenDid, - dbPostgresUrl, + dbPrimaryPostgresUrl, + dbReplicaPostgresUrls, + dbReplicaTags, dbPostgresSchema, didPlcUrl, didCacheStaleTTL, didCacheMaxTTL, - imgUriSalt, - imgUriKey, + handleResolveNameservers, imgUriEndpoint, blobCacheLocation, - repoProvider, labelerDid, - hiveApiKey, adminPassword, - labelerKeywords, + moderatorPassword, + triagePassword, + moderationActionReverseUrl, ...stripUndefineds(overrides ?? {}), }) } @@ -124,8 +143,16 @@ export class ServerConfig { return this.cfg.feedGenDid } - get dbPostgresUrl() { - return this.cfg.dbPostgresUrl + get dbPrimaryPostgresUrl() { + return this.cfg.dbPrimaryPostgresUrl + } + + get dbReplicaPostgresUrl() { + return this.cfg.dbReplicaPostgresUrls + } + + get dbReplicaTags() { + return this.cfg.dbReplicaTags } get dbPostgresSchema() { @@ -137,19 +164,15 @@ export class ServerConfig { } get didCacheMaxTTL() { - return this.cfg.didCacheStaleTTL - } - - get didPlcUrl() { - return this.cfg.didPlcUrl + return this.cfg.didCacheMaxTTL } - get imgUriSalt() { - return this.cfg.imgUriSalt + get handleResolveNameservers() { + return this.cfg.handleResolveNameservers } - get imgUriKey() { - return this.cfg.imgUriKey + get didPlcUrl() { + return this.cfg.didPlcUrl } get imgUriEndpoint() { @@ -160,29 +183,29 @@ export class ServerConfig { return this.cfg.blobCacheLocation } - get repoProvider() { - return this.cfg.repoProvider + get labelerDid() { + return this.cfg.labelerDid } - get repoSubLockId() { - return this.cfg.repoSubLockId + get adminPassword() { + return this.cfg.adminPassword } - get labelerDid() { - return this.cfg.labelerDid + get moderatorPassword() { + return this.cfg.moderatorPassword } - get hiveApiKey() { - return this.cfg.hiveApiKey + get triagePassword() { + return this.cfg.triagePassword } - get labelerKeywords() { - return this.cfg.labelerKeywords + get moderationActionReverseUrl() { + return this.cfg.moderationActionReverseUrl } +} - get adminPassword() { - return this.cfg.adminPassword - } +function getTagIdxs(str?: string): number[] { + return str ? str.split(',').map((item) => parseInt(item, 10)) : [] } function stripUndefineds( diff --git a/packages/bsky/src/context.ts b/packages/bsky/src/context.ts index aee4706eee6..343d105ce1a 100644 --- a/packages/bsky/src/context.ts +++ b/packages/bsky/src/context.ts @@ -1,31 +1,33 @@ import * as plc from '@did-plc/lib' import { IdResolver } from '@atproto/identity' -import { Database } from './db' +import { DatabaseCoordinator } from './db' import { ServerConfig } from './config' import { ImageUriBuilder } from './image/uri' import { Services } from './services' import * as auth from './auth' import DidSqlCache from './did-cache' -import { Labeler } from './labeler' import { BackgroundQueue } from './background' import { MountedAlgos } from './feed-gen/types' +import { LabelCache } from './label-cache' +import { NotificationServer } from './notifications' export class AppContext { constructor( private opts: { - db: Database + db: DatabaseCoordinator imgUriBuilder: ImageUriBuilder cfg: ServerConfig services: Services idResolver: IdResolver didCache: DidSqlCache - labeler: Labeler + labelCache: LabelCache backgroundQueue: BackgroundQueue algos: MountedAlgos + notifServer: NotificationServer }, ) {} - get db(): Database { + get db(): DatabaseCoordinator { return this.opts.db } @@ -53,6 +55,14 @@ export class AppContext { return this.opts.didCache } + get labelCache(): LabelCache { + return this.opts.labelCache + } + + get notifServer(): NotificationServer { + return this.opts.notifServer + } + get authVerifier() { return auth.authVerifier(this.idResolver, { aud: this.cfg.serverDid }) } @@ -67,8 +77,12 @@ export class AppContext { }) } - get labeler(): Labeler { - return this.opts.labeler + get authOptionalAccessOrRoleVerifier() { + return auth.authOptionalAccessOrRoleVerifier(this.idResolver, this.cfg) + } + + get roleVerifier() { + return auth.roleVerifier(this.cfg) } get backgroundQueue(): BackgroundQueue { diff --git a/packages/bsky/src/db/coordinator.ts b/packages/bsky/src/db/coordinator.ts new file mode 100644 index 00000000000..a8f4cc3016c --- /dev/null +++ b/packages/bsky/src/db/coordinator.ts @@ -0,0 +1,107 @@ +import { Migrator } from 'kysely' +import PrimaryDatabase from './primary' +import Database from './db' +import { PgOptions } from './types' +import { dbLogger } from '../logger' + +type ReplicaTag = 'timeline' | 'feed' | 'search' | 'thread' | '*' +type ReplicaOptions = PgOptions & { tags?: ReplicaTag[] } + +type CoordinatorOptions = { + schema?: string + primary: PgOptions + replicas?: ReplicaOptions[] +} + +type ReplicaGroup = { + dbs: Database[] + roundRobinIdx: number +} + +export class DatabaseCoordinator { + migrator: Migrator + destroyed = false + + private primary: PrimaryDatabase + private allReplicas: Database[] + private tagged: Record + private untagged: ReplicaGroup + private tagWarns = new Set() + + constructor(public opts: CoordinatorOptions) { + this.primary = new PrimaryDatabase({ + schema: opts.schema, + ...opts.primary, + }) + this.allReplicas = [] + this.tagged = {} + this.untagged = { + dbs: [], + roundRobinIdx: 0, + } + for (const cfg of opts.replicas ?? []) { + const db = new Database({ + schema: opts.schema, + ...cfg, + }) + this.allReplicas.push(db) + // setup different groups of replicas based on tag, each round-robins separately. + if (cfg.tags?.length) { + for (const tag of cfg.tags) { + if (tag === '*') { + this.untagged.dbs.push(db) + } else { + this.tagged[tag] ??= { + dbs: [], + roundRobinIdx: 0, + } + this.tagged[tag].dbs.push(db) + } + } + } else { + this.untagged.dbs.push(db) + } + } + // guarantee there is always a replica around to service any query, falling back to primary. + if (!this.untagged.dbs.length) { + if (this.allReplicas.length) { + this.untagged.dbs = [...this.allReplicas] + } else { + this.untagged.dbs = [this.primary] + } + } + } + + getPrimary(): PrimaryDatabase { + return this.primary + } + + getReplicas(): Database[] { + return this.allReplicas + } + + getReplica(tag?: ReplicaTag): Database { + if (tag && this.tagged[tag]) { + return nextDb(this.tagged[tag]) + } + if (tag && !this.tagWarns.has(tag)) { + this.tagWarns.add(tag) + dbLogger.warn({ tag }, 'no replica for tag, falling back to any replica') + } + return nextDb(this.untagged) + } + + async close(): Promise { + await Promise.all([ + this.primary.close(), + ...this.allReplicas.map((db) => db.close()), + ]) + } +} + +// @NOTE mutates group incrementing roundRobinIdx +const nextDb = (group: ReplicaGroup) => { + const db = group.dbs[group.roundRobinIdx] + group.roundRobinIdx = (group.roundRobinIdx + 1) % group.dbs.length + return db +} diff --git a/packages/bsky/src/db/database-schema.ts b/packages/bsky/src/db/database-schema.ts index e9c097e866b..adb8c088207 100644 --- a/packages/bsky/src/db/database-schema.ts +++ b/packages/bsky/src/db/database-schema.ts @@ -4,7 +4,6 @@ import * as profile from './tables/profile' import * as profileAgg from './tables/profile-agg' import * as post from './tables/post' import * as postEmbed from './tables/post-embed' -import * as postHierarchy from './tables/post-hierarchy' import * as postAgg from './tables/post-agg' import * as repost from './tables/repost' import * as feedItem from './tables/feed-item' @@ -13,6 +12,7 @@ import * as like from './tables/like' import * as list from './tables/list' import * as listItem from './tables/list-item' import * as listMute from './tables/list-mute' +import * as listBlock from './tables/list-block' import * as mute from './tables/mute' import * as actorBlock from './tables/actor-block' import * as feedGenerator from './tables/feed-generator' @@ -22,19 +22,20 @@ import * as actorState from './tables/actor-state' import * as actorSync from './tables/actor-sync' import * as record from './tables/record' import * as notification from './tables/notification' +import * as notificationPushToken from './tables/notification-push-token' import * as didCache from './tables/did-cache' import * as moderation from './tables/moderation' import * as label from './tables/label' import * as algo from './tables/algo' import * as viewParam from './tables/view-param' import * as suggestedFollow from './tables/suggested-follow' +import * as suggestedFeed from './tables/suggested-feed' export type DatabaseSchemaType = duplicateRecord.PartialDB & profile.PartialDB & profileAgg.PartialDB & post.PartialDB & postEmbed.PartialDB & - postHierarchy.PartialDB & postAgg.PartialDB & repost.PartialDB & feedItem.PartialDB & @@ -43,6 +44,7 @@ export type DatabaseSchemaType = duplicateRecord.PartialDB & list.PartialDB & listItem.PartialDB & listMute.PartialDB & + listBlock.PartialDB & mute.PartialDB & actorBlock.PartialDB & feedGenerator.PartialDB & @@ -52,12 +54,14 @@ export type DatabaseSchemaType = duplicateRecord.PartialDB & actorSync.PartialDB & record.PartialDB & notification.PartialDB & + notificationPushToken.PartialDB & didCache.PartialDB & moderation.PartialDB & label.PartialDB & algo.PartialDB & viewParam.PartialDB & - suggestedFollow.PartialDB + suggestedFollow.PartialDB & + suggestedFeed.PartialDB export type DatabaseSchema = Kysely diff --git a/packages/bsky/src/db/db.ts b/packages/bsky/src/db/db.ts new file mode 100644 index 00000000000..cb58eb4742b --- /dev/null +++ b/packages/bsky/src/db/db.ts @@ -0,0 +1,91 @@ +import assert from 'assert' +import { Kysely, PostgresDialect } from 'kysely' +import { Pool as PgPool, types as pgTypes } from 'pg' +import DatabaseSchema, { DatabaseSchemaType } from './database-schema' +import { PgOptions } from './types' +import { dbLogger } from '../logger' + +export class Database { + pool: PgPool + db: DatabaseSchema + destroyed = false + isPrimary = false + + constructor( + public opts: PgOptions, + instances?: { db: DatabaseSchema; pool: PgPool }, + ) { + // if instances are provided, use those + if (instances) { + this.db = instances.db + this.pool = instances.pool + return + } + + // else create a pool & connect + const { schema, url } = opts + const pool = + opts.pool ?? + new PgPool({ + connectionString: url, + max: opts.poolSize, + maxUses: opts.poolMaxUses, + idleTimeoutMillis: opts.poolIdleTimeoutMs, + }) + + // Select count(*) and other pg bigints as js integer + pgTypes.setTypeParser(pgTypes.builtins.INT8, (n) => parseInt(n, 10)) + + // Setup schema usage, primarily for test parallelism (each test suite runs in its own pg schema) + if (schema && !/^[a-z_]+$/i.test(schema)) { + throw new Error(`Postgres schema must only contain [A-Za-z_]: ${schema}`) + } + + pool.on('error', onPoolError) + pool.on('connect', (client) => { + client.on('error', onClientError) + // Used for trigram indexes, e.g. on actor search + client.query('SET pg_trgm.word_similarity_threshold TO .4;') + if (schema) { + // Shared objects such as extensions will go in the public schema + client.query(`SET search_path TO "${schema}",public;`) + } + }) + + this.pool = pool + this.db = new Kysely({ + dialect: new PostgresDialect({ pool }), + }) + } + + get schema(): string | undefined { + return this.opts.schema + } + + get isTransaction() { + return this.db.isTransaction + } + + assertTransaction() { + assert(this.isTransaction, 'Transaction required') + } + + assertNotTransaction() { + assert(!this.isTransaction, 'Cannot be in a transaction') + } + + asPrimary(): Database { + throw new Error('Primary db required') + } + + async close(): Promise { + if (this.destroyed) return + await this.db.destroy() + this.destroyed = true + } +} + +export default Database + +const onPoolError = (err: Error) => dbLogger.error({ err }, 'db pool error') +const onClientError = (err: Error) => dbLogger.error({ err }, 'db client error') diff --git a/packages/bsky/src/db/index.ts b/packages/bsky/src/db/index.ts index e31ee5ffea2..1c5886fb10e 100644 --- a/packages/bsky/src/db/index.ts +++ b/packages/bsky/src/db/index.ts @@ -1,233 +1,3 @@ -import assert from 'assert' -import EventEmitter from 'events' -import { - Kysely, - PostgresDialect, - Migrator, - KyselyPlugin, - PluginTransformQueryArgs, - PluginTransformResultArgs, - RootOperationNode, - QueryResult, - UnknownRow, - sql, -} from 'kysely' -import { Pool as PgPool, types as pgTypes } from 'pg' -import TypedEmitter from 'typed-emitter' -import { wait } from '@atproto/common' -import DatabaseSchema, { DatabaseSchemaType } from './database-schema' -import * as migrations from './migrations' -import { CtxMigrationProvider } from './migrations/provider' -import { dbLogger as log } from '../logger' - -export class Database { - migrator: Migrator - txEvt = new EventEmitter() as TxnEmitter - destroyed = false - - constructor(public db: DatabaseSchema, public cfg: PgConfig) { - this.migrator = new Migrator({ - db, - migrationTableSchema: cfg.schema, - provider: new CtxMigrationProvider(migrations, cfg.dialect), - }) - } - - static postgres(opts: PgOptions): Database { - const { schema, url } = opts - const pool = - opts.pool ?? - new PgPool({ - connectionString: url, - max: opts.poolSize, - maxUses: opts.poolMaxUses, - idleTimeoutMillis: opts.poolIdleTimeoutMs, - }) - - // Select count(*) and other pg bigints as js integer - pgTypes.setTypeParser(pgTypes.builtins.INT8, (n) => parseInt(n, 10)) - - // Setup schema usage, primarily for test parallelism (each test suite runs in its own pg schema) - if (schema && !/^[a-z_]+$/i.test(schema)) { - throw new Error(`Postgres schema must only contain [A-Za-z_]: ${schema}`) - } - - pool.on('connect', (client) => { - // Used for trigram indexes, e.g. on actor search - client.query('SET pg_trgm.strict_word_similarity_threshold TO .1;') - if (schema) { - // Shared objects such as extensions will go in the public schema - client.query(`SET search_path TO "${schema}",public;`) - } - }) - - const db = new Kysely({ - dialect: new PostgresDialect({ pool }), - }) - - return new Database(db, { dialect: 'pg', pool, schema, url }) - } - - async transaction(fn: (db: Database) => Promise): Promise { - const leakyTxPlugin = new LeakyTxPlugin() - const { dbTxn, txRes } = await this.db - .withPlugin(leakyTxPlugin) - .transaction() - .execute(async (txn) => { - const dbTxn = new Database(txn, this.cfg) - const txRes = await fn(dbTxn) - .catch(async (err) => { - leakyTxPlugin.endTx() - // ensure that all in-flight queries are flushed & the connection is open - await dbTxn.db.getExecutor().provideConnection(noopAsync) - throw err - }) - .finally(() => leakyTxPlugin.endTx()) - return { dbTxn, txRes } - }) - dbTxn?.txEvt.emit('commit') - return txRes - } - - onCommit(fn: () => void) { - this.assertTransaction() - this.txEvt.once('commit', fn) - } - - get schema(): string | undefined { - return this.cfg.dialect === 'pg' ? this.cfg.schema : undefined - } - - get isTransaction() { - return this.db.isTransaction - } - - assertTransaction() { - assert(this.isTransaction, 'Transaction required') - } - - assertNotTransaction() { - assert(!this.isTransaction, 'Cannot be in a transaction') - } - - async close(): Promise { - if (this.destroyed) return - await this.db.destroy() - this.destroyed = true - } - - async migrateToOrThrow(migration: string) { - if (this.schema) { - await this.db.schema.createSchema(this.schema).ifNotExists().execute() - } - const { error, results } = await this.migrator.migrateTo(migration) - if (error) { - throw error - } - if (!results) { - throw new Error('An unknown failure occurred while migrating') - } - return results - } - - async migrateToLatestOrThrow() { - if (this.schema) { - await this.db.schema.createSchema(this.schema).ifNotExists().execute() - } - const { error, results } = await this.migrator.migrateToLatest() - if (error) { - throw error - } - if (!results) { - throw new Error('An unknown failure occurred while migrating') - } - return results - } - - async maintainMaterializedViews(opts: { - views: string[] - intervalSec: number - signal: AbortSignal - }) { - const { views, intervalSec, signal } = opts - while (!signal.aborted) { - // super basic synchronization by agreeing when the intervals land relative to unix timestamp - const now = Date.now() - const intervalMs = 1000 * intervalSec - const nextIteration = Math.ceil(now / intervalMs) - const nextInMs = nextIteration * intervalMs - now - await wait(nextInMs) - if (signal.aborted) break - await Promise.all( - views.map(async (view) => { - try { - await this.refreshMaterializedView(view) - log.info( - { view, time: new Date().toISOString() }, - 'materialized view refreshed', - ) - } catch (err) { - log.error( - { view, err, time: new Date().toISOString() }, - 'materialized view refresh failed', - ) - } - }), - ) - } - } - - async refreshMaterializedView(view: string) { - const { ref } = this.db.dynamic - await sql`refresh materialized view concurrently ${ref(view)}`.execute( - this.db, - ) - } -} - -export default Database - -export type PgConfig = { - dialect: 'pg' - pool: PgPool - url: string - schema?: string -} - -type PgOptions = { - url: string - pool?: PgPool - schema?: string - poolSize?: number - poolMaxUses?: number - poolIdleTimeoutMs?: number -} - -class LeakyTxPlugin implements KyselyPlugin { - private txOver: boolean - - endTx() { - this.txOver = true - } - - transformQuery(args: PluginTransformQueryArgs): RootOperationNode { - if (this.txOver) { - throw new Error('tx already failed') - } - return args.node - } - - async transformResult( - args: PluginTransformResultArgs, - ): Promise> { - return args.result - } -} - -type TxnEmitter = TypedEmitter - -type TxnEvents = { - commit: () => void -} - -const noopAsync = async () => {} +export * from './primary' +export * from './db' +export * from './coordinator' diff --git a/packages/bsky/src/db/leader.ts b/packages/bsky/src/db/leader.ts index 20b6757a919..ebd44bf98d6 100644 --- a/packages/bsky/src/db/leader.ts +++ b/packages/bsky/src/db/leader.ts @@ -1,9 +1,9 @@ import { PoolClient } from 'pg' -import Database from '.' +import PrimaryDatabase from './primary' export class Leader { session: Session | null = null - constructor(public id: number, public db: Database) {} + constructor(public id: number, public db: PrimaryDatabase) {} async run( task: (ctx: { signal: AbortSignal }) => Promise, @@ -29,7 +29,7 @@ export class Leader { // Postgres implementation uses advisory locking, automatically released by ending connection. - const client = await this.db.cfg.pool.connect() + const client = await this.db.pool.connect() try { const lock = await client.query( 'SELECT pg_try_advisory_lock($1) as acquired', diff --git a/packages/bsky/src/db/migrations/20230627T212437895Z-optional-handle.ts b/packages/bsky/src/db/migrations/20230627T212437895Z-optional-handle.ts new file mode 100644 index 00000000000..74279ef12e1 --- /dev/null +++ b/packages/bsky/src/db/migrations/20230627T212437895Z-optional-handle.ts @@ -0,0 +1,17 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .alterTable('actor') + .alterColumn('handle') + .dropNotNull() + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema + .alterTable('actor') + .alterColumn('handle') + .setNotNull() + .execute() +} diff --git a/packages/bsky/src/db/migrations/20230629T220835893Z-remove-post-hierarchy.ts b/packages/bsky/src/db/migrations/20230629T220835893Z-remove-post-hierarchy.ts new file mode 100644 index 00000000000..36d9b7cd37d --- /dev/null +++ b/packages/bsky/src/db/migrations/20230629T220835893Z-remove-post-hierarchy.ts @@ -0,0 +1,32 @@ +import { Kysely, sql } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema.dropTable('post_hierarchy').execute() + // recreate index that calculates e.g. "replyCount", turning it into a covering index + // for uri so that recursive query for post descendents can use an index-only scan. + await db.schema.dropIndex('post_replyparent_idx').execute() + await sql`create index "post_replyparent_idx" on "post" ("replyParent") include ("uri")`.execute( + db, + ) +} + +export async function down(db: Kysely): Promise { + await db.schema + .createTable('post_hierarchy') + .addColumn('uri', 'varchar', (col) => col.notNull()) + .addColumn('ancestorUri', 'varchar', (col) => col.notNull()) + .addColumn('depth', 'integer', (col) => col.notNull()) + .addPrimaryKeyConstraint('post_hierarchy_pkey', ['uri', 'ancestorUri']) + .execute() + await db.schema + .createIndex('post_hierarchy_ancestoruri_idx') + .on('post_hierarchy') + .column('ancestorUri') + .execute() + await db.schema.dropIndex('post_replyparent_idx').execute() + await db.schema + .createIndex('post_replyparent_idx') + .on('post') + .column('replyParent') + .execute() +} diff --git a/packages/bsky/src/db/migrations/20230703T045536691Z-feed-and-label-indices.ts b/packages/bsky/src/db/migrations/20230703T045536691Z-feed-and-label-indices.ts new file mode 100644 index 00000000000..bf54deeae2a --- /dev/null +++ b/packages/bsky/src/db/migrations/20230703T045536691Z-feed-and-label-indices.ts @@ -0,0 +1,31 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .createIndex('label_cts_idx') + .on('label') + .column('cts') + .execute() + await db.schema.dropIndex('feed_item_originator_idx').execute() + await db.schema + .createIndex('feed_item_originator_cursor_idx') + .on('feed_item') + .columns(['originatorDid', 'sortAt', 'cid']) + .execute() + await db.schema + .createIndex('record_did_idx') + .on('record') + .column('did') + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.dropIndex('label_cts_idx').execute() + await db.schema.dropIndex('feed_item_originator_cursor_idx').execute() + await db.schema + .createIndex('feed_item_originator_idx') + .on('feed_item') + .column('originatorDid') + .execute() + await db.schema.dropIndex('record_did_idx').execute() +} diff --git a/packages/bsky/src/db/migrations/20230720T164800037Z-posts-cursor-idx.ts b/packages/bsky/src/db/migrations/20230720T164800037Z-posts-cursor-idx.ts new file mode 100644 index 00000000000..4bf7539b1c3 --- /dev/null +++ b/packages/bsky/src/db/migrations/20230720T164800037Z-posts-cursor-idx.ts @@ -0,0 +1,19 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .createIndex('post_creator_cursor_idx') + .on('post') + .columns(['creator', 'sortAt', 'cid']) + .execute() + await db.schema.dropIndex('post_creator_idx').execute() +} + +export async function down(db: Kysely): Promise { + await db.schema + .createIndex('post_creator_idx') + .on('post') + .column('creator') + .execute() + await db.schema.dropIndex('post_creator_cursor_idx').execute() +} diff --git a/packages/bsky/src/db/migrations/20230807T035309811Z-feed-item-delete-invite-for-user-idx.ts b/packages/bsky/src/db/migrations/20230807T035309811Z-feed-item-delete-invite-for-user-idx.ts new file mode 100644 index 00000000000..2e1c05ba33a --- /dev/null +++ b/packages/bsky/src/db/migrations/20230807T035309811Z-feed-item-delete-invite-for-user-idx.ts @@ -0,0 +1,14 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + // supports post deletion + await db.schema + .createIndex('feed_item_post_uri_idx') + .on('feed_item') + .column('postUri') + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.dropIndex('feed_item_post_uri_idx').execute() +} diff --git a/packages/bsky/src/db/migrations/20230808T172902639Z-repo-rev.ts b/packages/bsky/src/db/migrations/20230808T172902639Z-repo-rev.ts new file mode 100644 index 00000000000..0e0141b073b --- /dev/null +++ b/packages/bsky/src/db/migrations/20230808T172902639Z-repo-rev.ts @@ -0,0 +1,12 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .alterTable('actor_sync') + .addColumn('repoRev', 'varchar') + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.alterTable('actor_sync').dropColumn('repoRev').execute() +} diff --git a/packages/bsky/src/db/migrations/20230810T203349843Z-action-duration.ts b/packages/bsky/src/db/migrations/20230810T203349843Z-action-duration.ts new file mode 100644 index 00000000000..0530d4d74fd --- /dev/null +++ b/packages/bsky/src/db/migrations/20230810T203349843Z-action-duration.ts @@ -0,0 +1,23 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .alterTable('moderation_action') + .addColumn('durationInHours', 'integer') + .execute() + await db.schema + .alterTable('moderation_action') + .addColumn('expiresAt', 'varchar') + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema + .alterTable('moderation_action') + .dropColumn('durationInHours') + .execute() + await db.schema + .alterTable('moderation_action') + .dropColumn('expiresAt') + .execute() +} diff --git a/packages/bsky/src/db/migrations/20230817T195936007Z-native-notifications.ts b/packages/bsky/src/db/migrations/20230817T195936007Z-native-notifications.ts new file mode 100644 index 00000000000..22cda5b78a4 --- /dev/null +++ b/packages/bsky/src/db/migrations/20230817T195936007Z-native-notifications.ts @@ -0,0 +1,16 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .createTable('notification_push_token') + .addColumn('did', 'varchar', (col) => col.notNull()) + .addColumn('platform', 'varchar', (col) => col.notNull()) + .addColumn('token', 'varchar', (col) => col.notNull()) + .addColumn('appId', 'varchar', (col) => col.notNull()) + .addPrimaryKeyConstraint('notification_push_token_pkey', ['did', 'token']) + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable('notification_push_token').execute() +} diff --git a/packages/bsky/src/db/migrations/20230830T205507322Z-suggested-feeds.ts b/packages/bsky/src/db/migrations/20230830T205507322Z-suggested-feeds.ts new file mode 100644 index 00000000000..fd99206be25 --- /dev/null +++ b/packages/bsky/src/db/migrations/20230830T205507322Z-suggested-feeds.ts @@ -0,0 +1,13 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .createTable('suggested_feed') + .addColumn('uri', 'varchar', (col) => col.primaryKey()) + .addColumn('order', 'integer', (col) => col.notNull()) + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable('suggested_feed').execute() +} diff --git a/packages/bsky/src/db/migrations/20230904T211011773Z-block-lists.ts b/packages/bsky/src/db/migrations/20230904T211011773Z-block-lists.ts new file mode 100644 index 00000000000..e61996c573f --- /dev/null +++ b/packages/bsky/src/db/migrations/20230904T211011773Z-block-lists.ts @@ -0,0 +1,24 @@ +import { Kysely, sql } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .createTable('list_block') + .addColumn('uri', 'varchar', (col) => col.primaryKey()) + .addColumn('cid', 'varchar', (col) => col.notNull()) + .addColumn('creator', 'varchar', (col) => col.notNull()) + .addColumn('subjectUri', 'varchar', (col) => col.notNull()) + .addColumn('createdAt', 'varchar', (col) => col.notNull()) + .addColumn('indexedAt', 'varchar', (col) => col.notNull()) + .addColumn('sortAt', 'varchar', (col) => + col + .generatedAlwaysAs(sql`least("createdAt", "indexedAt")`) + .stored() + .notNull(), + ) + .addUniqueConstraint('list_block_unique_subject', ['creator', 'subjectUri']) + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable('list_block').execute() +} diff --git a/packages/bsky/src/db/migrations/index.ts b/packages/bsky/src/db/migrations/index.ts index e575cd70103..505f7c84909 100644 --- a/packages/bsky/src/db/migrations/index.ts +++ b/packages/bsky/src/db/migrations/index.ts @@ -17,3 +17,13 @@ export * as _20230609T232122649Z from './20230609T232122649Z-actor-deletion-inde export * as _20230610T203555962Z from './20230610T203555962Z-suggested-follows' export * as _20230611T215300060Z from './20230611T215300060Z-actor-state' export * as _20230620T161134972Z from './20230620T161134972Z-post-langs' +export * as _20230627T212437895Z from './20230627T212437895Z-optional-handle' +export * as _20230629T220835893Z from './20230629T220835893Z-remove-post-hierarchy' +export * as _20230703T045536691Z from './20230703T045536691Z-feed-and-label-indices' +export * as _20230720T164800037Z from './20230720T164800037Z-posts-cursor-idx' +export * as _20230807T035309811Z from './20230807T035309811Z-feed-item-delete-invite-for-user-idx' +export * as _20230808T172902639Z from './20230808T172902639Z-repo-rev' +export * as _20230810T203349843Z from './20230810T203349843Z-action-duration' +export * as _20230817T195936007Z from './20230817T195936007Z-native-notifications' +export * as _20230830T205507322Z from './20230830T205507322Z-suggested-feeds' +export * as _20230904T211011773Z from './20230904T211011773Z-block-lists' diff --git a/packages/bsky/src/db/periodic-moderation-action-reversal.ts b/packages/bsky/src/db/periodic-moderation-action-reversal.ts new file mode 100644 index 00000000000..d15cef91afb --- /dev/null +++ b/packages/bsky/src/db/periodic-moderation-action-reversal.ts @@ -0,0 +1,129 @@ +import { wait } from '@atproto/common' +import { Leader } from './leader' +import { dbLogger } from '../logger' +import AppContext from '../context' +import AtpAgent from '@atproto/api' +import { buildBasicAuth } from '../auth' +import { LabelService } from '../services/label' +import { ModerationActionRow } from '../services/moderation' + +export const MODERATION_ACTION_REVERSAL_ID = 1011 + +export class PeriodicModerationActionReversal { + leader = new Leader( + MODERATION_ACTION_REVERSAL_ID, + this.appContext.db.getPrimary(), + ) + destroyed = false + pushAgent?: AtpAgent + + constructor(private appContext: AppContext) { + if (appContext.cfg.moderationActionReverseUrl) { + const url = new URL(appContext.cfg.moderationActionReverseUrl) + this.pushAgent = new AtpAgent({ service: url.origin }) + this.pushAgent.api.setHeader( + 'authorization', + buildBasicAuth(url.username, url.password), + ) + } + } + + // invert label creation & negations + async reverseLabels(labelTxn: LabelService, actionRow: ModerationActionRow) { + let uri: string + let cid: string | null = null + + if (actionRow.subjectUri && actionRow.subjectCid) { + uri = actionRow.subjectUri + cid = actionRow.subjectCid + } else { + uri = actionRow.subjectDid + } + + await labelTxn.formatAndCreate(this.appContext.cfg.labelerDid, uri, cid, { + create: actionRow.negateLabelVals + ? actionRow.negateLabelVals.split(' ') + : undefined, + negate: actionRow.createLabelVals + ? actionRow.createLabelVals.split(' ') + : undefined, + }) + } + + async revertAction(actionRow: ModerationActionRow) { + const reverseAction = { + id: actionRow.id, + createdBy: actionRow.createdBy, + createdAt: new Date(), + reason: `[SCHEDULED_REVERSAL] Reverting action as originally scheduled`, + } + + if (this.pushAgent) { + await this.pushAgent.com.atproto.admin.reverseModerationAction( + reverseAction, + ) + return + } + + await this.appContext.db.getPrimary().transaction(async (dbTxn) => { + const moderationTxn = this.appContext.services.moderation(dbTxn) + await moderationTxn.revertAction(reverseAction) + const labelTxn = this.appContext.services.label(dbTxn) + await this.reverseLabels(labelTxn, actionRow) + }) + } + + async findAndRevertDueActions() { + const moderationService = this.appContext.services.moderation( + this.appContext.db.getPrimary(), + ) + const actionsDueForReversal = + await moderationService.getActionsDueForReversal() + + // We shouldn't have too many actions due for reversal at any given time, so running in parallel is probably fine + // Internally, each reversal runs within its own transaction + await Promise.all(actionsDueForReversal.map(this.revertAction.bind(this))) + } + + async run() { + while (!this.destroyed) { + try { + const { ran } = await this.leader.run(async ({ signal }) => { + while (!signal.aborted) { + // super basic synchronization by agreeing when the intervals land relative to unix timestamp + const now = Date.now() + const intervalMs = 1000 * 60 + const nextIteration = Math.ceil(now / intervalMs) + const nextInMs = nextIteration * intervalMs - now + await wait(nextInMs) + if (signal.aborted) break + await this.findAndRevertDueActions() + } + }) + if (ran && !this.destroyed) { + throw new Error('View maintainer completed, but should be persistent') + } + } catch (err) { + dbLogger.error( + { + err, + lockId: MODERATION_ACTION_REVERSAL_ID, + }, + 'moderation action reversal errored', + ) + } + if (!this.destroyed) { + await wait(10000 + jitter(2000)) + } + } + } + + destroy() { + this.destroyed = true + this.leader.destroy() + } +} + +function jitter(maxMs) { + return Math.round((Math.random() - 0.5) * maxMs * 2) +} diff --git a/packages/bsky/src/db/primary.ts b/packages/bsky/src/db/primary.ts new file mode 100644 index 00000000000..e6e69872fd5 --- /dev/null +++ b/packages/bsky/src/db/primary.ts @@ -0,0 +1,184 @@ +import EventEmitter from 'events' +import { + Migrator, + KyselyPlugin, + PluginTransformQueryArgs, + PluginTransformResultArgs, + RootOperationNode, + QueryResult, + UnknownRow, + sql, +} from 'kysely' +import { Pool as PgPool } from 'pg' +import TypedEmitter from 'typed-emitter' +import { wait } from '@atproto/common' +import DatabaseSchema from './database-schema' +import * as migrations from './migrations' +import { CtxMigrationProvider } from './migrations/provider' +import { dbLogger as log } from '../logger' +import { PgOptions } from './types' +import { Database } from './db' + +export class PrimaryDatabase extends Database { + migrator: Migrator + txEvt = new EventEmitter() as TxnEmitter + destroyed = false + isPrimary = true + + constructor( + public opts: PgOptions, + instances?: { db: DatabaseSchema; pool: PgPool }, + ) { + super(opts, instances) + this.migrator = new Migrator({ + db: this.db, + migrationTableSchema: opts.schema, + provider: new CtxMigrationProvider(migrations, 'pg'), + }) + } + + static is(db: Database): db is PrimaryDatabase { + return db.isPrimary + } + + asPrimary(): PrimaryDatabase { + return this + } + + async transaction(fn: (db: PrimaryDatabase) => Promise): Promise { + const leakyTxPlugin = new LeakyTxPlugin() + const { dbTxn, txRes } = await this.db + .withPlugin(leakyTxPlugin) + .transaction() + .execute(async (txn) => { + const dbTxn = new PrimaryDatabase(this.opts, { + db: txn, + pool: this.pool, + }) + const txRes = await fn(dbTxn) + .catch(async (err) => { + leakyTxPlugin.endTx() + // ensure that all in-flight queries are flushed & the connection is open + await dbTxn.db.getExecutor().provideConnection(noopAsync) + throw err + }) + .finally(() => leakyTxPlugin.endTx()) + return { dbTxn, txRes } + }) + dbTxn?.txEvt.emit('commit') + return txRes + } + + onCommit(fn: () => void) { + this.assertTransaction() + this.txEvt.once('commit', fn) + } + + async close(): Promise { + if (this.destroyed) return + await this.db.destroy() + this.destroyed = true + } + + async migrateToOrThrow(migration: string) { + if (this.schema) { + await this.db.schema.createSchema(this.schema).ifNotExists().execute() + } + const { error, results } = await this.migrator.migrateTo(migration) + if (error) { + throw error + } + if (!results) { + throw new Error('An unknown failure occurred while migrating') + } + return results + } + + async migrateToLatestOrThrow() { + if (this.schema) { + await this.db.schema.createSchema(this.schema).ifNotExists().execute() + } + const { error, results } = await this.migrator.migrateToLatest() + if (error) { + throw error + } + if (!results) { + throw new Error('An unknown failure occurred while migrating') + } + return results + } + + async maintainMaterializedViews(opts: { + views: string[] + intervalSec: number + signal: AbortSignal + }) { + const { views, intervalSec, signal } = opts + while (!signal.aborted) { + // super basic synchronization by agreeing when the intervals land relative to unix timestamp + const now = Date.now() + const intervalMs = 1000 * intervalSec + const nextIteration = Math.ceil(now / intervalMs) + const nextInMs = nextIteration * intervalMs - now + await wait(nextInMs) + if (signal.aborted) break + await Promise.all( + views.map(async (view) => { + try { + await this.refreshMaterializedView(view) + log.info( + { view, time: new Date().toISOString() }, + 'materialized view refreshed', + ) + } catch (err) { + log.error( + { view, err, time: new Date().toISOString() }, + 'materialized view refresh failed', + ) + } + }), + ) + } + } + + async refreshMaterializedView(view: string) { + const { ref } = this.db.dynamic + await sql`refresh materialized view concurrently ${ref(view)}`.execute( + this.db, + ) + } +} + +export default PrimaryDatabase + +// utils +// ------- + +class LeakyTxPlugin implements KyselyPlugin { + private txOver: boolean + + endTx() { + this.txOver = true + } + + transformQuery(args: PluginTransformQueryArgs): RootOperationNode { + if (this.txOver) { + throw new Error('tx already failed') + } + return args.node + } + + async transformResult( + args: PluginTransformResultArgs, + ): Promise> { + return args.result + } +} + +type TxnEmitter = TypedEmitter + +type TxnEvents = { + commit: () => void +} + +const noopAsync = async () => {} diff --git a/packages/bsky/src/db/tables/actor-sync.ts b/packages/bsky/src/db/tables/actor-sync.ts index 0ab0446ee7b..a9e2372ccb2 100644 --- a/packages/bsky/src/db/tables/actor-sync.ts +++ b/packages/bsky/src/db/tables/actor-sync.ts @@ -2,6 +2,7 @@ export interface ActorSync { did: string commitCid: string commitDataCid: string + repoRev: string | null rebaseCount: number tooBigCount: number } diff --git a/packages/bsky/src/db/tables/actor.ts b/packages/bsky/src/db/tables/actor.ts index 5a7c20f9f39..312c5808cab 100644 --- a/packages/bsky/src/db/tables/actor.ts +++ b/packages/bsky/src/db/tables/actor.ts @@ -1,6 +1,6 @@ export interface Actor { did: string - handle: string + handle: string | null indexedAt: string takedownId: number | null // @TODO(bsky) } diff --git a/packages/bsky/src/db/tables/list-block.ts b/packages/bsky/src/db/tables/list-block.ts new file mode 100644 index 00000000000..69936f4d7fd --- /dev/null +++ b/packages/bsky/src/db/tables/list-block.ts @@ -0,0 +1,15 @@ +import { GeneratedAlways } from 'kysely' + +export const tableName = 'list_block' + +export interface ListBlock { + uri: string + cid: string + creator: string + subjectUri: string + createdAt: string + indexedAt: string + sortAt: GeneratedAlways +} + +export type PartialDB = { [tableName]: ListBlock } diff --git a/packages/bsky/src/db/tables/moderation.ts b/packages/bsky/src/db/tables/moderation.ts index 36e888e1294..ef2bd3b5f6c 100644 --- a/packages/bsky/src/db/tables/moderation.ts +++ b/packages/bsky/src/db/tables/moderation.ts @@ -34,6 +34,8 @@ export interface ModerationAction { reversedAt: string | null reversedBy: string | null reversedReason: string | null + durationInHours: number | null + expiresAt: string | null } export interface ModerationActionSubjectBlob { diff --git a/packages/bsky/src/db/tables/notification-push-token.ts b/packages/bsky/src/db/tables/notification-push-token.ts new file mode 100644 index 00000000000..36a82fdddfe --- /dev/null +++ b/packages/bsky/src/db/tables/notification-push-token.ts @@ -0,0 +1,10 @@ +export const tableName = 'notification_push_token' + +export interface NotificationPushToken { + did: string + platform: 'ios' | 'android' | 'web' + token: string + appId: string +} + +export type PartialDB = { [tableName]: NotificationPushToken } diff --git a/packages/bsky/src/db/tables/post-hierarchy.ts b/packages/bsky/src/db/tables/post-hierarchy.ts deleted file mode 100644 index ba7e11ccb1b..00000000000 --- a/packages/bsky/src/db/tables/post-hierarchy.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const tableName = 'post_hierarchy' - -export interface PostHierarchy { - uri: string - ancestorUri: string - depth: number -} - -export type PartialDB = { - [tableName]: PostHierarchy -} diff --git a/packages/bsky/src/db/tables/suggested-feed.ts b/packages/bsky/src/db/tables/suggested-feed.ts new file mode 100644 index 00000000000..f579229793b --- /dev/null +++ b/packages/bsky/src/db/tables/suggested-feed.ts @@ -0,0 +1,10 @@ +export const tableName = 'suggested_feed' + +export interface SuggestedFeed { + uri: string + order: number +} + +export type PartialDB = { + [tableName]: SuggestedFeed +} diff --git a/packages/bsky/src/db/types.ts b/packages/bsky/src/db/types.ts new file mode 100644 index 00000000000..6dd5f084c80 --- /dev/null +++ b/packages/bsky/src/db/types.ts @@ -0,0 +1,10 @@ +import { Pool as PgPool } from 'pg' + +export type PgOptions = { + url: string + pool?: PgPool + schema?: string + poolSize?: number + poolMaxUses?: number + poolIdleTimeoutMs?: number +} diff --git a/packages/bsky/src/db/views.ts b/packages/bsky/src/db/views.ts index 0608cc0dad0..d5aa9941436 100644 --- a/packages/bsky/src/db/views.ts +++ b/packages/bsky/src/db/views.ts @@ -1,7 +1,7 @@ import { jitter, wait } from '@atproto/common' import { Leader } from './leader' import { dbLogger } from '../logger' -import Database from '.' +import { PrimaryDatabase } from '.' export const VIEW_MAINTAINER_ID = 1010 const VIEWS = ['algo_whats_hot_view'] @@ -11,7 +11,7 @@ export class ViewMaintainer { destroyed = false // @NOTE the db must be authed as the owner of the materialized view, per postgres. - constructor(public db: Database, public intervalSec = 60) {} + constructor(public db: PrimaryDatabase, public intervalSec = 60) {} async run() { while (!this.destroyed) { diff --git a/packages/bsky/src/did-cache.ts b/packages/bsky/src/did-cache.ts index da8854871cf..5da647b0108 100644 --- a/packages/bsky/src/did-cache.ts +++ b/packages/bsky/src/did-cache.ts @@ -1,13 +1,15 @@ import PQueue from 'p-queue' import { CacheResult, DidCache, DidDocument } from '@atproto/identity' -import Database from './db' +import { PrimaryDatabase } from './db' import { dbLogger } from './logger' export class DidSqlCache implements DidCache { public pQueue: PQueue | null //null during teardown constructor( - public db: Database, + // @TODO perhaps could use both primary and non-primary. not high enough + // throughput to matter right now. also may just move this over to redis before long! + public db: PrimaryDatabase, public staleTTL: number, public maxTTL: number, ) { diff --git a/packages/bsky/src/feed-gen/best-of-follows.ts b/packages/bsky/src/feed-gen/best-of-follows.ts index ba263b3ee6f..c1d4ee4d21b 100644 --- a/packages/bsky/src/feed-gen/best-of-follows.ts +++ b/packages/bsky/src/feed-gen/best-of-follows.ts @@ -10,10 +10,10 @@ const handler: AlgoHandler = async ( viewer: string, ): Promise => { const { limit, cursor } = params - const feedService = ctx.services.feed(ctx.db) - const graphService = ctx.services.graph(ctx.db) + const db = ctx.db.getReplica('feed') + const feedService = ctx.services.feed(db) - const { ref } = ctx.db.db.dynamic + const { ref } = db.db.dynamic // candidates are ranked within a materialized view by like count, depreciated over time. @@ -30,10 +30,6 @@ const handler: AlgoHandler = async ( .whereRef('follow.subjectDid', '=', 'post.creator'), ), ) - .where((qb) => - graphService.whereNotMuted(qb, viewer, [ref('post.creator')]), - ) - .whereNotExists(graphService.blockQb(viewer, [ref('post.creator')])) .select('candidate.score') .select('candidate.cid') diff --git a/packages/bsky/src/feed-gen/bsky-team.ts b/packages/bsky/src/feed-gen/bsky-team.ts index 8655ab5af2e..3592dd42e26 100644 --- a/packages/bsky/src/feed-gen/bsky-team.ts +++ b/packages/bsky/src/feed-gen/bsky-team.ts @@ -6,45 +6,33 @@ import { AlgoHandler, AlgoResponse } from './types' import { FeedKeyset } from '../api/app/bsky/util/feed' const BSKY_TEAM: NotEmptyArray = [ - 'did:plc:oky5czdrnfjpqslsw2a5iclo', // jay - 'did:plc:yk4dd2qkboz2yv6tpubpc6co', // daniel - 'did:plc:ragtjsm2j2vknwkz3zp4oxrd', // paul - 'did:plc:l3rouwludahu3ui3bt66mfvj', // devin - 'did:plc:tpg43qhh4lw4ksiffs4nbda3', // jake - 'did:plc:44ybard66vv44zksje25o7dz', // bryan - 'did:plc:qjeavhlw222ppsre4rscd3n2', // rose - 'did:plc:vjug55kidv6sye7ykr5faxxn', // emily - 'did:plc:fgsn4gf2dlgnybo4nbej5b2s', // ansh - 'did:plc:vpkhqolt662uhesyj6nxm7ys', // why 'did:plc:z72i7hdynmk6r22z27h6tvur', // @bsky.app 'did:plc:ewvi7nxzyoun6zhxrhs64oiz', // @atproto.com + 'did:plc:eon2iu7v3x2ukgxkqaf7e5np', // @safety.bsky.app ] const handler: AlgoHandler = async ( ctx: AppContext, params: SkeletonParams, - viewer: string, + _viewer: string, ): Promise => { const { limit = 50, cursor } = params - const feedService = ctx.services.feed(ctx.db) - const graphService = ctx.services.graph(ctx.db) + const db = ctx.db.getReplica('feed') + const feedService = ctx.services.feed(db) - const { ref } = ctx.db.db.dynamic + const { ref } = db.db.dynamic const postsQb = feedService .selectPostQb() .where('post.creator', 'in', BSKY_TEAM) - .where((qb) => - graphService.whereNotMuted(qb, viewer, [ref('post.creator')]), - ) - .whereNotExists(graphService.blockQb(viewer, [ref('post.creator')])) const keyset = new FeedKeyset(ref('sortAt'), ref('cid')) - let feedQb = ctx.db.db.selectFrom(postsQb.as('feed_items')).selectAll() + let feedQb = db.db.selectFrom(postsQb.as('feed_items')).selectAll() feedQb = paginate(feedQb, { limit, cursor, keyset }) const feedItems = await feedQb.execute() + return { feedItems, cursor: keyset.packFromResult(feedItems), diff --git a/packages/bsky/src/feed-gen/hot-classic.ts b/packages/bsky/src/feed-gen/hot-classic.ts index 672aee24edb..c042cea7116 100644 --- a/packages/bsky/src/feed-gen/hot-classic.ts +++ b/packages/bsky/src/feed-gen/hot-classic.ts @@ -6,26 +6,18 @@ import { AlgoHandler, AlgoResponse } from './types' import { FeedKeyset } from '../api/app/bsky/util/feed' import { valuesList } from '../db/util' -const NO_WHATS_HOT_LABELS: NotEmptyArray = [ - '!no-promote', - 'corpse', - 'self-harm', - 'porn', - 'sexual', - 'nudity', - 'underwear', -] +const NO_WHATS_HOT_LABELS: NotEmptyArray = ['!no-promote'] const handler: AlgoHandler = async ( ctx: AppContext, params: SkeletonParams, - viewer: string, + _viewer: string, ): Promise => { const { limit = 50, cursor } = params - const feedService = ctx.services.feed(ctx.db) - const graphService = ctx.services.graph(ctx.db) + const db = ctx.db.getReplica('feed') + const feedService = ctx.services.feed(db) - const { ref } = ctx.db.db.dynamic + const { ref } = db.db.dynamic const postsQb = feedService .selectPostQb() @@ -46,17 +38,14 @@ const handler: AlgoHandler = async ( .orWhereRef('label.uri', '=', ref('post_embed_record.embedUri')), ), ) - .where((qb) => - graphService.whereNotMuted(qb, viewer, [ref('post.creator')]), - ) - .whereNotExists(graphService.blockQb(viewer, [ref('post.creator')])) const keyset = new FeedKeyset(ref('sortAt'), ref('cid')) - let feedQb = ctx.db.db.selectFrom(postsQb.as('feed_items')).selectAll() + let feedQb = db.db.selectFrom(postsQb.as('feed_items')).selectAll() feedQb = paginate(feedQb, { limit, cursor, keyset }) const feedItems = await feedQb.execute() + return { feedItems, cursor: keyset.packFromResult(feedItems), diff --git a/packages/bsky/src/feed-gen/index.ts b/packages/bsky/src/feed-gen/index.ts index 73edb2b93de..d00d22c59d9 100644 --- a/packages/bsky/src/feed-gen/index.ts +++ b/packages/bsky/src/feed-gen/index.ts @@ -1,8 +1,7 @@ -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { ids } from '../lexicon/lexicons' import withFriends from './with-friends' import bskyTeam from './bsky-team' -import whatsHot from './whats-hot' import hotClassic from './hot-classic' import bestOfFollows from './best-of-follows' import mutuals from './mutuals' @@ -16,7 +15,6 @@ const feedgenUri = (did, name) => export const makeAlgos = (did: string): MountedAlgos => ({ [feedgenUri(did, 'with-friends')]: withFriends, [feedgenUri(did, 'bsky-team')]: bskyTeam, - [feedgenUri(did, 'whats-hot')]: whatsHot, [feedgenUri(did, 'hot-classic')]: hotClassic, [feedgenUri(did, 'best-of-follows')]: bestOfFollows, [feedgenUri(did, 'mutuals')]: mutuals, diff --git a/packages/bsky/src/feed-gen/mutuals.ts b/packages/bsky/src/feed-gen/mutuals.ts index d81ee46dd2f..65a3311a524 100644 --- a/packages/bsky/src/feed-gen/mutuals.ts +++ b/packages/bsky/src/feed-gen/mutuals.ts @@ -10,12 +10,12 @@ const handler: AlgoHandler = async ( viewer: string, ): Promise => { const { limit = 50, cursor } = params - const feedService = ctx.services.feed(ctx.db) - const graphService = ctx.services.graph(ctx.db) + const db = ctx.db.getReplica('feed') + const feedService = ctx.services.feed(db) - const { ref } = ctx.db.db.dynamic + const { ref } = db.db.dynamic - const mutualsSubquery = ctx.db.db + const mutualsSubquery = db.db .selectFrom('follow') .where('follow.creator', '=', viewer) .whereExists((qb) => @@ -39,14 +39,11 @@ const handler: AlgoHandler = async ( .orWhere('originatorDid', 'in', mutualsSubquery), ) .where('feed_item.sortAt', '>', getFeedDateThreshold(sortFrom)) - .where((qb) => - graphService.whereNotMuted(qb, viewer, [ref('originatorDid')]), - ) - .whereNotExists(graphService.blockQb(viewer, [ref('originatorDid')])) feedQb = paginate(feedQb, { limit, cursor, keyset }) const feedItems = await feedQb.execute() + return { feedItems, cursor: keyset.packFromResult(feedItems), diff --git a/packages/bsky/src/feed-gen/types.ts b/packages/bsky/src/feed-gen/types.ts index e278c9a80d9..11ebf53fb39 100644 --- a/packages/bsky/src/feed-gen/types.ts +++ b/packages/bsky/src/feed-gen/types.ts @@ -1,6 +1,7 @@ import AppContext from '../context' +import { SkeletonFeedPost } from '../lexicon/types/app/bsky/feed/defs' import { QueryParams as SkeletonParams } from '../lexicon/types/app/bsky/feed/getFeedSkeleton' -import { FeedRow } from '../services/types' +import { FeedRow } from '../services/feed' export type AlgoResponse = { feedItems: FeedRow[] @@ -14,3 +15,17 @@ export type AlgoHandler = ( ) => Promise export type MountedAlgos = Record + +export const toSkeletonItem = (feedItem: { + uri: string + postUri: string +}): SkeletonFeedPost => ({ + post: feedItem.postUri, + reason: + feedItem.uri === feedItem.postUri + ? undefined + : { + $type: 'app.bsky.feed.defs#skeletonReasonRepost', + repost: feedItem.uri, + }, +}) diff --git a/packages/bsky/src/feed-gen/whats-hot.ts b/packages/bsky/src/feed-gen/whats-hot.ts index 92771581865..511c767804e 100644 --- a/packages/bsky/src/feed-gen/whats-hot.ts +++ b/packages/bsky/src/feed-gen/whats-hot.ts @@ -6,7 +6,7 @@ import { GenericKeyset, paginate } from '../db/pagination' import AppContext from '../context' import { valuesList } from '../db/util' import { sql } from 'kysely' -import { FeedItemType } from '../services/types' +import { FeedItemType } from '../services/feed/types' const NO_WHATS_HOT_LABELS: NotEmptyArray = [ '!no-promote', @@ -21,16 +21,16 @@ const NO_WHATS_HOT_LABELS: NotEmptyArray = [ const handler: AlgoHandler = async ( ctx: AppContext, params: SkeletonParams, - viewer: string, + _viewer: string, ): Promise => { const { limit, cursor } = params - const graphService = ctx.services.graph(ctx.db) + const db = ctx.db.getReplica('feed') - const { ref } = ctx.db.db.dynamic + const { ref } = db.db.dynamic // candidates are ranked within a materialized view by like count, depreciated over time. - let builder = ctx.db.db + let builder = db.db .selectFrom('algo_whats_hot_view as candidate') .innerJoin('post', 'post.uri', 'candidate.uri') .leftJoin('post_embed_record', 'post_embed_record.postUri', 'candidate.uri') @@ -47,14 +47,9 @@ const handler: AlgoHandler = async ( .orWhereRef('label.uri', '=', ref('post_embed_record.embedUri')), ), ) - .where((qb) => - graphService.whereNotMuted(qb, viewer, [ref('post.creator')]), - ) - .whereNotExists(graphService.blockQb(viewer, [ref('post.creator')])) .select([ sql`${'post'}`.as('type'), 'post.uri as uri', - 'post.cid as cid', 'post.uri as postUri', 'post.creator as originatorDid', 'post.creator as postAuthorDid', diff --git a/packages/bsky/src/feed-gen/with-friends.ts b/packages/bsky/src/feed-gen/with-friends.ts index 6817116f40f..98f784102a5 100644 --- a/packages/bsky/src/feed-gen/with-friends.ts +++ b/packages/bsky/src/feed-gen/with-friends.ts @@ -10,33 +10,26 @@ const handler: AlgoHandler = async ( requester: string, ): Promise => { const { cursor, limit = 50 } = params - const feedService = ctx.services.feed(ctx.db) - const graphService = ctx.services.graph(ctx.db) + const db = ctx.db.getReplica('feed') + const feedService = ctx.services.feed(db) - const { ref } = ctx.db.db.dynamic + const { ref } = db.db.dynamic const keyset = new FeedKeyset(ref('post.indexedAt'), ref('post.cid')) const sortFrom = keyset.unpack(cursor)?.primary let postsQb = feedService .selectPostQb() + .innerJoin('follow', 'follow.subjectDid', 'post.creator') .innerJoin('post_agg', 'post_agg.uri', 'post.uri') .where('post_agg.likeCount', '>=', 5) - .whereExists((qb) => - qb - .selectFrom('follow') - .where('follow.creator', '=', requester) - .whereRef('follow.subjectDid', '=', 'post.creator'), - ) - .where((qb) => - graphService.whereNotMuted(qb, requester, [ref('post.creator')]), - ) - .whereNotExists(graphService.blockQb(requester, [ref('post.creator')])) + .where('follow.creator', '=', requester) .where('post.indexedAt', '>', getFeedDateThreshold(sortFrom)) postsQb = paginate(postsQb, { limit, cursor, keyset, tryIndex: true }) const feedItems = await postsQb.execute() + return { feedItems, cursor: keyset.packFromResult(feedItems), diff --git a/packages/bsky/src/image/invalidator.ts b/packages/bsky/src/image/invalidator.ts index d1319951500..70bf363371d 100644 --- a/packages/bsky/src/image/invalidator.ts +++ b/packages/bsky/src/image/invalidator.ts @@ -1,4 +1,5 @@ import { BlobCache } from './server' +import { ImageUriBuilder } from './uri' // Invalidation is a general interface for propagating an image blob // takedown through any caches where a representation of it may be stored. @@ -15,7 +16,13 @@ export class ImageProcessingServerInvalidator implements ImageInvalidator { paths.map(async (path) => { const [, signature] = path.split('/') if (!signature) throw new Error('Missing signature') - await this.cache.clear(signature) + const options = ImageUriBuilder.getOptions(path) + const cacheKey = [ + options.did, + options.cid.toString(), + options.preset, + ].join('::') + await this.cache.clear(cacheKey) }), ) const rejection = results.find( diff --git a/packages/bsky/src/image/logger.ts b/packages/bsky/src/image/logger.ts index 71893bfae6c..d3d25481f81 100644 --- a/packages/bsky/src/image/logger.ts +++ b/packages/bsky/src/image/logger.ts @@ -1,5 +1,6 @@ import { subsystemLogger } from '@atproto/common' -export const logger = subsystemLogger('bsky:image') +export const logger: ReturnType = + subsystemLogger('bsky:image') export default logger diff --git a/packages/bsky/src/image/server.ts b/packages/bsky/src/image/server.ts index d4509aa8e80..563d9b5bf4b 100644 --- a/packages/bsky/src/image/server.ts +++ b/packages/bsky/src/image/server.ts @@ -24,7 +24,7 @@ export class ImageProcessingServer { uriBuilder: ImageUriBuilder constructor(public cfg: ServerConfig, public cache: BlobCache) { - this.uriBuilder = new ImageUriBuilder('', cfg.imgUriSalt, cfg.imgUriKey) + this.uriBuilder = new ImageUriBuilder('') this.app.get('*', this.handler.bind(this)) this.app.use(errorMiddleware) } @@ -36,12 +36,17 @@ export class ImageProcessingServer { ) { try { const path = req.path - const options = this.uriBuilder.getVerifiedOptions(path) + const options = ImageUriBuilder.getOptions(path) + const cacheKey = [ + options.did, + options.cid.toString(), + options.preset, + ].join('::') // Cached flow try { - const cachedImage = await this.cache.get(options.signature) + const cachedImage = await this.cache.get(cacheKey) res.statusCode = 200 res.setHeader('x-cache', 'hit') res.setHeader('content-type', getMime(options.format)) @@ -69,7 +74,7 @@ export class ImageProcessingServer { // Cache in the background this.cache - .put(options.signature, cloneStream(processedImage)) + .put(cacheKey, cloneStream(processedImage)) .catch((err) => log.error(err, 'failed to cache image')) // Respond res.statusCode = 200 diff --git a/packages/bsky/src/image/uri.ts b/packages/bsky/src/image/uri.ts index 223b2be6f24..5e288e29d10 100644 --- a/packages/bsky/src/image/uri.ts +++ b/packages/bsky/src/image/uri.ts @@ -1,199 +1,67 @@ -import { createHmac } from 'crypto' -import * as uint8arrays from 'uint8arrays' import { CID } from 'multiformats/cid' import { Options } from './util' -// @NOTE if there are any additions here, ensure to include them on ImageUriBuilder.commonSignedUris -type CommonSignedUris = 'avatar' | 'banner' | 'feed_thumbnail' | 'feed_fullsize' +// @NOTE if there are any additions here, ensure to include them on ImageUriBuilder.presets +export type ImagePreset = + | 'avatar' + | 'banner' + | 'feed_thumbnail' + | 'feed_fullsize' -const PATH_REGEX = /^\/(.+)\/plain\/(.+?)\/(.+?)@(.+)$/ +const PATH_REGEX = /^\/(.+?)\/plain\/(.+?)\/(.+?)@(.+?)$/ export class ImageUriBuilder { - public endpoint: string - private salt: Uint8Array - private key: Uint8Array + constructor(public endpoint: string) {} - constructor( - endpoint: string, - salt: Uint8Array | string, - key: Uint8Array | string, - ) { - this.endpoint = endpoint - this.salt = - typeof salt === 'string' ? uint8arrays.fromString(salt, 'hex') : salt - this.key = - typeof key === 'string' ? uint8arrays.fromString(key, 'hex') : key - } - - getSignedPath(opts: Options & BlobLocation): string { - const path = ImageUriBuilder.getPath(opts) - const saltedPath = uint8arrays.concat([ - this.salt, - uint8arrays.fromString(path), - ]) - const sig = hmac(this.key, saltedPath).toString('base64url') - return `/${sig}${path}` - } - - getSignedUri(opts: Options & BlobLocation): string { - const path = this.getSignedPath(opts) - return this.endpoint + path - } - - static commonSignedUris: CommonSignedUris[] = [ + static presets: ImagePreset[] = [ 'avatar', 'banner', 'feed_thumbnail', 'feed_fullsize', ] - getCommonSignedUri( - id: CommonSignedUris, - did: string, - cid: string | CID, - ): string { - if (id === 'avatar') { - return this.getSignedUri({ - did, - cid: typeof cid === 'string' ? CID.parse(cid) : cid, - format: 'jpeg', - fit: 'cover', - height: 1000, - width: 1000, - min: true, - }) - } else if (id === 'banner') { - return this.getSignedUri({ - did, - cid: typeof cid === 'string' ? CID.parse(cid) : cid, - format: 'jpeg', - fit: 'cover', - height: 1000, - width: 3000, - min: true, - }) - } else if (id === 'feed_fullsize') { - return this.getSignedUri({ - did, - cid: typeof cid === 'string' ? CID.parse(cid) : cid, - format: 'jpeg', - fit: 'inside', - height: 2000, - width: 2000, - min: true, - }) - } else if (id === 'feed_thumbnail') { - return this.getSignedUri({ + getPresetUri(id: ImagePreset, did: string, cid: string | CID): string { + const options = presets[id] + if (!options) { + throw new Error(`Unrecognized requested common uri type: ${id}`) + } + return ( + this.endpoint + + ImageUriBuilder.getPath({ + preset: id, did, cid: typeof cid === 'string' ? CID.parse(cid) : cid, - format: 'jpeg', - fit: 'inside', - height: 1000, - width: 1000, - min: true, }) - } else { - const exhaustiveCheck: never = id - throw new Error( - `Unrecognized requested common uri type: ${exhaustiveCheck}`, - ) - } - } - - getVerifiedOptions( - path: string, - ): Options & BlobLocation & { signature: string } { - if (path.at(0) !== '/') { - throw new BadPathError('Invalid path: does not start with a slash') - } - const pathParts = path.split('/') // ['', sig, 'rs:fill:...', ...] - const [sig] = pathParts.splice(1, 1) // ['', 'rs:fill:...', ...] - const unsignedPath = pathParts.join('/') - if (!sig || sig.includes(':')) { - throw new BadPathError('Invalid path: missing signature') - } - const saltedPath = uint8arrays.concat([ - this.salt, - uint8arrays.fromString(unsignedPath), - ]) - const validSig = hmac(this.key, saltedPath).toString('base64url') - if (sig !== validSig) { - throw new BadPathError('Invalid path: bad signature') - } - const options = ImageUriBuilder.getOptions(unsignedPath) - return { - signature: validSig, - ...options, - } + ) } - static getPath(opts: Options & BlobLocation) { - const fit = opts.fit === 'inside' ? 'fit' : 'fill' // fit default is 'cover' - const enlarge = opts.min === true ? 1 : 0 // min default is false - const resize = `rs:${fit}:${opts.width}:${opts.height}:${enlarge}:0` // final ':0' is for interop with imgproxy - const minWidth = - opts.min && typeof opts.min === 'object' ? `mw:${opts.min.width}` : null - const minHeight = - opts.min && typeof opts.min === 'object' ? `mh:${opts.min.height}` : null - const quality = opts.quality ? `q:${opts.quality}` : null - return ( - `/` + - [resize, minWidth, minHeight, quality].filter(Boolean).join('/') + - `/plain/${opts.did}/${opts.cid.toString()}@${opts.format}` - ) + static getPath(opts: { preset: ImagePreset } & BlobLocation) { + const { format } = presets[opts.preset] + return `/${opts.preset}/plain/${opts.did}/${opts.cid.toString()}@${format}` } - static getOptions(path: string): Options & BlobLocation { + static getOptions( + path: string, + ): Options & BlobLocation & { preset: ImagePreset } { const match = path.match(PATH_REGEX) if (!match) { throw new BadPathError('Invalid path') } - const [, partsStr, did, cid, format] = match - if (format !== 'png' && format !== 'jpeg') { - throw new BadPathError('Invalid path: bad format') - } - const parts = partsStr.split('/') - const resizePart = parts.find((part) => part.startsWith('rs:')) - const qualityPart = parts.find((part) => part.startsWith('q:')) - const minWidthPart = parts.find((part) => part.startsWith('mw:')) - const minHeightPart = parts.find((part) => part.startsWith('mh:')) - const [, fit, width, height, enlarge] = resizePart?.split(':') ?? [] - const [, quality] = qualityPart?.split(':') ?? [] - const [, minWidth] = minWidthPart?.split(':') ?? [] - const [, minHeight] = minHeightPart?.split(':') ?? [] - if (fit !== 'fill' && fit !== 'fit') { - throw new BadPathError('Invalid path: bad resize fit param') - } - if (isNaN(toInt(width)) || isNaN(toInt(height))) { - throw new BadPathError('Invalid path: bad resize height/width param') - } - if (enlarge !== '0' && enlarge !== '1') { - throw new BadPathError('Invalid path: bad resize enlarge param') - } - if (quality && isNaN(toInt(quality))) { - throw new BadPathError('Invalid path: bad quality param') + const [, presetUnsafe, did, cid, formatUnsafe] = match + if (!(ImageUriBuilder.presets as string[]).includes(presetUnsafe)) { + throw new BadPathError('Invalid path: bad preset') } - if ( - (!minWidth && minHeight) || - (minWidth && !minHeight) || - (minWidth && isNaN(toInt(minWidth))) || - (minHeight && isNaN(toInt(minHeight))) || - (enlarge === '1' && (minHeight || minHeight)) - ) { - throw new BadPathError('Invalid path: bad min width/height param') + if (formatUnsafe !== 'jpeg' && formatUnsafe !== 'png') { + throw new BadPathError('Invalid path: bad format') } + const preset = presetUnsafe as ImagePreset + const format = formatUnsafe as Options['format'] return { + ...presets[preset], did, cid: CID.parse(cid), + preset, format, - height: toInt(height), - width: toInt(width), - fit: fit === 'fill' ? 'cover' : 'inside', - quality: quality ? toInt(quality) : undefined, - min: - minWidth && minHeight - ? { width: toInt(minWidth), height: toInt(minHeight) } - : enlarge === '1', } } } @@ -202,13 +70,33 @@ type BlobLocation = { cid: CID; did: string } export class BadPathError extends Error {} -function toInt(str: string) { - if (!/^\d+$/.test(str)) { - return NaN // String must be all numeric - } - return parseInt(str, 10) -} - -function hmac(key: Uint8Array, message: Uint8Array) { - return createHmac('sha256', key).update(message).digest() +export const presets: Record = { + avatar: { + format: 'jpeg', + fit: 'cover', + height: 1000, + width: 1000, + min: true, + }, + banner: { + format: 'jpeg', + fit: 'cover', + height: 1000, + width: 3000, + min: true, + }, + feed_thumbnail: { + format: 'jpeg', + fit: 'inside', + height: 2000, + width: 2000, + min: true, + }, + feed_fullsize: { + format: 'jpeg', + fit: 'inside', + height: 1000, + width: 1000, + min: true, + }, } diff --git a/packages/bsky/src/index.ts b/packages/bsky/src/index.ts index 3805f9a3376..b469c2f7b17 100644 --- a/packages/bsky/src/index.ts +++ b/packages/bsky/src/index.ts @@ -4,9 +4,10 @@ import { AddressInfo } from 'net' import events from 'events' import { createHttpTerminator, HttpTerminator } from 'http-terminator' import cors from 'cors' +import compression from 'compression' import { IdResolver } from '@atproto/identity' -import API, { health, blobResolver } from './api' -import Database from './db' +import API, { health, wellKnown, blobResolver } from './api' +import { DatabaseCoordinator } from './db' import * as error from './error' import { dbLogger, loggerMiddleware } from './logger' import { ServerConfig } from './config' @@ -15,44 +16,42 @@ import { ImageUriBuilder } from './image/uri' import { BlobDiskCache, ImageProcessingServer } from './image/server' import { createServices } from './services' import AppContext from './context' -import { RepoSubscription } from './subscription/repo' import DidSqlCache from './did-cache' import { ImageInvalidator, ImageProcessingServerInvalidator, } from './image/invalidator' -import { HiveLabeler, KeywordLabeler, Labeler } from './labeler' import { BackgroundQueue } from './background' import { MountedAlgos } from './feed-gen/types' +import { LabelCache } from './label-cache' +import { NotificationServer } from './notifications' export type { ServerConfigValues } from './config' export type { MountedAlgos } from './feed-gen/types' export { ServerConfig } from './config' -export { Database } from './db' +export { Database, PrimaryDatabase, DatabaseCoordinator } from './db' +export { PeriodicModerationActionReversal } from './db/periodic-moderation-action-reversal' +export { Redis } from './redis' export { ViewMaintainer } from './db/views' export { AppContext } from './context' export { makeAlgos } from './feed-gen' +export * from './indexer' +export * from './ingester' export class BskyAppView { public ctx: AppContext public app: express.Application - public sub?: RepoSubscription public server?: http.Server private terminator?: HttpTerminator private dbStatsInterval: NodeJS.Timer - constructor(opts: { - ctx: AppContext - app: express.Application - sub?: RepoSubscription - }) { + constructor(opts: { ctx: AppContext; app: express.Application }) { this.ctx = opts.ctx this.app = opts.app - this.sub = opts.sub } static create(opts: { - db: Database + db: DatabaseCoordinator config: ServerConfig imgInvalidator?: ImageInvalidator algos?: MountedAlgos @@ -62,18 +61,21 @@ export class BskyAppView { const app = express() app.use(cors()) app.use(loggerMiddleware) + app.use(compression()) const didCache = new DidSqlCache( - db, + db.getPrimary(), config.didCacheStaleTTL, config.didCacheMaxTTL, ) - const idResolver = new IdResolver({ plcUrl: config.didPlcUrl, didCache }) + const idResolver = new IdResolver({ + plcUrl: config.didPlcUrl, + didCache, + backupNameservers: config.handleResolveNameservers, + }) const imgUriBuilder = new ImageUriBuilder( - config.imgUriEndpoint || `${config.publicUrl}/image`, - config.imgUriSalt, - config.imgUriKey, + config.imgUriEndpoint || `${config.publicUrl}/img`, ) let imgProcessingServer: ImageProcessingServer | undefined @@ -95,32 +97,14 @@ export class BskyAppView { throw new Error('Missing appview image invalidator') } - const backgroundQueue = new BackgroundQueue(db) - - // @TODO background labeling tasks - let labeler: Labeler - if (config.hiveApiKey) { - labeler = new HiveLabeler(config.hiveApiKey, { - db, - cfg: config, - idResolver, - backgroundQueue, - }) - } else { - labeler = new KeywordLabeler({ - db, - cfg: config, - idResolver, - backgroundQueue, - }) - } + const backgroundQueue = new BackgroundQueue(db.getPrimary()) + const labelCache = new LabelCache(db.getPrimary()) + const notifServer = new NotificationServer(db.getPrimary()) const services = createServices({ imgUriBuilder, imgInvalidator, - idResolver, - labeler, - backgroundQueue, + labelCache, }) const ctx = new AppContext({ @@ -130,9 +114,10 @@ export class BskyAppView { imgUriBuilder, idResolver, didCache, - labeler, + labelCache, backgroundQueue, algos, + notifServer, }) let server = createServer({ @@ -147,29 +132,39 @@ export class BskyAppView { server = API(server, ctx) app.use(health.createRouter(ctx)) + app.use(wellKnown.createRouter(ctx)) app.use(blobResolver.createRouter(ctx)) if (imgProcessingServer) { - app.use('/image', imgProcessingServer.app) + app.use('/img', imgProcessingServer.app) } app.use(server.xrpc.router) app.use(error.handler) - const sub = config.repoProvider - ? new RepoSubscription(ctx, config.repoProvider, config.repoSubLockId) - : undefined - - return new BskyAppView({ ctx, app, sub }) + return new BskyAppView({ ctx, app }) } async start(): Promise { const { db, backgroundQueue } = this.ctx - const { pool } = db.cfg + const primary = db.getPrimary() + const replicas = db.getReplicas() this.dbStatsInterval = setInterval(() => { dbLogger.info( { - idleCount: pool.idleCount, - totalCount: pool.totalCount, - waitingCount: pool.waitingCount, + idleCount: replicas.reduce( + (tot, replica) => tot + replica.pool.idleCount, + 0, + ), + totalCount: replicas.reduce( + (tot, replica) => tot + replica.pool.totalCount, + 0, + ), + waitingCount: replicas.reduce( + (tot, replica) => tot + replica.pool.waitingCount, + 0, + ), + primaryIdleCount: primary.pool.idleCount, + primaryTotalCount: primary.pool.totalCount, + primaryWaitingCount: primary.pool.waitingCount, }, 'db pool stats', ) @@ -181,22 +176,23 @@ export class BskyAppView { 'background queue stats', ) }, 10000) + this.ctx.labelCache.start() const server = this.app.listen(this.ctx.cfg.port) this.server = server + server.keepAliveTimeout = 90000 this.terminator = createHttpTerminator({ server }) await events.once(server, 'listening') const { port } = server.address() as AddressInfo this.ctx.cfg.assignPort(port) - this.sub?.run() // Don't await, backgrounded return server } - async destroy(): Promise { + async destroy(opts?: { skipDb: boolean }): Promise { + this.ctx.labelCache.stop() await this.ctx.didCache.destroy() - await this.sub?.destroy() await this.terminator?.terminate() await this.ctx.backgroundQueue.destroy() - await this.ctx.db.close() + if (!opts?.skipDb) await this.ctx.db.close() clearInterval(this.dbStatsInterval) } } diff --git a/packages/bsky/src/indexer/config.ts b/packages/bsky/src/indexer/config.ts new file mode 100644 index 00000000000..dd8b9ab89d5 --- /dev/null +++ b/packages/bsky/src/indexer/config.ts @@ -0,0 +1,263 @@ +import assert from 'assert' +import { DAY, HOUR, parseIntWithFallback } from '@atproto/common' + +export interface IndexerConfigValues { + version: string + dbPostgresUrl: string + dbPostgresSchema?: string + redisHost?: string // either set redis host, or both sentinel name and hosts + redisSentinelName?: string + redisSentinelHosts?: string[] + redisPassword?: string + didPlcUrl: string + didCacheStaleTTL: number + didCacheMaxTTL: number + handleResolveNameservers?: string[] + labelerDid: string + hiveApiKey?: string + abyssEndpoint?: string + abyssPassword?: string + imgUriEndpoint?: string + fuzzyMatchB64?: string + fuzzyFalsePositiveB64?: string + labelerKeywords: Record + moderationPushUrl?: string + indexerConcurrency?: number + indexerPartitionIds: number[] + indexerPartitionBatchSize?: number + indexerSubLockId?: number + indexerPort?: number + ingesterPartitionCount: number + indexerNamespace?: string + pushNotificationEndpoint?: string +} + +export class IndexerConfig { + constructor(private cfg: IndexerConfigValues) {} + + static readEnv(overrides?: Partial) { + const version = process.env.BSKY_VERSION || '0.0.0' + const dbPostgresUrl = + overrides?.dbPostgresUrl || process.env.DB_PRIMARY_POSTGRES_URL + const dbPostgresSchema = + overrides?.dbPostgresSchema || process.env.DB_POSTGRES_SCHEMA + const redisHost = + overrides?.redisHost || process.env.REDIS_HOST || undefined + const redisSentinelName = + overrides?.redisSentinelName || + process.env.REDIS_SENTINEL_NAME || + undefined + const redisSentinelHosts = + overrides?.redisSentinelHosts || + (process.env.REDIS_SENTINEL_HOSTS + ? process.env.REDIS_SENTINEL_HOSTS.split(',') + : []) + const redisPassword = + overrides?.redisPassword || process.env.REDIS_PASSWORD || undefined + const didPlcUrl = process.env.DID_PLC_URL || 'http://localhost:2582' + const didCacheStaleTTL = parseIntWithFallback( + process.env.DID_CACHE_STALE_TTL, + HOUR, + ) + const didCacheMaxTTL = parseIntWithFallback( + process.env.DID_CACHE_MAX_TTL, + DAY, + ) + const handleResolveNameservers = process.env.HANDLE_RESOLVE_NAMESERVERS + ? process.env.HANDLE_RESOLVE_NAMESERVERS.split(',') + : [] + const labelerDid = process.env.LABELER_DID || 'did:example:labeler' + const moderationPushUrl = + overrides?.moderationPushUrl || + process.env.MODERATION_PUSH_URL || + undefined + const hiveApiKey = process.env.HIVE_API_KEY || undefined + const abyssEndpoint = process.env.ABYSS_ENDPOINT + const abyssPassword = process.env.ABYSS_PASSWORD + const imgUriEndpoint = process.env.IMG_URI_ENDPOINT + const indexerPartitionIds = + overrides?.indexerPartitionIds || + (process.env.INDEXER_PARTITION_IDS + ? process.env.INDEXER_PARTITION_IDS.split(',').map((n) => + parseInt(n, 10), + ) + : []) + const indexerPartitionBatchSize = maybeParseInt( + process.env.INDEXER_PARTITION_BATCH_SIZE, + ) + const indexerConcurrency = maybeParseInt(process.env.INDEXER_CONCURRENCY) + const indexerNamespace = overrides?.indexerNamespace + const indexerSubLockId = maybeParseInt(process.env.INDEXER_SUB_LOCK_ID) + const indexerPort = maybeParseInt(process.env.INDEXER_PORT) + const ingesterPartitionCount = + maybeParseInt(process.env.INGESTER_PARTITION_COUNT) ?? 64 + const labelerKeywords = {} + const fuzzyMatchB64 = process.env.FUZZY_MATCH_B64 || undefined + const fuzzyFalsePositiveB64 = + process.env.FUZZY_FALSE_POSITIVE_B64 || undefined + const pushNotificationEndpoint = process.env.PUSH_NOTIFICATION_ENDPOINT + assert(dbPostgresUrl) + assert(redisHost || (redisSentinelName && redisSentinelHosts?.length)) + assert(indexerPartitionIds.length > 0) + return new IndexerConfig({ + version, + dbPostgresUrl, + dbPostgresSchema, + redisHost, + redisSentinelName, + redisSentinelHosts, + redisPassword, + didPlcUrl, + didCacheStaleTTL, + didCacheMaxTTL, + handleResolveNameservers, + labelerDid, + moderationPushUrl, + hiveApiKey, + abyssEndpoint, + abyssPassword, + imgUriEndpoint, + indexerPartitionIds, + indexerConcurrency, + indexerPartitionBatchSize, + indexerNamespace, + indexerSubLockId, + indexerPort, + ingesterPartitionCount, + labelerKeywords, + fuzzyMatchB64, + fuzzyFalsePositiveB64, + pushNotificationEndpoint, + ...stripUndefineds(overrides ?? {}), + }) + } + + get version() { + return this.cfg.version + } + + get dbPostgresUrl() { + return this.cfg.dbPostgresUrl + } + + get dbPostgresSchema() { + return this.cfg.dbPostgresSchema + } + + get redisHost() { + return this.cfg.redisHost + } + + get redisSentinelName() { + return this.cfg.redisSentinelName + } + + get redisSentinelHosts() { + return this.cfg.redisSentinelHosts + } + + get redisPassword() { + return this.cfg.redisPassword + } + + get didPlcUrl() { + return this.cfg.didPlcUrl + } + + get didCacheStaleTTL() { + return this.cfg.didCacheStaleTTL + } + + get didCacheMaxTTL() { + return this.cfg.didCacheMaxTTL + } + + get handleResolveNameservers() { + return this.cfg.handleResolveNameservers + } + + get labelerDid() { + return this.cfg.labelerDid + } + + get moderationPushUrl() { + return this.cfg.moderationPushUrl + } + + get hiveApiKey() { + return this.cfg.hiveApiKey + } + + get abyssEndpoint() { + return this.cfg.abyssEndpoint + } + + get abyssPassword() { + return this.cfg.abyssPassword + } + + get imgUriEndpoint() { + return this.cfg.imgUriEndpoint + } + + get indexerConcurrency() { + return this.cfg.indexerConcurrency + } + + get indexerPartitionIds() { + return this.cfg.indexerPartitionIds + } + + get indexerPartitionBatchSize() { + return this.cfg.indexerPartitionBatchSize + } + + get indexerNamespace() { + return this.cfg.indexerNamespace + } + + get indexerSubLockId() { + return this.cfg.indexerSubLockId + } + + get indexerPort() { + return this.cfg.indexerPort + } + + get ingesterPartitionCount() { + return this.cfg.ingesterPartitionCount + } + + get labelerKeywords() { + return this.cfg.labelerKeywords + } + + get fuzzyMatchB64() { + return this.cfg.fuzzyMatchB64 + } + + get fuzzyFalsePositiveB64() { + return this.cfg.fuzzyFalsePositiveB64 + } + + get pushNotificationEndpoint() { + return this.cfg.pushNotificationEndpoint + } +} + +function stripUndefineds( + obj: Record, +): Record { + const result = {} + Object.entries(obj).forEach(([key, val]) => { + if (val !== undefined) { + result[key] = val + } + }) + return result +} + +function maybeParseInt(str) { + const parsed = parseInt(str) + return isNaN(parsed) ? undefined : parsed +} diff --git a/packages/bsky/src/indexer/context.ts b/packages/bsky/src/indexer/context.ts new file mode 100644 index 00000000000..e7fe24580fa --- /dev/null +++ b/packages/bsky/src/indexer/context.ts @@ -0,0 +1,57 @@ +import { IdResolver } from '@atproto/identity' +import { PrimaryDatabase } from '../db' +import { IndexerConfig } from './config' +import { Services } from './services' +import { BackgroundQueue } from '../background' +import DidSqlCache from '../did-cache' +import { Redis } from '../redis' +import { AutoModerator } from '../auto-moderator' + +export class IndexerContext { + constructor( + private opts: { + db: PrimaryDatabase + redis: Redis + cfg: IndexerConfig + services: Services + idResolver: IdResolver + didCache: DidSqlCache + backgroundQueue: BackgroundQueue + autoMod: AutoModerator + }, + ) {} + + get db(): PrimaryDatabase { + return this.opts.db + } + + get redis(): Redis { + return this.opts.redis + } + + get cfg(): IndexerConfig { + return this.opts.cfg + } + + get services(): Services { + return this.opts.services + } + + get idResolver(): IdResolver { + return this.opts.idResolver + } + + get didCache(): DidSqlCache { + return this.opts.didCache + } + + get backgroundQueue(): BackgroundQueue { + return this.opts.backgroundQueue + } + + get autoMod(): AutoModerator { + return this.opts.autoMod + } +} + +export default IndexerContext diff --git a/packages/bsky/src/indexer/index.ts b/packages/bsky/src/indexer/index.ts new file mode 100644 index 00000000000..ed8188d353b --- /dev/null +++ b/packages/bsky/src/indexer/index.ts @@ -0,0 +1,148 @@ +import express from 'express' +import { IdResolver } from '@atproto/identity' +import { BackgroundQueue } from '../background' +import { PrimaryDatabase } from '../db' +import DidSqlCache from '../did-cache' +import log from './logger' +import { dbLogger } from '../logger' +import { IndexerConfig } from './config' +import { IndexerContext } from './context' +import { createServices } from './services' +import { IndexerSubscription } from './subscription' +import { AutoModerator } from '../auto-moderator' +import { Redis } from '../redis' +import { NotificationServer } from '../notifications' +import { CloseFn, createServer, startServer } from './server' +import { ImageUriBuilder } from '../image/uri' +import { ImageInvalidator } from '../image/invalidator' + +export { IndexerConfig } from './config' +export type { IndexerConfigValues } from './config' + +export class BskyIndexer { + public ctx: IndexerContext + public sub: IndexerSubscription + public app: express.Application + private closeServer?: CloseFn + private dbStatsInterval: NodeJS.Timer + private subStatsInterval: NodeJS.Timer + + constructor(opts: { + ctx: IndexerContext + sub: IndexerSubscription + app: express.Application + }) { + this.ctx = opts.ctx + this.sub = opts.sub + this.app = opts.app + } + + static create(opts: { + db: PrimaryDatabase + redis: Redis + cfg: IndexerConfig + imgInvalidator?: ImageInvalidator + }): BskyIndexer { + const { db, redis, cfg } = opts + const didCache = new DidSqlCache( + db, + cfg.didCacheStaleTTL, + cfg.didCacheMaxTTL, + ) + const idResolver = new IdResolver({ + plcUrl: cfg.didPlcUrl, + didCache, + backupNameservers: cfg.handleResolveNameservers, + }) + const backgroundQueue = new BackgroundQueue(db) + + const imgUriBuilder = cfg.imgUriEndpoint + ? new ImageUriBuilder(cfg.imgUriEndpoint) + : undefined + const imgInvalidator = opts.imgInvalidator + const autoMod = new AutoModerator({ + db, + idResolver, + cfg, + backgroundQueue, + imgUriBuilder, + imgInvalidator, + }) + + const notifServer = cfg.pushNotificationEndpoint + ? new NotificationServer(db, cfg.pushNotificationEndpoint) + : undefined + const services = createServices({ + idResolver, + autoMod, + backgroundQueue, + notifServer, + }) + const ctx = new IndexerContext({ + db, + redis, + cfg, + services, + idResolver, + didCache, + backgroundQueue, + autoMod, + }) + const sub = new IndexerSubscription(ctx, { + partitionIds: cfg.indexerPartitionIds, + partitionBatchSize: cfg.indexerPartitionBatchSize, + concurrency: cfg.indexerConcurrency, + subLockId: cfg.indexerSubLockId, + }) + + const app = createServer(sub, cfg) + + return new BskyIndexer({ ctx, sub, app }) + } + + async start() { + const { db, backgroundQueue } = this.ctx + const pool = db.pool + this.dbStatsInterval = setInterval(() => { + dbLogger.info( + { + idleCount: pool.idleCount, + totalCount: pool.totalCount, + waitingCount: pool.waitingCount, + }, + 'db pool stats', + ) + dbLogger.info( + { + runningCount: backgroundQueue.queue.pending, + waitingCount: backgroundQueue.queue.size, + }, + 'background queue stats', + ) + }, 10000) + this.subStatsInterval = setInterval(() => { + log.info( + { + processedCount: this.sub.processedCount, + runningCount: this.sub.repoQueue.main.pending, + waitingCount: this.sub.repoQueue.main.size, + }, + 'indexer stats', + ) + }, 500) + this.sub.run() + this.closeServer = startServer(this.app, this.ctx.cfg.indexerPort) + return this + } + + async destroy(opts?: { skipDb: boolean; skipRedis: true }): Promise { + if (this.closeServer) await this.closeServer() + await this.sub.destroy() + clearInterval(this.subStatsInterval) + if (!opts?.skipRedis) await this.ctx.redis.destroy() + if (!opts?.skipDb) await this.ctx.db.close() + clearInterval(this.dbStatsInterval) + } +} + +export default BskyIndexer diff --git a/packages/bsky/src/indexer/logger.ts b/packages/bsky/src/indexer/logger.ts new file mode 100644 index 00000000000..45752727f99 --- /dev/null +++ b/packages/bsky/src/indexer/logger.ts @@ -0,0 +1,6 @@ +import { subsystemLogger } from '@atproto/common' + +const logger: ReturnType = + subsystemLogger('bsky:indexer') + +export default logger diff --git a/packages/bsky/src/indexer/server.ts b/packages/bsky/src/indexer/server.ts new file mode 100644 index 00000000000..dfafb741eb4 --- /dev/null +++ b/packages/bsky/src/indexer/server.ts @@ -0,0 +1,46 @@ +import express from 'express' +import { IndexerSubscription } from './subscription' +import { IndexerConfig } from './config' +import { randomIntFromSeed } from '@atproto/crypto' + +export type CloseFn = () => Promise + +export const createServer = ( + sub: IndexerSubscription, + cfg: IndexerConfig, +): express.Application => { + const app = express() + app.post('/reprocess/:did', async (req, res) => { + const did = req.params.did + try { + const partition = await randomIntFromSeed(did, cfg.ingesterPartitionCount) + const supportedPartition = cfg.indexerPartitionIds.includes(partition) + if (!supportedPartition) { + return res.status(400).send(`unsupported partition: ${partition}`) + } + } catch (err) { + return res.status(500).send('could not calculate partition') + } + await sub.requestReprocess(req.params.did) + res.sendStatus(200) + }) + return app +} + +export const startServer = ( + app: express.Application, + port?: number, +): CloseFn => { + const server = app.listen(port) + return () => { + return new Promise((resolve, reject) => { + server.close((err) => { + if (err) { + reject(err) + } else { + resolve() + } + }) + }) + } +} diff --git a/packages/bsky/src/indexer/services.ts b/packages/bsky/src/indexer/services.ts new file mode 100644 index 00000000000..df173352046 --- /dev/null +++ b/packages/bsky/src/indexer/services.ts @@ -0,0 +1,32 @@ +import { IdResolver } from '@atproto/identity' +import { PrimaryDatabase } from '../db' +import { BackgroundQueue } from '../background' +import { IndexingService } from '../services/indexing' +import { LabelService } from '../services/label' +import { NotificationServer } from '../notifications' +import { AutoModerator } from '../auto-moderator' + +export function createServices(resources: { + idResolver: IdResolver + autoMod: AutoModerator + backgroundQueue: BackgroundQueue + notifServer?: NotificationServer +}): Services { + const { idResolver, autoMod, backgroundQueue, notifServer } = resources + return { + indexing: IndexingService.creator( + idResolver, + autoMod, + backgroundQueue, + notifServer, + ), + label: LabelService.creator(null), + } +} + +export type Services = { + indexing: FromDbPrimary + label: FromDbPrimary +} + +type FromDbPrimary = (db: PrimaryDatabase) => T diff --git a/packages/bsky/src/indexer/subscription.ts b/packages/bsky/src/indexer/subscription.ts new file mode 100644 index 00000000000..76c9b7297df --- /dev/null +++ b/packages/bsky/src/indexer/subscription.ts @@ -0,0 +1,365 @@ +import assert from 'node:assert' +import { CID } from 'multiformats/cid' +import { AtUri } from '@atproto/syntax' +import { cborDecode, wait } from '@atproto/common' +import { DisconnectError } from '@atproto/xrpc-server' +import { + WriteOpAction, + readCarWithRoot, + cborToLexRecord, + def, + Commit, +} from '@atproto/repo' +import { ValidationError } from '@atproto/lexicon' +import * as message from '../lexicon/types/com/atproto/sync/subscribeRepos' +import { Leader } from '../db/leader' +import { IndexingService } from '../services/indexing' +import log from './logger' +import { + ConsecutiveItem, + ConsecutiveList, + LatestQueue, + PartitionedQueue, + PerfectMap, + ProcessableMessage, + jitter, + loggableMessage, + strToInt, +} from '../subscription/util' +import IndexerContext from './context' + +export const INDEXER_SUB_LOCK_ID = 1200 // need one per partition + +export class IndexerSubscription { + destroyed = false + leader = new Leader(this.opts.subLockId || INDEXER_SUB_LOCK_ID, this.ctx.db) + processedCount = 0 + repoQueue = new PartitionedQueue({ + concurrency: this.opts.concurrency ?? Infinity, + }) + partitions = new PerfectMap() + partitionIds = this.opts.partitionIds + indexingSvc: IndexingService + + constructor( + public ctx: IndexerContext, + public opts: { + partitionIds: number[] + subLockId?: number + concurrency?: number + partitionBatchSize?: number + }, + ) { + this.indexingSvc = ctx.services.indexing(ctx.db) + } + + async processEvents(opts: { signal: AbortSignal }) { + const done = () => this.destroyed || opts.signal.aborted + while (!done()) { + const results = await this.ctx.redis.readStreams( + this.partitionIds.map((id) => ({ + key: partitionKey(id), + cursor: this.partitions.get(id).cursor, + })), + { + blockMs: 1000, + count: this.opts.partitionBatchSize ?? 50, // events per stream + }, + ) + if (done()) break + for (const { key, messages } of results) { + const partition = this.partitions.get(partitionId(key)) + for (const msg of messages) { + const seq = strToInt(msg.cursor) + const envelope = getEnvelope(msg.contents) + partition.cursor = seq + const item = partition.consecutive.push(seq) + this.repoQueue.add(envelope.repo, async () => { + await this.handleMessage(partition, item, envelope) + }) + } + } + await this.repoQueue.main.onEmpty() // backpressure + } + } + + async run() { + while (!this.destroyed) { + try { + const { ran } = await this.leader.run(async ({ signal }) => { + // initialize cursors to 0 (read from beginning of stream) + for (const id of this.partitionIds) { + this.partitions.set(id, new Partition(id, 0)) + } + // process events + await this.processEvents({ signal }) + }) + if (ran && !this.destroyed) { + throw new Error('Indexer sub completed, but should be persistent') + } + } catch (err) { + log.error({ err }, 'indexer sub error') + } + if (!this.destroyed) { + await wait(5000 + jitter(1000)) // wait then try to become leader + } + } + } + + async requestReprocess(did: string) { + await this.repoQueue.add(did, async () => { + try { + await this.indexingSvc.indexRepo(did, undefined) + } catch (err) { + log.error({ did }, 'failed to reprocess repo') + } + }) + } + + async destroy() { + this.destroyed = true + await this.repoQueue.destroy() + await Promise.all( + [...this.partitions.values()].map((p) => p.cursorQueue.destroy()), + ) + this.leader.destroy(new DisconnectError()) + } + + async resume() { + this.destroyed = false + this.partitions = new Map() + this.repoQueue = new PartitionedQueue({ + concurrency: this.opts.concurrency ?? Infinity, + }) + await this.run() + } + + private async handleMessage( + partition: Partition, + item: ConsecutiveItem, + envelope: Envelope, + ) { + const msg = envelope.event + try { + if (message.isCommit(msg)) { + await this.handleCommit(msg) + } else if (message.isHandle(msg)) { + await this.handleUpdateHandle(msg) + } else if (message.isTombstone(msg)) { + await this.handleTombstone(msg) + } else if (message.isMigrate(msg)) { + // Ignore migrations + } else { + const exhaustiveCheck: never = msg + throw new Error(`Unhandled message type: ${exhaustiveCheck['$type']}`) + } + } catch (err) { + // We log messages we can't process and move on: + // otherwise the cursor would get stuck on a poison message. + log.error( + { err, message: loggableMessage(msg) }, + 'indexer message processing error', + ) + } finally { + this.processedCount++ + const latest = item.complete().at(-1) + if (latest !== undefined) { + partition.cursorQueue + .add(async () => { + await this.ctx.redis.trimStream(partition.key, latest + 1) + }) + .catch((err) => { + log.error({ err }, 'indexer cursor error') + }) + } + } + } + + private async handleCommit(msg: message.Commit) { + const indexRecords = async () => { + const { root, rootCid, ops } = await getOps(msg) + if (msg.tooBig) { + await this.indexingSvc.indexRepo(msg.repo, rootCid.toString()) + await this.indexingSvc.setCommitLastSeen(root, msg) + return + } + if (msg.rebase) { + const needsReindex = await this.indexingSvc.checkCommitNeedsIndexing( + root, + ) + if (needsReindex) { + await this.indexingSvc.indexRepo(msg.repo, rootCid.toString()) + } + await this.indexingSvc.setCommitLastSeen(root, msg) + return + } + for (const op of ops) { + if (op.action === WriteOpAction.Delete) { + await this.indexingSvc.deleteRecord(op.uri) + } else { + try { + await this.indexingSvc.indexRecord( + op.uri, + op.cid, + op.record, + op.action, // create or update + msg.time, + ) + } catch (err) { + if (err instanceof ValidationError) { + log.warn( + { + did: msg.repo, + commit: msg.commit.toString(), + uri: op.uri.toString(), + cid: op.cid.toString(), + }, + 'skipping indexing of invalid record', + ) + } else { + log.error( + { + err, + did: msg.repo, + commit: msg.commit.toString(), + uri: op.uri.toString(), + cid: op.cid.toString(), + }, + 'skipping indexing due to error processing record', + ) + } + } + } + } + await this.indexingSvc.setCommitLastSeen(root, msg) + } + const results = await Promise.allSettled([ + indexRecords(), + this.indexingSvc.indexHandle(msg.repo, msg.time), + ]) + handleAllSettledErrors(results) + } + + private async handleUpdateHandle(msg: message.Handle) { + await this.indexingSvc.indexHandle(msg.did, msg.time, true) + } + + private async handleTombstone(msg: message.Tombstone) { + await this.indexingSvc.tombstoneActor(msg.did) + } +} + +async function getOps( + msg: message.Commit, +): Promise<{ root: Commit; rootCid: CID; ops: PreparedWrite[] }> { + const car = await readCarWithRoot(msg.blocks as Uint8Array) + const rootBytes = car.blocks.get(car.root) + assert(rootBytes, 'Missing commit block in car slice') + + const root = def.commit.schema.parse(cborDecode(rootBytes)) + const ops: PreparedWrite[] = msg.ops.map((op) => { + const [collection, rkey] = op.path.split('/') + assert(collection && rkey) + if ( + op.action === WriteOpAction.Create || + op.action === WriteOpAction.Update + ) { + assert(op.cid) + const record = car.blocks.get(op.cid) + assert(record) + return { + action: + op.action === WriteOpAction.Create + ? WriteOpAction.Create + : WriteOpAction.Update, + cid: op.cid, + record: cborToLexRecord(record), + blobs: [], + uri: AtUri.make(msg.repo, collection, rkey), + } + } else if (op.action === WriteOpAction.Delete) { + return { + action: WriteOpAction.Delete, + uri: AtUri.make(msg.repo, collection, rkey), + } + } else { + throw new Error(`Unknown repo op action: ${op.action}`) + } + }) + + return { root, rootCid: car.root, ops } +} + +function getEnvelope(val: Record): Envelope { + assert(val.repo && val.event, 'malformed message contents') + return { + repo: val.repo.toString(), + event: cborDecode(val.event) as ProcessableMessage, + } +} + +type Envelope = { + repo: string + event: ProcessableMessage +} + +class Partition { + consecutive = new ConsecutiveList() + cursorQueue = new LatestQueue() + constructor(public id: number, public cursor: number) {} + get key() { + return partitionKey(this.id) + } +} + +function partitionId(key: string) { + assert(key.startsWith('repo:')) + return strToInt(key.replace('repo:', '')) +} + +function partitionKey(p: number) { + return `repo:${p}` +} + +type PreparedCreate = { + action: WriteOpAction.Create + uri: AtUri + cid: CID + record: Record + blobs: CID[] // differs from similar type in pds +} + +type PreparedUpdate = { + action: WriteOpAction.Update + uri: AtUri + cid: CID + record: Record + blobs: CID[] // differs from similar type in pds +} + +type PreparedDelete = { + action: WriteOpAction.Delete + uri: AtUri +} + +type PreparedWrite = PreparedCreate | PreparedUpdate | PreparedDelete + +function handleAllSettledErrors(results: PromiseSettledResult[]) { + const errors = results.filter(isRejected).map((res) => res.reason) + if (errors.length === 0) { + return + } + if (errors.length === 1) { + throw errors[0] + } + throw new AggregateError( + errors, + 'Multiple errors: ' + errors.map((err) => err?.message).join('\n'), + ) +} + +function isRejected( + result: PromiseSettledResult, +): result is PromiseRejectedResult { + return result.status === 'rejected' +} diff --git a/packages/bsky/src/ingester/config.ts b/packages/bsky/src/ingester/config.ts new file mode 100644 index 00000000000..969aeeff7aa --- /dev/null +++ b/packages/bsky/src/ingester/config.ts @@ -0,0 +1,141 @@ +import assert from 'assert' + +export interface IngesterConfigValues { + version: string + dbPostgresUrl: string + dbPostgresSchema?: string + redisHost?: string // either set redis host, or both sentinel name and hosts + redisSentinelName?: string + redisSentinelHosts?: string[] + redisPassword?: string + repoProvider: string + ingesterPartitionCount: number + ingesterNamespace?: string + ingesterSubLockId?: number + ingesterMaxItems?: number + ingesterCheckItemsEveryN?: number + ingesterInitialCursor?: number +} + +export class IngesterConfig { + constructor(private cfg: IngesterConfigValues) {} + + static readEnv(overrides?: Partial) { + const version = process.env.BSKY_VERSION || '0.0.0' + const dbPostgresUrl = + overrides?.dbPostgresUrl || process.env.DB_PRIMARY_POSTGRES_URL + const dbPostgresSchema = + overrides?.dbPostgresSchema || process.env.DB_POSTGRES_SCHEMA + const redisHost = + overrides?.redisHost || process.env.REDIS_HOST || undefined + const redisSentinelName = + overrides?.redisSentinelName || + process.env.REDIS_SENTINEL_NAME || + undefined + const redisSentinelHosts = + overrides?.redisSentinelHosts || + (process.env.REDIS_SENTINEL_HOSTS + ? process.env.REDIS_SENTINEL_HOSTS.split(',') + : []) + const redisPassword = + overrides?.redisPassword || process.env.REDIS_PASSWORD || undefined + const repoProvider = overrides?.repoProvider || process.env.REPO_PROVIDER // E.g. ws://abc.com:4000 + const ingesterPartitionCount = + overrides?.ingesterPartitionCount || + maybeParseInt(process.env.INGESTER_PARTITION_COUNT) + const ingesterSubLockId = + overrides?.ingesterSubLockId || + maybeParseInt(process.env.INGESTER_SUB_LOCK_ID) + const ingesterMaxItems = + overrides?.ingesterMaxItems || + maybeParseInt(process.env.INGESTER_MAX_ITEMS) + const ingesterCheckItemsEveryN = + overrides?.ingesterCheckItemsEveryN || + maybeParseInt(process.env.INGESTER_CHECK_ITEMS_EVERY_N) + const ingesterInitialCursor = + overrides?.ingesterInitialCursor || + maybeParseInt(process.env.INGESTER_INITIAL_CURSOR) + const ingesterNamespace = overrides?.ingesterNamespace + assert(dbPostgresUrl) + assert(redisHost || (redisSentinelName && redisSentinelHosts?.length)) + assert(repoProvider) + assert(ingesterPartitionCount) + return new IngesterConfig({ + version, + dbPostgresUrl, + dbPostgresSchema, + redisHost, + redisSentinelName, + redisSentinelHosts, + redisPassword, + repoProvider, + ingesterPartitionCount, + ingesterSubLockId, + ingesterNamespace, + ingesterMaxItems, + ingesterCheckItemsEveryN, + ingesterInitialCursor, + }) + } + + get version() { + return this.cfg.version + } + + get dbPostgresUrl() { + return this.cfg.dbPostgresUrl + } + + get dbPostgresSchema() { + return this.cfg.dbPostgresSchema + } + + get redisHost() { + return this.cfg.redisHost + } + + get redisSentinelName() { + return this.cfg.redisSentinelName + } + + get redisSentinelHosts() { + return this.cfg.redisSentinelHosts + } + + get redisPassword() { + return this.cfg.redisPassword + } + + get repoProvider() { + return this.cfg.repoProvider + } + + get ingesterPartitionCount() { + return this.cfg.ingesterPartitionCount + } + + get ingesterMaxItems() { + return this.cfg.ingesterMaxItems + } + + get ingesterCheckItemsEveryN() { + return this.cfg.ingesterCheckItemsEveryN + } + + get ingesterInitialCursor() { + return this.cfg.ingesterInitialCursor + } + + get ingesterNamespace() { + return this.cfg.ingesterNamespace + } + + get ingesterSubLockId() { + return this.cfg.ingesterSubLockId + } +} + +function maybeParseInt(str) { + const parsed = parseInt(str) + return isNaN(parsed) ? undefined : parsed +} diff --git a/packages/bsky/src/ingester/context.ts b/packages/bsky/src/ingester/context.ts new file mode 100644 index 00000000000..792d3c2015a --- /dev/null +++ b/packages/bsky/src/ingester/context.ts @@ -0,0 +1,27 @@ +import { PrimaryDatabase } from '../db' +import { Redis } from '../redis' +import { IngesterConfig } from './config' + +export class IngesterContext { + constructor( + private opts: { + db: PrimaryDatabase + redis: Redis + cfg: IngesterConfig + }, + ) {} + + get db(): PrimaryDatabase { + return this.opts.db + } + + get redis(): Redis { + return this.opts.redis + } + + get cfg(): IngesterConfig { + return this.opts.cfg + } +} + +export default IngesterContext diff --git a/packages/bsky/src/ingester/index.ts b/packages/bsky/src/ingester/index.ts new file mode 100644 index 00000000000..376da2887da --- /dev/null +++ b/packages/bsky/src/ingester/index.ts @@ -0,0 +1,79 @@ +import { PrimaryDatabase } from '../db' +import log from './logger' +import { dbLogger } from '../logger' +import { Redis } from '../redis' +import { IngesterConfig } from './config' +import { IngesterContext } from './context' +import { IngesterSubscription } from './subscription' + +export { IngesterConfig } from './config' +export type { IngesterConfigValues } from './config' + +export class BskyIngester { + public ctx: IngesterContext + public sub: IngesterSubscription + private dbStatsInterval: NodeJS.Timer + private subStatsInterval: NodeJS.Timer + + constructor(opts: { ctx: IngesterContext; sub: IngesterSubscription }) { + this.ctx = opts.ctx + this.sub = opts.sub + } + + static create(opts: { + db: PrimaryDatabase + redis: Redis + cfg: IngesterConfig + }): BskyIngester { + const { db, redis, cfg } = opts + const ctx = new IngesterContext({ db, redis, cfg }) + const sub = new IngesterSubscription(ctx, { + service: cfg.repoProvider, + subLockId: cfg.ingesterSubLockId, + partitionCount: cfg.ingesterPartitionCount, + maxItems: cfg.ingesterMaxItems, + checkItemsEveryN: cfg.ingesterCheckItemsEveryN, + initialCursor: cfg.ingesterInitialCursor, + }) + return new BskyIngester({ ctx, sub }) + } + + async start() { + const { db } = this.ctx + const pool = db.pool + this.dbStatsInterval = setInterval(() => { + dbLogger.info( + { + idleCount: pool.idleCount, + totalCount: pool.totalCount, + waitingCount: pool.waitingCount, + }, + 'db pool stats', + ) + }, 10000) + this.subStatsInterval = setInterval(() => { + log.info( + { + seq: this.sub.lastSeq, + streamsLength: + this.sub.backpressure.lastTotal !== null + ? this.sub.backpressure.lastTotal + : undefined, + }, + 'ingester stats', + ) + }, 500) + this.sub.run() + return this + } + + async destroy(opts?: { skipDb: boolean }): Promise { + await this.sub.destroy() + clearInterval(this.subStatsInterval) + await this.ctx.redis.destroy() + if (!opts?.skipDb) await this.ctx.db.close() + clearInterval(this.dbStatsInterval) + } +} + +export default BskyIngester diff --git a/packages/bsky/src/ingester/logger.ts b/packages/bsky/src/ingester/logger.ts new file mode 100644 index 00000000000..49855166481 --- /dev/null +++ b/packages/bsky/src/ingester/logger.ts @@ -0,0 +1,6 @@ +import { subsystemLogger } from '@atproto/common' + +const logger: ReturnType = + subsystemLogger('bsky:ingester') + +export default logger diff --git a/packages/bsky/src/ingester/subscription.ts b/packages/bsky/src/ingester/subscription.ts new file mode 100644 index 00000000000..14f301e07f9 --- /dev/null +++ b/packages/bsky/src/ingester/subscription.ts @@ -0,0 +1,288 @@ +import { + Deferrable, + cborEncode, + createDeferrable, + ui8ToBuffer, + wait, +} from '@atproto/common' +import { randomIntFromSeed } from '@atproto/crypto' +import { DisconnectError, Subscription } from '@atproto/xrpc-server' +import { OutputSchema as Message } from '../lexicon/types/com/atproto/sync/subscribeRepos' +import * as message from '../lexicon/types/com/atproto/sync/subscribeRepos' +import { ids, lexicons } from '../lexicon/lexicons' +import { Leader } from '../db/leader' +import log from './logger' +import { + LatestQueue, + ProcessableMessage, + loggableMessage, + jitter, + strToInt, +} from '../subscription/util' +import { IngesterContext } from './context' + +const METHOD = ids.ComAtprotoSyncSubscribeRepos +const CURSOR_KEY = 'ingester:cursor' +export const INGESTER_SUB_LOCK_ID = 1000 + +export class IngesterSubscription { + cursorQueue = new LatestQueue() + destroyed = false + lastSeq: number | undefined + backpressure = new Backpressure(this) + leader = new Leader(this.opts.subLockId || INGESTER_SUB_LOCK_ID, this.ctx.db) + processor = new Processor(this) + + constructor( + public ctx: IngesterContext, + public opts: { + service: string + partitionCount: number + maxItems?: number + checkItemsEveryN?: number + subLockId?: number + initialCursor?: number + }, + ) {} + + async run() { + while (!this.destroyed) { + try { + const { ran } = await this.leader.run(async ({ signal }) => { + const sub = this.getSubscription({ signal }) + for await (const msg of sub) { + const details = getMessageDetails(msg) + if ('info' in details) { + // These messages are not sequenced, we just log them and carry on + log.warn( + { provider: this.opts.service, message: loggableMessage(msg) }, + `ingester sub ${details.info ? 'info' : 'unknown'} message`, + ) + continue + } + this.processor.send(details) + await this.backpressure.ready() + } + }) + if (ran && !this.destroyed) { + throw new Error('Ingester sub completed, but should be persistent') + } + } catch (err) { + log.error({ err, provider: this.opts.service }, 'ingester sub error') + } + if (!this.destroyed) { + await wait(1000 + jitter(500)) // wait then try to become leader + } + } + } + + async destroy() { + this.destroyed = true + await this.processor.destroy() + await this.cursorQueue.destroy() + this.leader.destroy(new DisconnectError()) + } + + async resume() { + this.destroyed = false + this.processor = new Processor(this) + this.cursorQueue = new LatestQueue() + await this.run() + } + + async getCursor(): Promise { + const val = await this.ctx.redis.get(CURSOR_KEY) + const initialCursor = this.opts.initialCursor ?? 0 + return val !== null ? strToInt(val) : initialCursor + } + + async resetCursor(): Promise { + await this.ctx.redis.del(CURSOR_KEY) + } + + async setCursor(seq: number): Promise { + await this.ctx.redis.set(CURSOR_KEY, seq) + } + + private getSubscription(opts: { signal: AbortSignal }) { + return new Subscription({ + service: this.opts.service, + method: METHOD, + signal: opts.signal, + getParams: async () => { + const cursor = await this.getCursor() + return { cursor } + }, + onReconnectError: (err, reconnects, initial) => { + log.warn({ err, reconnects, initial }, 'ingester sub reconnect') + }, + validate: (value) => { + try { + return lexicons.assertValidXrpcMessage(METHOD, value) + } catch (err) { + log.warn( + { + err, + seq: ifNumber(value?.['seq']), + repo: ifString(value?.['repo']), + commit: ifString(value?.['commit']?.toString()), + time: ifString(value?.['time']), + provider: this.opts.service, + }, + 'ingester sub skipped invalid message', + ) + } + }, + }) + } +} + +function ifString(val: unknown): string | undefined { + return typeof val === 'string' ? val : undefined +} + +function ifNumber(val: unknown): number | undefined { + return typeof val === 'number' ? val : undefined +} + +function getMessageDetails(msg: Message): + | { info: message.Info | null } + | { + seq: number + repo: string + message: ProcessableMessage + } { + if (message.isCommit(msg)) { + return { seq: msg.seq, repo: msg.repo, message: msg } + } else if (message.isHandle(msg)) { + return { seq: msg.seq, repo: msg.did, message: msg } + } else if (message.isMigrate(msg)) { + return { seq: msg.seq, repo: msg.did, message: msg } + } else if (message.isTombstone(msg)) { + return { seq: msg.seq, repo: msg.did, message: msg } + } else if (message.isInfo(msg)) { + return { info: msg } + } + return { info: null } +} + +async function getPartition(did: string, n: number) { + const partition = await randomIntFromSeed(did, n) + return `repo:${partition}` +} + +class Processor { + running: Deferrable | null = null + destroyed = false + unprocessed: MessageEnvelope[] = [] + + constructor(public sub: IngesterSubscription) {} + + async handleBatch(batch: MessageEnvelope[]) { + if (!batch.length) return + const items = await Promise.all( + batch.map(async ({ seq, repo, message }) => { + const key = await getPartition(repo, this.sub.opts.partitionCount) + const fields: [string, string | Buffer][] = [ + ['repo', repo], + ['event', ui8ToBuffer(cborEncode(message))], + ] + return { key, id: seq, fields } + }), + ) + const results = await this.sub.ctx.redis.addMultiToStream(items) + results.forEach(([err], i) => { + if (err) { + // skipping over messages that have already been added or fully processed + const item = batch.at(i) + log.warn( + { seq: item?.seq, repo: item?.repo }, + 'ingester skipping message', + ) + } + }) + const lastSeq = batch[batch.length - 1].seq + this.sub.lastSeq = lastSeq + this.sub.cursorQueue.add(() => this.sub.setCursor(lastSeq)) + } + + async process() { + if (this.running || this.destroyed || !this.unprocessed.length) return + const next = this.unprocessed.splice(100) // pipeline no more than 100 + const processing = this.unprocessed + this.unprocessed = next + this.running = createDeferrable() + try { + await this.handleBatch(processing) + } catch (err) { + log.error( + { err, size: processing.length }, + 'ingester processing failed, rolling over to next batch', + ) + this.unprocessed.unshift(...processing) + } finally { + this.running.resolve() + this.running = null + this.process() + } + } + + send(envelope: MessageEnvelope) { + this.unprocessed.push(envelope) + this.process() + } + + async destroy() { + this.destroyed = true + this.unprocessed = [] + await this.running?.complete + } +} + +type MessageEnvelope = { + seq: number + repo: string + message: ProcessableMessage +} + +class Backpressure { + count = 0 + lastTotal: number | null = null + partitionCount = this.sub.opts.partitionCount + limit = this.sub.opts.maxItems ?? Infinity + checkEvery = this.sub.opts.checkItemsEveryN ?? 500 + + constructor(public sub: IngesterSubscription) {} + + async ready() { + this.count++ + const shouldCheck = + this.limit !== Infinity && + (this.count === 1 || this.count % this.checkEvery === 0) + if (!shouldCheck) return + let ready = false + const start = Date.now() + while (!ready) { + ready = await this.check() + if (!ready) { + log.warn( + { + limit: this.limit, + total: this.lastTotal, + duration: Date.now() - start, + }, + 'ingester backpressure', + ) + await wait(250) + } + } + } + + async check() { + const lens = await this.sub.ctx.redis.streamLengths( + [...Array(this.partitionCount)].map((_, i) => `repo:${i}`), + ) + this.lastTotal = lens.reduce((sum, len) => sum + len, 0) + return this.lastTotal < this.limit + } +} diff --git a/packages/bsky/src/label-cache.ts b/packages/bsky/src/label-cache.ts new file mode 100644 index 00000000000..b162a2d30bd --- /dev/null +++ b/packages/bsky/src/label-cache.ts @@ -0,0 +1,90 @@ +import { wait } from '@atproto/common' +import { PrimaryDatabase } from './db' +import { Label } from './db/tables/label' +import { labelerLogger as log } from './logger' + +export class LabelCache { + bySubject: Record = {} + latestLabel = '' + refreshes = 0 + + destroyed = false + + constructor(public db: PrimaryDatabase) {} + + start() { + this.poll() + } + + async fullRefresh() { + const allLabels = await this.db.db.selectFrom('label').selectAll().execute() + this.wipeCache() + this.processLabels(allLabels) + } + + async partialRefresh() { + const labels = await this.db.db + .selectFrom('label') + .selectAll() + .where('cts', '>', this.latestLabel) + .execute() + this.processLabels(labels) + } + + async poll() { + try { + if (this.destroyed) return + if (this.refreshes >= 120) { + await this.fullRefresh() + this.refreshes = 0 + } else { + await this.partialRefresh() + this.refreshes++ + } + } catch (err) { + log.error( + { err, latestLabel: this.latestLabel, refreshes: this.refreshes }, + 'label cache failed to refresh', + ) + } + await wait(500) + this.poll() + } + + processLabels(labels: Label[]) { + for (const label of labels) { + if (label.cts > this.latestLabel) { + this.latestLabel = label.cts + } + this.bySubject[label.uri] ??= [] + this.bySubject[label.uri].push(label) + } + } + + wipeCache() { + this.bySubject = {} + } + + stop() { + this.destroyed = true + } + + forSubject(subject: string, includeNeg = false): Label[] { + const labels = this.bySubject[subject] ?? [] + return includeNeg ? labels : labels.filter((l) => l.neg === false) + } + + forSubjects(subjects: string[], includeNeg?: boolean): Label[] { + let labels: Label[] = [] + const alreadyAdded = new Set() + for (const subject of subjects) { + if (alreadyAdded.has(subject)) { + continue + } + const subLabels = this.forSubject(subject, includeNeg) + labels = [...labels, ...subLabels] + alreadyAdded.add(subject) + } + return labels + } +} diff --git a/packages/bsky/src/labeler/base.ts b/packages/bsky/src/labeler/base.ts deleted file mode 100644 index df601875d03..00000000000 --- a/packages/bsky/src/labeler/base.ts +++ /dev/null @@ -1,74 +0,0 @@ -import stream from 'stream' -import { AtUri } from '@atproto/uri' -import { cidForRecord } from '@atproto/repo' -import { dedupe, getFieldsFromRecord } from './util' -import { labelerLogger as log } from '../logger' -import { resolveBlob } from '../api/blob-resolver' -import Database from '../db' -import { IdResolver } from '@atproto/identity' -import { ServerConfig } from '../config' -import { BackgroundQueue } from '../background' - -export abstract class Labeler { - public backgroundQueue: BackgroundQueue - constructor( - protected ctx: { - db: Database - idResolver: IdResolver - cfg: ServerConfig - backgroundQueue: BackgroundQueue - }, - ) { - this.backgroundQueue = ctx.backgroundQueue - } - - processRecord(uri: AtUri, obj: unknown) { - this.backgroundQueue.add(() => - this.createAndStoreLabels(uri, obj).catch((err) => { - log.error( - { err, uri: uri.toString(), record: obj }, - 'failed to label record', - ) - }), - ) - } - - async createAndStoreLabels(uri: AtUri, obj: unknown): Promise { - const labels = await this.labelRecord(uri, obj) - if (labels.length < 1) return - const cid = await cidForRecord(obj) - const rows = labels.map((val) => ({ - src: this.ctx.cfg.labelerDid, - uri: uri.toString(), - cid: cid.toString(), - val, - neg: false, - cts: new Date().toISOString(), - })) - - await this.ctx.db.db - .insertInto('label') - .values(rows) - .onConflict((oc) => oc.doNothing()) - .execute() - } - - async labelRecord(uri: AtUri, obj: unknown): Promise { - const { text, imgs } = getFieldsFromRecord(obj) - const txtLabels = await this.labelText(text.join(' ')) - const imgLabels = await Promise.all( - imgs.map(async (cid) => { - const { stream } = await resolveBlob(uri.host, cid, this.ctx) - return this.labelImg(stream) - }), - ) - return dedupe([...txtLabels, ...imgLabels.flat()]) - } - - abstract labelText(text: string): Promise - abstract labelImg(img: stream.Readable): Promise - - async processAll() { - await this.backgroundQueue.processAll() - } -} diff --git a/packages/bsky/src/labeler/hive.ts b/packages/bsky/src/labeler/hive.ts deleted file mode 100644 index 24414fa0468..00000000000 --- a/packages/bsky/src/labeler/hive.ts +++ /dev/null @@ -1,121 +0,0 @@ -import stream from 'stream' -import axios from 'axios' -import FormData from 'form-data' -import { Labeler } from './base' -import { keywordLabeling } from './util' -import { ServerConfig } from '../config' -import { IdResolver } from '@atproto/identity' -import Database from '../db' -import { BackgroundQueue } from '../background' - -const HIVE_ENDPOINT = 'https://api.thehive.ai/api/v2/task/sync' - -export class HiveLabeler extends Labeler { - hiveApiKey: string - keywords: Record - - constructor( - hiveApiKey: string, - protected ctx: { - db: Database - idResolver: IdResolver - cfg: ServerConfig - backgroundQueue: BackgroundQueue - }, - ) { - super(ctx) - this.hiveApiKey = hiveApiKey - this.keywords = ctx.cfg.labelerKeywords - } - - async labelText(text: string): Promise { - return keywordLabeling(this.keywords, text) - } - - async labelImg(img: stream.Readable): Promise { - return labelBlob(img, this.hiveApiKey) - } -} - -export const labelBlob = async ( - blob: stream.Readable, - hiveApiKey: string, -): Promise => { - const classes = await makeHiveReq(blob, hiveApiKey) - return summarizeLabels(classes) -} - -export const makeHiveReq = async ( - blob: stream.Readable, - hiveApiKey: string, -): Promise => { - const form = new FormData() - form.append('media', blob) - const res = await axios.post(HIVE_ENDPOINT, form, { - headers: { - 'Content-Type': 'multipart/form-data', - authorization: `token ${hiveApiKey}`, - accept: 'application/json', - }, - }) - return respToClasses(res.data) -} - -export const respToClasses = (res: HiveResp): HiveRespClass[] => { - const classes: HiveRespClass[] = [] - for (const status of res.status) { - for (const out of status.response.output) { - for (const cls of out.classes) { - classes.push(cls) - } - } - } - return classes -} - -// sexual: https://docs.thehive.ai/docs/sexual-content -// gore and violence: https://docs.thehive.ai/docs/class-descriptions-violence-gore -// iconography: https://docs.thehive.ai/docs/class-descriptions-hate-bullying -const labelForClass = { - yes_sexual_activity: 'porn', - animal_genitalia_and_human: 'porn', // for some reason not included in 'yes_sexual_activity' - yes_male_nudity: 'nudity', - yes_female_nudity: 'nudity', - general_suggestive: 'sexual', - very_bloody: 'gore', - human_corpse: 'corpse', - yes_self_harm: 'self-harm', - yes_nazi: 'icon-nazi', - yes_kkk: 'icon-kkk', - yes_confederate: 'icon-confederate', -} - -export const summarizeLabels = (classes: HiveRespClass[]): string[] => { - const labels: string[] = [] - for (const cls of classes) { - if (labelForClass[cls.class] && cls.score >= 0.9) { - labels.push(labelForClass[cls.class]) - } - } - return labels -} - -type HiveResp = { - status: HiveRespStatus[] -} - -type HiveRespStatus = { - response: { - output: HiveRespOutput[] - } -} - -type HiveRespOutput = { - time: number - classes: HiveRespClass[] -} - -type HiveRespClass = { - class: string - score: number -} diff --git a/packages/bsky/src/labeler/index.ts b/packages/bsky/src/labeler/index.ts deleted file mode 100644 index cd6d2a64345..00000000000 --- a/packages/bsky/src/labeler/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './base' -export * from './hive' -export * from './keyword' diff --git a/packages/bsky/src/labeler/keyword.ts b/packages/bsky/src/labeler/keyword.ts deleted file mode 100644 index d4d0e7735ac..00000000000 --- a/packages/bsky/src/labeler/keyword.ts +++ /dev/null @@ -1,30 +0,0 @@ -import Database from '../db' -import { Labeler } from './base' -import { keywordLabeling } from './util' -import { IdResolver } from '@atproto/identity' -import { ServerConfig } from '../config' -import { BackgroundQueue } from '../background' - -export class KeywordLabeler extends Labeler { - keywords: Record - - constructor( - protected ctx: { - db: Database - idResolver: IdResolver - cfg: ServerConfig - backgroundQueue: BackgroundQueue - }, - ) { - super(ctx) - this.keywords = ctx.cfg.labelerKeywords - } - - async labelText(text: string): Promise { - return keywordLabeling(this.keywords, text) - } - - async labelImg(): Promise { - return [] - } -} diff --git a/packages/bsky/src/lexicon/index.ts b/packages/bsky/src/lexicon/index.ts index 548011fc331..93435056503 100644 --- a/packages/bsky/src/lexicon/index.ts +++ b/packages/bsky/src/lexicon/index.ts @@ -19,10 +19,10 @@ import * as ComAtprotoAdminGetModerationReport from './types/com/atproto/admin/g import * as ComAtprotoAdminGetModerationReports from './types/com/atproto/admin/getModerationReports' import * as ComAtprotoAdminGetRecord from './types/com/atproto/admin/getRecord' import * as ComAtprotoAdminGetRepo from './types/com/atproto/admin/getRepo' -import * as ComAtprotoAdminRebaseRepo from './types/com/atproto/admin/rebaseRepo' import * as ComAtprotoAdminResolveModerationReports from './types/com/atproto/admin/resolveModerationReports' import * as ComAtprotoAdminReverseModerationAction from './types/com/atproto/admin/reverseModerationAction' import * as ComAtprotoAdminSearchRepos from './types/com/atproto/admin/searchRepos' +import * as ComAtprotoAdminSendEmail from './types/com/atproto/admin/sendEmail' import * as ComAtprotoAdminTakeModerationAction from './types/com/atproto/admin/takeModerationAction' import * as ComAtprotoAdminUpdateAccountEmail from './types/com/atproto/admin/updateAccountEmail' import * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/updateAccountHandle' @@ -38,7 +38,6 @@ import * as ComAtprotoRepoDescribeRepo from './types/com/atproto/repo/describeRe import * as ComAtprotoRepoGetRecord from './types/com/atproto/repo/getRecord' import * as ComAtprotoRepoListRecords from './types/com/atproto/repo/listRecords' import * as ComAtprotoRepoPutRecord from './types/com/atproto/repo/putRecord' -import * as ComAtprotoRepoRebaseRepo from './types/com/atproto/repo/rebaseRepo' import * as ComAtprotoRepoUploadBlob from './types/com/atproto/repo/uploadBlob' import * as ComAtprotoServerCreateAccount from './types/com/atproto/server/createAccount' import * as ComAtprotoServerCreateAppPassword from './types/com/atproto/server/createAppPassword' @@ -59,8 +58,8 @@ import * as ComAtprotoServerRevokeAppPassword from './types/com/atproto/server/r import * as ComAtprotoSyncGetBlob from './types/com/atproto/sync/getBlob' import * as ComAtprotoSyncGetBlocks from './types/com/atproto/sync/getBlocks' import * as ComAtprotoSyncGetCheckout from './types/com/atproto/sync/getCheckout' -import * as ComAtprotoSyncGetCommitPath from './types/com/atproto/sync/getCommitPath' import * as ComAtprotoSyncGetHead from './types/com/atproto/sync/getHead' +import * as ComAtprotoSyncGetLatestCommit from './types/com/atproto/sync/getLatestCommit' import * as ComAtprotoSyncGetRecord from './types/com/atproto/sync/getRecord' import * as ComAtprotoSyncGetRepo from './types/com/atproto/sync/getRepo' import * as ComAtprotoSyncListBlobs from './types/com/atproto/sync/listBlobs' @@ -77,6 +76,7 @@ import * as AppBskyActorSearchActors from './types/app/bsky/actor/searchActors' import * as AppBskyActorSearchActorsTypeahead from './types/app/bsky/actor/searchActorsTypeahead' import * as AppBskyFeedDescribeFeedGenerator from './types/app/bsky/feed/describeFeedGenerator' import * as AppBskyFeedGetActorFeeds from './types/app/bsky/feed/getActorFeeds' +import * as AppBskyFeedGetActorLikes from './types/app/bsky/feed/getActorLikes' import * as AppBskyFeedGetAuthorFeed from './types/app/bsky/feed/getAuthorFeed' import * as AppBskyFeedGetFeed from './types/app/bsky/feed/getFeed' import * as AppBskyFeedGetFeedGenerator from './types/app/bsky/feed/getFeedGenerator' @@ -86,23 +86,29 @@ import * as AppBskyFeedGetLikes from './types/app/bsky/feed/getLikes' import * as AppBskyFeedGetPostThread from './types/app/bsky/feed/getPostThread' import * as AppBskyFeedGetPosts from './types/app/bsky/feed/getPosts' import * as AppBskyFeedGetRepostedBy from './types/app/bsky/feed/getRepostedBy' +import * as AppBskyFeedGetSuggestedFeeds from './types/app/bsky/feed/getSuggestedFeeds' import * as AppBskyFeedGetTimeline from './types/app/bsky/feed/getTimeline' import * as AppBskyGraphGetBlocks from './types/app/bsky/graph/getBlocks' import * as AppBskyGraphGetFollowers from './types/app/bsky/graph/getFollowers' import * as AppBskyGraphGetFollows from './types/app/bsky/graph/getFollows' import * as AppBskyGraphGetList from './types/app/bsky/graph/getList' +import * as AppBskyGraphGetListBlocks from './types/app/bsky/graph/getListBlocks' import * as AppBskyGraphGetListMutes from './types/app/bsky/graph/getListMutes' import * as AppBskyGraphGetLists from './types/app/bsky/graph/getLists' import * as AppBskyGraphGetMutes from './types/app/bsky/graph/getMutes' +import * as AppBskyGraphGetSuggestedFollowsByActor from './types/app/bsky/graph/getSuggestedFollowsByActor' import * as AppBskyGraphMuteActor from './types/app/bsky/graph/muteActor' import * as AppBskyGraphMuteActorList from './types/app/bsky/graph/muteActorList' import * as AppBskyGraphUnmuteActor from './types/app/bsky/graph/unmuteActor' import * as AppBskyGraphUnmuteActorList from './types/app/bsky/graph/unmuteActorList' import * as AppBskyNotificationGetUnreadCount from './types/app/bsky/notification/getUnreadCount' import * as AppBskyNotificationListNotifications from './types/app/bsky/notification/listNotifications' +import * as AppBskyNotificationRegisterPush from './types/app/bsky/notification/registerPush' import * as AppBskyNotificationUpdateSeen from './types/app/bsky/notification/updateSeen' +import * as AppBskyUnspeccedApplyLabels from './types/app/bsky/unspecced/applyLabels' import * as AppBskyUnspeccedGetPopular from './types/app/bsky/unspecced/getPopular' import * as AppBskyUnspeccedGetPopularFeedGenerators from './types/app/bsky/unspecced/getPopularFeedGenerators' +import * as AppBskyUnspeccedGetTimelineSkeleton from './types/app/bsky/unspecced/getTimelineSkeleton' export const COM_ATPROTO_ADMIN = { DefsTakedown: 'com.atproto.admin.defs#takedown', @@ -180,7 +186,8 @@ export class AdminNS { disableAccountInvites( cfg: ConfigOf< AV, - ComAtprotoAdminDisableAccountInvites.Handler> + ComAtprotoAdminDisableAccountInvites.Handler>, + ComAtprotoAdminDisableAccountInvites.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.disableAccountInvites' // @ts-ignore @@ -190,7 +197,8 @@ export class AdminNS { disableInviteCodes( cfg: ConfigOf< AV, - ComAtprotoAdminDisableInviteCodes.Handler> + ComAtprotoAdminDisableInviteCodes.Handler>, + ComAtprotoAdminDisableInviteCodes.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.disableInviteCodes' // @ts-ignore @@ -200,7 +208,8 @@ export class AdminNS { enableAccountInvites( cfg: ConfigOf< AV, - ComAtprotoAdminEnableAccountInvites.Handler> + ComAtprotoAdminEnableAccountInvites.Handler>, + ComAtprotoAdminEnableAccountInvites.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.enableAccountInvites' // @ts-ignore @@ -208,7 +217,11 @@ export class AdminNS { } getInviteCodes( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoAdminGetInviteCodes.Handler>, + ComAtprotoAdminGetInviteCodes.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.admin.getInviteCodes' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -217,7 +230,8 @@ export class AdminNS { getModerationAction( cfg: ConfigOf< AV, - ComAtprotoAdminGetModerationAction.Handler> + ComAtprotoAdminGetModerationAction.Handler>, + ComAtprotoAdminGetModerationAction.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.getModerationAction' // @ts-ignore @@ -227,7 +241,8 @@ export class AdminNS { getModerationActions( cfg: ConfigOf< AV, - ComAtprotoAdminGetModerationActions.Handler> + ComAtprotoAdminGetModerationActions.Handler>, + ComAtprotoAdminGetModerationActions.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.getModerationActions' // @ts-ignore @@ -237,7 +252,8 @@ export class AdminNS { getModerationReport( cfg: ConfigOf< AV, - ComAtprotoAdminGetModerationReport.Handler> + ComAtprotoAdminGetModerationReport.Handler>, + ComAtprotoAdminGetModerationReport.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.getModerationReport' // @ts-ignore @@ -247,7 +263,8 @@ export class AdminNS { getModerationReports( cfg: ConfigOf< AV, - ComAtprotoAdminGetModerationReports.Handler> + ComAtprotoAdminGetModerationReports.Handler>, + ComAtprotoAdminGetModerationReports.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.getModerationReports' // @ts-ignore @@ -255,30 +272,32 @@ export class AdminNS { } getRecord( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoAdminGetRecord.Handler>, + ComAtprotoAdminGetRecord.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.admin.getRecord' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getRepo( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoAdminGetRepo.Handler>, + ComAtprotoAdminGetRepo.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.admin.getRepo' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } - rebaseRepo( - cfg: ConfigOf>>, - ) { - const nsid = 'com.atproto.admin.rebaseRepo' // @ts-ignore - return this._server.xrpc.method(nsid, cfg) - } - resolveModerationReports( cfg: ConfigOf< AV, - ComAtprotoAdminResolveModerationReports.Handler> + ComAtprotoAdminResolveModerationReports.Handler>, + ComAtprotoAdminResolveModerationReports.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.resolveModerationReports' // @ts-ignore @@ -288,7 +307,8 @@ export class AdminNS { reverseModerationAction( cfg: ConfigOf< AV, - ComAtprotoAdminReverseModerationAction.Handler> + ComAtprotoAdminReverseModerationAction.Handler>, + ComAtprotoAdminReverseModerationAction.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.reverseModerationAction' // @ts-ignore @@ -296,16 +316,32 @@ export class AdminNS { } searchRepos( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoAdminSearchRepos.Handler>, + ComAtprotoAdminSearchRepos.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.admin.searchRepos' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + sendEmail( + cfg: ConfigOf< + AV, + ComAtprotoAdminSendEmail.Handler>, + ComAtprotoAdminSendEmail.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.admin.sendEmail' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + takeModerationAction( cfg: ConfigOf< AV, - ComAtprotoAdminTakeModerationAction.Handler> + ComAtprotoAdminTakeModerationAction.Handler>, + ComAtprotoAdminTakeModerationAction.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.takeModerationAction' // @ts-ignore @@ -315,7 +351,8 @@ export class AdminNS { updateAccountEmail( cfg: ConfigOf< AV, - ComAtprotoAdminUpdateAccountEmail.Handler> + ComAtprotoAdminUpdateAccountEmail.Handler>, + ComAtprotoAdminUpdateAccountEmail.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.updateAccountEmail' // @ts-ignore @@ -325,7 +362,8 @@ export class AdminNS { updateAccountHandle( cfg: ConfigOf< AV, - ComAtprotoAdminUpdateAccountHandle.Handler> + ComAtprotoAdminUpdateAccountHandle.Handler>, + ComAtprotoAdminUpdateAccountHandle.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.updateAccountHandle' // @ts-ignore @@ -341,14 +379,22 @@ export class IdentityNS { } resolveHandle( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoIdentityResolveHandle.Handler>, + ComAtprotoIdentityResolveHandle.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.identity.resolveHandle' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } updateHandle( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoIdentityUpdateHandle.Handler>, + ComAtprotoIdentityUpdateHandle.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.identity.updateHandle' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -363,14 +409,22 @@ export class LabelNS { } queryLabels( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoLabelQueryLabels.Handler>, + ComAtprotoLabelQueryLabels.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.label.queryLabels' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } subscribeLabels( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoLabelSubscribeLabels.Handler>, + ComAtprotoLabelSubscribeLabels.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.label.subscribeLabels' // @ts-ignore return this._server.xrpc.streamMethod(nsid, cfg) @@ -387,7 +441,8 @@ export class ModerationNS { createReport( cfg: ConfigOf< AV, - ComAtprotoModerationCreateReport.Handler> + ComAtprotoModerationCreateReport.Handler>, + ComAtprotoModerationCreateReport.HandlerReqCtx> >, ) { const nsid = 'com.atproto.moderation.createReport' // @ts-ignore @@ -403,63 +458,88 @@ export class RepoNS { } applyWrites( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoRepoApplyWrites.Handler>, + ComAtprotoRepoApplyWrites.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.repo.applyWrites' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } createRecord( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoRepoCreateRecord.Handler>, + ComAtprotoRepoCreateRecord.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.repo.createRecord' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } deleteRecord( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoRepoDeleteRecord.Handler>, + ComAtprotoRepoDeleteRecord.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.repo.deleteRecord' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } describeRepo( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoRepoDescribeRepo.Handler>, + ComAtprotoRepoDescribeRepo.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.repo.describeRepo' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getRecord( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoRepoGetRecord.Handler>, + ComAtprotoRepoGetRecord.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.repo.getRecord' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } listRecords( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoRepoListRecords.Handler>, + ComAtprotoRepoListRecords.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.repo.listRecords' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } putRecord( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoRepoPutRecord.Handler>, + ComAtprotoRepoPutRecord.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.repo.putRecord' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } - rebaseRepo( - cfg: ConfigOf>>, - ) { - const nsid = 'com.atproto.repo.rebaseRepo' // @ts-ignore - return this._server.xrpc.method(nsid, cfg) - } - uploadBlob( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoRepoUploadBlob.Handler>, + ComAtprotoRepoUploadBlob.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.repo.uploadBlob' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -474,7 +554,11 @@ export class ServerNS { } createAccount( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoServerCreateAccount.Handler>, + ComAtprotoServerCreateAccount.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.server.createAccount' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -483,7 +567,8 @@ export class ServerNS { createAppPassword( cfg: ConfigOf< AV, - ComAtprotoServerCreateAppPassword.Handler> + ComAtprotoServerCreateAppPassword.Handler>, + ComAtprotoServerCreateAppPassword.HandlerReqCtx> >, ) { const nsid = 'com.atproto.server.createAppPassword' // @ts-ignore @@ -493,7 +578,8 @@ export class ServerNS { createInviteCode( cfg: ConfigOf< AV, - ComAtprotoServerCreateInviteCode.Handler> + ComAtprotoServerCreateInviteCode.Handler>, + ComAtprotoServerCreateInviteCode.HandlerReqCtx> >, ) { const nsid = 'com.atproto.server.createInviteCode' // @ts-ignore @@ -503,7 +589,8 @@ export class ServerNS { createInviteCodes( cfg: ConfigOf< AV, - ComAtprotoServerCreateInviteCodes.Handler> + ComAtprotoServerCreateInviteCodes.Handler>, + ComAtprotoServerCreateInviteCodes.HandlerReqCtx> >, ) { const nsid = 'com.atproto.server.createInviteCodes' // @ts-ignore @@ -511,28 +598,44 @@ export class ServerNS { } createSession( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoServerCreateSession.Handler>, + ComAtprotoServerCreateSession.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.server.createSession' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } deleteAccount( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoServerDeleteAccount.Handler>, + ComAtprotoServerDeleteAccount.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.server.deleteAccount' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } deleteSession( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoServerDeleteSession.Handler>, + ComAtprotoServerDeleteSession.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.server.deleteSession' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } describeServer( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoServerDescribeServer.Handler>, + ComAtprotoServerDescribeServer.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.server.describeServer' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -541,7 +644,8 @@ export class ServerNS { getAccountInviteCodes( cfg: ConfigOf< AV, - ComAtprotoServerGetAccountInviteCodes.Handler> + ComAtprotoServerGetAccountInviteCodes.Handler>, + ComAtprotoServerGetAccountInviteCodes.HandlerReqCtx> >, ) { const nsid = 'com.atproto.server.getAccountInviteCodes' // @ts-ignore @@ -549,7 +653,11 @@ export class ServerNS { } getSession( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoServerGetSession.Handler>, + ComAtprotoServerGetSession.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.server.getSession' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -558,7 +666,8 @@ export class ServerNS { listAppPasswords( cfg: ConfigOf< AV, - ComAtprotoServerListAppPasswords.Handler> + ComAtprotoServerListAppPasswords.Handler>, + ComAtprotoServerListAppPasswords.HandlerReqCtx> >, ) { const nsid = 'com.atproto.server.listAppPasswords' // @ts-ignore @@ -566,7 +675,11 @@ export class ServerNS { } refreshSession( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoServerRefreshSession.Handler>, + ComAtprotoServerRefreshSession.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.server.refreshSession' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -575,7 +688,8 @@ export class ServerNS { requestAccountDelete( cfg: ConfigOf< AV, - ComAtprotoServerRequestAccountDelete.Handler> + ComAtprotoServerRequestAccountDelete.Handler>, + ComAtprotoServerRequestAccountDelete.HandlerReqCtx> >, ) { const nsid = 'com.atproto.server.requestAccountDelete' // @ts-ignore @@ -585,7 +699,8 @@ export class ServerNS { requestPasswordReset( cfg: ConfigOf< AV, - ComAtprotoServerRequestPasswordReset.Handler> + ComAtprotoServerRequestPasswordReset.Handler>, + ComAtprotoServerRequestPasswordReset.HandlerReqCtx> >, ) { const nsid = 'com.atproto.server.requestPasswordReset' // @ts-ignore @@ -593,7 +708,11 @@ export class ServerNS { } resetPassword( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoServerResetPassword.Handler>, + ComAtprotoServerResetPassword.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.server.resetPassword' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -602,7 +721,8 @@ export class ServerNS { revokeAppPassword( cfg: ConfigOf< AV, - ComAtprotoServerRevokeAppPassword.Handler> + ComAtprotoServerRevokeAppPassword.Handler>, + ComAtprotoServerRevokeAppPassword.HandlerReqCtx> >, ) { const nsid = 'com.atproto.server.revokeAppPassword' // @ts-ignore @@ -618,84 +738,132 @@ export class SyncNS { } getBlob( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncGetBlob.Handler>, + ComAtprotoSyncGetBlob.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.getBlob' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getBlocks( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncGetBlocks.Handler>, + ComAtprotoSyncGetBlocks.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.getBlocks' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getCheckout( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncGetCheckout.Handler>, + ComAtprotoSyncGetCheckout.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.getCheckout' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } - getCommitPath( - cfg: ConfigOf>>, + getHead( + cfg: ConfigOf< + AV, + ComAtprotoSyncGetHead.Handler>, + ComAtprotoSyncGetHead.HandlerReqCtx> + >, ) { - const nsid = 'com.atproto.sync.getCommitPath' // @ts-ignore + const nsid = 'com.atproto.sync.getHead' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } - getHead( - cfg: ConfigOf>>, + getLatestCommit( + cfg: ConfigOf< + AV, + ComAtprotoSyncGetLatestCommit.Handler>, + ComAtprotoSyncGetLatestCommit.HandlerReqCtx> + >, ) { - const nsid = 'com.atproto.sync.getHead' // @ts-ignore + const nsid = 'com.atproto.sync.getLatestCommit' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getRecord( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncGetRecord.Handler>, + ComAtprotoSyncGetRecord.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.getRecord' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getRepo( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncGetRepo.Handler>, + ComAtprotoSyncGetRepo.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.getRepo' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } listBlobs( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncListBlobs.Handler>, + ComAtprotoSyncListBlobs.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.listBlobs' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } listRepos( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncListRepos.Handler>, + ComAtprotoSyncListRepos.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.listRepos' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } notifyOfUpdate( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncNotifyOfUpdate.Handler>, + ComAtprotoSyncNotifyOfUpdate.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.notifyOfUpdate' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } requestCrawl( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncRequestCrawl.Handler>, + ComAtprotoSyncRequestCrawl.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.requestCrawl' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } subscribeRepos( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncSubscribeRepos.Handler>, + ComAtprotoSyncSubscribeRepos.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.subscribeRepos' // @ts-ignore return this._server.xrpc.streamMethod(nsid, cfg) @@ -742,42 +910,66 @@ export class ActorNS { } getPreferences( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyActorGetPreferences.Handler>, + AppBskyActorGetPreferences.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.actor.getPreferences' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getProfile( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyActorGetProfile.Handler>, + AppBskyActorGetProfile.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.actor.getProfile' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getProfiles( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyActorGetProfiles.Handler>, + AppBskyActorGetProfiles.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.actor.getProfiles' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getSuggestions( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyActorGetSuggestions.Handler>, + AppBskyActorGetSuggestions.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.actor.getSuggestions' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } putPreferences( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyActorPutPreferences.Handler>, + AppBskyActorPutPreferences.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.actor.putPreferences' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } searchActors( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyActorSearchActors.Handler>, + AppBskyActorSearchActors.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.actor.searchActors' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -786,7 +978,8 @@ export class ActorNS { searchActorsTypeahead( cfg: ConfigOf< AV, - AppBskyActorSearchActorsTypeahead.Handler> + AppBskyActorSearchActorsTypeahead.Handler>, + AppBskyActorSearchActorsTypeahead.HandlerReqCtx> >, ) { const nsid = 'app.bsky.actor.searchActorsTypeahead' // @ts-ignore @@ -812,7 +1005,8 @@ export class FeedNS { describeFeedGenerator( cfg: ConfigOf< AV, - AppBskyFeedDescribeFeedGenerator.Handler> + AppBskyFeedDescribeFeedGenerator.Handler>, + AppBskyFeedDescribeFeedGenerator.HandlerReqCtx> >, ) { const nsid = 'app.bsky.feed.describeFeedGenerator' // @ts-ignore @@ -820,77 +1014,143 @@ export class FeedNS { } getActorFeeds( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetActorFeeds.Handler>, + AppBskyFeedGetActorFeeds.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getActorFeeds' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + getActorLikes( + cfg: ConfigOf< + AV, + AppBskyFeedGetActorLikes.Handler>, + AppBskyFeedGetActorLikes.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.feed.getActorLikes' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + getAuthorFeed( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetAuthorFeed.Handler>, + AppBskyFeedGetAuthorFeed.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getAuthorFeed' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getFeed( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetFeed.Handler>, + AppBskyFeedGetFeed.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getFeed' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getFeedGenerator( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetFeedGenerator.Handler>, + AppBskyFeedGetFeedGenerator.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getFeedGenerator' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getFeedGenerators( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetFeedGenerators.Handler>, + AppBskyFeedGetFeedGenerators.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getFeedGenerators' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getFeedSkeleton( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetFeedSkeleton.Handler>, + AppBskyFeedGetFeedSkeleton.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getFeedSkeleton' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getLikes( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetLikes.Handler>, + AppBskyFeedGetLikes.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getLikes' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getPostThread( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetPostThread.Handler>, + AppBskyFeedGetPostThread.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getPostThread' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getPosts( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetPosts.Handler>, + AppBskyFeedGetPosts.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getPosts' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getRepostedBy( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetRepostedBy.Handler>, + AppBskyFeedGetRepostedBy.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getRepostedBy' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + getSuggestedFeeds( + cfg: ConfigOf< + AV, + AppBskyFeedGetSuggestedFeeds.Handler>, + AppBskyFeedGetSuggestedFeeds.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.feed.getSuggestedFeeds' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + getTimeline( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetTimeline.Handler>, + AppBskyFeedGetTimeline.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getTimeline' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -905,77 +1165,143 @@ export class GraphNS { } getBlocks( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphGetBlocks.Handler>, + AppBskyGraphGetBlocks.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.getBlocks' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getFollowers( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphGetFollowers.Handler>, + AppBskyGraphGetFollowers.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.getFollowers' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getFollows( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphGetFollows.Handler>, + AppBskyGraphGetFollows.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.getFollows' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getList( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphGetList.Handler>, + AppBskyGraphGetList.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.getList' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + getListBlocks( + cfg: ConfigOf< + AV, + AppBskyGraphGetListBlocks.Handler>, + AppBskyGraphGetListBlocks.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.graph.getListBlocks' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + getListMutes( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphGetListMutes.Handler>, + AppBskyGraphGetListMutes.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.getListMutes' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getLists( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphGetLists.Handler>, + AppBskyGraphGetLists.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.getLists' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getMutes( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphGetMutes.Handler>, + AppBskyGraphGetMutes.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.getMutes' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + getSuggestedFollowsByActor( + cfg: ConfigOf< + AV, + AppBskyGraphGetSuggestedFollowsByActor.Handler>, + AppBskyGraphGetSuggestedFollowsByActor.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.graph.getSuggestedFollowsByActor' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + muteActor( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphMuteActor.Handler>, + AppBskyGraphMuteActor.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.muteActor' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } muteActorList( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphMuteActorList.Handler>, + AppBskyGraphMuteActorList.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.muteActorList' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } unmuteActor( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphUnmuteActor.Handler>, + AppBskyGraphUnmuteActor.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.unmuteActor' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } unmuteActorList( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphUnmuteActorList.Handler>, + AppBskyGraphUnmuteActorList.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.unmuteActorList' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -992,7 +1318,8 @@ export class NotificationNS { getUnreadCount( cfg: ConfigOf< AV, - AppBskyNotificationGetUnreadCount.Handler> + AppBskyNotificationGetUnreadCount.Handler>, + AppBskyNotificationGetUnreadCount.HandlerReqCtx> >, ) { const nsid = 'app.bsky.notification.getUnreadCount' // @ts-ignore @@ -1002,15 +1329,31 @@ export class NotificationNS { listNotifications( cfg: ConfigOf< AV, - AppBskyNotificationListNotifications.Handler> + AppBskyNotificationListNotifications.Handler>, + AppBskyNotificationListNotifications.HandlerReqCtx> >, ) { const nsid = 'app.bsky.notification.listNotifications' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + registerPush( + cfg: ConfigOf< + AV, + AppBskyNotificationRegisterPush.Handler>, + AppBskyNotificationRegisterPush.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.notification.registerPush' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + updateSeen( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyNotificationUpdateSeen.Handler>, + AppBskyNotificationUpdateSeen.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.notification.updateSeen' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -1032,8 +1375,23 @@ export class UnspeccedNS { this._server = server } + applyLabels( + cfg: ConfigOf< + AV, + AppBskyUnspeccedApplyLabels.Handler>, + AppBskyUnspeccedApplyLabels.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.unspecced.applyLabels' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + getPopular( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyUnspeccedGetPopular.Handler>, + AppBskyUnspeccedGetPopular.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.unspecced.getPopular' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -1042,18 +1400,43 @@ export class UnspeccedNS { getPopularFeedGenerators( cfg: ConfigOf< AV, - AppBskyUnspeccedGetPopularFeedGenerators.Handler> + AppBskyUnspeccedGetPopularFeedGenerators.Handler>, + AppBskyUnspeccedGetPopularFeedGenerators.HandlerReqCtx> >, ) { const nsid = 'app.bsky.unspecced.getPopularFeedGenerators' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + + getTimelineSkeleton( + cfg: ConfigOf< + AV, + AppBskyUnspeccedGetTimelineSkeleton.Handler>, + AppBskyUnspeccedGetTimelineSkeleton.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.unspecced.getTimelineSkeleton' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } } -type ConfigOf = +type SharedRateLimitOpts = { + name: string + calcKey?: (ctx: T) => string + calcPoints?: (ctx: T) => number +} +type RouteRateLimitOpts = { + durationMs: number + points: number + calcKey?: (ctx: T) => string + calcPoints?: (ctx: T) => number +} +type HandlerRateLimitOpts = SharedRateLimitOpts | RouteRateLimitOpts +type ConfigOf = | Handler | { auth?: Auth + rateLimit?: HandlerRateLimitOpts | HandlerRateLimitOpts[] handler: Handler } type ExtractAuth = Extract< diff --git a/packages/bsky/src/lexicon/lexicons.ts b/packages/bsky/src/lexicon/lexicons.ts index e7a5195328e..f3c93c5e805 100644 --- a/packages/bsky/src/lexicon/lexicons.ts +++ b/packages/bsky/src/lexicon/lexicons.ts @@ -28,6 +28,11 @@ export const schemaDict = { type: 'ref', ref: 'lex:com.atproto.admin.defs#actionType', }, + durationInHours: { + type: 'integer', + description: + 'Indicates how long this action was meant to be in effect before automatically expiring.', + }, subject: { type: 'union', refs: [ @@ -96,6 +101,11 @@ export const schemaDict = { type: 'ref', ref: 'lex:com.atproto.admin.defs#actionType', }, + durationInHours: { + type: 'integer', + description: + 'Indicates how long this action was meant to be in effect before automatically expiring.', + }, subject: { type: 'union', refs: [ @@ -159,6 +169,11 @@ export const schemaDict = { type: 'ref', ref: 'lex:com.atproto.admin.defs#actionType', }, + durationInHours: { + type: 'integer', + description: + 'Indicates how long this action was meant to be in effect before automatically expiring.', + }, }, }, actionReversal: { @@ -343,6 +358,9 @@ export const schemaDict = { invitesDisabled: { type: 'boolean', }, + inviteNote: { + type: 'string', + }, }, }, repoViewDetail: { @@ -401,6 +419,9 @@ export const schemaDict = { invitesDisabled: { type: 'boolean', }, + inviteNote: { + type: 'string', + }, }, }, repoViewNotFound: { @@ -530,7 +551,6 @@ export const schemaDict = { }, moderation: { type: 'object', - required: [], properties: { currentAction: { type: 'ref', @@ -640,6 +660,11 @@ export const schemaDict = { type: 'string', format: 'did', }, + note: { + type: 'string', + description: + 'Additionally add a note describing why the invites were disabled', + }, }, }, }, @@ -694,6 +719,11 @@ export const schemaDict = { type: 'string', format: 'did', }, + note: { + type: 'string', + description: + 'Additionally add a note describing why the invites were enabled', + }, }, }, }, @@ -865,6 +895,12 @@ export const schemaDict = { type: 'string', }, }, + actionedBy: { + type: 'string', + format: 'did', + description: + 'Get all reports that were actioned by a specific moderator', + }, reporters: { type: 'array', items: { @@ -990,44 +1026,6 @@ export const schemaDict = { }, }, }, - ComAtprotoAdminRebaseRepo: { - lexicon: 1, - id: 'com.atproto.admin.rebaseRepo', - defs: { - main: { - type: 'procedure', - description: "Administrative action to rebase an account's repo", - input: { - encoding: 'application/json', - schema: { - type: 'object', - required: ['repo'], - properties: { - repo: { - type: 'string', - format: 'at-identifier', - description: 'The handle or DID of the repo.', - }, - swapCommit: { - type: 'string', - format: 'cid', - description: - 'Compare and swap with the previous commit by cid.', - }, - }, - }, - }, - errors: [ - { - name: 'InvalidSwap', - }, - { - name: 'ConcurrentWrites', - }, - ], - }, - }, - }, ComAtprotoAdminResolveModerationReports: { lexicon: 1, id: 'com.atproto.admin.resolveModerationReports', @@ -1152,6 +1150,47 @@ export const schemaDict = { }, }, }, + ComAtprotoAdminSendEmail: { + lexicon: 1, + id: 'com.atproto.admin.sendEmail', + defs: { + main: { + type: 'procedure', + description: "Send email to a user's primary email address", + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['recipientDid', 'content'], + properties: { + recipientDid: { + type: 'string', + format: 'did', + }, + content: { + type: 'string', + }, + subject: { + type: 'string', + }, + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['sent'], + properties: { + sent: { + type: 'boolean', + }, + }, + }, + }, + }, + }, + }, ComAtprotoAdminTakeModerationAction: { lexicon: 1, id: 'com.atproto.admin.takeModerationAction', @@ -1202,6 +1241,11 @@ export const schemaDict = { reason: { type: 'string', }, + durationInHours: { + type: 'integer', + description: + 'Indicates how long this action was meant to be in effect before automatically expiring.', + }, createdBy: { type: 'string', format: 'did', @@ -1379,6 +1423,36 @@ export const schemaDict = { }, }, }, + selfLabels: { + type: 'object', + description: + 'Metadata tags on an atproto record, published by the author within the record.', + required: ['values'], + properties: { + values: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:com.atproto.label.defs#selfLabel', + }, + maxLength: 10, + }, + }, + }, + selfLabel: { + type: 'object', + description: + 'Metadata tag on an atproto record, published by the author within the record. Note -- schemas should use #selfLabels, not #selfLabel.', + required: ['val'], + properties: { + val: { + type: 'string', + maxLength: 128, + description: + 'the short string name of the value or type of this label', + }, + }, + }, }, }, ComAtprotoLabelQueryLabels: { @@ -1605,7 +1679,7 @@ export const schemaDict = { }, reasonSexual: { type: 'token', - description: 'Unwanted or mis-labeled sexual content', + description: 'Unwanted or mislabeled sexual content', }, reasonRude: { type: 'token', @@ -2117,44 +2191,6 @@ export const schemaDict = { }, }, }, - ComAtprotoRepoRebaseRepo: { - lexicon: 1, - id: 'com.atproto.repo.rebaseRepo', - defs: { - main: { - type: 'procedure', - description: 'Simple rebase of repo that deletes history', - input: { - encoding: 'application/json', - schema: { - type: 'object', - required: ['repo'], - properties: { - repo: { - type: 'string', - format: 'at-identifier', - description: 'The handle or DID of the repo.', - }, - swapCommit: { - type: 'string', - format: 'cid', - description: - 'Compare and swap with the previous commit by cid.', - }, - }, - }, - }, - errors: [ - { - name: 'InvalidSwap', - }, - { - name: 'ConcurrentWrites', - }, - ], - }, - }, - }, ComAtprotoRepoStrongRef: { lexicon: 1, id: 'com.atproto.repo.strongRef', @@ -2957,7 +2993,7 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Gets the repo state.', + description: 'DEPRECATED - please use com.atproto.sync.getRepo instead', parameters: { type: 'params', required: ['did'], @@ -2967,12 +3003,6 @@ export const schemaDict = { format: 'did', description: 'The DID of the repo.', }, - commit: { - type: 'string', - format: 'cid', - description: - 'The commit to get the checkout from. Defaults to current HEAD.', - }, }, }, output: { @@ -2981,13 +3011,14 @@ export const schemaDict = { }, }, }, - ComAtprotoSyncGetCommitPath: { + ComAtprotoSyncGetHead: { lexicon: 1, - id: 'com.atproto.sync.getCommitPath', + id: 'com.atproto.sync.getHead', defs: { main: { type: 'query', - description: 'Gets the path of repo commits', + description: + 'DEPRECATED - please use com.atproto.sync.getLatestCommit instead', parameters: { type: 'params', required: ['did'], @@ -2997,44 +3028,36 @@ export const schemaDict = { format: 'did', description: 'The DID of the repo.', }, - latest: { - type: 'string', - format: 'cid', - description: 'The most recent commit', - }, - earliest: { - type: 'string', - format: 'cid', - description: 'The earliest commit to start from', - }, }, }, output: { encoding: 'application/json', schema: { type: 'object', - required: ['commits'], + required: ['root'], properties: { - commits: { - type: 'array', - items: { - type: 'string', - format: 'cid', - }, + root: { + type: 'string', + format: 'cid', }, }, }, }, + errors: [ + { + name: 'HeadNotFound', + }, + ], }, }, }, - ComAtprotoSyncGetHead: { + ComAtprotoSyncGetLatestCommit: { lexicon: 1, - id: 'com.atproto.sync.getHead', + id: 'com.atproto.sync.getLatestCommit', defs: { main: { type: 'query', - description: 'Gets the current HEAD CID of a repo.', + description: 'Gets the current commit CID & revision of the repo.', parameters: { type: 'params', required: ['did'], @@ -3050,18 +3073,21 @@ export const schemaDict = { encoding: 'application/json', schema: { type: 'object', - required: ['root'], + required: ['cid', 'rev'], properties: { - root: { + cid: { type: 'string', format: 'cid', }, + rev: { + type: 'string', + }, }, }, }, errors: [ { - name: 'HeadNotFound', + name: 'RepoNotFound', }, ], }, @@ -3110,7 +3136,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Gets the repo state.', + description: + "Gets the did's repo, optionally catching up from a specific revision.", parameters: { type: 'params', required: ['did'], @@ -3120,16 +3147,9 @@ export const schemaDict = { format: 'did', description: 'The DID of the repo.', }, - earliest: { + since: { type: 'string', - format: 'cid', - description: - 'The earliest commit in the commit range (not inclusive)', - }, - latest: { - type: 'string', - format: 'cid', - description: 'The latest commit in the commit range (inclusive)', + description: 'The revision of the repo to catch up from.', }, }, }, @@ -3145,7 +3165,7 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'List blob cids for some range of commits', + description: 'List blob cids since some revision', parameters: { type: 'params', required: ['did'], @@ -3155,15 +3175,18 @@ export const schemaDict = { format: 'did', description: 'The DID of the repo.', }, - latest: { + since: { type: 'string', - format: 'cid', - description: 'The most recent commit', + description: 'Optional revision of the repo to list blobs since', + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 1000, + default: 500, }, - earliest: { + cursor: { type: 'string', - format: 'cid', - description: 'The earliest commit to start from', }, }, }, @@ -3173,6 +3196,9 @@ export const schemaDict = { type: 'object', required: ['cids'], properties: { + cursor: { + type: 'string', + }, cids: { type: 'array', items: { @@ -3337,13 +3363,14 @@ export const schemaDict = { 'tooBig', 'repo', 'commit', - 'prev', + 'rev', + 'since', 'blocks', 'ops', 'blobs', 'time', ], - nullable: ['prev'], + nullable: ['prev', 'since'], properties: { seq: { type: 'integer', @@ -3364,6 +3391,14 @@ export const schemaDict = { prev: { type: 'cid-link', }, + rev: { + type: 'string', + description: 'The rev of the emitted commit', + }, + since: { + type: 'string', + description: 'The rev of the last emitted commit from this repo', + }, blocks: { type: 'bytes', description: 'CAR file containing relevant blocks', @@ -3463,6 +3498,8 @@ export const schemaDict = { }, repoOp: { type: 'object', + description: + "A repo operation, ie a write of a single record. For creates and updates, cid is the record's CID as of this operation. For deletes, it's null.", required: ['action', 'path', 'cid'], nullable: ['cid'], properties: { @@ -3649,6 +3686,7 @@ export const schemaDict = { 'lex:app.bsky.actor.defs#adultContentPref', 'lex:app.bsky.actor.defs#contentLabelPref', 'lex:app.bsky.actor.defs#savedFeedsPref', + 'lex:app.bsky.actor.defs#personalDetailsPref', ], }, }, @@ -3695,6 +3733,16 @@ export const schemaDict = { }, }, }, + personalDetailsPref: { + type: 'object', + properties: { + birthDate: { + type: 'string', + format: 'datetime', + description: 'The birth date of the owner of the account.', + }, + }, + }, }, }, AppBskyActorGetPreferences: { @@ -3863,6 +3911,10 @@ export const schemaDict = { accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, + labels: { + type: 'union', + refs: ['lex:com.atproto.label.defs#selfLabels'], + }, }, }, }, @@ -4076,6 +4128,26 @@ export const schemaDict = { alt: { type: 'string', }, + aspectRatio: { + type: 'ref', + ref: 'lex:app.bsky.embed.images#aspectRatio', + }, + }, + }, + aspectRatio: { + type: 'object', + description: + 'width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit.', + required: ['width', 'height'], + properties: { + width: { + type: 'integer', + minimum: 1, + }, + height: { + type: 'integer', + minimum: 1, + }, }, }, view: { @@ -4105,6 +4177,10 @@ export const schemaDict = { alt: { type: 'string', }, + aspectRatio: { + type: 'ref', + ref: 'lex:app.bsky.embed.images#aspectRatio', + }, }, }, }, @@ -4187,22 +4263,34 @@ export const schemaDict = { }, viewNotFound: { type: 'object', - required: ['uri'], + required: ['uri', 'notFound'], properties: { uri: { type: 'string', format: 'at-uri', }, + notFound: { + type: 'boolean', + const: true, + }, }, }, viewBlocked: { type: 'object', - required: ['uri'], + required: ['uri', 'blocked', 'author'], properties: { uri: { type: 'string', format: 'at-uri', }, + blocked: { + type: 'boolean', + const: true, + }, + author: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#blockedAuthor', + }, }, }, }, @@ -4416,7 +4504,7 @@ export const schemaDict = { }, blockedPost: { type: 'object', - required: ['uri', 'blocked'], + required: ['uri', 'blocked', 'author'], properties: { uri: { type: 'string', @@ -4426,17 +4514,35 @@ export const schemaDict = { type: 'boolean', const: true, }, + author: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#blockedAuthor', + }, }, }, - generatorView: { + blockedAuthor: { type: 'object', - required: ['uri', 'cid', 'did', 'creator', 'displayName', 'indexedAt'], + required: ['did'], properties: { - uri: { + did: { type: 'string', - format: 'at-uri', + format: 'did', }, - cid: { + viewer: { + type: 'ref', + ref: 'lex:app.bsky.actor.defs#viewerState', + }, + }, + }, + generatorView: { + type: 'object', + required: ['uri', 'cid', 'did', 'creator', 'displayName', 'indexedAt'], + properties: { + uri: { + type: 'string', + format: 'at-uri', + }, + cid: { type: 'string', format: 'cid', }, @@ -4609,6 +4715,10 @@ export const schemaDict = { accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, + labels: { + type: 'union', + refs: ['lex:com.atproto.label.defs#selfLabels'], + }, createdAt: { type: 'string', format: 'datetime', @@ -4666,6 +4776,62 @@ export const schemaDict = { }, }, }, + AppBskyFeedGetActorLikes: { + lexicon: 1, + id: 'app.bsky.feed.getActorLikes', + defs: { + main: { + type: 'query', + description: 'A view of the posts liked by an actor.', + parameters: { + type: 'params', + required: ['actor'], + properties: { + actor: { + type: 'string', + format: 'at-identifier', + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['feed'], + properties: { + cursor: { + type: 'string', + }, + feed: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#feedViewPost', + }, + }, + }, + }, + }, + errors: [ + { + name: 'BlockedActor', + }, + { + name: 'BlockedByActor', + }, + ], + }, + }, + }, AppBskyFeedGetAuthorFeed: { lexicon: 1, id: 'app.bsky.feed.getAuthorFeed', @@ -4690,6 +4856,15 @@ export const schemaDict = { cursor: { type: 'string', }, + filter: { + type: 'string', + knownValues: [ + 'posts_with_replies', + 'posts_no_replies', + 'posts_with_media', + ], + default: 'posts_with_replies', + }, }, }, output: { @@ -5137,6 +5312,49 @@ export const schemaDict = { }, }, }, + AppBskyFeedGetSuggestedFeeds: { + lexicon: 1, + id: 'app.bsky.feed.getSuggestedFeeds', + defs: { + main: { + type: 'query', + description: 'Get a list of suggested feeds for the viewer.', + parameters: { + type: 'params', + properties: { + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['feeds'], + properties: { + cursor: { + type: 'string', + }, + feeds: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#generatorView', + }, + }, + }, + }, + }, + }, + }, + }, AppBskyFeedGetTimeline: { lexicon: 1, id: 'app.bsky.feed.getTimeline', @@ -5259,6 +5477,10 @@ export const schemaDict = { format: 'language', }, }, + labels: { + type: 'union', + refs: ['lex:com.atproto.label.defs#selfLabels'], + }, createdAt: { type: 'string', format: 'datetime', @@ -5478,6 +5700,10 @@ export const schemaDict = { muted: { type: 'boolean', }, + blocked: { + type: 'string', + format: 'at-uri', + }, }, }, }, @@ -5706,6 +5932,49 @@ export const schemaDict = { }, }, }, + AppBskyGraphGetListBlocks: { + lexicon: 1, + id: 'app.bsky.graph.getListBlocks', + defs: { + main: { + type: 'query', + description: "Which lists is the requester's account blocking?", + parameters: { + type: 'params', + properties: { + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['lists'], + properties: { + cursor: { + type: 'string', + }, + lists: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.graph.defs#listView', + }, + }, + }, + }, + }, + }, + }, + }, AppBskyGraphGetListMutes: { lexicon: 1, id: 'app.bsky.graph.getListMutes', @@ -5840,6 +6109,42 @@ export const schemaDict = { }, }, }, + AppBskyGraphGetSuggestedFollowsByActor: { + lexicon: 1, + id: 'app.bsky.graph.getSuggestedFollowsByActor', + defs: { + main: { + type: 'query', + description: 'Get suggested follows related to a given actor.', + parameters: { + type: 'params', + required: ['actor'], + properties: { + actor: { + type: 'string', + format: 'at-identifier', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['suggestions'], + properties: { + suggestions: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.actor.defs#profileView', + }, + }, + }, + }, + }, + }, + }, + }, AppBskyGraphList: { lexicon: 1, id: 'app.bsky.graph.list', @@ -5878,6 +6183,35 @@ export const schemaDict = { accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, + labels: { + type: 'union', + refs: ['lex:com.atproto.label.defs#selfLabels'], + }, + createdAt: { + type: 'string', + format: 'datetime', + }, + }, + }, + }, + }, + }, + AppBskyGraphListblock: { + lexicon: 1, + id: 'app.bsky.graph.listblock', + defs: { + main: { + type: 'record', + description: 'A block of an entire list of actors.', + key: 'tid', + record: { + type: 'object', + required: ['subject', 'createdAt'], + properties: { + subject: { + type: 'string', + format: 'at-uri', + }, createdAt: { type: 'string', format: 'datetime', @@ -6144,6 +6478,39 @@ export const schemaDict = { }, }, }, + AppBskyNotificationRegisterPush: { + lexicon: 1, + id: 'app.bsky.notification.registerPush', + defs: { + main: { + type: 'procedure', + description: 'Register for push notifications with a service', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['serviceDid', 'token', 'platform', 'appId'], + properties: { + serviceDid: { + type: 'string', + format: 'did', + }, + token: { + type: 'string', + }, + platform: { + type: 'string', + knownValues: ['ios', 'android', 'web'], + }, + appId: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, AppBskyNotificationUpdateSeen: { lexicon: 1, id: 'app.bsky.notification.updateSeen', @@ -6231,6 +6598,32 @@ export const schemaDict = { }, }, }, + AppBskyUnspeccedApplyLabels: { + lexicon: 1, + id: 'app.bsky.unspecced.applyLabels', + defs: { + main: { + type: 'procedure', + description: 'Allow a labeler to apply labels directly.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['labels'], + properties: { + labels: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:com.atproto.label.defs#label', + }, + }, + }, + }, + }, + }, + }, + }, AppBskyUnspeccedGetPopular: { lexicon: 1, id: 'app.bsky.unspecced.getPopular', @@ -6285,12 +6678,32 @@ export const schemaDict = { main: { type: 'query', description: 'An unspecced view of globally popular feed generators', + parameters: { + type: 'params', + properties: { + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + query: { + type: 'string', + }, + }, + }, output: { encoding: 'application/json', schema: { type: 'object', required: ['feeds'], properties: { + cursor: { + type: 'string', + }, feeds: { type: 'array', items: { @@ -6304,6 +6717,54 @@ export const schemaDict = { }, }, }, + AppBskyUnspeccedGetTimelineSkeleton: { + lexicon: 1, + id: 'app.bsky.unspecced.getTimelineSkeleton', + defs: { + main: { + type: 'query', + description: 'A skeleton of a timeline - UNSPECCED & WILL GO AWAY SOON', + parameters: { + type: 'params', + properties: { + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['feed'], + properties: { + cursor: { + type: 'string', + }, + feed: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#skeletonFeedPost', + }, + }, + }, + }, + }, + errors: [ + { + name: 'UnknownFeed', + }, + ], + }, + }, + }, } export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[] export const lexicons: Lexicons = new Lexicons(schemas) @@ -6320,12 +6781,12 @@ export const ids = { ComAtprotoAdminGetModerationReports: 'com.atproto.admin.getModerationReports', ComAtprotoAdminGetRecord: 'com.atproto.admin.getRecord', ComAtprotoAdminGetRepo: 'com.atproto.admin.getRepo', - ComAtprotoAdminRebaseRepo: 'com.atproto.admin.rebaseRepo', ComAtprotoAdminResolveModerationReports: 'com.atproto.admin.resolveModerationReports', ComAtprotoAdminReverseModerationAction: 'com.atproto.admin.reverseModerationAction', ComAtprotoAdminSearchRepos: 'com.atproto.admin.searchRepos', + ComAtprotoAdminSendEmail: 'com.atproto.admin.sendEmail', ComAtprotoAdminTakeModerationAction: 'com.atproto.admin.takeModerationAction', ComAtprotoAdminUpdateAccountEmail: 'com.atproto.admin.updateAccountEmail', ComAtprotoAdminUpdateAccountHandle: 'com.atproto.admin.updateAccountHandle', @@ -6343,7 +6804,6 @@ export const ids = { ComAtprotoRepoGetRecord: 'com.atproto.repo.getRecord', ComAtprotoRepoListRecords: 'com.atproto.repo.listRecords', ComAtprotoRepoPutRecord: 'com.atproto.repo.putRecord', - ComAtprotoRepoRebaseRepo: 'com.atproto.repo.rebaseRepo', ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef', ComAtprotoRepoUploadBlob: 'com.atproto.repo.uploadBlob', ComAtprotoServerCreateAccount: 'com.atproto.server.createAccount', @@ -6369,8 +6829,8 @@ export const ids = { ComAtprotoSyncGetBlob: 'com.atproto.sync.getBlob', ComAtprotoSyncGetBlocks: 'com.atproto.sync.getBlocks', ComAtprotoSyncGetCheckout: 'com.atproto.sync.getCheckout', - ComAtprotoSyncGetCommitPath: 'com.atproto.sync.getCommitPath', ComAtprotoSyncGetHead: 'com.atproto.sync.getHead', + ComAtprotoSyncGetLatestCommit: 'com.atproto.sync.getLatestCommit', ComAtprotoSyncGetRecord: 'com.atproto.sync.getRecord', ComAtprotoSyncGetRepo: 'com.atproto.sync.getRepo', ComAtprotoSyncListBlobs: 'com.atproto.sync.listBlobs', @@ -6395,6 +6855,7 @@ export const ids = { AppBskyFeedDescribeFeedGenerator: 'app.bsky.feed.describeFeedGenerator', AppBskyFeedGenerator: 'app.bsky.feed.generator', AppBskyFeedGetActorFeeds: 'app.bsky.feed.getActorFeeds', + AppBskyFeedGetActorLikes: 'app.bsky.feed.getActorLikes', AppBskyFeedGetAuthorFeed: 'app.bsky.feed.getAuthorFeed', AppBskyFeedGetFeed: 'app.bsky.feed.getFeed', AppBskyFeedGetFeedGenerator: 'app.bsky.feed.getFeedGenerator', @@ -6404,6 +6865,7 @@ export const ids = { AppBskyFeedGetPostThread: 'app.bsky.feed.getPostThread', AppBskyFeedGetPosts: 'app.bsky.feed.getPosts', AppBskyFeedGetRepostedBy: 'app.bsky.feed.getRepostedBy', + AppBskyFeedGetSuggestedFeeds: 'app.bsky.feed.getSuggestedFeeds', AppBskyFeedGetTimeline: 'app.bsky.feed.getTimeline', AppBskyFeedLike: 'app.bsky.feed.like', AppBskyFeedPost: 'app.bsky.feed.post', @@ -6415,10 +6877,14 @@ export const ids = { AppBskyGraphGetFollowers: 'app.bsky.graph.getFollowers', AppBskyGraphGetFollows: 'app.bsky.graph.getFollows', AppBskyGraphGetList: 'app.bsky.graph.getList', + AppBskyGraphGetListBlocks: 'app.bsky.graph.getListBlocks', AppBskyGraphGetListMutes: 'app.bsky.graph.getListMutes', AppBskyGraphGetLists: 'app.bsky.graph.getLists', AppBskyGraphGetMutes: 'app.bsky.graph.getMutes', + AppBskyGraphGetSuggestedFollowsByActor: + 'app.bsky.graph.getSuggestedFollowsByActor', AppBskyGraphList: 'app.bsky.graph.list', + AppBskyGraphListblock: 'app.bsky.graph.listblock', AppBskyGraphListitem: 'app.bsky.graph.listitem', AppBskyGraphMuteActor: 'app.bsky.graph.muteActor', AppBskyGraphMuteActorList: 'app.bsky.graph.muteActorList', @@ -6427,9 +6893,12 @@ export const ids = { AppBskyNotificationGetUnreadCount: 'app.bsky.notification.getUnreadCount', AppBskyNotificationListNotifications: 'app.bsky.notification.listNotifications', + AppBskyNotificationRegisterPush: 'app.bsky.notification.registerPush', AppBskyNotificationUpdateSeen: 'app.bsky.notification.updateSeen', AppBskyRichtextFacet: 'app.bsky.richtext.facet', + AppBskyUnspeccedApplyLabels: 'app.bsky.unspecced.applyLabels', AppBskyUnspeccedGetPopular: 'app.bsky.unspecced.getPopular', AppBskyUnspeccedGetPopularFeedGenerators: 'app.bsky.unspecced.getPopularFeedGenerators', + AppBskyUnspeccedGetTimelineSkeleton: 'app.bsky.unspecced.getTimelineSkeleton', } diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts index 3b338c06f3a..4446c1f7a03 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts @@ -108,6 +108,7 @@ export type Preferences = ( | AdultContentPref | ContentLabelPref | SavedFeedsPref + | PersonalDetailsPref | { $type: string; [k: string]: unknown } )[] @@ -163,3 +164,21 @@ export function isSavedFeedsPref(v: unknown): v is SavedFeedsPref { export function validateSavedFeedsPref(v: unknown): ValidationResult { return lexicons.validate('app.bsky.actor.defs#savedFeedsPref', v) } + +export interface PersonalDetailsPref { + /** The birth date of the owner of the account. */ + birthDate?: string + [k: string]: unknown +} + +export function isPersonalDetailsPref(v: unknown): v is PersonalDetailsPref { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.actor.defs#personalDetailsPref' + ) +} + +export function validatePersonalDetailsPref(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.actor.defs#personalDetailsPref', v) +} diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/getPreferences.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/getPreferences.ts index 9ba813a4313..88d78a57cba 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/getPreferences.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/getPreferences.ts @@ -32,10 +32,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/getProfile.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/getProfile.ts index 4a2ef252e7e..802afda5361 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/getProfile.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/getProfile.ts @@ -29,10 +29,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/getProfiles.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/getProfiles.ts index 89c014ebe0a..2549b264e33 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/getProfiles.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/getProfiles.ts @@ -34,10 +34,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/getSuggestions.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/getSuggestions.ts index 5e59cf571d5..a6d4d6102af 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/getSuggestions.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/getSuggestions.ts @@ -36,10 +36,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/profile.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/profile.ts index 2d4bf526601..7dbc4c1ccec 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/profile.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/profile.ts @@ -5,12 +5,16 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' +import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' export interface Record { displayName?: string description?: string avatar?: BlobRef banner?: BlobRef + labels?: + | ComAtprotoLabelDefs.SelfLabels + | { $type: string; [k: string]: unknown } [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/putPreferences.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/putPreferences.ts index ba0531cc3c0..1e5ee2d834e 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/putPreferences.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/putPreferences.ts @@ -27,10 +27,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/searchActors.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/searchActors.ts index 0b425ba4df7..f620a463cff 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/searchActors.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/searchActors.ts @@ -37,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts index 43a6be287b4..4f5bbb7c23c 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts @@ -35,10 +35,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/embed/images.ts b/packages/bsky/src/lexicon/types/app/bsky/embed/images.ts index 422b036fa70..4864fad3dea 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/embed/images.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/embed/images.ts @@ -27,6 +27,7 @@ export function validateMain(v: unknown): ValidationResult { export interface Image { image: BlobRef alt: string + aspectRatio?: AspectRatio [k: string]: unknown } @@ -40,6 +41,25 @@ export function validateImage(v: unknown): ValidationResult { return lexicons.validate('app.bsky.embed.images#image', v) } +/** width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit. */ +export interface AspectRatio { + width: number + height: number + [k: string]: unknown +} + +export function isAspectRatio(v: unknown): v is AspectRatio { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.embed.images#aspectRatio' + ) +} + +export function validateAspectRatio(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.embed.images#aspectRatio', v) +} + export interface View { images: ViewImage[] [k: string]: unknown @@ -59,6 +79,7 @@ export interface ViewImage { thumb: string fullsize: string alt: string + aspectRatio?: AspectRatio [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/app/bsky/embed/record.ts b/packages/bsky/src/lexicon/types/app/bsky/embed/record.ts index cdf38d6d7ad..cea5742a45e 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/embed/record.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/embed/record.ts @@ -84,6 +84,7 @@ export function validateViewRecord(v: unknown): ValidationResult { export interface ViewNotFound { uri: string + notFound: true [k: string]: unknown } @@ -101,6 +102,8 @@ export function validateViewNotFound(v: unknown): ValidationResult { export interface ViewBlocked { uri: string + blocked: true + author: AppBskyFeedDefs.BlockedAuthor [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/defs.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/defs.ts index 80069e4e412..463445fbd49 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/defs.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/defs.ts @@ -171,6 +171,7 @@ export function validateNotFoundPost(v: unknown): ValidationResult { export interface BlockedPost { uri: string blocked: true + author: BlockedAuthor [k: string]: unknown } @@ -186,6 +187,24 @@ export function validateBlockedPost(v: unknown): ValidationResult { return lexicons.validate('app.bsky.feed.defs#blockedPost', v) } +export interface BlockedAuthor { + did: string + viewer?: AppBskyActorDefs.ViewerState + [k: string]: unknown +} + +export function isBlockedAuthor(v: unknown): v is BlockedAuthor { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.feed.defs#blockedAuthor' + ) +} + +export function validateBlockedAuthor(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.feed.defs#blockedAuthor', v) +} + export interface GeneratorView { uri: string cid: string diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts index 9444257c905..d329bf20a5a 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts @@ -33,13 +33,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput export interface Feed { uri: string diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/generator.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/generator.ts index 5ad34318ee3..757e74db845 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/generator.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/generator.ts @@ -6,6 +6,7 @@ import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' import * as AppBskyRichtextFacet from '../richtext/facet' +import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' export interface Record { did: string @@ -13,6 +14,9 @@ export interface Record { description?: string descriptionFacets?: AppBskyRichtextFacet.Main[] avatar?: BlobRef + labels?: + | ComAtprotoLabelDefs.SelfLabels + | { $type: string; [k: string]: unknown } createdAt: string [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getActorFeeds.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getActorFeeds.ts index 2a1e9edb889..3e930cbe201 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getActorFeeds.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getActorFeeds.ts @@ -37,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getActorLikes.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getActorLikes.ts new file mode 100644 index 00000000000..df2f291e1a7 --- /dev/null +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getActorLikes.ts @@ -0,0 +1,50 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' +import * as AppBskyFeedDefs from './defs' + +export interface QueryParams { + actor: string + limit: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + feed: AppBskyFeedDefs.FeedViewPost[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string + error?: 'BlockedActor' | 'BlockedByActor' +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts index ce56c2667fd..cd66ef5c392 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts @@ -13,6 +13,11 @@ export interface QueryParams { actor: string limit: number cursor?: string + filter: + | 'posts_with_replies' + | 'posts_no_replies' + | 'posts_with_media' + | (string & {}) } export type InputSchema = undefined @@ -38,10 +43,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeed.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeed.ts index 837a6d9a892..e72b1010aea 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeed.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeed.ts @@ -38,10 +38,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts index 859f0c70b84..fab3b30c316 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts @@ -36,10 +36,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts index 85cbb544721..d7e082f2362 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts @@ -34,10 +34,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts index fe132e926f6..1c8f349b42b 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts @@ -38,10 +38,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getLikes.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getLikes.ts index deb95fb063a..d581f5bfa9c 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getLikes.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getLikes.ts @@ -40,13 +40,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput export interface Like { indexedAt: string diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getPostThread.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getPostThread.ts index e9f154a86a6..61de94b729d 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getPostThread.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getPostThread.ts @@ -41,10 +41,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getPosts.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getPosts.ts index 3cf5747ec1f..4282f5d349f 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getPosts.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getPosts.ts @@ -34,10 +34,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getRepostedBy.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getRepostedBy.ts index 7f7d51a77af..0b9c1a6f68b 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getRepostedBy.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getRepostedBy.ts @@ -40,10 +40,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getSuggestedFeeds.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getSuggestedFeeds.ts new file mode 100644 index 00000000000..9b271335466 --- /dev/null +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getSuggestedFeeds.ts @@ -0,0 +1,48 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' +import * as AppBskyFeedDefs from './defs' + +export interface QueryParams { + limit: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + feeds: AppBskyFeedDefs.GeneratorView[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getTimeline.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getTimeline.ts index 6be4293374c..832caf5c6f7 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getTimeline.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getTimeline.ts @@ -37,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/post.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/post.ts index b111c4783e6..8942bc724cd 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/post.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/post.ts @@ -10,6 +10,7 @@ import * as AppBskyEmbedImages from '../embed/images' import * as AppBskyEmbedExternal from '../embed/external' import * as AppBskyEmbedRecord from '../embed/record' import * as AppBskyEmbedRecordWithMedia from '../embed/recordWithMedia' +import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' export interface Record { @@ -25,6 +26,9 @@ export interface Record { | AppBskyEmbedRecordWithMedia.Main | { $type: string; [k: string]: unknown } langs?: string[] + labels?: + | ComAtprotoLabelDefs.SelfLabels + | { $type: string; [k: string]: unknown } createdAt: string [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/defs.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/defs.ts index e50338d488d..63c05b5faa3 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/defs.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/defs.ts @@ -81,6 +81,7 @@ export const MODLIST = 'app.bsky.graph.defs#modlist' export interface ListViewerState { muted?: boolean + blocked?: string [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getBlocks.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getBlocks.ts index f3a6eb4b55c..d380a14880a 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getBlocks.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getBlocks.ts @@ -36,10 +36,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getFollowers.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getFollowers.ts index e6bec023938..b337be52c1b 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getFollowers.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getFollowers.ts @@ -38,10 +38,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getFollows.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getFollows.ts index 96c18201e1b..71e9ca0270c 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getFollows.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getFollows.ts @@ -38,10 +38,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getList.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getList.ts index d0cfe398adf..fc45dd20985 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getList.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getList.ts @@ -38,10 +38,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getListBlocks.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getListBlocks.ts new file mode 100644 index 00000000000..04cca70b44d --- /dev/null +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getListBlocks.ts @@ -0,0 +1,48 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' +import * as AppBskyGraphDefs from './defs' + +export interface QueryParams { + limit: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + lists: AppBskyGraphDefs.ListView[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getListMutes.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getListMutes.ts index b0820a7ea53..04cca70b44d 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getListMutes.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getListMutes.ts @@ -36,10 +36,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getLists.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getLists.ts index b6626cbe096..8acf9362c00 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getLists.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getLists.ts @@ -37,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getMutes.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getMutes.ts index 32956284d7d..0034095b975 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getMutes.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getMutes.ts @@ -36,10 +36,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts new file mode 100644 index 00000000000..a2245846fd2 --- /dev/null +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts @@ -0,0 +1,46 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' +import * as AppBskyActorDefs from '../actor/defs' + +export interface QueryParams { + actor: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + suggestions: AppBskyActorDefs.ProfileView[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/list.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/list.ts index 4304ca98b03..36a7fb17a3f 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/list.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/list.ts @@ -7,6 +7,7 @@ import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' import * as AppBskyGraphDefs from './defs' import * as AppBskyRichtextFacet from '../richtext/facet' +import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' export interface Record { purpose: AppBskyGraphDefs.ListPurpose @@ -14,6 +15,9 @@ export interface Record { description?: string descriptionFacets?: AppBskyRichtextFacet.Main[] avatar?: BlobRef + labels?: + | ComAtprotoLabelDefs.SelfLabels + | { $type: string; [k: string]: unknown } createdAt: string [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/listblock.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/listblock.ts new file mode 100644 index 00000000000..59f2e057eb5 --- /dev/null +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/listblock.ts @@ -0,0 +1,26 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' + +export interface Record { + subject: string + createdAt: string + [k: string]: unknown +} + +export function isRecord(v: unknown): v is Record { + return ( + isObj(v) && + hasProp(v, '$type') && + (v.$type === 'app.bsky.graph.listblock#main' || + v.$type === 'app.bsky.graph.listblock') + ) +} + +export function validateRecord(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.graph.listblock#main', v) +} diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/muteActor.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/muteActor.ts index fd19f661533..52d1b864989 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/muteActor.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/muteActor.ts @@ -26,10 +26,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/muteActorList.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/muteActorList.ts index e099011b3f7..bf803f388af 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/muteActorList.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/muteActorList.ts @@ -26,10 +26,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/unmuteActor.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/unmuteActor.ts index fd19f661533..52d1b864989 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/unmuteActor.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/unmuteActor.ts @@ -26,10 +26,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/unmuteActorList.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/unmuteActorList.ts index e099011b3f7..bf803f388af 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/unmuteActorList.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/unmuteActorList.ts @@ -26,10 +26,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/notification/getUnreadCount.ts b/packages/bsky/src/lexicon/types/app/bsky/notification/getUnreadCount.ts index 365becb061b..6cf3c84beb5 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/notification/getUnreadCount.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/notification/getUnreadCount.ts @@ -33,10 +33,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/notification/listNotifications.ts b/packages/bsky/src/lexicon/types/app/bsky/notification/listNotifications.ts index 02df7ade82e..156ba349ec4 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/notification/listNotifications.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/notification/listNotifications.ts @@ -38,13 +38,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput export interface Notification { uri: string diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/rebaseRepo.ts b/packages/bsky/src/lexicon/types/app/bsky/notification/registerPush.ts similarity index 70% rename from packages/pds/src/lexicon/types/com/atproto/repo/rebaseRepo.ts rename to packages/bsky/src/lexicon/types/app/bsky/notification/registerPush.ts index d94a817f47c..9923aeb058e 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/rebaseRepo.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/notification/registerPush.ts @@ -11,10 +11,10 @@ import { HandlerAuth } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ - repo: string - /** Compare and swap with the previous commit by cid. */ - swapCommit?: string + serviceDid: string + token: string + platform: 'ios' | 'android' | 'web' | (string & {}) + appId: string [k: string]: unknown } @@ -26,14 +26,16 @@ export interface HandlerInput { export interface HandlerError { status: number message?: string - error?: 'InvalidSwap' | 'ConcurrentWrites' } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/notification/updateSeen.ts b/packages/bsky/src/lexicon/types/app/bsky/notification/updateSeen.ts index 177f1f4bdbc..136191edc40 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/notification/updateSeen.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/notification/updateSeen.ts @@ -26,10 +26,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/rebaseRepo.ts b/packages/bsky/src/lexicon/types/app/bsky/unspecced/applyLabels.ts similarity index 70% rename from packages/bsky/src/lexicon/types/com/atproto/admin/rebaseRepo.ts rename to packages/bsky/src/lexicon/types/app/bsky/unspecced/applyLabels.ts index d94a817f47c..1d359a9547d 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/rebaseRepo.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/unspecced/applyLabels.ts @@ -7,14 +7,12 @@ import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' import { HandlerAuth } from '@atproto/xrpc-server' +import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ - repo: string - /** Compare and swap with the previous commit by cid. */ - swapCommit?: string + labels: ComAtprotoLabelDefs.Label[] [k: string]: unknown } @@ -26,14 +24,16 @@ export interface HandlerInput { export interface HandlerError { status: number message?: string - error?: 'InvalidSwap' | 'ConcurrentWrites' } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/unspecced/getPopular.ts b/packages/bsky/src/lexicon/types/app/bsky/unspecced/getPopular.ts index 44cfe695920..8471ed77a6c 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/unspecced/getPopular.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/unspecced/getPopular.ts @@ -37,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts b/packages/bsky/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts index 981f7d92519..97937e926c2 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts @@ -9,11 +9,16 @@ import { CID } from 'multiformats/cid' import { HandlerAuth } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from '../feed/defs' -export interface QueryParams {} +export interface QueryParams { + limit: number + cursor?: string + query?: string +} export type InputSchema = undefined export interface OutputSchema { + cursor?: string feeds: AppBskyFeedDefs.GeneratorView[] [k: string]: unknown } @@ -32,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/unspecced/getTimelineSkeleton.ts b/packages/bsky/src/lexicon/types/app/bsky/unspecced/getTimelineSkeleton.ts new file mode 100644 index 00000000000..4ccad20c902 --- /dev/null +++ b/packages/bsky/src/lexicon/types/app/bsky/unspecced/getTimelineSkeleton.ts @@ -0,0 +1,49 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' +import * as AppBskyFeedDefs from '../feed/defs' + +export interface QueryParams { + limit: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + feed: AppBskyFeedDefs.SkeletonFeedPost[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string + error?: 'UnknownFeed' +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/defs.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/defs.ts index c73be1be6e4..968252a4c2c 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/defs.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/defs.ts @@ -13,6 +13,8 @@ import * as ComAtprotoLabelDefs from '../label/defs' export interface ActionView { id: number action: ActionType + /** Indicates how long this action was meant to be in effect before automatically expiring. */ + durationInHours?: number subject: | RepoRef | ComAtprotoRepoStrongRef.Main @@ -43,6 +45,8 @@ export function validateActionView(v: unknown): ValidationResult { export interface ActionViewDetail { id: number action: ActionType + /** Indicates how long this action was meant to be in effect before automatically expiring. */ + durationInHours?: number subject: | RepoView | RepoViewNotFound @@ -75,6 +79,8 @@ export function validateActionViewDetail(v: unknown): ValidationResult { export interface ActionViewCurrent { id: number action: ActionType + /** Indicates how long this action was meant to be in effect before automatically expiring. */ + durationInHours?: number [k: string]: unknown } @@ -189,6 +195,7 @@ export interface RepoView { moderation: Moderation invitedBy?: ComAtprotoServerDefs.InviteCode invitesDisabled?: boolean + inviteNote?: string [k: string]: unknown } @@ -215,6 +222,7 @@ export interface RepoViewDetail { invitedBy?: ComAtprotoServerDefs.InviteCode invites?: ComAtprotoServerDefs.InviteCode[] invitesDisabled?: boolean + inviteNote?: string [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts index fdba69e2e4b..051fabb65e1 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts @@ -12,6 +12,8 @@ export interface QueryParams {} export interface InputSchema { account: string + /** Additionally add a note describing why the invites were disabled */ + note?: string [k: string]: unknown } @@ -26,10 +28,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts index 2e9d326afe9..2b64371f1ed 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts @@ -27,10 +27,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts index fdba69e2e4b..4a26d302333 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts @@ -12,6 +12,8 @@ export interface QueryParams {} export interface InputSchema { account: string + /** Additionally add a note describing why the invites were enabled */ + note?: string [k: string]: unknown } @@ -26,10 +28,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/getInviteCodes.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/getInviteCodes.ts index e3f3fb6e739..1eb099aae66 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/getInviteCodes.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/getInviteCodes.ts @@ -37,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationAction.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationAction.ts index f774416a083..2ab52f237cc 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationAction.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationAction.ts @@ -29,10 +29,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationActions.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationActions.ts index b8a1e683ec5..4c29f965df6 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationActions.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationActions.ts @@ -37,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationReport.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationReport.ts index 63a1a551f97..28d714453f2 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationReport.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationReport.ts @@ -29,10 +29,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationReports.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationReports.ts index 93ec8bc879d..d50af44c757 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationReports.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationReports.ts @@ -12,6 +12,8 @@ import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { subject?: string ignoreSubjects?: string[] + /** Get all reports that were actioned by a specific moderator */ + actionedBy?: string /** Filter reports made by one or more DIDs */ reporters?: string[] resolved?: boolean @@ -49,10 +51,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/getRecord.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/getRecord.ts index 77895570d96..48222d9d819 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/getRecord.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/getRecord.ts @@ -31,10 +31,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/getRepo.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/getRepo.ts index 90aca8bdce5..19911baa90a 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/getRepo.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/getRepo.ts @@ -30,10 +30,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/resolveModerationReports.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/resolveModerationReports.ts index b8558d4e2b7..e3f4d028202 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/resolveModerationReports.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/resolveModerationReports.ts @@ -37,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/reverseModerationAction.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/reverseModerationAction.ts index 8f17d275761..17dcb5085de 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/reverseModerationAction.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/reverseModerationAction.ts @@ -37,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/searchRepos.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/searchRepos.ts index 4dc0570276b..c79cd046ca0 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/searchRepos.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/searchRepos.ts @@ -38,10 +38,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/sendEmail.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/sendEmail.ts new file mode 100644 index 00000000000..87e7ceec172 --- /dev/null +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/sendEmail.ts @@ -0,0 +1,51 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export interface InputSchema { + recipientDid: string + content: string + subject?: string + [k: string]: unknown +} + +export interface OutputSchema { + sent: boolean + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/takeModerationAction.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/takeModerationAction.ts index 737fe446fb7..fbbf14dff0f 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/takeModerationAction.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/takeModerationAction.ts @@ -26,6 +26,8 @@ export interface InputSchema { createLabelVals?: string[] negateLabelVals?: string[] reason: string + /** Indicates how long this action was meant to be in effect before automatically expiring. */ + durationInHours?: number createdBy: string [k: string]: unknown } @@ -50,10 +52,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts index eb00af00727..9e6140256ef 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts @@ -28,10 +28,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts index b9bb76a9c06..c378f421926 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts @@ -27,10 +27,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/identity/resolveHandle.ts b/packages/bsky/src/lexicon/types/com/atproto/identity/resolveHandle.ts index bdcd0ac45d9..ef90e99bb30 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/identity/resolveHandle.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/identity/resolveHandle.ts @@ -34,10 +34,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/identity/updateHandle.ts b/packages/bsky/src/lexicon/types/com/atproto/identity/updateHandle.ts index c0c3cf523c5..1f639c344e9 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/identity/updateHandle.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/identity/updateHandle.ts @@ -26,10 +26,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/label/defs.ts b/packages/bsky/src/lexicon/types/com/atproto/label/defs.ts index 17a8480b9d6..a01ad78e254 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/label/defs.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/label/defs.ts @@ -34,3 +34,40 @@ export function isLabel(v: unknown): v is Label { export function validateLabel(v: unknown): ValidationResult { return lexicons.validate('com.atproto.label.defs#label', v) } + +/** Metadata tags on an atproto record, published by the author within the record. */ +export interface SelfLabels { + values: SelfLabel[] + [k: string]: unknown +} + +export function isSelfLabels(v: unknown): v is SelfLabels { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'com.atproto.label.defs#selfLabels' + ) +} + +export function validateSelfLabels(v: unknown): ValidationResult { + return lexicons.validate('com.atproto.label.defs#selfLabels', v) +} + +/** Metadata tag on an atproto record, published by the author within the record. Note -- schemas should use #selfLabels, not #selfLabel. */ +export interface SelfLabel { + /** the short string name of the value or type of this label */ + val: string + [k: string]: unknown +} + +export function isSelfLabel(v: unknown): v is SelfLabel { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'com.atproto.label.defs#selfLabel' + ) +} + +export function validateSelfLabel(v: unknown): ValidationResult { + return lexicons.validate('com.atproto.label.defs#selfLabel', v) +} diff --git a/packages/bsky/src/lexicon/types/com/atproto/label/queryLabels.ts b/packages/bsky/src/lexicon/types/com/atproto/label/queryLabels.ts index 791e474c50b..72cf5c52be6 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/label/queryLabels.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/label/queryLabels.ts @@ -40,10 +40,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/label/subscribeLabels.ts b/packages/bsky/src/lexicon/types/com/atproto/label/subscribeLabels.ts index 758407e0437..9d4b4441ae0 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/label/subscribeLabels.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/label/subscribeLabels.ts @@ -20,12 +20,15 @@ export type OutputSchema = | { $type: string; [k: string]: unknown } export type HandlerError = ErrorFrame<'FutureCursor'> export type HandlerOutput = HandlerError | OutputSchema -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams req: IncomingMessage signal: AbortSignal -}) => AsyncIterable +} +export type Handler = ( + ctx: HandlerReqCtx, +) => AsyncIterable export interface Labels { seq: number diff --git a/packages/bsky/src/lexicon/types/com/atproto/moderation/createReport.ts b/packages/bsky/src/lexicon/types/com/atproto/moderation/createReport.ts index a1be0464bf3..96aaf4a9c29 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/moderation/createReport.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/moderation/createReport.ts @@ -53,10 +53,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/moderation/defs.ts b/packages/bsky/src/lexicon/types/com/atproto/moderation/defs.ts index 9ab6e60f9bd..81697226189 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/moderation/defs.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/moderation/defs.ts @@ -21,7 +21,7 @@ export const REASONSPAM = 'com.atproto.moderation.defs#reasonSpam' export const REASONVIOLATION = 'com.atproto.moderation.defs#reasonViolation' /** Misleading identity, affiliation, or content */ export const REASONMISLEADING = 'com.atproto.moderation.defs#reasonMisleading' -/** Unwanted or mis-labeled sexual content */ +/** Unwanted or mislabeled sexual content */ export const REASONSEXUAL = 'com.atproto.moderation.defs#reasonSexual' /** Rude, harassing, explicit, or otherwise unwelcoming behavior */ export const REASONRUDE = 'com.atproto.moderation.defs#reasonRude' diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/applyWrites.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/applyWrites.ts index 6fba024d715..53f2972e116 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/applyWrites.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/applyWrites.ts @@ -32,13 +32,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput /** Create a new record. */ export interface Create { diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/createRecord.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/createRecord.ts index f731dee536a..e069f8caf74 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/createRecord.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/createRecord.ts @@ -50,10 +50,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/deleteRecord.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/deleteRecord.ts index 016547e4075..5ee016cbed1 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/deleteRecord.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/deleteRecord.ts @@ -36,10 +36,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/describeRepo.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/describeRepo.ts index 6c1300daba4..7b8a2b995eb 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/describeRepo.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/describeRepo.ts @@ -38,10 +38,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/getRecord.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/getRecord.ts index ecabf517539..35c9b4b7166 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/getRecord.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/getRecord.ts @@ -42,10 +42,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/listRecords.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/listRecords.ts index 76c3429833c..e58d9714e33 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/listRecords.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/listRecords.ts @@ -46,13 +46,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput export interface Record { uri: string diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/putRecord.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/putRecord.ts index a56b6ca25f1..364eb59f6f1 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/putRecord.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/putRecord.ts @@ -52,10 +52,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/uploadBlob.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/uploadBlob.ts index b3f06f30f39..ad6002df925 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/uploadBlob.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/uploadBlob.ts @@ -35,10 +35,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts b/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts index 646091fde1e..c67e7445bf9 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts @@ -53,10 +53,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/createAppPassword.ts b/packages/bsky/src/lexicon/types/com/atproto/server/createAppPassword.ts index 39eb73140c6..8e4a0a519e0 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/createAppPassword.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/createAppPassword.ts @@ -35,13 +35,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput export interface AppPassword { name: string diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/createInviteCode.ts b/packages/bsky/src/lexicon/types/com/atproto/server/createInviteCode.ts index 10fef7ca0e4..acfac56ba76 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/createInviteCode.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/createInviteCode.ts @@ -38,10 +38,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/createInviteCodes.ts b/packages/bsky/src/lexicon/types/com/atproto/server/createInviteCodes.ts index 98d6585ea06..5887d77fada 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/createInviteCodes.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/createInviteCodes.ts @@ -39,13 +39,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput export interface AccountCodes { account: string diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/createSession.ts b/packages/bsky/src/lexicon/types/com/atproto/server/createSession.ts index 1b184da071f..b836551f301 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/createSession.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/createSession.ts @@ -44,10 +44,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/deleteAccount.ts b/packages/bsky/src/lexicon/types/com/atproto/server/deleteAccount.ts index 6fde5f17e90..37ddbba13e0 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/deleteAccount.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/deleteAccount.ts @@ -29,10 +29,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/deleteSession.ts b/packages/bsky/src/lexicon/types/com/atproto/server/deleteSession.ts index 909c2be8cab..e4244870425 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/deleteSession.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/deleteSession.ts @@ -19,10 +19,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/describeServer.ts b/packages/bsky/src/lexicon/types/com/atproto/server/describeServer.ts index 9d65f932ec2..bc73d541a04 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/describeServer.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/describeServer.ts @@ -33,13 +33,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput export interface Links { privacyPolicy?: string diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts b/packages/bsky/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts index d0a9dc34307..e387a5e38e4 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts @@ -36,10 +36,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/getSession.ts b/packages/bsky/src/lexicon/types/com/atproto/server/getSession.ts index c0f868ca0c1..388fb5eae9d 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/getSession.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/getSession.ts @@ -33,10 +33,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/listAppPasswords.ts b/packages/bsky/src/lexicon/types/com/atproto/server/listAppPasswords.ts index ca0d4e80839..ebd74da9d39 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/listAppPasswords.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/listAppPasswords.ts @@ -32,13 +32,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput export interface AppPassword { name: string diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/refreshSession.ts b/packages/bsky/src/lexicon/types/com/atproto/server/refreshSession.ts index 56e34eaa71f..e47bf09fbc2 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/refreshSession.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/refreshSession.ts @@ -35,10 +35,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/requestAccountDelete.ts b/packages/bsky/src/lexicon/types/com/atproto/server/requestAccountDelete.ts index 909c2be8cab..e4244870425 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/requestAccountDelete.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/requestAccountDelete.ts @@ -19,10 +19,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/requestPasswordReset.ts b/packages/bsky/src/lexicon/types/com/atproto/server/requestPasswordReset.ts index d1973cd4825..47fb4bb62f3 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/requestPasswordReset.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/requestPasswordReset.ts @@ -26,10 +26,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/resetPassword.ts b/packages/bsky/src/lexicon/types/com/atproto/server/resetPassword.ts index 5e0148284a1..9e6ece3e4c4 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/resetPassword.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/resetPassword.ts @@ -28,10 +28,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/revokeAppPassword.ts b/packages/bsky/src/lexicon/types/com/atproto/server/revokeAppPassword.ts index e6bdcd09801..4627f68eaa2 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/revokeAppPassword.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/revokeAppPassword.ts @@ -26,10 +26,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/getBlob.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/getBlob.ts index 8644d8ce5d9..60750902472 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/getBlob.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/getBlob.ts @@ -31,10 +31,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/getBlocks.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/getBlocks.ts index 9c20c12fc29..e73410efb41 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/getBlocks.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/getBlocks.ts @@ -30,10 +30,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/getCheckout.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/getCheckout.ts index 5b4fd2a12d8..63a657e56b9 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/getCheckout.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/getCheckout.ts @@ -12,8 +12,6 @@ import { HandlerAuth } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ did: string - /** The commit to get the checkout from. Defaults to current HEAD. */ - commit?: string } export type InputSchema = undefined @@ -31,10 +29,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/getHead.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/getHead.ts index ce8cf66cfb4..586ae1a4189 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/getHead.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/getHead.ts @@ -35,10 +35,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/getCommitPath.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/getLatestCommit.ts similarity index 78% rename from packages/pds/src/lexicon/types/com/atproto/sync/getCommitPath.ts rename to packages/bsky/src/lexicon/types/com/atproto/sync/getLatestCommit.ts index cf3bd997264..9b91e878724 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/getCommitPath.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/getLatestCommit.ts @@ -11,16 +11,13 @@ import { HandlerAuth } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ did: string - /** The most recent commit */ - latest?: string - /** The earliest commit to start from */ - earliest?: string } export type InputSchema = undefined export interface OutputSchema { - commits: string[] + cid: string + rev: string [k: string]: unknown } @@ -35,13 +32,17 @@ export interface HandlerSuccess { export interface HandlerError { status: number message?: string + error?: 'RepoNotFound' } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/getRecord.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/getRecord.ts index da29fb675ff..297f0ac7794 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/getRecord.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/getRecord.ts @@ -33,10 +33,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/getRepo.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/getRepo.ts index 98074484cbd..495d31a1a22 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/getRepo.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/getRepo.ts @@ -12,10 +12,8 @@ import { HandlerAuth } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ did: string - /** The earliest commit in the commit range (not inclusive) */ - earliest?: string - /** The latest commit in the commit range (inclusive) */ - latest?: string + /** The revision of the repo to catch up from. */ + since?: string } export type InputSchema = undefined @@ -33,10 +31,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/listBlobs.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/listBlobs.ts index 024a703440c..936b08a69f8 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/listBlobs.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/listBlobs.ts @@ -11,15 +11,16 @@ import { HandlerAuth } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ did: string - /** The most recent commit */ - latest?: string - /** The earliest commit to start from */ - earliest?: string + /** Optional revision of the repo to list blobs since */ + since?: string + limit: number + cursor?: string } export type InputSchema = undefined export interface OutputSchema { + cursor?: string cids: string[] [k: string]: unknown } @@ -38,10 +39,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/listRepos.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/listRepos.ts index 506328d6f60..afbc9df8475 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/listRepos.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/listRepos.ts @@ -35,13 +35,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput export interface Repo { did: string diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts index df921fb8bc2..3d310c1139a 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts @@ -27,10 +27,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/requestCrawl.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/requestCrawl.ts index c634409f6ba..87ef20d7297 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/requestCrawl.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/requestCrawl.ts @@ -27,10 +27,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/subscribeRepos.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/subscribeRepos.ts index b0bf73edaca..ae9cf01f8f2 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/subscribeRepos.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/subscribeRepos.ts @@ -22,12 +22,15 @@ export type OutputSchema = | { $type: string; [k: string]: unknown } export type HandlerError = ErrorFrame<'FutureCursor' | 'ConsumerTooSlow'> export type HandlerOutput = HandlerError | OutputSchema -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams req: IncomingMessage signal: AbortSignal -}) => AsyncIterable +} +export type Handler = ( + ctx: HandlerReqCtx, +) => AsyncIterable export interface Commit { seq: number @@ -35,7 +38,11 @@ export interface Commit { tooBig: boolean repo: string commit: CID - prev: CID | null + prev?: CID | null + /** The rev of the emitted commit */ + rev: string + /** The rev of the last emitted commit from this repo */ + since: string | null /** CAR file containing relevant blocks */ blocks: Uint8Array ops: RepoOp[] @@ -133,6 +140,7 @@ export function validateInfo(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#info', v) } +/** A repo operation, ie a write of a single record. For creates and updates, cid is the record's CID as of this operation. For deletes, it's null. */ export interface RepoOp { action: 'create' | 'update' | 'delete' | (string & {}) path: string diff --git a/packages/bsky/src/logger.ts b/packages/bsky/src/logger.ts index edfd6cd4d81..fb6c8b32e43 100644 --- a/packages/bsky/src/logger.ts +++ b/packages/bsky/src/logger.ts @@ -1,10 +1,14 @@ import pinoHttp from 'pino-http' import { subsystemLogger } from '@atproto/common' -export const dbLogger = subsystemLogger('bsky:db') -export const subLogger = subsystemLogger('bsky:sub') -export const labelerLogger = subsystemLogger('bsky:labeler') -export const httpLogger = subsystemLogger('bsky') +export const dbLogger: ReturnType = + subsystemLogger('bsky:db') +export const subLogger: ReturnType = + subsystemLogger('bsky:sub') +export const labelerLogger: ReturnType = + subsystemLogger('bsky:labeler') +export const httpLogger: ReturnType = + subsystemLogger('bsky') export const loggerMiddleware = pinoHttp({ logger: httpLogger, diff --git a/packages/bsky/src/notifications.ts b/packages/bsky/src/notifications.ts new file mode 100644 index 00000000000..fdf24919d19 --- /dev/null +++ b/packages/bsky/src/notifications.ts @@ -0,0 +1,382 @@ +import axios from 'axios' +import { Insertable, sql } from 'kysely' +import TTLCache from '@isaacs/ttlcache' +import { AtUri } from '@atproto/api' +import { MINUTE, chunkArray } from '@atproto/common' +import Database from './db/primary' +import { Notification } from './db/tables/notification' +import { NotificationPushToken as PushToken } from './db/tables/notification-push-token' +import logger from './indexer/logger' +import { notSoftDeletedClause, valuesList } from './db/util' +import { ids } from './lexicon/lexicons' +import { retryHttp } from './util/retry' + +export type Platform = 'ios' | 'android' | 'web' + +type PushNotification = { + tokens: string[] + platform: 1 | 2 // 1 = ios, 2 = android + title: string + message: string + topic: string + data?: { + [key: string]: string + } + collapse_id?: string + collapse_key?: string +} + +type InsertableNotif = Insertable + +type NotifDisplay = { + key: string + rateLimit: boolean + title: string + body: string + notif: InsertableNotif +} + +export class NotificationServer { + private rateLimiter = new RateLimiter(1, 30 * MINUTE) + + constructor(public db: Database, public pushEndpoint?: string) {} + + async getTokensByDid(dids: string[]) { + if (!dids.length) return {} + const tokens = await this.db.db + .selectFrom('notification_push_token') + .where('did', 'in', dids) + .selectAll() + .execute() + return tokens.reduce((acc, token) => { + acc[token.did] ??= [] + acc[token.did].push(token) + return acc + }, {} as Record) + } + + async prepareNotifsToSend(notifications: InsertableNotif[]) { + const now = Date.now() + const notifsToSend: PushNotification[] = [] + const tokensByDid = await this.getTokensByDid( + unique(notifications.map((n) => n.did)), + ) + // views for all notifications that have tokens + const notificationViews = await this.getNotificationDisplayAttributes( + notifications.filter((n) => tokensByDid[n.did]), + ) + + for (const notifView of notificationViews) { + if (!isRecent(notifView.notif.sortAt, 10 * MINUTE)) { + continue // if the notif is from > 10 minutes ago, don't send push notif + } + const { did: userDid } = notifView.notif + const userTokens = tokensByDid[userDid] ?? [] + for (const t of userTokens) { + const { appId, platform, token } = t + if (notifView.rateLimit && !this.rateLimiter.check(token, now)) { + continue + } + if (platform === 'ios' || platform === 'android') { + notifsToSend.push({ + tokens: [token], + platform: platform === 'ios' ? 1 : 2, + title: notifView.title, + message: notifView.body, + topic: appId, + data: { + reason: notifView.notif.reason, + recordUri: notifView.notif.recordUri, + recordCid: notifView.notif.recordCid, + }, + collapse_id: notifView.key, + collapse_key: notifView.key, + }) + } else { + // @TODO: Handle web notifs + logger.warn({ did: userDid }, 'cannot send web notification to user') + } + } + } + + return notifsToSend + } + + /** + * The function `addNotificationsToQueue` adds push notifications to a queue, taking into account rate + * limiting and batching the notifications for efficient processing. + * @param {PushNotification[]} notifs - An array of PushNotification objects. Each PushNotification + * object has a "tokens" property which is an array of tokens. + * @returns void + */ + async processNotifications(notifs: PushNotification[]) { + for (const batch of chunkArray(notifs, 20)) { + try { + await this.sendPushNotifications(batch) + } catch (err) { + logger.error({ err, batch }, 'notification push batch failed') + } + } + } + + /** 1. Get the user's token (APNS or FCM for iOS and Android respectively) from the database + User token will be in the format: + did || token || platform (1 = iOS, 2 = Android, 3 = Web) + 2. Send notification to `gorush` server with token + Notification will be in the format: + "notifications": [ + { + "tokens": string[], + "platform": 1 | 2, + "message": string, + "title": string, + "priority": "normal" | "high", + "image": string, (Android only) + "expiration": number, (iOS only) + "badge": number, (iOS only) + } + ] + 3. `gorush` will send notification to APNS or FCM + 4. store response from `gorush` which contains the ID of the notification + 5. If notification needs to be updated or deleted, find the ID of the notification from the database and send a new notification to `gorush` with the ID (repeat step 2) + */ + private async sendPushNotifications(notifications: PushNotification[]) { + // if pushEndpoint is not defined, we are not running in the indexer service, so we can't send push notifications + if (!this.pushEndpoint) { + throw new Error('Push endpoint not defined') + } + // if no notifications, skip and return early + if (notifications.length === 0) { + return + } + const pushEndpoint = this.pushEndpoint + await retryHttp(() => + axios.post( + pushEndpoint, + { notifications }, + { + headers: { + 'Content-Type': 'application/json', + accept: 'application/json', + }, + }, + ), + ) + } + + async registerDeviceForPushNotifications( + did: string, + token: string, + platform: Platform, + appId: string, + ) { + // if token doesn't exist, insert it, on conflict do nothing + await this.db.db + .insertInto('notification_push_token') + .values({ did, token, platform, appId }) + .onConflict((oc) => oc.doNothing()) + .execute() + } + + async getNotificationDisplayAttributes( + notifs: InsertableNotif[], + ): Promise { + const { ref } = this.db.db.dynamic + const authorDids = notifs.map((n) => n.author) + const subjectUris = notifs.flatMap((n) => n.reasonSubject ?? []) + const recordUris = notifs.map((n) => n.recordUri) + const allUris = [...subjectUris, ...recordUris] + + // gather potential display data for notifications in batch + const [authors, posts, blocksAndMutes] = await Promise.all([ + this.db.db + .selectFrom('actor') + .leftJoin('profile', 'profile.creator', 'actor.did') + .leftJoin('record', 'record.uri', 'profile.uri') + .where(notSoftDeletedClause(ref('actor'))) + .where(notSoftDeletedClause(ref('record'))) + .where('profile.creator', 'in', authorDids.length ? authorDids : ['']) + .select(['actor.did as did', 'handle', 'displayName']) + .execute(), + this.db.db + .selectFrom('post') + .innerJoin('actor', 'actor.did', 'post.creator') + .innerJoin('record', 'record.uri', 'post.uri') + .where(notSoftDeletedClause(ref('actor'))) + .where(notSoftDeletedClause(ref('record'))) + .where('post.uri', 'in', allUris.length ? allUris : ['']) + .select(['post.uri as uri', 'text']) + .execute(), + this.findBlocksAndMutes(notifs), + ]) + + const authorsByDid = authors.reduce((acc, author) => { + acc[author.did] = author + return acc + }, {} as Record) + const postsByUri = posts.reduce((acc, post) => { + acc[post.uri] = post + return acc + }, {} as Record) + + const results: NotifDisplay[] = [] + + for (const notif of notifs) { + const { + author: authorDid, + reason, + reasonSubject: subjectUri, // if like/reply/quote/mention, the post which was liked/replied to/mention is in/or quoted. if custom feed liked, the feed which was liked + recordUri, + } = notif + + const author = + authorsByDid[authorDid]?.displayName || authorsByDid[authorDid]?.handle + const postRecord = postsByUri[recordUri] + const postSubject = subjectUri ? postsByUri[subjectUri] : null + + // if blocked or muted, don't send notification + const shouldFilter = blocksAndMutes.some( + (pair) => pair.author === notif.author && pair.receiver === notif.did, + ) + if (shouldFilter || !author) { + // if no display name, dont send notification + continue + } + // const author = displayName.displayName + + // 2. Get post data content + // if follow, get the URI of the author's profile + // if reply, or mention, get URI of the postRecord + // if like, or custom feed like, or repost get the URI of the reasonSubject + const key = reason + let title = '' + let body = '' + let rateLimit = true + + // check follow first and mention first because they don't have subjectUri and return + // reply has subjectUri but the recordUri is the replied post + if (reason === 'follow') { + title = 'New follower!' + body = `${author} has followed you` + results.push({ key, title, body, notif, rateLimit }) + continue + } else if (reason === 'mention' || reason === 'reply') { + // use recordUri for mention and reply + title = + reason === 'mention' + ? `${author} mentioned you` + : `${author} replied to your post` + body = postRecord?.text || '' + rateLimit = false // always deliver + results.push({ key, title, body, notif, rateLimit }) + continue + } + + // if no subjectUri, don't send notification + // at this point, subjectUri should exist for all the other reasons + if (!postSubject) { + continue + } + + if (reason === 'like') { + title = `${author} liked your post` + body = postSubject?.text || '' + // custom feed like + const uri = subjectUri ? new AtUri(subjectUri) : null + if (uri?.collection === ids.AppBskyFeedGenerator) { + title = `${author} liked your custom feed` + body = uri?.rkey ?? '' + } + } else if (reason === 'quote') { + title = `${author} quoted your post` + body = postSubject?.text || '' + rateLimit = true // always deliver + } else if (reason === 'repost') { + title = `${author} reposted your post` + body = postSubject?.text || '' + } + + if (title === '' && body === '') { + logger.warn( + { notif }, + 'No notification display attributes found for this notification. Either profile or post data for this notification is missing.', + ) + continue + } + + results.push({ key, title, body, notif, rateLimit }) + } + + return results + } + + async findBlocksAndMutes(notifs: InsertableNotif[]) { + const pairs = notifs.map((n) => ({ author: n.author, receiver: n.did })) + const { ref } = this.db.db.dynamic + const blockQb = this.db.db + .selectFrom('actor_block') + .where((outer) => + outer + .where((qb) => + qb + .whereRef('actor_block.creator', '=', ref('author')) + .whereRef('actor_block.subjectDid', '=', ref('receiver')), + ) + .orWhere((qb) => + qb + .whereRef('actor_block.creator', '=', ref('receiver')) + .whereRef('actor_block.subjectDid', '=', ref('author')), + ), + ) + .select(['creator', 'subjectDid']) + const muteQb = this.db.db + .selectFrom('mute') + .whereRef('mute.subjectDid', '=', ref('author')) + .whereRef('mute.mutedByDid', '=', ref('receiver')) + .selectAll() + const muteListQb = this.db.db + .selectFrom('list_item') + .innerJoin('list_mute', 'list_mute.listUri', 'list_item.listUri') + .whereRef('list_mute.mutedByDid', '=', ref('receiver')) + .whereRef('list_item.subjectDid', '=', ref('author')) + .select('list_item.subjectDid') + + const values = valuesList(pairs.map((p) => sql`${p.author}, ${p.receiver}`)) + const filterPairs = await this.db.db + .selectFrom(values.as(sql`pair (author, receiver)`)) + .whereExists(muteQb) + .orWhereExists(muteListQb) + .orWhereExists(blockQb) + .selectAll() + .execute() + return filterPairs as { author: string; receiver: string }[] + } +} + +const isRecent = (isoTime: string, timeDiff: number): boolean => { + const diff = Date.now() - new Date(isoTime).getTime() + return diff < timeDiff +} + +const unique = (items: string[]) => [...new Set(items)] + +class RateLimiter { + private rateLimitCache = new TTLCache({ + max: 50000, + ttl: this.windowMs, + noUpdateTTL: true, + }) + constructor(private limit: number, private windowMs: number) {} + check(token: string, now = Date.now()) { + const key = getRateLimitKey(token, now) + const last = this.rateLimitCache.get(key) ?? 0 + const current = last + 1 + this.rateLimitCache.set(key, current) + return current <= this.limit + } +} + +const getRateLimitKey = (token: string, now: number) => { + const iteration = Math.floor(now / (20 * MINUTE)) + return `${iteration}:${token}` +} diff --git a/packages/bsky/src/pipeline.ts b/packages/bsky/src/pipeline.ts new file mode 100644 index 00000000000..7798519bfa2 --- /dev/null +++ b/packages/bsky/src/pipeline.ts @@ -0,0 +1,22 @@ +export function createPipeline< + Params, + SkeletonState, + HydrationState extends SkeletonState, + View, + Context, +>( + skeleton: (params: Params, ctx: Context) => Promise, + hydration: (state: SkeletonState, ctx: Context) => Promise, + rules: (state: HydrationState, ctx: Context) => HydrationState, + presentation: (state: HydrationState, ctx: Context) => View, +) { + return async (params: Params, ctx: Context) => { + const skeletonState = await skeleton(params, ctx) + const hydrationState = await hydration(skeletonState, ctx) + return presentation(rules(hydrationState, ctx), ctx) + } +} + +export function noRules(state: T) { + return state +} diff --git a/packages/bsky/src/redis.ts b/packages/bsky/src/redis.ts new file mode 100644 index 00000000000..72d895be24c --- /dev/null +++ b/packages/bsky/src/redis.ts @@ -0,0 +1,165 @@ +import assert from 'assert' +import { Redis as RedisDriver } from 'ioredis' + +export class Redis { + driver: RedisDriver + namespace?: string + constructor(opts: RedisOptions) { + if ('sentinel' in opts) { + assert(opts.sentinel && Array.isArray(opts.hosts) && opts.hosts.length) + this.driver = new RedisDriver({ + name: opts.sentinel, + sentinels: opts.hosts.map((h) => addressParts(h, 26379)), + password: opts.password, + }) + } else if ('host' in opts) { + assert(opts.host) + this.driver = new RedisDriver({ + ...addressParts(opts.host), + password: opts.password, + }) + } else { + assert(opts.driver) + this.driver = opts.driver + } + this.namespace = opts.namespace + } + + async readStreams( + streams: StreamRef[], + opts: { count: number; blockMs?: number }, + ) { + const allRead = await this.driver.xreadBuffer( + 'COUNT', + opts.count, // events per stream + 'BLOCK', + opts.blockMs ?? 1000, // millis + 'STREAMS', + ...streams.map((s) => this.ns(s.key)), + ...streams.map((s) => s.cursor), + ) + const results: StreamOutput[] = [] + for (const [key, messages] of allRead ?? []) { + const result: StreamOutput = { + key: this.rmns(key.toString()), + messages: [], + } + results.push(result) + for (const [seqBuf, values] of messages) { + const message = { cursor: seqBuf.toString(), contents: {} } + result.messages.push(message) + for (let i = 0; i < values.length; ++i) { + if (i % 2 === 0) continue + const field = values[i - 1].toString() + message.contents[field] = values[i] + } + } + } + return results + } + + async addToStream( + key: string, + id: number | string, + fields: [key: string, value: string | Buffer][], + ) { + await this.driver.xadd(this.ns(key), id, ...fields.flat()) + } + + async addMultiToStream( + evts: { + key: string + id: number | string + fields: [key: string, value: string | Buffer][] + }[], + ) { + const pipeline = this.driver.pipeline() + for (const { key, id, fields } of evts) { + pipeline.xadd(this.ns(key), id, ...fields.flat()) + } + return (await pipeline.exec()) ?? [] + } + + async trimStream(key: string, cursor: number | string) { + await this.driver.xtrim(this.ns(key), 'MINID', cursor) + } + + async streamLengths(keys: string[]) { + const pipeline = this.driver.pipeline() + for (const key of keys) { + pipeline.xlen(this.ns(key)) + } + const results = await pipeline.exec() + return (results ?? []).map(([, len = 0]) => Number(len)) + } + + async get(key: string) { + return await this.driver.get(this.ns(key)) + } + + async set(key: string, val: string | number) { + await this.driver.set(this.ns(key), val) + } + + async del(key: string) { + return await this.driver.del(this.ns(key)) + } + + async expire(key: string, seconds: number) { + return await this.driver.expire(this.ns(key), seconds) + } + + async zremrangebyscore(key: string, min: number, max: number) { + return await this.driver.zremrangebyscore(this.ns(key), min, max) + } + + async zcount(key: string, min: number, max: number) { + return await this.driver.zcount(this.ns(key), min, max) + } + + async zadd(key: string, score: number, member: number | string) { + return await this.driver.zadd(this.ns(key), score, member) + } + + async destroy() { + await this.driver.quit() + } + + // namespace redis keys + ns(key: string) { + return this.namespace ? `${this.namespace}:${key}` : key + } + + // remove namespace from redis key + rmns(key: string) { + return this.namespace && key.startsWith(`${this.namespace}:`) + ? key.replace(`${this.namespace}:`, '') + : key + } +} + +type StreamRef = { key: string; cursor: string | number } + +type StreamOutput = { + key: string + messages: { cursor: string; contents: Record }[] +} + +export type RedisOptions = ( + | { driver: RedisDriver } + | { host: string } + | { sentinel: string; hosts: string[] } +) & { + password?: string + namespace?: string +} + +function addressParts( + addr: string, + defaultPort = 6379, +): { host: string; port: number } { + const [host, portStr, ...others] = addr.split(':') + const port = portStr ? parseInt(portStr, 10) : defaultPort + assert(host && !isNaN(port) && !others.length, `invalid address: ${addr}`) + return { host, port } +} diff --git a/packages/bsky/src/services/actor/index.ts b/packages/bsky/src/services/actor/index.ts index be3fbf669e5..54bfb714146 100644 --- a/packages/bsky/src/services/actor/index.ts +++ b/packages/bsky/src/services/actor/index.ts @@ -1,19 +1,27 @@ import { sql } from 'kysely' -import Database from '../../db' -import { DbRef, notSoftDeletedClause } from '../../db/util' +import { Database } from '../../db' +import { notSoftDeletedClause } from '../../db/util' import { ActorViews } from './views' import { ImageUriBuilder } from '../../image/uri' import { Actor } from '../../db/tables/actor' -import { TimeCidKeyset } from '../../db/pagination' +import { LabelCache } from '../../label-cache' +import { TimeCidKeyset, paginate } from '../../db/pagination' +import { SearchKeyset, getUserSearchQuery } from '../util/search' + +export * from './types' export class ActorService { - constructor(public db: Database, public imgUriBuilder: ImageUriBuilder) {} + constructor( + public db: Database, + public imgUriBuilder: ImageUriBuilder, + public labelCache: LabelCache, + ) {} - static creator(imgUriBuilder: ImageUriBuilder) { - return (db: Database) => new ActorService(db, imgUriBuilder) + static creator(imgUriBuilder: ImageUriBuilder, labelCache: LabelCache) { + return (db: Database) => new ActorService(db, imgUriBuilder, labelCache) } - views = new ActorViews(this.db, this.imgUriBuilder) + views = new ActorViews(this.db, this.imgUriBuilder, this.labelCache) async getActorDid(handleOrDid: string): Promise { if (handleOrDid.startsWith('did:')) { @@ -66,53 +74,78 @@ export class ActorService { .execute() return results.sort((a, b) => { - const orderA = order[a.did] ?? order[a.handle.toLowerCase()] - const orderB = order[b.did] ?? order[b.handle.toLowerCase()] + const orderA = order[a.did] ?? order[a.handle?.toLowerCase() ?? ''] + const orderB = order[b.did] ?? order[b.handle?.toLowerCase() ?? ''] return orderA - orderB }) } - searchQb(searchField: 'did' | 'handle' = 'handle', term?: string) { + async getSearchResults({ + cursor, + limit = 25, + term = '', + includeSoftDeleted, + }: { + cursor?: string + limit?: number + term?: string + includeSoftDeleted?: boolean + }) { + const searchField = term.startsWith('did:') ? 'did' : 'handle' + let paginatedBuilder const { ref } = this.db.db.dynamic - let builder = this.db.db.selectFrom('actor') - - // When searchField === 'did', the term will always be a valid string because - // searchField is set to 'did' after checking that the term is a valid did - if (searchField === 'did' && term) { - return builder.where('actor.did', '=', term) + const paginationOptions = { + limit, + cursor, + direction: 'asc' as const, } + let keyset - if (term) { - builder = builder.where((qb) => { - // Performing matching by word using "strict word similarity" operator. - // The more characters the user gives us, the more we can ratchet down - // the distance threshold for matching. - const threshold = term.length < 3 ? 0.9 : 0.8 - return qb - .where(distance(term, ref('handle')), '<', threshold) - .orWhereExists((q) => - q - .selectFrom('profile') - .whereRef('profile.creator', '=', 'actor.did') - .where(distance(term, ref('displayName')), '<', threshold), - ) + if (term && searchField === 'handle') { + keyset = new SearchKeyset(sql``, sql``) + paginatedBuilder = getUserSearchQuery(this.db, { + term, + includeSoftDeleted, + ...paginationOptions, + }).select('distance') + } else { + paginatedBuilder = this.db.db + .selectFrom('actor') + .select([sql`0`.as('distance')]) + keyset = new ListKeyset(ref('indexedAt'), ref('did')) + + // When searchField === 'did', the term will always be a valid string because + // searchField is set to 'did' after checking that the term is a valid did + if (term && searchField === 'did') { + paginatedBuilder = paginatedBuilder.where('actor.did', '=', term) + } + paginatedBuilder = paginate(paginatedBuilder, { + keyset, + ...paginationOptions, }) } - return builder + + const results: Actor[] = await paginatedBuilder.selectAll('actor').execute() + return { results, cursor: keyset.packFromResult(results) } + } + + async getRepoRev(did: string | null): Promise { + if (did === null) return null + const res = await this.db.db + .selectFrom('actor_sync') + .select('repoRev') + .where('did', '=', did) + .executeTakeFirst() + return res?.repoRev ?? null } } type ActorResult = Actor - -// Uses pg_trgm strict word similarity to check similarity between a search term and a stored value -const distance = (term: string, ref: DbRef) => - sql`(${term} <<<-> ${ref})` - export class ListKeyset extends TimeCidKeyset<{ indexedAt: string - handle: string // handles are treated identically to cids in TimeCidKeyset + did: string // handles are treated identically to cids in TimeCidKeyset }> { - labelResult(result: { indexedAt: string; handle: string }) { - return { primary: result.indexedAt, secondary: result.handle } + labelResult(result: { indexedAt: string; did: string }) { + return { primary: result.indexedAt, secondary: result.did } } } diff --git a/packages/bsky/src/services/actor/types.ts b/packages/bsky/src/services/actor/types.ts new file mode 100644 index 00000000000..e853406e22e --- /dev/null +++ b/packages/bsky/src/services/actor/types.ts @@ -0,0 +1,74 @@ +import { ListViewBasic } from '../../lexicon/types/app/bsky/graph/defs' +import { Label } from '../../lexicon/types/com/atproto/label/defs' +import { BlockAndMuteState } from '../graph' +import { ListInfoMap } from '../graph/types' +import { Labels } from '../label' + +export type ActorInfo = { + did: string + handle: string + displayName?: string + description?: string // omitted from basic profile view + avatar?: string + indexedAt?: string // omitted from basic profile view + viewer?: { + muted?: boolean + mutedByList?: ListViewBasic + blockedBy?: boolean + blocking?: string + following?: string + followedBy?: string + } + labels?: Label[] +} +export type ActorInfoMap = { [did: string]: ActorInfo } + +export type ProfileViewMap = ActorInfoMap + +export type ProfileInfo = { + did: string + handle: string | null + profileUri: string | null + profileCid: string | null + displayName: string | null + description: string | null + avatarCid: string | null + indexedAt: string | null + profileJson: string | null + viewerFollowing: string | null + viewerFollowedBy: string | null +} + +export type ProfileInfoMap = { [did: string]: ProfileInfo } + +export type ProfileHydrationState = { + profiles: ProfileInfoMap + labels: Labels + lists: ListInfoMap + bam: BlockAndMuteState +} + +export type ProfileDetailInfo = ProfileInfo & { + bannerCid: string | null + followsCount: number | null + followersCount: number | null + postsCount: number | null +} + +export type ProfileDetailInfoMap = { [did: string]: ProfileDetailInfo } + +export type ProfileDetailHydrationState = { + profilesDetailed: ProfileDetailInfoMap + labels: Labels + lists: ListInfoMap + bam: BlockAndMuteState +} + +export const toMapByDid = ( + items: T[], +): Record => { + return items.reduce((cur, item) => { + cur[item.did] = item + return cur + }, {} as Record) +} diff --git a/packages/bsky/src/services/actor/views.ts b/packages/bsky/src/services/actor/views.ts index 346406c74c5..80652599f80 100644 --- a/packages/bsky/src/services/actor/views.ts +++ b/packages/bsky/src/services/actor/views.ts @@ -1,49 +1,109 @@ -import { ArrayEl } from '@atproto/common' +import { mapDefined } from '@atproto/common' +import { INVALID_HANDLE } from '@atproto/syntax' +import { jsonStringToLex } from '@atproto/lexicon' import { ProfileViewDetailed, ProfileView, - ProfileViewBasic, } from '../../lexicon/types/app/bsky/actor/defs' -import Database from '../../db' -import { noMatch } from '../../db/util' +import { Database } from '../../db' +import { noMatch, notSoftDeletedClause } from '../../db/util' import { Actor } from '../../db/tables/actor' import { ImageUriBuilder } from '../../image/uri' -import { LabelService } from '../label' -import { ListViewBasic } from '../../lexicon/types/app/bsky/graph/defs' +import { LabelService, Labels, getSelfLabels } from '../label' +import { BlockAndMuteState, GraphService } from '../graph' +import { LabelCache } from '../../label-cache' +import { + ActorInfoMap, + ProfileDetailHydrationState, + ProfileHydrationState, + ProfileInfoMap, + ProfileViewMap, + toMapByDid, +} from './types' +import { ListInfoMap } from '../graph/types' export class ActorViews { - constructor(private db: Database, private imgUriBuilder: ImageUriBuilder) {} + constructor( + private db: Database, + private imgUriBuilder: ImageUriBuilder, + private labelCache: LabelCache, + ) {} services = { - label: LabelService.creator(), + label: LabelService.creator(this.labelCache)(this.db), + graph: GraphService.creator(this.imgUriBuilder)(this.db), } - profileDetailed( - result: ActorResult, + async profiles( + results: (ActorResult | string)[], // @TODO simplify down to just string[] viewer: string | null, - ): Promise - profileDetailed( - result: ActorResult[], + opts?: { includeSoftDeleted?: boolean }, + ): Promise { + if (results.length === 0) return {} + const dids = results.map((res) => (typeof res === 'string' ? res : res.did)) + const hydrated = await this.profileHydration(dids, { + viewer, + ...opts, + }) + return this.profilePresentation(dids, hydrated, { + viewer, + ...opts, + }) + } + + async profilesBasic( + results: (ActorResult | string)[], viewer: string | null, - ): Promise - async profileDetailed( - result: ActorResult | ActorResult[], + opts?: { omitLabels?: boolean; includeSoftDeleted?: boolean }, + ): Promise { + if (results.length === 0) return {} + const dids = results.map((res) => (typeof res === 'string' ? res : res.did)) + const hydrated = await this.profileHydration(dids, { + viewer, + includeSoftDeleted: opts?.includeSoftDeleted, + }) + return this.profileBasicPresentation(dids, hydrated, { + viewer, + omitLabels: opts?.omitLabels, + }) + } + + async profilesList( + results: ActorResult[], viewer: string | null, - ): Promise { - const results = Array.isArray(result) ? result : [result] - if (results.length === 0) return [] + opts?: { includeSoftDeleted?: boolean }, + ): Promise { + const profiles = await this.profiles(results, viewer, opts) + return mapDefined(results, (result) => profiles[result.did]) + } + async profileDetailHydration( + dids: string[], + opts: { + viewer?: string | null + includeSoftDeleted?: boolean + }, + state?: { + bam: BlockAndMuteState + labels: Labels + }, + ): Promise { + const { viewer = null, includeSoftDeleted } = opts const { ref } = this.db.db.dynamic - const dids = results.map((r) => r.did) - const profileInfosQb = this.db.db .selectFrom('actor') - .where('actor.did', 'in', dids) + .where('actor.did', 'in', dids.length ? dids : ['']) .leftJoin('profile', 'profile.creator', 'actor.did') .leftJoin('profile_agg', 'profile_agg.did', 'actor.did') + .leftJoin('record', 'record.uri', 'profile.uri') + .if(!includeSoftDeleted, (qb) => + qb.where(notSoftDeletedClause(ref('actor'))), + ) .select([ 'actor.did as did', + 'actor.handle as handle', 'profile.uri as profileUri', + 'profile.cid as profileCid', 'profile.displayName as displayName', 'profile.description as description', 'profile.avatarCid as avatarCid', @@ -52,266 +112,239 @@ export class ActorViews { 'profile_agg.followsCount as followsCount', 'profile_agg.followersCount as followersCount', 'profile_agg.postsCount as postsCount', + 'record.json as profileJson', this.db.db .selectFrom('follow') .if(!viewer, (q) => q.where(noMatch)) .where('creator', '=', viewer ?? '') .whereRef('subjectDid', '=', ref('actor.did')) .select('uri') - .as('requesterFollowing'), + .as('viewerFollowing'), this.db.db .selectFrom('follow') .if(!viewer, (q) => q.where(noMatch)) .whereRef('creator', '=', ref('actor.did')) .where('subjectDid', '=', viewer ?? '') .select('uri') - .as('requesterFollowedBy'), - this.db.db - .selectFrom('actor_block') - .if(!viewer, (q) => q.where(noMatch)) - .where('creator', '=', viewer ?? '') - .whereRef('subjectDid', '=', ref('actor.did')) - .select('uri') - .as('requesterBlocking'), - this.db.db - .selectFrom('actor_block') - .if(!viewer, (q) => q.where(noMatch)) - .whereRef('creator', '=', ref('actor.did')) - .where('subjectDid', '=', viewer ?? '') - .select('uri') - .as('requesterBlockedBy'), - this.db.db - .selectFrom('mute') - .if(!viewer, (q) => q.where(noMatch)) - .whereRef('subjectDid', '=', ref('actor.did')) - .where('mutedByDid', '=', viewer ?? '') - .select('subjectDid') - .as('requesterMuted'), + .as('viewerFollowedBy'), ]) - - const [profileInfos, labels, listMutes] = await Promise.all([ + const [profiles, labels, bam] = await Promise.all([ profileInfosQb.execute(), - this.services.label(this.db).getLabelsForSubjects(dids), - this.getListMutes(dids, viewer), + this.services.label.getLabelsForSubjects(dids, state?.labels), + this.services.graph.getBlockAndMuteState( + viewer ? dids.map((did) => [viewer, did]) : [], + state?.bam, + ), ]) + const listUris = mapDefined(profiles, ({ did }) => { + const list = viewer && bam.muteList([viewer, did]) + if (!list) return + return list + }) + const lists = await this.services.graph.getListViews(listUris, viewer) + return { profilesDetailed: toMapByDid(profiles), labels, bam, lists } + } - const profileInfoByDid = profileInfos.reduce((acc, info) => { - return Object.assign(acc, { [info.did]: info }) - }, {} as Record>) - - const views = results.map((result) => { - const profileInfo = profileInfoByDid[result.did] - const avatar = profileInfo?.avatarCid - ? this.imgUriBuilder.getCommonSignedUri( - 'avatar', - profileInfo.did, - profileInfo.avatarCid, - ) + profileDetailPresentation( + dids: string[], + state: ProfileDetailHydrationState, + opts: { + viewer?: string | null + }, + ): Record { + const { viewer } = opts + const { profilesDetailed, lists, labels, bam } = state + return dids.reduce((acc, did) => { + const prof = profilesDetailed[did] + if (!prof) return acc + const avatar = prof?.avatarCid + ? this.imgUriBuilder.getPresetUri('avatar', prof.did, prof.avatarCid) : undefined - const banner = profileInfo?.bannerCid - ? this.imgUriBuilder.getCommonSignedUri( - 'banner', - profileInfo.did, - profileInfo.bannerCid, - ) + const banner = prof?.bannerCid + ? this.imgUriBuilder.getPresetUri('banner', prof.did, prof.bannerCid) : undefined - return { - did: result.did, - handle: result.handle, - displayName: profileInfo?.displayName || undefined, - description: profileInfo?.description || undefined, + const mutedByListUri = viewer && bam.muteList([viewer, did]) + const mutedByList = + mutedByListUri && lists[mutedByListUri] + ? this.services.graph.formatListViewBasic(lists[mutedByListUri]) + : undefined + const actorLabels = labels[did] ?? [] + const selfLabels = getSelfLabels({ + uri: prof.profileUri, + cid: prof.profileCid, + record: + prof.profileJson !== null + ? (jsonStringToLex(prof.profileJson) as Record) + : null, + }) + acc[did] = { + did: prof.did, + handle: prof.handle ?? INVALID_HANDLE, + displayName: prof?.displayName || undefined, + description: prof?.description || undefined, avatar, banner, - followsCount: profileInfo?.followsCount ?? 0, - followersCount: profileInfo?.followersCount ?? 0, - postsCount: profileInfo?.postsCount ?? 0, - indexedAt: profileInfo?.indexedAt || undefined, + followsCount: prof?.followsCount ?? 0, + followersCount: prof?.followersCount ?? 0, + postsCount: prof?.postsCount ?? 0, + indexedAt: prof?.indexedAt || undefined, viewer: viewer ? { - following: profileInfo?.requesterFollowing || undefined, - followedBy: profileInfo?.requesterFollowedBy || undefined, - muted: !!profileInfo?.requesterMuted || !!listMutes[result.did], - mutedByList: listMutes[result.did], - blockedBy: !!profileInfo.requesterBlockedBy, - blocking: profileInfo.requesterBlocking || undefined, + muted: bam.mute([viewer, did]), + mutedByList, + blockedBy: !!bam.blockedBy([viewer, did]), + blocking: bam.blocking([viewer, did]) ?? undefined, + following: prof?.viewerFollowing || undefined, + followedBy: prof?.viewerFollowedBy || undefined, } : undefined, - labels: labels[result.did] ?? [], + labels: [...actorLabels, ...selfLabels], } - }) - - return Array.isArray(result) ? views : views[0] + return acc + }, {} as Record) } - profile(result: ActorResult, viewer: string | null): Promise - profile(result: ActorResult[], viewer: string | null): Promise - async profile( - result: ActorResult | ActorResult[], - viewer: string | null, - ): Promise { - const results = Array.isArray(result) ? result : [result] - if (results.length === 0) return [] - + async profileHydration( + dids: string[], + opts: { + viewer?: string | null + includeSoftDeleted?: boolean + }, + state?: { + bam: BlockAndMuteState + labels: Labels + }, + ): Promise { + const { viewer = null, includeSoftDeleted } = opts const { ref } = this.db.db.dynamic - const dids = results.map((r) => r.did) - const profileInfosQb = this.db.db .selectFrom('actor') - .where('actor.did', 'in', dids) + .where('actor.did', 'in', dids.length ? dids : ['']) .leftJoin('profile', 'profile.creator', 'actor.did') + .leftJoin('record', 'record.uri', 'profile.uri') + .if(!includeSoftDeleted, (qb) => + qb.where(notSoftDeletedClause(ref('actor'))), + ) .select([ 'actor.did as did', + 'actor.handle as handle', 'profile.uri as profileUri', + 'profile.cid as profileCid', 'profile.displayName as displayName', 'profile.description as description', 'profile.avatarCid as avatarCid', 'profile.indexedAt as indexedAt', + 'record.json as profileJson', this.db.db .selectFrom('follow') .if(!viewer, (q) => q.where(noMatch)) .where('creator', '=', viewer ?? '') .whereRef('subjectDid', '=', ref('actor.did')) .select('uri') - .as('requesterFollowing'), + .as('viewerFollowing'), this.db.db .selectFrom('follow') .if(!viewer, (q) => q.where(noMatch)) .whereRef('creator', '=', ref('actor.did')) .where('subjectDid', '=', viewer ?? '') .select('uri') - .as('requesterFollowedBy'), - this.db.db - .selectFrom('actor_block') - .if(!viewer, (q) => q.where(noMatch)) - .where('creator', '=', viewer ?? '') - .whereRef('subjectDid', '=', ref('actor.did')) - .select('uri') - .as('requesterBlocking'), - this.db.db - .selectFrom('actor_block') - .if(!viewer, (q) => q.where(noMatch)) - .whereRef('creator', '=', ref('actor.did')) - .where('subjectDid', '=', viewer ?? '') - .select('uri') - .as('requesterBlockedBy'), - this.db.db - .selectFrom('mute') - .if(!viewer, (q) => q.where(noMatch)) - .whereRef('subjectDid', '=', ref('actor.did')) - .where('mutedByDid', '=', viewer ?? '') - .select('subjectDid') - .as('requesterMuted'), + .as('viewerFollowedBy'), ]) - - const [profileInfos, labels, listMutes] = await Promise.all([ + const [profiles, labels, bam] = await Promise.all([ profileInfosQb.execute(), - this.services.label(this.db).getLabelsForSubjects(dids), - this.getListMutes(dids, viewer), + this.services.label.getLabelsForSubjects(dids, state?.labels), + this.services.graph.getBlockAndMuteState( + viewer ? dids.map((did) => [viewer, did]) : [], + state?.bam, + ), ]) + const listUris = mapDefined(profiles, ({ did }) => { + const list = viewer && bam.muteList([viewer, did]) + if (!list) return + return list + }) + const lists = await this.services.graph.getListViews(listUris, viewer) + return { profiles: toMapByDid(profiles), labels, bam, lists } + } - const profileInfoByDid = profileInfos.reduce((acc, info) => { - return Object.assign(acc, { [info.did]: info }) - }, {} as Record>) - - const views = results.map((result) => { - const profileInfo = profileInfoByDid[result.did] - const avatar = profileInfo?.avatarCid - ? this.imgUriBuilder.getCommonSignedUri( - 'avatar', - profileInfo.did, - profileInfo.avatarCid, - ) + profilePresentation( + dids: string[], + state: { + profiles: ProfileInfoMap + lists: ListInfoMap + labels: Labels + bam: BlockAndMuteState + }, + opts?: { + viewer?: string | null + }, + ): ProfileViewMap { + const { viewer } = opts ?? {} + const { profiles, lists, labels, bam } = state + return dids.reduce((acc, did) => { + const prof = profiles[did] + if (!prof) return acc + const avatar = prof?.avatarCid + ? this.imgUriBuilder.getPresetUri('avatar', prof.did, prof.avatarCid) : undefined - return { - did: result.did, - handle: result.handle, - displayName: profileInfo?.displayName || undefined, - description: profileInfo?.description || undefined, + const mutedByListUri = viewer && bam.muteList([viewer, did]) + const mutedByList = + mutedByListUri && lists[mutedByListUri] + ? this.services.graph.formatListViewBasic(lists[mutedByListUri]) + : undefined + const actorLabels = labels[did] ?? [] + const selfLabels = getSelfLabels({ + uri: prof.profileUri, + cid: prof.profileCid, + record: + prof.profileJson !== null + ? (jsonStringToLex(prof.profileJson) as Record) + : null, + }) + acc[did] = { + did: prof.did, + handle: prof.handle ?? INVALID_HANDLE, + displayName: prof?.displayName || undefined, + description: prof?.description || undefined, avatar, - indexedAt: profileInfo?.indexedAt || undefined, + indexedAt: prof?.indexedAt || undefined, viewer: viewer ? { - muted: !!profileInfo?.requesterMuted || !!listMutes[result.did], - mutedByList: listMutes[result.did], - blockedBy: !!profileInfo.requesterBlockedBy, - blocking: profileInfo.requesterBlocking || undefined, - following: profileInfo?.requesterFollowing || undefined, - followedBy: profileInfo?.requesterFollowedBy || undefined, + muted: bam.mute([viewer, did]), + mutedByList, + blockedBy: !!bam.blockedBy([viewer, did]), + blocking: bam.blocking([viewer, did]) ?? undefined, + following: prof?.viewerFollowing || undefined, + followedBy: prof?.viewerFollowedBy || undefined, } : undefined, - labels: labels[result.did] ?? [], + labels: [...actorLabels, ...selfLabels], } - }) - - return Array.isArray(result) ? views : views[0] + return acc + }, {} as ProfileViewMap) } - // @NOTE keep in sync with feedService.getActorViews() - profileBasic( - result: ActorResult, - viewer: string | null, - ): Promise - profileBasic( - result: ActorResult[], - viewer: string | null, - ): Promise - async profileBasic( - result: ActorResult | ActorResult[], - viewer: string | null, - ): Promise { - const results = Array.isArray(result) ? result : [result] - if (results.length === 0) return [] - - const profiles = await this.profile(results, viewer) - const views = profiles.map((view) => ({ - did: view.did, - handle: view.handle, - displayName: view.displayName, - avatar: view.avatar, - viewer: view.viewer, - })) - - return Array.isArray(result) ? views : views[0] - } - - async getListMutes( - subjects: string[], - mutedBy: string | null, - ): Promise> { - if (mutedBy === null) return {} - if (subjects.length < 1) return {} - const res = await this.db.db - .selectFrom('list_item') - .innerJoin('list_mute', 'list_mute.listUri', 'list_item.listUri') - .innerJoin('list', 'list.uri', 'list_item.listUri') - .where('list_mute.mutedByDid', '=', mutedBy) - .where('list_item.subjectDid', 'in', subjects) - .selectAll('list') - .select('list_item.subjectDid as subjectDid') - .execute() - return res.reduce( - (acc, cur) => ({ - ...acc, - [cur.subjectDid]: { - uri: cur.uri, - cid: cur.cid, - name: cur.name, - purpose: cur.purpose, - avatar: cur.avatarCid - ? this.imgUriBuilder.getCommonSignedUri( - 'avatar', - cur.creator, - cur.avatarCid, - ) - : undefined, - viewer: { - muted: true, - }, - indexedAt: cur.indexedAt, - }, - }), - {} as Record, - ) + profileBasicPresentation( + dids: string[], + state: ProfileHydrationState, + opts?: { + viewer?: string | null + omitLabels?: boolean + }, + ): ProfileViewMap { + const result = this.profilePresentation(dids, state, opts) + return Object.values(result).reduce((acc, prof) => { + const profileBasic = { + did: prof.did, + handle: prof.handle, + displayName: prof.displayName, + avatar: prof.avatar, + viewer: prof.viewer, + labels: opts?.omitLabels ? undefined : prof.labels, + } + acc[prof.did] = profileBasic + return acc + }, {} as ProfileViewMap) } } diff --git a/packages/bsky/src/services/feed/index.ts b/packages/bsky/src/services/feed/index.ts index 43e747c903b..db32f1971bc 100644 --- a/packages/bsky/src/services/feed/index.ts +++ b/packages/bsky/src/services/feed/index.ts @@ -1,45 +1,58 @@ import { sql } from 'kysely' -import { AtUri } from '@atproto/uri' -import { dedupeStrs } from '@atproto/common' -import Database from '../../db' +import { AtUri } from '@atproto/syntax' +import { jsonStringToLex } from '@atproto/lexicon' +import { Database } from '../../db' import { countAll, noMatch, notSoftDeletedClause } from '../../db/util' import { ImageUriBuilder } from '../../image/uri' import { ids } from '../../lexicon/lexicons' -import { isView as isViewImages } from '../../lexicon/types/app/bsky/embed/images' -import { isView as isViewExternal } from '../../lexicon/types/app/bsky/embed/external' import { - ViewRecord, - View as RecordEmbedView, - ViewNotFound, - ViewBlocked, + Record as PostRecord, + isRecord as isPostRecord, +} from '../../lexicon/types/app/bsky/feed/post' +import { isMain as isEmbedImages } from '../../lexicon/types/app/bsky/embed/images' +import { isMain as isEmbedExternal } from '../../lexicon/types/app/bsky/embed/external' +import { + isMain as isEmbedRecord, + isViewRecord, } from '../../lexicon/types/app/bsky/embed/record' -import { FeedViewPost, PostView } from '../../lexicon/types/app/bsky/feed/defs' +import { isMain as isEmbedRecordWithMedia } from '../../lexicon/types/app/bsky/embed/recordWithMedia' import { - ActorViewMap, - FeedEmbeds, PostInfoMap, FeedItemType, FeedRow, -} from '../types' + FeedGenInfoMap, + PostEmbedViews, + RecordEmbedViewRecordMap, + PostInfo, + RecordEmbedViewRecord, + PostBlocksMap, + FeedHydrationState, +} from './types' import { LabelService } from '../label' import { ActorService } from '../actor' -import { GraphService } from '../graph' +import { BlockAndMuteState, GraphService, RelationshipPair } from '../graph' import { FeedViews } from './views' -import { FeedGenInfoMap } from './types' +import { LabelCache } from '../../label-cache' + +export * from './types' export class FeedService { - constructor(public db: Database, public imgUriBuilder: ImageUriBuilder) {} + constructor( + public db: Database, + public imgUriBuilder: ImageUriBuilder, + public labelCache: LabelCache, + ) {} - views = new FeedViews(this.db, this.imgUriBuilder) + views = new FeedViews(this.db, this.imgUriBuilder, this.labelCache) services = { - label: LabelService.creator()(this.db), - actor: ActorService.creator(this.imgUriBuilder)(this.db), + label: LabelService.creator(this.labelCache)(this.db), + actor: ActorService.creator(this.imgUriBuilder, this.labelCache)(this.db), graph: GraphService.creator(this.imgUriBuilder)(this.db), } - static creator(imgUriBuilder: ImageUriBuilder) { - return (db: Database) => new FeedService(db, imgUriBuilder) + static creator(imgUriBuilder: ImageUriBuilder, labelCache: LabelCache) { + return (db: Database) => new FeedService(db, imgUriBuilder, labelCache) } selectPostQb() { @@ -97,101 +110,7 @@ export class FeedService { ) } - // @TODO just use actor service?? - // @NOTE keep in sync with actorService.views.profile() - async getActorViews( - dids: string[], - viewer: string | null, - opts?: { skipLabels?: boolean }, // @NOTE used by hydrateFeed() to batch label hydration - ): Promise { - if (dids.length < 1) return {} - const { ref } = this.db.db.dynamic - const { skipLabels } = opts ?? {} - const [actors, labels, listMutes] = await Promise.all([ - this.db.db - .selectFrom('actor') - .leftJoin('profile', 'profile.creator', 'actor.did') - .where('actor.did', 'in', dids) - .where(notSoftDeletedClause(ref('actor'))) - .selectAll('actor') - .select([ - 'profile.uri as profileUri', - 'profile.displayName as displayName', - 'profile.description as description', - 'profile.avatarCid as avatarCid', - 'profile.indexedAt as indexedAt', - this.db.db - .selectFrom('follow') - .if(!viewer, (q) => q.where(noMatch)) - .where('creator', '=', viewer ?? '') - .whereRef('subjectDid', '=', ref('actor.did')) - .select('uri') - .as('requesterFollowing'), - this.db.db - .selectFrom('follow') - .if(!viewer, (q) => q.where(noMatch)) - .whereRef('creator', '=', ref('actor.did')) - .where('subjectDid', '=', viewer ?? '') - .select('uri') - .as('requesterFollowedBy'), - this.db.db - .selectFrom('actor_block') - .if(!viewer, (q) => q.where(noMatch)) - .where('creator', '=', viewer ?? '') - .whereRef('subjectDid', '=', ref('actor.did')) - .select('uri') - .as('requesterBlocking'), - this.db.db - .selectFrom('actor_block') - .if(!viewer, (q) => q.where(noMatch)) - .whereRef('creator', '=', ref('actor.did')) - .where('subjectDid', '=', viewer ?? '') - .select('uri') - .as('requesterBlockedBy'), - this.db.db - .selectFrom('mute') - .if(!viewer, (q) => q.where(noMatch)) - .whereRef('subjectDid', '=', ref('actor.did')) - .where('mutedByDid', '=', viewer ?? '') - .select('subjectDid') - .as('requesterMuted'), - ]) - .execute(), - this.services.label.getLabelsForSubjects(skipLabels ? [] : dids), - this.services.actor.views.getListMutes(dids, viewer), - ]) - return actors.reduce((acc, cur) => { - const actorLabels = labels[cur.did] ?? [] - return { - ...acc, - [cur.did]: { - did: cur.did, - handle: cur.handle, - displayName: cur.displayName ?? undefined, - avatar: cur.avatarCid - ? this.imgUriBuilder.getCommonSignedUri( - 'avatar', - cur.did, - cur.avatarCid, - ) - : undefined, - viewer: viewer - ? { - muted: !!cur?.requesterMuted || !!listMutes[cur.did], - mutedByList: listMutes[cur.did], - blockedBy: !!cur?.requesterBlockedBy, - blocking: cur?.requesterBlocking || undefined, - following: cur?.requesterFollowing || undefined, - followedBy: cur?.requesterFollowedBy || undefined, - } - : undefined, - labels: skipLabels ? undefined : actorLabels, - }, - } - }, {} as ActorViewMap) - } - - async getPostViews( + async getPostInfos( postUris: string[], viewer: string | null, ): Promise { @@ -231,16 +150,18 @@ export class FeedService { .as('requesterLike'), ]) .execute() - return posts.reduce( - (acc, cur) => ({ - ...acc, - [cur.uri]: Object.assign(cur, { viewer }), - }), - {} as PostInfoMap, - ) + return posts.reduce((acc, cur) => { + const { recordJson, ...post } = cur + const info: PostInfo = { + ...post, + record: jsonStringToLex(recordJson) as Record, + viewer, + } + return Object.assign(acc, { [post.uri]: info }) + }, {} as PostInfoMap) } - async getFeedGeneratorViews(generatorUris: string[], viewer: string | null) { + async getFeedGeneratorInfos(generatorUris: string[], viewer: string | null) { if (generatorUris.length < 1) return {} const feedGens = await this.selectFeedGeneratorQb(viewer) .where('feed_generator.uri', 'in', generatorUris) @@ -248,200 +169,32 @@ export class FeedService { return feedGens.reduce( (acc, cur) => ({ ...acc, - [cur.uri]: cur, + [cur.uri]: { + ...cur, + viewer: viewer ? { like: cur.viewerLike } : undefined, + }, }), {} as FeedGenInfoMap, ) } - async embedsForPosts( - uris: string[], - viewer: string | null, - _depth = 0, - ): Promise { - if (uris.length < 1 || _depth > 1) { - // If a post has a record embed which contains additional embeds, the depth check - // above ensures that we don't recurse indefinitely into those additional embeds. - // In short, you receive up to two layers of embeds for the post: this allows us to - // handle the case that a post has a record embed, which in turn has images embedded in it. - return {} - } - const imgPromise = this.db.db - .selectFrom('post_embed_image') - .selectAll() - .where('postUri', 'in', uris) - .orderBy('postUri') - .orderBy('position') + async getFeedItems(uris: string[]): Promise> { + if (uris.length < 1) return {} + const feedItems = await this.selectFeedItemQb() + .where('feed_item.uri', 'in', uris) .execute() - const extPromise = this.db.db - .selectFrom('post_embed_external') - .selectAll() - .where('postUri', 'in', uris) - .execute() - const recordPromise = this.db.db - .selectFrom('post_embed_record') - .innerJoin('record as embed', 'embed.uri', 'embedUri') - .where('postUri', 'in', uris) - .select(['postUri', 'embed.uri as uri', 'embed.did as did']) - .execute() - const [images, externals, records] = await Promise.all([ - imgPromise, - extPromise, - recordPromise, - ]) - const nestedUris = dedupeStrs(records.map((p) => p.uri)) - const nestedDids = dedupeStrs(records.map((p) => p.did)) - const nestedPostUris = nestedUris.filter( - (uri) => new AtUri(uri).collection === ids.AppBskyFeedPost, - ) - const nestedFeedGenUris = nestedUris.filter( - (uri) => new AtUri(uri).collection === ids.AppBskyFeedGenerator, - ) - const nestedListUris = nestedUris.filter( - (uri) => new AtUri(uri).collection === ids.AppBskyGraphList, - ) - const [ - postViews, - actorViews, - deepEmbedViews, - labelViews, - feedGenViews, - listViews, - ] = await Promise.all([ - this.getPostViews(nestedPostUris, viewer), - this.getActorViews(nestedDids, viewer, { skipLabels: true }), - this.embedsForPosts(nestedUris, viewer, _depth + 1), - this.services.label.getLabelsForSubjects([...nestedUris, ...nestedDids]), - this.getFeedGeneratorViews(nestedFeedGenUris, viewer), - this.services.graph.getListViews(nestedListUris, viewer), - ]) - let embeds = images.reduce((acc, cur) => { - const embed = (acc[cur.postUri] ??= { - $type: 'app.bsky.embed.images#view', - images: [], - }) - if (!isViewImages(embed)) return acc - const postUri = new AtUri(cur.postUri) - embed.images.push({ - thumb: this.imgUriBuilder.getCommonSignedUri( - 'feed_thumbnail', - postUri.host, - cur.imageCid, - ), - fullsize: this.imgUriBuilder.getCommonSignedUri( - 'feed_fullsize', - postUri.host, - cur.imageCid, - ), - alt: cur.alt, - }) - return acc - }, {} as FeedEmbeds) - embeds = externals.reduce((acc, cur) => { - if (!acc[cur.postUri]) { - const postUri = new AtUri(cur.postUri) - acc[cur.postUri] = { - $type: 'app.bsky.embed.external#view', - external: { - uri: cur.uri, - title: cur.title, - description: cur.description, - thumb: cur.thumbCid - ? this.imgUriBuilder.getCommonSignedUri( - 'feed_thumbnail', - postUri.host, - cur.thumbCid, - ) - : undefined, - }, - } - } - return acc - }, embeds) - embeds = records.reduce((acc, cur) => { - const collection = new AtUri(cur.uri).collection - let recordEmbed: RecordEmbedView - if (collection === ids.AppBskyFeedGenerator && feedGenViews[cur.uri]) { - recordEmbed = { - record: { - $type: 'app.bsky.feed.defs#generatorView', - ...this.views.formatFeedGeneratorView( - feedGenViews[cur.uri], - actorViews, - labelViews, - ), - }, - } - } else if (collection === ids.AppBskyGraphList && listViews[cur.uri]) { - recordEmbed = { - record: { - $type: 'app.bsky.graph.defs#listView', - ...this.services.graph.formatListView( - listViews[cur.uri], - actorViews, - ), - }, - } - } else if (collection === ids.AppBskyFeedPost && postViews[cur.uri]) { - const formatted = this.views.formatPostView( - cur.uri, - actorViews, - postViews, - deepEmbedViews, - labelViews, - ) - let deepEmbeds: ViewRecord['embeds'] | undefined - if (_depth < 1) { - // Omit field entirely when too deep: e.g. don't include it on the embeds within a record embed. - // Otherwise list any embeds that appear within the record. A consumer may discover an embed - // within the raw record, then look within this array to find the presented view of it. - deepEmbeds = formatted?.embed ? [formatted.embed] : [] - } - recordEmbed = { - record: getRecordEmbedView(cur.uri, formatted, deepEmbeds), - } - } else { - recordEmbed = { - record: { - $type: 'app.bsky.embed.record#viewNotFound', - uri: cur.uri, - }, - } - } - if (acc[cur.postUri]) { - const mediaEmbed = acc[cur.postUri] - if (isViewImages(mediaEmbed) || isViewExternal(mediaEmbed)) { - acc[cur.postUri] = { - $type: 'app.bsky.embed.recordWithMedia#view', - record: recordEmbed, - media: mediaEmbed, - } - } - } else { - acc[cur.postUri] = { - $type: 'app.bsky.embed.record#view', - ...recordEmbed, - } - } - return acc - }, embeds) - return embeds + return feedItems.reduce((acc, item) => { + return Object.assign(acc, { [item.uri]: item }) + }, {} as Record) } - async hydrateFeed( - items: FeedRow[], - viewer: string | null, - // @TODO (deprecated) remove this once all clients support the blocked/not-found union on post views - usePostViewUnion?: boolean, - ): Promise { + feedItemRefs(items: FeedRow[]) { const actorDids = new Set() const postUris = new Set() for (const item of items) { - actorDids.add(item.postAuthorDid) postUris.add(item.postUri) - if (item.postAuthorDid !== item.originatorDid) { - actorDids.add(item.originatorDid) - } + actorDids.add(item.postAuthorDid) + actorDids.add(item.originatorDid) if (item.replyParent) { postUris.add(item.replyParent) actorDids.add(new AtUri(item.replyParent).hostname) @@ -451,51 +204,269 @@ export class FeedService { actorDids.add(new AtUri(item.replyRoot).hostname) } } - const [actors, posts, embeds, labels] = await Promise.all([ - this.getActorViews(Array.from(actorDids), viewer, { - skipLabels: true, - }), - this.getPostViews(Array.from(postUris), viewer), - this.embedsForPosts(Array.from(postUris), viewer), - this.services.label.getLabelsForSubjects([...postUris, ...actorDids]), - ]) + return { dids: actorDids, uris: postUris } + } - return this.views.formatFeed( - items, - actors, + async feedHydration( + refs: { + dids: Set + uris: Set + viewer: string | null + }, + depth = 0, + ): Promise { + const { viewer, dids, uris } = refs + const [posts, labels, bam] = await Promise.all([ + this.getPostInfos(Array.from(uris), viewer), + this.services.label.getLabelsForSubjects([...uris, ...dids]), + this.services.graph.getBlockAndMuteState( + viewer ? [...dids].map((did) => [viewer, did]) : [], + ), + ]) + // profileState for labels and bam handled above, profileHydration() shouldn't fetch additional + const [profileState, blocks] = await Promise.all([ + this.services.actor.views.profileHydration( + Array.from(dids), + { viewer }, + { bam, labels }, + ), + this.blocksForPosts(posts, bam), + ]) + const embeds = await this.embedsForPosts(posts, blocks, viewer, depth) + return { posts, + blocks, embeds, - labels, - usePostViewUnion, + labels, // includes info for profiles + bam, // includes info for profiles + profiles: profileState.profiles, + lists: profileState.lists, + } + } + + // applies blocks for visibility to third-parties (i.e. based on post content) + async blocksForPosts( + posts: PostInfoMap, + bam?: BlockAndMuteState, + ): Promise { + const relationships: RelationshipPair[] = [] + const byPost: Record = {} + const didFromUri = (uri) => new AtUri(uri).host + for (const post of Object.values(posts)) { + // skip posts that we can't process or appear to already have been processed + if (!isPostRecord(post.record)) continue + if (byPost[post.uri]) continue + byPost[post.uri] = {} + // 3p block for replies + const parentUri = post.record.reply?.parent.uri + const parentDid = parentUri ? didFromUri(parentUri) : null + // 3p block for record embeds + const embedUris = nestedRecordUris([post.record]) + // gather actor relationships among posts + if (parentDid) { + const pair: RelationshipPair = [post.creator, parentDid] + relationships.push(pair) + byPost[post.uri].reply = pair + } + for (const embedUri of embedUris) { + const pair: RelationshipPair = [post.creator, didFromUri(embedUri)] + relationships.push(pair) + byPost[post.uri].embed = pair + } + } + // compute block state from all actor relationships among posts + const blockState = await this.services.graph.getBlockState( + relationships, + bam, ) + const result: PostBlocksMap = {} + Object.entries(byPost).forEach(([uri, block]) => { + if (block.embed && blockState.block(block.embed)) { + result[uri] ??= {} + result[uri].embed = true + } + if (block.reply && blockState.block(block.reply)) { + result[uri] ??= {} + result[uri].reply = true + } + }) + return result + } + + async embedsForPosts( + postInfos: PostInfoMap, + blocks: PostBlocksMap, + viewer: string | null, + depth: number, + ) { + const postMap = postRecordsFromInfos(postInfos) + const posts = Object.values(postMap) + if (posts.length < 1) { + return {} + } + const recordEmbedViews = + depth > 1 ? {} : await this.nestedRecordViews(posts, viewer, depth) + + const postEmbedViews: PostEmbedViews = {} + for (const [uri, post] of Object.entries(postMap)) { + const creator = new AtUri(uri).hostname + if (!post.embed) continue + if (isEmbedImages(post.embed)) { + postEmbedViews[uri] = this.views.imagesEmbedView(creator, post.embed) + } else if (isEmbedExternal(post.embed)) { + postEmbedViews[uri] = this.views.externalEmbedView(creator, post.embed) + } else if (isEmbedRecord(post.embed)) { + if (!recordEmbedViews[post.embed.record.uri]) continue + postEmbedViews[uri] = { + $type: 'app.bsky.embed.record#view', + record: applyEmbedBlock( + uri, + blocks, + recordEmbedViews[post.embed.record.uri], + ), + } + } else if (isEmbedRecordWithMedia(post.embed)) { + const embedRecordView = recordEmbedViews[post.embed.record.record.uri] + if (!embedRecordView) continue + const formatted = this.views.getRecordWithMediaEmbedView( + creator, + post.embed, + applyEmbedBlock(uri, blocks, embedRecordView), + ) + if (formatted) { + postEmbedViews[uri] = formatted + } + } + } + return postEmbedViews + } + + async nestedRecordViews( + posts: PostRecord[], + viewer: string | null, + depth: number, + ): Promise { + const nestedUris = nestedRecordUris(posts) + if (nestedUris.length < 1) return {} + const nestedDids = new Set() + const nestedPostUris = new Set() + const nestedFeedGenUris = new Set() + const nestedListUris = new Set() + for (const uri of nestedUris) { + const parsed = new AtUri(uri) + nestedDids.add(parsed.hostname) + if (parsed.collection === ids.AppBskyFeedPost) { + nestedPostUris.add(uri) + } else if (parsed.collection === ids.AppBskyFeedGenerator) { + nestedFeedGenUris.add(uri) + } else if (parsed.collection === ids.AppBskyGraphList) { + nestedListUris.add(uri) + } + } + const [feedState, feedGenInfos, listViews] = await Promise.all([ + this.feedHydration( + { + dids: nestedDids, + uris: nestedPostUris, + viewer, + }, + depth + 1, + ), + this.getFeedGeneratorInfos([...nestedFeedGenUris], viewer), + this.services.graph.getListViews([...nestedListUris], viewer), + ]) + const actorInfos = this.services.actor.views.profileBasicPresentation( + [...nestedDids], + feedState, + { viewer }, + ) + const recordEmbedViews: RecordEmbedViewRecordMap = {} + for (const uri of nestedUris) { + const collection = new AtUri(uri).collection + if (collection === ids.AppBskyFeedGenerator && feedGenInfos[uri]) { + recordEmbedViews[uri] = { + $type: 'app.bsky.feed.defs#generatorView', + ...this.views.formatFeedGeneratorView(feedGenInfos[uri], actorInfos), + } + } else if (collection === ids.AppBskyGraphList && listViews[uri]) { + recordEmbedViews[uri] = { + $type: 'app.bsky.graph.defs#listView', + ...this.services.graph.formatListView(listViews[uri], actorInfos), + } + } else if (collection === ids.AppBskyFeedPost && feedState.posts[uri]) { + const formatted = this.views.formatPostView( + uri, + actorInfos, + feedState.posts, + feedState.embeds, + feedState.labels, + ) + recordEmbedViews[uri] = this.views.getRecordEmbedView( + uri, + formatted, + depth > 0, + ) + } else { + recordEmbedViews[uri] = { + $type: 'app.bsky.embed.record#viewNotFound', + uri, + notFound: true, + } + } + } + return recordEmbedViews } } -function getRecordEmbedView( - uri: string, - post?: PostView, - embeds?: ViewRecord['embeds'], -): (ViewRecord | ViewNotFound | ViewBlocked) & { $type: string } { - if (!post) { - return { - $type: 'app.bsky.embed.record#viewNotFound', - uri, +const postRecordsFromInfos = ( + infos: PostInfoMap, +): { [uri: string]: PostRecord } => { + const records: { [uri: string]: PostRecord } = {} + for (const [uri, info] of Object.entries(infos)) { + if (isPostRecord(info.record)) { + records[uri] = info.record + } + } + return records +} + +const nestedRecordUris = (posts: PostRecord[]): string[] => { + const uris: string[] = [] + for (const post of posts) { + if (!post.embed) continue + if (isEmbedRecord(post.embed)) { + uris.push(post.embed.record.uri) + } else if (isEmbedRecordWithMedia(post.embed)) { + uris.push(post.embed.record.record.uri) + } else { + continue } } - if (post.author.viewer?.blocking || post.author.viewer?.blockedBy) { + return uris +} + +type PostRelationships = { reply?: RelationshipPair; embed?: RelationshipPair } + +function applyEmbedBlock( + uri: string, + blocks: PostBlocksMap, + view: RecordEmbedViewRecord, +): RecordEmbedViewRecord { + if (isViewRecord(view) && blocks[uri]?.embed) { return { $type: 'app.bsky.embed.record#viewBlocked', - uri, + uri: view.uri, + blocked: true, + author: { + did: view.author.did, + viewer: view.author.viewer + ? { + blockedBy: view.author.viewer?.blockedBy, + blocking: view.author.viewer?.blocking, + } + : undefined, + }, } } - return { - $type: 'app.bsky.embed.record#viewRecord', - uri: post.uri, - cid: post.cid, - author: post.author, - value: post.record, - labels: post.labels, - indexedAt: post.indexedAt, - embeds, - } + return view } diff --git a/packages/bsky/src/services/feed/types.ts b/packages/bsky/src/services/feed/types.ts index 983d7403683..894ee0a564f 100644 --- a/packages/bsky/src/services/feed/types.ts +++ b/packages/bsky/src/services/feed/types.ts @@ -1,16 +1,93 @@ import { Selectable } from 'kysely' +import { View as ImagesEmbedView } from '../../lexicon/types/app/bsky/embed/images' +import { View as ExternalEmbedView } from '../../lexicon/types/app/bsky/embed/external' +import { + ViewBlocked, + ViewNotFound, + ViewRecord, + View as RecordEmbedView, +} from '../../lexicon/types/app/bsky/embed/record' +import { View as RecordWithMediaEmbedView } from '../../lexicon/types/app/bsky/embed/recordWithMedia' import { BlockedPost, + GeneratorView, NotFoundPost, PostView, } from '../../lexicon/types/app/bsky/feed/defs' import { FeedGenerator } from '../../db/tables/feed-generator' +import { ListView } from '../../lexicon/types/app/bsky/graph/defs' +import { ProfileHydrationState } from '../actor' +import { Labels } from '../label' +import { BlockAndMuteState } from '../graph' + +export type PostEmbedViews = { + [uri: string]: PostEmbedView +} + +export type PostEmbedView = + | ImagesEmbedView + | ExternalEmbedView + | RecordEmbedView + | RecordWithMediaEmbedView + +export type PostInfo = { + uri: string + cid: string + creator: string + record: Record + indexedAt: string + likeCount: number | null + repostCount: number | null + replyCount: number | null + requesterRepost: string | null + requesterLike: string | null + viewer: string | null +} + +export type PostInfoMap = { [uri: string]: PostInfo } + +export type PostBlocksMap = { + [uri: string]: { reply?: boolean; embed?: boolean } +} export type FeedGenInfo = Selectable & { likeCount: number - viewerLike: string | null + viewer?: { + like?: string + } } export type FeedGenInfoMap = { [uri: string]: FeedGenInfo } +export type FeedItemType = 'post' | 'repost' + +export type FeedRow = { + type: FeedItemType + uri: string + cid: string + postUri: string + postAuthorDid: string + originatorDid: string + replyParent: string | null + replyRoot: string | null + sortAt: string +} + export type MaybePostView = PostView | NotFoundPost | BlockedPost + +export type RecordEmbedViewRecord = + | ViewRecord + | ViewNotFound + | ViewBlocked + | GeneratorView + | ListView + +export type RecordEmbedViewRecordMap = { [uri: string]: RecordEmbedViewRecord } + +export type FeedHydrationState = ProfileHydrationState & { + posts: PostInfoMap + embeds: PostEmbedViews + labels: Labels + blocks: PostBlocksMap + bam: BlockAndMuteState +} diff --git a/packages/bsky/src/services/feed/views.ts b/packages/bsky/src/services/feed/views.ts index 99d0c95cf20..439e68f3d1f 100644 --- a/packages/bsky/src/services/feed/views.ts +++ b/packages/bsky/src/services/feed/views.ts @@ -1,64 +1,101 @@ -import Database from '../../db' +import { Database } from '../../db' import { FeedViewPost, GeneratorView, PostView, } from '../../lexicon/types/app/bsky/feed/defs' -import { ProfileView } from '../../lexicon/types/app/bsky/actor/defs' -import { FeedGenInfo, MaybePostView } from './types' -import { Labels } from '../label' +import { + Main as EmbedImages, + isMain as isEmbedImages, + View as EmbedImagesView, +} from '../../lexicon/types/app/bsky/embed/images' +import { + Main as EmbedExternal, + isMain as isEmbedExternal, + View as EmbedExternalView, +} from '../../lexicon/types/app/bsky/embed/external' +import { Main as EmbedRecordWithMedia } from '../../lexicon/types/app/bsky/embed/recordWithMedia' +import { + ViewBlocked, + ViewNotFound, + ViewRecord, +} from '../../lexicon/types/app/bsky/embed/record' +import { + PostEmbedViews, + FeedGenInfo, + FeedRow, + MaybePostView, + PostInfoMap, + RecordEmbedViewRecord, + PostBlocksMap, + FeedHydrationState, +} from './types' +import { Labels, getSelfLabels } from '../label' import { ImageUriBuilder } from '../../image/uri' -import { ActorViewMap, FeedEmbeds, FeedRow, PostInfoMap } from '../types' -import { jsonStringToLex } from '@atproto/lexicon' +import { LabelCache } from '../../label-cache' +import { ActorInfoMap, ActorService } from '../actor' export class FeedViews { - constructor(public db: Database, public imgUriBuilder: ImageUriBuilder) {} + constructor( + public db: Database, + public imgUriBuilder: ImageUriBuilder, + public labelCache: LabelCache, + ) {} - static creator(imgUriBuilder: ImageUriBuilder) { - return (db: Database) => new FeedViews(db, imgUriBuilder) + static creator(imgUriBuilder: ImageUriBuilder, labelCache: LabelCache) { + return (db: Database) => new FeedViews(db, imgUriBuilder, labelCache) } - formatPostView( - uri: string, - actors: ActorViewMap, - posts: PostInfoMap, - embeds: FeedEmbeds, - labels: Labels, - ): PostView | undefined { - const post = posts[uri] - const author = actors[post?.creator] - if (!post || !author) return undefined - // If the author labels are not hydrated yet, attempt to pull them - // from labels: e.g. compatible with hydrateFeed() batching label hydration. - author.labels ??= labels[author.did] ?? [] + services = { + actor: ActorService.creator(this.imgUriBuilder, this.labelCache)(this.db), + } + + formatFeedGeneratorView( + info: FeedGenInfo, + profiles: ActorInfoMap, + ): GeneratorView { + const profile = profiles[info.creator] return { - uri: post.uri, - cid: post.cid, - author: author, - record: jsonStringToLex(post.recordJson) as Record, - embed: embeds[uri], - replyCount: post.replyCount ?? 0, - repostCount: post.repostCount ?? 0, - likeCount: post.likeCount ?? 0, - indexedAt: post.indexedAt, - viewer: post.viewer + uri: info.uri, + cid: info.cid, + did: info.feedDid, + creator: profile, + displayName: info.displayName ?? undefined, + description: info.description ?? undefined, + descriptionFacets: info.descriptionFacets + ? JSON.parse(info.descriptionFacets) + : undefined, + avatar: info.avatarCid + ? this.imgUriBuilder.getPresetUri( + 'avatar', + info.creator, + info.avatarCid, + ) + : undefined, + likeCount: info.likeCount, + viewer: info.viewer ? { - repost: post.requesterRepost ?? undefined, - like: post.requesterLike ?? undefined, + like: info.viewer.like ?? undefined, } : undefined, - labels: labels[uri] ?? [], + indexedAt: info.indexedAt, } } formatFeed( items: FeedRow[], - actors: ActorViewMap, - posts: PostInfoMap, - embeds: FeedEmbeds, - labels: Labels, - usePostViewUnion?: boolean, + state: FeedHydrationState, + opts?: { + viewer?: string | null + usePostViewUnion?: boolean + }, ): FeedViewPost[] { + const { posts, profiles, blocks, embeds, labels } = state + const actors = this.services.actor.views.profileBasicPresentation( + Object.keys(profiles), + state, + opts, + ) const feed: FeedViewPost[] = [] for (const item of items) { const post = this.formatPostView( @@ -69,7 +106,7 @@ export class FeedViews { labels, ) // skip over not found & blocked posts - if (!post) { + if (!post || blocks[post.uri]?.reply) { continue } const feedPost = { post } @@ -81,10 +118,7 @@ export class FeedViews { } else { feedPost['reason'] = { $type: 'app.bsky.feed.defs#reasonRepost', - by: { - ...originator, - labels: labels[item.originatorDid] ?? [], - }, + by: originator, indexedAt: item.sortAt, } } @@ -96,7 +130,8 @@ export class FeedViews { posts, embeds, labels, - usePostViewUnion, + blocks, + opts, ) const replyRoot = this.formatMaybePostView( item.replyRoot, @@ -104,7 +139,8 @@ export class FeedViews { posts, embeds, labels, - usePostViewUnion, + blocks, + opts, ) if (replyRoot && replyParent) { feedPost['reply'] = { @@ -118,59 +154,65 @@ export class FeedViews { return feed } - formatFeedGeneratorView( - info: FeedGenInfo, - profiles: Record, - labels?: Labels, - ): GeneratorView { - const profile = profiles[info.creator] - if (profile) { - // If the creator labels are not hydrated yet, attempt to pull them - // from labels: e.g. compatible with embedsForPosts() batching label hydration. - profile.labels ??= labels?.[info.creator] ?? [] - } + formatPostView( + uri: string, + actors: ActorInfoMap, + posts: PostInfoMap, + embeds: PostEmbedViews, + labels: Labels, + ): PostView | undefined { + const post = posts[uri] + const author = actors[post?.creator] + if (!post || !author) return undefined + const postLabels = labels[uri] ?? [] + const postSelfLabels = getSelfLabels({ + uri: post.uri, + cid: post.cid, + record: post.record, + }) return { - uri: info.uri, - cid: info.cid, - did: info.feedDid, - creator: profile, - displayName: info.displayName ?? undefined, - description: info.description ?? undefined, - descriptionFacets: info.descriptionFacets - ? JSON.parse(info.descriptionFacets) - : undefined, - avatar: info.avatarCid - ? this.imgUriBuilder.getCommonSignedUri( - 'avatar', - info.creator, - info.avatarCid, - ) + uri: post.uri, + cid: post.cid, + author: author, + record: post.record, + embed: embeds[uri], + replyCount: post.replyCount ?? 0, + repostCount: post.repostCount ?? 0, + likeCount: post.likeCount ?? 0, + indexedAt: post.indexedAt, + viewer: post.viewer + ? { + repost: post.requesterRepost ?? undefined, + like: post.requesterLike ?? undefined, + } : undefined, - likeCount: info.likeCount, - viewer: { - like: info.viewerLike ?? undefined, - }, - indexedAt: info.sortAt, + labels: [...postLabels, ...postSelfLabels], } } - // @TODO present block here (w/ usePostViewUnion) formatMaybePostView( uri: string, - actors: ActorViewMap, + actors: ActorInfoMap, posts: PostInfoMap, - embeds: FeedEmbeds, + embeds: PostEmbedViews, labels: Labels, - usePostViewUnion?: boolean, + blocks: PostBlocksMap, + opts?: { + usePostViewUnion?: boolean + }, ): MaybePostView | undefined { const post = this.formatPostView(uri, actors, posts, embeds, labels) if (!post) { - if (!usePostViewUnion) return + if (!opts?.usePostViewUnion) return return this.notFoundPost(uri) } - if (post.author.viewer?.blockedBy || post.author.viewer?.blocking) { - if (!usePostViewUnion) return - return this.blockedPost(uri) + if ( + post.author.viewer?.blockedBy || + post.author.viewer?.blocking || + blocks[uri]?.reply + ) { + if (!opts?.usePostViewUnion) return + return this.blockedPost(post) } return { $type: 'app.bsky.feed.defs#postView', @@ -178,11 +220,20 @@ export class FeedViews { } } - blockedPost(uri: string) { + blockedPost(post: PostView) { return { $type: 'app.bsky.feed.defs#blockedPost', - uri: uri, + uri: post.uri, blocked: true as const, + author: { + did: post.author.did, + viewer: post.author.viewer + ? { + blockedBy: post.author.viewer?.blockedBy, + blocking: post.author.viewer?.blocking, + } + : undefined, + }, } } @@ -193,4 +244,102 @@ export class FeedViews { notFound: true as const, } } + + imagesEmbedView(did: string, embed: EmbedImages) { + const imgViews = embed.images.map((img) => ({ + thumb: this.imgUriBuilder.getPresetUri( + 'feed_thumbnail', + did, + img.image.ref, + ), + fullsize: this.imgUriBuilder.getPresetUri( + 'feed_fullsize', + did, + img.image.ref, + ), + alt: img.alt, + aspectRatio: img.aspectRatio, + })) + return { + $type: 'app.bsky.embed.images#view', + images: imgViews, + } + } + + externalEmbedView(did: string, embed: EmbedExternal) { + const { uri, title, description, thumb } = embed.external + return { + $type: 'app.bsky.embed.external#view', + external: { + uri, + title, + description, + thumb: thumb + ? this.imgUriBuilder.getPresetUri('feed_thumbnail', did, thumb.ref) + : undefined, + }, + } + } + + getRecordEmbedView( + uri: string, + post?: PostView, + omitEmbeds = false, + ): (ViewRecord | ViewNotFound | ViewBlocked) & { $type: string } { + if (!post) { + return { + $type: 'app.bsky.embed.record#viewNotFound', + uri, + notFound: true, + } + } + if (post.author.viewer?.blocking || post.author.viewer?.blockedBy) { + return { + $type: 'app.bsky.embed.record#viewBlocked', + uri, + blocked: true, + author: { + did: post.author.did, + viewer: post.author.viewer + ? { + blockedBy: post.author.viewer?.blockedBy, + blocking: post.author.viewer?.blocking, + } + : undefined, + }, + } + } + return { + $type: 'app.bsky.embed.record#viewRecord', + uri: post.uri, + cid: post.cid, + author: post.author, + value: post.record, + labels: post.labels, + indexedAt: post.indexedAt, + embeds: omitEmbeds ? undefined : post.embed ? [post.embed] : [], + } + } + + getRecordWithMediaEmbedView( + did: string, + embed: EmbedRecordWithMedia, + embedRecordView: RecordEmbedViewRecord, + ) { + let mediaEmbed: EmbedImagesView | EmbedExternalView + if (isEmbedImages(embed.media)) { + mediaEmbed = this.imagesEmbedView(did, embed.media) + } else if (isEmbedExternal(embed.media)) { + mediaEmbed = this.externalEmbedView(did, embed.media) + } else { + return + } + return { + $type: 'app.bsky.embed.recordWithMedia#view', + record: { + record: embedRecordView, + }, + media: mediaEmbed, + } + } } diff --git a/packages/bsky/src/services/graph/index.ts b/packages/bsky/src/services/graph/index.ts index 8abdfe6ce0b..53592ac4021 100644 --- a/packages/bsky/src/services/graph/index.ts +++ b/packages/bsky/src/services/graph/index.ts @@ -1,10 +1,11 @@ -import Database from '../../db' +import { sql } from 'kysely' +import { Database } from '../../db' import { ImageUriBuilder } from '../../image/uri' -import { ProfileView } from '../../lexicon/types/app/bsky/actor/defs' -import { List } from '../../db/tables/list' -import { Selectable, WhereInterface, sql } from 'kysely' -import { NotEmptyArray } from '@atproto/common' -import { DbRef, noMatch } from '../../db/util' +import { valuesList } from '../../db/util' +import { ListInfo } from './types' +import { ActorInfoMap } from '../actor' + +export * from './types' export class GraphService { constructor(public db: Database, public imgUriBuilder: ImageUriBuilder) {} @@ -19,8 +20,9 @@ export class GraphService { createdAt?: Date }) { const { subjectDid, mutedByDid, createdAt = new Date() } = info - await this.db.db - .insertInto('mute') + await this.db + .asPrimary() + .db.insertInto('mute') .values({ subjectDid, mutedByDid, @@ -32,8 +34,9 @@ export class GraphService { async unmuteActor(info: { subjectDid: string; mutedByDid: string }) { const { subjectDid, mutedByDid } = info - await this.db.db - .deleteFrom('mute') + await this.db + .asPrimary() + .db.deleteFrom('mute') .where('subjectDid', '=', subjectDid) .where('mutedByDid', '=', mutedByDid) .execute() @@ -45,8 +48,9 @@ export class GraphService { createdAt?: Date }) { const { list, mutedByDid, createdAt = new Date() } = info - await this.db.db - .insertInto('list_mute') + await this.db + .asPrimary() + .db.insertInto('list_mute') .values({ listUri: list, mutedByDid, @@ -58,34 +62,14 @@ export class GraphService { async unmuteActorList(info: { list: string; mutedByDid: string }) { const { list, mutedByDid } = info - await this.db.db - .deleteFrom('list_mute') + await this.db + .asPrimary() + .db.deleteFrom('list_mute') .where('listUri', '=', list) .where('mutedByDid', '=', mutedByDid) .execute() } - whereNotMuted>( - qb: W, - requester: string, - refs: NotEmptyArray, - ) { - const subjectRefs = sql.join(refs) - const actorMute = this.db.db - .selectFrom('mute') - .where('mutedByDid', '=', requester) - .where('subjectDid', 'in', sql`(${subjectRefs})`) - .select('subjectDid as muted') - const listMute = this.db.db - .selectFrom('list_item') - .innerJoin('list_mute', 'list_mute.listUri', 'list_item.listUri') - .where('list_mute.mutedByDid', '=', requester) - .whereRef('list_item.subjectDid', 'in', sql`(${subjectRefs})`) - .select('list_item.subjectDid as muted') - // Splitting the mute from list-mute checks seems to be more flexible for the query-planner and often quicker - return qb.whereNotExists(actorMute).whereNotExists(listMute) - } - getListsQb(viewer: string | null) { const { ref } = this.db.db.dynamic return this.db.db @@ -94,14 +78,20 @@ export class GraphService { .selectAll('list') .selectAll('actor') .select('list.sortAt as sortAt') - .select( + .select([ this.db.db .selectFrom('list_mute') .where('list_mute.mutedByDid', '=', viewer ?? '') .whereRef('list_mute.listUri', '=', ref('list.uri')) .select('list_mute.listUri') .as('viewerMuted'), - ) + this.db.db + .selectFrom('list_block') + .where('list_block.creator', '=', viewer ?? '') + .whereRef('list_block.subjectUri', '=', ref('list.uri')) + .select('list_block.uri') + .as('viewerListBlockUri'), + ]) } getListItemsQb() { @@ -112,65 +102,117 @@ export class GraphService { .select(['list_item.cid as cid', 'list_item.sortAt as sortAt']) } - blockQb(viewer: string | null, refs: NotEmptyArray) { - const subjectRefs = sql.join(refs) - return this.db.db - .selectFrom('actor_block') - .if(!viewer, (q) => q.where(noMatch)) - .where((outer) => - outer - .where((qb) => - qb - .where('actor_block.creator', '=', viewer ?? '') - .whereRef('actor_block.subjectDid', 'in', sql`(${subjectRefs})`), - ) - .orWhere((qb) => - qb - .where('actor_block.subjectDid', '=', viewer ?? '') - .whereRef('actor_block.creator', 'in', sql`(${subjectRefs})`), - ), - ) - .select(['creator', 'subjectDid']) - } - - async getBlocks( - requester: string, - subjectHandleOrDid: string, - ): Promise<{ blocking: boolean; blockedBy: boolean }> { - let subjectDid - if (subjectHandleOrDid.startsWith('did:')) { - subjectDid = subjectHandleOrDid - } else { - const res = await this.db.db - .selectFrom('actor') - .where('handle', '=', subjectHandleOrDid) - .select('did') - .executeTakeFirst() - if (!res) { - return { blocking: false, blockedBy: false } - } - subjectDid = res.did - } - - const accnts = [requester, subjectDid] - const blockRes = await this.db.db - .selectFrom('actor_block') - .where('creator', 'in', accnts) - .where('subjectDid', 'in', accnts) + async getBlockAndMuteState( + pairs: RelationshipPair[], + bam?: BlockAndMuteState, + ) { + pairs = bam ? pairs.filter((pair) => !bam.has(pair)) : pairs + const result = bam ?? new BlockAndMuteState() + if (!pairs.length) return result + const { ref } = this.db.db.dynamic + const sourceRef = ref('pair.source') + const targetRef = ref('pair.target') + const values = valuesList(pairs.map((p) => sql`${p[0]}, ${p[1]}`)) + const items = await this.db.db + .selectFrom(values.as(sql`pair (source, target)`)) + .select([ + sql`${sourceRef}`.as('source'), + sql`${targetRef}`.as('target'), + this.db.db + .selectFrom('actor_block') + .whereRef('creator', '=', sourceRef) + .whereRef('subjectDid', '=', targetRef) + .select('uri') + .as('blocking'), + this.db.db + .selectFrom('list_item') + .innerJoin('list_block', 'list_block.subjectUri', 'list_item.listUri') + .whereRef('list_block.creator', '=', sourceRef) + .whereRef('list_item.subjectDid', '=', targetRef) + .select('list_item.listUri') + .limit(1) + .as('blockingViaList'), + this.db.db + .selectFrom('actor_block') + .whereRef('creator', '=', targetRef) + .whereRef('subjectDid', '=', sourceRef) + .select('uri') + .as('blockedBy'), + this.db.db + .selectFrom('list_item') + .innerJoin('list_block', 'list_block.subjectUri', 'list_item.listUri') + .whereRef('list_block.creator', '=', targetRef) + .whereRef('list_item.subjectDid', '=', sourceRef) + .select('list_item.listUri') + .limit(1) + .as('blockedByViaList'), + this.db.db + .selectFrom('mute') + .whereRef('mutedByDid', '=', sourceRef) + .whereRef('subjectDid', '=', targetRef) + .select(sql`${true}`.as('val')) + .as('muting'), + this.db.db + .selectFrom('list_item') + .innerJoin('list_mute', 'list_mute.listUri', 'list_item.listUri') + .whereRef('list_mute.mutedByDid', '=', sourceRef) + .whereRef('list_item.subjectDid', '=', targetRef) + .select('list_item.listUri') + .limit(1) + .as('mutingViaList'), + ]) .selectAll() .execute() + items.forEach((item) => result.add(item)) + return result + } - const blocking = blockRes.some( - (row) => row.creator === requester && row.subjectDid === subjectDid, - ) - const blockedBy = blockRes.some( - (row) => row.creator === subjectDid && row.subjectDid === requester, - ) - - return { - blocking, - blockedBy, - } + async getBlockState(pairs: RelationshipPair[], bam?: BlockAndMuteState) { + pairs = bam ? pairs.filter((pair) => !bam.has(pair)) : pairs + const result = bam ?? new BlockAndMuteState() + if (!pairs.length) return result + const { ref } = this.db.db.dynamic + const sourceRef = ref('pair.source') + const targetRef = ref('pair.target') + const values = valuesList(pairs.map((p) => sql`${p[0]}, ${p[1]}`)) + const items = await this.db.db + .selectFrom(values.as(sql`pair (source, target)`)) + .select([ + sql`${sourceRef}`.as('source'), + sql`${targetRef}`.as('target'), + this.db.db + .selectFrom('actor_block') + .whereRef('creator', '=', sourceRef) + .whereRef('subjectDid', '=', targetRef) + .select('uri') + .as('blocking'), + this.db.db + .selectFrom('list_item') + .innerJoin('list_block', 'list_block.subjectUri', 'list_item.listUri') + .whereRef('list_block.creator', '=', sourceRef) + .whereRef('list_item.subjectDid', '=', targetRef) + .select('list_item.listUri') + .limit(1) + .as('blockingViaList'), + this.db.db + .selectFrom('actor_block') + .whereRef('creator', '=', targetRef) + .whereRef('subjectDid', '=', sourceRef) + .select('uri') + .as('blockedBy'), + this.db.db + .selectFrom('list_item') + .innerJoin('list_block', 'list_block.subjectUri', 'list_item.listUri') + .whereRef('list_block.creator', '=', targetRef) + .whereRef('list_item.subjectDid', '=', sourceRef) + .select('list_item.listUri') + .limit(1) + .as('blockedByViaList'), + ]) + .selectAll() + .execute() + items.forEach((item) => result.add(item)) + return result } async getListViews(listUris: string[], requester: string | null) { @@ -187,19 +229,25 @@ export class GraphService { ) } - formatListView(list: ListInfo, profiles: Record) { + formatListView(list: ListInfo, profiles: ActorInfoMap) { return { - uri: list.uri, - cid: list.cid, + ...this.formatListViewBasic(list), creator: profiles[list.creator], - name: list.name, - purpose: list.purpose, description: list.description ?? undefined, descriptionFacets: list.descriptionFacets ? JSON.parse(list.descriptionFacets) : undefined, + } + } + + formatListViewBasic(list: ListInfo) { + return { + uri: list.uri, + cid: list.cid, + name: list.name, + purpose: list.purpose, avatar: list.avatarCid - ? this.imgUriBuilder.getCommonSignedUri( + ? this.imgUriBuilder.getPresetUri( 'avatar', list.creator, list.avatarCid, @@ -208,11 +256,89 @@ export class GraphService { indexedAt: list.sortAt, viewer: { muted: !!list.viewerMuted, + blocked: list.viewerListBlockUri ?? undefined, }, } } } -type ListInfo = Selectable & { - viewerMuted: string | null +export type RelationshipPair = [didA: string, didB: string] + +export class BlockAndMuteState { + hasIdx = new Map>() // did -> did + blockIdx = new Map>() // did -> did -> block uri + muteIdx = new Map>() // did -> did + muteListIdx = new Map>() // did -> did -> list uri + constructor(items: BlockAndMuteInfo[] = []) { + items.forEach((item) => this.add(item)) + } + add(item: BlockAndMuteInfo) { + const blocking = item.blocking || item.blockingViaList // block or list uri + if (blocking) { + const map = this.blockIdx.get(item.source) ?? new Map() + map.set(item.target, blocking) + if (!this.blockIdx.has(item.source)) { + this.blockIdx.set(item.source, map) + } + } + const blockedBy = item.blockedBy || item.blockedByViaList // block or list uri + if (blockedBy) { + const map = this.blockIdx.get(item.target) ?? new Map() + map.set(item.source, blockedBy) + if (!this.blockIdx.has(item.target)) { + this.blockIdx.set(item.target, map) + } + } + if (item.muting) { + const set = this.muteIdx.get(item.source) ?? new Set() + set.add(item.target) + if (!this.muteIdx.has(item.source)) { + this.muteIdx.set(item.source, set) + } + } + if (item.mutingViaList) { + const map = this.muteListIdx.get(item.source) ?? new Map() + map.set(item.target, item.mutingViaList) + if (!this.muteListIdx.has(item.source)) { + this.muteListIdx.set(item.source, map) + } + } + const set = this.hasIdx.get(item.source) ?? new Set() + set.add(item.target) + if (!this.hasIdx.has(item.source)) { + this.hasIdx.set(item.source, set) + } + } + block(pair: RelationshipPair): boolean { + return !!this.blocking(pair) || !!this.blockedBy(pair) + } + // block or list uri + blocking(pair: RelationshipPair): string | null { + return this.blockIdx.get(pair[0])?.get(pair[1]) ?? null + } + // block or list uri + blockedBy(pair: RelationshipPair): string | null { + return this.blocking([pair[1], pair[0]]) + } + mute(pair: RelationshipPair): boolean { + return !!this.muteIdx.get(pair[0])?.has(pair[1]) || !!this.muteList(pair) + } + // list uri + muteList(pair: RelationshipPair): string | null { + return this.muteListIdx.get(pair[0])?.get(pair[1]) ?? null + } + has(pair: RelationshipPair) { + return !!this.hasIdx.get(pair[0])?.has(pair[1]) + } +} + +type BlockAndMuteInfo = { + source: string + target: string + blocking?: string | null + blockingViaList?: string | null + blockedBy?: string | null + blockedByViaList?: string | null + muting?: true | null + mutingViaList?: string | null } diff --git a/packages/bsky/src/services/graph/types.ts b/packages/bsky/src/services/graph/types.ts new file mode 100644 index 00000000000..f5ee0c13026 --- /dev/null +++ b/packages/bsky/src/services/graph/types.ts @@ -0,0 +1,9 @@ +import { Selectable } from 'kysely' +import { List } from '../../db/tables/list' + +export type ListInfo = Selectable & { + viewerMuted: string | null + viewerListBlockUri: string | null +} + +export type ListInfoMap = Record diff --git a/packages/bsky/src/services/index.ts b/packages/bsky/src/services/index.ts index a17c71c6d13..c3fe47e6eff 100644 --- a/packages/bsky/src/services/index.ts +++ b/packages/bsky/src/services/index.ts @@ -1,37 +1,25 @@ -import { IdResolver } from '@atproto/identity' -import Database from '../db' +import { Database, PrimaryDatabase } from '../db' import { ImageUriBuilder } from '../image/uri' import { ActorService } from './actor' import { FeedService } from './feed' import { GraphService } from './graph' -import { IndexingService } from './indexing' import { ModerationService } from './moderation' import { LabelService } from './label' import { ImageInvalidator } from '../image/invalidator' -import { Labeler } from '../labeler' -import { BackgroundQueue } from '../background' +import { LabelCache } from '../label-cache' export function createServices(resources: { imgUriBuilder: ImageUriBuilder imgInvalidator: ImageInvalidator - idResolver: IdResolver - labeler: Labeler - backgroundQueue: BackgroundQueue + labelCache: LabelCache }): Services { - const { - imgUriBuilder, - imgInvalidator, - idResolver, - labeler, - backgroundQueue, - } = resources + const { imgUriBuilder, imgInvalidator, labelCache } = resources return { - actor: ActorService.creator(imgUriBuilder), - feed: FeedService.creator(imgUriBuilder), + actor: ActorService.creator(imgUriBuilder, labelCache), + feed: FeedService.creator(imgUriBuilder, labelCache), graph: GraphService.creator(imgUriBuilder), - indexing: IndexingService.creator(idResolver, labeler, backgroundQueue), moderation: ModerationService.creator(imgUriBuilder, imgInvalidator), - label: LabelService.creator(), + label: LabelService.creator(labelCache), } } @@ -39,9 +27,10 @@ export type Services = { actor: FromDb feed: FromDb graph: FromDb - indexing: FromDb - moderation: FromDb + moderation: FromDbPrimary label: FromDb } type FromDb = (db: Database) => T + +type FromDbPrimary = (db: PrimaryDatabase) => T diff --git a/packages/bsky/src/services/indexing/index.ts b/packages/bsky/src/services/indexing/index.ts index 02265e12446..05e591c92c4 100644 --- a/packages/bsky/src/services/indexing/index.ts +++ b/packages/bsky/src/services/indexing/index.ts @@ -1,19 +1,18 @@ import { sql } from 'kysely' import { CID } from 'multiformats/cid' -import AtpAgent, { ComAtprotoSyncGetHead } from '@atproto/api' +import AtpAgent, { ComAtprotoSyncGetLatestCommit } from '@atproto/api' import { - MemoryBlockstore, readCarWithRoot, WriteOpAction, - verifyCheckoutWithCids, - RepoContentsWithCids, + verifyRepo, Commit, + VerifiedRepo, } from '@atproto/repo' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { IdResolver, getPds } from '@atproto/identity' -import { DAY, chunkArray } from '@atproto/common' +import { DAY, HOUR } from '@atproto/common' import { ValidationError } from '@atproto/lexicon' -import Database from '../../db' +import { PrimaryDatabase } from '../../db' import * as Post from './plugins/post' import * as Like from './plugins/like' import * as Repost from './plugins/repost' @@ -21,13 +20,16 @@ import * as Follow from './plugins/follow' import * as Profile from './plugins/profile' import * as List from './plugins/list' import * as ListItem from './plugins/list-item' +import * as ListBlock from './plugins/list-block' import * as Block from './plugins/block' import * as FeedGenerator from './plugins/feed-generator' import RecordProcessor from './processor' import { subLogger } from '../../logger' import { retryHttp } from '../../util/retry' -import { Labeler } from '../../labeler' import { BackgroundQueue } from '../../background' +import { NotificationServer } from '../../notifications' +import { AutoModerator } from '../../auto-moderator' +import { Actor } from '../../db/tables/actor' export class IndexingService { records: { @@ -38,46 +40,55 @@ export class IndexingService { profile: Profile.PluginType list: List.PluginType listItem: ListItem.PluginType + listBlock: ListBlock.PluginType block: Block.PluginType feedGenerator: FeedGenerator.PluginType } constructor( - public db: Database, + public db: PrimaryDatabase, public idResolver: IdResolver, - public labeler: Labeler, + public autoMod: AutoModerator, public backgroundQueue: BackgroundQueue, + public notifServer?: NotificationServer, ) { this.records = { - post: Post.makePlugin(this.db, backgroundQueue), - like: Like.makePlugin(this.db, backgroundQueue), - repost: Repost.makePlugin(this.db, backgroundQueue), - follow: Follow.makePlugin(this.db, backgroundQueue), - profile: Profile.makePlugin(this.db, backgroundQueue), - list: List.makePlugin(this.db, backgroundQueue), - listItem: ListItem.makePlugin(this.db, backgroundQueue), - block: Block.makePlugin(this.db, backgroundQueue), - feedGenerator: FeedGenerator.makePlugin(this.db, backgroundQueue), + post: Post.makePlugin(this.db, backgroundQueue, notifServer), + like: Like.makePlugin(this.db, backgroundQueue, notifServer), + repost: Repost.makePlugin(this.db, backgroundQueue, notifServer), + follow: Follow.makePlugin(this.db, backgroundQueue, notifServer), + profile: Profile.makePlugin(this.db, backgroundQueue, notifServer), + list: List.makePlugin(this.db, backgroundQueue, notifServer), + listItem: ListItem.makePlugin(this.db, backgroundQueue, notifServer), + listBlock: ListBlock.makePlugin(this.db, backgroundQueue, notifServer), + block: Block.makePlugin(this.db, backgroundQueue, notifServer), + feedGenerator: FeedGenerator.makePlugin( + this.db, + backgroundQueue, + notifServer, + ), } } - transact(txn: Database) { + transact(txn: PrimaryDatabase) { txn.assertTransaction() return new IndexingService( txn, this.idResolver, - this.labeler, + this.autoMod, this.backgroundQueue, + this.notifServer, ) } static creator( idResolver: IdResolver, - labeler: Labeler, + autoMod: AutoModerator, backgroundQueue: BackgroundQueue, + notifServer?: NotificationServer, ) { - return (db: Database) => - new IndexingService(db, idResolver, labeler, backgroundQueue) + return (db: PrimaryDatabase) => + new IndexingService(db, idResolver, autoMod, backgroundQueue, notifServer) } async indexRecord( @@ -86,6 +97,7 @@ export class IndexingService { obj: unknown, action: WriteOpAction.Create | WriteOpAction.Update, timestamp: string, + opts?: { disableNotifs?: boolean; disableLabels?: boolean }, ) { this.db.assertNotTransaction() await this.db.transaction(async (txn) => { @@ -93,12 +105,14 @@ export class IndexingService { const indexer = indexingTx.findIndexerForCollection(uri.collection) if (!indexer) return if (action === WriteOpAction.Create) { - await indexer.insertRecord(uri, cid, obj, timestamp) + await indexer.insertRecord(uri, cid, obj, timestamp, opts) } else { await indexer.updateRecord(uri, cid, obj, timestamp) } }) - this.labeler.processRecord(uri, obj) + if (!opts?.disableLabels) { + this.autoMod.processRecord(uri, cid, obj) + } } async deleteRecord(uri: AtUri, cascading = false) { @@ -118,37 +132,49 @@ export class IndexingService { .where('did', '=', did) .selectAll() .executeTakeFirst() - const timestampAt = new Date(timestamp) - const lastIndexedAt = actor && new Date(actor.indexedAt) - const needsReindex = - force || - !lastIndexedAt || - timestampAt.getTime() - lastIndexedAt.getTime() > DAY - if (!needsReindex) { + if (!force && !needsHandleReindex(actor, timestamp)) { return } - const { handle } = await this.idResolver.did.resolveAtprotoData(did, true) - const handleToDid = await this.idResolver.handle.resolve(handle) - if (did !== handleToDid) { - return // No bidirectional link between did and handle + const atpData = await this.idResolver.did.resolveAtprotoData(did, true) + const handleToDid = await this.idResolver.handle.resolve(atpData.handle) + + const handle: string | null = + did === handleToDid ? atpData.handle.toLowerCase() : null + + if (actor && actor.handle !== handle) { + const actorWithHandle = + handle !== null + ? await this.db.db + .selectFrom('actor') + .where('handle', '=', handle) + .selectAll() + .executeTakeFirst() + : null + + // handle contention + if (handle && actorWithHandle && did !== actorWithHandle.did) { + await this.db.db + .updateTable('actor') + .where('actor.did', '=', actorWithHandle.did) + .set({ handle: null }) + .execute() + } } + const actorInfo = { handle, indexedAt: timestamp } - const inserted = await this.db.db + await this.db.db .insertInto('actor') .values({ did, ...actorInfo }) - .onConflict((oc) => oc.doNothing()) + .onConflict((oc) => oc.column('did').doUpdateSet(actorInfo)) .returning('did') .executeTakeFirst() - if (!inserted) { - await this.db.db - .updateTable('actor') - .set(actorInfo) - .where('did', '=', did) - .execute() + + if (handle) { + this.autoMod.processHandle(handle, did) } } - async indexRepo(did: string, commit: string) { + async indexRepo(did: string, commit?: string) { this.db.assertNotTransaction() const now = new Date().toISOString() const { pds, signingKey } = await this.idResolver.did.resolveAtprotoData( @@ -158,30 +184,30 @@ export class IndexingService { const { api } = new AtpAgent({ service: pds }) const { data: car } = await retryHttp(() => - api.com.atproto.sync.getCheckout({ did, commit }), + api.com.atproto.sync.getRepo({ did }), ) const { root, blocks } = await readCarWithRoot(car) - const storage = new MemoryBlockstore(blocks) - const checkout = await verifyCheckoutWithCids( - storage, - root, - did, - signingKey, - ) - - // Wipe index for actor, prep for reindexing - await this.unindexActor(did) + const verifiedRepo = await verifyRepo(blocks, root, did, signingKey) - // Iterate over all records and index them in batches - const contentList = [...walkContentsWithCids(checkout.contents)] - const chunks = chunkArray(contentList, 100) + const currRecords = await this.getCurrentRecords(did) + const repoRecords = formatCheckout(did, verifiedRepo) + const diff = findDiffFromCheckout(currRecords, repoRecords) - for (const chunk of chunks) { - const processChunk = chunk.map(async (item) => { - const { cid, collection, rkey, record } = item - const uri = AtUri.make(did, collection, rkey) + await Promise.all( + diff.map(async (op) => { + const { uri, cid } = op try { - await this.indexRecord(uri, cid, record, WriteOpAction.Create, now) + if (op.op === 'delete') { + await this.deleteRecord(uri) + } else { + await this.indexRecord( + uri, + cid, + op.value, + op.op === 'create' ? WriteOpAction.Create : WriteOpAction.Update, + now, + ) + } } catch (err) { if (err instanceof ValidationError) { subLogger.warn( @@ -189,12 +215,29 @@ export class IndexingService { 'skipping indexing of invalid record', ) } else { - throw err + subLogger.error( + { err, did, commit, uri: uri.toString(), cid: cid.toString() }, + 'skipping indexing due to error processing record', + ) } } - }) - await Promise.all(processChunk) - } + }), + ) + } + + async getCurrentRecords(did: string) { + const res = await this.db.db + .selectFrom('record') + .where('did', '=', did) + .select(['uri', 'cid']) + .execute() + return res.reduce((acc, cur) => { + acc[cur.uri] = { + uri: new AtUri(cur.uri), + cid: CID.parse(cur.cid), + } + return acc + }, {} as Record) } async setCommitLastSeen( @@ -208,6 +251,7 @@ export class IndexingService { did: commit.did, commitCid: details.commit.toString(), commitDataCid: commit.data.toString(), + repoRev: commit.rev ?? null, rebaseCount: details.rebase ? 1 : 0, tooBigCount: details.tooBig ? 1 : 0, }) @@ -217,6 +261,7 @@ export class IndexingService { return oc.column('did').doUpdateSet({ commitCid: sql`${excluded('commitCid')}`, commitDataCid: sql`${excluded('commitDataCid')}`, + repoRev: sql`${excluded('repoRev')}`, rebaseCount: sql`${sync('rebaseCount')} + ${excluded('rebaseCount')}`, tooBigCount: sql`${sync('tooBigCount')} + ${excluded('tooBigCount')}`, }) @@ -260,10 +305,10 @@ export class IndexingService { if (!pds) return false const { api } = new AtpAgent({ service: pds }) try { - await retryHttp(() => api.com.atproto.sync.getHead({ did })) + await retryHttp(() => api.com.atproto.sync.getLatestCommit({ did })) return true } catch (err) { - if (err instanceof ComAtprotoSyncGetHead.HeadNotFoundError) { + if (err instanceof ComAtprotoSyncGetLatestCommit.RepoNotFoundError) { return false } return null @@ -292,6 +337,10 @@ export class IndexingService { .deleteFrom('actor_block') .where('creator', '=', did) .execute() + await this.db.db + .deleteFrom('list_block') + .where('creator', '=', did) + .execute() // posts const postByUser = (qb) => qb @@ -330,11 +379,70 @@ export class IndexingService { } } -function* walkContentsWithCids(contents: RepoContentsWithCids) { - for (const collection of Object.keys(contents)) { - for (const rkey of Object.keys(contents[collection])) { - const { cid, value } = contents[collection][rkey] - yield { collection, rkey, cid, record: value } +type UriAndCid = { + uri: AtUri + cid: CID +} + +type RecordDescript = UriAndCid & { + value: unknown +} + +type IndexOp = + | ({ + op: 'create' | 'update' + } & RecordDescript) + | ({ op: 'delete' } & UriAndCid) + +const findDiffFromCheckout = ( + curr: Record, + checkout: Record, +): IndexOp[] => { + const ops: IndexOp[] = [] + for (const uri of Object.keys(checkout)) { + const record = checkout[uri] + if (!curr[uri]) { + ops.push({ op: 'create', ...record }) + } else { + if (curr[uri].cid.equals(record.cid)) { + // no-op + continue + } + ops.push({ op: 'update', ...record }) + } + } + for (const uri of Object.keys(curr)) { + const record = curr[uri] + if (!checkout[uri]) { + ops.push({ op: 'delete', ...record }) + } + } + return ops +} + +const formatCheckout = ( + did: string, + verifiedRepo: VerifiedRepo, +): Record => { + const records: Record = {} + for (const create of verifiedRepo.creates) { + const uri = AtUri.make(did, create.collection, create.rkey) + records[uri.toString()] = { + uri, + cid: create.cid, + value: create.record, } } + return records +} + +const needsHandleReindex = (actor: Actor | undefined, timestamp: string) => { + if (!actor) return true + const timeDiff = + new Date(timestamp).getTime() - new Date(actor.indexedAt).getTime() + // revalidate daily + if (timeDiff > DAY) return true + // revalidate more aggressively for invalidated handles + if (actor.handle === null && timeDiff > HOUR) return true + return false } diff --git a/packages/bsky/src/services/indexing/plugins/block.ts b/packages/bsky/src/services/indexing/plugins/block.ts index 1d3c3850c9d..c0c8fe9d770 100644 --- a/packages/bsky/src/services/indexing/plugins/block.ts +++ b/packages/bsky/src/services/indexing/plugins/block.ts @@ -1,13 +1,14 @@ import { Selectable } from 'kysely' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { CID } from 'multiformats/cid' import * as Block from '../../../lexicon/types/app/bsky/graph/block' import * as lex from '../../../lexicon/lexicons' import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' import RecordProcessor from '../processor' import { toSimplifiedISOSafe } from '../util' -import Database from '../../../db' +import { PrimaryDatabase } from '../../../db' import { BackgroundQueue } from '../../../background' +import { NotificationServer } from '../../../notifications' const lexId = lex.ids.AppBskyGraphBlock type IndexedBlock = Selectable @@ -72,10 +73,11 @@ const notifsForDelete = () => { export type PluginType = RecordProcessor export const makePlugin = ( - db: Database, + db: PrimaryDatabase, backgroundQueue: BackgroundQueue, + notifServer?: NotificationServer, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, { + return new RecordProcessor(db, backgroundQueue, notifServer, { lexId, insertFn, findDuplicate, diff --git a/packages/bsky/src/services/indexing/plugins/feed-generator.ts b/packages/bsky/src/services/indexing/plugins/feed-generator.ts index 18812f21b4b..a0c32ff9129 100644 --- a/packages/bsky/src/services/indexing/plugins/feed-generator.ts +++ b/packages/bsky/src/services/indexing/plugins/feed-generator.ts @@ -1,13 +1,14 @@ import { Selectable } from 'kysely' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { CID } from 'multiformats/cid' import * as FeedGenerator from '../../../lexicon/types/app/bsky/feed/generator' import * as lex from '../../../lexicon/lexicons' -import Database from '../../../db' +import { PrimaryDatabase } from '../../../db' import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' import { BackgroundQueue } from '../../../background' import RecordProcessor from '../processor' import { toSimplifiedISOSafe } from '../util' +import { NotificationServer } from '../../../notifications' const lexId = lex.ids.AppBskyFeedGenerator type IndexedFeedGenerator = Selectable @@ -71,10 +72,11 @@ export type PluginType = RecordProcessor< > export const makePlugin = ( - db: Database, + db: PrimaryDatabase, backgroundQueue: BackgroundQueue, + notifServer?: NotificationServer, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, { + return new RecordProcessor(db, backgroundQueue, notifServer, { lexId, insertFn, findDuplicate, diff --git a/packages/bsky/src/services/indexing/plugins/follow.ts b/packages/bsky/src/services/indexing/plugins/follow.ts index eca46e4be36..d6cf1996f98 100644 --- a/packages/bsky/src/services/indexing/plugins/follow.ts +++ b/packages/bsky/src/services/indexing/plugins/follow.ts @@ -1,14 +1,15 @@ import { Selectable } from 'kysely' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { CID } from 'multiformats/cid' import * as Follow from '../../../lexicon/types/app/bsky/graph/follow' import * as lex from '../../../lexicon/lexicons' import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' import RecordProcessor from '../processor' import { toSimplifiedISOSafe } from '../util' -import Database from '../../../db' +import { PrimaryDatabase } from '../../../db' import { countAll, excluded } from '../../../db/util' import { BackgroundQueue } from '../../../background' +import { NotificationServer } from '../../../notifications' const lexId = lex.ids.AppBskyGraphFollow type IndexedFollow = Selectable @@ -59,7 +60,7 @@ const notifsForInsert = (obj: IndexedFollow) => { recordCid: obj.cid, reason: 'follow' as const, reasonSubject: null, - sortAt: obj.indexedAt, + sortAt: obj.sortAt, }, ] } @@ -119,10 +120,11 @@ const updateAggregates = async (db: DatabaseSchema, follow: IndexedFollow) => { export type PluginType = RecordProcessor export const makePlugin = ( - db: Database, + db: PrimaryDatabase, backgroundQueue: BackgroundQueue, + notifServer?: NotificationServer, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, { + return new RecordProcessor(db, backgroundQueue, notifServer, { lexId, insertFn, findDuplicate, diff --git a/packages/bsky/src/services/indexing/plugins/like.ts b/packages/bsky/src/services/indexing/plugins/like.ts index d92cf2e6b2e..7899b48b1fd 100644 --- a/packages/bsky/src/services/indexing/plugins/like.ts +++ b/packages/bsky/src/services/indexing/plugins/like.ts @@ -1,5 +1,5 @@ import { Selectable } from 'kysely' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { CID } from 'multiformats/cid' import * as Like from '../../../lexicon/types/app/bsky/feed/like' import * as lex from '../../../lexicon/lexicons' @@ -7,8 +7,9 @@ import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' import RecordProcessor from '../processor' import { toSimplifiedISOSafe } from '../util' import { countAll, excluded } from '../../../db/util' -import Database from '../../../db' +import { PrimaryDatabase } from '../../../db' import { BackgroundQueue } from '../../../background' +import { NotificationServer } from '../../../notifications' const lexId = lex.ids.AppBskyFeedLike type IndexedLike = Selectable @@ -53,17 +54,21 @@ const findDuplicate = async ( const notifsForInsert = (obj: IndexedLike) => { const subjectUri = new AtUri(obj.subject) - return [ - { - did: subjectUri.host, - author: obj.creator, - recordUri: obj.uri, - recordCid: obj.cid, - reason: 'like' as const, - reasonSubject: subjectUri.toString(), - sortAt: obj.indexedAt, - }, - ] + // prevent self-notifications + const isSelf = subjectUri.host === obj.creator + return isSelf + ? [] + : [ + { + did: subjectUri.host, + author: obj.creator, + recordUri: obj.uri, + recordCid: obj.cid, + reason: 'like' as const, + reasonSubject: subjectUri.toString(), + sortAt: obj.sortAt, + }, + ] } const deleteFn = async ( @@ -105,10 +110,11 @@ const updateAggregates = async (db: DatabaseSchema, like: IndexedLike) => { export type PluginType = RecordProcessor export const makePlugin = ( - db: Database, + db: PrimaryDatabase, backgroundQueue: BackgroundQueue, + notifServer?: NotificationServer, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, { + return new RecordProcessor(db, backgroundQueue, notifServer, { lexId, insertFn, findDuplicate, diff --git a/packages/bsky/src/services/indexing/plugins/list-block.ts b/packages/bsky/src/services/indexing/plugins/list-block.ts new file mode 100644 index 00000000000..4285ca8d4bc --- /dev/null +++ b/packages/bsky/src/services/indexing/plugins/list-block.ts @@ -0,0 +1,90 @@ +import { Selectable } from 'kysely' +import { AtUri } from '@atproto/syntax' +import { CID } from 'multiformats/cid' +import * as ListBlock from '../../../lexicon/types/app/bsky/graph/listblock' +import * as lex from '../../../lexicon/lexicons' +import { PrimaryDatabase } from '../../../db' +import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' +import RecordProcessor from '../processor' +import { BackgroundQueue } from '../../../background' +import { NotificationServer } from '../../../notifications' +import { toSimplifiedISOSafe } from '../util' + +const lexId = lex.ids.AppBskyGraphListblock +type IndexedListBlock = Selectable + +const insertFn = async ( + db: DatabaseSchema, + uri: AtUri, + cid: CID, + obj: ListBlock.Record, + timestamp: string, +): Promise => { + const inserted = await db + .insertInto('list_block') + .values({ + uri: uri.toString(), + cid: cid.toString(), + creator: uri.host, + subjectUri: obj.subject, + createdAt: toSimplifiedISOSafe(obj.createdAt), + indexedAt: timestamp, + }) + .onConflict((oc) => oc.doNothing()) + .returningAll() + .executeTakeFirst() + return inserted || null +} + +const findDuplicate = async ( + db: DatabaseSchema, + uri: AtUri, + obj: ListBlock.Record, +): Promise => { + const found = await db + .selectFrom('list_block') + .where('creator', '=', uri.host) + .where('subjectUri', '=', obj.subject) + .selectAll() + .executeTakeFirst() + return found ? new AtUri(found.uri) : null +} + +const notifsForInsert = () => { + return [] +} + +const deleteFn = async ( + db: DatabaseSchema, + uri: AtUri, +): Promise => { + const deleted = await db + .deleteFrom('list_block') + .where('uri', '=', uri.toString()) + .returningAll() + .executeTakeFirst() + return deleted || null +} + +const notifsForDelete = () => { + return { notifs: [], toDelete: [] } +} + +export type PluginType = RecordProcessor + +export const makePlugin = ( + db: PrimaryDatabase, + backgroundQueue: BackgroundQueue, + notifServer?: NotificationServer, +): PluginType => { + return new RecordProcessor(db, backgroundQueue, notifServer, { + lexId, + insertFn, + findDuplicate, + deleteFn, + notifsForInsert, + notifsForDelete, + }) +} + +export default makePlugin diff --git a/packages/bsky/src/services/indexing/plugins/list-item.ts b/packages/bsky/src/services/indexing/plugins/list-item.ts index f28fc8bf62c..231fb761e16 100644 --- a/packages/bsky/src/services/indexing/plugins/list-item.ts +++ b/packages/bsky/src/services/indexing/plugins/list-item.ts @@ -1,5 +1,5 @@ import { Selectable } from 'kysely' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { CID } from 'multiformats/cid' import * as ListItem from '../../../lexicon/types/app/bsky/graph/listitem' import * as lex from '../../../lexicon/lexicons' @@ -7,8 +7,9 @@ import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' import RecordProcessor from '../processor' import { toSimplifiedISOSafe } from '../util' import { InvalidRequestError } from '@atproto/xrpc-server' -import Database from '../../../db' +import { PrimaryDatabase } from '../../../db' import { BackgroundQueue } from '../../../background' +import { NotificationServer } from '../../../notifications' const lexId = lex.ids.AppBskyGraphListitem type IndexedListItem = Selectable @@ -80,10 +81,11 @@ const notifsForDelete = () => { export type PluginType = RecordProcessor export const makePlugin = ( - db: Database, + db: PrimaryDatabase, backgroundQueue: BackgroundQueue, + notifServer?: NotificationServer, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, { + return new RecordProcessor(db, backgroundQueue, notifServer, { lexId, insertFn, findDuplicate, diff --git a/packages/bsky/src/services/indexing/plugins/list.ts b/packages/bsky/src/services/indexing/plugins/list.ts index 9ba8444eaf7..c74c09c274f 100644 --- a/packages/bsky/src/services/indexing/plugins/list.ts +++ b/packages/bsky/src/services/indexing/plugins/list.ts @@ -1,13 +1,14 @@ import { Selectable } from 'kysely' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { CID } from 'multiformats/cid' import * as List from '../../../lexicon/types/app/bsky/graph/list' import * as lex from '../../../lexicon/lexicons' import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' import RecordProcessor from '../processor' import { toSimplifiedISOSafe } from '../util' -import Database from '../../../db' +import { PrimaryDatabase } from '../../../db' import { BackgroundQueue } from '../../../background' +import { NotificationServer } from '../../../notifications' const lexId = lex.ids.AppBskyGraphList type IndexedList = Selectable @@ -68,10 +69,11 @@ const notifsForDelete = () => { export type PluginType = RecordProcessor export const makePlugin = ( - db: Database, + db: PrimaryDatabase, backgroundQueue: BackgroundQueue, + notifServer?: NotificationServer, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, { + return new RecordProcessor(db, backgroundQueue, notifServer, { lexId, insertFn, findDuplicate, diff --git a/packages/bsky/src/services/indexing/plugins/post.ts b/packages/bsky/src/services/indexing/plugins/post.ts index 35028020069..7ce431fdcd8 100644 --- a/packages/bsky/src/services/indexing/plugins/post.ts +++ b/packages/bsky/src/services/indexing/plugins/post.ts @@ -1,6 +1,6 @@ import { Insertable, Selectable, sql } from 'kysely' import { CID } from 'multiformats/cid' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { Record as PostRecord } from '../../../lexicon/types/app/bsky/feed/post' import { isMain as isEmbedImage } from '../../../lexicon/types/app/bsky/embed/images' import { isMain as isEmbedExternal } from '../../../lexicon/types/app/bsky/embed/external' @@ -13,28 +13,42 @@ import { import * as lex from '../../../lexicon/lexicons' import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' import RecordProcessor from '../processor' -import { PostHierarchy } from '../../../db/tables/post-hierarchy' import { Notification } from '../../../db/tables/notification' import { toSimplifiedISOSafe } from '../util' -import Database from '../../../db' +import { PrimaryDatabase } from '../../../db' import { countAll, excluded } from '../../../db/util' import { BackgroundQueue } from '../../../background' +import { getAncestorsAndSelfQb, getDescendentsQb } from '../../util/post' +import { NotificationServer } from '../../../notifications' type Notif = Insertable type Post = Selectable type PostEmbedImage = DatabaseSchemaType['post_embed_image'] type PostEmbedExternal = DatabaseSchemaType['post_embed_external'] type PostEmbedRecord = DatabaseSchemaType['post_embed_record'] +type PostAncestor = { + uri: string + height: number +} +type PostDescendent = { + uri: string + depth: number + cid: string + creator: string + sortAt: string +} type IndexedPost = { post: Post facets?: { type: 'mention' | 'link'; value: string }[] embeds?: (PostEmbedImage[] | PostEmbedExternal | PostEmbedRecord)[] - ancestors?: PostHierarchy[] - descendents?: IndexedPost[] + ancestors?: PostAncestor[] + descendents?: PostDescendent[] } const lexId = lex.ids.AppBskyFeedPost +const REPLY_NOTIF_DEPTH = 5 + const insertFn = async ( db: DatabaseSchema, uri: AtUri, @@ -135,94 +149,30 @@ const insertFn = async ( await db.insertInto('post_embed_record').values(recordEmbed).execute() } } - // Thread indexing - // Concurrent, out-of-order updates are difficult, so we essentially - // take a lock on the thread for indexing, by locking the thread's root post. - await db - .selectFrom('post') - .forUpdate() + const ancestors = await getAncestorsAndSelfQb(db, { + uri: post.uri, + parentHeight: REPLY_NOTIF_DEPTH, + }) + .selectFrom('ancestor') .selectAll() - .where('uri', '=', post.replyRoot ?? post.uri) - .execute() - - // Track the minimum we know about the post hierarchy: the post and its parent. - // Importantly, this works even if the parent hasn't been indexed yet. - const minimalPostHierarchy = [ - { - uri: post.uri, - ancestorUri: post.uri, - depth: 0, - }, - ] - if (post.replyParent) { - minimalPostHierarchy.push({ - uri: post.uri, - ancestorUri: post.replyParent, - depth: 1, - }) - } - const ancestors = await db - .insertInto('post_hierarchy') - .values(minimalPostHierarchy) - .onConflict((oc) => oc.doNothing()) - .returningAll() .execute() - - // Copy all the parent's relations down to the post. - // It's possible that the parent hasn't been indexed yet and this will no-op. - if (post.replyParent) { - const deepAncestors = await db - .insertInto('post_hierarchy') - .columns(['uri', 'ancestorUri', 'depth']) - .expression( - db - .selectFrom('post_hierarchy as parent_hierarchy') - .where('parent_hierarchy.uri', '=', post.replyParent) - .select([ - sql`${post.uri}`.as('uri'), - 'ancestorUri', - sql`depth + 1`.as('depth'), - ]), - ) - .onConflict((oc) => oc.doNothing()) - .returningAll() - .execute() - ancestors.push(...deepAncestors) - } - - // Copy all post's relations down to its descendents. This ensures - // that out-of-order indexing (i.e. descendent before parent) is resolved. - const descendents = await db - .insertInto('post_hierarchy') - .columns(['uri', 'ancestorUri', 'depth']) - .expression( - db - .selectFrom('post_hierarchy as target') - .innerJoin( - 'post_hierarchy as source', - 'source.ancestorUri', - 'target.uri', - ) - .where('target.uri', '=', post.uri) - .select([ - 'source.uri as uri', - 'target.ancestorUri as ancestorUri', - sql`source.depth + target.depth`.as('depth'), - ]), - ) - .onConflict((oc) => oc.doNothing()) - .returningAll() + const descendents = await getDescendentsQb(db, { + uri: post.uri, + depth: REPLY_NOTIF_DEPTH, + }) + .selectFrom('descendent') + .innerJoin('post', 'post.uri', 'descendent.uri') + .selectAll('descendent') + .select(['cid', 'creator', 'sortAt']) .execute() - return { post: insertedPost, facets, embeds, ancestors, - descendents: await collateDescendents(db, descendents), + descendents, } - // return { post: insertedPost, facets, embeds, ancestors } } const findDuplicate = async (): Promise => { @@ -246,7 +196,7 @@ const notifsForInsert = (obj: IndexedPost) => { author: obj.post.creator, recordUri: obj.post.uri, recordCid: obj.post.cid, - sortAt: obj.post.indexedAt, + sortAt: obj.post.sortAt, }) } } @@ -261,20 +211,16 @@ const notifsForInsert = (obj: IndexedPost) => { author: obj.post.creator, recordUri: obj.post.uri, recordCid: obj.post.cid, - sortAt: obj.post.indexedAt, + sortAt: obj.post.sortAt, }) } } } - const ancestors = (obj.ancestors ?? []) - .filter((a) => a.depth > 0) // no need to notify self - .sort((a, b) => a.depth - b.depth) - const BLESSED_HELL_THREAD = - 'at://did:plc:wgaezxqi2spqm3mhrb5xvkzi/app.bsky.feed.post/3juzlwllznd24' - for (const relation of ancestors) { - if (relation.depth < 5 || obj.post.replyRoot === BLESSED_HELL_THREAD) { - const ancestorUri = new AtUri(relation.ancestorUri) + for (const ancestor of obj.ancestors ?? []) { + if (ancestor.uri === obj.post.uri) continue // no need to notify for own post + if (ancestor.height < REPLY_NOTIF_DEPTH) { + const ancestorUri = new AtUri(ancestor.uri) maybeNotify({ did: ancestorUri.host, reason: 'reply', @@ -282,15 +228,28 @@ const notifsForInsert = (obj: IndexedPost) => { author: obj.post.creator, recordUri: obj.post.uri, recordCid: obj.post.cid, - sortAt: obj.post.indexedAt, + sortAt: obj.post.sortAt, }) } } - if (obj.descendents) { - // May generate notifications for out-of-order indexing of replies - for (const descendent of obj.descendents) { - notifs.push(...notifsForInsert(descendent)) + // descendents indicate out-of-order indexing: need to notify + // the current post and upwards. + for (const descendent of obj.descendents ?? []) { + for (const ancestor of obj.ancestors ?? []) { + const totalHeight = descendent.depth + ancestor.height + if (totalHeight < REPLY_NOTIF_DEPTH) { + const ancestorUri = new AtUri(ancestor.uri) + maybeNotify({ + did: ancestorUri.host, + reason: 'reply', + reasonSubject: ancestorUri.toString(), + author: descendent.creator, + recordUri: descendent.uri, + recordCid: descendent.cid, + sortAt: descendent.sortAt, + }) + } } } @@ -341,19 +300,11 @@ const deleteFn = async ( if (deletedPosts) { deletedEmbeds.push(deletedPosts) } - // Do not delete, maintain thread hierarchy even if post no longer exists - const ancestors = await db - .selectFrom('post_hierarchy') - .where('uri', '=', uriStr) - .where('depth', '>', 0) - .selectAll() - .execute() return deleted ? { post: deleted, facets: [], // Not used embeds: deletedEmbeds, - ancestors, } : null } @@ -404,10 +355,11 @@ const updateAggregates = async (db: DatabaseSchema, postIdx: IndexedPost) => { export type PluginType = RecordProcessor export const makePlugin = ( - db: Database, + db: PrimaryDatabase, backgroundQueue: BackgroundQueue, + notifServer?: NotificationServer, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, { + return new RecordProcessor(db, backgroundQueue, notifServer, { lexId, insertFn, findDuplicate, @@ -429,29 +381,3 @@ function separateEmbeds(embed: PostRecord['embed']) { } return [embed] } - -async function collateDescendents( - db: DatabaseSchema, - descendents: PostHierarchy[], -): Promise { - if (!descendents.length) return - - const ancestorsByUri = descendents.reduce((acc, descendent) => { - acc[descendent.uri] ??= [] - acc[descendent.uri].push(descendent) - return acc - }, {} as Record) - - const descendentPosts = await db - .selectFrom('post') - .selectAll() - .where('uri', 'in', Object.keys(ancestorsByUri)) - .execute() - - return descendentPosts.map((post) => { - return { - post, - ancestors: ancestorsByUri[post.uri], - } - }) -} diff --git a/packages/bsky/src/services/indexing/plugins/profile.ts b/packages/bsky/src/services/indexing/plugins/profile.ts index 8f67aad0ae2..ea0c8f07f98 100644 --- a/packages/bsky/src/services/indexing/plugins/profile.ts +++ b/packages/bsky/src/services/indexing/plugins/profile.ts @@ -1,11 +1,12 @@ -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { CID } from 'multiformats/cid' import * as Profile from '../../../lexicon/types/app/bsky/actor/profile' import * as lex from '../../../lexicon/lexicons' import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' import RecordProcessor from '../processor' -import Database from '../../../db' +import { PrimaryDatabase } from '../../../db' import { BackgroundQueue } from '../../../background' +import { NotificationServer } from '../../../notifications' const lexId = lex.ids.AppBskyActorProfile type IndexedProfile = DatabaseSchemaType['profile'] @@ -63,10 +64,11 @@ const notifsForDelete = () => { export type PluginType = RecordProcessor export const makePlugin = ( - db: Database, + db: PrimaryDatabase, backgroundQueue: BackgroundQueue, + notifServer?: NotificationServer, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, { + return new RecordProcessor(db, backgroundQueue, notifServer, { lexId, insertFn, findDuplicate, diff --git a/packages/bsky/src/services/indexing/plugins/repost.ts b/packages/bsky/src/services/indexing/plugins/repost.ts index 679d94e0972..aa93d7b0f61 100644 --- a/packages/bsky/src/services/indexing/plugins/repost.ts +++ b/packages/bsky/src/services/indexing/plugins/repost.ts @@ -1,14 +1,15 @@ import { Selectable } from 'kysely' import { CID } from 'multiformats/cid' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import * as Repost from '../../../lexicon/types/app/bsky/feed/repost' import * as lex from '../../../lexicon/lexicons' import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' import RecordProcessor from '../processor' import { toSimplifiedISOSafe } from '../util' -import Database from '../../../db' +import { PrimaryDatabase } from '../../../db' import { countAll, excluded } from '../../../db/util' import { BackgroundQueue } from '../../../background' +import { NotificationServer } from '../../../notifications' const lexId = lex.ids.AppBskyFeedRepost type IndexedRepost = Selectable @@ -72,17 +73,21 @@ const findDuplicate = async ( const notifsForInsert = (obj: IndexedRepost) => { const subjectUri = new AtUri(obj.subject) - return [ - { - did: subjectUri.host, - author: obj.creator, - recordUri: obj.uri, - recordCid: obj.cid, - reason: 'repost' as const, - reasonSubject: subjectUri.toString(), - sortAt: obj.indexedAt, - }, - ] + // prevent self-notifications + const isSelf = subjectUri.host === obj.creator + return isSelf + ? [] + : [ + { + did: subjectUri.host, + author: obj.creator, + recordUri: obj.uri, + recordCid: obj.cid, + reason: 'repost' as const, + reasonSubject: subjectUri.toString(), + sortAt: obj.sortAt, + }, + ] } const deleteFn = async ( @@ -130,10 +135,11 @@ const updateAggregates = async (db: DatabaseSchema, repost: IndexedRepost) => { export type PluginType = RecordProcessor export const makePlugin = ( - db: Database, + db: PrimaryDatabase, backgroundQueue: BackgroundQueue, + notifServer?: NotificationServer, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, { + return new RecordProcessor(db, backgroundQueue, notifServer, { lexId, insertFn, findDuplicate, diff --git a/packages/bsky/src/services/indexing/processor.ts b/packages/bsky/src/services/indexing/processor.ts index fcce53fb15c..2a02c61125e 100644 --- a/packages/bsky/src/services/indexing/processor.ts +++ b/packages/bsky/src/services/indexing/processor.ts @@ -1,13 +1,15 @@ import { Insertable } from 'kysely' import { CID } from 'multiformats/cid' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { jsonStringToLex, stringifyLex } from '@atproto/lexicon' import DatabaseSchema from '../../db/database-schema' import { lexicons } from '../../lexicon/lexicons' import { Notification } from '../../db/tables/notification' import { chunkArray } from '@atproto/common' -import Database from '../../db' +import { PrimaryDatabase } from '../../db' import { BackgroundQueue } from '../../background' +import { NotificationServer } from '../../notifications' +import { dbLogger } from '../../logger' // @NOTE re: insertions and deletions. Due to how record updates are handled, // (insertFn) should have the same effect as (insertFn -> deleteFn -> insertFn). @@ -40,8 +42,9 @@ export class RecordProcessor { collection: string db: DatabaseSchema constructor( - private appDb: Database, + private appDb: PrimaryDatabase, private backgroundQueue: BackgroundQueue, + private notifServer: NotificationServer | undefined, private params: RecordProcessorParams, ) { this.db = appDb.db @@ -61,7 +64,13 @@ export class RecordProcessor { lexicons.assertValidRecord(this.params.lexId, obj) } - async insertRecord(uri: AtUri, cid: CID, obj: unknown, timestamp: string) { + async insertRecord( + uri: AtUri, + cid: CID, + obj: unknown, + timestamp: string, + opts?: { disableNotifs?: boolean }, + ) { this.assertValidRecord(obj) await this.db .insertInto('record') @@ -83,7 +92,9 @@ export class RecordProcessor { ) if (inserted) { this.aggregateOnCommit(inserted) - await this.handleNotifs({ inserted }) + if (!opts?.disableNotifs) { + await this.handleNotifs({ inserted }) + } return } // if duplicate, insert into duplicates table with no events @@ -106,7 +117,13 @@ export class RecordProcessor { // for the uri then replace it. The main upside is that this allows the indexer // for each collection to avoid bespoke logic for in-place updates, which isn't // straightforward in the general case. We still get nice control over notifications. - async updateRecord(uri: AtUri, cid: CID, obj: unknown, timestamp: string) { + async updateRecord( + uri: AtUri, + cid: CID, + obj: unknown, + timestamp: string, + opts?: { disableNotifs?: boolean }, + ) { this.assertValidRecord(obj) await this.db .updateTable('record') @@ -155,7 +172,9 @@ export class RecordProcessor { ) } this.aggregateOnCommit(inserted) - await this.handleNotifs({ inserted, deleted }) + if (!opts?.disableNotifs) { + await this.handleNotifs({ inserted, deleted }) + } } async deleteRecord(uri: AtUri, cascading = false) { @@ -209,7 +228,8 @@ export class RecordProcessor { async handleNotifs(op: { deleted?: S; inserted?: S }) { let notifs: Notif[] = [] - const runOnCommit: ((db: Database) => Promise)[] = [] + const runOnCommit: ((db: PrimaryDatabase) => Promise)[] = [] + const sendOnCommit: (() => Promise)[] = [] if (op.deleted) { const forDelete = this.params.notifsForDelete( op.deleted, @@ -233,6 +253,17 @@ export class RecordProcessor { runOnCommit.push(async (db) => { await db.db.insertInto('notification').values(chunk).execute() }) + if (this.notifServer) { + const notifServer = this.notifServer + sendOnCommit.push(async () => { + try { + const preparedNotifs = await notifServer.prepareNotifsToSend(chunk) + await notifServer.processNotifications(preparedNotifs) + } catch (error) { + dbLogger.error({ error }, 'error sending push notifications') + } + }) + } } if (runOnCommit.length) { // Need to ensure notif deletion always happens before creation, otherwise delete may clobber in a race. @@ -244,6 +275,16 @@ export class RecordProcessor { }) }) } + if (sendOnCommit.length) { + // Need to ensure notif deletion always happens before creation, otherwise delete may clobber in a race. + this.appDb.onCommit(() => { + this.backgroundQueue.add(async () => { + for (const fn of sendOnCommit) { + await fn() + } + }) + }) + } } aggregateOnCommit(indexed: S) { diff --git a/packages/bsky/src/services/indexing/util.ts b/packages/bsky/src/services/indexing/util.ts index d0a5755d589..5ce78959f8f 100644 --- a/packages/bsky/src/services/indexing/util.ts +++ b/packages/bsky/src/services/indexing/util.ts @@ -1,3 +1,5 @@ +import { isValidISODateString } from 'iso-datestring-validator' + // Normalize date strings to simplified ISO so that the lexical sort preserves temporal sort. // Rather than failing on an invalid date format, returns valid unix epoch. export function toSimplifiedISOSafe(dateStr: string) { @@ -5,5 +7,10 @@ export function toSimplifiedISOSafe(dateStr: string) { if (isNaN(date.getTime())) { return new Date(0).toISOString() } - return date.toISOString() // YYYY-MM-DDTHH:mm:ss.sssZ + const iso = date.toISOString() + if (!isValidISODateString(iso)) { + // Occurs in rare cases, e.g. where resulting UTC year is negative. These also don't preserve lexical sort. + return new Date(0).toISOString() + } + return iso // YYYY-MM-DDTHH:mm:ss.sssZ } diff --git a/packages/bsky/src/services/label/index.ts b/packages/bsky/src/services/label/index.ts index b979799f126..855038ab14c 100644 --- a/packages/bsky/src/services/label/index.ts +++ b/packages/bsky/src/services/label/index.ts @@ -1,16 +1,18 @@ -import { AtUri } from '@atproto/uri' -import Database from '../../db' -import { Label } from '../../lexicon/types/com/atproto/label/defs' -import { ids } from '../../lexicon/lexicons' import { sql } from 'kysely' +import { AtUri } from '@atproto/syntax' +import { Database } from '../../db' +import { Label, isSelfLabels } from '../../lexicon/types/com/atproto/label/defs' +import { ids } from '../../lexicon/lexicons' +import { toSimplifiedISOSafe } from '../indexing/util' +import { LabelCache } from '../../label-cache' export type Labels = Record export class LabelService { - constructor(public db: Database) {} + constructor(public db: Database, public cache: LabelCache | null) {} - static creator() { - return (db: Database) => new LabelService(db) + static creator(cache: LabelCache | null) { + return (db: Database) => new LabelService(db, cache) } async formatAndCreate( @@ -18,7 +20,7 @@ export class LabelService { uri: string, cid: string | null, labels: { create?: string[]; negate?: string[] }, - ) { + ): Promise { const { create = [], negate = [] } = labels const toCreate = create.map((val) => ({ src, @@ -36,7 +38,9 @@ export class LabelService { neg: true, cts: new Date().toISOString(), })) - await this.createLabels([...toCreate, ...toNegate]) + const formatted = [...toCreate, ...toNegate] + await this.createLabels(formatted) + return formatted } async createLabels(labels: Label[]) { @@ -48,8 +52,9 @@ export class LabelService { })) const { ref } = this.db.db.dynamic const excluded = (col: string) => ref(`excluded.${col}`) - await this.db.db - .insertInto('label') + await this.db + .asPrimary() + .db.insertInto('label') .values(dbVals) .onConflict((oc) => oc.columns(['src', 'uri', 'cid', 'val']).doUpdateSet({ @@ -62,15 +67,21 @@ export class LabelService { async getLabelsForUris( subjects: string[], - includeNeg?: boolean, + opts?: { + includeNeg?: boolean + skipCache?: boolean + }, ): Promise { if (subjects.length < 1) return {} - const res = await this.db.db - .selectFrom('label') - .where('label.uri', 'in', subjects) - .if(!includeNeg, (qb) => qb.where('neg', '=', false)) - .selectAll() - .execute() + const res = + this.cache === null || opts?.skipCache + ? await this.db.db + .selectFrom('label') + .where('label.uri', 'in', subjects) + .if(!opts?.includeNeg, (qb) => qb.where('neg', '=', false)) + .selectAll() + .execute() + : this.cache.forSubjects(subjects, opts?.includeNeg) return res.reduce((acc, cur) => { acc[cur.uri] ??= [] acc[cur.uri].push({ @@ -85,10 +96,15 @@ export class LabelService { // gets labels for any record. when did is present, combine labels for both did & profile record. async getLabelsForSubjects( subjects: string[], - includeNeg?: boolean, + opts?: { + includeNeg?: boolean + skipCache?: boolean + }, + labels: Labels = {}, ): Promise { - if (subjects.length < 1) return {} + if (subjects.length < 1) return labels const expandedSubjects = subjects.flatMap((subject) => { + if (labels[subject]) return [] // skip over labels we already have fetched if (subject.startsWith('did:')) { return [ subject, @@ -97,8 +113,8 @@ export class LabelService { } return subject }) - const labels = await this.getLabelsForUris(expandedSubjects, includeNeg) - return Object.keys(labels).reduce((acc, cur) => { + const labelsByUri = await this.getLabelsForUris(expandedSubjects, opts) + return Object.keys(labelsByUri).reduce((acc, cur) => { const uri = cur.startsWith('at://') ? new AtUri(cur) : null if ( uri && @@ -108,24 +124,51 @@ export class LabelService { // combine labels for profile + did const did = uri.hostname acc[did] ??= [] - acc[did].push(...labels[cur]) + acc[did].push(...labelsByUri[cur]) } acc[cur] ??= [] - acc[cur].push(...labels[cur]) + acc[cur].push(...labelsByUri[cur]) return acc - }, {} as Labels) + }, labels) } - async getLabels(subject: string, includeNeg?: boolean): Promise { - const labels = await this.getLabelsForUris([subject], includeNeg) + async getLabels( + subject: string, + opts?: { + includeNeg?: boolean + skipCache?: boolean + }, + ): Promise { + const labels = await this.getLabelsForUris([subject], opts) return labels[subject] ?? [] } async getLabelsForProfile( did: string, - includeNeg?: boolean, + opts?: { + includeNeg?: boolean + skipCache?: boolean + }, ): Promise { - const labels = await this.getLabelsForSubjects([did], includeNeg) + const labels = await this.getLabelsForSubjects([did], opts) return labels[did] ?? [] } } + +export function getSelfLabels(details: { + uri: string | null + cid: string | null + record: Record | null +}): Label[] { + const { uri, cid, record } = details + if (!uri || !cid || !record) return [] + if (!isSelfLabels(record.labels)) return [] + const src = new AtUri(uri).host // record creator + const cts = + typeof record.createdAt === 'string' + ? toSimplifiedISOSafe(record.createdAt) + : new Date(0).toISOString() + return record.labels.values.map(({ val }) => { + return { src, uri, cid, val, cts, neg: false } + }) +} diff --git a/packages/bsky/src/services/moderation/index.ts b/packages/bsky/src/services/moderation/index.ts index 4d86b4fa9b3..0abf8f348eb 100644 --- a/packages/bsky/src/services/moderation/index.ts +++ b/packages/bsky/src/services/moderation/index.ts @@ -1,16 +1,18 @@ import { Selectable, sql } from 'kysely' import { CID } from 'multiformats/cid' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { InvalidRequestError } from '@atproto/xrpc-server' -import Database from '../../db' +import { PrimaryDatabase } from '../../db' import { ModerationAction, ModerationReport } from '../../db/tables/moderation' import { ModerationViews } from './views' import { ImageUriBuilder } from '../../image/uri' import { ImageInvalidator } from '../../image/invalidator' +import { TAKEDOWN } from '../../lexicon/types/com/atproto/admin/defs' +import { addHoursToDate } from '../../util/date' export class ModerationService { constructor( - public db: Database, + public db: PrimaryDatabase, public imgUriBuilder: ImageUriBuilder, public imgInvalidator: ImageInvalidator, ) {} @@ -19,7 +21,7 @@ export class ModerationService { imgUriBuilder: ImageUriBuilder, imgInvalidator: ImageInvalidator, ) { - return (db: Database) => + return (db: PrimaryDatabase) => new ModerationService(db, imgUriBuilder, imgInvalidator) } @@ -84,6 +86,7 @@ export class ModerationService { ignoreSubjects?: string[] reverse?: boolean reporters?: string[] + actionedBy?: string }): Promise { const { subject, @@ -94,6 +97,7 @@ export class ModerationService { ignoreSubjects, reverse = false, reporters, + actionedBy, } = opts const { ref } = this.db.db.dynamic let builder = this.db.db.selectFrom('moderation_report') @@ -104,12 +108,31 @@ export class ModerationService { .orWhere('subjectUri', '=', subject) }) } + if (ignoreSubjects?.length) { - builder = builder.where((qb) => { - return qb - .where('subjectDid', 'not in', ignoreSubjects) - .where('subjectUri', 'not in', ignoreSubjects) + const ignoreUris: string[] = [] + const ignoreDids: string[] = [] + + ignoreSubjects.forEach((subject) => { + if (subject.startsWith('at://')) { + ignoreUris.push(subject) + } else if (subject.startsWith('did:')) { + ignoreDids.push(subject) + } }) + + if (ignoreDids.length) { + builder = builder.where('subjectDid', 'not in', ignoreDids) + } + if (ignoreUris.length) { + builder = builder.where((qb) => { + // Without the null condition, postgres will ignore all reports where `subjectUri` is null + // which will make all the account reports be ignored as well + return qb + .where('subjectUri', 'not in', ignoreUris) + .orWhere('subjectUri', 'is', null) + }) + } } if (reporters?.length) { @@ -129,8 +152,8 @@ export class ModerationService { ? builder.whereExists(resolutionsQuery) : builder.whereNotExists(resolutionsQuery) } - if (actionType !== undefined) { - const resolutionActionsQuery = this.db.db + if (actionType !== undefined || actionedBy !== undefined) { + let resolutionActionsQuery = this.db.db .selectFrom('moderation_report_resolution') .innerJoin( 'moderation_action', @@ -142,10 +165,22 @@ export class ModerationService { '=', ref('moderation_report.id'), ) - .where('moderation_action.action', '=', sql`${actionType}`) - .where('moderation_action.reversedAt', 'is', null) - .selectAll() - builder = builder.whereExists(resolutionActionsQuery) + + if (actionType) { + resolutionActionsQuery = resolutionActionsQuery + .where('moderation_action.action', '=', sql`${actionType}`) + .where('moderation_action.reversedAt', 'is', null) + } + + if (actionedBy) { + resolutionActionsQuery = resolutionActionsQuery.where( + 'moderation_action.createdBy', + '=', + actionedBy, + ) + } + + builder = builder.whereExists(resolutionActionsQuery.selectAll()) } if (cursor) { const cursorNumeric = parseInt(cursor, 10) @@ -208,6 +243,7 @@ export class ModerationService { negateLabelVals?: string[] createdBy: string createdAt?: Date + durationInHours?: number }): Promise { this.db.assertTransaction() const { @@ -216,6 +252,7 @@ export class ModerationService { reason, subject, subjectBlobCids, + durationInHours, createdAt = new Date(), } = info const createLabelVals = @@ -257,7 +294,6 @@ export class ModerationService { 'SubjectHasAction', ) } - const actionResult = await this.db.db .insertInto('moderation_action') .values({ @@ -267,6 +303,11 @@ export class ModerationService { createdBy, createLabelVals, negateLabelVals, + durationInHours, + expiresAt: + durationInHours !== undefined + ? addHoursToDate(durationInHours, createdAt).toISOString() + : undefined, ...subjectInfo, }) .returningAll() @@ -297,12 +338,58 @@ export class ModerationService { return actionResult } - async logReverseAction(info: { - id: number - reason: string - createdBy: string - createdAt?: Date - }): Promise { + async getActionsDueForReversal(): Promise { + const actionsDueForReversal = await this.db.db + .selectFrom('moderation_action') + .where('durationInHours', 'is not', null) + .where('expiresAt', '<', new Date().toISOString()) + .where('reversedAt', 'is', null) + .selectAll() + .execute() + + return actionsDueForReversal + } + + async revertAction({ + id, + createdBy, + createdAt, + reason, + }: ReversibleModerationAction) { + this.db.assertTransaction() + const result = await this.logReverseAction({ + id, + createdAt, + createdBy, + reason, + }) + + if ( + result.action === TAKEDOWN && + result.subjectType === 'com.atproto.admin.defs#repoRef' && + result.subjectDid + ) { + await this.reverseTakedownRepo({ + did: result.subjectDid, + }) + } + + if ( + result.action === TAKEDOWN && + result.subjectType === 'com.atproto.repo.strongRef' && + result.subjectUri + ) { + await this.reverseTakedownRecord({ + uri: new AtUri(result.subjectUri), + }) + } + + return result + } + + async logReverseAction( + info: ReversibleModerationAction, + ): Promise { const { id, createdBy, reason, createdAt = new Date() } = info const result = await this.db.db @@ -355,12 +442,8 @@ export class ModerationService { if (info.blobCids) { await Promise.all( info.blobCids.map(async (cid) => { - const paths = ImageUriBuilder.commonSignedUris.map((id) => { - const uri = this.imgUriBuilder.getCommonSignedUri( - id, - info.uri.host, - cid, - ) + const paths = ImageUriBuilder.presets.map((id) => { + const uri = this.imgUriBuilder.getPresetUri(id, info.uri.host, cid) return uri.replace(this.imgUriBuilder.endpoint, '') }) await this.imgInvalidator.invalidate(cid.toString(), paths) @@ -481,6 +564,12 @@ export class ModerationService { } export type ModerationActionRow = Selectable +export type ReversibleModerationAction = Pick< + ModerationActionRow, + 'id' | 'createdBy' | 'reason' +> & { + createdAt?: Date +} export type ModerationReportRow = Selectable export type ModerationReportRowWithHandle = ModerationReportRow & { diff --git a/packages/bsky/src/services/moderation/views.ts b/packages/bsky/src/services/moderation/views.ts index a79c81749d9..b8d745a594d 100644 --- a/packages/bsky/src/services/moderation/views.ts +++ b/packages/bsky/src/services/moderation/views.ts @@ -1,8 +1,9 @@ import { Selectable } from 'kysely' import { ArrayEl } from '@atproto/common' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' +import { INVALID_HANDLE } from '@atproto/syntax' import { BlobRef, jsonStringToLex } from '@atproto/lexicon' -import Database from '../../db' +import { Database } from '../../db' import { Actor } from '../../db/tables/actor' import { Record as RecordRow } from '../../db/tables/record' import { ModerationAction } from '../../db/tables/moderation' @@ -20,6 +21,7 @@ import { import { OutputSchema as ReportOutput } from '../../lexicon/types/com/atproto/moderation/createReport' import { Label } from '../../lexicon/types/com/atproto/label/defs' import { ModerationReportRowWithHandle } from '.' +import { getSelfLabels } from '../label' export class ModerationViews { constructor(private db: Database) {} @@ -57,7 +59,7 @@ export class ModerationViews { 'in', results.map((r) => r.did), ) - .select(['id', 'action', 'subjectDid']) + .select(['id', 'action', 'durationInHours', 'subjectDid']) .execute(), ]) @@ -82,12 +84,16 @@ export class ModerationViews { return { // No email or invite info on appview did: r.did, - handle: r.handle, + handle: r.handle ?? INVALID_HANDLE, relatedRecords, indexedAt: r.indexedAt, moderation: { currentAction: action - ? { id: action.id, action: action.action } + ? { + id: action.id, + action: action.action, + durationInHours: action.durationInHours ?? undefined, + } : undefined, }, } @@ -157,7 +163,7 @@ export class ModerationViews { 'in', results.map((r) => r.uri), ) - .select(['id', 'action', 'subjectUri']) + .select(['id', 'action', 'durationInHours', 'subjectUri']) .execute(), ]) const repos = await this.repo(repoResults) @@ -185,7 +191,11 @@ export class ModerationViews { repo, moderation: { currentAction: action - ? { id: action.id, action: action.action } + ? { + id: action.id, + action: action.action, + durationInHours: action.durationInHours ?? undefined, + } : undefined, }, } @@ -218,6 +228,11 @@ export class ModerationViews { this.blob(findBlobRefs(record.value)), this.labels(record.uri), ]) + const selfLabels = getSelfLabels({ + uri: result.uri, + cid: result.cid, + record: jsonStringToLex(result.json) as Record, + }) return { ...record, blobs, @@ -226,7 +241,7 @@ export class ModerationViews { reports, actions, }, - labels, + labels: [...labels, ...selfLabels], } } @@ -274,6 +289,7 @@ export class ModerationViews { const views = results.map((res) => ({ id: res.id, action: res.action, + durationInHours: res.durationInHours ?? undefined, subject: res.subjectType === 'com.atproto.admin.defs#repoRef' ? { @@ -336,6 +352,7 @@ export class ModerationViews { return { id: action.id, action: action.action, + durationInHours: action.durationInHours, subject, subjectBlobs, createLabelVals: action.createLabelVals, @@ -506,7 +523,7 @@ export class ModerationViews { 'in', blobs.map((blob) => blob.ref.toString()), ) - .select(['id', 'action', 'cid']) + .select(['id', 'action', 'durationInHours', 'cid']) .execute() const actionByCid = actionResults.reduce( (acc, cur) => Object.assign(acc, { [cur.cid]: cur }), @@ -525,7 +542,11 @@ export class ModerationViews { createdAt: unknownTime, moderation: { currentAction: action - ? { id: action.id, action: action.action } + ? { + id: action.id, + action: action.action, + durationInHours: action.durationInHours ?? undefined, + } : undefined, }, } diff --git a/packages/bsky/src/services/types.ts b/packages/bsky/src/services/types.ts deleted file mode 100644 index d29c1c44c17..00000000000 --- a/packages/bsky/src/services/types.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { View as ViewImages } from '../lexicon/types/app/bsky/embed/images' -import { View as ViewExternal } from '../lexicon/types/app/bsky/embed/external' -import { View as ViewRecord } from '../lexicon/types/app/bsky/embed/record' -import { View as ViewRecordWithMedia } from '../lexicon/types/app/bsky/embed/recordWithMedia' -import { Label } from '../lexicon/types/com/atproto/label/defs' - -export type FeedEmbeds = { - [uri: string]: ViewImages | ViewExternal | ViewRecord | ViewRecordWithMedia -} - -export type PostInfo = { - uri: string - cid: string - creator: string - recordJson: string - indexedAt: string - likeCount: number | null - repostCount: number | null - replyCount: number | null - requesterRepost: string | null - requesterLike: string | null - viewer: string | null -} - -export type PostInfoMap = { [uri: string]: PostInfo } - -export type ActorView = { - did: string - handle: string - displayName?: string - avatar?: string - viewer?: { muted?: boolean; following?: string; followedBy?: string } - labels?: Label[] -} -export type ActorViewMap = { [did: string]: ActorView } - -export type FeedItemType = 'post' | 'repost' - -export type FeedRow = { - type: FeedItemType - uri: string - cid: string - postUri: string - postAuthorDid: string - originatorDid: string - replyParent: string | null - replyRoot: string | null - sortAt: string -} diff --git a/packages/bsky/src/services/util/post.ts b/packages/bsky/src/services/util/post.ts new file mode 100644 index 00000000000..19e7fa3ee2c --- /dev/null +++ b/packages/bsky/src/services/util/post.ts @@ -0,0 +1,65 @@ +import { sql } from 'kysely' +import DatabaseSchema from '../../db/database-schema' + +export const getDescendentsQb = ( + db: DatabaseSchema, + opts: { + uri: string + depth: number // required, protects against cycles + }, +) => { + const { uri, depth } = opts + const query = db.withRecursive('descendent(uri, depth)', (cte) => { + return cte + .selectFrom('post') + .select(['post.uri as uri', sql`1`.as('depth')]) + .where(sql`1`, '<=', depth) + .where('replyParent', '=', uri) + .unionAll( + cte + .selectFrom('post') + .innerJoin('descendent', 'descendent.uri', 'post.replyParent') + .where('descendent.depth', '<', depth) + .select([ + 'post.uri as uri', + sql`descendent.depth + 1`.as('depth'), + ]), + ) + }) + return query +} + +export const getAncestorsAndSelfQb = ( + db: DatabaseSchema, + opts: { + uri: string + parentHeight: number // required, protects against cycles + }, +) => { + const { uri, parentHeight } = opts + const query = db.withRecursive( + 'ancestor(uri, ancestorUri, height)', + (cte) => { + return cte + .selectFrom('post') + .select([ + 'post.uri as uri', + 'post.replyParent as ancestorUri', + sql`0`.as('height'), + ]) + .where('uri', '=', uri) + .unionAll( + cte + .selectFrom('post') + .innerJoin('ancestor', 'ancestor.ancestorUri', 'post.uri') + .where('ancestor.height', '<', parentHeight) + .select([ + 'post.uri as uri', + 'post.replyParent as ancestorUri', + sql`ancestor.height + 1`.as('height'), + ]), + ) + }, + ) + return query +} diff --git a/packages/bsky/src/services/util/search.ts b/packages/bsky/src/services/util/search.ts index 826854941f7..9dfebadb613 100644 --- a/packages/bsky/src/services/util/search.ts +++ b/packages/bsky/src/services/util/search.ts @@ -1,6 +1,6 @@ import { sql } from 'kysely' import { InvalidRequestError } from '@atproto/xrpc-server' -import Database from '../../db' +import { Database } from '../../db' import { notSoftDeletedClause, DbRef, AnyQb } from '../../db/util' import { GenericKeyset, paginate } from '../../db/pagination' @@ -22,7 +22,7 @@ export const getUserSearchQuery = ( limit, cursor, direction: 'asc', - keyset: new SearchKeyset(distanceAccount, ref('handle')), + keyset: new SearchKeyset(distanceAccount, ref('actor.did')), }) // Matching profiles based on display name const distanceProfile = distance(term, ref('displayName')) @@ -31,14 +31,14 @@ export const getUserSearchQuery = ( limit, cursor, direction: 'asc', - keyset: new SearchKeyset(distanceProfile, ref('handle')), + keyset: new SearchKeyset(distanceProfile, ref('actor.did')), }) // Combine and paginate result set return paginate(combineAccountsAndProfilesQb(db, accountsQb, profilesQb), { limit, cursor, direction: 'asc', - keyset: new SearchKeyset(ref('distance'), ref('handle')), + keyset: new SearchKeyset(ref('distance'), ref('actor.did')), }) } @@ -64,7 +64,7 @@ export const getUserSearchQuerySimple = ( return paginate(combineAccountsAndProfilesQb(db, accountsQb, profilesQb), { limit, direction: 'asc', - keyset: new SearchKeyset(ref('distance'), ref('handle')), + keyset: new SearchKeyset(ref('distance'), ref('actor.did')), }) } @@ -81,8 +81,8 @@ const getMatchingAccountsQb = ( .if(!includeSoftDeleted, (qb) => qb.where(notSoftDeletedClause(ref('actor'))), ) + .where('actor.handle', 'is not', null) .where(similar(term, ref('handle'))) // Coarse filter engaging trigram index - .where(distanceAccount, '<', getMatchThreshold(term)) // Refines results from trigram index .select(['actor.did as did', distanceAccount.as('distance')]) } @@ -100,8 +100,8 @@ const getMatchingProfilesQb = ( .if(!includeSoftDeleted, (qb) => qb.where(notSoftDeletedClause(ref('actor'))), ) + .where('actor.handle', 'is not', null) .where(similar(term, ref('displayName'))) // Coarse filter engaging trigram index - .where(distanceProfile, '<', getMatchThreshold(term)) // Refines results from trigram index .select(['profile.creator as did', distanceProfile.as('distance')]) } @@ -137,25 +137,19 @@ export const cleanTerm = (term: string) => term.trim().replace(/^@/g, '') // Uses pg_trgm strict word similarity to check similarity between a search term and a stored value const distance = (term: string, ref: DbRef) => - sql`(${term} <<<-> ${ref})` + sql`(${term} <<-> ${ref})` -// Can utilize trigram index to match on strict word similarity -const similar = (term: string, ref: DbRef) => sql`(${term} <<% ${ref})` +// Can utilize trigram index to match on strict word similarity. +// The word_similarity_threshold is set to .4 (i.e. distance < .6) in db/index.ts. +const similar = (term: string, ref: DbRef) => sql`(${term} <% ${ref})` -const getMatchThreshold = (term: string) => { - // Performing matching by word using "strict word similarity" operator. - // The more characters the user gives us, the more we can ratchet down - // the distance threshold for matching. - return term.length < 3 ? 0.9 : 0.8 -} - -type Result = { distance: number; handle: string } +type Result = { distance: number; did: string } type LabeledResult = { primary: number; secondary: string } export class SearchKeyset extends GenericKeyset { labelResult(result: Result) { return { primary: result.distance, - secondary: result.handle, + secondary: result.did, } } labeledResultToCursor(labeled: LabeledResult) { diff --git a/packages/bsky/src/subscription/repo.ts b/packages/bsky/src/subscription/repo.ts deleted file mode 100644 index 174bd283d0f..00000000000 --- a/packages/bsky/src/subscription/repo.ts +++ /dev/null @@ -1,422 +0,0 @@ -import assert from 'node:assert' -import { CID } from 'multiformats/cid' -import { AtUri } from '@atproto/uri' -import { cborDecode, wait } from '@atproto/common' -import { DisconnectError, Subscription } from '@atproto/xrpc-server' -import { - WriteOpAction, - readCarWithRoot, - cborToLexRecord, - def, - Commit, -} from '@atproto/repo' -import { OutputSchema as Message } from '../lexicon/types/com/atproto/sync/subscribeRepos' -import * as message from '../lexicon/types/com/atproto/sync/subscribeRepos' -import { ids, lexicons } from '../lexicon/lexicons' -import Database from '../db' -import AppContext from '../context' -import { Leader } from '../db/leader' -import { subLogger } from '../logger' -import { ConsecutiveList, LatestQueue, PartitionedQueue } from './util' -import { ValidationError } from '@atproto/lexicon' - -const METHOD = ids.ComAtprotoSyncSubscribeRepos -export const REPO_SUB_ID = 1000 - -export class RepoSubscription { - leader = new Leader(this.subLockId, this.ctx.db) - repoQueue = new PartitionedQueue() - cursorQueue = new LatestQueue() - consecutive = new ConsecutiveList() - destroyed = false - - constructor( - public ctx: AppContext, - public service: string, - public subLockId = REPO_SUB_ID, - ) {} - - async run() { - while (!this.destroyed) { - try { - const { ran } = await this.leader.run(async ({ signal }) => { - const sub = this.getSubscription({ signal }) - for await (const msg of sub) { - const details = getMessageDetails(msg) - if ('info' in details) { - // These messages are not sequenced, we just log them and carry on - subLogger.warn( - { provider: this.service, message: loggableMessage(msg) }, - `repo subscription ${ - details.info ? 'info' : 'unknown' - } message`, - ) - continue - } - const item = this.consecutive.push(details.message) - this.repoQueue - .add(details.repo, () => this.handleMessage(details.message)) - .catch((err) => { - // We log messages we can't process and move on. Barring a - // durable queue this is the best we can do for now: otherwise - // the cursor would get stuck on a poison message. - subLogger.error( - { - err, - provider: this.service, - message: loggableMessage(msg), - }, - 'repo subscription message processing error', - ) - }) - .finally(() => { - const latest = item.complete().at(-1) - if (!latest) return - this.cursorQueue - .add(() => this.handleCursor(latest)) - .catch((err) => { - subLogger.error( - { err, provider: this.service }, - 'repo subscription cursor error', - ) - }) - }) - } - }) - if (ran && !this.destroyed) { - throw new Error('Repo sub completed, but should be persistent') - } - } catch (err) { - subLogger.error( - { err, provider: this.service }, - 'repo subscription error', - ) - } - if (!this.destroyed) { - await wait(5000 + jitter(1000)) // wait then try to become leader - } - } - } - - async destroy() { - this.destroyed = true - await this.repoQueue.destroy() - await this.cursorQueue.destroy() - this.leader.destroy(new DisconnectError()) - } - - async resume() { - this.destroyed = false - this.repoQueue = new PartitionedQueue() - this.cursorQueue = new LatestQueue() - this.consecutive = new ConsecutiveList() - await this.run() - } - - private async handleMessage(msg: ProcessableMessage) { - if (message.isCommit(msg)) { - await this.handleCommit(msg) - } else if (message.isHandle(msg)) { - await this.handleUpdateHandle(msg) - } else if (message.isTombstone(msg)) { - await this.handleTombstone(msg) - } else if (message.isMigrate(msg)) { - // Ignore migrations - } else { - const exhaustiveCheck: never = msg - throw new Error(`Unhandled message type: ${exhaustiveCheck['$type']}`) - } - } - - private async handleCommit(msg: message.Commit) { - const { db, services } = this.ctx - const indexingService = services.indexing(db) - const indexRecords = async () => { - const { root, rootCid, ops } = await getOps(msg) - if (msg.tooBig) { - return await indexingService.indexRepo(msg.repo, rootCid.toString()) - } - if (msg.rebase) { - const needsReindex = await indexingService.checkCommitNeedsIndexing( - root, - ) - if (!needsReindex) return - return await indexingService.indexRepo(msg.repo, rootCid.toString()) - } - for (const op of ops) { - if (op.action === WriteOpAction.Delete) { - await indexingService.deleteRecord(op.uri) - } else { - try { - await indexingService.indexRecord( - op.uri, - op.cid, - op.record, - op.action, // create or update - msg.time, - ) - } catch (err) { - if (err instanceof ValidationError) { - subLogger.warn( - { - did: msg.repo, - commit: msg.commit.toString(), - uri: op.uri.toString(), - cid: op.cid.toString(), - }, - 'skipping indexing of invalid record', - ) - } else { - throw err - } - } - } - } - await indexingService.setCommitLastSeen(root, msg) - } - const results = await Promise.allSettled([ - indexRecords(), - indexingService.indexHandle(msg.repo, msg.time), - ]) - handleAllSettledErrors(results) - } - - private async handleUpdateHandle(msg: message.Handle) { - const { db, services } = this.ctx - await services.indexing(db).indexHandle(msg.did, msg.time, true) - } - - private async handleTombstone(msg: message.Tombstone) { - const { db, services } = this.ctx - await services.indexing(db).tombstoneActor(msg.did) - } - - private async handleCursor(msg: ProcessableMessage) { - const { db } = this.ctx - await db.transaction(async (tx) => { - await this.setState(tx, { cursor: msg.seq }) - }) - } - - async getState(): Promise { - const sub = await this.ctx.db.db - .selectFrom('subscription') - .selectAll() - .where('service', '=', this.service) - .where('method', '=', METHOD) - .executeTakeFirst() - return sub ? (JSON.parse(sub.state) as State) : { cursor: 0 } - } - - async resetState(): Promise { - await this.ctx.db.db - .deleteFrom('subscription') - .where('service', '=', this.service) - .where('method', '=', METHOD) - .executeTakeFirst() - } - - private async setState(tx: Database, state: State): Promise { - tx.assertTransaction() - const res = await tx.db - .updateTable('subscription') - .where('service', '=', this.service) - .where('method', '=', METHOD) - .set({ state: JSON.stringify(state) }) - .executeTakeFirst() - if (res.numUpdatedRows < 1) { - await tx.db - .insertInto('subscription') - .values({ - service: this.service, - method: METHOD, - state: JSON.stringify(state), - }) - .executeTakeFirst() - } - } - - private getSubscription(opts: { signal: AbortSignal }) { - return new Subscription({ - service: this.service, - method: METHOD, - signal: opts.signal, - getParams: () => this.getState(), - onReconnectError: (err, reconnects, initial) => { - subLogger.warn( - { err, reconnects, initial }, - 'repo subscription reconnect', - ) - }, - validate: (value) => { - try { - return lexicons.assertValidXrpcMessage(METHOD, value) - } catch (err) { - subLogger.warn( - { - err, - seq: ifNumber(value?.['seq']), - repo: ifString(value?.['repo']), - commit: ifString(value?.['commit']?.toString()), - time: ifString(value?.['time']), - provider: this.service, - }, - 'repo subscription skipped invalid message', - ) - } - }, - }) - } -} - -// These are the message types that have a sequence number and a repo -type ProcessableMessage = - | message.Commit - | message.Handle - | message.Migrate - | message.Tombstone - -async function getOps( - msg: message.Commit, -): Promise<{ root: Commit; rootCid: CID; ops: PreparedWrite[] }> { - const car = await readCarWithRoot(msg.blocks as Uint8Array) - const rootBytes = car.blocks.get(car.root) - assert(rootBytes, 'Missing commit block in car slice') - - const root = def.commit.schema.parse(cborDecode(rootBytes)) - const ops: PreparedWrite[] = msg.ops.map((op) => { - const [collection, rkey] = op.path.split('/') - assert(collection && rkey) - if ( - op.action === WriteOpAction.Create || - op.action === WriteOpAction.Update - ) { - assert(op.cid) - const record = car.blocks.get(op.cid) - assert(record) - return { - action: - op.action === WriteOpAction.Create - ? WriteOpAction.Create - : WriteOpAction.Update, - cid: op.cid, - record: cborToLexRecord(record), - blobs: [], - uri: AtUri.make(msg.repo, collection, rkey), - } - } else if (op.action === WriteOpAction.Delete) { - return { - action: WriteOpAction.Delete, - uri: AtUri.make(msg.repo, collection, rkey), - } - } else { - throw new Error(`Unknown repo op action: ${op.action}`) - } - }) - - return { root, rootCid: car.root, ops } -} - -function jitter(maxMs) { - return Math.round((Math.random() - 0.5) * maxMs * 2) -} - -function ifString(val: unknown): string | undefined { - return typeof val === 'string' ? val : undefined -} - -function ifNumber(val: unknown): number | undefined { - return typeof val === 'number' ? val : undefined -} - -function loggableMessage(msg: Message) { - if (message.isCommit(msg)) { - const { seq, rebase, prev, repo, commit, time, tooBig, blobs } = msg - return { - $type: msg.$type, - seq, - rebase, - prev: prev?.toString(), - repo, - commit: commit.toString(), - time, - tooBig, - hasBlobs: blobs.length > 0, - } - } else if (message.isHandle(msg)) { - return msg - } else if (message.isMigrate(msg)) { - return msg - } else if (message.isTombstone(msg)) { - return msg - } else if (message.isInfo(msg)) { - return msg - } - return msg -} - -type State = { cursor: number } - -type PreparedCreate = { - action: WriteOpAction.Create - uri: AtUri - cid: CID - record: Record - blobs: CID[] // differs from similar type in pds -} - -type PreparedUpdate = { - action: WriteOpAction.Update - uri: AtUri - cid: CID - record: Record - blobs: CID[] // differs from similar type in pds -} - -type PreparedDelete = { - action: WriteOpAction.Delete - uri: AtUri -} - -type PreparedWrite = PreparedCreate | PreparedUpdate | PreparedDelete - -function getMessageDetails(msg: Message): - | { info: message.Info | null } - | { - seq: number - repo: string - message: ProcessableMessage - } { - if (message.isCommit(msg)) { - return { seq: msg.seq, repo: msg.repo, message: msg } - } else if (message.isHandle(msg)) { - return { seq: msg.seq, repo: msg.did, message: msg } - } else if (message.isMigrate(msg)) { - return { seq: msg.seq, repo: msg.did, message: msg } - } else if (message.isTombstone(msg)) { - return { seq: msg.seq, repo: msg.did, message: msg } - } else if (message.isInfo(msg)) { - return { info: msg } - } - return { info: null } -} - -function handleAllSettledErrors(results: PromiseSettledResult[]) { - const errors = results.filter(isRejected).map((res) => res.reason) - if (errors.length === 0) { - return - } - if (errors.length === 1) { - throw errors[0] - } - throw new AggregateError( - errors, - 'Multiple errors: ' + errors.map((err) => err?.message).join('\n'), - ) -} - -function isRejected( - result: PromiseSettledResult, -): result is PromiseRejectedResult { - return result.status === 'rejected' -} diff --git a/packages/bsky/src/subscription/util.ts b/packages/bsky/src/subscription/util.ts index 70f3ec74905..fe367bcc24c 100644 --- a/packages/bsky/src/subscription/util.ts +++ b/packages/bsky/src/subscription/util.ts @@ -1,11 +1,18 @@ import PQueue from 'p-queue' +import { OutputSchema as RepoMessage } from '../lexicon/types/com/atproto/sync/subscribeRepos' +import * as message from '../lexicon/types/com/atproto/sync/subscribeRepos' +import assert from 'node:assert' // A queue with arbitrarily many partitions, each processing work sequentially. // Partitions are created lazily and taken out of memory when they go idle. export class PartitionedQueue { - main = new PQueue({ concurrency: Infinity }) + main: PQueue partitions = new Map() + constructor(opts: { concurrency: number }) { + this.main = new PQueue({ concurrency: opts.concurrency }) + } + async add(partitionId: string, task: () => Promise) { if (this.main.isPaused) return return this.main.add(() => { @@ -88,3 +95,54 @@ export class ConsecutiveItem { return this.consecutive.complete() } } + +export class PerfectMap extends Map { + get(key: K): V { + const val = super.get(key) + assert(val !== undefined, `Key not found in PerfectMap: ${key}`) + return val + } +} + +// These are the message types that have a sequence number and a repo +export type ProcessableMessage = + | message.Commit + | message.Handle + | message.Migrate + | message.Tombstone + +export function loggableMessage(msg: RepoMessage) { + if (message.isCommit(msg)) { + const { seq, rebase, prev, repo, commit, time, tooBig, blobs } = msg + return { + $type: msg.$type, + seq, + rebase, + prev: prev?.toString(), + repo, + commit: commit.toString(), + time, + tooBig, + hasBlobs: blobs.length > 0, + } + } else if (message.isHandle(msg)) { + return msg + } else if (message.isMigrate(msg)) { + return msg + } else if (message.isTombstone(msg)) { + return msg + } else if (message.isInfo(msg)) { + return msg + } + return msg +} + +export function jitter(maxMs) { + return Math.round((Math.random() - 0.5) * maxMs * 2) +} + +export function strToInt(str: string) { + const int = parseInt(str, 10) + assert(!isNaN(int), 'string could not be parsed to an integer') + return int +} diff --git a/packages/bsky/src/util/date.ts b/packages/bsky/src/util/date.ts new file mode 100644 index 00000000000..af9767a0f7f --- /dev/null +++ b/packages/bsky/src/util/date.ts @@ -0,0 +1,14 @@ +/** + * This function takes a number as input and returns a Date object, + * which is the current date and time plus the input number of hours. + * + * @param {number} hours - The number of hours to add to the current date and time. + * @param {Date} startingDate - If provided, the function will add `hours` to the provided date instead of the current date. + * @returns {Date} - The new Date object, which is the current date and time plus the input number of hours. + */ +export function addHoursToDate(hours: number, startingDate?: Date): Date { + // When date is passe, let's clone before calling `setHours()` so that we are not mutating the original date + const currentDate = startingDate ? new Date(startingDate) : new Date() + currentDate.setHours(currentDate.getHours() + hours) + return currentDate +} diff --git a/packages/bsky/tests/__snapshots__/feed-generation.test.ts.snap b/packages/bsky/tests/__snapshots__/feed-generation.test.ts.snap index 92d6e4e8a6c..3ddf6266fbd 100644 --- a/packages/bsky/tests/__snapshots__/feed-generation.test.ts.snap +++ b/packages/bsky/tests/__snapshots__/feed-generation.test.ts.snap @@ -3,7 +3,7 @@ exports[`feed generation does not embed taken-down feed generator records in posts 1`] = ` Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "bobby", "handle": "bob.test", @@ -18,6 +18,7 @@ Object { "$type": "app.bsky.embed.record#view", "record": Object { "$type": "app.bsky.embed.record#viewNotFound", + "notFound": true, "uri": "record(1)", }, }, @@ -46,7 +47,7 @@ Object { exports[`feed generation embeds feed generator records in posts 1`] = ` Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "bobby", "handle": "bob.test", @@ -63,11 +64,28 @@ Object { "$type": "app.bsky.feed.defs#generatorView", "cid": "cids(2)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", "did": "user(3)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(3)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(4)", + "val": "self-label-a", + }, + Object { + "cid": "cids(3)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(4)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(3)", @@ -82,7 +100,7 @@ Object { "likeCount": 2, "uri": "record(1)", "viewer": Object { - "like": "record(4)", + "like": "record(5)", }, }, }, @@ -113,13 +131,30 @@ Array [ Object { "cid": "cids(0)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(2)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", "description": "its me!", "did": "user(1)", "displayName": "ali", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -136,15 +171,32 @@ Array [ "viewer": Object {}, }, Object { - "cid": "cids(2)", + "cid": "cids(3)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(2)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", "description": "its me!", "did": "user(1)", "displayName": "ali", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -157,19 +209,36 @@ Array [ "displayName": "Bad Pagination", "indexedAt": "1970-01-01T00:00:00.000Z", "likeCount": 0, - "uri": "record(3)", + "uri": "record(4)", "viewer": Object {}, }, Object { - "cid": "cids(3)", + "cid": "cids(4)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(2)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", "description": "its me!", "did": "user(1)", "displayName": "ali", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -182,19 +251,36 @@ Array [ "displayName": "Even", "indexedAt": "1970-01-01T00:00:00.000Z", "likeCount": 0, - "uri": "record(4)", + "uri": "record(5)", "viewer": Object {}, }, Object { - "cid": "cids(4)", + "cid": "cids(5)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(2)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", "description": "its me!", "did": "user(1)", "displayName": "ali", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -207,9 +293,9 @@ Array [ "displayName": "All", "indexedAt": "1970-01-01T00:00:00.000Z", "likeCount": 2, - "uri": "record(5)", + "uri": "record(6)", "viewer": Object { - "like": "record(6)", + "like": "record(7)", }, }, ] @@ -220,11 +306,28 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -232,11 +335,28 @@ Array [ }, "cid": "cids(0)", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(0)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(0)", + "val": "self-label", + }, + ], "likeCount": 0, "record": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label", + }, + ], + }, "text": "hey there", }, "replyCount": 0, @@ -248,19 +368,19 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(3)", - "following": "record(2)", + "followedBy": "record(4)", + "following": "record(3)", "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -275,7 +395,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(1)", + "uri": "record(2)", "viewer": Object {}, }, }, @@ -287,12 +407,12 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(6)", - "following": "record(5)", + "followedBy": "record(7)", + "following": "record(6)", "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(4)", "embed": Object { "$type": "app.bsky.embed.recordWithMedia#view", "media": Object { @@ -300,13 +420,13 @@ Array [ "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(5)@jpeg", }, Object { "alt": "tests/image/fixtures/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(6)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(6)@jpeg", }, ], }, @@ -314,23 +434,23 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(3)", - "following": "record(2)", + "followedBy": "record(4)", + "following": "record(3)", "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "embeds": Array [], "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(1)", + "uri": "record(2)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -360,7 +480,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(5)", }, "size": 4114, }, @@ -371,7 +491,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(5)", + "$link": "cids(6)", }, "size": 12736, }, @@ -380,8 +500,8 @@ Array [ }, "record": Object { "record": Object { - "cid": "cids(2)", - "uri": "record(1)", + "cid": "cids(3)", + "uri": "record(2)", }, }, }, @@ -389,9 +509,9 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(4)", + "uri": "record(5)", "viewer": Object { - "like": "record(7)", + "like": "record(8)", }, }, }, @@ -403,12 +523,12 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(6)", - "following": "record(5)", + "followedBy": "record(7)", + "following": "record(6)", "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(7)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -417,36 +537,53 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(7)", - "uri": "record(9)", + "cid": "cids(8)", + "uri": "record(10)", }, "root": Object { - "cid": "cids(7)", - "uri": "record(9)", + "cid": "cids(8)", + "uri": "record(10)", }, }, "text": "of course", }, "replyCount": 0, "repostCount": 0, - "uri": "record(8)", + "uri": "record(9)", "viewer": Object {}, }, "reply": Object { "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -457,23 +594,40 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(9)", + "uri": "record(10)", "viewer": Object {}, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -484,7 +638,7 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(9)", + "uri": "record(10)", "viewer": Object {}, }, }, @@ -497,11 +651,11 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(11)", + "following": "record(12)", "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(9)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { @@ -512,12 +666,12 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(6)", - "following": "record(5)", + "followedBy": "record(7)", + "following": "record(6)", "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(4)", "embeds": Array [ Object { "$type": "app.bsky.embed.recordWithMedia#view", @@ -526,13 +680,13 @@ Array [ "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(5)@jpeg", }, Object { "alt": "tests/image/fixtures/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(6)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(6)@jpeg", }, ], }, @@ -540,22 +694,22 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(3)", - "following": "record(2)", + "followedBy": "record(4)", + "following": "record(3)", "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(1)", + "uri": "record(2)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -571,7 +725,7 @@ Array [ ], "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(4)", + "uri": "record(5)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -586,7 +740,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(5)", }, "size": 4114, }, @@ -597,7 +751,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(5)", + "$link": "cids(6)", }, "size": 12736, }, @@ -606,8 +760,8 @@ Array [ }, "record": Object { "record": Object { - "cid": "cids(2)", - "uri": "record(1)", + "cid": "cids(3)", + "uri": "record(2)", }, }, }, @@ -624,8 +778,8 @@ Array [ "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(3)", - "uri": "record(4)", + "cid": "cids(4)", + "uri": "record(5)", }, }, "facets": Array [ @@ -646,7 +800,7 @@ Array [ }, "replyCount": 0, "repostCount": 1, - "uri": "record(10)", + "uri": "record(11)", "viewer": Object {}, }, "reason": Object { @@ -657,8 +811,8 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(6)", - "following": "record(5)", + "followedBy": "record(7)", + "following": "record(6)", "muted": false, }, }, @@ -673,11 +827,28 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -685,11 +856,28 @@ Array [ }, "cid": "cids(0)", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(0)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(0)", + "val": "self-label", + }, + ], "likeCount": 0, "record": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label", + }, + ], + }, "text": "hey there", }, "replyCount": 0, @@ -706,12 +894,12 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(3)", - "following": "record(2)", + "followedBy": "record(4)", + "following": "record(3)", "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "embed": Object { "$type": "app.bsky.embed.recordWithMedia#view", "media": Object { @@ -719,13 +907,13 @@ Array [ "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(3)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(3)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(4)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(4)@jpeg", }, Object { "alt": "tests/image/fixtures/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(5)@jpeg", }, ], }, @@ -733,23 +921,23 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(6)", - "following": "record(5)", + "followedBy": "record(7)", + "following": "record(6)", "muted": false, }, }, - "cid": "cids(5)", + "cid": "cids(6)", "embeds": Array [], "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(4)", + "uri": "record(5)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -779,7 +967,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(3)", + "$link": "cids(4)", }, "size": 4114, }, @@ -790,7 +978,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(5)", }, "size": 12736, }, @@ -799,8 +987,8 @@ Array [ }, "record": Object { "record": Object { - "cid": "cids(5)", - "uri": "record(4)", + "cid": "cids(6)", + "uri": "record(5)", }, }, }, @@ -808,9 +996,9 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(1)", + "uri": "record(2)", "viewer": Object { - "like": "record(7)", + "like": "record(8)", }, }, }, @@ -822,12 +1010,12 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(3)", - "following": "record(2)", + "followedBy": "record(4)", + "following": "record(3)", "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(7)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -836,36 +1024,53 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(7)", - "uri": "record(9)", + "cid": "cids(8)", + "uri": "record(10)", }, "root": Object { - "cid": "cids(7)", - "uri": "record(9)", + "cid": "cids(8)", + "uri": "record(10)", }, }, "text": "of course", }, "replyCount": 0, "repostCount": 0, - "uri": "record(8)", + "uri": "record(9)", "viewer": Object {}, }, "reply": Object { "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -876,23 +1081,40 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(9)", + "uri": "record(10)", "viewer": Object {}, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -903,7 +1125,7 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(9)", + "uri": "record(10)", "viewer": Object {}, }, }, @@ -918,11 +1140,28 @@ Object { "view": Object { "cid": "cids(0)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(2)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -937,7 +1176,7 @@ Object { "likeCount": 2, "uri": "record(0)", "viewer": Object { - "like": "record(3)", + "like": "record(4)", }, }, } @@ -949,11 +1188,28 @@ Object { Object { "cid": "cids(0)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(2)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -968,17 +1224,123 @@ Object { "likeCount": 2, "uri": "record(0)", "viewer": Object { - "like": "record(3)", + "like": "record(4)", }, }, Object { - "cid": "cids(2)", + "cid": "cids(3)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(2)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(2)", + "following": "record(1)", + "muted": false, + }, + }, + "description": "Provides even-indexed feed candidates", + "did": "user(0)", + "displayName": "Even", + "indexedAt": "1970-01-01T00:00:00.000Z", + "likeCount": 0, + "uri": "record(5)", + "viewer": Object {}, + }, + ], +} +`; + +exports[`feed generation getSuggestedFeeds returns list of suggested feed generators 1`] = ` +Object { + "feeds": Array [ + Object { + "cid": "cids(0)", + "creator": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", + "did": "user(1)", + "displayName": "ali", + "handle": "alice.test", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(2)", + "following": "record(1)", + "muted": false, + }, + }, + "description": "Provides all feed candidates", + "did": "user(0)", + "displayName": "All", + "indexedAt": "1970-01-01T00:00:00.000Z", + "likeCount": 2, + "uri": "record(0)", + "viewer": Object { + "like": "record(4)", + }, + }, + Object { + "cid": "cids(3)", + "creator": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", + "did": "user(1)", + "displayName": "ali", + "handle": "alice.test", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -991,7 +1353,47 @@ Object { "displayName": "Even", "indexedAt": "1970-01-01T00:00:00.000Z", "likeCount": 0, - "uri": "record(4)", + "uri": "record(5)", + "viewer": Object {}, + }, + Object { + "cid": "cids(4)", + "creator": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", + "did": "user(1)", + "displayName": "ali", + "handle": "alice.test", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(2)", + "following": "record(1)", + "muted": false, + }, + }, + "description": "Provides all feed candidates, blindly ignoring pagination limit", + "did": "user(0)", + "displayName": "Bad Pagination", + "indexedAt": "1970-01-01T00:00:00.000Z", + "likeCount": 0, + "uri": "record(6)", "viewer": Object {}, }, ], diff --git a/packages/bsky/tests/__snapshots__/indexing.test.ts.snap b/packages/bsky/tests/__snapshots__/indexing.test.ts.snap index a3d11a3fae7..9abbe8a3f64 100644 --- a/packages/bsky/tests/__snapshots__/indexing.test.ts.snap +++ b/packages/bsky/tests/__snapshots__/indexing.test.ts.snap @@ -76,7 +76,7 @@ Array [ "text": "thanks bob", }, "replyCount": 0, - "repostCount": 0, + "repostCount": 1, "uri": "record(1)", "viewer": Object {}, }, @@ -84,7 +84,7 @@ Array [ "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(2)/cids(4)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(4)@jpeg", "did": "user(1)", "displayName": "bobby", "handle": "bob.test", @@ -102,8 +102,8 @@ Array [ "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(2)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(2)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(2)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(2)/cids(5)@jpeg", }, ], }, @@ -380,11 +380,28 @@ Array [ }, "cid": "cids(11)", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(11)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(12)", + "val": "self-label", + }, + ], "likeCount": 0, "record": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label", + }, + ], + }, "text": "hey there", }, "replyCount": 0, @@ -409,7 +426,7 @@ Array [ }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(2)/cids(4)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(4)@jpeg", "description": "hi im bob label_me", "did": "user(1)", "displayName": "bobby", @@ -445,11 +462,28 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -494,11 +528,28 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, diff --git a/packages/bsky/tests/_util.ts b/packages/bsky/tests/_util.ts index b984c5009f3..4f08af9e0f6 100644 --- a/packages/bsky/tests/_util.ts +++ b/packages/bsky/tests/_util.ts @@ -1,4 +1,4 @@ -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { lexToJson } from '@atproto/lexicon' import { CID } from 'multiformats/cid' import { @@ -52,17 +52,14 @@ export const forSnapshot = (obj: unknown) => { if (str.match(/^\d+::bafy/)) { return constantKeysetCursor } - if (str.match(/\/image\/[^/]+\/.+\/did:plc:[^/]+\/[^/]+@[\w]+$/)) { + if (str.match(/\/img\/[^/]+\/.+\/did:plc:[^/]+\/[^/]+@[\w]+$/)) { // Match image urls const match = str.match( - /\/image\/([^/]+)\/.+\/(did:plc:[^/]+)\/([^/]+)@[\w]+$/, + /\/img\/[^/]+\/.+\/(did:plc:[^/]+)\/([^/]+)@[\w]+$/, ) if (!match) return str - const [, sig, did, cid] = match - return str - .replace(sig, 'sig()') - .replace(did, take(users, did)) - .replace(cid, take(cids, cid)) + const [, did, cid] = match + return str.replace(did, take(users, did)).replace(cid, take(cids, cid)) } let isCid: boolean try { diff --git a/packages/bsky/tests/algos/hot-classic.test.ts b/packages/bsky/tests/algos/hot-classic.test.ts index 1383bfe325c..d7f3c221e1b 100644 --- a/packages/bsky/tests/algos/hot-classic.test.ts +++ b/packages/bsky/tests/algos/hot-classic.test.ts @@ -33,7 +33,7 @@ describe('algo hot-classic', () => { alice = sc.dids.alice bob = sc.dids.bob await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() }) afterAll(async () => { @@ -61,7 +61,7 @@ describe('algo hot-classic', () => { await sc.like(sc.dids[name], two.ref) await sc.like(sc.dids[name], three.ref) } - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() const res = await agent.api.app.bsky.feed.getFeed( { feed: feedUri }, diff --git a/packages/bsky/tests/algos/whats-hot.test.ts b/packages/bsky/tests/algos/whats-hot.test.ts index 3f6ef48c29c..23cac215dda 100644 --- a/packages/bsky/tests/algos/whats-hot.test.ts +++ b/packages/bsky/tests/algos/whats-hot.test.ts @@ -5,7 +5,7 @@ import basicSeed from '../seeds/basic' import { makeAlgos } from '../../src' import { TestNetwork } from '@atproto/dev-env' -describe('algo whats-hot', () => { +describe.skip('algo whats-hot', () => { let network: TestNetwork let agent: AtpAgent let sc: SeedClient @@ -36,7 +36,7 @@ describe('algo whats-hot', () => { bob = sc.dids.bob carol = sc.dids.carol await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() }) afterAll(async () => { @@ -74,16 +74,19 @@ describe('algo whats-hot', () => { await sc.like(sc.dids[name], five.ref) } } - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() // move the 3rd post 5 hours into the past to check gravity - await network.bsky.ctx.db.db - .updateTable('post') + await network.bsky.ctx.db + .getPrimary() + .db.updateTable('post') .where('uri', '=', three.ref.uriStr) .set({ indexedAt: new Date(Date.now() - 5 * HOUR).toISOString() }) .execute() - await network.bsky.ctx.db.refreshMaterializedView('algo_whats_hot_view') + await network.bsky.ctx.db + .getPrimary() + .refreshMaterializedView('algo_whats_hot_view') const res = await agent.api.app.bsky.feed.getFeed( { feed: feedUri }, diff --git a/packages/bsky/tests/algos/with-friends.test.ts b/packages/bsky/tests/algos/with-friends.test.ts index c0927486e18..12f35083ae4 100644 --- a/packages/bsky/tests/algos/with-friends.test.ts +++ b/packages/bsky/tests/algos/with-friends.test.ts @@ -37,7 +37,7 @@ describe.skip('algo with friends', () => { carol = sc.dids.carol dan = sc.dids.dan await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() }) afterAll(async () => { @@ -109,7 +109,7 @@ describe.skip('algo with friends', () => { await sc.like(carol, nine.ref) await sc.like(carol, ten.ref) - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() expectedFeed = [ ten.ref.uriStr, diff --git a/packages/bsky/tests/labeler/fixtures/hiveai_resp_example.json b/packages/bsky/tests/auto-moderator/fixtures/hiveai_resp_example.json similarity index 100% rename from packages/bsky/tests/labeler/fixtures/hiveai_resp_example.json rename to packages/bsky/tests/auto-moderator/fixtures/hiveai_resp_example.json diff --git a/packages/bsky/tests/auto-moderator/fuzzy-matcher.test.ts b/packages/bsky/tests/auto-moderator/fuzzy-matcher.test.ts new file mode 100644 index 00000000000..f9fc320dcb9 --- /dev/null +++ b/packages/bsky/tests/auto-moderator/fuzzy-matcher.test.ts @@ -0,0 +1,165 @@ +import { FuzzyMatcher, encode } from '../../src/auto-moderator/fuzzy-matcher' +import { TestNetwork } from '@atproto/dev-env' +import { SeedClient } from '../seeds/client' +import basicSeed from '../seeds/basic' +import { AtpAgent } from '@atproto/api' +import { ImageInvalidator } from '../../src/image/invalidator' + +describe('fuzzy matcher', () => { + let network: TestNetwork + let agent: AtpAgent + let sc: SeedClient + let fuzzyMatcher: FuzzyMatcher + + let alice: string + + beforeAll(async () => { + network = await TestNetwork.create({ + dbPostgresSchema: 'fuzzy_matcher', + bsky: { + imgInvalidator: new NoopInvalidator(), + indexer: { + fuzzyMatchB64: encode(['evil']), + }, + }, + }) + fuzzyMatcher = new FuzzyMatcher(['evil', 'mean', 'bad'], ['baddie']) + agent = network.pds.getClient() + sc = new SeedClient(agent) + await basicSeed(sc) + await network.processAll() + alice = sc.dids.alice + }) + + afterAll(async () => { + await network.close() + }) + + const getAllReports = () => { + return network.bsky.ctx.db + .getPrimary() + .db.selectFrom('moderation_report') + .selectAll() + .orderBy('id', 'asc') + .execute() + } + + it('identifies fuzzy matches', () => { + expect(fuzzyMatcher.getMatches('evil.john.test')).toMatchObject(['evil']) + expect(fuzzyMatcher.getMatches('john.evil.test')).toMatchObject(['evil']) + expect(fuzzyMatcher.getMatches('john.test.evil')).toMatchObject(['evil']) + expect(fuzzyMatcher.getMatches('ev1l.test.john')).toMatchObject(['evil']) + expect(fuzzyMatcher.getMatches('ev-1l.test.john')).toMatchObject(['evil']) + expect(fuzzyMatcher.getMatches('ev-11.test.john')).toMatchObject(['evil']) + expect(fuzzyMatcher.getMatches('ev.-1.l-test.john')).toMatchObject(['evil']) + }) + + it('identifies fuzzy false positivies', () => { + expect(fuzzyMatcher.getMatches('john.test')).toHaveLength(0) + expect(fuzzyMatcher.getMatches('good.john.test')).toHaveLength(0) + expect(fuzzyMatcher.getMatches('john.baddie.test')).toHaveLength(0) + }) + + it('doesnt label any of the content in the seed', async () => { + const reports = await getAllReports() + expect(reports.length).toBe(0) + }) + + it('flags a handle with an unacceptable word', async () => { + await sc.updateHandle(alice, 'evil.test') + await network.processAll() + const reports = await getAllReports() + expect(reports.length).toBe(1) + expect(reports.at(-1)?.subjectDid).toEqual(alice) + }) + + it('flags a profile with an unacceptable displayName', async () => { + const res = await agent.api.com.atproto.repo.putRecord( + { + repo: alice, + collection: 'app.bsky.actor.profile', + rkey: 'self', + record: { + displayName: 'evil alice', + }, + }, + { headers: sc.getHeaders(alice), encoding: 'application/json' }, + ) + await network.processAll() + + const reports = await getAllReports() + expect(reports.length).toBe(2) + expect(reports.at(-1)?.subjectUri).toEqual(res.data.uri) + expect(reports.at(-1)?.subjectCid).toEqual(res.data.cid) + }) + + it('flags a list with an unacceptable name', async () => { + const res = await agent.api.com.atproto.repo.createRecord( + { + repo: alice, + collection: 'app.bsky.graph.list', + rkey: 'list', + record: { + name: 'myevillist', + purpose: 'app.bsky.graph.defs#modList', + createdAt: new Date().toISOString(), + }, + }, + { headers: sc.getHeaders(alice), encoding: 'application/json' }, + ) + await network.processAll() + + const reports = await getAllReports() + expect(reports.length).toBe(3) + expect(reports.at(-1)?.subjectUri).toEqual(res.data.uri) + expect(reports.at(-1)?.subjectCid).toEqual(res.data.cid) + }) + + it('flags a feed generator with an unacceptable displayName', async () => { + const res = await agent.api.com.atproto.repo.createRecord( + { + repo: alice, + collection: 'app.bsky.feed.generator', + rkey: 'generator', + record: { + did: alice, + displayName: 'myevilfeed', + createdAt: new Date().toISOString(), + }, + }, + { headers: sc.getHeaders(alice), encoding: 'application/json' }, + ) + await network.processAll() + + const reports = await getAllReports() + expect(reports.length).toBe(4) + expect(reports.at(-1)?.subjectUri).toEqual(res.data.uri) + expect(reports.at(-1)?.subjectCid).toEqual(res.data.cid) + }) + + it('flags a record with an unacceptable rkey', async () => { + const res = await agent.api.com.atproto.repo.createRecord( + { + repo: alice, + collection: 'app.bsky.feed.generator', + rkey: 'evilrkey', + record: { + did: alice, + displayName: 'totally fine feed', + createdAt: new Date().toISOString(), + }, + }, + { headers: sc.getHeaders(alice), encoding: 'application/json' }, + ) + await network.processAll() + + const reports = await getAllReports() + expect(reports.length).toBe(5) + expect(reports.at(-1)?.subjectUri).toEqual(res.data.uri) + expect(reports.at(-1)?.subjectCid).toEqual(res.data.cid) + }) +}) + +class NoopInvalidator implements ImageInvalidator { + async invalidate() {} +} diff --git a/packages/bsky/tests/labeler/hive.test.ts b/packages/bsky/tests/auto-moderator/hive.test.ts similarity index 56% rename from packages/bsky/tests/labeler/hive.test.ts rename to packages/bsky/tests/auto-moderator/hive.test.ts index 30a41d1a44b..3a5cef45a37 100644 --- a/packages/bsky/tests/labeler/hive.test.ts +++ b/packages/bsky/tests/auto-moderator/hive.test.ts @@ -1,13 +1,13 @@ import fs from 'fs/promises' -import * as hive from '../../src/labeler/hive' +import * as hive from '../../src/auto-moderator/hive' describe('labeling', () => { it('correctly parses hive responses', async () => { const exampleRespBytes = await fs.readFile( - 'tests/labeler/fixtures/hiveai_resp_example.json', + 'tests/auto-moderator/fixtures/hiveai_resp_example.json', ) - const exmapleResp = JSON.parse(exampleRespBytes.toString()) - const classes = hive.respToClasses(exmapleResp) + const exampleResp = JSON.parse(exampleRespBytes.toString()) + const classes = hive.respToClasses(exampleResp) expect(classes.length).toBeGreaterThan(10) const labels = hive.summarizeLabels(classes) diff --git a/packages/bsky/tests/labeler/labeler.test.ts b/packages/bsky/tests/auto-moderator/labeler.test.ts similarity index 72% rename from packages/bsky/tests/labeler/labeler.test.ts rename to packages/bsky/tests/auto-moderator/labeler.test.ts index 7521349deaf..7227e769549 100644 --- a/packages/bsky/tests/labeler/labeler.test.ts +++ b/packages/bsky/tests/auto-moderator/labeler.test.ts @@ -1,39 +1,41 @@ import { AtUri, AtpAgent, BlobRef } from '@atproto/api' -import stream, { Readable } from 'stream' -import { Labeler } from '../../src/labeler' -import { AppContext, Database, ServerConfig } from '../../src' +import { Readable } from 'stream' +import { AutoModerator } from '../../src/auto-moderator' +import IndexerContext from '../../src/indexer/context' import { cidForRecord } from '@atproto/repo' -import { keywordLabeling } from '../../src/labeler/util' -import { cidForCbor, streamToBytes, TID } from '@atproto/common' -import * as ui8 from 'uint8arrays' +import { cidForCbor, TID } from '@atproto/common' import { LabelService } from '../../src/services/label' import { TestNetwork } from '@atproto/dev-env' -import { IdResolver } from '@atproto/identity' import { SeedClient } from '../seeds/client' import usersSeed from '../seeds/users' -import { BackgroundQueue } from '../../src/background' +import { CID } from 'multiformats/cid' +import { ImgLabeler } from '../../src/auto-moderator/hive' + +// outside of test suite so that TestLabeler can access them +let badCid1: CID | undefined = undefined +let badCid2: CID | undefined = undefined describe('labeler', () => { let network: TestNetwork - let labeler: Labeler + let autoMod: AutoModerator let labelSrvc: LabelService - let ctx: AppContext + let ctx: IndexerContext let labelerDid: string let badBlob1: BlobRef let badBlob2: BlobRef let goodBlob: BlobRef let alice: string const postUri = () => AtUri.make(alice, 'app.bsky.feed.post', TID.nextStr()) - const profileUri = () => AtUri.make(alice, 'app.bsky.actor.profile', 'self') beforeAll(async () => { network = await TestNetwork.create({ dbPostgresSchema: 'bsky_labeler', }) - ctx = network.bsky.ctx + ctx = network.bsky.indexer.ctx const pdsCtx = network.pds.ctx labelerDid = ctx.cfg.labelerDid - labeler = new TestLabeler(ctx) + autoMod = ctx.autoMod + autoMod.imgLabeler = new TestImgLabeler() labelSrvc = ctx.services.label(ctx.db) const pdsAgent = new AtpAgent({ service: network.pds.url }) const sc = new SeedClient(pdsAgent) @@ -67,6 +69,8 @@ describe('labeler', () => { badBlob1 = await storeBlob(bytes1) badBlob2 = await storeBlob(bytes2) goodBlob = await storeBlob(bytes3) + badCid1 = badBlob1.ref + badCid2 = badBlob2.ref }) afterAll(async () => { @@ -81,8 +85,8 @@ describe('labeler', () => { } const cid = await cidForRecord(post) const uri = postUri() - labeler.processRecord(uri, post) - await labeler.processAll() + autoMod.processRecord(uri, cid, post) + await autoMod.processAll() const labels = await labelSrvc.getLabels(uri.toString()) expect(labels.length).toBe(1) expect(labels[0]).toMatchObject({ @@ -118,8 +122,9 @@ describe('labeler', () => { createdAt: new Date().toISOString(), } const uri = postUri() - labeler.processRecord(uri, post) - await labeler.processAll() + const cid = await cidForRecord(post) + autoMod.processRecord(uri, cid, post) + await autoMod.processAll() const dbLabels = await labelSrvc.getLabels(uri.toString()) const labels = dbLabels.map((row) => row.val).sort() expect(labels).toEqual( @@ -152,32 +157,12 @@ describe('labeler', () => { }) }) -class TestLabeler extends Labeler { - hiveApiKey: string - keywords: Record - - constructor(opts: { - db: Database - idResolver: IdResolver - cfg: ServerConfig - backgroundQueue: BackgroundQueue - }) { - const { db, cfg, idResolver, backgroundQueue } = opts - super({ db, cfg, idResolver, backgroundQueue }) - this.keywords = cfg.labelerKeywords - } - - async labelText(text: string): Promise { - return keywordLabeling(this.keywords, text) - } - - async labelImg(img: stream.Readable): Promise { - const buf = await streamToBytes(img) - if (ui8.equals(buf, new Uint8Array([1, 2, 3, 4]))) { +class TestImgLabeler implements ImgLabeler { + async labelImg(_did: string, cid: CID): Promise { + if (cid.equals(badCid1)) { return ['img-label'] } - - if (ui8.equals(buf, new Uint8Array([5, 6, 7, 8]))) { + if (cid.equals(badCid2)) { return ['other-img-label'] } return [] diff --git a/packages/bsky/tests/auto-moderator/takedowns.test.ts b/packages/bsky/tests/auto-moderator/takedowns.test.ts new file mode 100644 index 00000000000..a42a1d01c8a --- /dev/null +++ b/packages/bsky/tests/auto-moderator/takedowns.test.ts @@ -0,0 +1,168 @@ +import fs from 'fs/promises' +import { AtpAgent } from '@atproto/api' +import { AutoModerator } from '../../src/auto-moderator' +import IndexerContext from '../../src/indexer/context' +import { sha256RawToCid } from '@atproto/common' +import { TestNetwork } from '@atproto/dev-env' +import { ImageRef, SeedClient } from '../seeds/client' +import usersSeed from '../seeds/users' +import { CID } from 'multiformats/cid' +import { ImageFlagger } from '../../src/auto-moderator/abyss' +import { ImageInvalidator } from '../../src/image/invalidator' +import { sha256 } from '@atproto/crypto' +import { ids } from '../../src/lexicon/lexicons' + +// outside of test suite so that TestLabeler can access them +let badCid1: CID | undefined = undefined +let badCid2: CID | undefined = undefined + +describe('takedowner', () => { + let network: TestNetwork + let autoMod: AutoModerator + let testInvalidator: TestInvalidator + let ctx: IndexerContext + let pdsAgent: AtpAgent + let sc: SeedClient + let alice: string + let badBlob1: ImageRef + let badBlob2: ImageRef + let goodBlob: ImageRef + + beforeAll(async () => { + testInvalidator = new TestInvalidator() + network = await TestNetwork.create({ + dbPostgresSchema: 'bsky_automod_takedown', + bsky: { + imgInvalidator: testInvalidator, + }, + }) + ctx = network.bsky.indexer.ctx + autoMod = ctx.autoMod + autoMod.imageFlagger = new TestFlagger() + pdsAgent = new AtpAgent({ service: network.pds.url }) + sc = new SeedClient(pdsAgent) + await usersSeed(sc) + await network.processAll() + alice = sc.dids.alice + const fileBytes1 = await fs.readFile( + 'tests/image/fixtures/key-portrait-small.jpg', + ) + const fileBytes2 = await fs.readFile( + 'tests/image/fixtures/key-portrait-large.jpg', + ) + badCid1 = sha256RawToCid(await sha256(fileBytes1)) + badCid2 = sha256RawToCid(await sha256(fileBytes2)) + goodBlob = await sc.uploadFile( + alice, + 'tests/image/fixtures/key-landscape-small.jpg', + 'image/jpeg', + ) + badBlob1 = await sc.uploadFile( + alice, + 'tests/image/fixtures/key-portrait-small.jpg', + 'image/jpeg', + ) + badBlob2 = await sc.uploadFile( + alice, + 'tests/image/fixtures/key-portrait-large.jpg', + 'image/jpeg', + ) + }) + + afterAll(async () => { + await network.close() + }) + + it('takes down flagged content in posts', async () => { + const post = await sc.post(alice, 'blah', undefined, [goodBlob, badBlob1]) + await network.processAll() + await autoMod.processAll() + const modAction = await ctx.db.db + .selectFrom('moderation_action') + .where('subjectUri', '=', post.ref.uriStr) + .select(['action', 'id']) + .executeTakeFirst() + if (!modAction) { + throw new Error('expected mod action') + } + expect(modAction.action).toEqual('com.atproto.admin.defs#takedown') + const record = await ctx.db.db + .selectFrom('record') + .where('uri', '=', post.ref.uriStr) + .select('takedownId') + .executeTakeFirst() + expect(record?.takedownId).toEqual(modAction.id) + + const recordPds = await network.pds.ctx.db.db + .selectFrom('record') + .where('uri', '=', post.ref.uriStr) + .select('takedownId') + .executeTakeFirst() + expect(recordPds?.takedownId).toEqual(modAction.id.toString()) + + expect(testInvalidator.invalidated.length).toBe(1) + expect(testInvalidator.invalidated[0].subject).toBe( + badBlob1.image.ref.toString(), + ) + }) + + it('takes down flagged content in profiles', async () => { + const res = await pdsAgent.api.com.atproto.repo.putRecord( + { + repo: alice, + collection: ids.AppBskyActorProfile, + rkey: 'self', + record: { + avatar: badBlob2.image, + }, + }, + { headers: sc.getHeaders(alice), encoding: 'application/json' }, + ) + await network.processAll() + const modAction = await ctx.db.db + .selectFrom('moderation_action') + .where('subjectUri', '=', res.data.uri) + .select(['action', 'id']) + .executeTakeFirst() + if (!modAction) { + throw new Error('expected mod action') + } + expect(modAction.action).toEqual('com.atproto.admin.defs#takedown') + const record = await ctx.db.db + .selectFrom('record') + .where('uri', '=', res.data.uri) + .select('takedownId') + .executeTakeFirst() + expect(record?.takedownId).toEqual(modAction.id) + + const recordPds = await network.pds.ctx.db.db + .selectFrom('record') + .where('uri', '=', res.data.uri) + .select('takedownId') + .executeTakeFirst() + expect(recordPds?.takedownId).toEqual(modAction.id.toString()) + + expect(testInvalidator.invalidated.length).toBe(2) + expect(testInvalidator.invalidated[1].subject).toBe( + badBlob2.image.ref.toString(), + ) + }) +}) + +class TestInvalidator implements ImageInvalidator { + public invalidated: { subject: string; paths: string[] }[] = [] + async invalidate(subject: string, paths: string[]) { + this.invalidated.push({ subject, paths }) + } +} + +class TestFlagger implements ImageFlagger { + async scanImage(_did: string, cid: CID): Promise { + if (cid.equals(badCid1)) { + return ['kill-it'] + } else if (cid.equals(badCid2)) { + return ['with-fire'] + } + return [] + } +} diff --git a/packages/bsky/tests/blob-resolver.test.ts b/packages/bsky/tests/blob-resolver.test.ts index 3f067ac66fb..fcb2b657ee5 100644 --- a/packages/bsky/tests/blob-resolver.test.ts +++ b/packages/bsky/tests/blob-resolver.test.ts @@ -20,7 +20,7 @@ describe('blob resolver', () => { const sc = new SeedClient(pdsAgent) await basicSeed(sc) await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() fileDid = sc.dids.carol fileCid = sc.posts[fileDid][0].images[0].image.ref client = axios.create({ diff --git a/packages/bsky/tests/db.test.ts b/packages/bsky/tests/db.test.ts index dcb28efe6cc..bb7562e9a92 100644 --- a/packages/bsky/tests/db.test.ts +++ b/packages/bsky/tests/db.test.ts @@ -1,24 +1,50 @@ import { once } from 'events' +import { sql } from 'kysely' import { wait } from '@atproto/common' import { TestNetwork } from '@atproto/dev-env' import { Database } from '../src' +import { PrimaryDatabase } from '../src/db' import { Leader } from '../src/db/leader' describe('db', () => { let network: TestNetwork - let db: Database + let db: PrimaryDatabase beforeAll(async () => { network = await TestNetwork.create({ dbPostgresSchema: 'bsky_db', }) - db = network.bsky.ctx.db + db = network.bsky.ctx.db.getPrimary() }) afterAll(async () => { await network.close() }) + it('handles client errors without crashing.', async () => { + const tryKillConnection = db.transaction(async (dbTxn) => { + const result = await sql`select pg_backend_pid() as pid;`.execute( + dbTxn.db, + ) + const pid = result.rows[0]?.['pid'] as number + await sql`select pg_terminate_backend(${pid});`.execute(db.db) + await sql`select 1;`.execute(dbTxn.db) + }) + // This should throw, but no unhandled error + await expect(tryKillConnection).rejects.toThrow() + }) + + it('handles pool errors without crashing.', async () => { + const conn1 = await db.pool.connect() + const conn2 = await db.pool.connect() + const result = await conn1.query('select pg_backend_pid() as pid;') + const conn1pid: number = result.rows[0].pid + conn1.release() + await wait(100) // let release apply, conn is now idle on pool. + await conn2.query(`select pg_terminate_backend(${conn1pid});`) + conn2.release() + }) + describe('transaction()', () => { it('commits changes', async () => { const result = await db.transaction(async (dbTxn) => { @@ -126,7 +152,7 @@ describe('db', () => { expect(res.length).toBe(0) }) - it('ensures all inflight querys are rolled back', async () => { + it('ensures all inflight queries are rolled back', async () => { let promise: Promise | undefined = undefined const names: string[] = [] try { diff --git a/packages/bsky/tests/did-cache.test.ts b/packages/bsky/tests/did-cache.test.ts index 9c8a1ec1602..be7544a4a3b 100644 --- a/packages/bsky/tests/did-cache.test.ts +++ b/packages/bsky/tests/did-cache.test.ts @@ -21,8 +21,8 @@ describe('did cache', () => { network = await TestNetwork.create({ dbPostgresSchema: 'bsky_did_cache', }) - idResolver = network.bsky.ctx.idResolver - didCache = network.bsky.ctx.didCache + idResolver = network.bsky.indexer.ctx.idResolver + didCache = network.bsky.indexer.ctx.didCache const pdsAgent = new AtpAgent({ service: network.pds.url }) sc = new SeedClient(pdsAgent) await userSeed(sc) @@ -84,7 +84,7 @@ describe('did cache', () => { }) it('accurately reports expired dids & refreshes the cache', async () => { - const didCache = new DidSqlCache(network.bsky.ctx.db, 1, 60000) + const didCache = new DidSqlCache(network.bsky.ctx.db.getPrimary(), 1, 60000) const shortCacheResolver = new IdResolver({ plcUrl: network.bsky.ctx.cfg.didPlcUrl, didCache, @@ -113,7 +113,7 @@ describe('did cache', () => { }) it('does not return expired dids & refreshes the cache', async () => { - const didCache = new DidSqlCache(network.bsky.ctx.db, 0, 1) + const didCache = new DidSqlCache(network.bsky.ctx.db.getPrimary(), 0, 1) const shortExpireResolver = new IdResolver({ plcUrl: network.bsky.ctx.cfg.didPlcUrl, didCache, diff --git a/packages/bsky/tests/duplicate-records.test.ts b/packages/bsky/tests/duplicate-records.test.ts index d02014d99a7..9c7617bd668 100644 --- a/packages/bsky/tests/duplicate-records.test.ts +++ b/packages/bsky/tests/duplicate-records.test.ts @@ -1,23 +1,24 @@ -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { cidForCbor, TID } from '@atproto/common' import { WriteOpAction } from '@atproto/repo' import { TestNetwork } from '@atproto/dev-env' import { Database } from '../src' +import { PrimaryDatabase } from '../src/db' import * as lex from '../src/lexicon/lexicons' -import { Services } from '../src/services' +import { Services } from '../src/indexer/services' describe('duplicate record', () => { let network: TestNetwork let did: string - let db: Database + let db: PrimaryDatabase let services: Services beforeAll(async () => { network = await TestNetwork.create({ dbPostgresSchema: 'bsky_duplicates', }) - db = network.bsky.ctx.db - services = network.bsky.ctx.services + db = network.bsky.indexer.ctx.db + services = network.bsky.indexer.ctx.services did = 'did:example:alice' }) diff --git a/packages/bsky/tests/feed-generation.test.ts b/packages/bsky/tests/feed-generation.test.ts index 97d16c3e156..17df03de966 100644 --- a/packages/bsky/tests/feed-generation.test.ts +++ b/packages/bsky/tests/feed-generation.test.ts @@ -41,7 +41,6 @@ describe('feed generation', () => { sc = new SeedClient(pdsAgent) await basicSeed(sc) await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() alice = sc.dids.alice const allUri = AtUri.make(alice, 'app.bsky.feed.generator', 'all') const feedUriBadPagination = AtUri.make( @@ -57,6 +56,18 @@ describe('feed generation', () => { [feedUriBadPagination.toString()]: feedGenHandler('bad-pagination'), [primeUri.toString()]: feedGenHandler('prime'), }) + + const feedSuggestions = [ + { uri: allUri.toString(), order: 1 }, + { uri: evenUri.toString(), order: 2 }, + { uri: feedUriBadPagination.toString(), order: 3 }, + { uri: primeUri.toString(), order: 4 }, + ] + await network.bsky.ctx.db + .getPrimary() + .db.insertInto('suggested_feed') + .values(feedSuggestions) + .execute() }) afterAll(async () => { @@ -166,7 +177,6 @@ describe('feed generation', () => { { headers: sc.getHeaders(alice), encoding: 'application/json' }, ) await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() }) it('getActorFeeds fetches feed generators by actor.', async () => { @@ -174,7 +184,6 @@ describe('feed generation', () => { await sc.like(sc.dids.bob, feedUriAllRef) await sc.like(sc.dids.carol, feedUriAllRef) await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() const results = (results) => results.flatMap((res) => res.feeds) const paginator = async (cursor?: string) => { @@ -210,7 +219,6 @@ describe('feed generation', () => { sc.getHeaders(sc.dids.bob), ) await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() const view = await agent.api.app.bsky.feed.getPosts( { uris: [res.uri] }, { headers: await network.serviceHeaders(sc.dids.bob) }, @@ -293,7 +301,7 @@ describe('feed generation', () => { sc.getHeaders(sc.dids.bob), ) await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() // now take it offline await bobFg.close() @@ -320,6 +328,17 @@ describe('feed generation', () => { }) }) + describe('getSuggestedFeeds', () => { + it('returns list of suggested feed generators', async () => { + const resEven = await agent.api.app.bsky.feed.getSuggestedFeeds( + {}, + { headers: await network.serviceHeaders(sc.dids.bob) }, + ) + expect(forSnapshot(resEven.data)).toMatchSnapshot() + expect(resEven.data.feeds.map((fg) => fg.uri)).not.toContain(feedUriPrime) // taken-down + }) + }) + describe('getPopularFeedGenerators', () => { it('gets popular feed generators', async () => { const resEven = @@ -330,6 +349,28 @@ describe('feed generation', () => { expect(resEven.data.feeds.map((f) => f.likeCount)).toEqual([2, 0, 0, 0]) expect(resEven.data.feeds.map((f) => f.uri)).not.toContain(feedUriPrime) // taken-down }) + + it('paginates', async () => { + const resFull = + await agent.api.app.bsky.unspecced.getPopularFeedGenerators( + {}, + { headers: await network.serviceHeaders(sc.dids.bob) }, + ) + + const resOne = + await agent.api.app.bsky.unspecced.getPopularFeedGenerators( + { limit: 2 }, + { headers: await network.serviceHeaders(sc.dids.bob) }, + ) + const resTwo = + await agent.api.app.bsky.unspecced.getPopularFeedGenerators( + { cursor: resOne.data.cursor }, + { headers: await network.serviceHeaders(sc.dids.bob) }, + ) + expect([...resOne.data.feeds, ...resTwo.data.feeds]).toEqual( + resFull.data.feeds, + ) + }) }) describe('getFeed', () => { diff --git a/packages/bsky/tests/handle-invalidation.test.ts b/packages/bsky/tests/handle-invalidation.test.ts new file mode 100644 index 00000000000..3b9ae789265 --- /dev/null +++ b/packages/bsky/tests/handle-invalidation.test.ts @@ -0,0 +1,132 @@ +import { DAY } from '@atproto/common' +import { TestNetwork } from '@atproto/dev-env' +import { AtpAgent } from '@atproto/api' +import { SeedClient } from './seeds/client' +import userSeed from './seeds/users' + +describe('handle invalidation', () => { + let network: TestNetwork + let agent: AtpAgent + let pdsAgent: AtpAgent + let sc: SeedClient + let alice: string + let bob: string + + const mockHandles = {} + + beforeAll(async () => { + network = await TestNetwork.create({ + dbPostgresSchema: 'bsky_handle_invalidation', + }) + agent = network.bsky.getClient() + pdsAgent = network.pds.getClient() + sc = new SeedClient(pdsAgent) + await userSeed(sc) + await network.processAll() + + alice = sc.dids.alice + bob = sc.dids.bob + + const origResolve = network.bsky.indexer.ctx.idResolver.handle.resolve + network.bsky.indexer.ctx.idResolver.handle.resolve = async ( + handle: string, + ) => { + if (mockHandles[handle] === null) { + return undefined + } else if (mockHandles[handle]) { + return mockHandles[handle] + } + return origResolve(handle) + } + }) + + afterAll(async () => { + await network.close() + }) + + const backdateIndexedAt = async (did: string) => { + const TWO_DAYS_AGO = new Date(Date.now() - 2 * DAY).toISOString() + await network.bsky.ctx.db + .getPrimary() + .db.updateTable('actor') + .set({ indexedAt: TWO_DAYS_AGO }) + .where('did', '=', did) + .execute() + } + + it('indexes an account with no proper handle', async () => { + mockHandles['eve.test'] = null + const eveAccnt = await sc.createAccount('eve', { + handle: 'eve.test', + email: 'eve@test.com', + password: 'eve-pass', + }) + await network.processAll() + + const res = await agent.api.app.bsky.actor.getProfile( + { actor: eveAccnt.did }, + { headers: await network.serviceHeaders(alice) }, + ) + expect(res.data.handle).toEqual('handle.invalid') + }) + + it('invalidates out of date handles', async () => { + await backdateIndexedAt(alice) + + const aliceHandle = sc.accounts[alice].handle + // alice's handle no longer resolves + mockHandles[aliceHandle] = null + await sc.post(alice, 'blah') + await network.processAll() + const res = await agent.api.app.bsky.actor.getProfile( + { actor: alice }, + { headers: await network.serviceHeaders(alice) }, + ) + expect(res.data.handle).toEqual('handle.invalid') + }) + + it('revalidates an out of date handle', async () => { + await backdateIndexedAt(alice) + const aliceHandle = sc.accounts[alice].handle + // alice's handle no longer resolves + delete mockHandles[aliceHandle] + + await sc.post(alice, 'blah') + await network.processAll() + const res = await agent.api.app.bsky.actor.getProfile( + { actor: alice }, + { headers: await network.serviceHeaders(alice) }, + ) + expect(res.data.handle).toEqual(sc.accounts[alice].handle) + }) + + it('deals with handle contention', async () => { + await backdateIndexedAt(bob) + // update alices handle so that the pds will let bob take her old handle + await network.pds.ctx.db.db + .updateTable('did_handle') + .where('did', '=', alice) + .set({ handle: 'not-alice.test' }) + .execute() + + await pdsAgent.api.com.atproto.identity.updateHandle( + { + handle: sc.accounts[alice].handle, + }, + { headers: sc.getHeaders(bob), encoding: 'application/json' }, + ) + await network.processAll() + + const aliceRes = await agent.api.app.bsky.actor.getProfile( + { actor: alice }, + { headers: await network.serviceHeaders(alice) }, + ) + expect(aliceRes.data.handle).toEqual('handle.invalid') + + const bobRes = await agent.api.app.bsky.actor.getProfile( + { actor: bob }, + { headers: await network.serviceHeaders(alice) }, + ) + expect(bobRes.data.handle).toEqual(sc.accounts[alice].handle) + }) +}) diff --git a/packages/bsky/tests/image/server.test.ts b/packages/bsky/tests/image/server.test.ts index ff887a28920..072059b52df 100644 --- a/packages/bsky/tests/image/server.test.ts +++ b/packages/bsky/tests/image/server.test.ts @@ -6,6 +6,7 @@ import { TestNetwork } from '@atproto/dev-env' import { getInfo } from '../../src/image/sharp' import { SeedClient } from '../seeds/client' import basicSeed from '../seeds/basic' +import { ImageUriBuilder } from '../../src/image/uri' describe('image processing server', () => { let network: TestNetwork @@ -16,21 +17,16 @@ describe('image processing server', () => { beforeAll(async () => { network = await TestNetwork.create({ dbPostgresSchema: 'bsky_image_processing_server', - bsky: { - imgUriKey: - 'f23ecd142835025f42c3db2cf25dd813956c178392760256211f9d315f8ab4d8', - imgUriSalt: '9dd04221f5755bce5f55f47464c27e1e', - }, }) const pdsAgent = new AtpAgent({ service: network.pds.url }) const sc = new SeedClient(pdsAgent) await basicSeed(sc) await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() fileDid = sc.dids.carol fileCid = sc.posts[fileDid][0].images[0].image.ref client = axios.create({ - baseURL: `${network.bsky.url}/image`, + baseURL: `${network.bsky.url}/img`, validateStatus: () => true, }) }) @@ -41,42 +37,36 @@ describe('image processing server', () => { it('processes image from blob resolver.', async () => { const res = await client.get( - network.bsky.ctx.imgUriBuilder.getSignedPath({ + ImageUriBuilder.getPath({ + preset: 'feed_fullsize', did: fileDid, cid: fileCid, - format: 'jpeg', - fit: 'cover', - width: 500, - height: 500, - min: true, }), { responseType: 'stream' }, ) const info = await getInfo(res.data) - expect(info).toEqual( - expect.objectContaining({ - height: 500, - width: 500, - size: 67221, - }), - ) + + expect(info).toEqual({ + height: 580, + width: 1000, + size: 127578, + mime: 'image/jpeg', + }) expect(res.headers).toEqual( expect.objectContaining({ 'content-type': 'image/jpeg', 'cache-control': 'public, max-age=31536000', - 'content-length': '67221', + 'content-length': '127578', }), ) }) it('caches results.', async () => { - const path = network.bsky.ctx.imgUriBuilder.getSignedPath({ + const path = ImageUriBuilder.getPath({ + preset: 'avatar', did: fileDid, cid: fileCid, - format: 'jpeg', - width: 25, // Special number for this test - height: 25, }) const res1 = await client.get(path, { responseType: 'arraybuffer' }) expect(res1.headers['x-cache']).toEqual('miss') @@ -88,32 +78,13 @@ describe('image processing server', () => { expect(Buffer.compare(res1.data, res3.data)).toEqual(0) }) - it('errors on bad signature.', async () => { - const path = network.bsky.ctx.imgUriBuilder.getSignedPath({ - did: fileDid, - cid: fileCid, - format: 'jpeg', - fit: 'cover', - width: 500, - height: 500, - min: true, - }) - const res = await client.get(path.replace('/', '/_'), {}) - expect(res.status).toEqual(400) - expect(res.data).toEqual({ message: 'Invalid path: bad signature' }) - }) - it('errors on missing file.', async () => { const missingCid = await cidForCbor('missing-file') const res = await client.get( - network.bsky.ctx.imgUriBuilder.getSignedPath({ + ImageUriBuilder.getPath({ + preset: 'feed_fullsize', did: fileDid, cid: missingCid, - format: 'jpeg', - fit: 'cover', - width: 500, - height: 500, - min: true, }), ) expect(res.status).toEqual(404) diff --git a/packages/bsky/tests/image/uri.test.ts b/packages/bsky/tests/image/uri.test.ts index 63f0b3204f5..60586d23f6b 100644 --- a/packages/bsky/tests/image/uri.test.ts +++ b/packages/bsky/tests/image/uri.test.ts @@ -5,220 +5,80 @@ import { ImageUriBuilder, BadPathError } from '../../src/image/uri' describe('image uri builder', () => { let uriBuilder: ImageUriBuilder let cid: CID - const did = 'plc:did:xyz' + const did = 'did:plc:xyz' beforeAll(async () => { - const endpoint = 'https://example.com' - const salt = '9dd04221f5755bce5f55f47464c27e1e' - const key = - 'f23ecd142835025f42c3db2cf25dd813956c178392760256211f9d315f8ab4d8' - uriBuilder = new ImageUriBuilder(endpoint, salt, key) + const endpoint = 'https://example.com/img' + uriBuilder = new ImageUriBuilder(endpoint) cid = await cidForCbor('test cid') }) - it('signs and verifies uri options.', () => { - const path = uriBuilder.getSignedPath({ - did, - cid, - format: 'png', - height: 200, - width: 300, - }) - expect(path).toEqual( - `/1Hl07jYd8LUqPDAGVVw3Le2iT0OaH4l4dPbmh2lL21Y/rs:fill:300:200:0:0/plain/${did}/${cid.toString()}@png`, + it('generates paths.', () => { + expect(ImageUriBuilder.getPath({ preset: 'banner', did, cid })).toEqual( + `/banner/plain/${did}/${cid.toString()}@jpeg`, ) - expect(uriBuilder.getVerifiedOptions(path)).toEqual({ - signature: '1Hl07jYd8LUqPDAGVVw3Le2iT0OaH4l4dPbmh2lL21Y', - did, - cid, - format: 'png', - fit: 'cover', - height: 200, - width: 300, - min: false, - }) - }) - - it('errors on bad signature.', () => { - const tryGetVerifiedOptions = (path) => () => - uriBuilder.getVerifiedOptions(path) - - tryGetVerifiedOptions( - // Confirm this is a good signed uri - `/BtHM_4IOak5MOc2gOPDxbfS4_HG6VPcry2OAV03L29g/rs:fill:300:200:0:0/plain/${did}/${cid.toString()}@png`, - ) - - expect( - tryGetVerifiedOptions( - // Tamper with signature - `/DtHM_4IOak5MOc2gOPDxbfS4_HG6VPcry2OAV03L29g/rs:fill:300:200:0:0/plain/${did}/${cid.toString()}@png`, - ), - ).toThrow(new BadPathError('Invalid path: bad signature')) - expect( - tryGetVerifiedOptions( - // Tamper with params - `/DtHM_4IOak5MOc2gOPDxbfS4_HG6VPcry2OAV03L29g/rs:fill:300:200:0:0/plain/${did}/${cid.toString()}@jpeg`, - ), - ).toThrow(new BadPathError('Invalid path: bad signature')) - - expect( - tryGetVerifiedOptions( - // Missing signature - `/rs:fill:300:200:0:0/plain/${did}/${cid.toString()}@jpeg`, - ), - ).toThrow(new BadPathError('Invalid path: missing signature')) + ImageUriBuilder.getPath({ preset: 'feed_thumbnail', did, cid }), + ).toEqual(`/feed_thumbnail/plain/${did}/${cid.toString()}@jpeg`) }) - it('supports basic options.', () => { - const path = ImageUriBuilder.getPath({ - did, - cid, - format: 'png', - height: 200, - width: 300, - }) - expect(path).toEqual( - `/rs:fill:300:200:0:0/plain/${did}/${cid.toString()}@png`, + it('generates uris.', () => { + expect(uriBuilder.getPresetUri('banner', did, cid)).toEqual( + `https://example.com/img/banner/plain/${did}/${cid.toString()}@jpeg`, ) - expect(ImageUriBuilder.getOptions(path)).toEqual({ - did, - cid, - format: 'png', - fit: 'cover', - height: 200, - width: 300, - min: false, - }) - }) - - it('supports fit option.', () => { - const path = ImageUriBuilder.getPath({ - did, - cid, - format: 'png', - fit: 'inside', - height: 200, - width: 300, - }) - expect(path).toEqual( - `/rs:fit:300:200:0:0/plain/${did}/${cid.toString()}@png`, + expect( + uriBuilder.getPresetUri('feed_thumbnail', did, cid.toString()), + ).toEqual( + `https://example.com/img/feed_thumbnail/plain/${did}/${cid.toString()}@jpeg`, ) - expect(ImageUriBuilder.getOptions(path)).toEqual({ - did, - cid, - format: 'png', - fit: 'inside', - height: 200, - width: 300, - min: false, - }) }) - it('supports min=true option.', () => { - const path = ImageUriBuilder.getPath({ - did, - cid, - format: 'png', - height: 200, - width: 300, - min: true, - }) - expect(path).toEqual( - `/rs:fill:300:200:1:0/plain/${did}/${cid.toString()}@png`, - ) - expect(ImageUriBuilder.getOptions(path)).toEqual({ - did, + it('parses options.', () => { + expect( + ImageUriBuilder.getOptions(`/banner/plain/${did}/${cid.toString()}@png`), + ).toEqual({ + did: 'did:plc:xyz', cid, - format: 'png', fit: 'cover', - height: 200, - width: 300, + format: 'png', + height: 1000, min: true, + preset: 'banner', + width: 3000, }) - }) - - it('supports min={height,width} option.', () => { - const path = ImageUriBuilder.getPath({ - did, - cid, - format: 'jpeg', - height: 200, - width: 300, - min: { height: 50, width: 100 }, - }) - expect(path).toEqual( - `/rs:fill:300:200:0:0/mw:100/mh:50/plain/${did}/${cid.toString()}@jpeg`, - ) - expect(ImageUriBuilder.getOptions(path)).toEqual({ - did, + expect( + ImageUriBuilder.getOptions( + `/feed_thumbnail/plain/${did}/${cid.toString()}@jpeg`, + ), + ).toEqual({ + did: 'did:plc:xyz', cid, + fit: 'inside', format: 'jpeg', - fit: 'cover', - height: 200, - width: 300, - min: { height: 50, width: 100 }, + height: 2000, + min: true, + preset: 'feed_thumbnail', + width: 2000, }) }) - it('errors on bad did/cid/format part.', () => { - expect( - tryGetOptions(`/rs:fill:300:200:1:0/plain/${did}/${cid.toString()}@mp4`), - ).toThrow(new BadPathError('Invalid path: bad format')) - expect(tryGetOptions(`/rs:fill:300:200:1:0/plain/@jpg`)).toThrow( + it('errors on bad url pattern.', () => { + expect(tryGetOptions(`/a`)).toThrow(new BadPathError('Invalid path')) + expect(tryGetOptions(`/banner/plain/${did}@jpeg`)).toThrow( new BadPathError('Invalid path'), ) - expect( - tryGetOptions(`/rs:fill:300:200:1:0/plain/${did}/${cid.toString()}@`), - ).toThrow(new BadPathError('Invalid path')) - expect( - tryGetOptions(`/rs:fill:300:200:1:0/plain/${did}/${cid.toString()}@`), - ).toThrow(new BadPathError('Invalid path')) - expect( - tryGetOptions( - `/rs:fill:300:200:1:0/plain/${did}/${cid.toString()}@x@jpeg`, - ), - ).toThrow(new BadPathError('Invalid path: bad format')) }) - it('errors on mismatching min settings.', () => { - expect( - tryGetOptions( - `/rs:fill:300:200:1:0/mw:100/mh:50/plain/${did}/${did}/${cid.toString()}@jpeg`, - ), - ).toThrow(new BadPathError('Invalid path: bad min width/height param')) + it('errors on bad preset.', () => { expect( - tryGetOptions( - `/rs:fill:300:200:0:0/mw:100/plain/${did}/${did}/${cid.toString()}@jpeg`, - ), - ).toThrow(new BadPathError('Invalid path: bad min width/height param')) + tryGetOptions(`/bad_banner/plain/${did}/${cid.toString()}@jpeg`), + ).toThrow(new BadPathError('Invalid path: bad preset')) }) - it('errors on bad fit setting.', () => { + it('errors on bad format.', () => { expect( - tryGetOptions( - `/rs:blah:300:200:1:0/plain/${did}/${did}/${cid.toString()}@jpeg`, - ), - ).toThrow(new BadPathError('Invalid path: bad resize fit param')) - }) - - it('errors on bad dimension settings.', () => { - expect( - tryGetOptions(`/rs:fill:30x:200:1:0/plain/${did}/${cid.toString()}@jpeg`), - ).toThrow(new BadPathError('Invalid path: bad resize height/width param')) - expect( - tryGetOptions(`/rs:fill:300:20x:1:0/plain/${did}/${cid.toString()}@jpeg`), - ).toThrow(new BadPathError('Invalid path: bad resize height/width param')) - expect( - tryGetOptions( - `/rs:fill:300:200:1:0/mw:10x/mh:50/plain/${did}/${cid.toString()}@jpeg`, - ), - ).toThrow(new BadPathError('Invalid path: bad min width/height param')) - expect( - tryGetOptions( - `/rs:fill:300:200:1:0/mw:100/mh:5x/plain/${did}/${cid.toString()}@jpeg`, - ), - ).toThrow(new BadPathError('Invalid path: bad min width/height param')) + tryGetOptions(`/banner/plain/${did}/${cid.toString()}@webp`), + ).toThrow(new BadPathError('Invalid path: bad format')) }) function tryGetOptions(path: string) { diff --git a/packages/bsky/tests/indexing.test.ts b/packages/bsky/tests/indexing.test.ts index 180b7b643e6..cee3ed5a768 100644 --- a/packages/bsky/tests/indexing.test.ts +++ b/packages/bsky/tests/indexing.test.ts @@ -3,8 +3,7 @@ import { CID } from 'multiformats/cid' import { cidForCbor, TID } from '@atproto/common' import * as pdsRepo from '@atproto/pds/src/repo/prepare' import { WriteOpAction } from '@atproto/repo' -import { AtUri } from '@atproto/uri' -import { Client } from '@did-plc/lib' +import { AtUri } from '@atproto/syntax' import AtpAgent, { AppBskyActorProfile, AppBskyFeedPost, @@ -36,8 +35,9 @@ describe('indexing', () => { await usersSeed(sc) // Data in tests is not processed from subscription await network.processAll() - await network.bsky.sub.destroy() - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.ingester.sub.destroy() + await network.bsky.indexer.sub.destroy() + await network.bsky.processAll() }) afterAll(async () => { @@ -45,7 +45,7 @@ describe('indexing', () => { }) it('indexes posts.', async () => { - const { db, services } = network.bsky.ctx + const { db, services } = network.bsky.indexer.ctx const createdAt = new Date().toISOString() const createRecord = await prepareCreate({ did: sc.dids.alice, @@ -135,7 +135,7 @@ describe('indexing', () => { }) it('indexes profiles.', async () => { - const { db, services } = network.bsky.ctx + const { db, services } = network.bsky.indexer.ctx const createRecord = await prepareCreate({ did: sc.dids.dan, collection: ids.AppBskyActorProfile, @@ -190,7 +190,7 @@ describe('indexing', () => { }) it('handles post aggregations out of order.', async () => { - const { db, services } = network.bsky.ctx + const { db, services } = network.bsky.indexer.ctx const createdAt = new Date().toISOString() const originalPost = await prepareCreate({ did: sc.dids.alice, @@ -241,7 +241,7 @@ describe('indexing', () => { await services.indexing(db).indexRecord(...like) await services.indexing(db).indexRecord(...repost) await services.indexing(db).indexRecord(...originalPost) - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() const agg = await db.db .selectFrom('post_agg') .selectAll() @@ -267,8 +267,105 @@ describe('indexing', () => { await services.indexing(db).deleteRecord(...del(originalPost[0])) }) + it('does not notify user of own like or repost', async () => { + const { db, services } = network.bsky.indexer.ctx + const createdAt = new Date().toISOString() + + const originalPost = await prepareCreate({ + did: sc.dids.bob, + collection: ids.AppBskyFeedPost, + record: { + $type: ids.AppBskyFeedPost, + text: 'original post', + createdAt, + } as AppBskyFeedPost.Record, + }) + + const originalPostRef = { + uri: originalPost[0].toString(), + cid: originalPost[1].toString(), + } + + // own actions + const ownLike = await prepareCreate({ + did: sc.dids.bob, + collection: ids.AppBskyFeedLike, + record: { + $type: ids.AppBskyFeedLike, + subject: originalPostRef, + createdAt, + } as AppBskyFeedLike.Record, + }) + const ownRepost = await prepareCreate({ + did: sc.dids.bob, + collection: ids.AppBskyFeedRepost, + record: { + $type: ids.AppBskyFeedRepost, + subject: originalPostRef, + createdAt, + } as AppBskyFeedRepost.Record, + }) + + // other actions + const aliceLike = await prepareCreate({ + did: sc.dids.alice, + collection: ids.AppBskyFeedLike, + record: { + $type: ids.AppBskyFeedLike, + subject: originalPostRef, + createdAt, + } as AppBskyFeedLike.Record, + }) + const aliceRepost = await prepareCreate({ + did: sc.dids.alice, + collection: ids.AppBskyFeedRepost, + record: { + $type: ids.AppBskyFeedRepost, + subject: originalPostRef, + createdAt, + } as AppBskyFeedRepost.Record, + }) + + await services.indexing(db).indexRecord(...originalPost) + await services.indexing(db).indexRecord(...ownLike) + await services.indexing(db).indexRecord(...ownRepost) + await services.indexing(db).indexRecord(...aliceLike) + await services.indexing(db).indexRecord(...aliceRepost) + + await network.bsky.processAll() + + const { + data: { notifications }, + } = await agent.api.app.bsky.notification.listNotifications( + {}, + { headers: await network.serviceHeaders(sc.dids.bob) }, + ) + + expect(notifications).toHaveLength(2) + expect( + notifications.every((n) => { + return n.author.did !== sc.dids.bob + }), + ).toBeTruthy() + + // Cleanup + const del = (uri: AtUri) => { + return prepareDelete({ + did: uri.host, + collection: uri.collection, + rkey: uri.rkey, + }) + } + + await services.indexing(db).deleteRecord(...del(ownLike[0])) + await services.indexing(db).deleteRecord(...del(ownRepost[0])) + await services.indexing(db).deleteRecord(...del(aliceLike[0])) + await services.indexing(db).deleteRecord(...del(aliceRepost[0])) + await services.indexing(db).deleteRecord(...del(originalPost[0])) + }) + it('handles profile aggregations out of order.', async () => { - const { db, services } = network.bsky.ctx + const { db, services } = network.bsky.indexer.ctx const createdAt = new Date().toISOString() const unknownDid = 'did:example:unknown' const follow = await prepareCreate({ @@ -281,7 +378,7 @@ describe('indexing', () => { } as AppBskyGraphFollow.Record, }) await services.indexing(db).indexRecord(...follow) - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() const agg = await db.db .selectFrom('profile_agg') .select(['did', 'followersCount']) @@ -304,15 +401,17 @@ describe('indexing', () => { describe('indexRepo', () => { beforeAll(async () => { - network.bsky.sub.resume() + network.bsky.indexer.sub.resume() + network.bsky.ingester.sub.resume() await basicSeed(sc, false) await network.processAll() - await network.bsky.sub.destroy() - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.ingester.sub.destroy() + await network.bsky.indexer.sub.destroy() + await network.bsky.processAll() }) it('preserves indexes when no record changes.', async () => { - const { db, services } = network.bsky.ctx + const { db, services } = network.bsky.indexer.ctx // Mark originals const { data: origProfile } = await agent.api.app.bsky.actor.getProfile( { actor: sc.dids.alice }, @@ -327,10 +426,12 @@ describe('indexing', () => { { headers: await network.serviceHeaders(sc.dids.alice) }, ) // Index - const { data: head } = await pdsAgent.api.com.atproto.sync.getHead({ - did: sc.dids.alice, - }) - await services.indexing(db).indexRepo(sc.dids.alice, head.root) + const { data: commit } = + await pdsAgent.api.com.atproto.sync.getLatestCommit({ + did: sc.dids.alice, + }) + await services.indexing(db).indexRepo(sc.dids.alice, commit.cid) + await network.bsky.processAll() // Check const { data: profile } = await agent.api.app.bsky.actor.getProfile( { actor: sc.dids.alice }, @@ -350,7 +451,7 @@ describe('indexing', () => { }) it('updates indexes when records change.', async () => { - const { db, services } = network.bsky.ctx + const { db, services } = network.bsky.indexer.ctx // Update profile await pdsAgent.api.com.atproto.repo.putRecord( { @@ -370,10 +471,12 @@ describe('indexing', () => { sc.getHeaders(sc.dids.alice), ) // Index - const { data: head } = await pdsAgent.api.com.atproto.sync.getHead({ - did: sc.dids.alice, - }) - await services.indexing(db).indexRepo(sc.dids.alice, head.root) + const { data: commit } = + await pdsAgent.api.com.atproto.sync.getLatestCommit({ + did: sc.dids.alice, + }) + await services.indexing(db).indexRepo(sc.dids.alice, commit.cid) + await network.bsky.processAll() // Check const { data: profile } = await agent.api.app.bsky.actor.getProfile( { actor: sc.dids.alice }, @@ -395,7 +498,7 @@ describe('indexing', () => { }) it('skips invalid records.', async () => { - const { db, services } = network.bsky.ctx + const { db, services } = network.bsky.indexer.ctx const { db: pdsDb, services: pdsServices } = network.pds.ctx // Create a good and a bad post record const writes = await Promise.all([ @@ -415,10 +518,11 @@ describe('indexing', () => { .repo(pdsDb) .processWrites({ did: sc.dids.alice, writes }, 1) // Index - const { data: head } = await pdsAgent.api.com.atproto.sync.getHead({ - did: sc.dids.alice, - }) - await services.indexing(db).indexRepo(sc.dids.alice, head.root) + const { data: commit } = + await pdsAgent.api.com.atproto.sync.getLatestCommit({ + did: sc.dids.alice, + }) + await services.indexing(db).indexRepo(sc.dids.alice, commit.cid) // Check const getGoodPost = agent.api.app.bsky.feed.getPostThread( { uri: writes[0].uri.toString(), depth: 0 }, @@ -443,7 +547,7 @@ describe('indexing', () => { } it('indexes handle for a fresh did', async () => { - const { db, services } = network.bsky.ctx + const { db, services } = network.bsky.indexer.ctx const now = new Date().toISOString() const sessionAgent = new AtpAgent({ service: network.pds.url }) const { @@ -459,7 +563,7 @@ describe('indexing', () => { }) it('reindexes handle for existing did when forced', async () => { - const { db, services } = network.bsky.ctx + const { db, services } = network.bsky.indexer.ctx const now = new Date().toISOString() const sessionAgent = new AtpAgent({ service: network.pds.url }) const { @@ -481,7 +585,7 @@ describe('indexing', () => { }) it('handles profile aggregations out of order', async () => { - const { db, services } = network.bsky.ctx + const { db, services } = network.bsky.indexer.ctx const now = new Date().toISOString() const sessionAgent = new AtpAgent({ service: network.pds.url }) const { @@ -502,7 +606,7 @@ describe('indexing', () => { }) await services.indexing(db).indexRecord(...follow) await services.indexing(db).indexHandle(did, now) - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() const agg = await db.db .selectFrom('profile_agg') .select(['did', 'followersCount']) @@ -517,7 +621,7 @@ describe('indexing', () => { describe('tombstoneActor', () => { it('does not unindex actor when they are still being hosted by their pds', async () => { - const { db, services } = network.bsky.ctx + const { db, services } = network.bsky.indexer.ctx const { data: profileBefore } = await agent.api.app.bsky.actor.getProfile( { actor: sc.dids.alice }, { headers: await network.serviceHeaders(sc.dids.bob) }, @@ -532,7 +636,7 @@ describe('indexing', () => { }) it('unindexes actor when they are no longer hosted by their pds', async () => { - const { db, services } = network.bsky.ctx + const { db, services } = network.bsky.indexer.ctx const { alice } = sc.dids const getProfileBefore = agent.api.app.bsky.actor.getProfile( { actor: alice }, diff --git a/packages/bsky/tests/moderation.test.ts b/packages/bsky/tests/moderation.test.ts index c490f1a36e0..109b576bb6f 100644 --- a/packages/bsky/tests/moderation.test.ts +++ b/packages/bsky/tests/moderation.test.ts @@ -1,11 +1,13 @@ import { TestNetwork } from '@atproto/dev-env' +import { TID, cidForCbor } from '@atproto/common' import AtpAgent, { ComAtprotoAdminTakeModerationAction } from '@atproto/api' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { forSnapshot } from './_util' import { ImageRef, RecordRef, SeedClient } from './seeds/client' import basicSeed from './seeds/basic' import { ACKNOWLEDGE, + ESCALATE, FLAG, TAKEDOWN, } from '../src/lexicon/types/com/atproto/admin/defs' @@ -13,7 +15,7 @@ import { REASONOTHER, REASONSPAM, } from '../src/lexicon/types/com/atproto/moderation/defs' -import { TID, cidForCbor } from '@atproto/common' +import { PeriodicModerationActionReversal } from '../src' describe('moderation', () => { let network: TestNetwork @@ -210,7 +212,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) const { data: actionResolvedReports } = @@ -222,7 +224,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) @@ -237,7 +239,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) }) @@ -292,7 +294,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) await agent.api.com.atproto.admin.resolveModerationReports( @@ -303,24 +305,24 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) // Check report and action details const { data: recordActionDetail } = await agent.api.com.atproto.admin.getModerationAction( { id: action.id }, - { headers: network.pds.adminAuthHeaders() }, + { headers: network.bsky.adminAuthHeaders() }, ) const { data: reportADetail } = await agent.api.com.atproto.admin.getModerationReport( { id: reportA.id }, - { headers: network.pds.adminAuthHeaders() }, + { headers: network.bsky.adminAuthHeaders() }, ) const { data: reportBDetail } = await agent.api.com.atproto.admin.getModerationReport( { id: reportB.id }, - { headers: network.pds.adminAuthHeaders() }, + { headers: network.bsky.adminAuthHeaders() }, ) expect( forSnapshot({ @@ -338,7 +340,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) }) @@ -371,7 +373,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) @@ -383,7 +385,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) @@ -400,7 +402,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) }) @@ -437,7 +439,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) @@ -449,7 +451,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) @@ -466,18 +468,18 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) }) - it('supports flagging and acknowledging.', async () => { + it('supports escalating and acknowledging for triage.', async () => { const postRef1 = sc.posts[sc.dids.alice][0].ref const postRef2 = sc.posts[sc.dids.bob][0].ref const { data: action1 } = await agent.api.com.atproto.admin.takeModerationAction( { - action: FLAG, + action: ESCALATE, subject: { $type: 'com.atproto.repo.strongRef', uri: postRef1.uri.toString(), @@ -488,12 +490,12 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders('triage'), }, ) expect(action1).toEqual( expect.objectContaining({ - action: FLAG, + action: ESCALATE, subject: { $type: 'com.atproto.repo.strongRef', uri: postRef1.uriStr, @@ -515,7 +517,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders('triage'), }, ) expect(action2).toEqual( @@ -537,7 +539,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders('triage'), }, ) await agent.api.com.atproto.admin.reverseModerationAction( @@ -548,7 +550,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders('triage'), }, ) }) @@ -569,7 +571,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) const flagPromise = agent.api.com.atproto.admin.takeModerationAction( @@ -585,7 +587,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) await expect(flagPromise).rejects.toThrow( @@ -601,7 +603,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) const { data: flag } = @@ -618,7 +620,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) @@ -631,7 +633,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) }) @@ -650,7 +652,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) const flagPromise = agent.api.com.atproto.admin.takeModerationAction( @@ -665,7 +667,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) await expect(flagPromise).rejects.toThrow( @@ -681,7 +683,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) const { data: flag } = @@ -697,7 +699,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) @@ -710,7 +712,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) }) @@ -734,7 +736,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) const flagPromise = agent.api.com.atproto.admin.takeModerationAction( @@ -751,7 +753,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) await expect(flagPromise).rejects.toThrow( @@ -766,7 +768,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) const { data: flag } = @@ -784,7 +786,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) @@ -797,7 +799,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) }) @@ -805,7 +807,7 @@ describe('moderation', () => { it('negates an existing label and reverses.', async () => { const { ctx } = network.bsky const post = sc.posts[sc.dids.bob][0].ref - const labelingService = ctx.services.label(ctx.db) + const labelingService = ctx.services.label(ctx.db.getPrimary()) await labelingService.formatAndCreate( ctx.cfg.labelerDid, post.uriStr, @@ -835,7 +837,7 @@ describe('moderation', () => { it('no-ops when negating an already-negated label and reverses.', async () => { const { ctx } = network.bsky const post = sc.posts[sc.dids.bob][0].ref - const labelingService = ctx.services.label(ctx.db) + const labelingService = ctx.services.label(ctx.db.getPrimary()) const action = await actionWithLabels({ negateLabelVals: ['bears'], subject: { @@ -878,7 +880,7 @@ describe('moderation', () => { it('no-ops when creating an existing label and reverses.', async () => { const { ctx } = network.bsky const post = sc.posts[sc.dids.bob][0].ref - const labelingService = ctx.services.label(ctx.db) + const labelingService = ctx.services.label(ctx.db.getPrimary()) await labelingService.formatAndCreate( ctx.cfg.labelerDid, post.uriStr, @@ -917,7 +919,7 @@ describe('moderation', () => { it('creates and negates labels on a repo and reverses.', async () => { const { ctx } = network.bsky - const labelingService = ctx.services.label(ctx.db) + const labelingService = ctx.services.label(ctx.db.getPrimary()) await labelingService.formatAndCreate( ctx.cfg.labelerDid, sc.dids.bob, @@ -937,6 +939,119 @@ describe('moderation', () => { await expect(getRepoLabels(sc.dids.bob)).resolves.toEqual(['kittens']) }) + it('does not allow triage moderators to label.', async () => { + const attemptLabel = agent.api.com.atproto.admin.takeModerationAction( + { + action: ACKNOWLEDGE, + createdBy: 'did:example:moderator', + reason: 'Y', + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: sc.dids.bob, + }, + negateLabelVals: ['a'], + createLabelVals: ['b', 'c'], + }, + { + encoding: 'application/json', + headers: network.bsky.adminAuthHeaders('triage'), + }, + ) + await expect(attemptLabel).rejects.toThrow( + 'Must be a full moderator to label content', + ) + }) + + it('allows full moderators to takedown.', async () => { + const { data: action } = + await agent.api.com.atproto.admin.takeModerationAction( + { + action: TAKEDOWN, + createdBy: 'did:example:moderator', + reason: 'Y', + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: sc.dids.bob, + }, + }, + { + encoding: 'application/json', + headers: network.bsky.adminAuthHeaders('moderator'), + }, + ) + // cleanup + await reverse(action.id) + }) + + it('does not allow non-full moderators to takedown.', async () => { + const attemptTakedownTriage = + agent.api.com.atproto.admin.takeModerationAction( + { + action: TAKEDOWN, + createdBy: 'did:example:moderator', + reason: 'Y', + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: sc.dids.bob, + }, + }, + { + encoding: 'application/json', + headers: network.bsky.adminAuthHeaders('triage'), + }, + ) + await expect(attemptTakedownTriage).rejects.toThrow( + 'Must be a full moderator to perform an account takedown', + ) + }) + it('automatically reverses actions marked with duration', async () => { + const { data: action } = + await agent.api.com.atproto.admin.takeModerationAction( + { + action: TAKEDOWN, + createdBy: 'did:example:moderator', + reason: 'Y', + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: sc.dids.bob, + }, + createLabelVals: ['takendown'], + // Use negative value to set the expiry time in the past so that the action is automatically reversed + // right away without having to wait n number of hours for a successful assertion + durationInHours: -1, + }, + { + encoding: 'application/json', + headers: network.bsky.adminAuthHeaders('moderator'), + }, + ) + + const labelsAfterTakedown = await getRepoLabels(sc.dids.bob) + expect(labelsAfterTakedown).toContain('takendown') + // In the actual app, this will be instantiated and run on server startup + const periodicReversal = new PeriodicModerationActionReversal( + network.bsky.ctx, + ) + await periodicReversal.findAndRevertDueActions() + + const { data: reversedAction } = + await agent.api.com.atproto.admin.getModerationAction( + { id: action.id }, + { headers: network.bsky.adminAuthHeaders('moderator') }, + ) + + // Verify that the automatic reversal is attributed to the original moderator of the temporary action + // and that the reason is set to indicate that the action was automatically reversed. + expect(reversedAction.reversal).toMatchObject({ + createdBy: action.createdBy, + reason: '[SCHEDULED_REVERSAL] Reverting action as originally scheduled', + }) + + // Verify that labels are also reversed when takedown action is reversed + const labelsAfterReversal = await getRepoLabels(sc.dids.bob) + expect(labelsAfterReversal).not.toContain('takendown') + }) + async function actionWithLabels( opts: Partial & { subject: ComAtprotoAdminTakeModerationAction.InputSchema['subject'] @@ -951,7 +1066,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) return result.data @@ -966,7 +1081,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) } @@ -974,7 +1089,7 @@ describe('moderation', () => { async function getRecordLabels(uri: string) { const result = await agent.api.com.atproto.admin.getRecord( { uri }, - { headers: network.pds.adminAuthHeaders() }, + { headers: network.bsky.adminAuthHeaders() }, ) const labels = result.data.labels ?? [] return labels.map((l) => l.val) @@ -983,7 +1098,7 @@ describe('moderation', () => { async function getRepoLabels(did: string) { const result = await agent.api.com.atproto.admin.getRepo( { did }, - { headers: network.pds.adminAuthHeaders() }, + { headers: network.bsky.adminAuthHeaders() }, ) const labels = result.data.labels ?? [] return labels.map((l) => l.val) @@ -1000,7 +1115,7 @@ describe('moderation', () => { post = sc.posts[sc.dids.carol][0] blob = post.images[1] imageUri = ctx.imgUriBuilder - .getCommonSignedUri( + .getPresetUri( 'feed_thumbnail', sc.dids.carol, blob.image.ref.toString(), @@ -1024,7 +1139,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) actionId = takeAction.data.id @@ -1055,7 +1170,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), + headers: network.bsky.adminAuthHeaders(), }, ) diff --git a/packages/bsky/tests/notification-server.test.ts b/packages/bsky/tests/notification-server.test.ts new file mode 100644 index 00000000000..aeb7f8ae97c --- /dev/null +++ b/packages/bsky/tests/notification-server.test.ts @@ -0,0 +1,232 @@ +import AtpAgent, { AtUri } from '@atproto/api' +import { TestNetwork } from '@atproto/dev-env' +import { SeedClient } from './seeds/client' +import basicSeed from './seeds/basic' +import { NotificationServer } from '../src/notifications' +import { Database } from '../src' + +describe('notification server', () => { + let network: TestNetwork + let agent: AtpAgent + let pdsAgent: AtpAgent + let sc: SeedClient + let notifServer: NotificationServer + + // account dids, for convenience + let alice: string + + beforeAll(async () => { + network = await TestNetwork.create({ + dbPostgresSchema: 'bsky_notification_server', + }) + agent = network.bsky.getClient() + pdsAgent = network.pds.getClient() + sc = new SeedClient(pdsAgent) + await basicSeed(sc) + await network.processAll() + await network.bsky.processAll() + alice = sc.dids.alice + notifServer = network.bsky.ctx.notifServer + }) + + afterAll(async () => { + await network.close() + }) + + describe('registerPushNotification', () => { + it('registers push notification token and device.', async () => { + const res = await agent.api.app.bsky.notification.registerPush( + { + serviceDid: network.bsky.ctx.cfg.serverDid, + platform: 'ios', + token: '123', + appId: 'xyz.blueskyweb.app', + }, + { + encoding: 'application/json', + headers: await network.serviceHeaders(alice), + }, + ) + expect(res.success).toEqual(true) + }) + + it('allows reregistering push notification token.', async () => { + const res1 = await agent.api.app.bsky.notification.registerPush( + { + serviceDid: network.bsky.ctx.cfg.serverDid, + platform: 'web', + token: '234', + appId: 'xyz.blueskyweb.app', + }, + { + encoding: 'application/json', + headers: await network.serviceHeaders(alice), + }, + ) + const res2 = await agent.api.app.bsky.notification.registerPush( + { + serviceDid: network.bsky.ctx.cfg.serverDid, + platform: 'web', + token: '234', + appId: 'xyz.blueskyweb.app', + }, + { + encoding: 'application/json', + headers: await network.serviceHeaders(alice), + }, + ) + expect(res1.success).toEqual(true) + expect(res2.success).toEqual(true) + }) + + it('does not allows registering push notification at mismatching service.', async () => { + const tryRegister = agent.api.app.bsky.notification.registerPush( + { + serviceDid: 'did:web:notifservice.com', + platform: 'ios', + token: '123', + appId: 'xyz.blueskyweb.app', + }, + { + encoding: 'application/json', + headers: await network.serviceHeaders(alice), + }, + ) + await expect(tryRegister).rejects.toThrow('Invalid serviceDid.') + }) + }) + + describe('NotificationServer', () => { + it('gets user tokens from db', async () => { + const tokens = await notifServer.getTokensByDid([alice]) + expect(tokens[alice][0].token).toEqual('123') + }) + + it('gets notification display attributes: title and body', async () => { + const db = network.bsky.ctx.db.getPrimary() + const notif = await getLikeNotification(db, alice) + if (!notif) throw new Error('no notification found') + const attrs = await notifServer.getNotificationDisplayAttributes([notif]) + if (!attrs.length) + throw new Error('no notification display attributes found') + expect(attrs[0].title).toEqual('bobby liked your post') + }) + + it('filters notifications that violate blocks', async () => { + const db = network.bsky.ctx.db.getPrimary() + const notif = await getLikeNotification(db, alice) + if (!notif) throw new Error('no notification found') + const blockRef = await pdsAgent.api.app.bsky.graph.block.create( + { repo: alice }, + { subject: notif.author, createdAt: new Date().toISOString() }, + sc.getHeaders(alice), + ) + await network.processAll() + // verify inverse of block + const flippedNotif = { + ...notif, + did: notif.author, + author: notif.did, + } + const attrs = await notifServer.getNotificationDisplayAttributes([ + notif, + flippedNotif, + ]) + expect(attrs.length).toBe(0) + const uri = new AtUri(blockRef.uri) + await pdsAgent.api.app.bsky.graph.block.delete( + { repo: alice, rkey: uri.rkey }, + sc.getHeaders(alice), + ) + await network.processAll() + }) + + it('filters notifications that violate mutes', async () => { + const db = network.bsky.ctx.db.getPrimary() + const notif = await getLikeNotification(db, alice) + if (!notif) throw new Error('no notification found') + await pdsAgent.api.app.bsky.graph.muteActor( + { actor: notif.author }, + { headers: sc.getHeaders(alice), encoding: 'application/json' }, + ) + const attrs = await notifServer.getNotificationDisplayAttributes([notif]) + expect(attrs.length).toBe(0) + await pdsAgent.api.app.bsky.graph.unmuteActor( + { actor: notif.author }, + { headers: sc.getHeaders(alice), encoding: 'application/json' }, + ) + }) + + it('filters notifications that violate mutelists', async () => { + const db = network.bsky.ctx.db.getPrimary() + const notif = await getLikeNotification(db, alice) + if (!notif) throw new Error('no notification found') + const listRef = await pdsAgent.api.app.bsky.graph.list.create( + { repo: alice }, + { + name: 'mute', + purpose: 'app.bsky.graph.defs#modlist', + createdAt: new Date().toISOString(), + }, + sc.getHeaders(alice), + ) + await pdsAgent.api.app.bsky.graph.listitem.create( + { repo: alice }, + { + subject: notif.author, + list: listRef.uri, + createdAt: new Date().toISOString(), + }, + sc.getHeaders(alice), + ) + await network.processAll() + await pdsAgent.api.app.bsky.graph.muteActorList( + { list: listRef.uri }, + { headers: sc.getHeaders(alice), encoding: 'application/json' }, + ) + const attrs = await notifServer.getNotificationDisplayAttributes([notif]) + expect(attrs.length).toBe(0) + await pdsAgent.api.app.bsky.graph.unmuteActorList( + { list: listRef.uri }, + { headers: sc.getHeaders(alice), encoding: 'application/json' }, + ) + }) + + it('prepares notification to be sent', async () => { + const db = network.bsky.ctx.db.getPrimary() + const notif = await getLikeNotification(db, alice) + if (!notif) throw new Error('no notification found') + const notifAsArray = [ + notif, + notif /* second one will get dropped by rate limit */, + ] + const prepared = await notifServer.prepareNotifsToSend(notifAsArray) + expect(prepared).toEqual([ + { + collapse_id: 'like', + collapse_key: 'like', + data: { + reason: notif.reason, + recordCid: notif.recordCid, + recordUri: notif.recordUri, + }, + message: 'again', + platform: 1, + title: 'bobby liked your post', + tokens: ['123'], + topic: 'xyz.blueskyweb.app', + }, + ]) + }) + }) + + async function getLikeNotification(db: Database, did: string) { + return await db.db + .selectFrom('notification') + .selectAll() + .where('did', '=', did) + .where('reason', '=', 'like') + .orderBy('sortAt') + .executeTakeFirst() + } +}) diff --git a/packages/bsky/tests/pipeline/backpressure.test.ts b/packages/bsky/tests/pipeline/backpressure.test.ts new file mode 100644 index 00000000000..a265bc948c5 --- /dev/null +++ b/packages/bsky/tests/pipeline/backpressure.test.ts @@ -0,0 +1,69 @@ +import { wait } from '@atproto/common' +import { + BskyIndexers, + TestNetworkNoAppView, + getIndexers, + getIngester, + processAll, +} from '@atproto/dev-env' +import { SeedClient } from '../seeds/client' +import basicSeed from '../seeds/basic' +import { BskyIngester } from '../../src' + +const TEST_NAME = 'pipeline_backpressure' + +describe('pipeline backpressure', () => { + let network: TestNetworkNoAppView + let ingester: BskyIngester + let indexers: BskyIndexers + + let sc: SeedClient + + beforeAll(async () => { + network = await TestNetworkNoAppView.create({ + dbPostgresSchema: TEST_NAME, + }) + ingester = await getIngester(network, { + name: TEST_NAME, + ingesterPartitionCount: 2, + ingesterMaxItems: 10, + ingesterCheckItemsEveryN: 5, + }) + indexers = await getIndexers(network, { + name: TEST_NAME, + partitionIdsByIndexer: [[0], [1]], + }) + const pdsAgent = network.pds.getClient() + sc = new SeedClient(pdsAgent) + await basicSeed(sc) + }) + + afterAll(async () => { + await network.close() + }) + + it('ingester issues backpressure based on total of partition lengths.', async () => { + // ingest until first 10 are seen + await ingester.start() + while ((ingester.sub.lastSeq ?? 0) < 10) { + await wait(50) + } + // allow additional time to pass to ensure no additional events are being consumed + await wait(200) + // check that max items has been respected (i.e. backpressure was applied) + const lengths = await ingester.ctx.redis.streamLengths(['repo:0', 'repo:1']) + expect(lengths).toHaveLength(2) + expect(lengths[0] + lengths[1]).toBeLessThanOrEqual(10 + 5) // not exact due to batching, may catch on following check backpressure + // drain all items using indexers, releasing backpressure + await indexers.start() + await processAll(network, ingester) + const lengthsFinal = await ingester.ctx.redis.streamLengths([ + 'repo:0', + 'repo:1', + ]) + expect(lengthsFinal).toHaveLength(2) + expect(lengthsFinal[0] + lengthsFinal[1]).toEqual(0) + await indexers.destroy() + await ingester.destroy() + }) +}) diff --git a/packages/bsky/tests/pipeline/reingest.test.ts b/packages/bsky/tests/pipeline/reingest.test.ts new file mode 100644 index 00000000000..ed8afdfe36d --- /dev/null +++ b/packages/bsky/tests/pipeline/reingest.test.ts @@ -0,0 +1,52 @@ +import { TestNetworkNoAppView, getIngester, ingestAll } from '@atproto/dev-env' +import { SeedClient } from '../seeds/client' +import basicSeed from '../seeds/basic' +import { BskyIngester } from '../../src' + +const TEST_NAME = 'pipeline_reingest' + +describe('pipeline reingestion', () => { + let network: TestNetworkNoAppView + let ingester: BskyIngester + let sc: SeedClient + + beforeAll(async () => { + network = await TestNetworkNoAppView.create({ + dbPostgresSchema: TEST_NAME, + }) + ingester = await getIngester(network, { + name: TEST_NAME, + ingesterPartitionCount: 1, + }) + const pdsAgent = network.pds.getClient() + sc = new SeedClient(pdsAgent) + await basicSeed(sc) + }) + + afterAll(async () => { + await network.close() + await ingester.destroy() + }) + + it('allows events to be reingested multiple times.', async () => { + // ingest all events once + await ingester.start() + await ingestAll(network, ingester) + const initialCursor = await ingester.sub.getCursor() + const [initialLen] = await ingester.ctx.redis.streamLengths(['repo:0']) + expect(initialCursor).toBeGreaterThan(10) + expect(initialLen).toBeGreaterThan(10) + // stop ingesting and reset ingester state + await ingester.sub.destroy() + await ingester.sub.resetCursor() + // add one new event and reingest + await sc.post(sc.dids.alice, 'one more event!') // add one event to firehose + ingester.sub.resume() + await ingestAll(network, ingester) + // confirm the newest event was ingested + const finalCursor = await ingester.sub.getCursor() + const [finalLen] = await ingester.ctx.redis.streamLengths(['repo:0']) + expect(finalCursor).toEqual(initialCursor + 1) + expect(finalLen).toEqual(initialLen + 1) + }) +}) diff --git a/packages/bsky/tests/pipeline/repartition.test.ts b/packages/bsky/tests/pipeline/repartition.test.ts new file mode 100644 index 00000000000..12205e56315 --- /dev/null +++ b/packages/bsky/tests/pipeline/repartition.test.ts @@ -0,0 +1,87 @@ +import { + BskyIndexers, + TestNetworkNoAppView, + getIndexers, + getIngester, + ingestAll, + processAll, +} from '@atproto/dev-env' +import { SeedClient } from '../seeds/client' +import usersSeed from '../seeds/users' +import { BskyIngester } from '../../src' +import { countAll } from '../../src/db/util' + +const TEST_NAME = 'pipeline_repartition' + +describe('pipeline indexer repartitioning', () => { + let network: TestNetworkNoAppView + let ingester: BskyIngester + let indexers1: BskyIndexers + let indexers2: BskyIndexers + let sc: SeedClient + + beforeAll(async () => { + network = await TestNetworkNoAppView.create({ + dbPostgresSchema: TEST_NAME, + }) + ingester = await getIngester(network, { + name: TEST_NAME, + ingesterPartitionCount: 2, + }) + indexers1 = await getIndexers(network, { + name: TEST_NAME, + partitionIdsByIndexer: [[0, 1]], // one indexer consuming two partitions + }) + indexers2 = await getIndexers(network, { + name: TEST_NAME, + partitionIdsByIndexer: [[0], [1]], // two indexers, each consuming one partition + }) + const pdsAgent = network.pds.getClient() + sc = new SeedClient(pdsAgent) + await usersSeed(sc) + }) + + afterAll(async () => { + await network.close() + }) + + it('indexers repartition without missing events.', async () => { + const poster = createPoster(sc) + await Promise.all([poster.post(4), indexers1.start(), ingester.start()]) + await poster.post(1) + await processAll(network, ingester) + const { count: indexedPosts } = await indexers1.db.db + .selectFrom('post') + .select(countAll.as('count')) + .executeTakeFirstOrThrow() + expect(indexedPosts).toEqual(5) + await Promise.all([poster.post(3), indexers1.destroy()]) + await poster.post(3) // miss some events + await ingestAll(network, ingester) + await Promise.all([poster.post(3), indexers2.start()]) // handle some events on indexers2 + await processAll(network, ingester) + const { count: allIndexedPosts } = await indexers2.db.db + .selectFrom('post') + .select(countAll.as('count')) + .executeTakeFirstOrThrow() + expect(allIndexedPosts).toBeGreaterThan(indexedPosts) + expect(allIndexedPosts).toEqual(poster.postCount) + await indexers2.destroy() + await ingester.destroy() + }) +}) + +function createPoster(sc: SeedClient) { + return { + postCount: 0, + destroyed: false, + async post(n = 1) { + const dids = Object.values(sc.dids) + for (let i = 0; i < n; ++i) { + const did = dids[this.postCount % dids.length] + await sc.post(did, `post ${this.postCount}`) + this.postCount++ + } + }, + } +} diff --git a/packages/bsky/tests/reprocessing.test.ts b/packages/bsky/tests/reprocessing.test.ts new file mode 100644 index 00000000000..dd170c570ab --- /dev/null +++ b/packages/bsky/tests/reprocessing.test.ts @@ -0,0 +1,75 @@ +import axios from 'axios' +import { AtUri } from '@atproto/syntax' +import AtpAgent from '@atproto/api' +import { TestNetwork } from '@atproto/dev-env' +import { SeedClient } from './seeds/client' +import basicSeed from './seeds/basic' +import { Database } from '../src/db' + +describe('reprocessing', () => { + let network: TestNetwork + let pdsAgent: AtpAgent + let sc: SeedClient + let alice: string + + beforeAll(async () => { + network = await TestNetwork.create({ + dbPostgresSchema: 'bsky_reprocessing', + }) + pdsAgent = network.pds.getClient() + sc = new SeedClient(pdsAgent) + await basicSeed(sc) + alice = sc.dids.alice + await network.processAll() + }) + + afterAll(async () => { + await network.close() + }) + + const getRecordUris = async (db: Database, did: string) => { + const res = await db.db + .selectFrom('record') + .select('uri') + .where('did', '=', did) + .execute() + return res.map((row) => row.uri) + } + it('reprocesses repo data', async () => { + const db = network.bsky.ctx.db.getPrimary() + const urisBefore = await getRecordUris(db, alice) + await db.db.deleteFrom('record').where('did', '=', alice).execute() + const indexerPort = network.bsky.indexer.ctx.cfg.indexerPort + await axios.post(`http://localhost:${indexerPort}/reprocess/${alice}`) + await network.processAll() + const urisAfter = await getRecordUris(db, alice) + expect(urisAfter.sort()).toEqual(urisBefore.sort()) + }) + + it('buffers commits while reprocessing repo data', async () => { + const db = network.bsky.ctx.db.getPrimary() + const urisBefore = await getRecordUris(db, alice) + await db.db.deleteFrom('record').where('did', '=', alice).execute() + const indexerPort = network.bsky.indexer.ctx.cfg.indexerPort + const toDeleteIndex = urisBefore.findIndex((uri) => + uri.includes('app.bsky.feed.post'), + ) + if (toDeleteIndex < 0) { + throw new Error('could not find post to delete') + } + // request reprocess while buffering a new post & delete + const [newPost] = await Promise.all([ + sc.post(alice, 'blah blah'), + axios.post(`http://localhost:${indexerPort}/reprocess/${alice}`), + sc.deletePost(alice, new AtUri(urisBefore[toDeleteIndex])), + ]) + await network.processAll() + const urisAfter = await getRecordUris(db, alice) + const expected = [ + ...urisBefore.slice(0, toDeleteIndex), + ...urisBefore.slice(toDeleteIndex + 1), + newPost.ref.uriStr, + ] + expect(urisAfter.sort()).toEqual(expected.sort()) + }) +}) diff --git a/packages/bsky/tests/seeds/basic.ts b/packages/bsky/tests/seeds/basic.ts index 5c22b2da65d..c1bd7e41e09 100644 --- a/packages/bsky/tests/seeds/basic.ts +++ b/packages/bsky/tests/seeds/basic.ts @@ -23,7 +23,12 @@ export default async (sc: SeedClient, users = true) => { await sc.follow(bob, alice) await sc.follow(bob, carol, createdAtMicroseconds()) await sc.follow(dan, bob, createdAtTimezone()) - await sc.post(alice, posts.alice[0]) + await sc.post(alice, posts.alice[0], undefined, undefined, undefined, { + labels: { + $type: 'com.atproto.label.defs#selfLabels', + values: [{ val: 'self-label' }], + }, + }) await sc.post(bob, posts.bob[0], undefined, undefined, undefined, { langs: ['en-US', 'i-klingon'], }) @@ -112,7 +117,7 @@ export default async (sc: SeedClient, users = true) => { sc.posts[alice][1].ref, replies.carol[0], ) - await sc.reply( + const alicesReplyToBob = await sc.reply( alice, sc.posts[alice][1].ref, sc.replies[bob][0].ref, @@ -120,6 +125,7 @@ export default async (sc: SeedClient, users = true) => { ) await sc.repost(carol, sc.posts[dan][1].ref) await sc.repost(dan, sc.posts[alice][1].ref) + await sc.repost(dan, alicesReplyToBob.ref) return sc } diff --git a/packages/bsky/tests/seeds/client.ts b/packages/bsky/tests/seeds/client.ts index ea4167cdbd2..ddd1acb9192 100644 --- a/packages/bsky/tests/seeds/client.ts +++ b/packages/bsky/tests/seeds/client.ts @@ -1,7 +1,7 @@ import fs from 'fs/promises' import { CID } from 'multiformats/cid' import AtpAgent from '@atproto/api' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { BlobRef } from '@atproto/lexicon' import { Main as Facet } from '@atproto/api/src/client/types/app/bsky/richtext/facet' import { InputSchema as TakeActionInput } from '@atproto/api/src/client/types/com/atproto/admin/takeModerationAction' @@ -117,7 +117,7 @@ export class SeedClient { by: string, displayName: string, description: string, - fromUser?: string, + selfLabels?: string[], ) { AVATAR_IMG ??= await fs.readFile( 'tests/image/fixtures/key-portrait-small.jpg', @@ -127,7 +127,7 @@ export class SeedClient { { const res = await this.agent.api.com.atproto.repo.uploadBlob(AVATAR_IMG, { encoding: 'image/jpeg', - headers: this.getHeaders(fromUser || by), + headers: this.getHeaders(by), } as any) avatarBlob = res.data.blob } @@ -139,8 +139,14 @@ export class SeedClient { displayName, description, avatar: avatarBlob, + labels: selfLabels + ? { + $type: 'com.atproto.label.defs#selfLabels', + values: selfLabels.map((val) => ({ val })), + } + : undefined, }, - this.getHeaders(fromUser || by), + this.getHeaders(by), ) this.profiles[by] = { displayName, diff --git a/packages/bsky/tests/seeds/likes.ts b/packages/bsky/tests/seeds/likes.ts index 27eeba09c40..1747fb2fa59 100644 --- a/packages/bsky/tests/seeds/likes.ts +++ b/packages/bsky/tests/seeds/likes.ts @@ -10,5 +10,34 @@ export default async (sc: SeedClient) => { }) await sc.like(sc.dids.eve, sc.posts[sc.dids.alice][1].ref) await sc.like(sc.dids.carol, sc.replies[sc.dids.bob][0].ref) + + // give alice > 100 likes + for (let i = 0; i < 50; i++) { + const [b, c, d] = await Promise.all([ + sc.post(sc.dids.bob, `bob post ${i}`), + sc.post(sc.dids.carol, `carol post ${i}`), + sc.post(sc.dids.dan, `dan post ${i}`), + ]) + await Promise.all( + [ + sc.like(sc.dids.alice, b.ref), // likes 50 of bobs posts + i < 45 && sc.like(sc.dids.alice, c.ref), // likes 45 of carols posts + i < 40 && sc.like(sc.dids.alice, d.ref), // likes 40 of dans posts + ].filter(Boolean), + ) + } + + // couple more NPCs for suggested follows + await sc.createAccount('fred', { + email: 'fred@test.com', + handle: 'fred.test', + password: 'fred-pass', + }) + await sc.createAccount('gina', { + email: 'gina@test.com', + handle: 'gina.test', + password: 'gina-pass', + }) + return sc } diff --git a/packages/bsky/tests/seeds/thread.ts b/packages/bsky/tests/seeds/thread.ts deleted file mode 100644 index 921736e919e..00000000000 --- a/packages/bsky/tests/seeds/thread.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { RecordRef, SeedClient } from './client' - -export default async (sc: SeedClient, did, threads: Item[]) => { - const refByItemId: Record = {} - const rootByItemId: Record = {} - await walk(threads, async (item, _depth, parent) => { - if (parent !== undefined) { - const parentRef = refByItemId[parent.id] - const rootRef = rootByItemId[parent.id] - const { ref } = await sc.reply(did, rootRef, parentRef, String(item.id)) - refByItemId[item.id] = ref - rootByItemId[item.id] = rootRef - } else { - const { ref } = await sc.post(did, String(item.id)) - refByItemId[item.id] = ref - rootByItemId[item.id] = ref - } - }) -} - -export function item(id: number, children: Item[] = []) { - return { id, children } -} - -export async function walk( - items: Item[], - cb: (item: Item, depth: number, parent?: Item) => Promise, - depth = 0, - parent?: Item, -) { - for (const item of items) { - await cb(item, depth, parent) - await walk(item.children, cb, depth + 1, item) - } -} - -export interface Item { - id: number - children: Item[] -} diff --git a/packages/bsky/tests/seeds/users.ts b/packages/bsky/tests/seeds/users.ts index 0a7d530d998..8c14b894db4 100644 --- a/packages/bsky/tests/seeds/users.ts +++ b/packages/bsky/tests/seeds/users.ts @@ -10,11 +10,13 @@ export default async (sc: SeedClient) => { sc.dids.alice, users.alice.displayName, users.alice.description, + users.alice.selfLabels, ) await sc.createProfile( sc.dids.bob, users.bob.displayName, users.bob.description, + users.bob.selfLabels, ) return sc @@ -27,6 +29,7 @@ const users = { password: 'alice-pass', displayName: 'ali', description: 'its me!', + selfLabels: ['self-label-a', 'self-label-b'], }, bob: { email: 'bob@test.com', @@ -34,6 +37,7 @@ const users = { password: 'bob-pass', displayName: 'bobby', description: 'hi im bob label_me', + selfLabels: undefined, }, carol: { email: 'carol@test.com', @@ -41,6 +45,7 @@ const users = { password: 'carol-pass', displayName: undefined, description: undefined, + selfLabels: undefined, }, dan: { email: 'dan@test.com', @@ -48,5 +53,6 @@ const users = { password: 'dan-pass', displayName: undefined, description: undefined, + selfLabels: undefined, }, } diff --git a/packages/bsky/tests/server.test.ts b/packages/bsky/tests/server.test.ts index 64b89491f82..be2f1c0213e 100644 --- a/packages/bsky/tests/server.test.ts +++ b/packages/bsky/tests/server.test.ts @@ -4,16 +4,24 @@ import axios, { AxiosError } from 'axios' import { TestNetwork } from '@atproto/dev-env' import { handler as errorHandler } from '../src/error' import { Database } from '../src' +import { SeedClient } from './seeds/client' +import basicSeed from './seeds/basic' describe('server', () => { let network: TestNetwork let db: Database + let alice: string beforeAll(async () => { network = await TestNetwork.create({ dbPostgresSchema: 'bsky_server', }) - db = network.bsky.ctx.db + const pdsAgent = network.pds.getClient() + const sc = new SeedClient(pdsAgent) + await basicSeed(sc) + await network.processAll() + alice = sc.dids.alice + db = network.bsky.ctx.db.getPrimary() }) afterAll(async () => { @@ -80,8 +88,31 @@ describe('server', () => { }) }) + it('compresses large json responses', async () => { + const res = await axios.get( + `${network.bsky.url}/xrpc/app.bsky.feed.getTimeline`, + { + decompress: false, + headers: { + ...(await network.serviceHeaders(alice)), + 'accept-encoding': 'gzip', + }, + }, + ) + expect(res.headers['content-encoding']).toEqual('gzip') + }) + + it('does not compress small payloads', async () => { + const res = await axios.get(`${network.bsky.url}/xrpc/_health`, { + decompress: false, + headers: { 'accept-encoding': 'gzip' }, + }) + expect(res.headers['content-encoding']).toBeUndefined() + }) + it('healthcheck fails when database is unavailable.', async () => { - await network.bsky.sub.destroy() + await network.bsky.ingester.sub.destroy() + await network.bsky.indexer.sub.destroy() await db.close() let error: AxiosError try { diff --git a/packages/bsky/tests/subscription/repo.test.ts b/packages/bsky/tests/subscription/repo.test.ts index 3dfdd9ca796..43c1287ba95 100644 --- a/packages/bsky/tests/subscription/repo.test.ts +++ b/packages/bsky/tests/subscription/repo.test.ts @@ -34,7 +34,7 @@ describe('sync', () => { }) it('indexes permit history being replayed.', async () => { - const { db } = ctx + const db = ctx.db.getPrimary() // Generate some modifications and dupes const { alice, bob, carol, dan } = sc.dids @@ -66,9 +66,16 @@ describe('sync', () => { const originalTableDump = await getTableDump() // Reprocess repos via sync subscription, on top of existing indices - await network.bsky.sub?.destroy() - await network.bsky.sub?.resetState() - network.bsky.sub?.resume() + await network.bsky.ingester.sub.destroy() + await network.bsky.indexer.sub.destroy() + // Hard reset of state in redis + await network.bsky.ingester.sub.resetCursor() + const indexerSub = network.bsky.indexer.sub + const partition = indexerSub.partitions.get(0) + await network.bsky.indexer.ctx.redis.del(partition.key) + // Boot streams back up + network.bsky.indexer.sub.resume() + network.bsky.ingester.sub.resume() await network.processAll() // Permissive of indexedAt times changing @@ -98,9 +105,10 @@ describe('sync', () => { email: 'jack@test.com', password: 'password', }) + await network.pds.ctx.sequencerLeader?.isCaughtUp() await network.processAll() // confirm jack was indexed as an actor despite the bad event - const actors = await dumpTable(ctx.db, 'actor', ['did']) + const actors = await dumpTable(ctx.db.getPrimary(), 'actor', ['did']) expect(actors.map((a) => a.handle)).toContain('jack.test') RepoService.prototype.afterWriteProcessing = afterWriteProcessingOriginal }) diff --git a/packages/bsky/tests/subscription/util.test.ts b/packages/bsky/tests/subscription/util.test.ts new file mode 100644 index 00000000000..497532f643b --- /dev/null +++ b/packages/bsky/tests/subscription/util.test.ts @@ -0,0 +1,185 @@ +import { wait } from '@atproto/common' +import { + ConsecutiveList, + LatestQueue, + PartitionedQueue, +} from '../../src/subscription/util' +import { randomStr } from '../../../crypto/src' + +describe('subscription utils', () => { + describe('ConsecutiveList', () => { + it('tracks consecutive complete items.', () => { + const consecutive = new ConsecutiveList() + // add items + const item1 = consecutive.push(1) + const item2 = consecutive.push(2) + const item3 = consecutive.push(3) + expect(item1.isComplete).toEqual(false) + expect(item2.isComplete).toEqual(false) + expect(item3.isComplete).toEqual(false) + // complete items out of order + expect(consecutive.list.length).toBe(3) + expect(item2.complete()).toEqual([]) + expect(item2.isComplete).toEqual(true) + expect(consecutive.list.length).toBe(3) + expect(item1.complete()).toEqual([1, 2]) + expect(item1.isComplete).toEqual(true) + expect(consecutive.list.length).toBe(1) + expect(item3.complete()).toEqual([3]) + expect(consecutive.list.length).toBe(0) + expect(item3.isComplete).toEqual(true) + }) + }) + + describe('LatestQueue', () => { + it('only performs most recently queued item.', async () => { + const latest = new LatestQueue() + const complete: number[] = [] + latest.add(async () => { + await wait(1) + complete.push(1) + }) + latest.add(async () => { + await wait(1) + complete.push(2) + }) + latest.add(async () => { + await wait(1) + complete.push(3) + }) + latest.add(async () => { + await wait(1) + complete.push(4) + }) + await latest.queue.onIdle() + expect(complete).toEqual([1, 4]) // skip 2, 3 + latest.add(async () => { + await wait(1) + complete.push(5) + }) + latest.add(async () => { + await wait(1) + complete.push(6) + }) + await latest.queue.onIdle() + expect(complete).toEqual([1, 4, 5, 6]) + }) + + it('stops processing queued messages on destroy.', async () => { + const latest = new LatestQueue() + const complete: number[] = [] + latest.add(async () => { + await wait(1) + complete.push(1) + }) + latest.add(async () => { + await wait(1) + complete.push(2) + }) + const destroyed = latest.destroy() + latest.add(async () => { + await wait(1) + complete.push(3) + }) + await destroyed + expect(complete).toEqual([1]) // 2 was cleared, 3 was after destroy + // show that waiting on destroyed above was already enough to reflect all complete items + await latest.queue.onIdle() + expect(complete).toEqual([1]) + }) + }) + + describe('PartitionedQueue', () => { + it('performs work in parallel across partitions, serial within a partition.', async () => { + const partitioned = new PartitionedQueue({ concurrency: Infinity }) + const complete: number[] = [] + // partition 1 items start slow but get faster: slow should still complete first. + partitioned.add('1', async () => { + await wait(30) + complete.push(11) + }) + partitioned.add('1', async () => { + await wait(20) + complete.push(12) + }) + partitioned.add('1', async () => { + await wait(1) + complete.push(13) + }) + expect(partitioned.partitions.size).toEqual(1) + // partition 2 items complete quickly except the last, which is slowest of all events. + partitioned.add('2', async () => { + await wait(1) + complete.push(21) + }) + partitioned.add('2', async () => { + await wait(1) + complete.push(22) + }) + partitioned.add('2', async () => { + await wait(1) + complete.push(23) + }) + partitioned.add('2', async () => { + await wait(60) + complete.push(24) + }) + expect(partitioned.partitions.size).toEqual(2) + await partitioned.main.onIdle() + expect(complete).toEqual([21, 22, 23, 11, 12, 13, 24]) + expect(partitioned.partitions.size).toEqual(0) + }) + + it('limits overall concurrency.', async () => { + const partitioned = new PartitionedQueue({ concurrency: 1 }) + const complete: number[] = [] + // if concurrency were not constrained, partition 1 would complete all items + // before any items from partition 2. since it is constrained, the work is complete in the order added. + partitioned.add('1', async () => { + await wait(1) + complete.push(11) + }) + partitioned.add('2', async () => { + await wait(10) + complete.push(21) + }) + partitioned.add('1', async () => { + await wait(1) + complete.push(12) + }) + partitioned.add('2', async () => { + await wait(10) + complete.push(22) + }) + // only partition 1 exists so far due to the concurrency + expect(partitioned.partitions.size).toEqual(1) + await partitioned.main.onIdle() + expect(complete).toEqual([11, 21, 12, 22]) + expect(partitioned.partitions.size).toEqual(0) + }) + + it('settles with many items.', async () => { + const partitioned = new PartitionedQueue({ concurrency: 100 }) + const complete: { partition: string; id: number }[] = [] + const partitions = new Set() + for (let i = 0; i < 500; ++i) { + const partition = randomStr(1, 'base16').slice(0, 1) + partitions.add(partition) + partitioned.add(partition, async () => { + await wait((i % 2) * 2) + complete.push({ partition, id: i }) + }) + } + expect(partitioned.partitions.size).toEqual(partitions.size) + await partitioned.main.onIdle() + expect(complete.length).toEqual(500) + for (const partition of partitions) { + const ids = complete + .filter((item) => item.partition === partition) + .map((item) => item.id) + expect(ids).toEqual([...ids].sort((a, b) => a - b)) + } + expect(partitioned.partitions.size).toEqual(0) + }) + }) +}) diff --git a/packages/bsky/tests/views/__snapshots__/actor-search.test.ts.snap b/packages/bsky/tests/views/__snapshots__/actor-search.test.ts.snap index f5fc00b818b..a6990b60ed3 100644 --- a/packages/bsky/tests/views/__snapshots__/actor-search.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/actor-search.test.ts.snap @@ -3,8 +3,11 @@ exports[`pds actor search views search gives relevant results 1`] = ` Array [ Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "did": "user(0)", - "handle": "cara-wiegand69.test", + "displayName": "Carlton Abernathy IV", + "handle": "aliya-hodkiewicz.test", + "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { "blockedBy": false, @@ -12,11 +15,8 @@ Array [ }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(2)/cids(0)@jpeg", - "did": "user(1)", - "displayName": "Carol Littel", - "handle": "eudora-dietrich4.test", - "indexedAt": "1970-01-01T00:00:00.000Z", + "did": "user(2)", + "handle": "cara-wiegand69.test", "labels": Array [], "viewer": Object { "blockedBy": false, @@ -24,11 +24,8 @@ Array [ }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(0)@jpeg", "did": "user(3)", - "displayName": "Sadie Carter", - "handle": "shane-torphy52.test", - "indexedAt": "1970-01-01T00:00:00.000Z", + "handle": "carlos6.test", "labels": Array [], "viewer": Object { "blockedBy": false, @@ -36,10 +33,10 @@ Array [ }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(0)@jpeg", - "did": "user(5)", - "displayName": "Carlton Abernathy IV", - "handle": "aliya-hodkiewicz.test", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(0)@jpeg", + "did": "user(4)", + "displayName": "Latoya Windler", + "handle": "carolina-mcdermott77.test", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { @@ -48,8 +45,11 @@ Array [ }, }, Object { - "did": "user(7)", - "handle": "carlos6.test", + "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(0)@jpeg", + "did": "user(6)", + "displayName": "Rachel Kshlerin", + "handle": "cayla-marquardt39.test", + "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { "blockedBy": false, @@ -57,10 +57,10 @@ Array [ }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(9)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(9)/cids(0)@jpeg", "did": "user(8)", - "displayName": "Latoya Windler", - "handle": "carolina-mcdermott77.test", + "displayName": "Carol Littel", + "handle": "eudora-dietrich4.test", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { @@ -69,10 +69,10 @@ Array [ }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(11)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(11)/cids(0)@jpeg", "did": "user(10)", - "displayName": "Rachel Kshlerin", - "handle": "cayla-marquardt39.test", + "displayName": "Sadie Carter", + "handle": "shane-torphy52.test", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { @@ -86,66 +86,66 @@ Array [ exports[`pds actor search views typeahead gives relevant results 1`] = ` Array [ Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "did": "user(0)", - "handle": "cara-wiegand69.test", + "displayName": "Carlton Abernathy IV", + "handle": "aliya-hodkiewicz.test", "viewer": Object { "blockedBy": false, "muted": false, }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(2)/cids(0)@jpeg", - "did": "user(1)", - "displayName": "Carol Littel", - "handle": "eudora-dietrich4.test", + "did": "user(2)", + "handle": "cara-wiegand69.test", "viewer": Object { "blockedBy": false, "muted": false, }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(0)@jpeg", "did": "user(3)", - "displayName": "Sadie Carter", - "handle": "shane-torphy52.test", + "handle": "carlos6.test", "viewer": Object { "blockedBy": false, "muted": false, }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(0)@jpeg", - "did": "user(5)", - "displayName": "Carlton Abernathy IV", - "handle": "aliya-hodkiewicz.test", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(0)@jpeg", + "did": "user(4)", + "displayName": "Latoya Windler", + "handle": "carolina-mcdermott77.test", "viewer": Object { "blockedBy": false, "muted": false, }, }, Object { - "did": "user(7)", - "handle": "carlos6.test", + "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(0)@jpeg", + "did": "user(6)", + "displayName": "Rachel Kshlerin", + "handle": "cayla-marquardt39.test", "viewer": Object { "blockedBy": false, "muted": false, }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(9)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(9)/cids(0)@jpeg", "did": "user(8)", - "displayName": "Latoya Windler", - "handle": "carolina-mcdermott77.test", + "displayName": "Carol Littel", + "handle": "eudora-dietrich4.test", "viewer": Object { "blockedBy": false, "muted": false, }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(11)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(11)/cids(0)@jpeg", "did": "user(10)", - "displayName": "Rachel Kshlerin", - "handle": "cayla-marquardt39.test", + "displayName": "Sadie Carter", + "handle": "shane-torphy52.test", "viewer": Object { "blockedBy": false, "muted": false, diff --git a/packages/bsky/tests/views/__snapshots__/author-feed.test.ts.snap b/packages/bsky/tests/views/__snapshots__/author-feed.test.ts.snap index 7eaebb72be6..59123e54b20 100644 --- a/packages/bsky/tests/views/__snapshots__/author-feed.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/author-feed.test.ts.snap @@ -5,11 +5,28 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -24,18 +41,18 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(3)", - "uri": "record(2)", + "cid": "cids(4)", + "uri": "record(3)", }, "root": Object { - "cid": "cids(2)", - "uri": "record(1)", + "cid": "cids(3)", + "uri": "record(2)", }, }, "text": "thanks bob", }, "replyCount": 0, - "repostCount": 0, + "repostCount": 1, "uri": "record(0)", "viewer": Object {}, }, @@ -43,45 +60,45 @@ Array [ "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(4)", - "following": "record(3)", + "followedBy": "record(5)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(4)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(3)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(3)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(5)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(3)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(2)", + "uri": "record(3)", "val": "test-label", }, Object { - "cid": "cids(3)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(2)", + "uri": "record(3)", "val": "test-label-2", }, ], @@ -98,7 +115,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(5)", }, "size": 4114, }, @@ -107,35 +124,52 @@ Array [ }, "reply": Object { "parent": Object { - "cid": "cids(2)", - "uri": "record(1)", + "cid": "cids(3)", + "uri": "record(2)", }, "root": Object { - "cid": "cids(2)", - "uri": "record(1)", + "cid": "cids(3)", + "uri": "record(2)", }, }, "text": "hear that label_me label_me_2", }, "replyCount": 1, "repostCount": 0, - "uri": "record(2)", + "uri": "record(3)", "viewer": Object {}, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -146,7 +180,7 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(1)", + "uri": "record(2)", "viewer": Object {}, }, }, @@ -154,17 +188,34 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(5)", + "cid": "cids(6)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { @@ -175,11 +226,11 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(7)", + "following": "record(8)", "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(7)", "embeds": Array [ Object { "$type": "app.bsky.embed.record#view", @@ -191,15 +242,15 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(10)", - "following": "record(9)", + "followedBy": "record(11)", + "following": "record(10)", "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(8)", + "uri": "record(9)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -214,7 +265,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(5)", }, "size": 4114, }, @@ -225,7 +276,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(8)", + "$link": "cids(9)", }, "size": 12736, }, @@ -234,8 +285,8 @@ Array [ }, "record": Object { "record": Object { - "cid": "cids(9)", - "uri": "record(11)", + "cid": "cids(10)", + "uri": "record(12)", }, }, }, @@ -246,15 +297,15 @@ Array [ ], "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(6)", + "uri": "record(7)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(7)", - "uri": "record(8)", + "cid": "cids(8)", + "uri": "record(9)", }, }, "facets": Array [ @@ -278,11 +329,11 @@ Array [ "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(5)", + "cid": "cids(6)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(5)", + "uri": "record(6)", "val": "test-label", }, ], @@ -293,32 +344,49 @@ Array [ "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(6)", - "uri": "record(6)", + "cid": "cids(7)", + "uri": "record(7)", }, }, "text": "yoohoo label_me", }, "replyCount": 0, "repostCount": 0, - "uri": "record(5)", + "uri": "record(6)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -329,35 +397,69 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(1)", + "uri": "record(2)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(10)", + "cid": "cids(11)", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(11)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(13)", + "val": "self-label", + }, + ], "likeCount": 0, "record": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label", + }, + ], + }, "text": "hey there", }, "replyCount": 0, "repostCount": 0, - "uri": "record(12)", + "uri": "record(13)", "viewer": Object {}, }, }, @@ -369,7 +471,7 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "bobby", "handle": "bob.test", @@ -385,8 +487,8 @@ Array [ "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(1)/cids(2)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(1)/cids(2)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(1)/cids(2)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(1)/cids(2)@jpeg", }, ], }, @@ -450,11 +552,28 @@ Array [ "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(4)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(4)", + "val": "self-label-a", + }, + Object { + "cid": "cids(4)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(4)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(3)", @@ -475,17 +594,34 @@ Array [ "repostCount": 1, "uri": "record(1)", "viewer": Object { - "like": "record(4)", + "like": "record(5)", }, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(4)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(4)", + "val": "self-label-a", + }, + Object { + "cid": "cids(4)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(4)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(3)", @@ -506,7 +642,7 @@ Array [ "repostCount": 1, "uri": "record(1)", "viewer": Object { - "like": "record(4)", + "like": "record(5)", }, }, }, @@ -514,7 +650,7 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "bobby", "handle": "bob.test", @@ -524,7 +660,7 @@ Array [ "muted": false, }, }, - "cid": "cids(4)", + "cid": "cids(5)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -535,14 +671,14 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(5)", + "uri": "record(6)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "bobby", "handle": "bob.test", @@ -552,7 +688,7 @@ Array [ "muted": false, }, }, - "cid": "cids(5)", + "cid": "cids(6)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -567,7 +703,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(6)", + "uri": "record(7)", "viewer": Object {}, }, }, @@ -610,13 +746,13 @@ Array [ "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(2)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(2)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(2)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(2)@jpeg", }, Object { "alt": "tests/image/fixtures/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(3)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(3)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(3)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(3)@jpeg", }, ], }, @@ -624,7 +760,7 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(5)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", @@ -787,11 +923,28 @@ Array [ "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(8)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(9)", + "val": "self-label-a", + }, + Object { + "cid": "cids(8)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(9)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(8)", @@ -812,17 +965,34 @@ Array [ "repostCount": 1, "uri": "record(6)", "viewer": Object { - "like": "record(9)", + "like": "record(10)", }, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(8)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(9)", + "val": "self-label-a", + }, + Object { + "cid": "cids(8)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(9)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(8)", @@ -843,7 +1013,7 @@ Array [ "repostCount": 1, "uri": "record(6)", "viewer": Object { - "like": "record(9)", + "like": "record(10)", }, }, }, @@ -867,13 +1037,13 @@ Array [ "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(2)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(2)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(2)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(2)@jpeg", }, Object { "alt": "tests/image/fixtures/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(3)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(3)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(3)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(3)@jpeg", }, ], }, @@ -881,7 +1051,7 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(5)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", @@ -967,11 +1137,28 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(1)", @@ -981,6 +1168,208 @@ Array [ "cid": "cids(0)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], + "likeCount": 0, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000Z", + "reply": Object { + "parent": Object { + "cid": "cids(4)", + "uri": "record(4)", + }, + "root": Object { + "cid": "cids(3)", + "uri": "record(3)", + }, + }, + "text": "thanks bob", + }, + "replyCount": 0, + "repostCount": 1, + "uri": "record(0)", + "viewer": Object { + "repost": "record(5)", + }, + }, + "reason": Object { + "$type": "app.bsky.feed.defs#reasonRepost", + "by": Object { + "did": "user(2)", + "handle": "dan.test", + "labels": Array [], + "viewer": Object { + "blockedBy": false, + "muted": false, + }, + }, + "indexedAt": "1970-01-01T00:00:00.000Z", + }, + "reply": Object { + "parent": Object { + "$type": "app.bsky.feed.defs#postView", + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", + "displayName": "bobby", + "handle": "bob.test", + "labels": Array [], + "viewer": Object { + "blockedBy": false, + "following": "record(8)", + "muted": false, + }, + }, + "cid": "cids(4)", + "embed": Object { + "$type": "app.bsky.embed.images#view", + "images": Array [ + Object { + "alt": "tests/image/fixtures/key-landscape-small.jpg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(5)@jpeg", + }, + ], + }, + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [ + Object { + "cid": "cids(4)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "record(4)", + "val": "test-label", + }, + Object { + "cid": "cids(4)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "record(4)", + "val": "test-label-2", + }, + ], + "likeCount": 0, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000Z", + "embed": Object { + "$type": "app.bsky.embed.images", + "images": Array [ + Object { + "alt": "tests/image/fixtures/key-landscape-small.jpg", + "image": Object { + "$type": "blob", + "mimeType": "image/jpeg", + "ref": Object { + "$link": "cids(5)", + }, + "size": 4114, + }, + }, + ], + }, + "reply": Object { + "parent": Object { + "cid": "cids(3)", + "uri": "record(3)", + }, + "root": Object { + "cid": "cids(3)", + "uri": "record(3)", + }, + }, + "text": "hear that label_me label_me_2", + }, + "replyCount": 1, + "repostCount": 0, + "uri": "record(4)", + "viewer": Object {}, + }, + "root": Object { + "$type": "app.bsky.feed.defs#postView", + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(1)", + "muted": false, + }, + }, + "cid": "cids(3)", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [], + "likeCount": 3, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000000Z", + "text": "again", + }, + "replyCount": 2, + "repostCount": 1, + "uri": "record(3)", + "viewer": Object { + "like": "record(7)", + "repost": "record(6)", + }, + }, + }, + }, + Object { + "post": Object { + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(1)", + "muted": false, + }, + }, + "cid": "cids(3)", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [], "likeCount": 3, "record": Object { "$type": "app.bsky.feed.post", @@ -989,10 +1378,10 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(3)", "viewer": Object { - "like": "record(3)", - "repost": "record(2)", + "like": "record(7)", + "repost": "record(6)", }, }, "reason": Object { @@ -1020,13 +1409,13 @@ Array [ "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(6)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(3)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -1034,7 +1423,7 @@ Array [ "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(7)", "embeds": Array [ Object { "$type": "app.bsky.embed.recordWithMedia#view", @@ -1043,13 +1432,13 @@ Array [ "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(5)@jpeg", }, Object { "alt": "tests/image/fixtures/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(8)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(8)@jpeg", }, ], }, @@ -1057,21 +1446,21 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(7)", + "following": "record(8)", "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(9)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(6)", + "uri": "record(11)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -1087,7 +1476,7 @@ Array [ ], "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(5)", + "uri": "record(10)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -1102,7 +1491,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(5)", }, "size": 4114, }, @@ -1113,7 +1502,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(5)", + "$link": "cids(8)", }, "size": 12736, }, @@ -1122,8 +1511,8 @@ Array [ }, "record": Object { "record": Object { - "cid": "cids(6)", - "uri": "record(6)", + "cid": "cids(9)", + "uri": "record(11)", }, }, }, @@ -1140,8 +1529,8 @@ Array [ "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(3)", - "uri": "record(5)", + "cid": "cids(7)", + "uri": "record(10)", }, }, "facets": Array [ @@ -1162,7 +1551,7 @@ Array [ }, "replyCount": 0, "repostCount": 1, - "uri": "record(4)", + "uri": "record(9)", "viewer": Object {}, }, }, @@ -1177,7 +1566,7 @@ Array [ "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(10)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -1188,7 +1577,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(8)", + "uri": "record(12)", "viewer": Object {}, }, }, @@ -1200,11 +1589,28 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -1221,18 +1627,18 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(3)", - "uri": "record(4)", + "cid": "cids(4)", + "uri": "record(5)", }, "root": Object { - "cid": "cids(2)", - "uri": "record(3)", + "cid": "cids(3)", + "uri": "record(4)", }, }, "text": "thanks bob", }, "replyCount": 0, - "repostCount": 0, + "repostCount": 1, "uri": "record(0)", "viewer": Object {}, }, @@ -1240,44 +1646,44 @@ Array [ "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(6)", + "followedBy": "record(7)", "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(4)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(3)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(3)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(5)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(3)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(4)", + "uri": "record(5)", "val": "test-label", }, Object { - "cid": "cids(3)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(4)", + "uri": "record(5)", "val": "test-label-2", }, ], @@ -1294,7 +1700,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(5)", }, "size": 4114, }, @@ -1303,29 +1709,46 @@ Array [ }, "reply": Object { "parent": Object { - "cid": "cids(2)", - "uri": "record(3)", + "cid": "cids(3)", + "uri": "record(4)", }, "root": Object { - "cid": "cids(2)", - "uri": "record(3)", + "cid": "cids(3)", + "uri": "record(4)", }, }, "text": "hear that label_me label_me_2", }, "replyCount": 1, "repostCount": 0, - "uri": "record(4)", + "uri": "record(5)", "viewer": Object {}, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -1333,7 +1756,7 @@ Array [ "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -1344,9 +1767,9 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(3)", + "uri": "record(4)", "viewer": Object { - "like": "record(5)", + "like": "record(6)", }, }, }, @@ -1354,11 +1777,28 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -1366,7 +1806,7 @@ Array [ "muted": false, }, }, - "cid": "cids(5)", + "cid": "cids(6)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { @@ -1380,7 +1820,7 @@ Array [ "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(7)", "embeds": Array [ Object { "$type": "app.bsky.embed.record#view", @@ -1395,10 +1835,10 @@ Array [ "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(9)", + "uri": "record(10)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -1413,7 +1853,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(5)", }, "size": 4114, }, @@ -1424,7 +1864,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(8)", + "$link": "cids(9)", }, "size": 12736, }, @@ -1433,8 +1873,8 @@ Array [ }, "record": Object { "record": Object { - "cid": "cids(9)", - "uri": "record(10)", + "cid": "cids(10)", + "uri": "record(11)", }, }, }, @@ -1445,15 +1885,15 @@ Array [ ], "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(8)", + "uri": "record(9)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(7)", - "uri": "record(9)", + "cid": "cids(8)", + "uri": "record(10)", }, }, "facets": Array [ @@ -1477,11 +1917,11 @@ Array [ "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(5)", + "cid": "cids(6)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(7)", + "uri": "record(8)", "val": "test-label", }, ], @@ -1492,28 +1932,45 @@ Array [ "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(6)", - "uri": "record(8)", + "cid": "cids(7)", + "uri": "record(9)", }, }, "text": "yoohoo label_me", }, "replyCount": 0, "repostCount": 0, - "uri": "record(7)", + "uri": "record(8)", "viewer": Object { - "like": "record(11)", + "like": "record(12)", }, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -1521,7 +1978,7 @@ Array [ "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -1532,20 +1989,37 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(3)", + "uri": "record(4)", "viewer": Object { - "like": "record(5)", + "like": "record(6)", }, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -1553,18 +2027,35 @@ Array [ "muted": false, }, }, - "cid": "cids(10)", + "cid": "cids(11)", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(11)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(13)", + "val": "self-label", + }, + ], "likeCount": 0, "record": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label", + }, + ], + }, "text": "hey there", }, "replyCount": 0, "repostCount": 0, - "uri": "record(12)", + "uri": "record(13)", "viewer": Object {}, }, }, diff --git a/packages/bsky/tests/views/__snapshots__/block-lists.test.ts.snap b/packages/bsky/tests/views/__snapshots__/block-lists.test.ts.snap new file mode 100644 index 00000000000..fae6e7f4fa9 --- /dev/null +++ b/packages/bsky/tests/views/__snapshots__/block-lists.test.ts.snap @@ -0,0 +1,557 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`pds views with blocking from block lists blocks record embeds 1`] = ` +Object { + "thread": Object { + "$type": "app.bsky.feed.defs#threadViewPost", + "post": Object { + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(1)", + "muted": false, + }, + }, + "cid": "cids(0)", + "embed": Object { + "$type": "app.bsky.embed.record#view", + "record": Object { + "$type": "app.bsky.embed.record#viewRecord", + "author": Object { + "did": "user(2)", + "handle": "dan.test", + "labels": Array [], + "viewer": Object { + "blockedBy": false, + "muted": false, + }, + }, + "cid": "cids(3)", + "embeds": Array [ + Object { + "$type": "app.bsky.embed.record#view", + "record": Object { + "$type": "app.bsky.embed.record#viewBlocked", + "author": Object { + "did": "user(3)", + "viewer": Object { + "blockedBy": false, + "blocking": "record(5)", + }, + }, + "blocked": true, + "uri": "record(4)", + }, + }, + ], + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [], + "uri": "record(3)", + "value": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000Z", + "embed": Object { + "$type": "app.bsky.embed.record", + "record": Object { + "cid": "cids(4)", + "uri": "record(4)", + }, + }, + "facets": Array [ + Object { + "features": Array [ + Object { + "$type": "app.bsky.richtext.facet#mention", + "did": "user(0)", + }, + ], + "index": Object { + "byteEnd": 18, + "byteStart": 0, + }, + }, + ], + "text": "@alice.bluesky.xyz is the best", + }, + }, + }, + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [ + Object { + "cid": "cids(0)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "record(0)", + "val": "test-label", + }, + ], + "likeCount": 2, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000Z", + "embed": Object { + "$type": "app.bsky.embed.record", + "record": Object { + "cid": "cids(3)", + "uri": "record(3)", + }, + }, + "text": "yoohoo label_me", + }, + "replyCount": 0, + "repostCount": 0, + "uri": "record(0)", + "viewer": Object {}, + }, + }, +} +`; + +exports[`pds views with blocking from block lists blocks thread parent 1`] = ` +Object { + "thread": Object { + "$type": "app.bsky.feed.defs#threadViewPost", + "parent": Object { + "$type": "app.bsky.feed.defs#blockedPost", + "author": Object { + "did": "user(2)", + "viewer": Object { + "blockedBy": true, + }, + }, + "blocked": true, + "uri": "record(4)", + }, + "post": Object { + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(2)", + "following": "record(1)", + "muted": false, + }, + }, + "cid": "cids(0)", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [], + "likeCount": 0, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000Z", + "reply": Object { + "parent": Object { + "cid": "cids(3)", + "uri": "record(4)", + }, + "root": Object { + "cid": "cids(3)", + "uri": "record(4)", + }, + }, + "text": "alice replies to dan", + }, + "replyCount": 0, + "repostCount": 0, + "uri": "record(0)", + "viewer": Object {}, + }, + "replies": Array [], + }, +} +`; + +exports[`pds views with blocking from block lists blocks thread reply 1`] = ` +Object { + "thread": Object { + "$type": "app.bsky.feed.defs#threadViewPost", + "post": Object { + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(1)", + "muted": false, + }, + }, + "cid": "cids(0)", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [], + "likeCount": 3, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000000Z", + "text": "again", + }, + "replyCount": 2, + "repostCount": 1, + "uri": "record(0)", + "viewer": Object { + "like": "record(4)", + "repost": "record(3)", + }, + }, + "replies": Array [ + Object { + "$type": "app.bsky.feed.defs#blockedPost", + "author": Object { + "did": "user(2)", + "viewer": Object { + "blockedBy": false, + "blocking": "record(6)", + }, + }, + "blocked": true, + "uri": "record(5)", + }, + Object { + "$type": "app.bsky.feed.defs#blockedPost", + "author": Object { + "did": "user(3)", + "viewer": Object { + "blockedBy": false, + "blocking": "record(6)", + }, + }, + "blocked": true, + "uri": "record(7)", + }, + ], + }, +} +`; + +exports[`pds views with blocking from block lists returns a users own list blocks 1`] = ` +Object { + "cursor": "0000000000000::bafycid", + "lists": Array [ + Object { + "cid": "cids(0)", + "creator": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "description": "its me!", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(2)", + "muted": false, + }, + }, + "description": "blah blah", + "indexedAt": "1970-01-01T00:00:00.000Z", + "name": "new list", + "purpose": "app.bsky.graph.defs#blocklist", + "uri": "record(0)", + "viewer": Object { + "blocked": "record(1)", + "muted": false, + }, + }, + Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "cid": "cids(3)", + "creator": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "description": "its me!", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(2)", + "muted": false, + }, + }, + "description": "big list of blocks", + "indexedAt": "1970-01-01T00:00:00.000Z", + "name": "alice blocks", + "purpose": "app.bsky.graph.defs#blocklist", + "uri": "record(4)", + "viewer": Object { + "blocked": "record(5)", + "muted": false, + }, + }, + ], +} +`; + +exports[`pds views with blocking from block lists returns lists associated with a user 1`] = ` +Object { + "cursor": "0000000000000::bafycid", + "lists": Array [ + Object { + "cid": "cids(0)", + "creator": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "description": "its me!", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(1)", + "muted": false, + }, + }, + "description": "blah blah", + "indexedAt": "1970-01-01T00:00:00.000Z", + "name": "new list", + "purpose": "app.bsky.graph.defs#blocklist", + "uri": "record(0)", + "viewer": Object { + "muted": false, + }, + }, + Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "cid": "cids(3)", + "creator": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "description": "its me!", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(1)", + "muted": false, + }, + }, + "description": "big list of blocks", + "indexedAt": "1970-01-01T00:00:00.000Z", + "name": "alice blocks", + "purpose": "app.bsky.graph.defs#blocklist", + "uri": "record(3)", + "viewer": Object { + "blocked": "record(4)", + "muted": false, + }, + }, + ], +} +`; + +exports[`pds views with blocking from block lists returns the contents of a list 1`] = ` +Object { + "cursor": "0000000000000::bafycid", + "items": Array [ + Object { + "subject": Object { + "did": "user(2)", + "handle": "carol.test", + "labels": Array [], + "viewer": Object { + "blockedBy": false, + "blocking": "record(0)", + "muted": false, + }, + }, + }, + Object { + "subject": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "description": "hi im bob label_me", + "did": "user(3)", + "displayName": "bobby", + "handle": "bob.test", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [], + "viewer": Object { + "blockedBy": false, + "blocking": "record(0)", + "following": "record(4)", + "muted": false, + }, + }, + }, + ], + "list": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(0)/cids(1)@jpeg", + "cid": "cids(0)", + "creator": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(0)/cids(1)@jpeg", + "description": "its me!", + "did": "user(1)", + "displayName": "ali", + "handle": "alice.test", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(2)", + "muted": false, + }, + }, + "description": "big list of blocks", + "indexedAt": "1970-01-01T00:00:00.000Z", + "name": "alice blocks", + "purpose": "app.bsky.graph.defs#blocklist", + "uri": "record(0)", + "viewer": Object { + "blocked": "record(1)", + "muted": false, + }, + }, +} +`; diff --git a/packages/bsky/tests/views/__snapshots__/blocks.test.ts.snap b/packages/bsky/tests/views/__snapshots__/blocks.test.ts.snap index 582de290e96..086f6e10d4d 100644 --- a/packages/bsky/tests/views/__snapshots__/blocks.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/blocks.test.ts.snap @@ -6,11 +6,28 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(1)", @@ -31,27 +48,35 @@ Object { "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "embeds": Array [ Object { "$type": "app.bsky.embed.record#view", "record": Object { "$type": "app.bsky.embed.record#viewBlocked", - "uri": "record(3)", + "author": Object { + "did": "user(3)", + "viewer": Object { + "blockedBy": false, + "blocking": "record(5)", + }, + }, + "blocked": true, + "uri": "record(4)", }, }, ], "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(2)", + "uri": "record(3)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(3)", - "uri": "record(3)", + "cid": "cids(4)", + "uri": "record(4)", }, }, "facets": Array [ @@ -90,8 +115,8 @@ Object { "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(2)", - "uri": "record(2)", + "cid": "cids(3)", + "uri": "record(3)", }, }, "text": "yoohoo label_me", @@ -111,16 +136,39 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "parent": Object { "$type": "app.bsky.feed.defs#blockedPost", + "author": Object { + "did": "user(2)", + "viewer": Object { + "blockedBy": true, + }, + }, "blocked": true, - "uri": "record(3)", + "uri": "record(4)", }, "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -137,12 +185,12 @@ Object { "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(2)", - "uri": "record(3)", + "cid": "cids(3)", + "uri": "record(4)", }, "root": Object { - "cid": "cids(2)", - "uri": "record(3)", + "cid": "cids(3)", + "uri": "record(4)", }, }, "text": "alice replies to dan", @@ -163,11 +211,28 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(1)", @@ -187,58 +252,65 @@ Object { "repostCount": 1, "uri": "record(0)", "viewer": Object { - "like": "record(3)", - "repost": "record(2)", + "like": "record(4)", + "repost": "record(3)", }, }, "replies": Array [ Object { "$type": "app.bsky.feed.defs#blockedPost", + "author": Object { + "did": "user(2)", + "viewer": Object { + "blockedBy": false, + "blocking": "record(6)", + }, + }, "blocked": true, - "uri": "record(4)", + "uri": "record(5)", }, Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", - "did": "user(2)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(6)", + "following": "record(8)", "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(3)/cids(3)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(3)/cids(3)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(4)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(4)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(2)", + "cid": "cids(3)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(5)", + "uri": "record(7)", "val": "test-label", }, Object { - "cid": "cids(2)", + "cid": "cids(3)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(5)", + "uri": "record(7)", "val": "test-label-2", }, ], @@ -255,7 +327,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(3)", + "$link": "cids(4)", }, "size": 4114, }, @@ -276,7 +348,7 @@ Object { }, "replyCount": 1, "repostCount": 0, - "uri": "record(5)", + "uri": "record(7)", "viewer": Object {}, }, }, diff --git a/packages/bsky/tests/views/__snapshots__/follows.test.ts.snap b/packages/bsky/tests/views/__snapshots__/follows.test.ts.snap index 2f21896ef62..c85b0549de7 100644 --- a/packages/bsky/tests/views/__snapshots__/follows.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/follows.test.ts.snap @@ -5,9 +5,9 @@ Object { "cursor": "0000000000000::bafycid", "followers": Array [ Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "description": "descript-eve", - "did": "user(2)", + "did": "user(0)", "displayName": "display-eve", "handle": "eve.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -20,9 +20,9 @@ Object { }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)@jpeg", "description": "descript-dan", - "did": "user(4)", + "did": "user(2)", "displayName": "display-dan", "handle": "dan.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -35,9 +35,9 @@ Object { }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(7)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(0)@jpeg", "description": "descript-bob", - "did": "user(6)", + "did": "user(4)", "displayName": "display-bob", "handle": "bob.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -50,9 +50,9 @@ Object { }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(9)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(0)@jpeg", "description": "descript-carol", - "did": "user(8)", + "did": "user(6)", "displayName": "display-carol", "handle": "carol.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -66,9 +66,9 @@ Object { }, ], "subject": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(9)/cids(0)@jpeg", "description": "descript-alice", - "did": "user(0)", + "did": "user(8)", "displayName": "display-alice", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -86,24 +86,24 @@ Object { "cursor": "0000000000000::bafycid", "followers": Array [ Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "description": "descript-dan", - "did": "user(2)", + "did": "user(0)", "displayName": "display-dan", "handle": "dan.test", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(3)", - "following": "record(2)", + "followedBy": "record(1)", + "following": "record(0)", "muted": false, }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)@jpeg", "description": "descript-alice", - "did": "user(4)", + "did": "user(2)", "displayName": "display-alice", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -115,17 +115,17 @@ Object { }, ], "subject": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(0)@jpeg", "description": "descript-bob", - "did": "user(0)", + "did": "user(4)", "displayName": "display-bob", "handle": "bob.test", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(1)", - "following": "record(0)", + "followedBy": "record(3)", + "following": "record(2)", "muted": false, }, }, @@ -137,39 +137,39 @@ Object { "cursor": "0000000000000::bafycid", "followers": Array [ Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "description": "descript-eve", - "did": "user(2)", + "did": "user(0)", "displayName": "display-eve", "handle": "eve.test", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(3)", - "following": "record(2)", + "followedBy": "record(1)", + "following": "record(0)", "muted": false, }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)@jpeg", "description": "descript-bob", - "did": "user(4)", + "did": "user(2)", "displayName": "display-bob", "handle": "bob.test", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(5)", - "following": "record(4)", + "followedBy": "record(3)", + "following": "record(2)", "muted": false, }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(7)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(0)@jpeg", "description": "descript-alice", - "did": "user(6)", + "did": "user(4)", "displayName": "display-alice", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -181,17 +181,17 @@ Object { }, ], "subject": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(0)@jpeg", "description": "descript-carol", - "did": "user(0)", + "did": "user(6)", "displayName": "display-carol", "handle": "carol.test", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(1)", - "following": "record(0)", + "followedBy": "record(5)", + "following": "record(4)", "muted": false, }, }, @@ -203,9 +203,9 @@ Object { "cursor": "0000000000000::bafycid", "followers": Array [ Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "description": "descript-alice", - "did": "user(2)", + "did": "user(0)", "displayName": "display-alice", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -217,9 +217,9 @@ Object { }, ], "subject": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)@jpeg", "description": "descript-dan", - "did": "user(0)", + "did": "user(2)", "displayName": "display-dan", "handle": "dan.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -239,24 +239,24 @@ Object { "cursor": "0000000000000::bafycid", "followers": Array [ Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "description": "descript-dan", - "did": "user(2)", + "did": "user(0)", "displayName": "display-dan", "handle": "dan.test", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(3)", - "following": "record(2)", + "followedBy": "record(1)", + "following": "record(0)", "muted": false, }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)@jpeg", "description": "descript-alice", - "did": "user(4)", + "did": "user(2)", "displayName": "display-alice", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -268,17 +268,17 @@ Object { }, ], "subject": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(0)@jpeg", "description": "descript-eve", - "did": "user(0)", + "did": "user(4)", "displayName": "display-eve", "handle": "eve.test", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(1)", - "following": "record(0)", + "followedBy": "record(3)", + "following": "record(2)", "muted": false, }, }, @@ -290,9 +290,9 @@ Object { "cursor": "0000000000000::bafycid", "follows": Array [ Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "description": "descript-eve", - "did": "user(2)", + "did": "user(0)", "displayName": "display-eve", "handle": "eve.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -305,9 +305,9 @@ Object { }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)@jpeg", "description": "descript-dan", - "did": "user(4)", + "did": "user(2)", "displayName": "display-dan", "handle": "dan.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -320,9 +320,9 @@ Object { }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(7)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(0)@jpeg", "description": "descript-carol", - "did": "user(6)", + "did": "user(4)", "displayName": "display-carol", "handle": "carol.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -335,9 +335,9 @@ Object { }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(9)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(0)@jpeg", "description": "descript-bob", - "did": "user(8)", + "did": "user(6)", "displayName": "display-bob", "handle": "bob.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -351,9 +351,9 @@ Object { }, ], "subject": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(9)/cids(0)@jpeg", "description": "descript-alice", - "did": "user(0)", + "did": "user(8)", "displayName": "display-alice", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -371,24 +371,24 @@ Object { "cursor": "0000000000000::bafycid", "follows": Array [ Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "description": "descript-carol", - "did": "user(2)", + "did": "user(0)", "displayName": "display-carol", "handle": "carol.test", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(3)", - "following": "record(2)", + "followedBy": "record(1)", + "following": "record(0)", "muted": false, }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)@jpeg", "description": "descript-alice", - "did": "user(4)", + "did": "user(2)", "displayName": "display-alice", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -400,17 +400,17 @@ Object { }, ], "subject": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(0)@jpeg", "description": "descript-bob", - "did": "user(0)", + "did": "user(4)", "displayName": "display-bob", "handle": "bob.test", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(1)", - "following": "record(0)", + "followedBy": "record(3)", + "following": "record(2)", "muted": false, }, }, @@ -422,9 +422,9 @@ Object { "cursor": "0000000000000::bafycid", "follows": Array [ Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "description": "descript-alice", - "did": "user(2)", + "did": "user(0)", "displayName": "display-alice", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -436,9 +436,9 @@ Object { }, ], "subject": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)@jpeg", "description": "descript-carol", - "did": "user(0)", + "did": "user(2)", "displayName": "display-carol", "handle": "carol.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -458,39 +458,39 @@ Object { "cursor": "0000000000000::bafycid", "follows": Array [ Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "description": "descript-eve", - "did": "user(2)", + "did": "user(0)", "displayName": "display-eve", "handle": "eve.test", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(3)", - "following": "record(2)", + "followedBy": "record(1)", + "following": "record(0)", "muted": false, }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)@jpeg", "description": "descript-bob", - "did": "user(4)", + "did": "user(2)", "displayName": "display-bob", "handle": "bob.test", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(5)", - "following": "record(4)", + "followedBy": "record(3)", + "following": "record(2)", "muted": false, }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(7)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(0)@jpeg", "description": "descript-alice", - "did": "user(6)", + "did": "user(4)", "displayName": "display-alice", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -502,17 +502,17 @@ Object { }, ], "subject": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(0)@jpeg", "description": "descript-dan", - "did": "user(0)", + "did": "user(6)", "displayName": "display-dan", "handle": "dan.test", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(1)", - "following": "record(0)", + "followedBy": "record(5)", + "following": "record(4)", "muted": false, }, }, @@ -524,24 +524,24 @@ Object { "cursor": "0000000000000::bafycid", "follows": Array [ Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "description": "descript-carol", - "did": "user(2)", + "did": "user(0)", "displayName": "display-carol", "handle": "carol.test", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(3)", - "following": "record(2)", + "followedBy": "record(1)", + "following": "record(0)", "muted": false, }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)@jpeg", "description": "descript-alice", - "did": "user(4)", + "did": "user(2)", "displayName": "display-alice", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -553,17 +553,17 @@ Object { }, ], "subject": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(0)@jpeg", "description": "descript-eve", - "did": "user(0)", + "did": "user(4)", "displayName": "display-eve", "handle": "eve.test", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(1)", - "following": "record(0)", + "followedBy": "record(3)", + "following": "record(2)", "muted": false, }, }, diff --git a/packages/bsky/tests/views/__snapshots__/likes.test.ts.snap b/packages/bsky/tests/views/__snapshots__/likes.test.ts.snap index ec116eb4b49..426467a3fa7 100644 --- a/packages/bsky/tests/views/__snapshots__/likes.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/likes.test.ts.snap @@ -24,7 +24,7 @@ Object { "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(1)", + "following": "record(0)", "muted": false, }, }, @@ -38,8 +38,8 @@ Object { "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(3)", - "following": "record(2)", + "followedBy": "record(2)", + "following": "record(1)", "muted": false, }, }, @@ -48,7 +48,7 @@ Object { }, Object { "actor": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(0)@jpeg", "description": "hi im bob label_me", "did": "user(3)", "displayName": "bobby", @@ -57,8 +57,8 @@ Object { "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(5)", - "following": "record(4)", + "followedBy": "record(4)", + "following": "record(3)", "muted": false, }, }, @@ -66,7 +66,7 @@ Object { "indexedAt": "1970-01-01T00:00:00.000Z", }, ], - "uri": "record(0)", + "uri": "record(5)", } `; @@ -81,8 +81,8 @@ Object { "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(2)", - "following": "record(1)", + "followedBy": "record(1)", + "following": "record(0)", "muted": false, }, }, @@ -90,6 +90,6 @@ Object { "indexedAt": "1970-01-01T00:00:00.000Z", }, ], - "uri": "record(0)", + "uri": "record(2)", } `; diff --git a/packages/bsky/tests/views/__snapshots__/mute-lists.test.ts.snap b/packages/bsky/tests/views/__snapshots__/mute-lists.test.ts.snap index 2b8a2a55aa4..0a081f91292 100644 --- a/packages/bsky/tests/views/__snapshots__/mute-lists.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/mute-lists.test.ts.snap @@ -3,11 +3,28 @@ exports[`bsky views with mutes from mute lists embeds lists in posts 1`] = ` Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -18,12 +35,30 @@ Object { "$type": "app.bsky.embed.record#view", "record": Object { "$type": "app.bsky.graph.defs#listView", - "cid": "cids(3)", + "cid": "cids(4)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -33,7 +68,7 @@ Object { "indexedAt": "1970-01-01T00:00:00.000Z", "name": "updated alice mutes", "purpose": "app.bsky.graph.defs#modlist", - "uri": "record(1)", + "uri": "record(2)", "viewer": Object { "muted": false, }, @@ -48,8 +83,8 @@ Object { "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(2)", - "uri": "record(1)", + "cid": "cids(3)", + "uri": "record(2)", }, }, "text": "list embed!", @@ -66,11 +101,28 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(1)", @@ -90,8 +142,8 @@ Object { "repostCount": 1, "uri": "record(0)", "viewer": Object { - "like": "record(3)", - "repost": "record(2)", + "like": "record(4)", + "repost": "record(3)", }, }, "replies": Array [ @@ -104,23 +156,23 @@ Object { "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(7)", - "following": "record(6)", + "followedBy": "record(8)", + "following": "record(7)", "muted": true, "mutedByList": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", - "cid": "cids(3)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "cid": "cids(4)", "indexedAt": "1970-01-01T00:00:00.000Z", "name": "alice mutes", "purpose": "app.bsky.graph.defs#modlist", - "uri": "record(5)", + "uri": "record(6)", "viewer": Object { "muted": true, }, }, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -141,7 +193,7 @@ Object { }, "replyCount": 0, "repostCount": 0, - "uri": "record(4)", + "uri": "record(5)", "viewer": Object {}, }, }, @@ -149,55 +201,55 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(9)", + "following": "record(10)", "muted": true, "mutedByList": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", - "cid": "cids(3)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "cid": "cids(4)", "indexedAt": "1970-01-01T00:00:00.000Z", "name": "alice mutes", "purpose": "app.bsky.graph.defs#modlist", - "uri": "record(5)", + "uri": "record(6)", "viewer": Object { "muted": true, }, }, }, }, - "cid": "cids(4)", + "cid": "cids(5)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(4)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(4)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(6)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(6)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(4)", + "cid": "cids(5)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(8)", + "uri": "record(9)", "val": "test-label", }, Object { - "cid": "cids(4)", + "cid": "cids(5)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(8)", + "uri": "record(9)", "val": "test-label-2", }, ], @@ -214,7 +266,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(5)", + "$link": "cids(6)", }, "size": 4114, }, @@ -235,7 +287,7 @@ Object { }, "replyCount": 1, "repostCount": 0, - "uri": "record(8)", + "uri": "record(9)", "viewer": Object {}, }, }, @@ -250,13 +302,30 @@ Object { Object { "cid": "cids(0)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "description": "its me!", "did": "user(0)", "displayName": "ali", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(1)", @@ -273,16 +342,33 @@ Object { }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", - "cid": "cids(2)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "cid": "cids(3)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "description": "its me!", "did": "user(0)", "displayName": "ali", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(1)", @@ -293,7 +379,7 @@ Object { "indexedAt": "1970-01-01T00:00:00.000Z", "name": "alice mutes", "purpose": "app.bsky.graph.defs#modlist", - "uri": "record(2)", + "uri": "record(3)", "viewer": Object { "muted": true, }, @@ -309,13 +395,30 @@ Object { Object { "cid": "cids(0)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "description": "its me!", "did": "user(0)", "displayName": "ali", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(1)", @@ -332,16 +435,33 @@ Object { }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", - "cid": "cids(2)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "cid": "cids(3)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "description": "its me!", "did": "user(0)", "displayName": "ali", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(1)", @@ -352,7 +472,7 @@ Object { "indexedAt": "1970-01-01T00:00:00.000Z", "name": "alice mutes", "purpose": "app.bsky.graph.defs#modlist", - "uri": "record(2)", + "uri": "record(3)", "viewer": Object { "muted": true, }, @@ -367,15 +487,15 @@ Object { "items": Array [ Object { "subject": Object { - "did": "user(0)", + "did": "user(2)", "handle": "carol.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(1)", + "followedBy": "record(3)", "muted": true, "mutedByList": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(0)/cids(1)@jpeg", "cid": "cids(0)", "indexedAt": "1970-01-01T00:00:00.000Z", "name": "alice mutes", @@ -390,19 +510,19 @@ Object { }, Object { "subject": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", "description": "hi im bob label_me", - "did": "user(2)", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(2)", + "following": "record(4)", "muted": true, "mutedByList": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(0)/cids(1)@jpeg", "cid": "cids(0)", "indexedAt": "1970-01-01T00:00:00.000Z", "name": "alice mutes", @@ -417,19 +537,36 @@ Object { }, ], "list": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(0)/cids(1)@jpeg", "cid": "cids(0)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(0)/cids(1)@jpeg", "description": "its me!", - "did": "user(4)", + "did": "user(1)", "displayName": "ali", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, - "followedBy": "record(3)", + "followedBy": "record(1)", "muted": false, }, }, diff --git a/packages/bsky/tests/views/__snapshots__/mutes.test.ts.snap b/packages/bsky/tests/views/__snapshots__/mutes.test.ts.snap index 0e91aad70c9..d6836a810a5 100644 --- a/packages/bsky/tests/views/__snapshots__/mutes.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/mutes.test.ts.snap @@ -3,7 +3,7 @@ exports[`mute views fetches mutes for the logged-in user. 1`] = ` Array [ Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "did": "user(0)", "displayName": "Dr. Lowell DuBuque", "handle": "elta48.test", @@ -15,7 +15,7 @@ Array [ }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)@jpeg", "did": "user(2)", "displayName": "Sally Funk", "handle": "magnus53.test", @@ -36,7 +36,7 @@ Array [ }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(0)@jpeg", "did": "user(5)", "displayName": "Patrick Sawayn", "handle": "jeffrey-sawayn87.test", @@ -48,7 +48,7 @@ Array [ }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(8)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(8)/cids(0)@jpeg", "did": "user(7)", "displayName": "Kim Streich", "handle": "adrienne49.test", @@ -60,7 +60,7 @@ Array [ }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(10)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(10)/cids(0)@jpeg", "did": "user(9)", "displayName": "Carlton Abernathy IV", "handle": "aliya-hodkiewicz.test", @@ -82,7 +82,7 @@ Array [ }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(13)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(13)/cids(0)@jpeg", "description": "hi im bob label_me", "did": "user(12)", "displayName": "bobby", @@ -103,11 +103,28 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -137,12 +154,12 @@ Object { "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(3)", - "following": "record(2)", + "followedBy": "record(4)", + "following": "record(3)", "muted": true, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -163,7 +180,7 @@ Object { }, "replyCount": 0, "repostCount": 0, - "uri": "record(1)", + "uri": "record(2)", "viewer": Object {}, }, }, @@ -171,45 +188,45 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(6)", - "following": "record(5)", + "followedBy": "record(7)", + "following": "record(6)", "muted": true, }, }, - "cid": "cids(3)", + "cid": "cids(4)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(4)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(4)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(5)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(3)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(4)", + "uri": "record(5)", "val": "test-label", }, Object { - "cid": "cids(3)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(4)", + "uri": "record(5)", "val": "test-label-2", }, ], @@ -226,7 +243,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(5)", }, "size": 4114, }, @@ -247,7 +264,7 @@ Object { }, "replyCount": 1, "repostCount": 0, - "uri": "record(4)", + "uri": "record(5)", "viewer": Object {}, }, }, diff --git a/packages/bsky/tests/views/__snapshots__/notifications.test.ts.snap b/packages/bsky/tests/views/__snapshots__/notifications.test.ts.snap index c516d6803ce..5fddc479c76 100644 --- a/packages/bsky/tests/views/__snapshots__/notifications.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/notifications.test.ts.snap @@ -44,17 +44,44 @@ Array [ "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [], + "reason": "repost", + "reasonSubject": "record(4)", + "record": Object { + "$type": "app.bsky.feed.repost", + "createdAt": "1970-01-01T00:00:00.000Z", + "subject": Object { + "cid": "cids(3)", + "uri": "record(4)", + }, + }, + "uri": "record(3)", + }, + Object { + "author": Object { + "did": "user(0)", + "handle": "dan.test", + "labels": Array [], + "viewer": Object { + "blockedBy": false, + "following": "record(1)", + "muted": false, + }, + }, + "cid": "cids(4)", + "indexedAt": "1970-01-01T00:00:00.000Z", + "isRead": false, + "labels": Array [], "reason": "like", - "reasonSubject": "record(2)", + "reasonSubject": "record(4)", "record": Object { "$type": "app.bsky.feed.like", "createdAt": "1970-01-01T00:00:00.000Z", "subject": Object { - "cid": "cids(1)", - "uri": "record(2)", + "cid": "cids(3)", + "uri": "record(4)", }, }, - "uri": "record(3)", + "uri": "record(5)", }, Object { "author": Object { @@ -63,12 +90,12 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(4)", - "following": "record(5)", + "followedBy": "record(6)", + "following": "record(7)", "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(5)", "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [], @@ -78,7 +105,7 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "subject": "user(2)", }, - "uri": "record(4)", + "uri": "record(6)", }, Object { "author": Object { @@ -87,33 +114,33 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(4)", - "following": "record(5)", + "followedBy": "record(6)", + "following": "record(7)", "muted": false, }, }, - "cid": "cids(4)", + "cid": "cids(6)", "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [], "reason": "reply", - "reasonSubject": "record(7)", + "reasonSubject": "record(2)", "record": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(5)", - "uri": "record(7)", - }, - "root": Object { "cid": "cids(1)", "uri": "record(2)", }, + "root": Object { + "cid": "cids(3)", + "uri": "record(4)", + }, }, "text": "indeed", }, - "uri": "record(6)", + "uri": "record(8)", }, Object { "author": Object { @@ -122,26 +149,26 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(4)", - "following": "record(5)", + "followedBy": "record(6)", + "following": "record(7)", "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(7)", "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [], "reason": "like", - "reasonSubject": "record(9)", + "reasonSubject": "record(10)", "record": Object { "$type": "app.bsky.feed.like", "createdAt": "1970-01-01T00:00:00.000Z", "subject": Object { - "cid": "cids(7)", - "uri": "record(9)", + "cid": "cids(8)", + "uri": "record(10)", }, }, - "uri": "record(8)", + "uri": "record(9)", }, Object { "author": Object { @@ -150,30 +177,30 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(4)", - "following": "record(5)", + "followedBy": "record(6)", + "following": "record(7)", "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(9)", "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [], "reason": "like", - "reasonSubject": "record(2)", + "reasonSubject": "record(4)", "record": Object { "$type": "app.bsky.feed.like", "createdAt": "1970-01-01T00:00:00.000Z", "subject": Object { - "cid": "cids(1)", - "uri": "record(2)", + "cid": "cids(3)", + "uri": "record(4)", }, }, - "uri": "record(10)", + "uri": "record(11)", }, Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(10)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(11)@jpeg", "description": "hi im bob label_me", "did": "user(3)", "displayName": "bobby", @@ -182,12 +209,12 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(11)", - "following": "record(12)", + "followedBy": "record(12)", + "following": "record(13)", "muted": false, }, }, - "cid": "cids(9)", + "cid": "cids(10)", "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [], @@ -197,11 +224,11 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "subject": "user(2)", }, - "uri": "record(11)", + "uri": "record(12)", }, Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(10)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(11)@jpeg", "description": "hi im bob label_me", "did": "user(3)", "displayName": "bobby", @@ -210,34 +237,34 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(11)", - "following": "record(12)", + "followedBy": "record(12)", + "following": "record(13)", "muted": false, }, }, - "cid": "cids(11)", + "cid": "cids(12)", "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [ Object { - "cid": "cids(11)", + "cid": "cids(12)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(13)", + "uri": "record(14)", "val": "test-label", }, Object { - "cid": "cids(11)", + "cid": "cids(12)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(13)", + "uri": "record(14)", "val": "test-label-2", }, ], "reason": "reply", - "reasonSubject": "record(2)", + "reasonSubject": "record(4)", "record": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -250,7 +277,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(12)", + "$link": "cids(13)", }, "size": 4114, }, @@ -259,21 +286,21 @@ Array [ }, "reply": Object { "parent": Object { - "cid": "cids(1)", - "uri": "record(2)", + "cid": "cids(3)", + "uri": "record(4)", }, "root": Object { - "cid": "cids(1)", - "uri": "record(2)", + "cid": "cids(3)", + "uri": "record(4)", }, }, "text": "hear that label_me label_me_2", }, - "uri": "record(13)", + "uri": "record(14)", }, Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(10)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(11)@jpeg", "description": "hi im bob label_me", "did": "user(3)", "displayName": "bobby", @@ -282,30 +309,30 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(11)", - "following": "record(12)", + "followedBy": "record(12)", + "following": "record(13)", "muted": false, }, }, - "cid": "cids(13)", + "cid": "cids(14)", "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [], "reason": "like", - "reasonSubject": "record(9)", + "reasonSubject": "record(10)", "record": Object { "$type": "app.bsky.feed.like", "createdAt": "1970-01-01T00:00:00.000Z", "subject": Object { - "cid": "cids(7)", - "uri": "record(9)", + "cid": "cids(8)", + "uri": "record(10)", }, }, - "uri": "record(14)", + "uri": "record(15)", }, Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(10)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(11)@jpeg", "description": "hi im bob label_me", "did": "user(3)", "displayName": "bobby", @@ -314,26 +341,26 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(11)", - "following": "record(12)", + "followedBy": "record(12)", + "following": "record(13)", "muted": false, }, }, - "cid": "cids(14)", + "cid": "cids(15)", "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [], "reason": "like", - "reasonSubject": "record(2)", + "reasonSubject": "record(4)", "record": Object { "$type": "app.bsky.feed.like", "createdAt": "1970-01-01T00:00:00.000Z", "subject": Object { - "cid": "cids(1)", - "uri": "record(2)", + "cid": "cids(3)", + "uri": "record(4)", }, }, - "uri": "record(15)", + "uri": "record(16)", }, ] `; @@ -382,6 +409,33 @@ Array [ "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [], + "reason": "repost", + "reasonSubject": "record(4)", + "record": Object { + "$type": "app.bsky.feed.repost", + "createdAt": "1970-01-01T00:00:00.000Z", + "subject": Object { + "cid": "cids(3)", + "uri": "record(4)", + }, + }, + "uri": "record(3)", + }, + Object { + "author": Object { + "did": "user(0)", + "handle": "dan.test", + "labels": Array [], + "viewer": Object { + "blockedBy": false, + "following": "record(1)", + "muted": false, + }, + }, + "cid": "cids(4)", + "indexedAt": "1970-01-01T00:00:00.000Z", + "isRead": false, + "labels": Array [], "reason": "mention", "record": Object { "$type": "app.bsky.feed.post", @@ -389,8 +443,8 @@ Array [ "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(3)", - "uri": "record(4)", + "cid": "cids(5)", + "uri": "record(6)", }, }, "facets": Array [ @@ -409,7 +463,7 @@ Array [ ], "text": "@alice.bluesky.xyz is the best", }, - "uri": "record(3)", + "uri": "record(5)", }, Object { "author": Object { @@ -422,21 +476,21 @@ Array [ "muted": false, }, }, - "cid": "cids(4)", + "cid": "cids(6)", "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [], "reason": "like", - "reasonSubject": "record(2)", + "reasonSubject": "record(4)", "record": Object { "$type": "app.bsky.feed.like", "createdAt": "1970-01-01T00:00:00.000Z", "subject": Object { - "cid": "cids(1)", - "uri": "record(2)", + "cid": "cids(3)", + "uri": "record(4)", }, }, - "uri": "record(5)", + "uri": "record(7)", }, Object { "author": Object { @@ -445,12 +499,12 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(6)", - "following": "record(7)", + "followedBy": "record(8)", + "following": "record(9)", "muted": false, }, }, - "cid": "cids(5)", + "cid": "cids(7)", "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [], @@ -460,7 +514,7 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "subject": "user(1)", }, - "uri": "record(6)", + "uri": "record(8)", }, Object { "author": Object { @@ -469,33 +523,33 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(6)", - "following": "record(7)", + "followedBy": "record(8)", + "following": "record(9)", "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [], "reason": "reply", - "reasonSubject": "record(9)", + "reasonSubject": "record(2)", "record": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(7)", - "uri": "record(9)", - }, - "root": Object { "cid": "cids(1)", "uri": "record(2)", }, + "root": Object { + "cid": "cids(3)", + "uri": "record(4)", + }, }, "text": "indeed", }, - "uri": "record(8)", + "uri": "record(10)", }, Object { "author": Object { @@ -504,33 +558,33 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(6)", - "following": "record(7)", + "followedBy": "record(8)", + "following": "record(9)", "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(9)", "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [], "reason": "reply", - "reasonSubject": "record(2)", + "reasonSubject": "record(4)", "record": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(1)", - "uri": "record(2)", + "cid": "cids(3)", + "uri": "record(4)", }, "root": Object { - "cid": "cids(1)", - "uri": "record(2)", + "cid": "cids(3)", + "uri": "record(4)", }, }, "text": "of course", }, - "uri": "record(10)", + "uri": "record(11)", }, Object { "author": Object { @@ -539,26 +593,26 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(6)", - "following": "record(7)", + "followedBy": "record(8)", + "following": "record(9)", "muted": false, }, }, - "cid": "cids(9)", + "cid": "cids(10)", "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [], "reason": "like", - "reasonSubject": "record(12)", + "reasonSubject": "record(13)", "record": Object { "$type": "app.bsky.feed.like", "createdAt": "1970-01-01T00:00:00.000Z", "subject": Object { - "cid": "cids(10)", - "uri": "record(12)", + "cid": "cids(11)", + "uri": "record(13)", }, }, - "uri": "record(11)", + "uri": "record(12)", }, Object { "author": Object { @@ -567,30 +621,30 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(6)", - "following": "record(7)", + "followedBy": "record(8)", + "following": "record(9)", "muted": false, }, }, - "cid": "cids(11)", + "cid": "cids(12)", "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [], "reason": "like", - "reasonSubject": "record(2)", + "reasonSubject": "record(4)", "record": Object { "$type": "app.bsky.feed.like", "createdAt": "1970-01-01T00:00:00.000Z", "subject": Object { - "cid": "cids(1)", - "uri": "record(2)", + "cid": "cids(3)", + "uri": "record(4)", }, }, - "uri": "record(13)", + "uri": "record(14)", }, Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(13)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(14)@jpeg", "description": "hi im bob label_me", "did": "user(3)", "displayName": "bobby", @@ -599,12 +653,12 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(14)", - "following": "record(15)", + "followedBy": "record(15)", + "following": "record(16)", "muted": false, }, }, - "cid": "cids(12)", + "cid": "cids(13)", "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [], @@ -614,11 +668,11 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "subject": "user(1)", }, - "uri": "record(14)", + "uri": "record(15)", }, Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(13)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(14)@jpeg", "description": "hi im bob label_me", "did": "user(3)", "displayName": "bobby", @@ -627,34 +681,34 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(14)", - "following": "record(15)", + "followedBy": "record(15)", + "following": "record(16)", "muted": false, }, }, - "cid": "cids(14)", + "cid": "cids(15)", "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [ Object { - "cid": "cids(14)", + "cid": "cids(15)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(16)", + "uri": "record(17)", "val": "test-label", }, Object { - "cid": "cids(14)", + "cid": "cids(15)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(16)", + "uri": "record(17)", "val": "test-label-2", }, ], "reason": "reply", - "reasonSubject": "record(2)", + "reasonSubject": "record(4)", "record": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -667,7 +721,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(15)", + "$link": "cids(16)", }, "size": 4114, }, @@ -676,21 +730,21 @@ Array [ }, "reply": Object { "parent": Object { - "cid": "cids(1)", - "uri": "record(2)", + "cid": "cids(3)", + "uri": "record(4)", }, "root": Object { - "cid": "cids(1)", - "uri": "record(2)", + "cid": "cids(3)", + "uri": "record(4)", }, }, "text": "hear that label_me label_me_2", }, - "uri": "record(16)", + "uri": "record(17)", }, Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(13)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(14)@jpeg", "description": "hi im bob label_me", "did": "user(3)", "displayName": "bobby", @@ -699,30 +753,30 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(14)", - "following": "record(15)", + "followedBy": "record(15)", + "following": "record(16)", "muted": false, }, }, - "cid": "cids(16)", + "cid": "cids(17)", "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [], "reason": "like", - "reasonSubject": "record(12)", + "reasonSubject": "record(13)", "record": Object { "$type": "app.bsky.feed.like", "createdAt": "1970-01-01T00:00:00.000Z", "subject": Object { - "cid": "cids(10)", - "uri": "record(12)", + "cid": "cids(11)", + "uri": "record(13)", }, }, - "uri": "record(17)", + "uri": "record(18)", }, Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(13)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(14)@jpeg", "description": "hi im bob label_me", "did": "user(3)", "displayName": "bobby", @@ -731,26 +785,26 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(14)", - "following": "record(15)", + "followedBy": "record(15)", + "following": "record(16)", "muted": false, }, }, - "cid": "cids(17)", + "cid": "cids(18)", "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [], "reason": "like", - "reasonSubject": "record(2)", + "reasonSubject": "record(4)", "record": Object { "$type": "app.bsky.feed.like", "createdAt": "1970-01-01T00:00:00.000Z", "subject": Object { - "cid": "cids(1)", - "uri": "record(2)", + "cid": "cids(3)", + "uri": "record(4)", }, }, - "uri": "record(18)", + "uri": "record(19)", }, ] `; @@ -785,13 +839,30 @@ Array [ }, Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(2)/cids(3)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(3)@jpeg", "description": "its me!", "did": "user(1)", "displayName": "ali", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(4)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(4)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -812,29 +883,46 @@ Array [ }, Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(2)/cids(3)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(3)@jpeg", "description": "its me!", "did": "user(1)", "displayName": "ali", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(4)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(4)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", "muted": false, }, }, - "cid": "cids(4)", + "cid": "cids(5)", "indexedAt": "1970-01-01T00:00:00.000Z", "isRead": false, "labels": Array [ Object { - "cid": "cids(4)", + "cid": "cids(5)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(3)", + "uri": "record(4)", "val": "test-label", }, ], @@ -852,7 +940,7 @@ Array [ }, "text": "yoohoo label_me", }, - "uri": "record(3)", + "uri": "record(4)", }, ] `; diff --git a/packages/bsky/tests/views/__snapshots__/posts.test.ts.snap b/packages/bsky/tests/views/__snapshots__/posts.test.ts.snap index 3df6a281cfe..df8a4cdf826 100644 --- a/packages/bsky/tests/views/__snapshots__/posts.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/posts.test.ts.snap @@ -4,11 +4,28 @@ exports[`pds posts views fetches posts 1`] = ` Array [ Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -16,11 +33,28 @@ Array [ }, "cid": "cids(0)", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(0)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(0)", + "val": "self-label", + }, + ], "likeCount": 0, "record": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label", + }, + ], + }, "text": "hey there", }, "replyCount": 0, @@ -30,17 +64,34 @@ Array [ }, Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -51,24 +102,24 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(1)", + "uri": "record(2)", "viewer": Object {}, }, Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(4)", - "following": "record(3)", + "followedBy": "record(5)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(4)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -83,7 +134,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(2)", + "uri": "record(3)", "viewer": Object {}, }, Object { @@ -93,12 +144,12 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(7)", - "following": "record(6)", + "followedBy": "record(8)", + "following": "record(7)", "muted": false, }, }, - "cid": "cids(4)", + "cid": "cids(5)", "embed": Object { "$type": "app.bsky.embed.recordWithMedia#view", "media": Object { @@ -106,13 +157,13 @@ Array [ "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(6)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(6)@jpeg", }, Object { "alt": "tests/image/fixtures/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(6)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(6)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(7)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(7)@jpeg", }, ], }, @@ -120,23 +171,23 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(4)", - "following": "record(3)", + "followedBy": "record(5)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(4)", "embeds": Array [], "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(2)", + "uri": "record(3)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -166,7 +217,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(5)", + "$link": "cids(6)", }, "size": 4114, }, @@ -177,7 +228,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(6)", + "$link": "cids(7)", }, "size": 12736, }, @@ -186,8 +237,8 @@ Array [ }, "record": Object { "record": Object { - "cid": "cids(3)", - "uri": "record(2)", + "cid": "cids(4)", + "uri": "record(3)", }, }, }, @@ -195,9 +246,9 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(5)", + "uri": "record(6)", "viewer": Object { - "like": "record(8)", + "like": "record(9)", }, }, Object { @@ -207,11 +258,11 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(10)", + "following": "record(11)", "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(8)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { @@ -222,12 +273,12 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(7)", - "following": "record(6)", + "followedBy": "record(8)", + "following": "record(7)", "muted": false, }, }, - "cid": "cids(4)", + "cid": "cids(5)", "embeds": Array [ Object { "$type": "app.bsky.embed.recordWithMedia#view", @@ -236,13 +287,13 @@ Array [ "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(6)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(6)@jpeg", }, Object { "alt": "tests/image/fixtures/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(6)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(6)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(7)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(7)@jpeg", }, ], }, @@ -250,22 +301,22 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(4)", - "following": "record(3)", + "followedBy": "record(5)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(4)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(2)", + "uri": "record(3)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -281,7 +332,7 @@ Array [ ], "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(5)", + "uri": "record(6)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -296,7 +347,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(5)", + "$link": "cids(6)", }, "size": 4114, }, @@ -307,7 +358,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(6)", + "$link": "cids(7)", }, "size": 12736, }, @@ -316,8 +367,8 @@ Array [ }, "record": Object { "record": Object { - "cid": "cids(3)", - "uri": "record(2)", + "cid": "cids(4)", + "uri": "record(3)", }, }, }, @@ -334,8 +385,8 @@ Array [ "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(4)", - "uri": "record(5)", + "cid": "cids(5)", + "uri": "record(6)", }, }, "facets": Array [ @@ -356,22 +407,39 @@ Array [ }, "replyCount": 0, "repostCount": 1, - "uri": "record(9)", + "uri": "record(10)", "viewer": Object {}, }, Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(9)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -380,19 +448,19 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(9)", - "uri": "record(12)", + "cid": "cids(10)", + "uri": "record(13)", }, "root": Object { - "cid": "cids(2)", - "uri": "record(1)", + "cid": "cids(3)", + "uri": "record(2)", }, }, "text": "thanks bob", }, "replyCount": 0, - "repostCount": 0, - "uri": "record(11)", + "repostCount": 1, + "uri": "record(12)", "viewer": Object {}, }, ] diff --git a/packages/bsky/tests/views/__snapshots__/profile.test.ts.snap b/packages/bsky/tests/views/__snapshots__/profile.test.ts.snap index bf1072ed2e8..62b88f825e0 100644 --- a/packages/bsky/tests/views/__snapshots__/profile.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/profile.test.ts.snap @@ -3,7 +3,7 @@ exports[`pds profile views fetches multiple profiles 1`] = ` Array [ Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "description": "its me!", "did": "user(0)", "displayName": "ali", @@ -11,7 +11,24 @@ Array [ "followsCount": 3, "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], "postsCount": 4, "viewer": Object { "blockedBy": false, @@ -21,7 +38,7 @@ Array [ }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)@jpeg", "description": "hi im bob label_me", "did": "user(2)", "displayName": "bobby", @@ -45,7 +62,7 @@ Array [ "postsCount": 2, "viewer": Object { "blockedBy": false, - "following": "record(2)", + "following": "record(3)", "muted": false, }, }, @@ -58,7 +75,7 @@ Array [ "postsCount": 2, "viewer": Object { "blockedBy": false, - "followedBy": "record(3)", + "followedBy": "record(4)", "muted": false, }, }, @@ -67,7 +84,7 @@ Array [ exports[`pds profile views fetches other's profile, with a follow 1`] = ` Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "description": "its me!", "did": "user(0)", "displayName": "ali", @@ -75,7 +92,24 @@ Object { "followsCount": 3, "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], "postsCount": 4, "viewer": Object { "blockedBy": false, @@ -104,7 +138,7 @@ Object { exports[`pds profile views fetches own profile 1`] = ` Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "description": "its me!", "did": "user(0)", "displayName": "ali", @@ -112,7 +146,24 @@ Object { "followsCount": 3, "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(0)", + "val": "self-label-a", + }, + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(0)", + "val": "self-label-b", + }, + ], "postsCount": 4, "viewer": Object { "blockedBy": false, @@ -123,8 +174,8 @@ Object { exports[`pds profile views presents avatars & banners 1`] = ` Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", - "banner": "https://bsky.public.url/image/sig()/rs:fill:3000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", + "banner": "https://bsky.public.url/img/banner/plain/user(1)/cids(1)@jpeg", "description": "new descript", "did": "user(0)", "displayName": "ali", diff --git a/packages/bsky/tests/views/__snapshots__/reposts.test.ts.snap b/packages/bsky/tests/views/__snapshots__/reposts.test.ts.snap index cf5f9910211..e927d73dfac 100644 --- a/packages/bsky/tests/views/__snapshots__/reposts.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/reposts.test.ts.snap @@ -33,7 +33,7 @@ Array [ }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(0)@jpeg", "description": "hi im bob label_me", "did": "user(3)", "displayName": "bobby", diff --git a/packages/bsky/tests/views/__snapshots__/thread.test.ts.snap b/packages/bsky/tests/views/__snapshots__/thread.test.ts.snap index 846f19ec0b7..5886ed56019 100644 --- a/packages/bsky/tests/views/__snapshots__/thread.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/thread.test.ts.snap @@ -9,11 +9,28 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -21,7 +38,7 @@ Object { "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -32,16 +49,16 @@ Object { }, "replyCount": 2, "repostCount": 1, - "uri": "record(3)", + "uri": "record(4)", "viewer": Object { - "like": "record(6)", + "like": "record(7)", }, }, "replies": Array [], }, "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "bobby", "handle": "bob.test", @@ -51,33 +68,33 @@ Object { "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(4)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(3)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(3)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(5)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(3)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(4)", + "uri": "record(5)", "val": "test-label", }, Object { - "cid": "cids(3)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(4)", + "uri": "record(5)", "val": "test-label-2", }, ], @@ -94,7 +111,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(5)", }, "size": 4114, }, @@ -103,30 +120,47 @@ Object { }, "reply": Object { "parent": Object { - "cid": "cids(2)", - "uri": "record(3)", + "cid": "cids(3)", + "uri": "record(4)", }, "root": Object { - "cid": "cids(2)", - "uri": "record(3)", + "cid": "cids(3)", + "uri": "record(4)", }, }, "text": "hear that label_me label_me_2", }, "replyCount": 1, "repostCount": 0, - "uri": "record(4)", + "uri": "record(5)", "viewer": Object {}, }, "replies": Array [], }, "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -143,21 +177,21 @@ Object { "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(3)", - "uri": "record(4)", + "cid": "cids(4)", + "uri": "record(5)", }, "root": Object { - "cid": "cids(2)", - "uri": "record(3)", + "cid": "cids(3)", + "uri": "record(4)", }, }, "text": "thanks bob", }, "replyCount": 0, - "repostCount": 1, + "repostCount": 2, "uri": "record(0)", "viewer": Object { - "repost": "record(5)", + "repost": "record(6)", }, }, "replies": Array [], @@ -169,11 +203,28 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -194,7 +245,7 @@ Object { "repostCount": 1, "uri": "record(0)", "viewer": Object { - "like": "record(3)", + "like": "record(4)", }, }, "replies": Array [ @@ -207,11 +258,11 @@ Object { "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(5)", + "following": "record(6)", "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -232,7 +283,7 @@ Object { }, "replyCount": 0, "repostCount": 0, - "uri": "record(4)", + "uri": "record(5)", "viewer": Object {}, }, "replies": Array [], @@ -241,7 +292,7 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", @@ -251,33 +302,33 @@ Object { "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(4)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(4)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(4)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(5)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(3)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(6)", + "uri": "record(7)", "val": "test-label", }, Object { - "cid": "cids(3)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(6)", + "uri": "record(7)", "val": "test-label-2", }, ], @@ -294,7 +345,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(5)", }, "size": 4114, }, @@ -315,7 +366,7 @@ Object { }, "replyCount": 1, "repostCount": 0, - "uri": "record(6)", + "uri": "record(7)", "viewer": Object {}, }, "replies": Array [ @@ -323,11 +374,28 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -335,7 +403,7 @@ Object { "muted": false, }, }, - "cid": "cids(5)", + "cid": "cids(6)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -344,8 +412,8 @@ Object { "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(3)", - "uri": "record(6)", + "cid": "cids(4)", + "uri": "record(7)", }, "root": Object { "cid": "cids(0)", @@ -355,10 +423,10 @@ Object { "text": "thanks bob", }, "replyCount": 0, - "repostCount": 1, - "uri": "record(7)", + "repostCount": 2, + "uri": "record(8)", "viewer": Object { - "repost": "record(8)", + "repost": "record(9)", }, }, "replies": Array [], @@ -374,11 +442,28 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -399,7 +484,7 @@ Object { "repostCount": 1, "uri": "record(0)", "viewer": Object { - "like": "record(3)", + "like": "record(4)", }, }, "replies": Array [ @@ -412,11 +497,11 @@ Object { "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(5)", + "following": "record(6)", "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -437,7 +522,7 @@ Object { }, "replyCount": 0, "repostCount": 0, - "uri": "record(4)", + "uri": "record(5)", "viewer": Object {}, }, }, @@ -445,7 +530,7 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", @@ -455,33 +540,33 @@ Object { "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(4)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(4)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(4)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(5)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(3)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(6)", + "uri": "record(7)", "val": "test-label", }, Object { - "cid": "cids(3)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(6)", + "uri": "record(7)", "val": "test-label-2", }, ], @@ -498,7 +583,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(5)", }, "size": 4114, }, @@ -519,7 +604,7 @@ Object { }, "replyCount": 1, "repostCount": 0, - "uri": "record(6)", + "uri": "record(7)", "viewer": Object {}, }, }, @@ -532,11 +617,28 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -563,7 +665,7 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "bobby", "handle": "bob.test", @@ -573,7 +675,7 @@ Object { "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -594,7 +696,7 @@ Object { }, "replyCount": 1, "repostCount": 0, - "uri": "record(3)", + "uri": "record(4)", "viewer": Object {}, }, "replies": Array [ @@ -602,11 +704,28 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -614,7 +733,7 @@ Object { "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(4)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -623,8 +742,8 @@ Object { "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(2)", - "uri": "record(3)", + "cid": "cids(3)", + "uri": "record(4)", }, "root": Object { "cid": "cids(0)", @@ -635,7 +754,7 @@ Object { }, "replyCount": 0, "repostCount": 0, - "uri": "record(4)", + "uri": "record(5)", "viewer": Object {}, }, "replies": Array [], @@ -651,11 +770,28 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -687,15 +823,32 @@ Object { "parent": Object { "$type": "app.bsky.feed.defs#notFoundPost", "notFound": true, - "uri": "record(4)", + "uri": "record(5)", }, "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -712,12 +865,12 @@ Object { "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(3)", - "uri": "record(4)", + "cid": "cids(4)", + "uri": "record(5)", }, "root": Object { - "cid": "cids(2)", - "uri": "record(3)", + "cid": "cids(3)", + "uri": "record(4)", }, }, "text": "Reply reply", @@ -737,15 +890,32 @@ Object { "parent": Object { "$type": "app.bsky.feed.defs#notFoundPost", "notFound": true, - "uri": "record(4)", + "uri": "record(5)", }, "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -762,21 +932,21 @@ Object { "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(3)", - "uri": "record(4)", + "cid": "cids(4)", + "uri": "record(5)", }, "root": Object { - "cid": "cids(2)", - "uri": "record(3)", + "cid": "cids(3)", + "uri": "record(4)", }, }, "text": "thanks bob", }, "replyCount": 0, - "repostCount": 1, + "repostCount": 2, "uri": "record(0)", "viewer": Object { - "repost": "record(5)", + "repost": "record(6)", }, }, "replies": Array [], @@ -789,15 +959,32 @@ Object { "parent": Object { "$type": "app.bsky.feed.defs#notFoundPost", "notFound": true, - "uri": "record(4)", + "uri": "record(5)", }, "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -814,21 +1001,21 @@ Object { "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(3)", - "uri": "record(4)", + "cid": "cids(4)", + "uri": "record(5)", }, "root": Object { - "cid": "cids(2)", - "uri": "record(3)", + "cid": "cids(3)", + "uri": "record(4)", }, }, "text": "thanks bob", }, "replyCount": 0, - "repostCount": 1, + "repostCount": 2, "uri": "record(0)", "viewer": Object { - "repost": "record(5)", + "repost": "record(6)", }, }, "replies": Array [], @@ -840,11 +1027,28 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -865,7 +1069,7 @@ Object { "repostCount": 1, "uri": "record(0)", "viewer": Object { - "like": "record(3)", + "like": "record(4)", }, }, "replies": Array [ @@ -873,7 +1077,7 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "bobby", "handle": "bob.test", @@ -883,33 +1087,33 @@ Object { "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(3)/cids(3)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(3)/cids(3)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(4)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(4)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(2)", + "cid": "cids(3)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(4)", + "uri": "record(5)", "val": "test-label", }, Object { - "cid": "cids(2)", + "cid": "cids(3)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(4)", + "uri": "record(5)", "val": "test-label-2", }, ], @@ -926,7 +1130,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(3)", + "$link": "cids(4)", }, "size": 4114, }, @@ -947,7 +1151,7 @@ Object { }, "replyCount": 1, "repostCount": 0, - "uri": "record(4)", + "uri": "record(5)", "viewer": Object {}, }, "replies": Array [ @@ -955,11 +1159,28 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -967,7 +1188,7 @@ Object { "muted": false, }, }, - "cid": "cids(4)", + "cid": "cids(5)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -976,8 +1197,8 @@ Object { "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(2)", - "uri": "record(4)", + "cid": "cids(3)", + "uri": "record(5)", }, "root": Object { "cid": "cids(0)", @@ -987,10 +1208,10 @@ Object { "text": "thanks bob", }, "replyCount": 0, - "repostCount": 1, - "uri": "record(5)", + "repostCount": 2, + "uri": "record(6)", "viewer": Object { - "repost": "record(6)", + "repost": "record(7)", }, }, "replies": Array [], @@ -1006,11 +1227,28 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -1031,7 +1269,7 @@ Object { "repostCount": 1, "uri": "record(0)", "viewer": Object { - "like": "record(3)", + "like": "record(4)", }, }, "replies": Array [ @@ -1039,7 +1277,7 @@ Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "bobby", "handle": "bob.test", @@ -1049,33 +1287,33 @@ Object { "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(3)/cids(3)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(3)/cids(3)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(4)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(4)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(2)", + "cid": "cids(3)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(4)", + "uri": "record(5)", "val": "test-label", }, Object { - "cid": "cids(2)", + "cid": "cids(3)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(4)", + "uri": "record(5)", "val": "test-label-2", }, ], @@ -1092,7 +1330,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(3)", + "$link": "cids(4)", }, "size": 4114, }, @@ -1113,7 +1351,7 @@ Object { }, "replyCount": 1, "repostCount": 0, - "uri": "record(4)", + "uri": "record(5)", "viewer": Object {}, }, "replies": Array [], diff --git a/packages/bsky/tests/views/__snapshots__/timeline.test.ts.snap b/packages/bsky/tests/views/__snapshots__/timeline.test.ts.snap index 55fd71ed34c..fe9b243c10a 100644 --- a/packages/bsky/tests/views/__snapshots__/timeline.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/timeline.test.ts.snap @@ -5,11 +5,28 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -18,6 +35,75 @@ Array [ "cid": "cids(0)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], + "likeCount": 0, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000Z", + "reply": Object { + "parent": Object { + "cid": "cids(4)", + "uri": "record(3)", + }, + "root": Object { + "cid": "cids(3)", + "uri": "record(2)", + }, + }, + "text": "thanks bob", + }, + "replyCount": 0, + "repostCount": 1, + "uri": "record(0)", + "viewer": Object {}, + }, + "reason": Object { + "$type": "app.bsky.feed.defs#reasonRepost", + "by": Object { + "did": "user(2)", + "handle": "dan.test", + "labels": Array [], + "viewer": Object { + "blockedBy": false, + "following": "record(4)", + "muted": false, + }, + }, + "indexedAt": "1970-01-01T00:00:00.000Z", + }, + }, + Object { + "post": Object { + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "muted": false, + }, + }, + "cid": "cids(3)", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [], "likeCount": 3, "record": Object { "$type": "app.bsky.feed.post", @@ -26,7 +112,7 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(2)", "viewer": Object {}, }, "reason": Object { @@ -37,7 +123,7 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(1)", + "following": "record(4)", "muted": false, }, }, @@ -47,17 +133,34 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(0)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -66,36 +169,53 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(3)", + "cid": "cids(4)", "uri": "record(3)", }, "root": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(2)", }, }, "text": "thanks bob", }, "replyCount": 0, - "repostCount": 0, - "uri": "record(2)", + "repostCount": 1, + "uri": "record(0)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(4)", + "cid": "cids(5)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { @@ -106,31 +226,32 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(1)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(5)", + "cid": "cids(6)", "embeds": Array [ Object { "$type": "app.bsky.embed.record#view", "record": Object { "$type": "app.bsky.embed.record#viewNotFound", - "uri": "record(6)", + "notFound": true, + "uri": "record(7)", }, }, ], "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(5)", + "uri": "record(6)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(6)", - "uri": "record(6)", + "cid": "cids(7)", + "uri": "record(7)", }, }, "facets": Array [ @@ -154,11 +275,11 @@ Array [ "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(4)", + "cid": "cids(5)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(4)", + "uri": "record(5)", "val": "test-label", }, ], @@ -169,32 +290,49 @@ Array [ "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(5)", - "uri": "record(5)", + "cid": "cids(6)", + "uri": "record(6)", }, }, "text": "yoohoo label_me", }, "replyCount": 0, "repostCount": 0, - "uri": "record(4)", + "uri": "record(5)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -205,7 +343,7 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(2)", "viewer": Object {}, }, }, @@ -217,16 +355,17 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(1)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(5)", + "cid": "cids(6)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { "$type": "app.bsky.embed.record#viewNotFound", - "uri": "record(6)", + "notFound": true, + "uri": "record(7)", }, }, "indexedAt": "1970-01-01T00:00:00.000Z", @@ -238,8 +377,8 @@ Array [ "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(6)", - "uri": "record(6)", + "cid": "cids(7)", + "uri": "record(7)", }, }, "facets": Array [ @@ -260,7 +399,7 @@ Array [ }, "replyCount": 0, "repostCount": 1, - "uri": "record(5)", + "uri": "record(6)", "viewer": Object {}, }, }, @@ -272,11 +411,11 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(1)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -287,35 +426,69 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(7)", + "uri": "record(8)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(9)", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(9)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(9)", + "val": "self-label", + }, + ], "likeCount": 0, "record": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label", + }, + ], + }, "text": "hey there", }, "replyCount": 0, "repostCount": 0, - "uri": "record(8)", + "uri": "record(9)", "viewer": Object {}, }, }, @@ -327,11 +500,28 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -340,13 +530,23 @@ Array [ "cid": "cids(0)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "likeCount": 3, + "likeCount": 0, "record": Object { "$type": "app.bsky.feed.post", - "createdAt": "1970-01-01T00:00:00.000000Z", - "text": "again", + "createdAt": "1970-01-01T00:00:00.000Z", + "reply": Object { + "parent": Object { + "cid": "cids(4)", + "uri": "record(3)", + }, + "root": Object { + "cid": "cids(3)", + "uri": "record(2)", + }, + }, + "text": "thanks bob", }, - "replyCount": 2, + "replyCount": 0, "repostCount": 1, "uri": "record(0)", "viewer": Object {}, @@ -359,7 +559,7 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(1)", + "following": "record(4)", "muted": false, }, }, @@ -369,38 +569,114 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "likeCount": 0, + "likeCount": 3, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000000Z", + "text": "again", + }, + "replyCount": 2, + "repostCount": 1, + "uri": "record(2)", + "viewer": Object {}, + }, + "reason": Object { + "$type": "app.bsky.feed.defs#reasonRepost", + "by": Object { + "did": "user(2)", + "handle": "dan.test", + "labels": Array [], + "viewer": Object { + "blockedBy": false, + "following": "record(4)", + "muted": false, + }, + }, + "indexedAt": "1970-01-01T00:00:00.000Z", + }, + }, + Object { + "post": Object { + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "muted": false, + }, + }, + "cid": "cids(0)", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [], + "likeCount": 0, "record": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(3)", + "cid": "cids(4)", "uri": "record(3)", }, "root": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(2)", }, }, "text": "thanks bob", }, "replyCount": 0, - "repostCount": 0, - "uri": "record(2)", + "repostCount": 1, + "uri": "record(0)", "viewer": Object {}, }, }, @@ -412,12 +688,12 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(6)", - "following": "record(5)", + "followedBy": "record(7)", + "following": "record(6)", "muted": false, }, }, - "cid": "cids(4)", + "cid": "cids(5)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -426,36 +702,53 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(2)", }, "root": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(2)", }, }, "text": "of course", }, "replyCount": 0, "repostCount": 0, - "uri": "record(4)", + "uri": "record(5)", "viewer": Object {}, }, "reply": Object { "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -466,23 +759,40 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(2)", "viewer": Object {}, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -493,7 +803,7 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(2)", "viewer": Object {}, }, }, @@ -501,32 +811,50 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(5)", + "cid": "cids(6)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { "$type": "app.bsky.embed.record#viewNotFound", - "uri": "record(8)", + "notFound": true, + "uri": "record(9)", }, }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(5)", + "cid": "cids(6)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(7)", + "uri": "record(8)", "val": "test-label", }, ], @@ -537,34 +865,34 @@ Array [ "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(6)", - "uri": "record(8)", + "cid": "cids(7)", + "uri": "record(9)", }, }, "text": "yoohoo label_me", }, "replyCount": 0, "repostCount": 0, - "uri": "record(7)", + "uri": "record(8)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(1)@jpeg", "did": "user(4)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(11)", - "following": "record(10)", + "followedBy": "record(12)", + "following": "record(11)", "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -575,24 +903,41 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(9)", + "uri": "record(10)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -603,7 +948,7 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(2)", "viewer": Object {}, }, }, @@ -615,11 +960,11 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(1)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(9)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -630,7 +975,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(12)", + "uri": "record(13)", "viewer": Object {}, }, }, @@ -642,12 +987,12 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(6)", - "following": "record(5)", + "followedBy": "record(7)", + "following": "record(6)", "muted": false, }, }, - "cid": "cids(9)", + "cid": "cids(10)", "embed": Object { "$type": "app.bsky.embed.recordWithMedia#view", "media": Object { @@ -655,13 +1000,13 @@ Array [ "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(10)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(10)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(11)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(11)@jpeg", }, Object { "alt": "tests/image/fixtures/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(11)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(11)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(12)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(12)@jpeg", }, ], }, @@ -669,32 +1014,32 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(1)@jpeg", "did": "user(4)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(11)", - "following": "record(10)", + "followedBy": "record(12)", + "following": "record(11)", "muted": false, }, }, - "cid": "cids(12)", + "cid": "cids(13)", "embeds": Array [], "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(12)", + "cid": "cids(13)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(14)", + "uri": "record(15)", "val": "kind", }, ], - "uri": "record(14)", + "uri": "record(15)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -710,11 +1055,11 @@ Array [ "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(9)", + "cid": "cids(10)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(13)", + "uri": "record(14)", "val": "kind", }, ], @@ -723,8 +1068,280 @@ Array [ "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", "embed": Object { - "$type": "app.bsky.embed.recordWithMedia", - "media": Object { + "$type": "app.bsky.embed.recordWithMedia", + "media": Object { + "$type": "app.bsky.embed.images", + "images": Array [ + Object { + "alt": "tests/image/fixtures/key-landscape-small.jpg", + "image": Object { + "$type": "blob", + "mimeType": "image/jpeg", + "ref": Object { + "$link": "cids(11)", + }, + "size": 4114, + }, + }, + Object { + "alt": "tests/image/fixtures/key-alt.jpg", + "image": Object { + "$type": "blob", + "mimeType": "image/jpeg", + "ref": Object { + "$link": "cids(12)", + }, + "size": 12736, + }, + }, + ], + }, + "record": Object { + "record": Object { + "cid": "cids(13)", + "uri": "record(15)", + }, + }, + }, + "text": "hi im carol", + }, + "replyCount": 0, + "repostCount": 0, + "uri": "record(14)", + "viewer": Object { + "like": "record(16)", + }, + }, + }, + Object { + "post": Object { + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(1)@jpeg", + "did": "user(4)", + "displayName": "bobby", + "handle": "bob.test", + "labels": Array [], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(12)", + "following": "record(11)", + "muted": false, + }, + }, + "cid": "cids(13)", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [ + Object { + "cid": "cids(13)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "record(15)", + "val": "kind", + }, + ], + "likeCount": 0, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000Z", + "langs": Array [ + "en-US", + "i-klingon", + ], + "text": "bob back at it again!", + }, + "replyCount": 0, + "repostCount": 0, + "uri": "record(15)", + "viewer": Object {}, + }, + }, + Object { + "post": Object { + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "muted": false, + }, + }, + "cid": "cids(14)", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [ + Object { + "cid": "cids(14)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(17)", + "val": "self-label", + }, + ], + "likeCount": 0, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000Z", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label", + }, + ], + }, + "text": "hey there", + }, + "replyCount": 0, + "repostCount": 0, + "uri": "record(17)", + "viewer": Object {}, + }, + }, +] +`; + +exports[`timeline views fetches authenticated user's home feed w/ reverse-chronological algorithm 1`] = ` +Array [ + Object { + "post": Object { + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "muted": false, + }, + }, + "cid": "cids(0)", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [], + "likeCount": 0, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000Z", + "reply": Object { + "parent": Object { + "cid": "cids(4)", + "uri": "record(3)", + }, + "root": Object { + "cid": "cids(3)", + "uri": "record(2)", + }, + }, + "text": "thanks bob", + }, + "replyCount": 0, + "repostCount": 1, + "uri": "record(0)", + "viewer": Object {}, + }, + "reason": Object { + "$type": "app.bsky.feed.defs#reasonRepost", + "by": Object { + "did": "user(2)", + "handle": "dan.test", + "labels": Array [], + "viewer": Object { + "blockedBy": false, + "following": "record(4)", + "muted": false, + }, + }, + "indexedAt": "1970-01-01T00:00:00.000Z", + }, + "reply": Object { + "parent": Object { + "$type": "app.bsky.feed.defs#postView", + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", + "displayName": "bobby", + "handle": "bob.test", + "labels": Array [], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(6)", + "following": "record(5)", + "muted": false, + }, + }, + "cid": "cids(4)", + "embed": Object { + "$type": "app.bsky.embed.images#view", + "images": Array [ + Object { + "alt": "tests/image/fixtures/key-landscape-small.jpg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(5)@jpeg", + }, + ], + }, + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [ + Object { + "cid": "cids(4)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "record(3)", + "val": "test-label", + }, + Object { + "cid": "cids(4)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "record(3)", + "val": "test-label-2", + }, + ], + "likeCount": 0, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000Z", + "embed": Object { "$type": "app.bsky.embed.images", "images": Array [ Object { @@ -733,131 +1350,107 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(10)", + "$link": "cids(5)", }, "size": 4114, }, }, - Object { - "alt": "tests/image/fixtures/key-alt.jpg", - "image": Object { - "$type": "blob", - "mimeType": "image/jpeg", - "ref": Object { - "$link": "cids(11)", - }, - "size": 12736, - }, - }, ], }, - "record": Object { - "record": Object { - "cid": "cids(12)", - "uri": "record(14)", + "reply": Object { + "parent": Object { + "cid": "cids(3)", + "uri": "record(2)", + }, + "root": Object { + "cid": "cids(3)", + "uri": "record(2)", }, }, + "text": "hear that label_me label_me_2", }, - "text": "hi im carol", - }, - "replyCount": 0, - "repostCount": 0, - "uri": "record(13)", - "viewer": Object { - "like": "record(15)", - }, - }, - }, - Object { - "post": Object { - "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", - "displayName": "bobby", - "handle": "bob.test", - "labels": Array [], - "viewer": Object { - "blockedBy": false, - "followedBy": "record(11)", - "following": "record(10)", - "muted": false, - }, + "replyCount": 1, + "repostCount": 0, + "uri": "record(3)", + "viewer": Object {}, }, - "cid": "cids(12)", - "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [ - Object { - "cid": "cids(12)", - "cts": "1970-01-01T00:00:00.000Z", - "neg": false, - "src": "did:example:labeler", - "uri": "record(14)", - "val": "kind", + "root": Object { + "$type": "app.bsky.feed.defs#postView", + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "muted": false, + }, }, - ], - "likeCount": 0, - "record": Object { - "$type": "app.bsky.feed.post", - "createdAt": "1970-01-01T00:00:00.000Z", - "langs": Array [ - "en-US", - "i-klingon", - ], - "text": "bob back at it again!", - }, - "replyCount": 0, - "repostCount": 0, - "uri": "record(14)", - "viewer": Object {}, - }, - }, - Object { - "post": Object { - "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", - "did": "user(0)", - "displayName": "ali", - "handle": "alice.test", + "cid": "cids(3)", + "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "viewer": Object { - "blockedBy": false, - "muted": false, + "likeCount": 3, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000000Z", + "text": "again", }, + "replyCount": 2, + "repostCount": 1, + "uri": "record(2)", + "viewer": Object {}, }, - "cid": "cids(13)", - "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], - "likeCount": 0, - "record": Object { - "$type": "app.bsky.feed.post", - "createdAt": "1970-01-01T00:00:00.000Z", - "text": "hey there", - }, - "replyCount": 0, - "repostCount": 0, - "uri": "record(16)", - "viewer": Object {}, }, }, -] -`; - -exports[`timeline views fetches authenticated user's home feed w/ reverse-chronological algorithm 1`] = ` -Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -868,7 +1461,7 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(2)", "viewer": Object {}, }, "reason": Object { @@ -879,7 +1472,7 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(1)", + "following": "record(4)", "muted": false, }, }, @@ -894,27 +1487,27 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(1)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(6)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(3)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(5)", - "following": "record(4)", + "followedBy": "record(10)", + "following": "record(9)", "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(7)", "embeds": Array [ Object { "$type": "app.bsky.embed.recordWithMedia#view", @@ -923,13 +1516,13 @@ Array [ "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(5)@jpeg", }, Object { "alt": "tests/image/fixtures/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(8)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(8)@jpeg", }, ], }, @@ -937,31 +1530,31 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(8)", - "following": "record(7)", + "followedBy": "record(6)", + "following": "record(5)", "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(9)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(6)", + "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(6)", + "uri": "record(11)", "val": "kind", }, ], - "uri": "record(6)", + "uri": "record(11)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -978,15 +1571,15 @@ Array [ "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(3)", + "cid": "cids(7)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(3)", + "uri": "record(8)", "val": "kind", }, ], - "uri": "record(3)", + "uri": "record(8)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -1001,7 +1594,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(5)", }, "size": 4114, }, @@ -1012,7 +1605,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(5)", + "$link": "cids(8)", }, "size": 12736, }, @@ -1021,8 +1614,8 @@ Array [ }, "record": Object { "record": Object { - "cid": "cids(6)", - "uri": "record(6)", + "cid": "cids(9)", + "uri": "record(11)", }, }, }, @@ -1039,8 +1632,8 @@ Array [ "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(3)", - "uri": "record(3)", + "cid": "cids(7)", + "uri": "record(8)", }, }, "facets": Array [ @@ -1061,19 +1654,19 @@ Array [ }, "replyCount": 0, "repostCount": 1, - "uri": "record(2)", + "uri": "record(7)", "viewer": Object {}, }, "reason": Object { "$type": "app.bsky.feed.defs#reasonRepost", "by": Object { - "did": "user(3)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(5)", - "following": "record(4)", + "followedBy": "record(10)", + "following": "record(9)", "muted": false, }, }, @@ -1083,17 +1676,34 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(0)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -1102,64 +1712,64 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(8)", - "uri": "record(10)", + "cid": "cids(4)", + "uri": "record(3)", }, "root": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(2)", }, }, "text": "thanks bob", }, "replyCount": 0, - "repostCount": 0, - "uri": "record(9)", + "repostCount": 1, + "uri": "record(0)", "viewer": Object {}, }, "reply": Object { "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(8)", - "following": "record(7)", + "followedBy": "record(6)", + "following": "record(5)", "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(4)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(5)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(8)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(10)", + "uri": "record(3)", "val": "test-label", }, Object { - "cid": "cids(8)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(10)", + "uri": "record(3)", "val": "test-label-2", }, ], @@ -1176,7 +1786,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(5)", }, "size": 4114, }, @@ -1185,35 +1795,52 @@ Array [ }, "reply": Object { "parent": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(2)", }, "root": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(2)", }, }, "text": "hear that label_me label_me_2", }, "replyCount": 1, "repostCount": 0, - "uri": "record(10)", + "uri": "record(3)", "viewer": Object {}, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -1224,7 +1851,7 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(2)", "viewer": Object {}, }, }, @@ -1232,17 +1859,17 @@ Array [ Object { "post": Object { "author": Object { - "did": "user(3)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(5)", - "following": "record(4)", + "followedBy": "record(10)", + "following": "record(9)", "muted": false, }, }, - "cid": "cids(9)", + "cid": "cids(10)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -1251,36 +1878,53 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(2)", }, "root": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(2)", }, }, "text": "of course", }, "replyCount": 0, "repostCount": 0, - "uri": "record(11)", + "uri": "record(12)", "viewer": Object {}, }, "reply": Object { "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -1291,23 +1935,40 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(2)", "viewer": Object {}, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -1318,7 +1979,7 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(2)", "viewer": Object {}, }, }, @@ -1326,45 +1987,45 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(8)", - "following": "record(7)", + "followedBy": "record(6)", + "following": "record(5)", "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(4)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(5)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(8)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(10)", + "uri": "record(3)", "val": "test-label", }, Object { - "cid": "cids(8)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(10)", + "uri": "record(3)", "val": "test-label-2", }, ], @@ -1381,7 +2042,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(5)", }, "size": 4114, }, @@ -1390,36 +2051,53 @@ Array [ }, "reply": Object { "parent": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(2)", }, "root": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(2)", }, }, "text": "hear that label_me label_me_2", }, "replyCount": 1, "repostCount": 0, - "uri": "record(10)", + "uri": "record(3)", "viewer": Object {}, }, "reply": Object { "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -1430,23 +2108,40 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(2)", "viewer": Object {}, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -1457,7 +2152,7 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(2)", "viewer": Object {}, }, }, @@ -1465,17 +2160,34 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(10)", + "cid": "cids(11)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { @@ -1486,40 +2198,40 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(1)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(6)", "embeds": Array [ Object { "$type": "app.bsky.embed.record#view", "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(3)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(5)", - "following": "record(4)", + "followedBy": "record(10)", + "following": "record(9)", "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(7)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(3)", + "cid": "cids(7)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(3)", + "uri": "record(8)", "val": "kind", }, ], - "uri": "record(3)", + "uri": "record(8)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -1534,7 +2246,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(5)", }, "size": 4114, }, @@ -1545,7 +2257,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(5)", + "$link": "cids(8)", }, "size": 12736, }, @@ -1554,8 +2266,8 @@ Array [ }, "record": Object { "record": Object { - "cid": "cids(6)", - "uri": "record(6)", + "cid": "cids(9)", + "uri": "record(11)", }, }, }, @@ -1566,15 +2278,15 @@ Array [ ], "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(2)", + "uri": "record(7)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(3)", - "uri": "record(3)", + "cid": "cids(7)", + "uri": "record(8)", }, }, "facets": Array [ @@ -1598,11 +2310,11 @@ Array [ "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(10)", + "cid": "cids(11)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(12)", + "uri": "record(13)", "val": "test-label", }, ], @@ -1613,34 +2325,34 @@ Array [ "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(2)", - "uri": "record(2)", + "cid": "cids(6)", + "uri": "record(7)", }, }, "text": "yoohoo label_me", }, "replyCount": 0, "repostCount": 0, - "uri": "record(12)", + "uri": "record(13)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(8)", - "following": "record(7)", + "followedBy": "record(6)", + "following": "record(5)", "muted": false, }, }, - "cid": "cids(11)", + "cid": "cids(12)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -1651,24 +2363,41 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(13)", + "uri": "record(14)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -1679,7 +2408,7 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(2)", "viewer": Object {}, }, }, @@ -1691,27 +2420,27 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(1)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(6)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(3)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(5)", - "following": "record(4)", + "followedBy": "record(10)", + "following": "record(9)", "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(7)", "embeds": Array [ Object { "$type": "app.bsky.embed.recordWithMedia#view", @@ -1720,13 +2449,13 @@ Array [ "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(5)@jpeg", }, Object { "alt": "tests/image/fixtures/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(8)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(8)@jpeg", }, ], }, @@ -1734,31 +2463,31 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(8)", - "following": "record(7)", + "followedBy": "record(6)", + "following": "record(5)", "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(9)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(6)", + "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(6)", + "uri": "record(11)", "val": "kind", }, ], - "uri": "record(6)", + "uri": "record(11)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -1775,15 +2504,15 @@ Array [ "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(3)", + "cid": "cids(7)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(3)", + "uri": "record(8)", "val": "kind", }, ], - "uri": "record(3)", + "uri": "record(8)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -1798,7 +2527,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(5)", }, "size": 4114, }, @@ -1809,7 +2538,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(5)", + "$link": "cids(8)", }, "size": 12736, }, @@ -1818,8 +2547,8 @@ Array [ }, "record": Object { "record": Object { - "cid": "cids(6)", - "uri": "record(6)", + "cid": "cids(9)", + "uri": "record(11)", }, }, }, @@ -1836,8 +2565,8 @@ Array [ "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(3)", - "uri": "record(3)", + "cid": "cids(7)", + "uri": "record(8)", }, }, "facets": Array [ @@ -1858,7 +2587,7 @@ Array [ }, "replyCount": 0, "repostCount": 1, - "uri": "record(2)", + "uri": "record(7)", "viewer": Object {}, }, }, @@ -1870,11 +2599,11 @@ Array [ "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(1)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(12)", + "cid": "cids(13)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -1885,24 +2614,24 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(14)", + "uri": "record(15)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "did": "user(3)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(5)", - "following": "record(4)", + "followedBy": "record(10)", + "following": "record(9)", "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(7)", "embed": Object { "$type": "app.bsky.embed.recordWithMedia#view", "media": Object { @@ -1910,13 +2639,13 @@ Array [ "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(5)@jpeg", }, Object { "alt": "tests/image/fixtures/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(8)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(8)@jpeg", }, ], }, @@ -1924,32 +2653,32 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(8)", - "following": "record(7)", + "followedBy": "record(6)", + "following": "record(5)", "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(9)", "embeds": Array [], "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(6)", + "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(6)", + "uri": "record(11)", "val": "kind", }, ], - "uri": "record(6)", + "uri": "record(11)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -1965,11 +2694,11 @@ Array [ "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(3)", + "cid": "cids(7)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(3)", + "uri": "record(8)", "val": "kind", }, ], @@ -1988,7 +2717,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(5)", }, "size": 4114, }, @@ -1999,7 +2728,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(5)", + "$link": "cids(8)", }, "size": 12736, }, @@ -2008,8 +2737,8 @@ Array [ }, "record": Object { "record": Object { - "cid": "cids(6)", - "uri": "record(6)", + "cid": "cids(9)", + "uri": "record(11)", }, }, }, @@ -2017,36 +2746,36 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(3)", + "uri": "record(8)", "viewer": Object { - "like": "record(15)", + "like": "record(16)", }, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(8)", - "following": "record(7)", + "followedBy": "record(6)", + "following": "record(5)", "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(9)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(6)", + "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(6)", + "uri": "record(11)", "val": "kind", }, ], @@ -2062,35 +2791,69 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(6)", + "uri": "record(11)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(13)", + "cid": "cids(14)", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(14)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(17)", + "val": "self-label", + }, + ], "likeCount": 0, "record": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label", + }, + ], + }, "text": "hey there", }, "replyCount": 0, "repostCount": 0, - "uri": "record(16)", + "uri": "record(17)", "viewer": Object {}, }, }, @@ -2135,13 +2898,13 @@ Array [ "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(2)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(2)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(2)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(2)@jpeg", }, Object { "alt": "tests/image/fixtures/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(3)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(3)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(3)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(3)@jpeg", }, ], }, @@ -2149,7 +2912,7 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(5)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", @@ -2292,11 +3055,28 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-a", + }, + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(7)", @@ -2313,18 +3093,18 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(8)", - "uri": "record(9)", + "cid": "cids(9)", + "uri": "record(10)", }, "root": Object { - "cid": "cids(7)", - "uri": "record(8)", + "cid": "cids(8)", + "uri": "record(9)", }, }, "text": "thanks bob", }, "replyCount": 0, - "repostCount": 0, + "repostCount": 1, "uri": "record(5)", "viewer": Object {}, }, @@ -2332,7 +3112,7 @@ Array [ "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(5)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", @@ -2342,33 +3122,33 @@ Array [ "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(9)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(4)/cids(2)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(4)/cids(2)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(2)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(2)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(8)", + "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(9)", + "uri": "record(10)", "val": "test-label", }, Object { - "cid": "cids(8)", + "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(9)", + "uri": "record(10)", "val": "test-label-2", }, ], @@ -2394,29 +3174,46 @@ Array [ }, "reply": Object { "parent": Object { - "cid": "cids(7)", - "uri": "record(8)", + "cid": "cids(8)", + "uri": "record(9)", }, "root": Object { - "cid": "cids(7)", - "uri": "record(8)", + "cid": "cids(8)", + "uri": "record(9)", }, }, "text": "hear that label_me label_me_2", }, "replyCount": 1, "repostCount": 0, - "uri": "record(9)", + "uri": "record(10)", "viewer": Object {}, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-a", + }, + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(7)", @@ -2424,7 +3221,7 @@ Array [ "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -2435,9 +3232,9 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(8)", + "uri": "record(9)", "viewer": Object { - "like": "record(10)", + "like": "record(11)", }, }, }, @@ -2454,7 +3251,7 @@ Array [ "muted": false, }, }, - "cid": "cids(9)", + "cid": "cids(10)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -2463,30 +3260,47 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(7)", - "uri": "record(8)", + "cid": "cids(8)", + "uri": "record(9)", }, "root": Object { - "cid": "cids(7)", - "uri": "record(8)", + "cid": "cids(8)", + "uri": "record(9)", }, }, "text": "of course", }, "replyCount": 0, "repostCount": 0, - "uri": "record(11)", + "uri": "record(12)", "viewer": Object {}, }, "reply": Object { "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-a", + }, + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(7)", @@ -2494,7 +3308,7 @@ Array [ "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -2505,19 +3319,36 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(8)", + "uri": "record(9)", "viewer": Object { - "like": "record(10)", + "like": "record(11)", }, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-a", + }, + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(7)", @@ -2525,7 +3356,7 @@ Array [ "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -2536,9 +3367,9 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(8)", + "uri": "record(9)", "viewer": Object { - "like": "record(10)", + "like": "record(11)", }, }, }, @@ -2546,7 +3377,7 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(5)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", @@ -2556,33 +3387,33 @@ Array [ "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(9)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(4)/cids(2)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(4)/cids(2)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(2)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(2)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(8)", + "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(9)", + "uri": "record(10)", "val": "test-label", }, Object { - "cid": "cids(8)", + "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(9)", + "uri": "record(10)", "val": "test-label-2", }, ], @@ -2608,30 +3439,47 @@ Array [ }, "reply": Object { "parent": Object { - "cid": "cids(7)", - "uri": "record(8)", + "cid": "cids(8)", + "uri": "record(9)", }, "root": Object { - "cid": "cids(7)", - "uri": "record(8)", + "cid": "cids(8)", + "uri": "record(9)", }, }, "text": "hear that label_me label_me_2", }, "replyCount": 1, "repostCount": 0, - "uri": "record(9)", + "uri": "record(10)", "viewer": Object {}, }, "reply": Object { "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-a", + }, + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(7)", @@ -2639,7 +3487,7 @@ Array [ "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -2650,19 +3498,36 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(8)", + "uri": "record(9)", "viewer": Object { - "like": "record(10)", + "like": "record(11)", }, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-a", + }, + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(7)", @@ -2670,7 +3535,7 @@ Array [ "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -2681,9 +3546,9 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(8)", + "uri": "record(9)", "viewer": Object { - "like": "record(10)", + "like": "record(11)", }, }, }, @@ -2691,11 +3556,28 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-a", + }, + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(7)", @@ -2703,7 +3585,7 @@ Array [ "muted": false, }, }, - "cid": "cids(10)", + "cid": "cids(11)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { @@ -2825,11 +3707,11 @@ Array [ "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(10)", + "cid": "cids(11)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(12)", + "uri": "record(13)", "val": "test-label", }, ], @@ -2848,16 +3730,16 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(12)", + "uri": "record(13)", "viewer": Object { - "like": "record(13)", + "like": "record(14)", }, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(5)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", @@ -2867,7 +3749,7 @@ Array [ "muted": false, }, }, - "cid": "cids(11)", + "cid": "cids(12)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -2878,18 +3760,35 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(14)", + "uri": "record(15)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-a", + }, + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(7)", @@ -2897,7 +3796,7 @@ Array [ "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -2908,9 +3807,9 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(8)", + "uri": "record(9)", "viewer": Object { - "like": "record(10)", + "like": "record(11)", }, }, }, @@ -2934,13 +3833,13 @@ Array [ "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(2)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(2)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(2)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(2)@jpeg", }, Object { "alt": "tests/image/fixtures/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(3)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(3)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(3)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(3)@jpeg", }, ], }, @@ -2948,7 +3847,7 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(5)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", @@ -3041,14 +3940,14 @@ Array [ "repostCount": 0, "uri": "record(2)", "viewer": Object { - "like": "record(15)", + "like": "record(16)", }, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(5)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", @@ -3089,11 +3988,28 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-a", + }, + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(7)", @@ -3101,18 +4017,35 @@ Array [ "muted": false, }, }, - "cid": "cids(12)", + "cid": "cids(13)", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(13)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(17)", + "val": "self-label", + }, + ], "likeCount": 0, "record": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label", + }, + ], + }, "text": "hey there", }, "replyCount": 0, "repostCount": 0, - "uri": "record(16)", + "uri": "record(17)", "viewer": Object {}, }, }, @@ -3155,13 +4088,13 @@ Array [ "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(2)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(2)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(2)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(2)@jpeg", }, Object { "alt": "tests/image/fixtures/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(3)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(3)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(3)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(3)@jpeg", }, ], }, @@ -3169,7 +4102,7 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(5)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", @@ -3314,11 +4247,28 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-a", + }, + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(7)", @@ -3335,18 +4285,18 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(8)", - "uri": "record(9)", + "cid": "cids(9)", + "uri": "record(10)", }, "root": Object { - "cid": "cids(7)", - "uri": "record(8)", + "cid": "cids(8)", + "uri": "record(9)", }, }, "text": "thanks bob", }, "replyCount": 0, - "repostCount": 0, + "repostCount": 1, "uri": "record(5)", "viewer": Object {}, }, @@ -3354,7 +4304,7 @@ Array [ "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(5)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", @@ -3365,33 +4315,33 @@ Array [ "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(9)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(4)/cids(2)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(4)/cids(2)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(2)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(2)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(8)", + "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(9)", + "uri": "record(10)", "val": "test-label", }, Object { - "cid": "cids(8)", + "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(9)", + "uri": "record(10)", "val": "test-label-2", }, ], @@ -3417,29 +4367,46 @@ Array [ }, "reply": Object { "parent": Object { - "cid": "cids(7)", - "uri": "record(8)", + "cid": "cids(8)", + "uri": "record(9)", }, "root": Object { - "cid": "cids(7)", - "uri": "record(8)", + "cid": "cids(8)", + "uri": "record(9)", }, }, "text": "hear that label_me label_me_2", }, "replyCount": 1, "repostCount": 0, - "uri": "record(9)", + "uri": "record(10)", "viewer": Object {}, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-a", + }, + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(7)", @@ -3447,7 +4414,7 @@ Array [ "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -3458,9 +4425,9 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(8)", + "uri": "record(9)", "viewer": Object { - "like": "record(10)", + "like": "record(11)", }, }, }, @@ -3476,7 +4443,7 @@ Array [ "muted": false, }, }, - "cid": "cids(9)", + "cid": "cids(10)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -3485,30 +4452,47 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(7)", - "uri": "record(8)", + "cid": "cids(8)", + "uri": "record(9)", }, "root": Object { - "cid": "cids(7)", - "uri": "record(8)", + "cid": "cids(8)", + "uri": "record(9)", }, }, "text": "of course", }, "replyCount": 0, "repostCount": 0, - "uri": "record(11)", + "uri": "record(12)", "viewer": Object {}, }, "reply": Object { "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-a", + }, + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(7)", @@ -3516,7 +4500,7 @@ Array [ "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -3527,19 +4511,36 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(8)", + "uri": "record(9)", "viewer": Object { - "like": "record(10)", + "like": "record(11)", }, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-a", + }, + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(7)", @@ -3547,7 +4548,7 @@ Array [ "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -3558,9 +4559,9 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(8)", + "uri": "record(9)", "viewer": Object { - "like": "record(10)", + "like": "record(11)", }, }, }, @@ -3568,11 +4569,28 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-a", + }, + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(7)", @@ -3580,7 +4598,7 @@ Array [ "muted": false, }, }, - "cid": "cids(10)", + "cid": "cids(11)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { @@ -3700,11 +4718,11 @@ Array [ "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(10)", + "cid": "cids(11)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(12)", + "uri": "record(13)", "val": "test-label", }, ], @@ -3723,20 +4741,37 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(12)", + "uri": "record(13)", "viewer": Object { - "like": "record(13)", + "like": "record(14)", }, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-a", + }, + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(7)", @@ -3744,7 +4779,7 @@ Array [ "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -3755,9 +4790,9 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(8)", + "uri": "record(9)", "viewer": Object { - "like": "record(10)", + "like": "record(11)", }, }, }, @@ -3780,13 +4815,13 @@ Array [ "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(2)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(2)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(2)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(2)@jpeg", }, Object { "alt": "tests/image/fixtures/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(3)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(3)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(3)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(3)@jpeg", }, ], }, @@ -3794,7 +4829,7 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(5)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", @@ -3832,23 +4867,253 @@ Array [ }, }, "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [ - Object { - "cid": "cids(1)", - "cts": "1970-01-01T00:00:00.000Z", - "neg": false, - "src": "did:example:labeler", - "uri": "record(1)", - "val": "kind", + "labels": Array [ + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "record(1)", + "val": "kind", + }, + ], + "likeCount": 2, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000Z", + "embed": Object { + "$type": "app.bsky.embed.recordWithMedia", + "media": Object { + "$type": "app.bsky.embed.images", + "images": Array [ + Object { + "alt": "tests/image/fixtures/key-landscape-small.jpg", + "image": Object { + "$type": "blob", + "mimeType": "image/jpeg", + "ref": Object { + "$link": "cids(2)", + }, + "size": 4114, + }, + }, + Object { + "alt": "tests/image/fixtures/key-alt.jpg", + "image": Object { + "$type": "blob", + "mimeType": "image/jpeg", + "ref": Object { + "$link": "cids(3)", + }, + "size": 12736, + }, + }, + ], + }, + "record": Object { + "record": Object { + "cid": "cids(4)", + "uri": "record(2)", + }, + }, + }, + "text": "hi im carol", + }, + "replyCount": 0, + "repostCount": 0, + "uri": "record(1)", + "viewer": Object {}, + }, + }, + Object { + "post": Object { + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", + "did": "user(1)", + "displayName": "ali", + "handle": "alice.test", + "labels": Array [ + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-a", + }, + Object { + "cid": "cids(7)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(8)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(7)", + "following": "record(6)", + "muted": false, + }, + }, + "cid": "cids(12)", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [ + Object { + "cid": "cids(12)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(15)", + "val": "self-label", + }, + ], + "likeCount": 0, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000Z", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label", + }, + ], + }, + "text": "hey there", + }, + "replyCount": 0, + "repostCount": 0, + "uri": "record(15)", + "viewer": Object {}, + }, + }, +] +`; + +exports[`timeline views fetches authenticated user's home feed w/ reverse-chronological algorithm 4`] = ` +Array [ + Object { + "post": Object { + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(1)", + "muted": false, + }, + }, + "cid": "cids(0)", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [], + "likeCount": 0, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000Z", + "reply": Object { + "parent": Object { + "cid": "cids(4)", + "uri": "record(4)", + }, + "root": Object { + "cid": "cids(3)", + "uri": "record(3)", + }, + }, + "text": "thanks bob", + }, + "replyCount": 0, + "repostCount": 1, + "uri": "record(0)", + "viewer": Object { + "repost": "record(5)", + }, + }, + "reason": Object { + "$type": "app.bsky.feed.defs#reasonRepost", + "by": Object { + "did": "user(2)", + "handle": "dan.test", + "labels": Array [], + "viewer": Object { + "blockedBy": false, + "muted": false, + }, + }, + "indexedAt": "1970-01-01T00:00:00.000Z", + }, + "reply": Object { + "parent": Object { + "$type": "app.bsky.feed.defs#postView", + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", + "displayName": "bobby", + "handle": "bob.test", + "labels": Array [], + "viewer": Object { + "blockedBy": false, + "following": "record(8)", + "muted": false, + }, }, - ], - "likeCount": 2, - "record": Object { - "$type": "app.bsky.feed.post", - "createdAt": "1970-01-01T00:00:00.000Z", + "cid": "cids(4)", "embed": Object { - "$type": "app.bsky.embed.recordWithMedia", - "media": Object { + "$type": "app.bsky.embed.images#view", + "images": Array [ + Object { + "alt": "tests/image/fixtures/key-landscape-small.jpg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(5)@jpeg", + }, + ], + }, + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [ + Object { + "cid": "cids(4)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "record(4)", + "val": "test-label", + }, + Object { + "cid": "cids(4)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "record(4)", + "val": "test-label-2", + }, + ], + "likeCount": 0, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000Z", + "embed": Object { "$type": "app.bsky.embed.images", "images": Array [ Object { @@ -3857,89 +5122,112 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(2)", + "$link": "cids(5)", }, "size": 4114, }, }, - Object { - "alt": "tests/image/fixtures/key-alt.jpg", - "image": Object { - "$type": "blob", - "mimeType": "image/jpeg", - "ref": Object { - "$link": "cids(3)", - }, - "size": 12736, - }, - }, ], }, - "record": Object { - "record": Object { - "cid": "cids(4)", - "uri": "record(2)", + "reply": Object { + "parent": Object { + "cid": "cids(3)", + "uri": "record(3)", + }, + "root": Object { + "cid": "cids(3)", + "uri": "record(3)", }, }, + "text": "hear that label_me label_me_2", }, - "text": "hi im carol", + "replyCount": 1, + "repostCount": 0, + "uri": "record(4)", + "viewer": Object {}, }, - "replyCount": 0, - "repostCount": 0, - "uri": "record(1)", - "viewer": Object {}, - }, - }, - Object { - "post": Object { - "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", - "did": "user(1)", - "displayName": "ali", - "handle": "alice.test", + "root": Object { + "$type": "app.bsky.feed.defs#postView", + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(1)", + "muted": false, + }, + }, + "cid": "cids(3)", + "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], + "likeCount": 3, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000000Z", + "text": "again", + }, + "replyCount": 2, + "repostCount": 1, + "uri": "record(3)", "viewer": Object { - "blockedBy": false, - "followedBy": "record(7)", - "following": "record(6)", - "muted": false, + "like": "record(7)", + "repost": "record(6)", }, }, - "cid": "cids(11)", - "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], - "likeCount": 0, - "record": Object { - "$type": "app.bsky.feed.post", - "createdAt": "1970-01-01T00:00:00.000Z", - "text": "hey there", - }, - "replyCount": 0, - "repostCount": 0, - "uri": "record(14)", - "viewer": Object {}, }, }, -] -`; - -exports[`timeline views fetches authenticated user's home feed w/ reverse-chronological algorithm 4`] = ` -Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(1)", "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -3950,10 +5238,10 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(3)", "viewer": Object { - "like": "record(3)", - "repost": "record(2)", + "like": "record(7)", + "repost": "record(6)", }, }, "reason": Object { @@ -3973,32 +5261,32 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(5)", + "following": "record(8)", "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(4)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(4)/cids(3)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(4)/cids(3)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(5)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(2)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", @@ -4006,7 +5294,7 @@ Array [ "val": "test-label", }, Object { - "cid": "cids(2)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", @@ -4027,7 +5315,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(3)", + "$link": "cids(5)", }, "size": 4114, }, @@ -4036,12 +5324,12 @@ Array [ }, "reply": Object { "parent": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(3)", }, "root": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(3)", }, }, "text": "hear that label_me label_me_2", @@ -4055,18 +5343,35 @@ Array [ "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(1)", "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -4077,27 +5382,44 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(3)", "viewer": Object { - "like": "record(3)", - "repost": "record(2)", + "like": "record(7)", + "repost": "record(6)", }, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(1)", "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -4108,10 +5430,10 @@ Array [ }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(3)", "viewer": Object { - "like": "record(3)", - "repost": "record(2)", + "like": "record(7)", + "repost": "record(6)", }, }, }, @@ -4119,18 +5441,18 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(5)", + "following": "record(8)", "muted": false, }, }, - "cid": "cids(4)", + "cid": "cids(6)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -4141,7 +5463,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(6)", + "uri": "record(9)", "viewer": Object {}, }, }, @@ -4156,7 +5478,7 @@ Array [ "muted": false, }, }, - "cid": "cids(5)", + "cid": "cids(7)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { @@ -4170,7 +5492,7 @@ Array [ "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(8)", "embeds": Array [ Object { "$type": "app.bsky.embed.recordWithMedia#view", @@ -4179,13 +5501,13 @@ Array [ "images": Array [ Object { "alt": "tests/image/fixtures/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(3)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(3)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(5)@jpeg", }, Object { "alt": "tests/image/fixtures/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(7)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(7)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(9)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(9)@jpeg", }, ], }, @@ -4193,30 +5515,30 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(5)", + "following": "record(8)", "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(10)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(8)", + "cid": "cids(10)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(9)", + "uri": "record(12)", "val": "kind", }, ], - "uri": "record(9)", + "uri": "record(12)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -4233,15 +5555,15 @@ Array [ "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(6)", + "cid": "cids(8)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(8)", + "uri": "record(11)", "val": "kind", }, ], - "uri": "record(8)", + "uri": "record(11)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -4256,7 +5578,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(3)", + "$link": "cids(5)", }, "size": 4114, }, @@ -4267,7 +5589,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(7)", + "$link": "cids(9)", }, "size": 12736, }, @@ -4276,8 +5598,8 @@ Array [ }, "record": Object { "record": Object { - "cid": "cids(8)", - "uri": "record(9)", + "cid": "cids(10)", + "uri": "record(12)", }, }, }, @@ -4294,8 +5616,8 @@ Array [ "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(6)", - "uri": "record(8)", + "cid": "cids(8)", + "uri": "record(11)", }, }, "facets": Array [ @@ -4316,7 +5638,7 @@ Array [ }, "replyCount": 0, "repostCount": 1, - "uri": "record(7)", + "uri": "record(10)", "viewer": Object {}, }, }, @@ -4331,7 +5653,7 @@ Array [ "muted": false, }, }, - "cid": "cids(9)", + "cid": "cids(11)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -4342,33 +5664,33 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(10)", + "uri": "record(13)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(5)", + "following": "record(8)", "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(10)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(8)", + "cid": "cids(10)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(9)", + "uri": "record(12)", "val": "kind", }, ], @@ -4384,7 +5706,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(9)", + "uri": "record(12)", "viewer": Object {}, }, }, diff --git a/packages/bsky/tests/views/actor-likes.test.ts b/packages/bsky/tests/views/actor-likes.test.ts new file mode 100644 index 00000000000..774c3e63141 --- /dev/null +++ b/packages/bsky/tests/views/actor-likes.test.ts @@ -0,0 +1,126 @@ +import AtpAgent, { AtUri } from '@atproto/api' +import { TestNetwork } from '@atproto/dev-env' +import { SeedClient } from '../seeds/client' +import basicSeed from '../seeds/basic' +import { + BlockedByActorError, + BlockedActorError, +} from '@atproto/api/src/client/types/app/bsky/feed/getActorLikes' + +describe('bsky actor likes feed views', () => { + let network: TestNetwork + let agent: AtpAgent + let pdsAgent: AtpAgent + let sc: SeedClient + + // account dids, for convenience + let alice: string + let bob: string + let carol: string + let dan: string + + beforeAll(async () => { + network = await TestNetwork.create({ + dbPostgresSchema: 'bsky_views_actor_likes', + }) + agent = network.bsky.getClient() + pdsAgent = network.pds.getClient() + sc = new SeedClient(pdsAgent) + await basicSeed(sc) + await network.processAll() + alice = sc.dids.alice + bob = sc.dids.bob + carol = sc.dids.carol + dan = sc.dids.dan + }) + + afterAll(async () => { + await network.close() + }) + + it('returns posts liked by actor', async () => { + const { + data: { feed: bobLikes }, + } = await agent.api.app.bsky.feed.getActorLikes( + { actor: sc.accounts[bob].handle }, + { headers: await network.serviceHeaders(bob) }, + ) + + expect(bobLikes).toHaveLength(3) + + await expect( + agent.api.app.bsky.feed.getActorLikes( + { actor: sc.accounts[bob].handle }, + { headers: await network.serviceHeaders(carol) }, + ), + ).rejects.toThrow('Profile not found') + }) + + it('viewer has blocked author of liked post(s)', async () => { + const bobBlocksAlice = await pdsAgent.api.app.bsky.graph.block.create( + { + repo: bob, // bob blocks alice + }, + { + subject: alice, + createdAt: new Date().toISOString(), + }, + sc.getHeaders(bob), + ) + + await network.processAll() + + const { + data: { feed }, + } = await agent.api.app.bsky.feed.getActorLikes( + { actor: sc.accounts[bob].handle }, + { headers: await network.serviceHeaders(bob) }, + ) + + expect( + feed.every((item) => { + return item.post.author.did !== alice + }), + ).toBe(true) + + // unblock + await pdsAgent.api.app.bsky.graph.block.delete( + { repo: bob, rkey: new AtUri(bobBlocksAlice.uri).rkey }, + sc.getHeaders(bob), + ) + }) + + it('liked post author has blocked viewer', async () => { + const aliceBlockBob = await pdsAgent.api.app.bsky.graph.block.create( + { + repo: alice, // alice blocks bob + }, + { + subject: bob, + createdAt: new Date().toISOString(), + }, + sc.getHeaders(alice), + ) + + await network.processAll() + + const { + data: { feed }, + } = await agent.api.app.bsky.feed.getActorLikes( + { actor: sc.accounts[bob].handle }, + { headers: await network.serviceHeaders(bob) }, + ) + + expect( + feed.every((item) => { + return item.post.author.did !== alice + }), + ).toBe(true) + + // unblock + await pdsAgent.api.app.bsky.graph.block.delete( + { repo: alice, rkey: new AtUri(aliceBlockBob.uri).rkey }, + sc.getHeaders(alice), + ) + }) +}) diff --git a/packages/bsky/tests/views/actor-search.test.ts b/packages/bsky/tests/views/actor-search.test.ts index 39b9ce18a1a..77f657a9bf6 100644 --- a/packages/bsky/tests/views/actor-search.test.ts +++ b/packages/bsky/tests/views/actor-search.test.ts @@ -21,11 +21,11 @@ describe('pds actor search views', () => { sc = new SeedClient(pdsAgent) await wait(50) // allow pending sub to be established - await network.bsky.sub?.destroy() + await network.bsky.ingester.sub.destroy() await usersBulkSeed(sc) // Skip did/handle resolution for expediency - const { db } = network.bsky.ctx + const db = network.bsky.ctx.db.getPrimary() const now = new Date().toISOString() await db.db .insertInto('actor') @@ -40,9 +40,9 @@ describe('pds actor search views', () => { .execute() // Process remaining profiles - network.bsky.sub?.resume() + network.bsky.ingester.sub.resume() await network.processAll(50000) - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() headers = await network.serviceHeaders(Object.values(sc.dids)[0]) }) @@ -68,8 +68,7 @@ describe('pds actor search views', () => { ] shouldContain.forEach((handle) => expect(handles).toContain(handle)) - - expect(handles).toContain('cayla-marquardt39.test') // Fuzzy match supported by postgres + expect(handles).toContain('cayla-marquardt39.test') // Fuzzy match const shouldNotContain = [ 'sven70.test', @@ -83,7 +82,10 @@ describe('pds actor search views', () => { shouldNotContain.forEach((handle) => expect(handles).not.toContain(handle)) - expect(forSnapshot(result.data.actors)).toMatchSnapshot() + const sorted = result.data.actors.sort((a, b) => + a.handle > b.handle ? 1 : -1, + ) + expect(forSnapshot(sorted)).toMatchSnapshot() }) it('typeahead gives empty result set when provided empty term', async () => { @@ -108,7 +110,19 @@ describe('pds actor search views', () => { { headers }, ) - expect(limited.data.actors).toEqual(full.data.actors.slice(0, 5)) + // @NOTE it's expected that searchActorsTypeahead doesn't have stable pagination + + const limitedIndexInFull = limited.data.actors.map((needle) => { + return full.data.actors.findIndex( + (haystack) => needle.did === haystack.did, + ) + }) + + // subset exists in full and is monotonic + expect(limitedIndexInFull.every((idx) => idx !== -1)).toEqual(true) + expect(limitedIndexInFull).toEqual( + [...limitedIndexInFull].sort((a, b) => a - b), + ) }) it('typeahead gives results unauthed', async () => { @@ -143,8 +157,7 @@ describe('pds actor search views', () => { ] shouldContain.forEach((handle) => expect(handles).toContain(handle)) - - expect(handles).toContain('cayla-marquardt39.test') // Fuzzy match supported by postgres + expect(handles).toContain('cayla-marquardt39.test') // Fuzzy match const shouldNotContain = [ 'sven70.test', @@ -158,7 +171,10 @@ describe('pds actor search views', () => { shouldNotContain.forEach((handle) => expect(handles).not.toContain(handle)) - expect(forSnapshot(result.data.actors)).toMatchSnapshot() + const sorted = result.data.actors.sort((a, b) => + a.handle > b.handle ? 1 : -1, + ) + expect(forSnapshot(sorted)).toMatchSnapshot() }) it('search gives empty result set when provided empty term', async () => { @@ -191,7 +207,13 @@ describe('pds actor search views', () => { ) expect(full.data.actors.length).toBeGreaterThan(5) - expect(results(paginatedAll)).toEqual(results([full.data])) + const sortedFull = results([full.data]).sort((a, b) => + a.handle > b.handle ? 1 : -1, + ) + const sortedPaginated = results(paginatedAll).sort((a, b) => + a.handle > b.handle ? 1 : -1, + ) + expect(sortedPaginated).toEqual(sortedFull) }) it('search handles bad input', async () => { diff --git a/packages/bsky/tests/views/admin/repo-search.test.ts b/packages/bsky/tests/views/admin/repo-search.test.ts index 8bcf5a00e7e..ec53418eb46 100644 --- a/packages/bsky/tests/views/admin/repo-search.test.ts +++ b/packages/bsky/tests/views/admin/repo-search.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import AtpAgent, { ComAtprotoAdminSearchRepos } from '@atproto/api' import { wait } from '@atproto/common' import { TestNetwork } from '@atproto/dev-env' import { SeedClient } from '../../seeds/client' @@ -9,6 +9,20 @@ describe('pds admin repo search views', () => { let agent: AtpAgent let sc: SeedClient let headers: { [s: string]: string } + // In results that don't have a related profile record, we will only have handle but not a name + // And names are usually capitalized on each word so the comparison is done on lowercase version + const handleOrNameStartsWith = + (term: string) => (handleOrName: (string | undefined)[]) => + !!handleOrName.find((str) => + str?.toLowerCase().includes(term.toLowerCase()), + ) + const resultToHandlesAndNames = ( + result: ComAtprotoAdminSearchRepos.Response, + ) => + result.data.repos.map((u: any) => [ + u.handle, + (u.relatedRecords[0] as Record)?.displayName, + ]) beforeAll(async () => { network = await TestNetwork.create({ @@ -18,12 +32,12 @@ describe('pds admin repo search views', () => { const pdsAgent = network.pds.getClient() sc = new SeedClient(pdsAgent) - await wait(50) // allow pending sub to be established - await network.bsky.sub?.destroy() + await wait(100) // allow pending sub to be established + await network.bsky.ingester.sub.destroy() await usersBulkSeed(sc) // Skip did/handle resolution for expediency - const { db } = network.bsky.ctx + const db = network.bsky.ctx.db.getPrimary() const now = new Date().toISOString() await db.db .insertInto('actor') @@ -38,7 +52,7 @@ describe('pds admin repo search views', () => { .execute() // Process remaining profiles - network.bsky.sub?.resume() + network.bsky.ingester.sub.resume() await network.processAll(50000) headers = await network.adminHeaders({}) }) @@ -48,23 +62,64 @@ describe('pds admin repo search views', () => { }) it('gives relevant results when searched by handle', async () => { + const term = 'car' const result = await agent.api.com.atproto.admin.searchRepos( - { term: 'car' }, + { term }, { headers }, ) const shouldContain = [ - 'cara-wiegand69.test', // Present despite repo takedown - 'eudora-dietrich4.test', // Carol Littel - 'shane-torphy52.test', //Sadie Carter - 'aliya-hodkiewicz.test', // Carlton Abernathy IV + // Present despite repo takedown + // First item in the array because of direct handle match + 'cara-wiegand69.test', 'carlos6.test', + 'aliya-hodkiewicz.test', // Carlton Abernathy IV + 'eudora-dietrich4.test', // Carol Littel 'carolina-mcdermott77.test', + 'shane-torphy52.test', // Sadie Carter + // Last item in the array because handle and display name none match very close to the the search term + 'cayla-marquardt39.test', ] - const handles = result.data.repos.map((u) => u.handle) - + const handlesAndNames = resultToHandlesAndNames(result) + const handles = handlesAndNames.map(([handle]) => handle) + // Assert that all matches are found shouldContain.forEach((handle) => expect(handles).toContain(handle)) + // Assert that the order is correct, showing the closest match by handle first + const containsTerm = handleOrNameStartsWith(term) + expect(containsTerm(handlesAndNames[0])).toBeTruthy() + expect( + containsTerm(handlesAndNames[handlesAndNames.length - 1]), + ).toBeFalsy() + }) + + it('pagination respects matching order when searched by handle', async () => { + const term = 'car' + const resultPageOne = await agent.api.com.atproto.admin.searchRepos( + { term, limit: 4 }, + { headers }, + ) + const resultPageTwo = await agent.api.com.atproto.admin.searchRepos( + { term, limit: 4, cursor: resultPageOne.data.cursor }, + { headers }, + ) + + const handlesAndNamesPageOne = resultToHandlesAndNames(resultPageOne) + const handlesAndNamesPageTwo = resultToHandlesAndNames(resultPageTwo) + const containsTerm = handleOrNameStartsWith(term) + + // First result of first page always has matches either handle or did + expect(containsTerm(handlesAndNamesPageOne[0])).toBeTruthy() + // Since we only get 4 items per page max and know that among the test dataset + // at least 4 users have the term in handle or profile, last item in first page + // should contain the term + expect( + containsTerm(handlesAndNamesPageOne[handlesAndNamesPageOne.length - 1]), + ).toBeTruthy() + // However, the last item of second page, should not contain the term + expect( + containsTerm(handlesAndNamesPageTwo[handlesAndNamesPageTwo.length - 1]), + ).toBeFalsy() }) it('gives relevant results when searched by did', async () => { diff --git a/packages/bsky/tests/views/author-feed.test.ts b/packages/bsky/tests/views/author-feed.test.ts index 88fd1ec7579..62e0fd0826e 100644 --- a/packages/bsky/tests/views/author-feed.test.ts +++ b/packages/bsky/tests/views/author-feed.test.ts @@ -4,6 +4,9 @@ import { forSnapshot, paginateAll, stripViewerFromPost } from '../_util' import { SeedClient } from '../seeds/client' import basicSeed from '../seeds/basic' import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/defs' +import { isRecord } from '../../src/lexicon/types/app/bsky/feed/post' +import { isView as isEmbedRecordWithMedia } from '../../src/lexicon/types/app/bsky/embed/recordWithMedia' +import { isView as isImageEmbed } from '../../src/lexicon/types/app/bsky/embed/images' describe('pds author feed views', () => { let network: TestNetwork @@ -25,7 +28,6 @@ describe('pds author feed views', () => { sc = new SeedClient(pdsAgent) await basicSeed(sc) await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() alice = sc.dids.alice bob = sc.dids.bob carol = sc.dids.carol @@ -163,12 +165,11 @@ describe('pds author feed views', () => { }, ) - const { data: postBlock } = await agent.api.app.bsky.feed.getAuthorFeed( + const attempt = agent.api.app.bsky.feed.getAuthorFeed( { actor: alice }, { headers: await network.serviceHeaders(carol) }, ) - - expect(postBlock.feed.length).toEqual(0) + await expect(attempt).rejects.toThrow('Profile not found') // Cleanup await agent.api.com.atproto.admin.reverseModerationAction( @@ -233,4 +234,71 @@ describe('pds author feed views', () => { }, ) }) + + it('can filter by posts_with_media', async () => { + const { data: carolFeed } = await agent.api.app.bsky.feed.getAuthorFeed({ + actor: carol, + filter: 'posts_with_media', + }) + + expect(carolFeed.feed.length).toBeGreaterThan(0) + expect( + carolFeed.feed.every(({ post }) => { + const isRecordWithActorMedia = + isEmbedRecordWithMedia(post.embed) && isImageEmbed(post.embed?.media) + const isActorMedia = isImageEmbed(post.embed) + const isFromActor = post.author.did === carol + + return (isRecordWithActorMedia || isActorMedia) && isFromActor + }), + ).toBeTruthy() + + const { data: bobFeed } = await agent.api.app.bsky.feed.getAuthorFeed({ + actor: bob, + filter: 'posts_with_media', + }) + + expect( + bobFeed.feed.every(({ post }) => { + return isImageEmbed(post.embed) && post.author.did === bob + }), + ).toBeTruthy() + + const { data: danFeed } = await agent.api.app.bsky.feed.getAuthorFeed({ + actor: dan, + filter: 'posts_with_media', + }) + + expect(danFeed.feed.length).toEqual(0) + }) + + it('filters by posts_no_replies', async () => { + const { data: carolFeed } = await agent.api.app.bsky.feed.getAuthorFeed({ + actor: carol, + filter: 'posts_no_replies', + }) + + expect( + carolFeed.feed.every(({ post }) => { + return ( + (isRecord(post.record) && !post.record.reply) || + (isRecord(post.record) && post.record.reply) + ) + }), + ).toBeTruthy() + + const { data: danFeed } = await agent.api.app.bsky.feed.getAuthorFeed({ + actor: dan, + filter: 'posts_no_replies', + }) + + expect( + danFeed.feed.every(({ post }) => { + return ( + (isRecord(post.record) && !post.record.reply) || + (isRecord(post.record) && post.record.reply) + ) + }), + ).toBeTruthy() + }) }) diff --git a/packages/bsky/tests/views/block-lists.test.ts b/packages/bsky/tests/views/block-lists.test.ts new file mode 100644 index 00000000000..0a8a223e046 --- /dev/null +++ b/packages/bsky/tests/views/block-lists.test.ts @@ -0,0 +1,407 @@ +import AtpAgent from '@atproto/api' +import { TestNetwork } from '@atproto/dev-env' +import { forSnapshot } from '../_util' +import { SeedClient } from '../seeds/client' +import basicSeed from '../seeds/basic' +import { RecordRef } from '@atproto/bsky/tests/seeds/client' +import { BlockedActorError } from '@atproto/api/src/client/types/app/bsky/feed/getAuthorFeed' +import { BlockedByActorError } from '@atproto/api/src/client/types/app/bsky/feed/getAuthorFeed' + +describe('pds views with blocking from block lists', () => { + let network: TestNetwork + let agent: AtpAgent + let pdsAgent: AtpAgent + let sc: SeedClient + let aliceReplyToDan: { ref: RecordRef } + + let alice: string + let bob: string + let carol: string + let dan: string + + beforeAll(async () => { + network = await TestNetwork.create({ + dbPostgresSchema: 'views_block_lists', + }) + agent = network.bsky.getClient() + pdsAgent = network.pds.getClient() + sc = new SeedClient(pdsAgent) + await basicSeed(sc) + alice = sc.dids.alice + bob = sc.dids.bob + carol = sc.dids.carol + dan = sc.dids.dan + // add follows to ensure blocks work even w follows + await sc.follow(carol, dan) + await sc.follow(dan, carol) + aliceReplyToDan = await sc.reply( + alice, + sc.posts[dan][0].ref, + sc.posts[dan][0].ref, + 'alice replies to dan', + ) + await network.processAll() + }) + + afterAll(async () => { + await network.close() + }) + + let listUri: string + + it('creates a list with some items', async () => { + const avatar = await sc.uploadFile( + alice, + 'tests/image/fixtures/key-portrait-small.jpg', + 'image/jpeg', + ) + // alice creates block list with bob & carol that dan uses + const list = await pdsAgent.api.app.bsky.graph.list.create( + { repo: alice }, + { + name: 'alice blocks', + purpose: 'app.bsky.graph.defs#blocklist', + description: 'big list of blocks', + avatar: avatar.image, + createdAt: new Date().toISOString(), + }, + sc.getHeaders(alice), + ) + listUri = list.uri + await pdsAgent.api.app.bsky.graph.listitem.create( + { repo: alice }, + { + subject: sc.dids.bob, + list: list.uri, + createdAt: new Date().toISOString(), + }, + sc.getHeaders(alice), + ) + await pdsAgent.api.app.bsky.graph.listitem.create( + { repo: alice }, + { + subject: sc.dids.carol, + list: list.uri, + createdAt: new Date().toISOString(), + }, + sc.getHeaders(alice), + ) + await network.processAll() + }) + + it('uses a list for blocks', async () => { + await pdsAgent.api.app.bsky.graph.listblock.create( + { repo: dan }, + { + subject: listUri, + createdAt: new Date().toISOString(), + }, + sc.getHeaders(dan), + ) + await network.processAll() + }) + + it('blocks thread post', async () => { + const { carol, dan } = sc.dids + const { data: threadAlice } = await agent.api.app.bsky.feed.getPostThread( + { depth: 1, uri: sc.posts[carol][0].ref.uriStr }, + { headers: await network.serviceHeaders(dan) }, + ) + expect(threadAlice.thread).toEqual( + expect.objectContaining({ + $type: 'app.bsky.feed.defs#blockedPost', + uri: sc.posts[carol][0].ref.uriStr, + blocked: true, + }), + ) + const { data: threadCarol } = await agent.api.app.bsky.feed.getPostThread( + { depth: 1, uri: sc.posts[dan][0].ref.uriStr }, + { headers: await network.serviceHeaders(carol) }, + ) + expect(threadCarol.thread).toEqual( + expect.objectContaining({ + $type: 'app.bsky.feed.defs#blockedPost', + uri: sc.posts[dan][0].ref.uriStr, + blocked: true, + }), + ) + }) + + it('blocks thread reply', async () => { + // Contains reply by carol + const { data: thread } = await agent.api.app.bsky.feed.getPostThread( + { depth: 1, uri: sc.posts[alice][1].ref.uriStr }, + { headers: await network.serviceHeaders(dan) }, + ) + expect(forSnapshot(thread)).toMatchSnapshot() + }) + + it('blocks thread parent', async () => { + // Parent is a post by dan + const { data: thread } = await agent.api.app.bsky.feed.getPostThread( + { depth: 1, uri: aliceReplyToDan.ref.uriStr }, + { headers: await network.serviceHeaders(carol) }, + ) + expect(forSnapshot(thread)).toMatchSnapshot() + }) + + it('blocks record embeds', async () => { + // Contains a deep embed of carol's post, blocked by dan + const { data: thread } = await agent.api.app.bsky.feed.getPostThread( + { depth: 0, uri: sc.posts[alice][2].ref.uriStr }, + { headers: await network.serviceHeaders(dan) }, + ) + expect(forSnapshot(thread)).toMatchSnapshot() + }) + + it('errors on getting author feed', async () => { + const attempt1 = agent.api.app.bsky.feed.getAuthorFeed( + { actor: carol }, + { headers: await network.serviceHeaders(dan) }, + ) + await expect(attempt1).rejects.toThrow(BlockedActorError) + + const attempt2 = agent.api.app.bsky.feed.getAuthorFeed( + { actor: dan }, + { headers: await network.serviceHeaders(carol) }, + ) + await expect(attempt2).rejects.toThrow(BlockedByActorError) + }) + + it('strips blocked users out of getTimeline', async () => { + const resCarol = await agent.api.app.bsky.feed.getTimeline( + { limit: 100 }, + { headers: await network.serviceHeaders(carol) }, + ) + expect( + resCarol.data.feed.some((post) => post.post.author.did === dan), + ).toBeFalsy() + + const resDan = await agent.api.app.bsky.feed.getTimeline( + { limit: 100 }, + { headers: await network.serviceHeaders(dan) }, + ) + expect( + resDan.data.feed.some((post) => + [bob, carol].includes(post.post.author.did), + ), + ).toBeFalsy() + }) + + it('returns block status on getProfile', async () => { + const resCarol = await agent.api.app.bsky.actor.getProfile( + { actor: dan }, + { headers: await network.serviceHeaders(carol) }, + ) + expect(resCarol.data.viewer?.blocking).toBeUndefined() + expect(resCarol.data.viewer?.blockedBy).toBe(true) + + const resDan = await agent.api.app.bsky.actor.getProfile( + { actor: carol }, + { headers: await network.serviceHeaders(dan) }, + ) + expect(resDan.data.viewer?.blocking).toBeDefined() + expect(resDan.data.viewer?.blockedBy).toBe(false) + }) + + it('returns block status on getProfiles', async () => { + const resCarol = await agent.api.app.bsky.actor.getProfiles( + { actors: [alice, dan] }, + { headers: await network.serviceHeaders(carol) }, + ) + expect(resCarol.data.profiles[0].viewer?.blocking).toBeUndefined() + expect(resCarol.data.profiles[0].viewer?.blockedBy).toBe(false) + expect(resCarol.data.profiles[1].viewer?.blocking).toBeUndefined() + expect(resCarol.data.profiles[1].viewer?.blockedBy).toBe(true) + + const resDan = await agent.api.app.bsky.actor.getProfiles( + { actors: [alice, carol] }, + { headers: await network.serviceHeaders(dan) }, + ) + expect(resDan.data.profiles[0].viewer?.blocking).toBeUndefined() + expect(resDan.data.profiles[0].viewer?.blockedBy).toBe(false) + expect(resDan.data.profiles[1].viewer?.blocking).toBeDefined() + expect(resDan.data.profiles[1].viewer?.blockedBy).toBe(false) + }) + + it('does not return notifs for blocked accounts', async () => { + const resCarol = await agent.api.app.bsky.notification.listNotifications( + { + limit: 100, + }, + { headers: await network.serviceHeaders(carol) }, + ) + expect( + resCarol.data.notifications.some((notif) => notif.author.did === dan), + ).toBeFalsy() + + const resDan = await agent.api.app.bsky.notification.listNotifications( + { + limit: 100, + }, + { headers: await network.serviceHeaders(carol) }, + ) + expect( + resDan.data.notifications.some((notif) => notif.author.did === carol), + ).toBeFalsy() + }) + + it('does not return blocked accounts in actor search', async () => { + const resCarol = await agent.api.app.bsky.actor.searchActors( + { + term: 'dan.test', + }, + { headers: await network.serviceHeaders(carol) }, + ) + expect(resCarol.data.actors.some((actor) => actor.did === dan)).toBeFalsy() + + const resDan = await agent.api.app.bsky.actor.searchActors( + { + term: 'carol.test', + }, + { headers: await network.serviceHeaders(dan) }, + ) + expect(resDan.data.actors.some((actor) => actor.did === carol)).toBeFalsy() + }) + + it('does not return blocked accounts in actor search typeahead', async () => { + const resCarol = await agent.api.app.bsky.actor.searchActorsTypeahead( + { + term: 'dan.test', + }, + { headers: await network.serviceHeaders(carol) }, + ) + expect(resCarol.data.actors.some((actor) => actor.did === dan)).toBeFalsy() + + const resDan = await agent.api.app.bsky.actor.searchActorsTypeahead( + { + term: 'carol.test', + }, + { headers: await network.serviceHeaders(dan) }, + ) + expect(resDan.data.actors.some((actor) => actor.did === carol)).toBeFalsy() + }) + + it('does not return blocked accounts in get suggestions', async () => { + // unfollow so they _would_ show up in suggestions if not for block + await sc.unfollow(carol, dan) + await sc.unfollow(dan, carol) + await network.processAll() + + const resCarol = await agent.api.app.bsky.actor.getSuggestions( + { + limit: 100, + }, + { headers: await network.serviceHeaders(carol) }, + ) + expect(resCarol.data.actors.some((actor) => actor.did === dan)).toBeFalsy() + + const resDan = await agent.api.app.bsky.actor.getSuggestions( + { + limit: 100, + }, + { headers: await network.serviceHeaders(dan) }, + ) + expect(resDan.data.actors.some((actor) => actor.did === carol)).toBeFalsy() + }) + + it('returns the contents of a list', async () => { + const res = await agent.api.app.bsky.graph.getList( + { list: listUri }, + { headers: await network.serviceHeaders(dan) }, + ) + expect(forSnapshot(res.data)).toMatchSnapshot() + }) + + it('paginates getList', async () => { + const full = await agent.api.app.bsky.graph.getList( + { list: listUri }, + { headers: await network.serviceHeaders(dan) }, + ) + const first = await agent.api.app.bsky.graph.getList( + { list: listUri, limit: 1 }, + { headers: await network.serviceHeaders(dan) }, + ) + const second = await agent.api.app.bsky.graph.getList( + { list: listUri, cursor: first.data.cursor }, + { headers: await network.serviceHeaders(dan) }, + ) + const combined = [...first.data.items, ...second.data.items] + expect(combined).toEqual(full.data.items) + }) + + let otherListUri: string + + it('returns lists associated with a user', async () => { + const listRes = await pdsAgent.api.app.bsky.graph.list.create( + { repo: alice }, + { + name: 'new list', + purpose: 'app.bsky.graph.defs#blocklist', + description: 'blah blah', + createdAt: new Date().toISOString(), + }, + sc.getHeaders(alice), + ) + otherListUri = listRes.uri + await network.processAll() + + const res = await agent.api.app.bsky.graph.getLists( + { actor: alice }, + { headers: await network.serviceHeaders(dan) }, + ) + expect(forSnapshot(res.data)).toMatchSnapshot() + }) + + it('paginates getLists', async () => { + const full = await agent.api.app.bsky.graph.getLists( + { actor: alice }, + { headers: await network.serviceHeaders(dan) }, + ) + const first = await agent.api.app.bsky.graph.getLists( + { actor: alice, limit: 1 }, + { headers: await network.serviceHeaders(dan) }, + ) + const second = await agent.api.app.bsky.graph.getLists( + { actor: alice, cursor: first.data.cursor }, + { headers: await network.serviceHeaders(dan) }, + ) + const combined = [...first.data.lists, ...second.data.lists] + expect(combined).toEqual(full.data.lists) + }) + + it('returns a users own list blocks', async () => { + await pdsAgent.api.app.bsky.graph.listblock.create( + { repo: dan }, + { + subject: otherListUri, + createdAt: new Date().toISOString(), + }, + sc.getHeaders(dan), + ) + await network.processAll() + + const res = await agent.api.app.bsky.graph.getListBlocks( + {}, + { headers: await network.serviceHeaders(dan) }, + ) + expect(forSnapshot(res.data)).toMatchSnapshot() + }) + + it('paginates getListBlocks', async () => { + const full = await agent.api.app.bsky.graph.getListBlocks( + {}, + { headers: await network.serviceHeaders(dan) }, + ) + const first = await agent.api.app.bsky.graph.getListBlocks( + { limit: 1 }, + { headers: await network.serviceHeaders(dan) }, + ) + const second = await agent.api.app.bsky.graph.getListBlocks( + { cursor: first.data.cursor }, + { headers: await network.serviceHeaders(dan) }, + ) + const combined = [...first.data.lists, ...second.data.lists] + expect(combined).toEqual(full.data.lists) + }) +}) diff --git a/packages/bsky/tests/views/blocks.test.ts b/packages/bsky/tests/views/blocks.test.ts index 400b74823e0..127aac4fc6b 100644 --- a/packages/bsky/tests/views/blocks.test.ts +++ b/packages/bsky/tests/views/blocks.test.ts @@ -1,6 +1,12 @@ -import AtpAgent from '@atproto/api' +import assert from 'assert' +import AtpAgent, { AtUri } from '@atproto/api' import { BlockedActorError } from '@atproto/api/src/client/types/app/bsky/feed/getAuthorFeed' import { BlockedByActorError } from '@atproto/api/src/client/types/app/bsky/feed/getAuthorFeed' +import { isThreadViewPost } from '@atproto/api/src/client/types/app/bsky/feed/defs' +import { + isViewRecord as isEmbedViewRecord, + isViewBlocked as isEmbedViewBlocked, +} from '@atproto/api/src/client/types/app/bsky/embed/record' import { TestNetwork } from '@atproto/dev-env' import { forSnapshot } from '../_util' import { RecordRef, SeedClient } from '../seeds/client' @@ -11,11 +17,14 @@ describe('pds views with blocking', () => { let agent: AtpAgent let pdsAgent: AtpAgent let sc: SeedClient + let danBlockCarol: { uri: string } let aliceReplyToDan: { ref: RecordRef } + let carolReplyToDan: { ref: RecordRef } let alice: string let carol: string let dan: string + let danBlockUri: string beforeAll(async () => { network = await TestNetwork.create({ @@ -31,18 +40,25 @@ describe('pds views with blocking', () => { // add follows to ensure blocks work even w follows await sc.follow(carol, dan) await sc.follow(dan, carol) - // dan blocks carol - await pdsAgent.api.app.bsky.graph.block.create( - { repo: dan }, - { createdAt: new Date().toISOString(), subject: carol }, - sc.getHeaders(dan), - ) aliceReplyToDan = await sc.reply( alice, sc.posts[dan][0].ref, sc.posts[dan][0].ref, 'alice replies to dan', ) + carolReplyToDan = await sc.reply( + carol, + sc.posts[dan][0].ref, + sc.posts[dan][0].ref, + 'carol replies to dan', + ) + // dan blocks carol + danBlockCarol = await pdsAgent.api.app.bsky.graph.block.create( + { repo: dan }, + { createdAt: new Date().toISOString(), subject: carol }, + sc.getHeaders(dan), + ) + danBlockUri = danBlockCarol.uri await network.processAll() }) @@ -51,7 +67,6 @@ describe('pds views with blocking', () => { }) it('blocks thread post', async () => { - const { carol, dan } = sc.dids const { data: threadAlice } = await agent.api.app.bsky.feed.getPostThread( { depth: 1, uri: sc.posts[carol][0].ref.uriStr }, { headers: await network.serviceHeaders(dan) }, @@ -61,6 +76,13 @@ describe('pds views with blocking', () => { $type: 'app.bsky.feed.defs#blockedPost', uri: sc.posts[carol][0].ref.uriStr, blocked: true, + author: { + did: carol, + viewer: { + blockedBy: false, + blocking: danBlockUri, + }, + }, }, }) const { data: threadCarol } = await agent.api.app.bsky.feed.getPostThread( @@ -72,6 +94,12 @@ describe('pds views with blocking', () => { $type: 'app.bsky.feed.defs#blockedPost', uri: sc.posts[dan][0].ref.uriStr, blocked: true, + author: { + did: dan, + viewer: { + blockedBy: true, + }, + }, }, }) }) @@ -171,6 +199,56 @@ describe('pds views with blocking', () => { expect(resDan.data.profiles[1].viewer?.blockedBy).toBe(false) }) + it('does not return block violating follows', async () => { + const resCarol = await agent.api.app.bsky.graph.getFollows( + { actor: carol }, + { headers: await network.serviceHeaders(alice) }, + ) + expect(resCarol.data.follows.some((f) => f.did === dan)).toBe(false) + + const resDan = await agent.api.app.bsky.graph.getFollows( + { actor: dan }, + { headers: await network.serviceHeaders(alice) }, + ) + expect(resDan.data.follows.some((f) => f.did === carol)).toBe(false) + }) + + it('does not return block violating followers', async () => { + const resCarol = await agent.api.app.bsky.graph.getFollowers( + { actor: carol }, + { headers: await network.serviceHeaders(alice) }, + ) + expect(resCarol.data.followers.some((f) => f.did === dan)).toBe(false) + + const resDan = await agent.api.app.bsky.graph.getFollowers( + { actor: dan }, + { headers: await network.serviceHeaders(alice) }, + ) + expect(resDan.data.followers.some((f) => f.did === carol)).toBe(false) + }) + + it('does not return posts from blocked users', async () => { + const alicePost = sc.posts[alice][0].ref.uriStr + const carolPost = sc.posts[carol][0].ref.uriStr + const danPost = sc.posts[dan][0].ref.uriStr + + const resCarol = await agent.api.app.bsky.feed.getPosts( + { uris: [alicePost, carolPost, danPost] }, + { headers: await network.serviceHeaders(carol) }, + ) + expect(resCarol.data.posts.some((p) => p.uri === alicePost)).toBe(true) + expect(resCarol.data.posts.some((p) => p.uri === carolPost)).toBe(true) + expect(resCarol.data.posts.some((p) => p.uri === danPost)).toBe(false) + + const resDan = await agent.api.app.bsky.feed.getPosts( + { uris: [alicePost, carolPost, danPost] }, + { headers: await network.serviceHeaders(dan) }, + ) + expect(resDan.data.posts.some((p) => p.uri === alicePost)).toBe(true) + expect(resDan.data.posts.some((p) => p.uri === carolPost)).toBe(false) + expect(resDan.data.posts.some((p) => p.uri === danPost)).toBe(true) + }) + it('does not return notifs for blocked accounts', async () => { const resCarol = await agent.api.app.bsky.notification.listNotifications( { @@ -233,6 +311,7 @@ describe('pds views with blocking', () => { // unfollow so they _would_ show up in suggestions if not for block await sc.unfollow(carol, dan) await sc.unfollow(dan, carol) + await network.processAll() const resCarol = await agent.api.app.bsky.actor.getSuggestions( { @@ -251,6 +330,138 @@ describe('pds views with blocking', () => { expect(resDan.data.actors.some((actor) => actor.did === carol)).toBeFalsy() }) + it('does not serve blocked replies', async () => { + const getThreadPostUri = (r) => r?.['post']?.['uri'] + // reply then block + const { data: replyThenBlock } = + await agent.api.app.bsky.feed.getPostThread( + { depth: 1, uri: sc.posts[dan][0].ref.uriStr }, + { headers: await network.serviceHeaders(alice) }, + ) + assert(isThreadViewPost(replyThenBlock.thread)) + expect(replyThenBlock.thread.replies?.map(getThreadPostUri)).toEqual([ + aliceReplyToDan.ref.uriStr, + ]) + + // unblock + await pdsAgent.api.app.bsky.graph.block.delete( + { repo: dan, rkey: new AtUri(danBlockCarol.uri).rkey }, + sc.getHeaders(dan), + ) + await network.processAll() + const { data: unblock } = await agent.api.app.bsky.feed.getPostThread( + { depth: 1, uri: sc.posts[dan][0].ref.uriStr }, + { headers: await network.serviceHeaders(alice) }, + ) + assert(isThreadViewPost(unblock.thread)) + expect(unblock.thread.replies?.map(getThreadPostUri)).toEqual([ + carolReplyToDan.ref.uriStr, + aliceReplyToDan.ref.uriStr, + ]) + + // block then reply + danBlockCarol = await pdsAgent.api.app.bsky.graph.block.create( + { repo: dan }, + { createdAt: new Date().toISOString(), subject: carol }, + sc.getHeaders(dan), + ) + const carolReplyToDan2 = await sc.reply( + carol, + sc.posts[dan][1].ref, + sc.posts[dan][1].ref, + 'carol replies to dan again', + ) + await network.processAll() + const { data: blockThenReply } = + await agent.api.app.bsky.feed.getPostThread( + { depth: 1, uri: sc.posts[dan][0].ref.uriStr }, + { headers: await network.serviceHeaders(alice) }, + ) + assert(isThreadViewPost(blockThenReply.thread)) + expect(replyThenBlock.thread.replies?.map(getThreadPostUri)).toEqual([ + aliceReplyToDan.ref.uriStr, + ]) + + // cleanup + await pdsAgent.api.app.bsky.feed.post.delete( + { repo: carol, rkey: carolReplyToDan2.ref.uri.rkey }, + sc.getHeaders(carol), + ) + await network.processAll() + }) + + it('does not serve blocked embeds to third-party', async () => { + // embed then block + const { data: embedThenBlock } = + await agent.api.app.bsky.feed.getPostThread( + { depth: 0, uri: sc.posts[dan][1].ref.uriStr }, + { headers: await network.serviceHeaders(alice) }, + ) + assert(isThreadViewPost(embedThenBlock.thread)) + assert(isEmbedViewBlocked(embedThenBlock.thread.post.embed?.record)) + + // unblock + await pdsAgent.api.app.bsky.graph.block.delete( + { repo: dan, rkey: new AtUri(danBlockCarol.uri).rkey }, + sc.getHeaders(dan), + ) + await network.processAll() + const { data: unblock } = await agent.api.app.bsky.feed.getPostThread( + { depth: 0, uri: sc.posts[dan][1].ref.uriStr }, + { headers: await network.serviceHeaders(alice) }, + ) + assert(isThreadViewPost(unblock.thread)) + assert(isEmbedViewRecord(unblock.thread.post.embed?.record)) + + // block then embed + danBlockCarol = await pdsAgent.api.app.bsky.graph.block.create( + { repo: dan }, + { createdAt: new Date().toISOString(), subject: carol }, + sc.getHeaders(dan), + ) + const carolEmbedsDan = await sc.post( + carol, + 'carol embeds dan', + undefined, + undefined, + sc.posts[dan][0].ref, + ) + await network.processAll() + const { data: blockThenEmbed } = + await agent.api.app.bsky.feed.getPostThread( + { depth: 0, uri: carolEmbedsDan.ref.uriStr }, + { headers: await network.serviceHeaders(alice) }, + ) + assert(isThreadViewPost(blockThenEmbed.thread)) + assert(isEmbedViewBlocked(blockThenEmbed.thread.post.embed?.record)) + + // cleanup + await pdsAgent.api.app.bsky.feed.post.delete( + { repo: carol, rkey: carolEmbedsDan.ref.uri.rkey }, + sc.getHeaders(carol), + ) + await network.processAll() + }) + + it('applies third-party blocking rules in feeds.', async () => { + // alice follows carol and dan, block exists between carol and dan. + const replyBlockedUri = carolReplyToDan.ref.uriStr + const embedBlockedUri = sc.posts[dan][1].ref.uriStr + const { data: timeline } = await agent.api.app.bsky.feed.getTimeline( + { limit: 100 }, + { headers: await network.serviceHeaders(alice) }, + ) + const replyBlockedPost = timeline.feed.find( + (item) => item.post.uri === replyBlockedUri, + ) + expect(replyBlockedPost).toBeUndefined() + const embedBlockedPost = timeline.feed.find( + (item) => item.post.uri === embedBlockedUri, + ) + assert(embedBlockedPost) + assert(isEmbedViewBlocked(embedBlockedPost.post.embed?.record)) + }) + it('returns a list of blocks', async () => { await pdsAgent.api.app.bsky.graph.block.create( { repo: dan }, diff --git a/packages/bsky/tests/views/follows.test.ts b/packages/bsky/tests/views/follows.test.ts index 8e8903c45d5..e048b433b8e 100644 --- a/packages/bsky/tests/views/follows.test.ts +++ b/packages/bsky/tests/views/follows.test.ts @@ -22,7 +22,7 @@ describe('pds follow views', () => { sc = new SeedClient(pdsAgent) await followsSeed(sc) await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() alice = sc.dids.alice }) diff --git a/packages/bsky/tests/views/mute-lists.test.ts b/packages/bsky/tests/views/mute-lists.test.ts index f130ac508e4..fd2d02ed81f 100644 --- a/packages/bsky/tests/views/mute-lists.test.ts +++ b/packages/bsky/tests/views/mute-lists.test.ts @@ -81,7 +81,6 @@ describe('bsky views with mutes from mute lists', () => { sc.getHeaders(alice), ) await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() }) it('uses a list for mutes', async () => { @@ -166,7 +165,7 @@ describe('bsky views with mutes from mute lists', () => { // unfollow so they _would_ show up in suggestions if not for mute await sc.unfollow(dan, carol) await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() const res = await agent.api.app.bsky.actor.getSuggestions( { diff --git a/packages/bsky/tests/views/notifications.test.ts b/packages/bsky/tests/views/notifications.test.ts index 1db9dbf67a1..b125ffc3570 100644 --- a/packages/bsky/tests/views/notifications.test.ts +++ b/packages/bsky/tests/views/notifications.test.ts @@ -23,7 +23,7 @@ describe('notification views', () => { sc = new SeedClient(pdsAgent) await basicSeed(sc) await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() alice = sc.dids.alice }) @@ -56,7 +56,7 @@ describe('notification views', () => { { headers: await network.serviceHeaders(alice) }, ) - expect(notifCountAlice.data.count).toBe(11) + expect(notifCountAlice.data.count).toBe(12) const notifCountBob = await agent.api.app.bsky.notification.getUnreadCount( {}, @@ -76,7 +76,7 @@ describe('notification views', () => { 'indeed', ) await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() const notifCountAlice = await agent.api.app.bsky.notification.getUnreadCount( @@ -84,7 +84,7 @@ describe('notification views', () => { { headers: await network.serviceHeaders(alice) }, ) - expect(notifCountAlice.data.count).toBe(12) + expect(notifCountAlice.data.count).toBe(13) const notifCountBob = await agent.api.app.bsky.notification.getUnreadCount( {}, @@ -100,7 +100,7 @@ describe('notification views', () => { await sc.deletePost(sc.dids.alice, root.ref.uri) const second = await sc.reply(sc.dids.carol, root.ref, first.ref, 'second') await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() const notifsAlice = await agent.api.app.bsky.notification.listNotifications( {}, @@ -133,7 +133,7 @@ describe('notification views', () => { ) const notifs = notifRes.data.notifications - expect(notifs.length).toBe(12) + expect(notifs.length).toBe(13) const readStates = notifs.map((notif) => notif.isRead) expect(readStates).toEqual(notifs.map(() => false)) @@ -162,7 +162,7 @@ describe('notification views', () => { { headers: await network.serviceHeaders(alice) }, ) - expect(full.data.notifications.length).toEqual(12) + expect(full.data.notifications.length).toEqual(13) expect(results(paginatedAll)).toEqual(results([full.data])) }) @@ -218,7 +218,7 @@ describe('notification views', () => { ) const notifs = notifRes.data.notifications - expect(notifs.length).toBe(12) + expect(notifs.length).toBe(13) const readStates = notifs.map((notif) => notif.isRead) expect(readStates).toEqual(notifs.map((n) => n.indexedAt <= seenAt)) @@ -266,9 +266,9 @@ describe('notification views', () => { ) const notifs = sort(notifRes.data.notifications) - expect(notifs.length).toBe(10) + expect(notifs.length).toBe(11) expect(forSnapshot(notifs)).toMatchSnapshot() - expect(notifCount.data.count).toBe(10) + expect(notifCount.data.count).toBe(11) // Cleanup await Promise.all( diff --git a/packages/bsky/tests/views/posts.test.ts b/packages/bsky/tests/views/posts.test.ts index 9eeb2a7a962..99ec565dc59 100644 --- a/packages/bsky/tests/views/posts.test.ts +++ b/packages/bsky/tests/views/posts.test.ts @@ -18,7 +18,7 @@ describe('pds posts views', () => { sc = new SeedClient(pdsAgent) await basicSeed(sc) await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() }) afterAll(async () => { @@ -76,11 +76,11 @@ describe('pds posts views', () => { const posts = await agent.api.app.bsky.feed.getPosts({ uris }) expect(posts.data.posts.length).toBe(2) - const recivedUris = posts.data.posts.map((p) => p.uri).sort() + const receivedUris = posts.data.posts.map((p) => p.uri).sort() const expected = [ sc.posts[sc.dids.alice][0].ref.uriStr, sc.posts[sc.dids.bob][0].ref.uriStr, ].sort() - expect(recivedUris).toEqual(expected) + expect(receivedUris).toEqual(expected) }) }) diff --git a/packages/bsky/tests/views/profile.test.ts b/packages/bsky/tests/views/profile.test.ts index 36a5937c6af..a1224283794 100644 --- a/packages/bsky/tests/views/profile.test.ts +++ b/packages/bsky/tests/views/profile.test.ts @@ -27,7 +27,7 @@ describe('pds profile views', () => { sc = new SeedClient(pdsAgent) await basicSeed(sc) await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() alice = sc.dids.alice bob = sc.dids.bob dan = sc.dids.dan @@ -48,6 +48,20 @@ describe('pds profile views', () => { expect(forSnapshot(aliceForAlice.data)).toMatchSnapshot() }) + it('reflects self-labels', async () => { + const aliceForBob = await agent.api.app.bsky.actor.getProfile( + { actor: alice }, + { headers: await network.serviceHeaders(bob) }, + ) + + const labels = aliceForBob.data.labels + ?.filter((label) => label.src === alice) + .map((label) => label.val) + .sort() + + expect(labels).toEqual(['self-label-a', 'self-label-b']) + }) + it("fetches other's profile, with a follow", async () => { const aliceForBob = await agent.api.app.bsky.actor.getProfile( { actor: alice }, diff --git a/packages/bsky/tests/views/suggested-follows.test.ts b/packages/bsky/tests/views/suggested-follows.test.ts new file mode 100644 index 00000000000..6a2f3ebe1d7 --- /dev/null +++ b/packages/bsky/tests/views/suggested-follows.test.ts @@ -0,0 +1,147 @@ +import AtpAgent, { AtUri } from '@atproto/api' +import { TestNetwork } from '@atproto/dev-env' +import { SeedClient } from '../seeds/client' +import likesSeed from '../seeds/likes' + +describe('suggested follows', () => { + let network: TestNetwork + let agent: AtpAgent + let pdsAgent: AtpAgent + let sc: SeedClient + + beforeAll(async () => { + network = await TestNetwork.create({ + dbPostgresSchema: 'bsky_views_suggestions', + }) + agent = network.bsky.getClient() + pdsAgent = network.pds.getClient() + sc = new SeedClient(pdsAgent) + await likesSeed(sc) + await network.processAll() + await network.bsky.processAll() + + const suggestions = [ + { did: sc.dids.alice, order: 1 }, + { did: sc.dids.bob, order: 2 }, + { did: sc.dids.carol, order: 3 }, + { did: sc.dids.dan, order: 4 }, + { did: sc.dids.fred, order: 5 }, + { did: sc.dids.gina, order: 6 }, + ] + await network.bsky.ctx.db + .getPrimary() + .db.insertInto('suggested_follow') + .values(suggestions) + .execute() + }) + + afterAll(async () => { + await network.close() + }) + + it('returns sorted suggested follows for carol', async () => { + const result = await agent.api.app.bsky.graph.getSuggestedFollowsByActor( + { + actor: sc.dids.alice, + }, + { headers: await network.serviceHeaders(sc.dids.carol) }, + ) + + expect(result.data.suggestions.length).toBe(4) // backfilled with 2 NPCs + expect( + result.data.suggestions.find((sug) => { + return [sc.dids.alice, sc.dids.carol].includes(sug.did) + }), + ).toBeFalsy() // not actor or viewer + }) + + it('returns sorted suggested follows for fred', async () => { + const result = await agent.api.app.bsky.graph.getSuggestedFollowsByActor( + { + actor: sc.dids.alice, + }, + { headers: await network.serviceHeaders(sc.dids.fred) }, + ) + + expect(result.data.suggestions.length).toBe(4) // backfilled with 2 NPCs + expect( + result.data.suggestions.find((sug) => { + return [sc.dids.fred, sc.dids.alice].includes(sug.did) + }), + ).toBeFalsy() // not actor or viewer or followed + }) + + it('exludes users muted by viewer', async () => { + await pdsAgent.api.app.bsky.graph.muteActor( + { actor: sc.dids.bob }, + { headers: sc.getHeaders(sc.dids.carol), encoding: 'application/json' }, + ) + const result = await agent.api.app.bsky.graph.getSuggestedFollowsByActor( + { + actor: sc.dids.alice, + }, + { headers: await network.serviceHeaders(sc.dids.carol) }, + ) + + expect( + result.data.suggestions.find((sug) => { + return [sc.dids.alice, sc.dids.carol, sc.dids.bob].includes(sug.did) + }), + ).toBeFalsy() // not actor or viewer or muted + + await pdsAgent.api.app.bsky.graph.muteActor( + { actor: sc.dids.bob }, + { headers: sc.getHeaders(sc.dids.carol), encoding: 'application/json' }, + ) + }) + + it('exludes users blocked by viewer', async () => { + const carolBlocksBob = await pdsAgent.api.app.bsky.graph.block.create( + { repo: sc.dids.carol }, + { createdAt: new Date().toISOString(), subject: sc.dids.bob }, + sc.getHeaders(sc.dids.carol), + ) + const result = await agent.api.app.bsky.graph.getSuggestedFollowsByActor( + { + actor: sc.dids.alice, + }, + { headers: await network.serviceHeaders(sc.dids.carol) }, + ) + + expect( + result.data.suggestions.find((sug) => { + return [sc.dids.alice, sc.dids.carol, sc.dids.bob].includes(sug.did) + }), + ).toBeFalsy() // not actor or viewer or muted + + await pdsAgent.api.app.bsky.graph.block.delete( + { repo: sc.dids.carol, rkey: new AtUri(carolBlocksBob.uri).rkey }, + sc.getHeaders(sc.dids.carol), + ) + }) + + it('exludes users blocking viewer', async () => { + const bobBlocksCarol = await pdsAgent.api.app.bsky.graph.block.create( + { repo: sc.dids.bob }, + { createdAt: new Date().toISOString(), subject: sc.dids.carol }, + sc.getHeaders(sc.dids.bob), + ) + const result = await agent.api.app.bsky.graph.getSuggestedFollowsByActor( + { + actor: sc.dids.alice, + }, + { headers: await network.serviceHeaders(sc.dids.carol) }, + ) + + expect( + result.data.suggestions.find((sug) => { + return [sc.dids.alice, sc.dids.carol, sc.dids.bob].includes(sug.did) + }), + ).toBeFalsy() // not actor or viewer or muted + + await pdsAgent.api.app.bsky.graph.block.delete( + { repo: sc.dids.bob, rkey: new AtUri(bobBlocksCarol.uri).rkey }, + sc.getHeaders(sc.dids.bob), + ) + }) +}) diff --git a/packages/bsky/tests/views/suggestions.test.ts b/packages/bsky/tests/views/suggestions.test.ts index 85bf3fc3a4f..e69bd5e377e 100644 --- a/packages/bsky/tests/views/suggestions.test.ts +++ b/packages/bsky/tests/views/suggestions.test.ts @@ -18,15 +18,16 @@ describe('pds user search views', () => { sc = new SeedClient(pdsAgent) await basicSeed(sc) await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() const suggestions = [ { did: sc.dids.bob, order: 1 }, { did: sc.dids.carol, order: 2 }, { did: sc.dids.dan, order: 3 }, ] - await network.bsky.ctx.db.db - .insertInto('suggested_follow') + await network.bsky.ctx.db + .getPrimary() + .db.insertInto('suggested_follow') .values(suggestions) .execute() }) diff --git a/packages/bsky/tests/views/thread.test.ts b/packages/bsky/tests/views/thread.test.ts index af23c189ec4..d1c96f38603 100644 --- a/packages/bsky/tests/views/thread.test.ts +++ b/packages/bsky/tests/views/thread.test.ts @@ -1,16 +1,15 @@ import AtpAgent, { AppBskyFeedGetPostThread } from '@atproto/api' import { TestNetwork } from '@atproto/dev-env' import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/defs' -import { Database } from '../../src' import { forSnapshot, stripViewerFromThread } from '../_util' -import { RecordRef, SeedClient } from '../seeds/client' +import { SeedClient } from '../seeds/client' import basicSeed from '../seeds/basic' -import threadSeed, { walk, item, Item } from '../seeds/thread' +import assert from 'assert' +import { isThreadViewPost } from '@atproto/api/src/client/types/app/bsky/feed/defs' describe('pds thread views', () => { let network: TestNetwork let agent: AtpAgent - let db: Database let sc: SeedClient // account dids, for convenience @@ -22,7 +21,6 @@ describe('pds thread views', () => { network = await TestNetwork.create({ dbPostgresSchema: 'bsky_views_thread', }) - db = network.bsky.ctx.db agent = network.bsky.getClient() const pdsAgent = network.pds.getClient() sc = new SeedClient(pdsAgent) @@ -36,7 +34,7 @@ describe('pds thread views', () => { // Add a repost of a reply so that we can confirm myState in the thread await sc.repost(bob, sc.replies[alice][0].ref) await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() }) afterAll(async () => { @@ -120,7 +118,7 @@ describe('pds thread views', () => { ) indexes.aliceReplyReply = sc.replies[alice].length - 1 await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() const thread1 = await agent.api.app.bsky.feed.getPostThread( { uri: sc.posts[alice][indexes.aliceRoot].ref.uriStr }, @@ -130,7 +128,7 @@ describe('pds thread views', () => { await sc.deletePost(bob, sc.replies[bob][indexes.bobReply].ref.uri) await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() const thread2 = await agent.api.app.bsky.feed.getPostThread( { uri: sc.posts[alice][indexes.aliceRoot].ref.uriStr }, @@ -145,63 +143,27 @@ describe('pds thread views', () => { expect(forSnapshot(thread3.data.thread)).toMatchSnapshot() }) - it('builds post hierarchy index.', async () => { - const threads: Item[] = [ - item(1, [item(2, [item(3), item(4)])]), - item(5, [item(6), item(7, [item(9, [item(11)]), item(10)]), item(8)]), - item(12), - ] + it('reflects self-labels', async () => { + const { data: thread } = await agent.api.app.bsky.feed.getPostThread( + { uri: sc.posts[alice][0].ref.uriStr }, + { headers: await network.serviceHeaders(bob) }, + ) - await threadSeed(sc, sc.dids.alice, threads) - await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() - - let closureSize = 0 - const itemByUri: Record = {} - - const postsAndReplies = ([] as { text: string; ref: RecordRef }[]) - .concat(Object.values(sc.posts[sc.dids.alice])) - .concat(Object.values(sc.replies[sc.dids.alice])) - .filter((p) => { - const id = parseInt(p.text, 10) - return 0 < id && id <= 12 - }) - - await walk(threads, async (item, depth) => { - const post = postsAndReplies.find((p) => p.text === String(item.id)) - if (!post) throw new Error('Post not found') - itemByUri[post.ref.uriStr] = item - closureSize += depth + 1 - }) + assert(isThreadViewPost(thread.thread), 'post does not exist') + const post = thread.thread.post - const hierarchy = await db.db - .selectFrom('post_hierarchy') - .where( - 'uri', - 'in', - postsAndReplies.map((p) => p.ref.uriStr), - ) - .orWhere( - 'ancestorUri', - 'in', - postsAndReplies.map((p) => p.ref.uriStr), - ) - .selectAll() - .execute() - - expect(hierarchy.length).toEqual(closureSize) - - for (const relation of hierarchy) { - const item = itemByUri[relation.uri] - const ancestor = itemByUri[relation.ancestorUri] - let depth = -1 - await walk([ancestor], async (candidate, candidateDepth) => { - if (candidate === item) { - depth = candidateDepth - } - }) - expect(depth).toEqual(relation.depth) - } + const postSelfLabels = post.labels + ?.filter((label) => label.src === alice) + .map((label) => label.val) + + expect(postSelfLabels).toEqual(['self-label']) + + const authorSelfLabels = post.author.labels + ?.filter((label) => label.src === alice) + .map((label) => label.val) + .sort() + + expect(authorSelfLabels).toEqual(['self-label-a', 'self-label-b']) }) describe('takedown', () => { diff --git a/packages/bsky/tests/views/timeline.test.ts b/packages/bsky/tests/views/timeline.test.ts index 436684e0186..e7db746c7f3 100644 --- a/packages/bsky/tests/views/timeline.test.ts +++ b/packages/bsky/tests/views/timeline.test.ts @@ -1,3 +1,4 @@ +import assert from 'assert' import AtpAgent from '@atproto/api' import { TestNetwork } from '@atproto/dev-env' import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/defs' @@ -35,7 +36,7 @@ describe('timeline views', () => { const labelPostA = sc.posts[bob][0].ref const labelPostB = sc.posts[carol][0].ref await network.bsky.ctx.services - .label(network.bsky.ctx.db) + .label(network.bsky.ctx.db.getPrimary()) .formatAndCreate( network.bsky.ctx.cfg.labelerDid, labelPostA.uriStr, @@ -43,14 +44,14 @@ describe('timeline views', () => { { create: ['kind'] }, ) await network.bsky.ctx.services - .label(network.bsky.ctx.db) + .label(network.bsky.ctx.db.getPrimary()) .formatAndCreate( network.bsky.ctx.cfg.labelerDid, labelPostB.uriStr, labelPostB.cidStr, { create: ['kind'] }, ) - await network.bsky.ctx.backgroundQueue.processAll() + await network.bsky.processAll() }) afterAll(async () => { @@ -156,6 +157,32 @@ describe('timeline views', () => { expect(results(paginatedAll)).toEqual(results([full.data])) }) + it('reflects self-labels', async () => { + const carolTL = await agent.api.app.bsky.feed.getTimeline( + {}, + { headers: await network.serviceHeaders(carol) }, + ) + + const alicePost = carolTL.data.feed.find( + ({ post }) => post.uri === sc.posts[alice][0].ref.uriStr, + )?.post + + assert(alicePost, 'post does not exist') + + const postSelfLabels = alicePost.labels + ?.filter((label) => label.src === alice) + .map((label) => label.val) + + expect(postSelfLabels).toEqual(['self-label']) + + const authorSelfLabels = alicePost.author.labels + ?.filter((label) => label.src === alice) + .map((label) => label.val) + .sort() + + expect(authorSelfLabels).toEqual(['self-label-a', 'self-label-b']) + }) + it('blocks posts, reposts, replies by actor takedown', async () => { const actionResults = await Promise.all( [bob, carol].map((did) => diff --git a/packages/bsky/update-pkg.js b/packages/bsky/update-pkg.js deleted file mode 100644 index 15d70bdab33..00000000000 --- a/packages/bsky/update-pkg.js +++ /dev/null @@ -1,14 +0,0 @@ -const pkgJson = require('@npmcli/package-json') - -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} -if (process.argv.includes('--update-main-to-src')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'src/index.ts' })) - .then((pkg) => pkg.save()) -} diff --git a/packages/common-web/build.js b/packages/common-web/build.js index 5570ef4ee72..30fbe7cea56 100644 --- a/packages/common-web/build.js +++ b/packages/common-web/build.js @@ -1,16 +1,8 @@ -const pkgJson = require('@npmcli/package-json') const { nodeExternalsPlugin } = require('esbuild-node-externals') const buildShallow = process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true' -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} - require('esbuild').build({ logLevel: 'info', entryPoints: ['src/index.ts'], diff --git a/packages/common-web/package.json b/packages/common-web/package.json index fbbe4755c7a..92387c502ab 100644 --- a/packages/common-web/package.json +++ b/packages/common-web/package.json @@ -1,7 +1,11 @@ { "name": "@atproto/common-web", - "version": "0.1.0", + "version": "0.2.0", "main": "src/index.ts", + "publishConfig": { + "main": "dist/index.js", + "types": "dist/index.d.ts" + }, "license": "MIT", "repository": { "type": "git", @@ -10,22 +14,13 @@ }, "scripts": { "test": "jest", - "prettier": "prettier --check src/ tests/", - "prettier:fix": "prettier --write src/ tests/", - "lint": "eslint . --ext .ts,.tsx", - "lint:fix": "yarn lint --fix", - "verify": "run-p prettier lint", - "verify:fix": "yarn prettier:fix && yarn lint:fix", "build": "node ./build.js", "postbuild": "tsc --build tsconfig.build.json", - "update-main-to-dist": "node ./update-pkg.js --update-main-to-dist", - "update-main-to-src": "node ./update-pkg.js --update-main-to-src", - "prepublish": "npm run update-main-to-dist", - "postpublish": "npm run update-main-to-src" + "update-main-to-dist": "node ../../update-main-to-dist.js packages/common-web" }, "dependencies": { "graphemer": "^1.4.0", - "multiformats": "^9.6.4", + "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.21.4" } diff --git a/packages/common-web/src/arrays.ts b/packages/common-web/src/arrays.ts new file mode 100644 index 00000000000..51598fc86f1 --- /dev/null +++ b/packages/common-web/src/arrays.ts @@ -0,0 +1,13 @@ +export const mapDefined = ( + arr: T[], + fn: (obj: T) => S | undefined, +): S[] => { + const output: S[] = [] + for (const item of arr) { + const val = fn(item) + if (val !== undefined) { + output.push(val) + } + } + return output +} diff --git a/packages/common-web/src/index.ts b/packages/common-web/src/index.ts index 8be988e7035..e125677496f 100644 --- a/packages/common-web/src/index.ts +++ b/packages/common-web/src/index.ts @@ -1,6 +1,7 @@ export * as check from './check' export * as util from './util' +export * from './arrays' export * from './async' export * from './util' export * from './tid' diff --git a/packages/common-web/src/tid.ts b/packages/common-web/src/tid.ts index 21d00b67624..d802956854e 100644 --- a/packages/common-web/src/tid.ts +++ b/packages/common-web/src/tid.ts @@ -21,7 +21,7 @@ export class TID { this.str = noDashes } - static next(): TID { + static next(prev?: TID): TID { // javascript does not have microsecond precision // instead, we append a counter to the timestamp to indicate if multiple timestamps were created within the same millisecond // take max of current time & last timestamp to prevent tids moving backwards if system clock drifts backwards @@ -36,11 +36,15 @@ export class TID { if (clockid === null) { clockid = Math.floor(Math.random() * 32) } - return TID.fromTime(timestamp, clockid) + const tid = TID.fromTime(timestamp, clockid) + if (!prev || tid.newerThan(prev)) { + return tid + } + return TID.fromTime(prev.timestamp() + 1, clockid) } - static nextStr(): string { - return TID.next().toString() + static nextStr(prev?: string): string { + return TID.next(prev ? new TID(prev) : undefined).toString() } static fromTime(timestamp: number, clockid: number): TID { diff --git a/packages/common-web/src/util.ts b/packages/common-web/src/util.ts index 5777a83f961..04fd227af55 100644 --- a/packages/common-web/src/util.ts +++ b/packages/common-web/src/util.ts @@ -17,9 +17,12 @@ export const wait = (ms: number) => { return new Promise((res) => setTimeout(res, ms)) } -export const bailableWait = ( - ms: number, -): { bail: () => void; wait: () => Promise } => { +export type BailableWait = { + bail: () => void + wait: () => Promise +} + +export const bailableWait = (ms: number): BailableWait => { let bail const waitPromise = new Promise((res) => { const timeout = setTimeout(res, ms) diff --git a/packages/common-web/tests/tid.test.ts b/packages/common-web/tests/tid.test.ts index 00dac05b0ec..cc3cc7c0c8b 100644 --- a/packages/common-web/tests/tid.test.ts +++ b/packages/common-web/tests/tid.test.ts @@ -26,6 +26,12 @@ describe('TIDs', () => { expect(typeof str).toEqual('string') expect(str.length).toEqual(13) }) + + it('returns a next tid larger than a provided prev', () => { + const prev = TID.fromTime((Date.now() + 5000) * 1000, 0).toString() + const str = TID.nextStr(prev) + expect(str > prev).toBe(true) + }) }) describe('newestFirst', () => { diff --git a/packages/common-web/tsconfig.build.json b/packages/common-web/tsconfig.build.json index 27df65b89e2..02a84823b65 100644 --- a/packages/common-web/tsconfig.build.json +++ b/packages/common-web/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", "exclude": ["**/*.spec.ts", "**/*.test.ts"] -} \ No newline at end of file +} diff --git a/packages/common-web/tsconfig.json b/packages/common-web/tsconfig.json index c50c08b2e80..5c5ec40ce03 100644 --- a/packages/common-web/tsconfig.json +++ b/packages/common-web/tsconfig.json @@ -5,5 +5,5 @@ "outDir": "./dist", // Your outDir, "emitDeclarationOnly": true }, - "include": ["./src","__tests__/**/**.ts"], -} \ No newline at end of file + "include": ["./src", "__tests__/**/**.ts"] +} diff --git a/packages/common-web/update-pkg.js b/packages/common-web/update-pkg.js deleted file mode 100644 index 15d70bdab33..00000000000 --- a/packages/common-web/update-pkg.js +++ /dev/null @@ -1,14 +0,0 @@ -const pkgJson = require('@npmcli/package-json') - -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} -if (process.argv.includes('--update-main-to-src')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'src/index.ts' })) - .then((pkg) => pkg.save()) -} diff --git a/packages/common/build.js b/packages/common/build.js index 5628aa4f4eb..e880ae9930b 100644 --- a/packages/common/build.js +++ b/packages/common/build.js @@ -1,16 +1,8 @@ -const pkgJson = require('@npmcli/package-json') const { nodeExternalsPlugin } = require('esbuild-node-externals') const buildShallow = process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true' -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} - require('esbuild').build({ logLevel: 'info', entryPoints: ['src/index.ts'], diff --git a/packages/common/package.json b/packages/common/package.json index 580efdb2f55..c7f518c1a99 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,7 +1,11 @@ { "name": "@atproto/common", - "version": "0.2.0", + "version": "0.3.0", "main": "src/index.ts", + "publishConfig": { + "main": "dist/index.js", + "types": "dist/index.d.ts" + }, "license": "MIT", "repository": { "type": "git", @@ -10,24 +14,16 @@ }, "scripts": { "test": "jest", - "prettier": "prettier --check src/ tests/", - "prettier:fix": "prettier --write src/ tests/", - "lint": "eslint . --ext .ts,.tsx", - "lint:fix": "yarn lint --fix", - "verify": "run-p prettier lint", - "verify:fix": "yarn prettier:fix && yarn lint:fix", "build": "node ./build.js", "postbuild": "tsc --build tsconfig.build.json", - "update-main-to-dist": "node ./update-pkg.js --update-main-to-dist", - "update-main-to-src": "node ./update-pkg.js --update-main-to-src", - "prepublish": "npm run update-main-to-dist", - "postpublish": "npm run update-main-to-src" + "update-main-to-dist": "node ../../update-main-to-dist.js packages/common" }, "dependencies": { - "@atproto/common-web": "*", + "@atproto/common-web": "workspace:^", "@ipld/dag-cbor": "^7.0.3", "cbor-x": "^1.5.1", - "multiformats": "^9.6.4", - "pino": "^8.6.1" + "multiformats": "^9.9.0", + "pino": "^8.15.0", + "zod": "3.21.4" } } diff --git a/packages/common/src/buffers.ts b/packages/common/src/buffers.ts new file mode 100644 index 00000000000..8f03ea29845 --- /dev/null +++ b/packages/common/src/buffers.ts @@ -0,0 +1,10 @@ +export function ui8ToBuffer(bytes: Uint8Array): Buffer { + return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength) +} + +export function ui8ToArrayBuffer(bytes: Uint8Array): ArrayBuffer { + return bytes.buffer.slice( + bytes.byteOffset, + bytes.byteLength + bytes.byteOffset, + ) +} diff --git a/packages/common/src/fs.ts b/packages/common/src/fs.ts index a0af96a4e1c..db7c586b621 100644 --- a/packages/common/src/fs.ts +++ b/packages/common/src/fs.ts @@ -1,9 +1,10 @@ import { isErrnoException } from '@atproto/common-web' +import { constants } from 'fs' import fs from 'fs/promises' export const fileExists = async (location: string): Promise => { try { - await fs.access(location, fs.constants.F_OK) + await fs.access(location, constants.F_OK) return true } catch (err) { if (isErrnoException(err) && err.code === 'ENOENT') { diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 8fd068c8602..fd049fc2e5d 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -4,3 +4,4 @@ export * from './ipld' export * from './ipld-multi' export * from './logger' export * from './streams' +export * from './buffers' diff --git a/packages/common/src/ipld.ts b/packages/common/src/ipld.ts index bea8e5f4e81..6939afe1049 100644 --- a/packages/common/src/ipld.ts +++ b/packages/common/src/ipld.ts @@ -24,6 +24,15 @@ export const cidForCbor = async (data: unknown): Promise => { return block.cid } +export const isValidCid = async (cidStr: string): Promise => { + try { + const parsed = CID.parse(cidStr) + return parsed.toString() === cidStr + } catch (err) { + return false + } +} + export const cborBytesToRecord = ( bytes: Uint8Array, ): Record => { diff --git a/packages/common/tsconfig.build.json b/packages/common/tsconfig.build.json index 27df65b89e2..02a84823b65 100644 --- a/packages/common/tsconfig.build.json +++ b/packages/common/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", "exclude": ["**/*.spec.ts", "**/*.test.ts"] -} \ No newline at end of file +} diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json index c837279cd68..81714d5ec97 100644 --- a/packages/common/tsconfig.json +++ b/packages/common/tsconfig.json @@ -5,8 +5,6 @@ "outDir": "./dist", // Your outDir, "emitDeclarationOnly": true }, - "include": ["./src","__tests__/**/**.ts"], - "references": [ - { "path": "../common-web/tsconfig.build.json" } - ] -} \ No newline at end of file + "include": ["./src", "__tests__/**/**.ts"], + "references": [{ "path": "../common-web/tsconfig.build.json" }] +} diff --git a/packages/common/update-pkg.js b/packages/common/update-pkg.js deleted file mode 100644 index 15d70bdab33..00000000000 --- a/packages/common/update-pkg.js +++ /dev/null @@ -1,14 +0,0 @@ -const pkgJson = require('@npmcli/package-json') - -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} -if (process.argv.includes('--update-main-to-src')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'src/index.ts' })) - .then((pkg) => pkg.save()) -} diff --git a/packages/crypto/README.md b/packages/crypto/README.md index 062eb95ec2b..2258451f7ca 100644 --- a/packages/crypto/README.md +++ b/packages/crypto/README.md @@ -1,3 +1,3 @@ # ATP Crypto Library -ATP's common cryptographic operations. \ No newline at end of file +ATP's common cryptographic operations. diff --git a/packages/crypto/build.js b/packages/crypto/build.js index 5628aa4f4eb..e880ae9930b 100644 --- a/packages/crypto/build.js +++ b/packages/crypto/build.js @@ -1,16 +1,8 @@ -const pkgJson = require('@npmcli/package-json') const { nodeExternalsPlugin } = require('esbuild-node-externals') const buildShallow = process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true' -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} - require('esbuild').build({ logLevel: 'info', entryPoints: ['src/index.ts'], diff --git a/packages/crypto/jest.config.js b/packages/crypto/jest.config.js index 8b8fc38538f..365131c9293 100644 --- a/packages/crypto/jest.config.js +++ b/packages/crypto/jest.config.js @@ -2,5 +2,5 @@ const base = require('../../jest.config.base.js') module.exports = { ...base, - displayName: 'Crypto' + displayName: 'Crypto', } diff --git a/packages/crypto/package.json b/packages/crypto/package.json index f478ab6aa45..c1d7fa5b521 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -1,7 +1,11 @@ { "name": "@atproto/crypto", - "version": "0.1.1", + "version": "0.2.2", "main": "src/index.ts", + "publishConfig": { + "main": "dist/index.js", + "types": "dist/index.d.ts" + }, "license": "MIT", "repository": { "type": "git", @@ -10,18 +14,9 @@ }, "scripts": { "test": "jest ", - "prettier": "prettier --check src/ tests/", - "prettier:fix": "prettier --write src/ tests/", - "lint": "eslint . --ext .ts,.tsx", - "lint:fix": "yarn lint --fix", - "verify": "run-p prettier lint", - "verify:fix": "yarn prettier:fix && yarn lint:fix", "build": "node ./build.js", "postbuild": "tsc --build tsconfig.build.json", - "update-main-to-dist": "node ./update-pkg.js --update-main-to-dist", - "update-main-to-src": "node ./update-pkg.js --update-main-to-src", - "prepublish": "npm run update-main-to-dist", - "postpublish": "npm run update-main-to-src" + "update-main-to-dist": "node ../../update-main-to-dist.js packages/crypto" }, "dependencies": { "@noble/curves": "^1.1.0", diff --git a/packages/crypto/src/const.ts b/packages/crypto/src/const.ts index 6f09574ac8a..7fc1e0fcf43 100644 --- a/packages/crypto/src/const.ts +++ b/packages/crypto/src/const.ts @@ -1,6 +1,7 @@ export const P256_DID_PREFIX = new Uint8Array([0x80, 0x24]) export const SECP256K1_DID_PREFIX = new Uint8Array([0xe7, 0x01]) -export const BASE58_DID_PREFIX = 'did:key:z' // z is the multibase prefix for base58btc byte encoding +export const BASE58_MULTIBASE_PREFIX = 'z' +export const DID_KEY_PREFIX = 'did:key:' export const P256_JWT_ALG = 'ES256' export const SECP256K1_JWT_ALG = 'ES256K' diff --git a/packages/crypto/src/did.ts b/packages/crypto/src/did.ts index d95abdc734e..7ced78a394d 100644 --- a/packages/crypto/src/did.ts +++ b/packages/crypto/src/did.ts @@ -2,19 +2,24 @@ import * as uint8arrays from 'uint8arrays' import * as p256 from './p256/encoding' import * as secp from './secp256k1/encoding' import plugins from './plugins' -import { P256_JWT_ALG, SECP256K1_JWT_ALG, BASE58_DID_PREFIX } from './const' +import { + BASE58_MULTIBASE_PREFIX, + DID_KEY_PREFIX, + P256_JWT_ALG, + SECP256K1_JWT_ALG, +} from './const' -export type ParsedDidKey = { +export type ParsedMultikey = { jwtAlg: string keyBytes: Uint8Array } -export const parseDidKey = (did: string): ParsedDidKey => { - if (!did.startsWith(BASE58_DID_PREFIX)) { - throw new Error(`Incorrect prefix for did:key: ${did}`) +export const parseMultikey = (multikey: string): ParsedMultikey => { + if (!multikey.startsWith(BASE58_MULTIBASE_PREFIX)) { + throw new Error(`Incorrect prefix for multikey: ${multikey}`) } const prefixedBytes = uint8arrays.fromString( - did.slice(BASE58_DID_PREFIX.length), + multikey.slice(BASE58_MULTIBASE_PREFIX.length), 'base58btc', ) const plugin = plugins.find((p) => hasPrefix(prefixedBytes, p.prefix)) @@ -33,7 +38,10 @@ export const parseDidKey = (did: string): ParsedDidKey => { } } -export const formatDidKey = (jwtAlg: string, keyBytes: Uint8Array): string => { +export const formatMultikey = ( + jwtAlg: string, + keyBytes: Uint8Array, +): string => { const plugin = plugins.find((p) => p.jwtAlg === jwtAlg) if (!plugin) { throw new Error('Unsupported key type') @@ -44,7 +52,20 @@ export const formatDidKey = (jwtAlg: string, keyBytes: Uint8Array): string => { keyBytes = secp.compressPubkey(keyBytes) } const prefixedBytes = uint8arrays.concat([plugin.prefix, keyBytes]) - return BASE58_DID_PREFIX + uint8arrays.toString(prefixedBytes, 'base58btc') + return ( + BASE58_MULTIBASE_PREFIX + uint8arrays.toString(prefixedBytes, 'base58btc') + ) +} + +export const parseDidKey = (did: string): ParsedMultikey => { + if (!did.startsWith(DID_KEY_PREFIX)) { + throw new Error(`Incorrect prefix for did:key: ${did}`) + } + return parseMultikey(did.slice(DID_KEY_PREFIX.length)) +} + +export const formatDidKey = (jwtAlg: string, keyBytes: Uint8Array): string => { + return DID_KEY_PREFIX + formatMultikey(jwtAlg, keyBytes) } const hasPrefix = (bytes: Uint8Array, prefix: Uint8Array): boolean => { diff --git a/packages/crypto/src/random.ts b/packages/crypto/src/random.ts index e81682fb81d..a5c9bb460eb 100644 --- a/packages/crypto/src/random.ts +++ b/packages/crypto/src/random.ts @@ -1,6 +1,7 @@ import * as noble from '@noble/hashes/utils' import * as uint8arrays from 'uint8arrays' import { SupportedEncodings } from 'uint8arrays/to-string' +import { sha256 } from './sha' export const randomBytes = noble.randomBytes @@ -11,3 +12,15 @@ export const randomStr = ( const bytes = randomBytes(byteLength) return uint8arrays.toString(bytes, encoding) } + +export const randomIntFromSeed = async ( + seed: string, + high: number, + low = 0, +): Promise => { + const hash = await sha256(seed) + const number = Buffer.from(hash).readUintBE(0, 6) + const range = high - low + const normalized = number % range + return normalized + low +} diff --git a/packages/crypto/tests/random.test.ts b/packages/crypto/tests/random.test.ts new file mode 100644 index 00000000000..349bf0c2f27 --- /dev/null +++ b/packages/crypto/tests/random.test.ts @@ -0,0 +1,15 @@ +import { randomIntFromSeed } from '../src' + +describe('randomIntFromSeed()', () => { + it('has good distribution for low bucket count.', async () => { + const counts: [zero: number, one: number] = [0, 0] + const salt = Math.random() + for (let i = 0; i < 10000; ++i) { + const int = await randomIntFromSeed(`${i}${salt}`, 2) + counts[int]++ + } + const [zero, one] = counts + expect(zero + one).toEqual(10000) + expect(Math.max(zero, one) / Math.min(zero, one)).toBeLessThan(1.05) + }) +}) diff --git a/packages/crypto/tests/signature-fixtures.json b/packages/crypto/tests/signature-fixtures.json deleted file mode 100644 index 44102239bb3..00000000000 --- a/packages/crypto/tests/signature-fixtures.json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "messageBase64": "oWVoZWxsb2V3b3JsZA", - "algorithm": "ES256", - "publicKeyDid": "did:key:zDnaembgSGUhZULN2Caob4HLJPaxBh92N7rtH21TErzqf8HQo", - "publicKeyMultibase": "zxdM8dSstjrpZaRUwBmDvjGXweKuEMVN95A9oJBFjkWMh", - "signatureBase64": "2vZNsG3UKvvO/CDlrdvyZRISOFylinBh0Jupc6KcWoJWExHptCfduPleDbG3rko3YZnn9Lw0IjpixVmexJDegg", - "validSignature": true - }, - { - "messageBase64": "oWVoZWxsb2V3b3JsZA", - "algorithm": "ES256K", - "publicKeyDid": "did:key:zQ3shqwJEJyMBsBXCWyCBpUBMqxcon9oHB7mCvx4sSpMdLJwc", - "publicKeyMultibase": "z25z9DTpsiYYJKGsWmSPJK2NFN8PcJtZig12K59UgW7q5t", - "signatureBase64": "5WpdIuEUUfVUYaozsi8G0B3cWO09cgZbIIwg1t2YKdUn/FEznOndsz/qgiYb89zwxYCbB71f7yQK5Lr7NasfoA", - "validSignature": true - }, - { - "messageBase64": "oWVoZWxsb2V3b3JsZA", - "algorithm": "ES256", - "publicKeyDid": "did:key:zDnaembgSGUhZULN2Caob4HLJPaxBh92N7rtH21TErzqf8HQo", - "publicKeyMultibase": "zxdM8dSstjrpZaRUwBmDvjGXweKuEMVN95A9oJBFjkWMh", - "signatureBase64": "2vZNsG3UKvvO/CDlrdvyZRISOFylinBh0Jupc6KcWoKp7O4VS9giSAah8k5IUbXIW00SuOrjfEqQ9HEkN9JGzw", - "validSignature": false - }, - { - "messageBase64": "oWVoZWxsb2V3b3JsZA", - "algorithm": "ES256K", - "publicKeyDid": "did:key:zQ3shqwJEJyMBsBXCWyCBpUBMqxcon9oHB7mCvx4sSpMdLJwc", - "publicKeyMultibase": "z25z9DTpsiYYJKGsWmSPJK2NFN8PcJtZig12K59UgW7q5t", - "signatureBase64": "5WpdIuEUUfVUYaozsi8G0B3cWO09cgZbIIwg1t2YKdXYA67MYxYiTMAVfdnkDCMN9S5B3vHosRe07aORmoshoQ", - "validSignature": false - } -] diff --git a/packages/crypto/tests/signature-fixtures.json b/packages/crypto/tests/signature-fixtures.json new file mode 120000 index 00000000000..b8aa5efefdd --- /dev/null +++ b/packages/crypto/tests/signature-fixtures.json @@ -0,0 +1 @@ +../../../interop-test-files/crypto/signature-fixtures.json \ No newline at end of file diff --git a/packages/crypto/tsconfig.build.json b/packages/crypto/tsconfig.build.json index 27df65b89e2..02a84823b65 100644 --- a/packages/crypto/tsconfig.build.json +++ b/packages/crypto/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", "exclude": ["**/*.spec.ts", "**/*.test.ts"] -} \ No newline at end of file +} diff --git a/packages/crypto/tsconfig.json b/packages/crypto/tsconfig.json index afddb2bd4e1..5c5ec40ce03 100644 --- a/packages/crypto/tsconfig.json +++ b/packages/crypto/tsconfig.json @@ -5,5 +5,5 @@ "outDir": "./dist", // Your outDir, "emitDeclarationOnly": true }, - "include": ["./src","__tests__/**/**.ts"] -} \ No newline at end of file + "include": ["./src", "__tests__/**/**.ts"] +} diff --git a/packages/crypto/update-pkg.js b/packages/crypto/update-pkg.js deleted file mode 100644 index 15d70bdab33..00000000000 --- a/packages/crypto/update-pkg.js +++ /dev/null @@ -1,14 +0,0 @@ -const pkgJson = require('@npmcli/package-json') - -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} -if (process.argv.includes('--update-main-to-src')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'src/index.ts' })) - .then((pkg) => pkg.save()) -} diff --git a/packages/dev-env/README.md b/packages/dev-env/README.md index 442f7110eb1..c1a41e692fe 100644 --- a/packages/dev-env/README.md +++ b/packages/dev-env/README.md @@ -24,4 +24,4 @@ Create a new user. ### `user(handle: string): ServiceClient` -Get the `ServiceClient` for the given user. \ No newline at end of file +Get the `ServiceClient` for the given user. diff --git a/packages/dev-env/build.js b/packages/dev-env/build.js index fd0a02aa468..98bb2df6133 100644 --- a/packages/dev-env/build.js +++ b/packages/dev-env/build.js @@ -1,20 +1,12 @@ -const pkgJson = require('@npmcli/package-json') const { copy } = require('esbuild-plugin-copy') const { nodeExternalsPlugin } = require('esbuild-node-externals') const buildShallow = process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true' -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} - require('esbuild').build({ logLevel: 'info', - entryPoints: ['src/index.ts', 'src/bin.ts'], + entryPoints: ['src/index.ts', 'src/bin.ts', 'src/bin-network.ts'], bundle: true, sourcemap: true, outdir: 'dist', @@ -34,4 +26,4 @@ require('esbuild').build({ }, }), ]), -}) \ No newline at end of file +}) diff --git a/packages/dev-env/package.json b/packages/dev-env/package.json index 205e62a7181..adc1a4e21c8 100644 --- a/packages/dev-env/package.json +++ b/packages/dev-env/package.json @@ -1,7 +1,11 @@ { "name": "@atproto/dev-env", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", + "publishConfig": { + "main": "dist/index.js", + "types": "dist/index.d.ts" + }, "bin": "dist/bin.js", "license": "MIT", "repository": { @@ -12,31 +16,25 @@ "scripts": { "build": "node ./build.js", "postbuild": "tsc --build tsconfig.build.json", + "update-main-to-dist": "node ../../update-main-to-dist.js packages/dev-env", "start": "node dist/bin.js", - "prettier": "prettier --check src/", - "prettier:fix": "prettier --write src/", - "lint": "eslint . --ext .ts,.tsx", - "lint:fix": "yarn lint --fix", - "verify": "run-p prettier lint", - "verify:fix": "yarn prettier:fix && yarn lint:fix", - "update-main-to-dist": "node ./update-pkg.js --update-main-to-dist", - "update-main-to-src": "node ./update-pkg.js --update-main-to-src", - "prepublish": "npm run update-main-to-dist", - "postpublish": "npm run update-main-to-src" + "start:network": "../dev-infra/with-test-redis-and-db.sh node dist/bin-network.js" }, "dependencies": { - "@atproto/api": "*", - "@atproto/bsky": "*", - "@atproto/crypto": "*", - "@atproto/identity": "*", - "@atproto/pds": "* | >=0.2.0-beta.0", - "@atproto/uri": "*", - "@atproto/xrpc-server": "*", + "@atproto/api": "workspace:^", + "@atproto/bsky": "workspace:^", + "@atproto/common-web": "workspace:^", + "@atproto/crypto": "workspace:^", + "@atproto/identity": "workspace:^", + "@atproto/pds": "workspace:^", + "@atproto/syntax": "workspace:^", + "@atproto/xrpc-server": "workspace:^", "@did-plc/lib": "^0.0.1", "@did-plc/server": "^0.0.1", "better-sqlite3": "^7.6.2", "chalk": "^5.0.1", "dotenv": "^16.0.1", + "express": "^4.18.2", "get-port": "^6.1.2", "sharp": "^0.31.2", "uint8arrays": "3.0.0" diff --git a/packages/dev-env/src/bin-network.ts b/packages/dev-env/src/bin-network.ts new file mode 100644 index 00000000000..605c6b680bd --- /dev/null +++ b/packages/dev-env/src/bin-network.ts @@ -0,0 +1,77 @@ +import { generateMockSetup } from './mock' +import { TestNetwork } from './network' + +const run = async () => { + console.log(` +██████╗ +██╔═══██╗ +██║██╗██║ +██║██║██║ +╚█║████╔╝ + ╚╝╚═══╝ protocol + +[ created by Bluesky ]`) + + const network = await TestNetwork.create({ + pds: { + port: 2583, + enableLabelsCache: true, + dbPostgresSchema: 'pds', + }, + bsky: { + dbPostgresSchema: 'bsky', + }, + plc: { port: 2582 }, + }) + await enableProxy(network) + await generateMockSetup(network) + + console.log( + `👤 DID Placeholder server started http://localhost:${network.plc.port}`, + ) + console.log( + `🌞 Personal Data server started http://localhost:${network.pds.port}`, + ) + console.log(`🌅 Bsky Appview started http://localhost:${network.bsky.port}`) + for (const fg of network.feedGens) { + console.log(`🤖 Feed Generator started http://localhost:${fg.port}`) + } +} + +run() + +// @TODO remove once we remove proxy runtime flags +const enableProxy = async (network: TestNetwork) => { + const flags = [ + 'appview-proxy:app.bsky.feed.getAuthorFeed', + 'appview-proxy:app.bsky.graph.getFollowers', + 'appview-proxy:app.bsky.feed.getPosts', + 'appview-proxy:app.bsky.graph.getFollows', + 'appview-proxy:app.bsky.feed.getLikes', + 'appview-proxy:app.bsky.feed.getRepostedBy', + 'appview-proxy:app.bsky.feed.getPostThread', + 'appview-proxy:app.bsky.actor.getProfile', + 'appview-proxy:app.bsky.actor.getProfiles', + 'appview-proxy:app.bsky.feed.getTimeline', + 'appview-proxy:app.bsky.feed.getSuggestions', + 'appview-proxy:app.bsky.feed.getFeed', + 'appview-proxy:app.bsky.feed.getActorFeeds', + 'appview-proxy:app.bsky.feed.getActorLikes', + 'appview-proxy:app.bsky.feed.getFeedGenerator', + 'appview-proxy:app.bsky.feed.getFeedGenerators', + 'appview-proxy:app.bsky.feed.getBlocks', + 'appview-proxy:app.bsky.feed.getList', + 'appview-proxy:app.bsky.notification.listNotifications', + 'appview-proxy:app.bsky.feed.getLists', + 'appview-proxy:app.bsky.feed.getListMutes', + 'appview-proxy:com.atproto.repo.getRecord', + 'appview-proxy:com.atproto.identity.resolveHandle', + 'appview-proxy:app.bsky.notification.getUnreadCount', + 'appview-proxy:app.bsky.actor.searchActorsTypeahead', + 'appview-proxy:app.bsky.actor.searchActors', + ] + await network.pds.ctx.db.db + .insertInto('runtime_flag') + .values(flags.map((name) => ({ name, value: '10' }))) + .execute() +} diff --git a/packages/dev-env/src/bin.ts b/packages/dev-env/src/bin.ts index b9581a2cf5b..0aa81afb668 100644 --- a/packages/dev-env/src/bin.ts +++ b/packages/dev-env/src/bin.ts @@ -13,7 +13,10 @@ const run = async () => { [ created by Bluesky ]`) const network = await TestNetworkNoAppView.create({ - pds: { port: 2583, hostname: 'localhost' }, + pds: { + port: 2583, + hostname: 'localhost', + }, plc: { port: 2582 }, }) await generateMockSetup(network) diff --git a/packages/dev-env/src/bsky.ts b/packages/dev-env/src/bsky.ts index 75206fe768c..a99385b755b 100644 --- a/packages/dev-env/src/bsky.ts +++ b/packages/dev-env/src/bsky.ts @@ -1,17 +1,22 @@ +import assert from 'assert' import getPort from 'get-port' +import * as ui8 from 'uint8arrays' import * as bsky from '@atproto/bsky' -import { DAY, HOUR } from '@atproto/common-web' +import { DAY, HOUR, wait } from '@atproto/common-web' import { AtpAgent } from '@atproto/api' -import { Secp256k1Keypair } from '@atproto/crypto' +import { Secp256k1Keypair, randomIntFromSeed } from '@atproto/crypto' import { Client as PlcClient } from '@did-plc/lib' import { BskyConfig } from './types' import { uniqueLockId } from './util' +import { TestNetworkNoAppView } from './network-no-appview' export class TestBsky { constructor( public url: string, public port: number, public server: bsky.BskyAppView, + public indexer: bsky.BskyIndexer, + public ingester: bsky.BskyIngester, ) {} static async create(cfg: BskyConfig): Promise { @@ -34,28 +39,30 @@ export class TestBsky { didPlcUrl: cfg.plcUrl, publicUrl: 'https://bsky.public.url', serverDid, - imgUriSalt: '9dd04221f5755bce5f55f47464c27e1e', - imgUriKey: - 'f23ecd142835025f42c3db2cf25dd813956c178392760256211f9d315f8ab4d8', didCacheStaleTTL: HOUR, didCacheMaxTTL: DAY, ...cfg, // Each test suite gets its own lock id for the repo subscription - repoSubLockId: uniqueLockId(), adminPassword: 'admin-pass', + moderatorPassword: 'moderator-pass', + triagePassword: 'triage-pass', labelerDid: 'did:example:labeler', - labelerKeywords: { label_me: 'test-label', label_me_2: 'test-label-2' }, feedGenDid: 'did:example:feedGen', }) - const db = bsky.Database.postgres({ - url: cfg.dbPostgresUrl, + // shared across server, ingester, and indexer in order to share pool, avoid too many pg connections. + const db = new bsky.DatabaseCoordinator({ schema: cfg.dbPostgresSchema, + primary: { + url: cfg.dbPrimaryPostgresUrl, + poolSize: 10, + }, + replicas: [], }) // Separate migration db in case migration changes some connection state that we need in the tests, e.g. "alter database ... set ..." - const migrationDb = bsky.Database.postgres({ - url: cfg.dbPostgresUrl, + const migrationDb = new bsky.PrimaryDatabase({ + url: cfg.dbPrimaryPostgresUrl, schema: cfg.dbPostgresSchema, }) if (cfg.migration) { @@ -65,10 +72,79 @@ export class TestBsky { } await migrationDb.close() - const server = bsky.BskyAppView.create({ db, config, algos: cfg.algos }) + // api server + const server = bsky.BskyAppView.create({ + db, + config, + algos: cfg.algos, + imgInvalidator: cfg.imgInvalidator, + }) + // indexer + const ns = cfg.dbPostgresSchema + ? await randomIntFromSeed(cfg.dbPostgresSchema, 10000) + : undefined + const indexerCfg = new bsky.IndexerConfig({ + version: '0.0.0', + didCacheStaleTTL: HOUR, + didCacheMaxTTL: DAY, + labelerDid: 'did:example:labeler', + redisHost: cfg.redisHost, + dbPostgresUrl: cfg.dbPrimaryPostgresUrl, + dbPostgresSchema: cfg.dbPostgresSchema, + didPlcUrl: cfg.plcUrl, + labelerKeywords: { label_me: 'test-label', label_me_2: 'test-label-2' }, + abyssEndpoint: '', + abyssPassword: '', + imgUriEndpoint: 'img.example.com', + moderationPushUrl: `http://admin:${config.adminPassword}@localhost:${cfg.pdsPort}`, + indexerPartitionIds: [0], + indexerNamespace: `ns${ns}`, + indexerSubLockId: uniqueLockId(), + indexerPort: await getPort(), + ingesterPartitionCount: 1, + pushNotificationEndpoint: 'https://push.bsky.app/api/push', + ...(cfg.indexer ?? {}), + }) + assert(indexerCfg.redisHost) + const indexerRedis = new bsky.Redis({ + host: indexerCfg.redisHost, + namespace: indexerCfg.indexerNamespace, + }) + const indexer = bsky.BskyIndexer.create({ + cfg: indexerCfg, + db: db.getPrimary(), + redis: indexerRedis, + imgInvalidator: cfg.imgInvalidator, + }) + // ingester + const ingesterCfg = new bsky.IngesterConfig({ + version: '0.0.0', + redisHost: cfg.redisHost, + dbPostgresUrl: cfg.dbPrimaryPostgresUrl, + dbPostgresSchema: cfg.dbPostgresSchema, + repoProvider: cfg.repoProvider, + ingesterNamespace: `ns${ns}`, + ingesterSubLockId: uniqueLockId(), + ingesterPartitionCount: 1, + ...(cfg.ingester ?? {}), + }) + assert(ingesterCfg.redisHost) + const ingesterRedis = new bsky.Redis({ + host: ingesterCfg.redisHost, + namespace: ingesterCfg.ingesterNamespace, + }) + const ingester = bsky.BskyIngester.create({ + cfg: ingesterCfg, + db: db.getPrimary(), + redis: ingesterRedis, + }) + await ingester.start() + await indexer.start() await server.start() - return new TestBsky(url, port, server) + // we refresh label cache by hand in `processAll` instead of on a timer + server.ctx.labelCache.stop() + return new TestBsky(url, port, server, indexer, ingester) } get ctx(): bsky.AppContext { @@ -76,17 +152,197 @@ export class TestBsky { } get sub() { - if (!this.server.sub) { - throw new Error('No subscription on dev-env server') - } - return this.server.sub + return this.indexer.sub } getClient() { return new AtpAgent({ service: this.url }) } + adminAuth(role: 'admin' | 'moderator' | 'triage' = 'admin'): string { + const password = + role === 'triage' + ? this.ctx.cfg.triagePassword + : role === 'moderator' + ? this.ctx.cfg.moderatorPassword + : this.ctx.cfg.adminPassword + return ( + 'Basic ' + + ui8.toString(ui8.fromString(`admin:${password}`, 'utf8'), 'base64pad') + ) + } + + adminAuthHeaders(role?: 'admin' | 'moderator' | 'triage') { + return { + authorization: this.adminAuth(role), + } + } + + async processAll() { + await Promise.all([ + this.ctx.backgroundQueue.processAll(), + this.indexer.ctx.backgroundQueue.processAll(), + this.ctx.labelCache.fullRefresh(), + ]) + } + async close() { - await this.server.destroy() + await this.server.destroy({ skipDb: true }) + await this.ingester.destroy({ skipDb: true }) + await this.indexer.destroy() // closes shared db + } +} + +// Below are used for tests just of component parts of the appview, i.e. ingester and indexers: + +export async function getIngester( + network: TestNetworkNoAppView, + opts: { name: string } & Partial, +) { + const { name, ...config } = opts + const ns = name ? await randomIntFromSeed(name, 10000) : undefined + const cfg = new bsky.IngesterConfig({ + version: '0.0.0', + redisHost: process.env.REDIS_HOST || '', + dbPostgresUrl: process.env.DB_POSTGRES_URL || '', + dbPostgresSchema: `appview_${name}`, + repoProvider: network.pds.url.replace('http://', 'ws://'), + ingesterSubLockId: uniqueLockId(), + ingesterPartitionCount: config.ingesterPartitionCount ?? 1, + ingesterNamespace: `ns${ns}`, + ...config, + }) + const db = new bsky.PrimaryDatabase({ + url: cfg.dbPostgresUrl, + schema: cfg.dbPostgresSchema, + }) + assert(cfg.redisHost) + const redis = new bsky.Redis({ + host: cfg.redisHost, + namespace: cfg.ingesterNamespace, + }) + await db.migrateToLatestOrThrow() + return bsky.BskyIngester.create({ cfg, db, redis }) +} + +// get multiple indexers for separate partitions, sharing db and redis instance. +export async function getIndexers( + network: TestNetworkNoAppView, + opts: Partial & { + name: string + partitionIdsByIndexer: number[][] + }, +): Promise { + const { name, ...config } = opts + const ns = name ? await randomIntFromSeed(name, 10000) : undefined + const baseCfg: bsky.IndexerConfigValues = { + version: '0.0.0', + didCacheStaleTTL: HOUR, + didCacheMaxTTL: DAY, + labelerDid: 'did:example:labeler', + labelerKeywords: { label_me: 'test-label', label_me_2: 'test-label-2' }, + redisHost: process.env.REDIS_HOST || '', + dbPostgresUrl: process.env.DB_POSTGRES_URL || '', + dbPostgresSchema: `appview_${name}`, + didPlcUrl: network.plc.url, + imgUriEndpoint: '', + abyssEndpoint: '', + abyssPassword: '', + indexerPartitionIds: [0], + indexerNamespace: `ns${ns}`, + ingesterPartitionCount: config.ingesterPartitionCount ?? 1, + ...config, + } + const db = new bsky.PrimaryDatabase({ + url: baseCfg.dbPostgresUrl, + schema: baseCfg.dbPostgresSchema, + }) + assert(baseCfg.redisHost) + const redis = new bsky.Redis({ + host: baseCfg.redisHost, + namespace: baseCfg.indexerNamespace, + }) + const indexers = await Promise.all( + opts.partitionIdsByIndexer.map(async (indexerPartitionIds) => { + const cfg = new bsky.IndexerConfig({ + ...baseCfg, + indexerPartitionIds, + indexerSubLockId: uniqueLockId(), + indexerPort: await getPort(), + }) + return bsky.BskyIndexer.create({ cfg, db, redis }) + }), + ) + await db.migrateToLatestOrThrow() + return { + db, + list: indexers, + async start() { + await Promise.all(indexers.map((indexer) => indexer.start())) + }, + async destroy() { + const stopping = [...indexers] + const lastIndexer = stopping.pop() + await Promise.all( + stopping.map((indexer) => + indexer.destroy({ skipDb: true, skipRedis: true }), + ), + ) + await lastIndexer?.destroy() + }, + } +} + +export type BskyIndexers = { + db: bsky.Database + list: bsky.BskyIndexer[] + start(): Promise + destroy(): Promise +} + +export async function processAll( + network: TestNetworkNoAppView, + ingester: bsky.BskyIngester, +) { + assert(network.pds.ctx.sequencerLeader, 'sequencer leader does not exist') + await network.pds.processAll() + await ingestAll(network, ingester) + // eslint-disable-next-line no-constant-condition + while (true) { + // check indexers + const keys = [...Array(ingester.sub.opts.partitionCount)].map( + (_, i) => `repo:${i}`, + ) + const results = await ingester.sub.ctx.redis.streamLengths(keys) + const indexersCaughtUp = results.every((len) => len === 0) + if (indexersCaughtUp) return + await wait(50) + } +} + +export async function ingestAll( + network: TestNetworkNoAppView, + ingester: bsky.BskyIngester, +) { + assert(network.pds.ctx.sequencerLeader, 'sequencer leader does not exist') + const pdsDb = network.pds.ctx.db.db + await network.pds.processAll() + // eslint-disable-next-line no-constant-condition + while (true) { + await wait(50) + // check sequencer + const sequencerCaughtUp = await network.pds.ctx.sequencerLeader.isCaughtUp() + if (!sequencerCaughtUp) continue + // check ingester + const [ingesterCursor, { lastSeq }] = await Promise.all([ + ingester.sub.getCursor(), + pdsDb + .selectFrom('repo_seq') + .where('seq', 'is not', null) + .select(pdsDb.fn.max('repo_seq.seq').as('lastSeq')) + .executeTakeFirstOrThrow(), + ]) + const ingesterCaughtUp = ingesterCursor === lastSeq + if (ingesterCaughtUp) return } } diff --git a/packages/dev-env/src/mock/index.ts b/packages/dev-env/src/mock/index.ts index ee99ae0a839..3b6d739c499 100644 --- a/packages/dev-env/src/mock/index.ts +++ b/packages/dev-env/src/mock/index.ts @@ -1,4 +1,4 @@ -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import AtpAgent from '@atproto/api' import { REASONSPAM, diff --git a/packages/dev-env/src/network-no-appview.ts b/packages/dev-env/src/network-no-appview.ts index 45b7eb66edf..8ab85a5f8b4 100644 --- a/packages/dev-env/src/network-no-appview.ts +++ b/packages/dev-env/src/network-no-appview.ts @@ -41,6 +41,10 @@ export class TestNetworkNoAppView { return fg } + async processAll() { + await this.pds.processAll() + } + async close() { await Promise.all(this.feedGens.map((fg) => fg.close())) await this.pds.close() diff --git a/packages/dev-env/src/network.ts b/packages/dev-env/src/network.ts index b6550d783ba..a6c150f0353 100644 --- a/packages/dev-env/src/network.ts +++ b/packages/dev-env/src/network.ts @@ -21,8 +21,10 @@ export class TestNetwork extends TestNetworkNoAppView { static async create( params: Partial = {}, ): Promise { + const redisHost = process.env.REDIS_HOST const dbPostgresUrl = params.dbPostgresUrl || process.env.DB_POSTGRES_URL assert(dbPostgresUrl, 'Missing postgres url for tests') + assert(redisHost, 'Missing redis host for tests') const dbPostgresSchema = params.dbPostgresSchema || process.env.DB_POSTGRES_SCHEMA @@ -33,9 +35,11 @@ export class TestNetwork extends TestNetworkNoAppView { const bsky = await TestBsky.create({ port: bskyPort, plcUrl: plc.url, + pdsPort, repoProvider: `ws://localhost:${pdsPort}`, dbPostgresSchema: `appview_${dbPostgresSchema}`, - dbPostgresUrl, + dbPrimaryPostgresUrl: dbPostgresUrl, + redisHost, ...params.bsky, }) const pds = await TestPds.create({ @@ -46,24 +50,24 @@ export class TestNetwork extends TestNetworkNoAppView { didPlcUrl: plc.url, bskyAppViewUrl: bsky.url, bskyAppViewDid: bsky.ctx.cfg.serverDid, + bskyAppViewModeration: true, ...params.pds, }) - mockNetworkUtilities(pds) + mockNetworkUtilities(pds, bsky) return new TestNetwork(plc, pds, bsky) } async processFullSubscription(timeout = 5000) { - if (!this.bsky) return - const sub = this.bsky.sub - if (!sub) return + const sub = this.bsky.indexer.sub const { db } = this.pds.ctx.db const start = Date.now() while (Date.now() - start < timeout) { await wait(50) - if (!sub) return - const state = await sub.getState() + if (!this.pds.ctx.sequencerLeader) { + throw new Error('Sequencer leader not configured on the pds') + } const caughtUp = await this.pds.ctx.sequencerLeader.isCaughtUp() if (!caughtUp) continue const { lastSeq } = await db @@ -71,16 +75,20 @@ export class TestNetwork extends TestNetworkNoAppView { .where('seq', 'is not', null) .select(db.fn.max('repo_seq.seq').as('lastSeq')) .executeTakeFirstOrThrow() - if (state.cursor === lastSeq) return + const { cursor } = sub.partitions.get(0) + if (cursor === lastSeq) { + // has seen last seq, just need to wait for it to finish processing + await sub.repoQueue.main.onIdle() + return + } } throw new Error(`Sequence was not processed within ${timeout}ms`) } async processAll(timeout?: number) { - await this.pds.ctx.backgroundQueue.processAll() - if (!this.bsky) return + await this.pds.processAll() await this.processFullSubscription(timeout) - await this.bsky.ctx.backgroundQueue.processAll() + await this.bsky.processAll() } async serviceHeaders(did: string, aud?: string) { diff --git a/packages/dev-env/src/pds.ts b/packages/dev-env/src/pds.ts index fbc8a69172c..46a73e8813b 100644 --- a/packages/dev-env/src/pds.ts +++ b/packages/dev-env/src/pds.ts @@ -9,6 +9,8 @@ import { PdsConfig } from './types' import { uniqueLockId } from './util' const ADMIN_PASSWORD = 'admin-pass' +const MOD_PASSWORD = 'mod-pass' +const TRIAGE_PASSWORD = 'triage-pass' export class TestPds { constructor( @@ -34,9 +36,12 @@ export class TestPds { blobstoreDiskLocation: blobstoreLoc, recoveryDidKey: recoveryKey, adminPassword: ADMIN_PASSWORD, + moderatorPassword: MOD_PASSWORD, + triagePassword: TRIAGE_PASSWORD, jwtSecret: 'jwt-secret', serviceHandleDomains: ['.test'], sequencerLeaderLockId: uniqueLockId(), + bskyAppViewCdnUrlPattern: 'http://cdn.appview.com/%s/%s/%s', repoSigningKeyK256PrivateKeyHex: repoSigningPriv, plcRotationKeyK256PrivateKeyHex: plcRotationPriv, inviteRequired: false, @@ -62,6 +67,7 @@ export class TestPds { } await server.start() + return new TestPds(url, port, server) } @@ -73,22 +79,29 @@ export class TestPds { return new AtpAgent({ service: `http://localhost:${this.port}` }) } - adminAuth(): string { + adminAuth(role: 'admin' | 'moderator' | 'triage' = 'admin'): string { + const password = + role === 'triage' + ? TRIAGE_PASSWORD + : role === 'moderator' + ? MOD_PASSWORD + : ADMIN_PASSWORD return ( 'Basic ' + - ui8.toString( - ui8.fromString(`admin:${ADMIN_PASSWORD}`, 'utf8'), - 'base64pad', - ) + ui8.toString(ui8.fromString(`admin:${password}`, 'utf8'), 'base64pad') ) } - adminAuthHeaders() { + adminAuthHeaders(role?: 'admin' | 'moderator' | 'triage') { return { - authorization: this.adminAuth(), + authorization: this.adminAuth(role), } } + async processAll() { + await this.ctx.backgroundQueue.processAll() + } + async close() { await this.server.destroy() } diff --git a/packages/dev-env/src/types.ts b/packages/dev-env/src/types.ts index 2dc96040d06..9430f919ed4 100644 --- a/packages/dev-env/src/types.ts +++ b/packages/dev-env/src/types.ts @@ -1,5 +1,6 @@ import * as pds from '@atproto/pds' import * as bsky from '@atproto/bsky' +import { ImageInvalidator } from '@atproto/bsky/src/image/invalidator' export type PlcConfig = { port?: number @@ -9,20 +10,26 @@ export type PlcConfig = { export type PdsConfig = Partial & { didPlcUrl: string migration?: string + enableLabelsCache?: boolean } export type BskyConfig = Partial & { plcUrl: string repoProvider: string - dbPostgresUrl: string + dbPrimaryPostgresUrl: string + redisHost: string + pdsPort: number + imgInvalidator?: ImageInvalidator migration?: string algos?: bsky.MountedAlgos + indexer?: Partial + ingester?: Partial } export type TestServerParams = { dbPostgresUrl: string dbPostgresSchema: string - pds: Partial + pds: Partial plc: Partial bsky: Partial } diff --git a/packages/dev-env/src/util.ts b/packages/dev-env/src/util.ts index aae6dfb5b35..054544a0775 100644 --- a/packages/dev-env/src/util.ts +++ b/packages/dev-env/src/util.ts @@ -1,13 +1,23 @@ -import { DidResolver, HandleResolver } from '@atproto/identity' +import { IdResolver } from '@atproto/identity' import { TestPds } from './pds' +import { TestBsky } from './bsky' -export const mockNetworkUtilities = (pds: TestPds) => { +export const mockNetworkUtilities = (pds: TestPds, bsky?: TestBsky) => { + mockResolvers(pds.ctx.idResolver, pds) + if (bsky) { + mockResolvers(bsky.ctx.idResolver, pds) + mockResolvers(bsky.indexer.ctx.idResolver, pds) + } +} + +export const mockResolvers = (idResolver: IdResolver, pds: TestPds) => { // Map pds public url to its local url when resolving from plc - const origResolveDid = DidResolver.prototype.resolveNoCache - DidResolver.prototype.resolveNoCache = async function (did) { - const result = await (origResolveDid.call(this, did) as ReturnType< - typeof origResolveDid - >) + const origResolveDid = idResolver.did.resolveNoCache + idResolver.did.resolveNoCache = async (did: string) => { + const result = await (origResolveDid.call( + idResolver.did, + did, + ) as ReturnType) const service = result?.service?.find((svc) => svc.id === '#atproto_pds') if (typeof service?.serviceEndpoint === 'string') { service.serviceEndpoint = service.serviceEndpoint.replace( @@ -18,7 +28,7 @@ export const mockNetworkUtilities = (pds: TestPds) => { return result } - HandleResolver.prototype.resolve = async function (handle: string) { + idResolver.handle.resolve = async (handle: string) => { const isPdsHandle = pds.ctx.cfg.identity.serviceHandleDomains.some( (domain) => handle.endsWith(domain), ) diff --git a/packages/dev-env/tsconfig.build.json b/packages/dev-env/tsconfig.build.json index 27df65b89e2..02a84823b65 100644 --- a/packages/dev-env/tsconfig.build.json +++ b/packages/dev-env/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", "exclude": ["**/*.spec.ts", "**/*.test.ts"] -} \ No newline at end of file +} diff --git a/packages/dev-env/tsconfig.json b/packages/dev-env/tsconfig.json index 57cab99ed25..6e341637305 100644 --- a/packages/dev-env/tsconfig.json +++ b/packages/dev-env/tsconfig.json @@ -6,13 +6,13 @@ "emitDeclarationOnly": true }, "module": "esnext", - "include": ["./src","__tests__/**/**.ts"], + "include": ["./src", "__tests__/**/**.ts"], "references": [ { "path": "../api/tsconfig.build.json" }, { "path": "../bsky/tsconfig.build.json" }, { "path": "../crypto/tsconfig.build.json" }, { "path": "../identity/tsconfig.build.json" }, { "path": "../pds/tsconfig.json" }, - { "path": "../uri/tsconfig.build.json" }, + { "path": "../uri/tsconfig.build.json" } ] -} \ No newline at end of file +} diff --git a/packages/dev-env/update-pkg.js b/packages/dev-env/update-pkg.js deleted file mode 100644 index 15d70bdab33..00000000000 --- a/packages/dev-env/update-pkg.js +++ /dev/null @@ -1,14 +0,0 @@ -const pkgJson = require('@npmcli/package-json') - -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} -if (process.argv.includes('--update-main-to-src')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'src/index.ts' })) - .then((pkg) => pkg.save()) -} diff --git a/packages/pg/README.md b/packages/dev-infra/README.md similarity index 67% rename from packages/pg/README.md rename to packages/dev-infra/README.md index 774500f3b7b..e4864d7a8ce 100644 --- a/packages/pg/README.md +++ b/packages/dev-infra/README.md @@ -1,6 +1,6 @@ -# pg +# dev-infra -Helpers for working with postgres +Helpers for working with postgres and redis locally. Previously known as `pg`. ## Usage @@ -30,6 +30,12 @@ Going to remove pg-db_test-1 ⠿ Container pg-db_test-1 Removed ``` +### `with-redis-and-test-db.sh` + +This script is similar to `with-test-db.sh`, but in addition to an ephemeral/single-use postgres database it also provies a single-use redis instance. When the script starts, Dockerized postgres and redis containers start-up, and when the script completes the containers are removed. + +The environment variables `DB_POSTGRES_URL` and `REDIS_HOST` will be set with a connection strings that can be used to connect to postgres and redis respectively. + ### `docker-compose.yaml` The Docker compose file can be used to run containerized versions of postgres either for single use (as is used by `with-test-db.sh`), or for longer-term use. These are setup as separate services named `db_test` and `db` respectively. In both cases the database is available on the host machine's `localhost` and credentials are: @@ -63,3 +69,15 @@ $ docker compose stop db # stop container $ docker compose rm db # remove container $ docker volume rm pg_atp_db # remove volume ``` + +#### `redis_test` service for single use + +The single-use `redis_test` service does not have any persistent storage. When the container is removed, the data in redis disappears with it. + +This service runs on port `6380`. + +#### `redis` service for persistent use + +The `redis` service has persistent storage on the host machine managed by Docker under a volume named `atp_redis`. When the container is removed, the data in redis will remain on the host machine. In order to start fresh, you would need to remove the volume. + +This service runs on port `6379`. diff --git a/packages/dev-infra/_common.sh b/packages/dev-infra/_common.sh new file mode 100755 index 00000000000..0d66653c878 --- /dev/null +++ b/packages/dev-infra/_common.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env sh + +get_container_id() { + local compose_file=$1 + local service=$2 + if [ -z "${compose_file}" ] || [ -z "${service}" ]; then + echo "usage: get_container_id " + exit 1 + fi + + docker compose -f $compose_file ps --format json --status running \ + | jq -r '.[]? | select(.Service == "'${service}'") | .ID' +} + +# Exports all environment variables +export_env() { + export_pg_env + export_redis_env +} + +# Exports postgres environment variables +export_pg_env() { + # Based on creds in compose.yaml + export PGPORT=5433 + export PGHOST=localhost + export PGUSER=pg + export PGPASSWORD=password + export PGDATABASE=postgres + export DB_POSTGRES_URL="postgresql://pg:password@127.0.0.1:5433/postgres" +} + +# Exports redis environment variables +export_redis_env() { + export REDIS_HOST="127.0.0.1:6380" +} + +# Main entry point +main() { + # Expect a SERVICES env var to be set with the docker service names + local services=${SERVICES} + + dir=$(dirname $0) + compose_file="${dir}/docker-compose.yaml" + + # whether this particular script started the container(s) + started_container=false + + # trap SIGINT and performs cleanup as necessary, i.e. + # taking down containers if this script started them + trap "on_sigint ${services}" INT + on_sigint() { + local services=$@ + echo # newline + if $started_container; then + docker compose -f $compose_file rm -f --stop --volumes ${services} + fi + exit $? + } + + # check if all services are running already + not_running=false + for service in $services; do + container_id=$(get_container_id $compose_file $service) + if [ -z $container_id ]; then + not_running=true + break + fi + done + + # if any are missing, recreate all services + if $not_running; then + docker compose -f $compose_file up --wait --force-recreate ${services} + started_container=true + else + echo "all services ${services} are already running" + fi + + # setup environment variables and run args + export_env + "$@" + # save return code for later + code=$? + + # performs cleanup as necessary, i.e. taking down containers + # if this script started them + echo # newline + if $started_container; then + docker compose -f $compose_file rm -f --stop --volumes ${services} + fi + + exit ${code} +} diff --git a/packages/pg/docker-compose.yaml b/packages/dev-infra/docker-compose.yaml similarity index 51% rename from packages/pg/docker-compose.yaml rename to packages/dev-infra/docker-compose.yaml index 033afa3bb7c..3d582c18b37 100644 --- a/packages/pg/docker-compose.yaml +++ b/packages/dev-infra/docker-compose.yaml @@ -23,5 +23,27 @@ services: disable: true volumes: - atp_db:/var/lib/postgresql/data + # An ephermerally-stored redis cache for single-use test runs + redis_test: &redis_test + image: redis:7.0-alpine + ports: + - '6380:6379' + # Healthcheck ensures redis is queryable when `docker-compose up --wait` completes + healthcheck: + test: ['CMD-SHELL', '[ "$$(redis-cli ping)" = "PONG" ]'] + interval: 500ms + timeout: 10s + retries: 20 + # A persistently-stored redis cache + redis: + <<: *redis_test + command: redis-server --save 60 1 --loglevel warning + ports: + - '6379:6379' + healthcheck: + disable: true + volumes: + - atp_redis:/data volumes: atp_db: + atp_redis: diff --git a/packages/dev-infra/with-test-db.sh b/packages/dev-infra/with-test-db.sh new file mode 100755 index 00000000000..cc083491a59 --- /dev/null +++ b/packages/dev-infra/with-test-db.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env sh + +# Example usage: +# ./with-test-db.sh psql postgresql://pg:password@localhost:5433/postgres -c 'select 1;' + +dir=$(dirname $0) +. ${dir}/_common.sh + +SERVICES="db_test" main "$@" diff --git a/packages/dev-infra/with-test-redis-and-db.sh b/packages/dev-infra/with-test-redis-and-db.sh new file mode 100755 index 00000000000..c2b0c75ff14 --- /dev/null +++ b/packages/dev-infra/with-test-redis-and-db.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env sh + +# Example usage: +# ./with-test-redis-and-db.sh psql postgresql://pg:password@localhost:5433/postgres -c 'select 1;' +# ./with-test-redis-and-db.sh redis-cli -h localhost -p 6380 ping + +dir=$(dirname $0) +. ${dir}/_common.sh + +SERVICES="db_test redis_test" main "$@" diff --git a/packages/identifier/build.js b/packages/identifier/build.js index 5628aa4f4eb..e880ae9930b 100644 --- a/packages/identifier/build.js +++ b/packages/identifier/build.js @@ -1,16 +1,8 @@ -const pkgJson = require('@npmcli/package-json') const { nodeExternalsPlugin } = require('esbuild-node-externals') const buildShallow = process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true' -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} - require('esbuild').build({ logLevel: 'info', entryPoints: ['src/index.ts'], diff --git a/packages/identifier/package.json b/packages/identifier/package.json index cf433419e5f..1642c1046d0 100644 --- a/packages/identifier/package.json +++ b/packages/identifier/package.json @@ -1,21 +1,16 @@ { "name": "@atproto/identifier", - "version": "0.1.0", + "version": "0.2.0", "main": "src/index.ts", + "publishConfig": { + "main": "dist/index.js", + "types": "dist/index.d.ts" + }, "scripts": { - "test": "jest", - "prettier": "prettier --check src/ tests/", - "prettier:fix": "prettier --write src/ tests/", - "lint": "eslint . --ext .ts,.tsx", - "lint:fix": "yarn lint --fix", - "verify": "run-p prettier lint", - "verify:fix": "yarn prettier:fix && yarn lint:fix", + "test": "true", "build": "node ./build.js", "postbuild": "tsc --build tsconfig.build.json", - "update-main-to-dist": "node ./update-pkg.js --update-main-to-dist", - "update-main-to-src": "node ./update-pkg.js --update-main-to-src", - "prepublish": "npm run update-main-to-dist", - "postpublish": "npm run update-main-to-src" + "update-main-to-dist": "node ../../update-main-to-dist.js packages/identifier" }, "license": "MIT", "repository": { @@ -24,9 +19,6 @@ "directory": "packages/identifier" }, "dependencies": { - "@atproto/common-web": "*" - }, - "browser": { - "dns/promises": false + "@atproto/syntax": "workspace:^" } } diff --git a/packages/identifier/src/index.ts b/packages/identifier/src/index.ts index 87f0b851b0d..a590a9da09c 100644 --- a/packages/identifier/src/index.ts +++ b/packages/identifier/src/index.ts @@ -1,3 +1,25 @@ -export * from './handle' -export * from './did' -export * from './reserved' +export { + ATP_URI_REGEX, + AtUri, + DISALLOWED_TLDS, + DisallowedDomainError, + INVALID_HANDLE, + InvalidDidError, + InvalidHandleError, + InvalidNsidError, + NSID, + ReservedHandleError, + UnsupportedDomainError, + ensureValidAtUri, + ensureValidAtUriRegex, + ensureValidDid, + ensureValidDidRegex, + ensureValidHandle, + ensureValidHandleRegex, + ensureValidNsid, + ensureValidNsidRegex, + isValidHandle, + isValidTld, + normalizeAndEnsureValidHandle, + normalizeHandle, +} from '@atproto/syntax' diff --git a/packages/identifier/tsconfig.build.json b/packages/identifier/tsconfig.build.json index 27df65b89e2..02a84823b65 100644 --- a/packages/identifier/tsconfig.build.json +++ b/packages/identifier/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", "exclude": ["**/*.spec.ts", "**/*.test.ts"] -} \ No newline at end of file +} diff --git a/packages/identifier/tsconfig.json b/packages/identifier/tsconfig.json index aaa3b2d88bf..db7a7c4ad35 100644 --- a/packages/identifier/tsconfig.json +++ b/packages/identifier/tsconfig.json @@ -5,8 +5,6 @@ "outDir": "./dist", // Your outDir, "emitDeclarationOnly": true }, - "include": ["./src","__tests__/**/**.ts"], - "references": [ - { "path": "../common/tsconfig.build.json" }, - ] -} \ No newline at end of file + "include": ["./src", "__tests__/**/**.ts"], + "references": [{ "path": "../common/tsconfig.build.json" }] +} diff --git a/packages/identifier/update-pkg.js b/packages/identifier/update-pkg.js deleted file mode 100644 index 15d70bdab33..00000000000 --- a/packages/identifier/update-pkg.js +++ /dev/null @@ -1,14 +0,0 @@ -const pkgJson = require('@npmcli/package-json') - -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} -if (process.argv.includes('--update-main-to-src')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'src/index.ts' })) - .then((pkg) => pkg.save()) -} diff --git a/packages/identity/README.md b/packages/identity/README.md index 0a252d6ede2..c6d940f448e 100644 --- a/packages/identity/README.md +++ b/packages/identity/README.md @@ -1,3 +1,3 @@ # ATP DID Resolver -A library for resolving ATP's Decentralized ID methods. \ No newline at end of file +A library for resolving ATP's Decentralized ID methods. diff --git a/packages/identity/build.js b/packages/identity/build.js index 48de336eedb..709647b64ff 100644 --- a/packages/identity/build.js +++ b/packages/identity/build.js @@ -1,16 +1,8 @@ -const pkgJson = require('@npmcli/package-json') const { nodeExternalsPlugin } = require('esbuild-node-externals') const buildShallow = process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true' -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} - require('esbuild').build({ logLevel: 'info', entryPoints: ['src/index.ts'], diff --git a/packages/identity/jest.config.js b/packages/identity/jest.config.js index 85c2bc4cf32..4eb023d6fcc 100644 --- a/packages/identity/jest.config.js +++ b/packages/identity/jest.config.js @@ -2,5 +2,5 @@ const base = require('../../jest.config.base.js') module.exports = { ...base, - displayName: 'Identity' + displayName: 'Identity', } diff --git a/packages/identity/package.json b/packages/identity/package.json index 5474cae509e..1bd0e3cc3f4 100644 --- a/packages/identity/package.json +++ b/packages/identity/package.json @@ -1,7 +1,11 @@ { "name": "@atproto/identity", - "version": "0.0.1", + "version": "0.2.0", "main": "src/index.ts", + "publishConfig": { + "main": "dist/index.js", + "types": "dist/index.d.ts" + }, "license": "MIT", "repository": { "type": "git", @@ -11,22 +15,13 @@ "scripts": { "test": "jest", "test:log": "cat test.log | pino-pretty", - "prettier": "prettier --check src/ tests/", - "prettier:fix": "prettier --write src/ tests/", - "lint": "eslint . --ext .ts,.tsx", - "lint:fix": "yarn lint --fix", - "verify": "run-p prettier lint", - "verify:fix": "yarn prettier:fix && yarn lint:fix", "build": "node ./build.js", "postbuild": "tsc --build tsconfig.build.json", - "update-main-to-dist": "node ./update-pkg.js --update-main-to-dist", - "update-main-to-src": "node ./update-pkg.js --update-main-to-src", - "prepublish": "npm run update-main-to-dist", - "postpublish": "npm run update-main-to-src" + "update-main-to-dist": "node ../../update-main-to-dist.js packages/identity" }, "dependencies": { - "@atproto/common-web": "*", - "@atproto/crypto": "*", + "@atproto/common-web": "workspace:^", + "@atproto/crypto": "workspace:^", "axios": "^0.27.2", "zod": "^3.21.4" }, diff --git a/packages/identity/src/did/atproto-data.ts b/packages/identity/src/did/atproto-data.ts index d8cd3d06f91..3e7ee5829eb 100644 --- a/packages/identity/src/did/atproto-data.ts +++ b/packages/identity/src/did/atproto-data.ts @@ -10,13 +10,16 @@ export const getDid = (doc: DidDocument): string => { } export const getKey = (doc: DidDocument): string | undefined => { + const did = getDid(doc) let keys = doc.verificationMethod if (!keys) return undefined if (typeof keys !== 'object') return undefined if (!Array.isArray(keys)) { keys = [keys] } - const found = keys.find((key) => key.id === '#atproto') + const found = keys.find( + (key) => key.id === '#atproto' || key.id === `${did}#atproto`, + ) if (!found) return undefined // @TODO support jwk @@ -28,6 +31,9 @@ export const getKey = (doc: DidDocument): string | undefined => { didKey = crypto.formatDidKey(crypto.P256_JWT_ALG, keyBytes) } else if (found.type === 'EcdsaSecp256k1VerificationKey2019') { didKey = crypto.formatDidKey(crypto.SECP256K1_JWT_ALG, keyBytes) + } else if (found.type === 'Multikey') { + const parsed = crypto.parseMultikey(found.publicKeyMultibase) + didKey = crypto.formatDidKey(parsed.jwtAlg, parsed.keyBytes) } return didKey } @@ -42,41 +48,24 @@ export const getHandle = (doc: DidDocument): string | undefined => { } export const getPds = (doc: DidDocument): string | undefined => { - let services = doc.service - if (!services) return undefined - if (typeof services !== 'object') return undefined - if (!Array.isArray(services)) { - services = [services] - } - const found = services.find((service) => service.id === '#atproto_pds') - if (!found) return undefined - if (found.type !== 'AtprotoPersonalDataServer') { - return undefined - } - if (typeof found.serviceEndpoint !== 'string') { - return undefined - } - validateUrl(found.serviceEndpoint) - return found.serviceEndpoint + return getServiceEndpoint(doc, { + id: '#atproto_pds', + type: 'AtprotoPersonalDataServer', + }) } export const getFeedGen = (doc: DidDocument): string | undefined => { - let services = doc.service - if (!services) return undefined - if (typeof services !== 'object') return undefined - if (!Array.isArray(services)) { - services = [services] - } - const found = services.find((service) => service.id === '#bsky_fg') - if (!found) return undefined - if (found.type !== 'BskyFeedGenerator') { - return undefined - } - if (typeof found.serviceEndpoint !== 'string') { - return undefined - } - validateUrl(found.serviceEndpoint) - return found.serviceEndpoint + return getServiceEndpoint(doc, { + id: '#bsky_fg', + type: 'BskyFeedGenerator', + }) +} + +export const getNotif = (doc: DidDocument): string | undefined => { + return getServiceEndpoint(doc, { + id: '#bsky_notif', + type: 'BskyNotificationService', + }) } export const parseToAtprotoDocument = ( @@ -118,3 +107,28 @@ const validateUrl = (url: string) => { throw new Error('Invalid pds hostname') } } + +const getServiceEndpoint = ( + doc: DidDocument, + opts: { id: string; type: string }, +) => { + const did = getDid(doc) + let services = doc.service + if (!services) return undefined + if (typeof services !== 'object') return undefined + if (!Array.isArray(services)) { + services = [services] + } + const found = services.find( + (service) => service.id === opts.id || service.id === `${did}${opts.id}`, + ) + if (!found) return undefined + if (found.type !== opts.type) { + return undefined + } + if (typeof found.serviceEndpoint !== 'string') { + return undefined + } + validateUrl(found.serviceEndpoint) + return found.serviceEndpoint +} diff --git a/packages/identity/src/handle/index.ts b/packages/identity/src/handle/index.ts index fa9c016cd99..8780437efa7 100644 --- a/packages/identity/src/handle/index.ts +++ b/packages/identity/src/handle/index.ts @@ -6,9 +6,12 @@ const PREFIX = 'did=' export class HandleResolver { public timeout: number + private backupNameservers: string[] | undefined + private backupNameserverIps: string[] | undefined constructor(opts: HandleResolverOpts = {}) { this.timeout = opts.timeout ?? 3000 + this.backupNameservers = opts.backupNameservers } async resolve(handle: string): Promise { @@ -23,7 +26,11 @@ export class HandleResolver { httpAbort.abort() return dnsRes } - return httpPromise + const res = await httpPromise + if (res) { + return res + } + return this.resolveDnsBackup(handle) } async resolveDns(handle: string): Promise { @@ -33,12 +40,7 @@ export class HandleResolver { } catch (err) { return undefined } - const results = chunkedResults.map((chunks) => chunks.join('')) - const found = results.filter((i) => i.startsWith(PREFIX)) - if (found.length !== 1) { - return undefined - } - return found[0].slice(PREFIX.length) + return this.parseDnsResult(chunkedResults) } async resolveHttp( @@ -57,4 +59,44 @@ export class HandleResolver { return undefined } } + + async resolveDnsBackup(handle: string): Promise { + let chunkedResults: string[][] + try { + const backupIps = await this.getBackupNameserverIps() + if (!backupIps || backupIps.length < 1) return undefined + const resolver = new dns.Resolver() + resolver.setServers(backupIps) + chunkedResults = await resolver.resolveTxt(`${SUBDOMAIN}.${handle}`) + } catch (err) { + return undefined + } + return this.parseDnsResult(chunkedResults) + } + + parseDnsResult(chunkedResults: string[][]): string | undefined { + const results = chunkedResults.map((chunks) => chunks.join('')) + const found = results.filter((i) => i.startsWith(PREFIX)) + if (found.length !== 1) { + return undefined + } + return found[0].slice(PREFIX.length) + } + + private async getBackupNameserverIps(): Promise { + if (!this.backupNameservers) { + return undefined + } else if (!this.backupNameserverIps) { + const responses = await Promise.allSettled( + this.backupNameservers.map((h) => dns.lookup(h)), + ) + for (const res of responses) { + if (res.status === 'fulfilled') { + this.backupNameserverIps ??= [] + this.backupNameserverIps.push(res.value.address) + } + } + } + return this.backupNameserverIps + } } diff --git a/packages/identity/src/id-resolver.ts b/packages/identity/src/id-resolver.ts index e400e201832..ccf42ca9574 100644 --- a/packages/identity/src/id-resolver.ts +++ b/packages/identity/src/id-resolver.ts @@ -8,7 +8,10 @@ export class IdResolver { constructor(opts: IdentityResolverOpts = {}) { const { timeout = 3000, plcUrl, didCache } = opts - this.handle = new HandleResolver({ timeout }) + this.handle = new HandleResolver({ + timeout, + backupNameservers: opts.backupNameservers, + }) this.did = new DidResolver({ timeout, plcUrl, didCache }) } } diff --git a/packages/identity/src/types.ts b/packages/identity/src/types.ts index fdb1857f3b5..f1d983e6742 100644 --- a/packages/identity/src/types.ts +++ b/packages/identity/src/types.ts @@ -4,10 +4,12 @@ export type IdentityResolverOpts = { timeout?: number plcUrl?: string didCache?: DidCache + backupNameservers?: string[] } export type HandleResolverOpts = { timeout?: number + backupNameservers?: string[] } export type DidResolverOpts = { diff --git a/packages/identity/tests/did-document.test.ts b/packages/identity/tests/did-document.test.ts new file mode 100644 index 00000000000..c15759385da --- /dev/null +++ b/packages/identity/tests/did-document.test.ts @@ -0,0 +1,103 @@ +import { DidResolver, ensureAtpDocument } from '../src' + +describe('did parsing', () => { + it('throws on bad DID document', async () => { + const did = 'did:plc:yk4dd2qkboz2yv6tpubpc6co' + const docJson = `{ + "ideep": "did:plc:yk4dd2qkboz2yv6tpubpc6co", + "blah": [ + "https://dholms.xyz" + ], + "zoot": [ + { + "id": "#elsewhere", + "type": "EcdsaSecp256k1VerificationKey2019", + "controller": "did:plc:yk4dd2qkboz2yv6tpubpc6co", + "publicKeyMultibase": "zQYEBzXeuTM9UR3rfvNag6L3RNAs5pQZyYPsomTsgQhsxLdEgCrPTLgFna8yqCnxPpNT7DBk6Ym3dgPKNu86vt9GR" + } + ], + "yarg": [ ] +}` + const resolver = new DidResolver({}) + expect(() => { + resolver.validateDidDoc(did, JSON.parse(docJson)) + }).toThrow() + }) + + it('parses legacy DID format, extracts atpData', async () => { + const did = 'did:plc:yk4dd2qkboz2yv6tpubpc6co' + const docJson = `{ + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/suites/secp256k1-2019/v1" + ], + "id": "did:plc:yk4dd2qkboz2yv6tpubpc6co", + "alsoKnownAs": [ + "at://dholms.xyz" + ], + "verificationMethod": [ + { + "id": "#atproto", + "type": "EcdsaSecp256k1VerificationKey2019", + "controller": "did:plc:yk4dd2qkboz2yv6tpubpc6co", + "publicKeyMultibase": "zQYEBzXeuTM9UR3rfvNag6L3RNAs5pQZyYPsomTsgQhsxLdEgCrPTLgFna8yqCnxPpNT7DBk6Ym3dgPKNu86vt9GR" + } + ], + "service": [ + { + "id": "#atproto_pds", + "type": "AtprotoPersonalDataServer", + "serviceEndpoint": "https://bsky.social" + } + ] +}` + const resolver = new DidResolver({}) + const doc = resolver.validateDidDoc(did, JSON.parse(docJson)) + const atpData = ensureAtpDocument(doc) + expect(atpData.did).toEqual(did) + expect(atpData.handle).toEqual('dholms.xyz') + expect(atpData.pds).toEqual('https://bsky.social') + expect(atpData.signingKey).toEqual( + 'did:key:zQ3shXjHeiBuRCKmM36cuYnm7YEMzhGnCmCyW92sRJ9pribSF', + ) + }) + + it('parses newer Multikey DID format, extracts atpData', async () => { + const did = 'did:plc:yk4dd2qkboz2yv6tpubpc6co' + const docJson = `{ + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/multikey/v1", + "https://w3id.org/security/suites/secp256k1-2019/v1" + ], + "id": "did:plc:yk4dd2qkboz2yv6tpubpc6co", + "alsoKnownAs": [ + "at://dholms.xyz" + ], + "verificationMethod": [ + { + "id": "did:plc:yk4dd2qkboz2yv6tpubpc6co#atproto", + "type": "Multikey", + "controller": "did:plc:yk4dd2qkboz2yv6tpubpc6co", + "publicKeyMultibase": "zQ3shXjHeiBuRCKmM36cuYnm7YEMzhGnCmCyW92sRJ9pribSF" + } + ], + "service": [ + { + "id": "#atproto_pds", + "type": "AtprotoPersonalDataServer", + "serviceEndpoint": "https://bsky.social" + } + ] +}` + const resolver = new DidResolver({}) + const doc = resolver.validateDidDoc(did, JSON.parse(docJson)) + const atpData = ensureAtpDocument(doc) + expect(atpData.did).toEqual(did) + expect(atpData.handle).toEqual('dholms.xyz') + expect(atpData.pds).toEqual('https://bsky.social') + expect(atpData.signingKey).toEqual( + 'did:key:zQ3shXjHeiBuRCKmM36cuYnm7YEMzhGnCmCyW92sRJ9pribSF', + ) + }) +}) diff --git a/packages/identity/tsconfig.build.json b/packages/identity/tsconfig.build.json index 27df65b89e2..02a84823b65 100644 --- a/packages/identity/tsconfig.build.json +++ b/packages/identity/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", "exclude": ["**/*.spec.ts", "**/*.test.ts"] -} \ No newline at end of file +} diff --git a/packages/identity/tsconfig.json b/packages/identity/tsconfig.json index bcc1381b4ff..9d6fd378666 100644 --- a/packages/identity/tsconfig.json +++ b/packages/identity/tsconfig.json @@ -5,9 +5,9 @@ "outDir": "./dist", // Your outDir, "emitDeclarationOnly": true }, - "include": ["./src","__tests__/**/**.ts"], + "include": ["./src", "__tests__/**/**.ts"], "references": [ { "path": "../common/tsconfig.build.json" }, - { "path": "../crypto/tsconfig.build.json" }, + { "path": "../crypto/tsconfig.build.json" } ] -} \ No newline at end of file +} diff --git a/packages/identity/update-pkg.js b/packages/identity/update-pkg.js deleted file mode 100644 index 15d70bdab33..00000000000 --- a/packages/identity/update-pkg.js +++ /dev/null @@ -1,14 +0,0 @@ -const pkgJson = require('@npmcli/package-json') - -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} -if (process.argv.includes('--update-main-to-src')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'src/index.ts' })) - .then((pkg) => pkg.save()) -} diff --git a/packages/lex-cli/README.md b/packages/lex-cli/README.md index d898002e092..1eee8f0c5ac 100644 --- a/packages/lex-cli/README.md +++ b/packages/lex-cli/README.md @@ -36,4 +36,4 @@ $ lex gen-server ./server/src/xrpc ./schemas/com/service/*.json ./schemas/com/an ## License -MIT \ No newline at end of file +MIT diff --git a/packages/lex-cli/build.js b/packages/lex-cli/build.js index 5628aa4f4eb..e880ae9930b 100644 --- a/packages/lex-cli/build.js +++ b/packages/lex-cli/build.js @@ -1,16 +1,8 @@ -const pkgJson = require('@npmcli/package-json') const { nodeExternalsPlugin } = require('esbuild-node-externals') const buildShallow = process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true' -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} - require('esbuild').build({ logLevel: 'info', entryPoints: ['src/index.ts'], diff --git a/packages/lex-cli/package.json b/packages/lex-cli/package.json index ca0725f146b..6a79be7d34c 100644 --- a/packages/lex-cli/package.json +++ b/packages/lex-cli/package.json @@ -1,19 +1,18 @@ { "name": "@atproto/lex-cli", - "version": "0.1.0", + "version": "0.2.0", "bin": { "lex": "dist/index.js" }, "main": "src/index.ts", + "publishConfig": { + "main": "dist/index.js", + "types": "dist/src/index.d.ts" + }, "scripts": { - "prettier": "prettier --check src/", - "prettier:fix": "prettier --write src/", - "lint": "eslint . --ext .ts,.tsx", - "lint:fix": "yarn lint --fix", - "verify": "run-p prettier lint", - "verify:fix": "yarn prettier:fix && yarn lint:fix", "build": "node ./build.js", - "postbuild": "tsc --build tsconfig.build.json" + "postbuild": "tsc --build tsconfig.build.json", + "update-main-to-dist": "node ../../update-main-to-dist.js packages/lex-cli" }, "license": "MIT", "repository": { @@ -22,11 +21,12 @@ "directory": "packages/lex-cli" }, "dependencies": { - "@atproto/lexicon": "*", - "@atproto/nsid": "*", + "@atproto/lexicon": "workspace:^", + "@atproto/syntax": "workspace:^", "chalk": "^5.1.1", "commander": "^9.4.0", "ts-morph": "^16.0.0", - "yesno": "^0.4.0" + "yesno": "^0.4.0", + "zod": "^3.21.4" } } diff --git a/packages/lex-cli/src/codegen/client.ts b/packages/lex-cli/src/codegen/client.ts index 91475fb67f3..58feaf75ad2 100644 --- a/packages/lex-cli/src/codegen/client.ts +++ b/packages/lex-cli/src/codegen/client.ts @@ -11,7 +11,7 @@ import { LexXrpcQuery, LexRecord, } from '@atproto/lexicon' -import { NSID } from '@atproto/nsid' +import { NSID } from '@atproto/syntax' import { gen, utilTs, lexiconsTs } from './common' import { GeneratedAPI } from '../types' import { @@ -607,7 +607,7 @@ function genClientXrpcCommon( .addConstructor({ parameters: [{ name: 'src', type: 'XRPCError' }], }) - .setBodyText(`super(src.status, src.error, src.message)`) + .setBodyText(`super(src.status, src.error, src.message, src.headers)`) customErrors.push({ name: error.name, cls: name }) } diff --git a/packages/lex-cli/src/codegen/server.ts b/packages/lex-cli/src/codegen/server.ts index b4e5c3759ed..8363f1630c6 100644 --- a/packages/lex-cli/src/codegen/server.ts +++ b/packages/lex-cli/src/codegen/server.ts @@ -12,7 +12,7 @@ import { LexRecord, LexXrpcSubscription, } from '@atproto/lexicon' -import { NSID } from '@atproto/nsid' +import { NSID } from '@atproto/syntax' import { gen, lexiconsTs, utilTs } from './common' import { GeneratedAPI } from '../types' import { @@ -182,13 +182,45 @@ const indexTs = ( ].join('\n'), ) + file.addTypeAlias({ + name: 'SharedRateLimitOpts', + typeParameters: [{ name: 'T' }], + type: `{ + name: string + calcKey?: (ctx: T) => string + calcPoints?: (ctx: T) => number + }`, + }) + + file.addTypeAlias({ + name: 'RouteRateLimitOpts', + typeParameters: [{ name: 'T' }], + type: `{ + durationMs: number + points: number + calcKey?: (ctx: T) => string + calcPoints?: (ctx: T) => number + }`, + }) + + file.addTypeAlias({ + name: 'HandlerRateLimitOpts', + typeParameters: [{ name: 'T' }], + type: `SharedRateLimitOpts | RouteRateLimitOpts`, + }) + file.addTypeAlias({ name: 'ConfigOf', - typeParameters: [{ name: 'Auth' }, { name: 'Handler' }], + typeParameters: [ + { name: 'Auth' }, + { name: 'Handler' }, + { name: 'ReqCtx' }, + ], type: ` | Handler | { auth?: Auth + rateLimit?: HandlerRateLimitOpts | HandlerRateLimitOpts[] handler: Handler }`, }) @@ -269,7 +301,7 @@ function genNamespaceCls(file: SourceFile, ns: DefTreeNode) { }) method.addParameter({ name: 'cfg', - type: `ConfigOf>>`, + type: `ConfigOf>, ${moduleName}.HandlerReqCtx>>`, }) const methodType = isSubscription ? 'streamMethod' : 'method' method.setBodyText( @@ -476,18 +508,27 @@ function genServerXrpcMethod( }) file.addTypeAlias({ - name: 'Handler', + name: 'HandlerReqCtx', isExported: true, typeParameters: [ { name: 'HA', constraint: 'HandlerAuth', default: 'never' }, ], - type: `(ctx: { + type: `{ auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response - }) => Promise | HandlerOutput`, + }`, + }) + + file.addTypeAlias({ + name: 'Handler', + isExported: true, + typeParameters: [ + { name: 'HA', constraint: 'HandlerAuth', default: 'never' }, + ], + type: `(ctx: HandlerReqCtx) => Promise | HandlerOutput`, }) } @@ -525,17 +566,26 @@ function genServerXrpcStreaming( }) file.addTypeAlias({ - name: 'Handler', + name: 'HandlerReqCtx', isExported: true, typeParameters: [ { name: 'HA', constraint: 'HandlerAuth', default: 'never' }, ], - type: `(ctx: { + type: `{ auth: HA params: QueryParams req: IncomingMessage signal: AbortSignal - }) => AsyncIterable`, + }`, + }) + + file.addTypeAlias({ + name: 'Handler', + isExported: true, + typeParameters: [ + { name: 'HA', constraint: 'HandlerAuth', default: 'never' }, + ], + type: `(ctx: HandlerReqCtx) => AsyncIterable`, }) } diff --git a/packages/lex-cli/src/codegen/util.ts b/packages/lex-cli/src/codegen/util.ts index c7330fa276c..043821e4715 100644 --- a/packages/lex-cli/src/codegen/util.ts +++ b/packages/lex-cli/src/codegen/util.ts @@ -1,5 +1,5 @@ import { LexiconDoc, LexUserType } from '@atproto/lexicon' -import { NSID } from '@atproto/nsid' +import { NSID } from '@atproto/syntax' export interface DefTreeNodeUserType { nsid: string diff --git a/packages/lex-cli/tsconfig.build.json b/packages/lex-cli/tsconfig.build.json index 27df65b89e2..02a84823b65 100644 --- a/packages/lex-cli/tsconfig.build.json +++ b/packages/lex-cli/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", "exclude": ["**/*.spec.ts", "**/*.test.ts"] -} \ No newline at end of file +} diff --git a/packages/lex-cli/tsconfig.json b/packages/lex-cli/tsconfig.json index 41e06d6657d..bdf4c2a099a 100644 --- a/packages/lex-cli/tsconfig.json +++ b/packages/lex-cli/tsconfig.json @@ -4,9 +4,9 @@ "outDir": "./dist", // Your outDir, "emitDeclarationOnly": true }, - "include": ["./src","__tests__/**/**.ts"], + "include": ["./src", "__tests__/**/**.ts"], "references": [ { "path": "../nsid/tsconfig.build.json" }, - { "path": "../lexicon/tsconfig.build.json" }, + { "path": "../lexicon/tsconfig.build.json" } ] -} \ No newline at end of file +} diff --git a/packages/lexicon/build.js b/packages/lexicon/build.js index 5628aa4f4eb..e880ae9930b 100644 --- a/packages/lexicon/build.js +++ b/packages/lexicon/build.js @@ -1,16 +1,8 @@ -const pkgJson = require('@npmcli/package-json') const { nodeExternalsPlugin } = require('esbuild-node-externals') const buildShallow = process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true' -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} - require('esbuild').build({ logLevel: 'info', entryPoints: ['src/index.ts'], diff --git a/packages/lexicon/package.json b/packages/lexicon/package.json index 1f1a3fe5834..4b5f7470671 100644 --- a/packages/lexicon/package.json +++ b/packages/lexicon/package.json @@ -1,21 +1,16 @@ { "name": "@atproto/lexicon", - "version": "0.1.0", + "version": "0.2.0", "main": "src/index.ts", + "publishConfig": { + "main": "dist/index.js", + "types": "dist/index.d.ts" + }, "scripts": { "test": "jest", - "prettier": "prettier --check src/ tests/", - "prettier:fix": "prettier --write src/ tests/", - "lint": "eslint . --ext .ts,.tsx", - "lint:fix": "yarn lint --fix", - "verify": "run-p prettier lint", - "verify:fix": "yarn prettier:fix && yarn lint:fix", "build": "node ./build.js", "postbuild": "tsc --build tsconfig.build.json", - "update-main-to-dist": "node ./update-pkg.js --update-main-to-dist", - "update-main-to-src": "node ./update-pkg.js --update-main-to-src", - "prepublish": "npm run update-main-to-dist", - "postpublish": "npm run update-main-to-src" + "update-main-to-dist": "node ../../update-main-to-dist.js packages/lexicon" }, "license": "MIT", "repository": { @@ -24,12 +19,10 @@ "directory": "packages/lexicon" }, "dependencies": { - "@atproto/common-web": "*", - "@atproto/identifier": "*", - "@atproto/nsid": "*", - "@atproto/uri": "*", + "@atproto/common-web": "workspace:^", + "@atproto/syntax": "workspace:^", "iso-datestring-validator": "^2.2.2", - "multiformats": "^9.6.4", + "multiformats": "^9.9.0", "zod": "^3.21.4" } } diff --git a/packages/lexicon/src/types.ts b/packages/lexicon/src/types.ts index c237e3c0730..98616c9e59a 100644 --- a/packages/lexicon/src/types.ts +++ b/packages/lexicon/src/types.ts @@ -1,5 +1,5 @@ import { z } from 'zod' -import { NSID } from '@atproto/nsid' +import { NSID } from '@atproto/syntax' import { requiredPropertiesRefinement } from './util' // primitives diff --git a/packages/lexicon/src/validators/formats.ts b/packages/lexicon/src/validators/formats.ts index f94401ba6cb..63fc941628e 100644 --- a/packages/lexicon/src/validators/formats.ts +++ b/packages/lexicon/src/validators/formats.ts @@ -1,9 +1,12 @@ -import { ensureValidAtUri } from '@atproto/uri' import { isValidISODateString } from 'iso-datestring-validator' import { CID } from 'multiformats/cid' import { ValidationResult, ValidationError } from '../types' -import { ensureValidDid, ensureValidHandle } from '@atproto/identifier' -import { ensureValidNsid } from '@atproto/nsid' +import { + ensureValidDid, + ensureValidHandle, + ensureValidNsid, + ensureValidAtUri, +} from '@atproto/syntax' import { validateLanguage } from '@atproto/common-web' export function datetime(path: string, value: string): ValidationResult { diff --git a/packages/lexicon/tsconfig.build.json b/packages/lexicon/tsconfig.build.json index 27df65b89e2..02a84823b65 100644 --- a/packages/lexicon/tsconfig.build.json +++ b/packages/lexicon/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", "exclude": ["**/*.spec.ts", "**/*.test.ts"] -} \ No newline at end of file +} diff --git a/packages/lexicon/tsconfig.json b/packages/lexicon/tsconfig.json index 4b0566e70fc..5e60cf9920e 100644 --- a/packages/lexicon/tsconfig.json +++ b/packages/lexicon/tsconfig.json @@ -11,4 +11,4 @@ { "path": "../nsid/tsconfig.build.json" }, { "path": "../uri/tsconfig.build.json" } ] -} \ No newline at end of file +} diff --git a/packages/lexicon/update-pkg.js b/packages/lexicon/update-pkg.js deleted file mode 100644 index 15d70bdab33..00000000000 --- a/packages/lexicon/update-pkg.js +++ /dev/null @@ -1,14 +0,0 @@ -const pkgJson = require('@npmcli/package-json') - -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} -if (process.argv.includes('--update-main-to-src')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'src/index.ts' })) - .then((pkg) => pkg.save()) -} diff --git a/packages/nsid/README.md b/packages/nsid/README.md index a3455b6dbc3..180a88338bb 100644 --- a/packages/nsid/README.md +++ b/packages/nsid/README.md @@ -6,18 +6,18 @@ import { NSID } from '@atproto/nsid' const id1 = NSID.parse('com.example.foo') -id1.authority // => 'example.com' -id1.name // => 'foo' +id1.authority // => 'example.com' +id1.name // => 'foo' id1.toString() // => 'com.example.foo' const id2 = NSID.create('example.com', 'foo') -id2.authority // => 'example.com' -id2.name // => 'foo' +id2.authority // => 'example.com' +id2.name // => 'foo' id2.toString() // => 'com.example.foo' const id3 = NSID.create('example.com', 'someRecord') -id3.authority // => 'example.com' -id3.name // => 'someRecord' +id3.authority // => 'example.com' +id3.name // => 'someRecord' id3.toString() // => 'com.example.someRecord' NSID.isValid('com.example.foo') // => true diff --git a/packages/nsid/build.js b/packages/nsid/build.js index 5628aa4f4eb..e880ae9930b 100644 --- a/packages/nsid/build.js +++ b/packages/nsid/build.js @@ -1,16 +1,8 @@ -const pkgJson = require('@npmcli/package-json') const { nodeExternalsPlugin } = require('esbuild-node-externals') const buildShallow = process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true' -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} - require('esbuild').build({ logLevel: 'info', entryPoints: ['src/index.ts'], diff --git a/packages/nsid/package.json b/packages/nsid/package.json index 5bb2180cde5..af156718f49 100644 --- a/packages/nsid/package.json +++ b/packages/nsid/package.json @@ -1,17 +1,16 @@ { "name": "@atproto/nsid", - "version": "0.0.1", + "version": "0.1.0", "main": "src/index.ts", + "publishConfig": { + "main": "dist/index.js", + "types": "dist/src/index.d.ts" + }, "scripts": { - "test": "jest", - "prettier": "prettier --check src/ tests/", - "prettier:fix": "prettier --write src/ tests/", - "lint": "eslint . --ext .ts,.tsx", - "lint:fix": "yarn lint --fix", - "verify": "run-p prettier lint", - "verify:fix": "yarn prettier:fix && yarn lint:fix", + "test": "true", "build": "node ./build.js", - "postbuild": "tsc --build tsconfig.build.json" + "postbuild": "tsc --build tsconfig.build.json", + "update-main-to-dist": "node ../../update-main-to-dist.js packages/nsid" }, "license": "MIT", "repository": { @@ -19,5 +18,7 @@ "url": "https://github.com/bluesky-social/atproto.git", "directory": "packages/nsid" }, - "dependencies": {} + "dependencies": { + "@atproto/syntax": "workspace:^" + } } diff --git a/packages/nsid/src/index.ts b/packages/nsid/src/index.ts index b436912bf7d..7a2efe52465 100644 --- a/packages/nsid/src/index.ts +++ b/packages/nsid/src/index.ts @@ -1,111 +1,6 @@ -/* -Grammar: - -alpha = "a" / "b" / "c" / "d" / "e" / "f" / "g" / "h" / "i" / "j" / "k" / "l" / "m" / "n" / "o" / "p" / "q" / "r" / "s" / "t" / "u" / "v" / "w" / "x" / "y" / "z" / "A" / "B" / "C" / "D" / "E" / "F" / "G" / "H" / "I" / "J" / "K" / "L" / "M" / "N" / "O" / "P" / "Q" / "R" / "S" / "T" / "U" / "V" / "W" / "X" / "Y" / "Z" -number = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / "0" -delim = "." -segment = alpha *( alpha / number / "-" ) -authority = segment *( delim segment ) -name = alpha *( alpha ) -nsid = authority delim name - -*/ - -export class NSID { - segments: string[] = [] - - static parse(nsid: string): NSID { - return new NSID(nsid) - } - - static create(authority: string, name: string): NSID { - const segments = [...authority.split('.').reverse(), name].join('.') - return new NSID(segments) - } - - static isValid(nsid: string): boolean { - try { - NSID.parse(nsid) - return true - } catch (e) { - return false - } - } - - constructor(nsid: string) { - ensureValidNsid(nsid) - this.segments = nsid.split('.') - } - - get authority() { - return this.segments - .slice(0, this.segments.length - 1) - .reverse() - .join('.') - } - - get name() { - return this.segments.at(this.segments.length - 1) - } - - toString() { - return this.segments.join('.') - } -} - -// Human readable constraints on NSID: -// - a valid domain in reversed notation -// - followed by an additional period-separated name, which is camel-case letters -export const ensureValidNsid = (nsid: string): void => { - const toCheck = nsid - - // check that all chars are boring ASCII - if (!/^[a-zA-Z0-9.-]*$/.test(toCheck)) { - throw new InvalidNsidError( - 'Disallowed characters in NSID (ASCII letters, digits, dashes, periods only)', - ) - } - - if (toCheck.length > 253 + 1 + 63) { - throw new InvalidNsidError('NSID is too long (317 chars max)') - } - const labels = toCheck.split('.') - if (labels.length < 3) { - throw new InvalidNsidError('NSID needs at least three parts') - } - for (let i = 0; i < labels.length; i++) { - const l = labels[i] - if (l.length < 1) { - throw new InvalidNsidError('NSID parts can not be empty') - } - if (l.length > 63) { - throw new InvalidNsidError('NSID part too long (max 63 chars)') - } - if (l.endsWith('-') || l.startsWith('-')) { - throw new InvalidNsidError('NSID parts can not start or end with hyphen') - } - if (/^[0-9]/.test(l) && i == 0) { - throw new InvalidNsidError('NSID first part may not start with a digit') - } - if (!/^[a-zA-Z]+$/.test(l) && i + 1 == labels.length) { - throw new InvalidNsidError('NSID name part must be only letters') - } - } -} - -export const ensureValidNsidRegex = (nsid: string): void => { - // simple regex to enforce most constraints via just regex and length. - // hand wrote this regex based on above constraints - if ( - !/^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\.[a-zA-Z]([a-zA-Z]{0,61}[a-zA-Z])?)$/.test( - nsid, - ) - ) { - throw new InvalidNsidError("NSID didn't validate via regex") - } - if (nsid.length > 253 + 1 + 63) { - throw new InvalidNsidError('NSID is too long (317 chars max)') - } -} - -export class InvalidNsidError extends Error {} +export { + NSID, + ensureValidNsid, + ensureValidNsidRegex, + InvalidNsidError, +} from '@atproto/syntax' diff --git a/packages/nsid/tsconfig.build.json b/packages/nsid/tsconfig.build.json index 27df65b89e2..02a84823b65 100644 --- a/packages/nsid/tsconfig.build.json +++ b/packages/nsid/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", "exclude": ["**/*.spec.ts", "**/*.test.ts"] -} \ No newline at end of file +} diff --git a/packages/nsid/tsconfig.json b/packages/nsid/tsconfig.json index c1364fd69c1..fee83b7f23b 100644 --- a/packages/nsid/tsconfig.json +++ b/packages/nsid/tsconfig.json @@ -4,5 +4,5 @@ "outDir": "./dist", // Your outDir, "emitDeclarationOnly": true }, - "include": ["./src","__tests__/**/**.ts"], -} \ No newline at end of file + "include": ["./src", "__tests__/**/**.ts"] +} diff --git a/packages/pds/.eslintignore b/packages/pds/.eslintignore deleted file mode 100644 index 18dd0f354fc..00000000000 --- a/packages/pds/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -lexicon \ No newline at end of file diff --git a/packages/pds/.prettierignore b/packages/pds/.prettierignore deleted file mode 100644 index e74f52e0ce8..00000000000 --- a/packages/pds/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -src/lexicon/**/* -src/mailer/templates/**/* \ No newline at end of file diff --git a/packages/pds/README.md b/packages/pds/README.md index 81f32ad0791..547856de3d0 100644 --- a/packages/pds/README.md +++ b/packages/pds/README.md @@ -1,3 +1,3 @@ # ATP Personal Data Server (PDS) -The Personal Data Server (PDS). This is ATP's main server-side implementation. \ No newline at end of file +The Personal Data Server (PDS). This is ATP's main server-side implementation. diff --git a/packages/pds/bench/sequencer.bench.ts b/packages/pds/bench/sequencer.bench.ts new file mode 100644 index 00000000000..00c3e2c21c4 --- /dev/null +++ b/packages/pds/bench/sequencer.bench.ts @@ -0,0 +1,138 @@ +import { randomBytes } from '@atproto/crypto' +import { cborEncode } from '@atproto/common' +import { TestServerInfo, runTestServer } from '../tests/_util' +import { randomCid } from '@atproto/repo/tests/_util' +import { BlockMap, blocksToCarFile } from '@atproto/repo' +import { byFrame } from '@atproto/xrpc-server' +import { WebSocket } from 'ws' +import { Database } from '../src' + +describe('sequencer bench', () => { + let server: TestServerInfo + + let db: Database + + beforeAll(async () => { + server = await runTestServer({ + dbPostgresSchema: 'sequencer_bench', + maxSubscriptionBuffer: 20000, + }) + if (!server.ctx.cfg.dbPostgresUrl) { + throw new Error('no postgres url') + } + db = Database.postgres({ + url: server.ctx.cfg.dbPostgresUrl, + schema: server.ctx.cfg.dbPostgresSchema, + txLockNonce: server.ctx.cfg.dbTxLockNonce, + poolSize: 50, + }) + + server.ctx.sequencerLeader?.destroy() + }) + + afterAll(async () => { + await server.close() + }) + + const doWrites = async (batches: number, batchSize: number) => { + const cid = await randomCid() + const blocks = new BlockMap() + await blocks.add(randomBytes(500)) + await blocks.add(randomBytes(500)) + await blocks.add(randomBytes(500)) + await blocks.add(randomBytes(500)) + await blocks.add(randomBytes(500)) + await blocks.add(randomBytes(500)) + + const car = await blocksToCarFile(cid, blocks) + const evt = { + rebase: false, + tooBig: false, + repo: 'did:plc:123451234', + commit: cid, + prev: cid, + ops: [{ action: 'create', path: 'app.bsky.feed.post/abcdefg1234', cid }], + blocks: car, + blobs: [], + } + const encodeEvt = cborEncode(evt) + + const promises: Promise[] = [] + for (let i = 0; i < batches; i++) { + const rows: any[] = [] + for (let j = 0; j < batchSize; j++) { + rows.push({ + did: 'did:web:example.com', + eventType: 'append', + event: encodeEvt, + sequencedAt: new Date().toISOString(), + }) + } + const insert = db.db.insertInto('repo_seq').values(rows).execute() + promises.push(insert) + } + await Promise.all(promises) + } + + const readAll = async ( + totalToRead: number, + cursor?: number, + ): Promise => { + const serverHost = server.url.replace('http://', '') + let url = `ws://${serverHost}/xrpc/com.atproto.sync.subscribeRepos` + if (cursor !== undefined) { + url += `?cursor=${cursor}` + } + const ws = new WebSocket(url) + + let start = Date.now() + let count = 0 + const gen = byFrame(ws) + for await (const _frame of gen) { + if (count === 0) { + start = Date.now() + } + count++ + if (count >= totalToRead) { + break + } + } + if (count < totalToRead) { + throw new Error('Did not read full websocket') + } + return Date.now() - start + } + + it('benches', async () => { + const BATCHES = 100 + const BATCH_SIZE = 100 + const TOTAL = BATCHES * BATCH_SIZE + const readAllPromise = readAll(TOTAL, 0) + + const start = Date.now() + + await doWrites(BATCHES, BATCH_SIZE) + const setup = Date.now() + + await server.ctx.sequencerLeader?.sequenceOutgoing() + const sequencingTime = Date.now() - setup + + const liveTailTime = await readAllPromise + const backfillTime = await readAll(TOTAL, 0) + + console.log(` +${TOTAL} events +Setup: ${setup - start} ms +Sequencing: ${sequencingTime} ms +Sequencing Rate: ${formatRate(TOTAL, sequencingTime)} evt/s +Live tail: ${liveTailTime} ms +Live tail Rate: ${formatRate(TOTAL, liveTailTime)} evt/s +Backfilled: ${backfillTime} ms +Backfill Rate: ${formatRate(TOTAL, backfillTime)} evt/s`) + }) +}) + +const formatRate = (evts: number, timeMs: number): string => { + const evtPerSec = (evts * 1000) / timeMs + return evtPerSec.toFixed(3) +} diff --git a/packages/pds/build.js b/packages/pds/build.js index 151a73e2808..8686a6e4bf9 100644 --- a/packages/pds/build.js +++ b/packages/pds/build.js @@ -1,17 +1,9 @@ -const pkgJson = require('@npmcli/package-json') const { copy } = require('esbuild-plugin-copy') const { nodeExternalsPlugin } = require('esbuild-node-externals') const buildShallow = process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true' -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} - require('esbuild').build({ logLevel: 'info', entryPoints: ['src/index.ts', 'src/db/index.ts'], diff --git a/packages/pds/jest.bench.config.js b/packages/pds/jest.bench.config.js new file mode 100644 index 00000000000..3207cdd8b97 --- /dev/null +++ b/packages/pds/jest.bench.config.js @@ -0,0 +1,8 @@ +const base = require('./jest.config') + +module.exports = { + ...base, + roots: ['/bench'], + testRegex: '(.*.bench)', + testTimeout: 3000000, +} diff --git a/packages/pds/package.json b/packages/pds/package.json index fa08718c99c..2d78352f092 100644 --- a/packages/pds/package.json +++ b/packages/pds/package.json @@ -8,40 +8,37 @@ "directory": "packages/pds" }, "main": "src/index.ts", + "publishConfig": { + "main": "dist/index.js", + "types": "dist/index.d.ts" + }, + "bin": "dist/bin.js", "scripts": { "codegen": "lex gen-server ./src/lexicon ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/*", "build": "node ./build.js", "postbuild": "tsc --build tsconfig.build.json", - "test": "../pg/with-test-db.sh jest", + "test": "../dev-infra/with-test-redis-and-db.sh jest", + "bench": "../dev-infra/with-test-redis-and-db.sh jest --config jest.bench.config.js", "test:sqlite": "jest --testPathIgnorePatterns /tests/proxied/*", "test:log": "tail -50 test.log | pino-pretty", "test:updateSnapshot": "jest --updateSnapshot", - "prettier": "prettier --check src/ tests/", - "prettier:fix": "prettier --write src/ tests/", - "lint": "eslint . --ext .ts,.tsx", - "lint:fix": "yarn lint --fix", - "verify": "run-p prettier lint", - "verify:fix": "yarn prettier:fix && yarn lint:fix", - "migration:create": "ts-node ./bin/migration-create.ts", - "update-main-to-dist": "node ./update-pkg.js --update-main-to-dist", - "update-main-to-src": "node ./update-pkg.js --update-main-to-src", - "prepublish": "npm run update-main-to-dist", - "postpublish": "npm run update-main-to-src" + "migration:create": "ts-node ./bin/migration-create.ts" }, "dependencies": { - "@atproto/api": "*", - "@atproto/aws": "*", - "@atproto/common": "*", - "@atproto/crypto": "*", - "@atproto/identifier": "*", - "@atproto/identity": "*", - "@atproto/lexicon": "*", - "@atproto/repo": "*", - "@atproto/uri": "*", - "@atproto/xrpc-server": "*", + "@atproto/api": "workspace:^", + "@atproto/aws": "workspace:^", + "@atproto/common": "workspace:^", + "@atproto/crypto": "workspace:^", + "@atproto/syntax": "workspace:^", + "@atproto/identity": "workspace:^", + "@atproto/lexicon": "workspace:^", + "@atproto/repo": "workspace:^", + "@atproto/xrpc": "workspace:^", + "@atproto/xrpc-server": "workspace:^", "@did-plc/lib": "^0.0.1", "better-sqlite3": "^7.6.2", "bytes": "^3.1.2", + "compression": "^1.7.4", "cors": "^2.8.5", "dotenv": "^16.0.0", "express": "^4.17.2", @@ -51,28 +48,36 @@ "handlebars": "^4.7.7", "http-errors": "^2.0.0", "http-terminator": "^3.2.0", + "ioredis": "^5.3.2", + "iso-datestring-validator": "^2.2.2", "jsonwebtoken": "^8.5.1", "kysely": "^0.22.0", - "multiformats": "^9.6.4", + "lru-cache": "^10.0.1", + "multiformats": "^9.9.0", "nodemailer": "^6.8.0", "nodemailer-html-to-text": "^3.2.0", "p-queue": "^6.6.2", "pg": "^8.10.0", - "pino": "^8.6.1", + "pino": "^8.15.0", "pino-http": "^8.2.1", "sharp": "^0.31.2", "typed-emitter": "^2.1.0", - "uint8arrays": "3.0.0" + "uint8arrays": "3.0.0", + "zod": "^3.21.4" }, "devDependencies": { - "@atproto/api": "*", - "@atproto/lex-cli": "*", + "@atproto/bsky": "workspace:^", + "@atproto/dev-env": "workspace:^", + "@atproto/api": "workspace:^", + "@atproto/lex-cli": "workspace:^", "@did-plc/server": "^0.0.1", "@types/cors": "^2.8.12", "@types/express": "^4.17.13", + "@types/express-serve-static-core": "^4.17.36", "@types/jsonwebtoken": "^8.5.9", "@types/nodemailer": "^6.4.6", "@types/pg": "^8.6.6", + "@types/qs": "^6.9.7", "@types/sharp": "^0.31.0", "axios": "^0.27.2" } diff --git a/packages/pds/service/package.json b/packages/pds/service/package.json deleted file mode 100644 index f783a050553..00000000000 --- a/packages/pds/service/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "plc-service", - "private": true, - "dependencies": { - "dd-trace": "^3.8.0" - } -} diff --git a/packages/pds/service/yarn.lock b/packages/pds/service/yarn.lock deleted file mode 100644 index c1090549f70..00000000000 --- a/packages/pds/service/yarn.lock +++ /dev/null @@ -1,320 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@datadog/native-appsec@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@datadog/native-appsec/-/native-appsec-2.0.0.tgz#ad65ba19bfd68e6b6c6cf64bb8ef55d099af8edc" - integrity sha512-XHARZ6MVgbnfOUO6/F3ZoZ7poXHJCNYFlgcyS2Xetuk9ITA5bfcooX2B2F7tReVB+RLJ+j8bsm0t55SyF04KDw== - dependencies: - node-gyp-build "^3.9.0" - -"@datadog/native-iast-rewriter@1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@datadog/native-iast-rewriter/-/native-iast-rewriter-1.1.2.tgz#793cbf92d218ec80d645be0830023656b81018ea" - integrity sha512-pigRfRtAjZjMjqIXyXb98S4aDnuHz/EmqpoxAajFZsNjBLM87YonwSY5zoBdCsOyA46ddKOJRoCQd5ZalpOFMQ== - dependencies: - node-gyp-build "^4.5.0" - -"@datadog/native-iast-taint-tracking@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@datadog/native-iast-taint-tracking/-/native-iast-taint-tracking-1.1.0.tgz#8f7d0016157b32dbf5c01b15b8afb1c4286b4a18" - integrity sha512-TOrngpt6Qh52zWFOz1CkFXw0g43rnuUziFBtIMUsOLGzSHr9wdnTnE6HAyuvKy3f3ecAoZESlMfilGRKP93hXQ== - dependencies: - node-gyp-build "^3.9.0" - -"@datadog/native-metrics@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@datadog/native-metrics/-/native-metrics-1.5.0.tgz#e71b6b6d65f4bd58dfdffab2737890e8eef34584" - integrity sha512-K63XMDx74RLhOpM8I9GGZR9ft0CNNB/RkjYPLHcVGvVnBR47zmWE2KFa7Yrtzjbk73+88PXI4nzqLyR3PJsaIQ== - dependencies: - node-gyp-build "^3.9.0" - -"@datadog/pprof@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@datadog/pprof/-/pprof-1.1.1.tgz#17e86035140523ac3a96f3662e5dd29822042d61" - integrity sha512-5lYXUpikQhrJwzODtJ7aFM0oKmPccISnTCecuWhjxIj4/7UJv0DamkLak634bgEW+kiChgkKFDapHSesuXRDXQ== - dependencies: - delay "^5.0.0" - findit2 "^2.2.3" - node-gyp-build "^3.9.0" - p-limit "^3.1.0" - pify "^5.0.0" - protobufjs "^7.0.0" - source-map "^0.7.3" - split "^1.0.1" - -"@datadog/sketches-js@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@datadog/sketches-js/-/sketches-js-2.1.0.tgz#8c7e8028a5fc22ad102fa542b0a446c956830455" - integrity sha512-smLocSfrt3s53H/XSVP3/1kP42oqvrkjUPtyaFd1F79ux24oE31BKt+q0c6lsa6hOYrFzsIwyc5GXAI5JmfOew== - -"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== - -"@protobufjs/base64@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" - integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== - -"@protobufjs/codegen@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" - integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== - -"@protobufjs/eventemitter@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== - -"@protobufjs/fetch@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== - dependencies: - "@protobufjs/aspromise" "^1.1.1" - "@protobufjs/inquire" "^1.1.0" - -"@protobufjs/float@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== - -"@protobufjs/inquire@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== - -"@protobufjs/path@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== - -"@protobufjs/pool@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== - -"@protobufjs/utf8@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== - -"@types/node@>=13.7.0": - version "18.13.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.13.0.tgz#0400d1e6ce87e9d3032c19eb6c58205b0d3f7850" - integrity sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg== - -crypto-randomuuid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/crypto-randomuuid/-/crypto-randomuuid-1.0.0.tgz#acf583e5e085e867ae23e107ff70279024f9e9e7" - integrity sha512-/RC5F4l1SCqD/jazwUF6+t34Cd8zTSAGZ7rvvZu1whZUhD2a5MOGKjSGowoGcpj/cbVZk1ZODIooJEQQq3nNAA== - -dd-trace@^3.8.0: - version "3.13.2" - resolved "https://registry.yarnpkg.com/dd-trace/-/dd-trace-3.13.2.tgz#95b1ec480ab9ac406e1da7591a8c6f678d3799fd" - integrity sha512-POO9nEcAufe5pgp2xV1X3PfWip6wh+6TpEcRSlSgZJCIIMvWVCkcIVL/J2a6KAZq6V3Yjbkl8Ktfe+MOzQf5kw== - dependencies: - "@datadog/native-appsec" "2.0.0" - "@datadog/native-iast-rewriter" "1.1.2" - "@datadog/native-iast-taint-tracking" "1.1.0" - "@datadog/native-metrics" "^1.5.0" - "@datadog/pprof" "^1.1.1" - "@datadog/sketches-js" "^2.1.0" - crypto-randomuuid "^1.0.0" - diagnostics_channel "^1.1.0" - ignore "^5.2.0" - import-in-the-middle "^1.3.4" - ipaddr.js "^2.0.1" - istanbul-lib-coverage "3.2.0" - koalas "^1.0.2" - limiter "^1.1.4" - lodash.kebabcase "^4.1.1" - lodash.pick "^4.4.0" - lodash.sortby "^4.7.0" - lodash.uniq "^4.5.0" - lru-cache "^7.14.0" - methods "^1.1.2" - module-details-from-path "^1.0.3" - node-abort-controller "^3.0.1" - opentracing ">=0.12.1" - path-to-regexp "^0.1.2" - protobufjs "^7.1.2" - retry "^0.10.1" - semver "^5.5.0" - -delay@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" - integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== - -diagnostics_channel@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/diagnostics_channel/-/diagnostics_channel-1.1.0.tgz#bd66c49124ce3bac697dff57466464487f57cea5" - integrity sha512-OE1ngLDjSBPG6Tx0YATELzYzy3RKHC+7veQ8gLa8yS7AAgw65mFbVdcsu3501abqOZCEZqZyAIemB0zXlqDSuw== - -findit2@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/findit2/-/findit2-2.2.3.tgz#58a466697df8a6205cdfdbf395536b8bd777a5f6" - integrity sha512-lg/Moejf4qXovVutL0Lz4IsaPoNYMuxt4PA0nGqFxnJ1CTTGGlEO2wKgoDpwknhvZ8k4Q2F+eesgkLbG2Mxfog== - -ignore@^5.2.0: - version "5.2.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" - integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== - -import-in-the-middle@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.3.4.tgz#7074bbd4e84e8cdafd1eae400b04e6fe252a0768" - integrity sha512-TUXqqEFacJ2DWAeYOhHwGZTMJtFxFVw0C1pYA+AXmuWXZGnBqUhHdtVrSkSbW5D7k2yriBG45j23iH9TRtI+bQ== - dependencies: - module-details-from-path "^1.0.3" - -ipaddr.js@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" - integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== - -istanbul-lib-coverage@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" - integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== - -koalas@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/koalas/-/koalas-1.0.2.tgz#318433f074235db78fae5661a02a8ca53ee295cd" - integrity sha512-RYhBbYaTTTHId3l6fnMZc3eGQNW6FVCqMG6AMwA5I1Mafr6AflaXeoi6x3xQuATRotGYRLk6+1ELZH4dstFNOA== - -limiter@^1.1.4: - version "1.1.5" - resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.5.tgz#8f92a25b3b16c6131293a0cc834b4a838a2aa7c2" - integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA== - -lodash.kebabcase@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" - integrity sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g== - -lodash.pick@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" - integrity sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q== - -lodash.sortby@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" - integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== - -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== - -long@^5.0.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/long/-/long-5.2.1.tgz#e27595d0083d103d2fa2c20c7699f8e0c92b897f" - integrity sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A== - -lru-cache@^7.14.0: - version "7.16.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.16.0.tgz#b1b946cff368d3f3c569cc3d6a5ba8f90435160f" - integrity sha512-VJBdeMa9Bz27NNlx+DI/YXGQtXdjUU+9gdfN1rYfra7vtTjhodl5tVNmR42bo+ORHuDqDT+lGAUAb+lzvY42Bw== - -methods@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -module-details-from-path@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b" - integrity sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A== - -node-abort-controller@^3.0.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" - integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== - -node-gyp-build@^3.9.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.9.0.tgz#53a350187dd4d5276750da21605d1cb681d09e25" - integrity sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A== - -node-gyp-build@^4.5.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" - integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== - -opentracing@>=0.12.1: - version "0.14.7" - resolved "https://registry.yarnpkg.com/opentracing/-/opentracing-0.14.7.tgz#25d472bd0296dc0b64d7b94cbc995219031428f5" - integrity sha512-vz9iS7MJ5+Bp1URw8Khvdyw1H/hGvzHWlKQ7eRrQojSCDL1/SrWfrY9QebLw97n2deyRtzHRC3MkQfVNUCo91Q== - -p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -path-to-regexp@^0.1.2: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== - -pify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" - integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== - -protobufjs@^7.0.0, protobufjs@^7.1.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.2.tgz#2af401d8c547b9476fb37ffc65782cf302342ca3" - integrity sha512-++PrQIjrom+bFDPpfmqXfAGSQs40116JRrqqyf53dymUMvvb5d/LMRyicRoF1AUKoXVS1/IgJXlEgcpr4gTF3Q== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/node" ">=13.7.0" - long "^5.0.0" - -retry@^0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" - integrity sha512-ZXUSQYTHdl3uS7IuCehYfMzKyIDBNoAuUblvy5oGO5UJSUTmStUUVPXbA9Qxd173Bgre53yCQczQuHgRWAdvJQ== - -semver@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -source-map@^0.7.3: - version "0.7.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" - integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== - -split@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" - integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== - dependencies: - through "2" - -through@2: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/packages/pds/src/api/app/bsky/dynamic/index.ts b/packages/pds/src/api/app/bsky/dynamic/index.ts new file mode 100644 index 00000000000..2c14eb6d850 --- /dev/null +++ b/packages/pds/src/api/app/bsky/dynamic/index.ts @@ -0,0 +1,7 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import registerPush from './registerPush' + +export default function (server: Server, ctx: AppContext) { + registerPush(server, ctx) +} diff --git a/packages/pds/src/api/app/bsky/dynamic/registerPush.ts b/packages/pds/src/api/app/bsky/dynamic/registerPush.ts new file mode 100644 index 00000000000..647e0573a2a --- /dev/null +++ b/packages/pds/src/api/app/bsky/dynamic/registerPush.ts @@ -0,0 +1,49 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { getNotif } from '@atproto/identity' +import { InvalidRequestError } from '@atproto/xrpc-server' +import { AtpAgent } from '@atproto/api' +import { getDidDoc } from './util' + +export default function (server: Server, ctx: AppContext) { + server.app.bsky.notification.registerPush({ + auth: ctx.accessVerifier, + handler: async ({ auth, input }) => { + const { serviceDid } = input.body + const { + credentials: { did }, + } = auth + + const authHeaders = await ctx.serviceAuthHeaders(did, serviceDid) + + if (ctx.cfg.bskyAppView.did === serviceDid) { + await ctx.appViewAgent.api.app.bsky.notification.registerPush( + input.body, + { + ...authHeaders, + encoding: 'application/json', + }, + ) + return + } + + const notifEndpoint = await getEndpoint(ctx, serviceDid) + const agent = new AtpAgent({ service: notifEndpoint }) + await agent.api.app.bsky.notification.registerPush(input.body, { + ...authHeaders, + encoding: 'application/json', + }) + }, + }) +} + +const getEndpoint = async (ctx: AppContext, serviceDid: string) => { + const doc = await getDidDoc(ctx, serviceDid) + const notifEndpoint = getNotif(doc) + if (!notifEndpoint) { + throw new InvalidRequestError( + `invalid notification service details in did document: ${serviceDid}`, + ) + } + return notifEndpoint +} diff --git a/packages/pds/src/api/app/bsky/dynamic/util.ts b/packages/pds/src/api/app/bsky/dynamic/util.ts new file mode 100644 index 00000000000..eac4f916424 --- /dev/null +++ b/packages/pds/src/api/app/bsky/dynamic/util.ts @@ -0,0 +1,20 @@ +import { DidDocument, PoorlyFormattedDidDocumentError } from '@atproto/identity' +import AppContext from '../../../../context' +import { InvalidRequestError } from '@atproto/xrpc-server' + +// provides http-friendly errors during did resolution +export const getDidDoc = async (ctx: AppContext, did: string) => { + let resolved: DidDocument | null + try { + resolved = await ctx.idResolver.did.resolve(did) + } catch (err) { + if (err instanceof PoorlyFormattedDidDocumentError) { + throw new InvalidRequestError(`invalid did document: ${did}`) + } + throw err + } + if (!resolved) { + throw new InvalidRequestError(`could not resolve did document: ${did}`) + } + return resolved +} diff --git a/packages/pds/src/api/app/bsky/index.ts b/packages/pds/src/api/app/bsky/index.ts index 3f3aaed1ded..abef2b50a95 100644 --- a/packages/pds/src/api/app/bsky/index.ts +++ b/packages/pds/src/api/app/bsky/index.ts @@ -1,9 +1,13 @@ import { Server } from '../../../lexicon' import AppContext from '../../../context' -import actor from './actor' -import proxied from './proxied' +import simple from './simple' +import dynamic from './dynamic' +import preferences from './preferences' +import munged from './munged' export default function (server: Server, ctx: AppContext) { - actor(server, ctx) - proxied(server, ctx) + simple(server, ctx) + dynamic(server, ctx) + munged(server, ctx) + preferences(server, ctx) } diff --git a/packages/pds/src/api/app/bsky/munged/getActorLikes.ts b/packages/pds/src/api/app/bsky/munged/getActorLikes.ts new file mode 100644 index 00000000000..5a01cf5dbb0 --- /dev/null +++ b/packages/pds/src/api/app/bsky/munged/getActorLikes.ts @@ -0,0 +1,62 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { OutputSchema } from '../../../../lexicon/types/app/bsky/feed/getAuthorFeed' +import { handleReadAfterWrite } from './util' +import { authPassthru } from '../../../../api/com/atproto/admin/util' +import { LocalRecords } from '../../../../services/local' + +export default function (server: Server, ctx: AppContext) { + server.app.bsky.feed.getActorLikes({ + auth: ctx.accessOrRoleVerifier, + handler: async ({ req, params, auth }) => { + const requester = + auth.credentials.type === 'access' ? auth.credentials.did : null + + const res = await ctx.appViewAgent.api.app.bsky.feed.getActorLikes( + params, + requester ? await ctx.serviceAuthHeaders(requester) : authPassthru(req), + ) + if (requester) { + return await handleReadAfterWrite(ctx, requester, res, getAuthorMunge) + } + return { + encoding: 'application/json', + body: res.data, + } + }, + }) +} + +const getAuthorMunge = async ( + ctx: AppContext, + original: OutputSchema, + local: LocalRecords, + requester: string, +): Promise => { + const localSrvc = ctx.services.local(ctx.db) + const localProf = local.profile + let feed = original.feed + // first update any out of date profile pictures in feed + if (localProf) { + feed = feed.map((item) => { + if (item.post.author.did === requester) { + return { + ...item, + post: { + ...item.post, + author: localSrvc.updateProfileViewBasic( + item.post.author, + localProf.record, + ), + }, + } + } else { + return item + } + }) + } + return { + ...original, + feed, + } +} diff --git a/packages/pds/src/api/app/bsky/munged/getAuthorFeed.ts b/packages/pds/src/api/app/bsky/munged/getAuthorFeed.ts new file mode 100644 index 00000000000..12d095027cb --- /dev/null +++ b/packages/pds/src/api/app/bsky/munged/getAuthorFeed.ts @@ -0,0 +1,76 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { OutputSchema } from '../../../../lexicon/types/app/bsky/feed/getAuthorFeed' +import { handleReadAfterWrite } from './util' +import { authPassthru } from '../../../../api/com/atproto/admin/util' +import { LocalRecords } from '../../../../services/local' +import { isReasonRepost } from '../../../../lexicon/types/app/bsky/feed/defs' + +export default function (server: Server, ctx: AppContext) { + server.app.bsky.feed.getAuthorFeed({ + auth: ctx.accessOrRoleVerifier, + handler: async ({ req, params, auth }) => { + const requester = + auth.credentials.type === 'access' ? auth.credentials.did : null + const res = await ctx.appViewAgent.api.app.bsky.feed.getAuthorFeed( + params, + requester ? await ctx.serviceAuthHeaders(requester) : authPassthru(req), + ) + if (requester) { + return await handleReadAfterWrite(ctx, requester, res, getAuthorMunge) + } + return { + encoding: 'application/json', + body: res.data, + } + }, + }) +} + +const getAuthorMunge = async ( + ctx: AppContext, + original: OutputSchema, + local: LocalRecords, + requester: string, +): Promise => { + const localSrvc = ctx.services.local(ctx.db) + const localProf = local.profile + // only munge on own feed + if (!isUsersFeed(original, requester)) { + return original + } + let feed = original.feed + // first update any out of date profile pictures in feed + if (localProf) { + feed = feed.map((item) => { + if (item.post.author.did === requester) { + return { + ...item, + post: { + ...item.post, + author: localSrvc.updateProfileViewBasic( + item.post.author, + localProf.record, + ), + }, + } + } else { + return item + } + }) + } + feed = await localSrvc.formatAndInsertPostsInFeed(feed, local.posts) + return { + ...original, + feed, + } +} + +const isUsersFeed = (feed: OutputSchema, requester: string) => { + const first = feed.feed.at(0) + if (!first) return false + if (!first.reason && first.post.author.did === requester) return true + if (isReasonRepost(first.reason) && first.reason.by.did === requester) + return true + return false +} diff --git a/packages/pds/src/api/app/bsky/munged/getPostThread.ts b/packages/pds/src/api/app/bsky/munged/getPostThread.ts new file mode 100644 index 00000000000..9ed130e29b0 --- /dev/null +++ b/packages/pds/src/api/app/bsky/munged/getPostThread.ts @@ -0,0 +1,207 @@ +import { AtUri } from '@atproto/syntax' +import { AppBskyFeedGetPostThread } from '@atproto/api' +import { Headers } from '@atproto/xrpc' +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { + ThreadViewPost, + isThreadViewPost, +} from '../../../../lexicon/types/app/bsky/feed/defs' +import { Record as PostRecord } from '../../../../lexicon/types/app/bsky/feed/post' +import { + OutputSchema, + QueryParams, +} from '../../../../lexicon/types/app/bsky/feed/getPostThread' +import { + LocalRecords, + LocalService, + RecordDescript, +} from '../../../../services/local' +import { getLocalLag, getRepoRev, handleReadAfterWrite } from './util' + +export default function (server: Server, ctx: AppContext) { + server.app.bsky.feed.getPostThread({ + auth: ctx.accessVerifier, + handler: async ({ params, auth }) => { + const requester = auth.credentials.did + try { + const res = await ctx.appViewAgent.api.app.bsky.feed.getPostThread( + params, + await ctx.serviceAuthHeaders(requester), + ) + return await handleReadAfterWrite( + ctx, + requester, + res, + getPostThreadMunge, + ) + } catch (err) { + if (err instanceof AppBskyFeedGetPostThread.NotFoundError) { + const local = await readAfterWriteNotFound( + ctx, + params, + requester, + err.headers, + ) + if (local === null) { + throw err + } else { + return { + encoding: 'application/json', + body: local.data, + headers: local.lag + ? { + 'Atproto-Upstream-Lag': local.lag.toString(10), + } + : undefined, + } + } + } else { + throw err + } + } + }, + }) +} + +// READ AFTER WRITE +// ---------------- + +const getPostThreadMunge = async ( + ctx: AppContext, + original: OutputSchema, + local: LocalRecords, +): Promise => { + // @TODO if is NotFoundPost, handle similarly to error + // @NOTE not necessary right now as we never return those for the requested uri + if (!isThreadViewPost(original.thread)) { + return original + } + const thread = await addPostsToThread( + ctx.services.local(ctx.db), + original.thread, + local.posts, + ) + return { + ...original, + thread, + } +} + +const addPostsToThread = async ( + localSrvc: LocalService, + original: ThreadViewPost, + posts: RecordDescript[], +) => { + const inThread = findPostsInThread(original, posts) + if (inThread.length === 0) return original + let thread: ThreadViewPost = original + for (const record of inThread) { + thread = await insertIntoThreadReplies(localSrvc, thread, record) + } + return thread +} + +const findPostsInThread = ( + thread: ThreadViewPost, + posts: RecordDescript[], +): RecordDescript[] => { + return posts.filter((post) => { + const rootUri = post.record.reply?.root.uri + if (!rootUri) return false + if (rootUri === thread.post.uri) return true + return (thread.post.record as PostRecord).reply?.root.uri === rootUri + }) +} + +const insertIntoThreadReplies = async ( + localSrvc: LocalService, + view: ThreadViewPost, + descript: RecordDescript, +): Promise => { + if (descript.record.reply?.parent.uri === view.post.uri) { + const postView = await threadPostView(localSrvc, descript) + if (!postView) return view + const replies = [postView, ...(view.replies ?? [])] + return { + ...view, + replies, + } + } + if (!view.replies) return view + const replies = await Promise.all( + view.replies.map(async (reply) => + isThreadViewPost(reply) + ? await insertIntoThreadReplies(localSrvc, reply, descript) + : reply, + ), + ) + return { + ...view, + replies, + } +} + +const threadPostView = async ( + localSrvc: LocalService, + descript: RecordDescript, +): Promise => { + const postView = await localSrvc.getPost(descript) + if (!postView) return null + return { + $type: 'app.bsky.feed.defs#threadViewPost', + post: postView, + } +} + +// Read after write on error +// --------------------- + +const readAfterWriteNotFound = async ( + ctx: AppContext, + params: QueryParams, + requester: string, + headers?: Headers, +): Promise<{ data: OutputSchema; lag?: number } | null> => { + if (!headers) return null + const rev = getRepoRev(headers) + if (!rev) return null + const uri = new AtUri(params.uri) + if (uri.hostname !== requester) { + return null + } + const localSrvc = ctx.services.local(ctx.db) + const local = await localSrvc.getRecordsSinceRev(requester, rev) + const found = local.posts.find((p) => p.uri.toString() === uri.toString()) + if (!found) return null + let thread = await threadPostView(localSrvc, found) + if (!thread) return null + const rest = local.posts.filter((p) => p.uri.toString() !== uri.toString()) + thread = await addPostsToThread(localSrvc, thread, rest) + const highestParent = getHighestParent(thread) + if (highestParent) { + try { + const parentsRes = await ctx.appViewAgent.api.app.bsky.feed.getPostThread( + { uri: highestParent, parentHeight: params.parentHeight, depth: 0 }, + await ctx.serviceAuthHeaders(requester), + ) + thread.parent = parentsRes.data.thread + } catch (err) { + // do nothing + } + } + return { + data: { + thread, + }, + lag: getLocalLag(local), + } +} + +const getHighestParent = (thread: ThreadViewPost): string | undefined => { + if (isThreadViewPost(thread.parent)) { + return getHighestParent(thread.parent) + } else { + return (thread.post.record as PostRecord).reply?.parent.uri + } +} diff --git a/packages/pds/src/api/app/bsky/munged/getProfile.ts b/packages/pds/src/api/app/bsky/munged/getProfile.ts new file mode 100644 index 00000000000..64fe1200050 --- /dev/null +++ b/packages/pds/src/api/app/bsky/munged/getProfile.ts @@ -0,0 +1,38 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { authPassthru } from '../../../../api/com/atproto/admin/util' +import { OutputSchema } from '../../../../lexicon/types/app/bsky/actor/getProfile' +import { handleReadAfterWrite } from './util' +import { LocalRecords } from '../../../../services/local' + +export default function (server: Server, ctx: AppContext) { + server.app.bsky.actor.getProfile({ + auth: ctx.accessOrRoleVerifier, + handler: async ({ req, auth, params }) => { + const requester = + auth.credentials.type === 'access' ? auth.credentials.did : null + const res = await ctx.appViewAgent.api.app.bsky.actor.getProfile( + params, + requester ? await ctx.serviceAuthHeaders(requester) : authPassthru(req), + ) + if (res.data.did === requester) { + return await handleReadAfterWrite(ctx, requester, res, getProfileMunge) + } + return { + encoding: 'application/json', + body: res.data, + } + }, + }) +} + +const getProfileMunge = async ( + ctx: AppContext, + original: OutputSchema, + local: LocalRecords, +): Promise => { + if (!local.profile) return original + return ctx.services + .local(ctx.db) + .updateProfileDetailed(original, local.profile.record) +} diff --git a/packages/pds/src/api/app/bsky/munged/getProfiles.ts b/packages/pds/src/api/app/bsky/munged/getProfiles.ts new file mode 100644 index 00000000000..dc54e16fa5d --- /dev/null +++ b/packages/pds/src/api/app/bsky/munged/getProfiles.ts @@ -0,0 +1,46 @@ +import AppContext from '../../../../context' +import { Server } from '../../../../lexicon' +import { OutputSchema } from '../../../../lexicon/types/app/bsky/actor/getProfiles' +import { LocalRecords } from '../../../../services/local' +import { handleReadAfterWrite } from './util' + +export default function (server: Server, ctx: AppContext) { + server.app.bsky.actor.getProfiles({ + auth: ctx.accessVerifier, + handler: async ({ auth, params }) => { + const requester = auth.credentials.did + const res = await ctx.appViewAgent.api.app.bsky.actor.getProfiles( + params, + await ctx.serviceAuthHeaders(requester), + ) + const hasSelf = res.data.profiles.some((prof) => prof.did === requester) + if (hasSelf) { + return await handleReadAfterWrite(ctx, requester, res, getProfilesMunge) + } + return { + encoding: 'application/json', + body: res.data, + } + }, + }) +} + +const getProfilesMunge = async ( + ctx: AppContext, + original: OutputSchema, + local: LocalRecords, + requester: string, +): Promise => { + const localProf = local.profile + if (!localProf) return original + const profiles = original.profiles.map((prof) => { + if (prof.did !== requester) return prof + return ctx.services + .local(ctx.db) + .updateProfileDetailed(prof, localProf.record) + }) + return { + ...original, + profiles, + } +} diff --git a/packages/pds/src/api/app/bsky/munged/getTimeline.ts b/packages/pds/src/api/app/bsky/munged/getTimeline.ts new file mode 100644 index 00000000000..ac26fca47d4 --- /dev/null +++ b/packages/pds/src/api/app/bsky/munged/getTimeline.ts @@ -0,0 +1,34 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { OutputSchema } from '../../../../lexicon/types/app/bsky/feed/getTimeline' +import { handleReadAfterWrite } from './util' +import { LocalRecords } from '../../../../services/local' + +export default function (server: Server, ctx: AppContext) { + server.app.bsky.feed.getTimeline({ + auth: ctx.accessVerifier, + handler: async ({ params, auth }) => { + const requester = auth.credentials.did + + const res = await ctx.appViewAgent.api.app.bsky.feed.getTimeline( + params, + await ctx.serviceAuthHeaders(requester), + ) + return await handleReadAfterWrite(ctx, requester, res, getTimelineMunge) + }, + }) +} + +const getTimelineMunge = async ( + ctx: AppContext, + original: OutputSchema, + local: LocalRecords, +): Promise => { + const feed = await ctx.services + .local(ctx.db) + .formatAndInsertPostsInFeed([...original.feed], local.posts) + return { + ...original, + feed, + } +} diff --git a/packages/pds/src/api/app/bsky/munged/index.ts b/packages/pds/src/api/app/bsky/munged/index.ts new file mode 100644 index 00000000000..4a41ceaf293 --- /dev/null +++ b/packages/pds/src/api/app/bsky/munged/index.ts @@ -0,0 +1,17 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import getActorLikes from './getActorLikes' +import getAuthorFeed from './getAuthorFeed' +import getPostThread from './getPostThread' +import getProfile from './getProfile' +import getProfiles from './getProfiles' +import getTimeline from './getTimeline' + +export default function (server: Server, ctx: AppContext) { + getActorLikes(server, ctx) + getAuthorFeed(server, ctx) + getPostThread(server, ctx) + getProfile(server, ctx) + getProfiles(server, ctx) + getTimeline(server, ctx) +} diff --git a/packages/pds/src/api/app/bsky/munged/util.ts b/packages/pds/src/api/app/bsky/munged/util.ts new file mode 100644 index 00000000000..b834a91c1b7 --- /dev/null +++ b/packages/pds/src/api/app/bsky/munged/util.ts @@ -0,0 +1,82 @@ +import { Headers } from '@atproto/xrpc' +import { readStickyLogger as log } from '../../../../logger' +import { LocalRecords } from '../../../../services/local' +import AppContext from '../../../../context' + +export type ApiRes = { + headers: Headers + data: T +} + +export type MungeFn = ( + ctx: AppContext, + original: T, + local: LocalRecords, + requester: string, +) => Promise + +export type HandlerResponse = { + encoding: 'application/json' + body: T + headers?: Record +} + +export const getRepoRev = (headers: Headers): string | undefined => { + return headers['atproto-repo-rev'] +} + +export const getLocalLag = (local: LocalRecords): number | undefined => { + let oldest: string | undefined = local.profile?.indexedAt + for (const post of local.posts) { + if (!oldest || post.indexedAt < oldest) { + oldest = post.indexedAt + } + } + if (!oldest) return undefined + return Date.now() - new Date(oldest).getTime() +} + +export const handleReadAfterWrite = async ( + ctx: AppContext, + requester: string, + res: ApiRes, + munge: MungeFn, +): Promise> => { + let body: T + let lag: number | undefined = undefined + try { + const withLocal = await readAfterWriteInternal(ctx, requester, res, munge) + body = withLocal.data + lag = withLocal.lag + } catch (err) { + body = res.data + log.warn({ err, requester }, 'error in read after write munge') + } + return { + encoding: 'application/json', + body, + headers: + lag !== undefined + ? { + 'Atproto-Upstream-Lag': lag.toString(10), + } + : undefined, + } +} + +export const readAfterWriteInternal = async ( + ctx: AppContext, + requester: string, + res: ApiRes, + munge: MungeFn, +): Promise<{ data: T; lag?: number }> => { + const rev = getRepoRev(res.headers) + if (!rev) return { data: res.data } + const localSrvc = ctx.services.local(ctx.db) + const local = await localSrvc.getRecordsSinceRev(requester, rev) + const data = await munge(ctx, res.data, local, requester) + return { + data, + lag: getLocalLag(local), + } +} diff --git a/packages/pds/src/api/app/bsky/actor/getPreferences.ts b/packages/pds/src/api/app/bsky/preferences/getPreferences.ts similarity index 100% rename from packages/pds/src/api/app/bsky/actor/getPreferences.ts rename to packages/pds/src/api/app/bsky/preferences/getPreferences.ts diff --git a/packages/pds/src/api/app/bsky/actor/index.ts b/packages/pds/src/api/app/bsky/preferences/index.ts similarity index 100% rename from packages/pds/src/api/app/bsky/actor/index.ts rename to packages/pds/src/api/app/bsky/preferences/index.ts diff --git a/packages/pds/src/api/app/bsky/actor/putPreferences.ts b/packages/pds/src/api/app/bsky/preferences/putPreferences.ts similarity index 100% rename from packages/pds/src/api/app/bsky/actor/putPreferences.ts rename to packages/pds/src/api/app/bsky/preferences/putPreferences.ts diff --git a/packages/pds/src/api/app/bsky/proxied.ts b/packages/pds/src/api/app/bsky/simple.ts similarity index 86% rename from packages/pds/src/api/app/bsky/proxied.ts rename to packages/pds/src/api/app/bsky/simple.ts index 09d4ec7a57e..4603895228e 100644 --- a/packages/pds/src/api/app/bsky/proxied.ts +++ b/packages/pds/src/api/app/bsky/simple.ts @@ -2,36 +2,6 @@ import { Server } from '../../../lexicon' import AppContext from '../../../context' export default function (server: Server, ctx: AppContext) { - server.app.bsky.actor.getProfile({ - auth: ctx.accessVerifier, - handler: async ({ auth, params }) => { - const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.actor.getProfile( - params, - await ctx.serviceAuthHeaders(requester), - ) - return { - encoding: 'application/json', - body: res.data, - } - }, - }) - - server.app.bsky.actor.getProfiles({ - auth: ctx.accessVerifier, - handler: async ({ auth, params }) => { - const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.actor.getProfiles( - params, - await ctx.serviceAuthHeaders(requester), - ) - return { - encoding: 'application/json', - body: res.data, - } - }, - }) - server.app.bsky.actor.getSuggestions({ auth: ctx.accessVerifier, handler: async ({ params, auth }) => { @@ -93,21 +63,6 @@ export default function (server: Server, ctx: AppContext) { }, }) - server.app.bsky.feed.getAuthorFeed({ - auth: ctx.accessVerifier, - handler: async ({ params, auth }) => { - const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.feed.getAuthorFeed( - params, - await ctx.serviceAuthHeaders(requester), - ) - return { - encoding: 'application/json', - body: res.data, - } - }, - }) - server.app.bsky.feed.getFeed({ auth: ctx.accessVerifier, handler: async ({ params, auth }) => { @@ -173,21 +128,6 @@ export default function (server: Server, ctx: AppContext) { }, }) - server.app.bsky.feed.getPostThread({ - auth: ctx.accessVerifier, - handler: async ({ params, auth }) => { - const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.feed.getPostThread( - params, - await ctx.serviceAuthHeaders(requester), - ) - return { - encoding: 'application/json', - body: res.data, - } - }, - }) - server.app.bsky.feed.getLikes({ auth: ctx.accessVerifier, handler: async ({ params, auth }) => { @@ -218,21 +158,6 @@ export default function (server: Server, ctx: AppContext) { }, }) - server.app.bsky.feed.getTimeline({ - auth: ctx.accessVerifier, - handler: async ({ params, auth }) => { - const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.feed.getTimeline( - params, - await ctx.serviceAuthHeaders(requester), - ) - return { - encoding: 'application/json', - body: res.data, - } - }, - }) - server.app.bsky.unspecced.getPopularFeedGenerators({ auth: ctx.accessVerifier, handler: async ({ params, auth }) => { @@ -324,6 +249,21 @@ export default function (server: Server, ctx: AppContext) { }, }) + server.app.bsky.graph.getListBlocks({ + auth: ctx.accessVerifier, + handler: async ({ params, auth }) => { + const requester = auth.credentials.did + const res = await ctx.appViewAgent.api.app.bsky.graph.getListBlocks( + params, + await ctx.serviceAuthHeaders(requester), + ) + return { + encoding: 'application/json', + body: res.data, + } + }, + }) + server.app.bsky.graph.getLists({ auth: ctx.accessVerifier, handler: async ({ params, auth }) => { diff --git a/packages/pds/src/api/app/bsky/util/index.ts b/packages/pds/src/api/app/bsky/util/index.ts deleted file mode 100644 index fc3864fbd8f..00000000000 --- a/packages/pds/src/api/app/bsky/util/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const isEnum = ( - object: T, - possibleValue: unknown, -): possibleValue is T[keyof T] => { - return Object.values(object).includes(possibleValue) -} diff --git a/packages/pds/src/api/com/atproto/admin/disableAccountInvites.ts b/packages/pds/src/api/com/atproto/admin/disableAccountInvites.ts index b87b2132eb7..9c2b64b9697 100644 --- a/packages/pds/src/api/com/atproto/admin/disableAccountInvites.ts +++ b/packages/pds/src/api/com/atproto/admin/disableAccountInvites.ts @@ -1,15 +1,19 @@ +import { AuthRequiredError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.disableAccountInvites({ - auth: ctx.adminVerifier, - handler: async ({ input }) => { - const { account } = input.body + auth: ctx.roleVerifier, + handler: async ({ input, auth }) => { + if (!auth.credentials.admin) { + throw new AuthRequiredError('Insufficient privileges') + } + const { account, note } = input.body await ctx.db.db .updateTable('user_account') .where('did', '=', account) - .set({ invitesDisabled: 1 }) + .set({ invitesDisabled: 1, inviteNote: note?.trim() || null }) .execute() }, }) diff --git a/packages/pds/src/api/com/atproto/admin/disableInviteCodes.ts b/packages/pds/src/api/com/atproto/admin/disableInviteCodes.ts index fb7e387bbaf..d1fd4658b43 100644 --- a/packages/pds/src/api/com/atproto/admin/disableInviteCodes.ts +++ b/packages/pds/src/api/com/atproto/admin/disableInviteCodes.ts @@ -1,11 +1,14 @@ +import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { InvalidRequestError } from '@atproto/xrpc-server' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.disableInviteCodes({ - auth: ctx.adminVerifier, - handler: async ({ input }) => { + auth: ctx.roleVerifier, + handler: async ({ input, auth }) => { + if (!auth.credentials.admin) { + throw new AuthRequiredError('Insufficient privileges') + } const { codes = [], accounts = [] } = input.body if (accounts.includes('admin')) { throw new InvalidRequestError('cannot disable admin invite codes') diff --git a/packages/pds/src/api/com/atproto/admin/enableAccountInvites.ts b/packages/pds/src/api/com/atproto/admin/enableAccountInvites.ts index f09d8f3267f..1bb588368e4 100644 --- a/packages/pds/src/api/com/atproto/admin/enableAccountInvites.ts +++ b/packages/pds/src/api/com/atproto/admin/enableAccountInvites.ts @@ -1,15 +1,19 @@ +import { AuthRequiredError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.enableAccountInvites({ - auth: ctx.adminVerifier, - handler: async ({ input }) => { - const { account } = input.body + auth: ctx.roleVerifier, + handler: async ({ input, auth }) => { + if (!auth.credentials.admin) { + throw new AuthRequiredError('Insufficient privileges') + } + const { account, note } = input.body await ctx.db.db .updateTable('user_account') .where('did', '=', account) - .set({ invitesDisabled: 0 }) + .set({ invitesDisabled: 0, inviteNote: note?.trim() || null }) .execute() }, }) diff --git a/packages/pds/src/api/com/atproto/admin/getInviteCodes.ts b/packages/pds/src/api/com/atproto/admin/getInviteCodes.ts index c5ba289032b..02e9c0af82f 100644 --- a/packages/pds/src/api/com/atproto/admin/getInviteCodes.ts +++ b/packages/pds/src/api/com/atproto/admin/getInviteCodes.ts @@ -10,7 +10,7 @@ import { export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.getInviteCodes({ - auth: ctx.moderatorVerifier, + auth: ctx.roleVerifier, handler: async ({ params }) => { const { sort, limit, cursor } = params const ref = ctx.db.db.dynamic.ref diff --git a/packages/pds/src/api/com/atproto/admin/getModerationAction.ts b/packages/pds/src/api/com/atproto/admin/getModerationAction.ts index 8cecab167db..258ca9d94a1 100644 --- a/packages/pds/src/api/com/atproto/admin/getModerationAction.ts +++ b/packages/pds/src/api/com/atproto/admin/getModerationAction.ts @@ -1,17 +1,54 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { authPassthru, mergeRepoViewPdsDetails } from './util' +import { isRepoView } from '../../../../lexicon/types/com/atproto/admin/defs' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.getModerationAction({ - auth: ctx.moderatorVerifier, - handler: async ({ params }) => { + auth: ctx.roleVerifier, + handler: async ({ req, params, auth }) => { + const access = auth.credentials const { db, services } = ctx - const { id } = params + const accountService = services.account(db) const moderationService = services.moderation(db) + + if (ctx.cfg.bskyAppView.proxyModeration) { + const { data: resultAppview } = + await ctx.appViewAgent.com.atproto.admin.getModerationAction( + params, + authPassthru(req), + ) + // merge local repo state for subject if available + if (isRepoView(resultAppview.subject)) { + const account = await accountService.getAccount( + resultAppview.subject.did, + true, + ) + const repo = + account && + (await moderationService.views.repo(account, { + includeEmails: access.moderator, + })) + if (repo) { + resultAppview.subject = mergeRepoViewPdsDetails( + resultAppview.subject, + repo, + ) + } + } + return { + encoding: 'application/json', + body: resultAppview, + } + } + + const { id } = params const result = await moderationService.getActionOrThrow(id) return { encoding: 'application/json', - body: await moderationService.views.actionDetail(result), + body: await moderationService.views.actionDetail(result, { + includeEmails: access.moderator, + }), } }, }) diff --git a/packages/pds/src/api/com/atproto/admin/getModerationActions.ts b/packages/pds/src/api/com/atproto/admin/getModerationActions.ts index dfbf5e12efe..0ef48e99851 100644 --- a/packages/pds/src/api/com/atproto/admin/getModerationActions.ts +++ b/packages/pds/src/api/com/atproto/admin/getModerationActions.ts @@ -1,10 +1,23 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { authPassthru } from './util' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.getModerationActions({ - auth: ctx.moderatorVerifier, - handler: async ({ params }) => { + auth: ctx.roleVerifier, + handler: async ({ req, params }) => { + if (ctx.cfg.bskyAppView.proxyModeration) { + const { data: result } = + await ctx.appViewAgent.com.atproto.admin.getModerationActions( + params, + authPassthru(req), + ) + return { + encoding: 'application/json', + body: result, + } + } + const { db, services } = ctx const { subject, limit = 50, cursor } = params const moderationService = services.moderation(db) diff --git a/packages/pds/src/api/com/atproto/admin/getModerationReport.ts b/packages/pds/src/api/com/atproto/admin/getModerationReport.ts index 18c0c764426..b75268ebdf8 100644 --- a/packages/pds/src/api/com/atproto/admin/getModerationReport.ts +++ b/packages/pds/src/api/com/atproto/admin/getModerationReport.ts @@ -1,17 +1,54 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { authPassthru, mergeRepoViewPdsDetails } from './util' +import { isRepoView } from '../../../../lexicon/types/com/atproto/admin/defs' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.getModerationReport({ - auth: ctx.moderatorVerifier, - handler: async ({ params }) => { + auth: ctx.roleVerifier, + handler: async ({ req, params, auth }) => { + const access = auth.credentials const { db, services } = ctx - const { id } = params + const accountService = services.account(db) const moderationService = services.moderation(db) + + if (ctx.cfg.bskyAppView.proxyModeration) { + const { data: resultAppview } = + await ctx.appViewAgent.com.atproto.admin.getModerationReport( + params, + authPassthru(req), + ) + // merge local repo state for subject if available + if (isRepoView(resultAppview.subject)) { + const account = await accountService.getAccount( + resultAppview.subject.did, + true, + ) + const repo = + account && + (await moderationService.views.repo(account, { + includeEmails: access.moderator, + })) + if (repo) { + resultAppview.subject = mergeRepoViewPdsDetails( + resultAppview.subject, + repo, + ) + } + } + return { + encoding: 'application/json', + body: resultAppview, + } + } + + const { id } = params const result = await moderationService.getReportOrThrow(id) return { encoding: 'application/json', - body: await moderationService.views.reportDetail(result), + body: await moderationService.views.reportDetail(result, { + includeEmails: access.moderator, + }), } }, }) diff --git a/packages/pds/src/api/com/atproto/admin/getModerationReports.ts b/packages/pds/src/api/com/atproto/admin/getModerationReports.ts index 3eb1a26e9dd..2d5dd329bc4 100644 --- a/packages/pds/src/api/com/atproto/admin/getModerationReports.ts +++ b/packages/pds/src/api/com/atproto/admin/getModerationReports.ts @@ -1,10 +1,23 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { authPassthru } from './util' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.getModerationReports({ - auth: ctx.moderatorVerifier, - handler: async ({ params }) => { + auth: ctx.roleVerifier, + handler: async ({ req, params }) => { + if (ctx.cfg.bskyAppView.proxyModeration) { + const { data: result } = + await ctx.appViewAgent.com.atproto.admin.getModerationReports( + params, + authPassthru(req), + ) + return { + encoding: 'application/json', + body: result, + } + } + const { db, services } = ctx const { subject, @@ -15,6 +28,7 @@ export default function (server: Server, ctx: AppContext) { ignoreSubjects = [], reverse = false, reporters = [], + actionedBy, } = params const moderationService = services.moderation(db) const results = await moderationService.getReports({ @@ -26,6 +40,7 @@ export default function (server: Server, ctx: AppContext) { ignoreSubjects, reverse, reporters, + actionedBy, }) return { encoding: 'application/json', diff --git a/packages/pds/src/api/com/atproto/admin/getRecord.ts b/packages/pds/src/api/com/atproto/admin/getRecord.ts index 3d4f69fd6cc..b68d01aefda 100644 --- a/packages/pds/src/api/com/atproto/admin/getRecord.ts +++ b/packages/pds/src/api/com/atproto/admin/getRecord.ts @@ -1,23 +1,57 @@ +import { AtUri } from '@atproto/syntax' import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { AtUri } from '@atproto/uri' +import { authPassthru, mergeRepoViewPdsDetails } from './util' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.getRecord({ - auth: ctx.moderatorVerifier, - handler: async ({ params }) => { + auth: ctx.roleVerifier, + handler: async ({ req, params, auth }) => { + const access = auth.credentials const { db, services } = ctx const { uri, cid } = params const result = await services .record(db) .getRecord(new AtUri(uri), cid ?? null, true) - if (!result) { + const recordDetail = + result && + (await services.moderation(db).views.recordDetail(result, { + includeEmails: access.moderator, + })) + + if (ctx.cfg.bskyAppView.proxyModeration) { + try { + const { data: recordDetailAppview } = + await ctx.appViewAgent.com.atproto.admin.getRecord( + params, + authPassthru(req), + ) + if (recordDetail) { + recordDetailAppview.repo = mergeRepoViewPdsDetails( + recordDetailAppview.repo, + recordDetail.repo, + ) + } + return { + encoding: 'application/json', + body: recordDetailAppview, + } + } catch (err) { + if (err && err['error'] === 'RecordNotFound') { + throw new InvalidRequestError('Record not found', 'RecordNotFound') + } else { + throw err + } + } + } + + if (!recordDetail) { throw new InvalidRequestError('Record not found', 'RecordNotFound') } return { encoding: 'application/json', - body: await services.moderation(db).views.recordDetail(result), + body: recordDetail, } }, }) diff --git a/packages/pds/src/api/com/atproto/admin/getRepo.ts b/packages/pds/src/api/com/atproto/admin/getRepo.ts index 5e304409f2f..19e07862851 100644 --- a/packages/pds/src/api/com/atproto/admin/getRepo.ts +++ b/packages/pds/src/api/com/atproto/admin/getRepo.ts @@ -1,20 +1,54 @@ import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { authPassthru, mergeRepoViewPdsDetails } from './util' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.getRepo({ - auth: ctx.moderatorVerifier, - handler: async ({ params }) => { + auth: ctx.roleVerifier, + handler: async ({ req, params, auth }) => { + const access = auth.credentials const { db, services } = ctx const { did } = params const result = await services.account(db).getAccount(did, true) - if (!result) { + const repoDetail = + result && + (await services.moderation(db).views.repoDetail(result, { + includeEmails: access.moderator, + })) + + if (ctx.cfg.bskyAppView.proxyModeration) { + try { + let { data: repoDetailAppview } = + await ctx.appViewAgent.com.atproto.admin.getRepo( + params, + authPassthru(req), + ) + if (repoDetail) { + repoDetailAppview = mergeRepoViewPdsDetails( + repoDetailAppview, + repoDetail, + ) + } + return { + encoding: 'application/json', + body: repoDetailAppview, + } + } catch (err) { + if (err && err['error'] === 'RepoNotFound') { + throw new InvalidRequestError('Repo not found', 'RepoNotFound') + } else { + throw err + } + } + } + + if (!repoDetail) { throw new InvalidRequestError('Repo not found', 'RepoNotFound') } return { encoding: 'application/json', - body: await services.moderation(db).views.repoDetail(result), + body: repoDetail, } }, }) diff --git a/packages/pds/src/api/com/atproto/admin/index.ts b/packages/pds/src/api/com/atproto/admin/index.ts index e50e2324e46..84d1fe3218a 100644 --- a/packages/pds/src/api/com/atproto/admin/index.ts +++ b/packages/pds/src/api/com/atproto/admin/index.ts @@ -16,7 +16,7 @@ import disableInviteCodes from './disableInviteCodes' import getInviteCodes from './getInviteCodes' import updateAccountHandle from './updateAccountHandle' import updateAccountEmail from './updateAccountEmail' -import rebaseRepo from './rebaseRepo' +import sendEmail from './sendEmail' export default function (server: Server, ctx: AppContext) { resolveModerationReports(server, ctx) @@ -35,5 +35,5 @@ export default function (server: Server, ctx: AppContext) { getInviteCodes(server, ctx) updateAccountHandle(server, ctx) updateAccountEmail(server, ctx) - rebaseRepo(server, ctx) + sendEmail(server, ctx) } diff --git a/packages/pds/src/api/com/atproto/admin/rebaseRepo.ts b/packages/pds/src/api/com/atproto/admin/rebaseRepo.ts deleted file mode 100644 index 8b284da2519..00000000000 --- a/packages/pds/src/api/com/atproto/admin/rebaseRepo.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { InvalidRequestError } from '@atproto/xrpc-server' -import { Server } from '../../../../lexicon' -import AppContext from '../../../../context' -import { CID } from 'multiformats/cid' -import { BadCommitSwapError } from '../../../../repo' -import { ConcurrentWriteError } from '../../../../services/repo' - -export default function (server: Server, ctx: AppContext) { - server.com.atproto.admin.rebaseRepo({ - auth: ctx.adminVerifier, - handler: async ({ input }) => { - const { repo, swapCommit } = input.body - const swapCommitCid = swapCommit ? CID.parse(swapCommit) : undefined - try { - await ctx.services.repo(ctx.db).rebaseRepo(repo, swapCommitCid) - } catch (err) { - if (err instanceof BadCommitSwapError) { - throw new InvalidRequestError(err.message, 'InvalidSwap') - } else if (err instanceof ConcurrentWriteError) { - throw new InvalidRequestError(err.message, 'ConcurrentWrites') - } - throw err - } - }, - }) -} diff --git a/packages/pds/src/api/com/atproto/admin/resolveModerationReports.ts b/packages/pds/src/api/com/atproto/admin/resolveModerationReports.ts index 267238e2faf..52279745e46 100644 --- a/packages/pds/src/api/com/atproto/admin/resolveModerationReports.ts +++ b/packages/pds/src/api/com/atproto/admin/resolveModerationReports.ts @@ -1,10 +1,23 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { authPassthru } from './util' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.resolveModerationReports({ - auth: ctx.moderatorVerifier, - handler: async ({ input }) => { + auth: ctx.roleVerifier, + handler: async ({ req, input }) => { + if (ctx.cfg.bskyAppView.proxyModeration) { + const { data: result } = + await ctx.appViewAgent.com.atproto.admin.resolveModerationReports( + input.body, + authPassthru(req, true), + ) + return { + encoding: 'application/json', + body: result, + } + } + const { db, services } = ctx const moderationService = services.moderation(db) const { actionId, reportIds, createdBy } = input.body diff --git a/packages/pds/src/api/com/atproto/admin/reverseModerationAction.ts b/packages/pds/src/api/com/atproto/admin/reverseModerationAction.ts index cf7a2b435cf..a8e8d62a3ad 100644 --- a/packages/pds/src/api/com/atproto/admin/reverseModerationAction.ts +++ b/packages/pds/src/api/com/atproto/admin/reverseModerationAction.ts @@ -1,14 +1,59 @@ -import { AtUri } from '@atproto/uri' -import { InvalidRequestError } from '@atproto/xrpc-server' +import { AtUri } from '@atproto/syntax' +import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { TAKEDOWN } from '../../../../lexicon/types/com/atproto/admin/defs' +import { + isRepoRef, + ACKNOWLEDGE, + ESCALATE, + TAKEDOWN, +} from '../../../../lexicon/types/com/atproto/admin/defs' +import { isMain as isStrongRef } from '../../../../lexicon/types/com/atproto/repo/strongRef' +import { authPassthru } from './util' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.reverseModerationAction({ - auth: ctx.adminVerifier, - handler: async ({ input }) => { + auth: ctx.roleVerifier, + handler: async ({ req, input, auth }) => { + const access = auth.credentials const { db, services } = ctx + if (ctx.cfg.bskyAppView.proxyModeration) { + const { data: result } = + await ctx.appViewAgent.com.atproto.admin.reverseModerationAction( + input.body, + authPassthru(req, true), + ) + + const transact = db.transaction(async (dbTxn) => { + const moderationTxn = services.moderation(dbTxn) + // reverse takedowns + if (result.action === TAKEDOWN && isRepoRef(result.subject)) { + await moderationTxn.reverseTakedownRepo({ + did: result.subject.did, + }) + } + if (result.action === TAKEDOWN && isStrongRef(result.subject)) { + await moderationTxn.reverseTakedownRecord({ + uri: new AtUri(result.subject.uri), + }) + } + }) + + try { + await transact + } catch (err) { + req.log.error( + { err, actionId: input.body.id }, + 'proxied moderation action reversal failed', + ) + } + + return { + encoding: 'application/json', + body: result, + } + } + const moderationService = services.moderation(db) const { id, createdBy, reason } = input.body @@ -26,33 +71,35 @@ export default function (server: Server, ctx: AppContext) { ) } - const result = await moderationTxn.logReverseAction({ - id, - createdAt: now, - createdBy, - reason, - }) + // apply access rules + // if less than moderator access then can only reverse ack and escalation actions if ( - result.action === TAKEDOWN && - result.subjectType === 'com.atproto.admin.defs#repoRef' && - result.subjectDid + !access.moderator && + ![ACKNOWLEDGE, ESCALATE].includes(existing.action) ) { - await moderationTxn.reverseTakedownRepo({ - did: result.subjectDid, - }) + throw new AuthRequiredError( + 'Must be a full moderator to reverse this type of action', + ) } - + // if less than moderator access then cannot reverse takedown on an account if ( - result.action === TAKEDOWN && - result.subjectType === 'com.atproto.repo.strongRef' && - result.subjectUri + !access.moderator && + existing.action === TAKEDOWN && + existing.subjectType === 'com.atproto.admin.defs#repoRef' ) { - await moderationTxn.reverseTakedownRecord({ - uri: new AtUri(result.subjectUri), - }) + throw new AuthRequiredError( + 'Must be an admin to reverse an account takedown', + ) } + const result = await moderationTxn.revertAction({ + id, + createdAt: now, + createdBy, + reason, + }) + return result }) diff --git a/packages/pds/src/api/com/atproto/admin/searchRepos.ts b/packages/pds/src/api/com/atproto/admin/searchRepos.ts index ecf2cd73113..3ee72dec892 100644 --- a/packages/pds/src/api/com/atproto/admin/searchRepos.ts +++ b/packages/pds/src/api/com/atproto/admin/searchRepos.ts @@ -2,11 +2,27 @@ import { sql } from 'kysely' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import { ListKeyset } from '../../../../services/account' +import { authPassthru } from './util' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.searchRepos({ - auth: ctx.moderatorVerifier, - handler: async ({ params }) => { + auth: ctx.roleVerifier, + handler: async ({ req, params, auth }) => { + if (ctx.cfg.bskyAppView.proxyModeration) { + // @TODO merge invite details to this list view. could also add + // support for invitedBy param, which is not supported by appview. + const { data: result } = + await ctx.appViewAgent.com.atproto.admin.searchRepos( + params, + authPassthru(req), + ) + return { + encoding: 'application/json', + body: result, + } + } + + const access = auth.credentials const { db, services } = ctx const moderationService = services.moderation(db) const { limit, cursor, invitedBy } = params @@ -22,7 +38,9 @@ export default function (server: Server, ctx: AppContext) { encoding: 'application/json', body: { cursor: keyset.packFromResult(results), - repos: await moderationService.views.repo(results), + repos: await moderationService.views.repo(results, { + includeEmails: access.moderator, + }), }, } } @@ -34,8 +52,13 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: { - cursor: keyset.packFromResult(results), - repos: await moderationService.views.repo(results), + // For did search, we can only find 1 or no match, cursors can be ignored entirely + cursor: term.startsWith('did:') + ? undefined + : keyset.packFromResult(results), + repos: await moderationService.views.repo(results, { + includeEmails: access.moderator, + }), }, } }, diff --git a/packages/pds/src/api/com/atproto/admin/sendEmail.ts b/packages/pds/src/api/com/atproto/admin/sendEmail.ts new file mode 100644 index 00000000000..2d8c400ff95 --- /dev/null +++ b/packages/pds/src/api/com/atproto/admin/sendEmail.ts @@ -0,0 +1,38 @@ +import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server' +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' + +export default function (server: Server, ctx: AppContext) { + server.com.atproto.admin.sendEmail({ + auth: ctx.roleVerifier, + handler: async ({ input, auth }) => { + if (!auth.credentials.admin && !auth.credentials.moderator) { + throw new AuthRequiredError('Insufficient privileges') + } + + const { + content, + recipientDid, + subject = 'Message from Bluesky moderator', + } = input.body + const userInfo = await ctx.db.db + .selectFrom('user_account') + .where('did', '=', recipientDid) + .select('email') + .executeTakeFirst() + + if (!userInfo) { + throw new InvalidRequestError('Recipient not found') + } + + await ctx.moderationMailer.send( + { content }, + { subject, to: userInfo.email }, + ) + return { + encoding: 'application/json', + body: { sent: true }, + } + }, + }) +} diff --git a/packages/pds/src/api/com/atproto/admin/takeModerationAction.ts b/packages/pds/src/api/com/atproto/admin/takeModerationAction.ts index 9e309851e59..fb593b1c957 100644 --- a/packages/pds/src/api/com/atproto/admin/takeModerationAction.ts +++ b/packages/pds/src/api/com/atproto/admin/takeModerationAction.ts @@ -1,16 +1,66 @@ import { CID } from 'multiformats/cid' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { TAKEDOWN } from '../../../../lexicon/types/com/atproto/admin/defs' +import { + isRepoRef, + ACKNOWLEDGE, + ESCALATE, + TAKEDOWN, +} from '../../../../lexicon/types/com/atproto/admin/defs' +import { isMain as isStrongRef } from '../../../../lexicon/types/com/atproto/repo/strongRef' import { getSubject, getAction } from '../moderation/util' import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server' +import { authPassthru } from './util' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.takeModerationAction({ - auth: ctx.moderatorVerifier, - handler: async ({ input, auth }) => { + auth: ctx.roleVerifier, + handler: async ({ req, input, auth }) => { + const access = auth.credentials const { db, services } = ctx + if (ctx.cfg.bskyAppView.proxyModeration) { + const { data: result } = + await ctx.appViewAgent.com.atproto.admin.takeModerationAction( + input.body, + authPassthru(req, true), + ) + + const transact = db.transaction(async (dbTxn) => { + const authTxn = services.auth(dbTxn) + const moderationTxn = services.moderation(dbTxn) + // perform takedowns + if (result.action === TAKEDOWN && isRepoRef(result.subject)) { + await authTxn.revokeRefreshTokensByDid(result.subject.did) + await moderationTxn.takedownRepo({ + takedownId: result.id, + did: result.subject.did, + }) + } + if (result.action === TAKEDOWN && isStrongRef(result.subject)) { + await moderationTxn.takedownRecord({ + takedownId: result.id, + uri: new AtUri(result.subject.uri), + blobCids: result.subjectBlobCids.map((cid) => CID.parse(cid)), + }) + } + }) + + try { + await transact + } catch (err) { + req.log.error( + { err, actionId: result.id }, + 'proxied moderation action failed', + ) + } + + return { + encoding: 'application/json', + body: result, + } + } + const moderationService = services.moderation(db) const { action, @@ -20,18 +70,30 @@ export default function (server: Server, ctx: AppContext) { createLabelVals, negateLabelVals, subjectBlobCids, + durationInHours, } = input.body - if ( - !auth.credentials.admin && - (createLabelVals?.length || - negateLabelVals?.length || - action === TAKEDOWN) - ) { + // apply access rules + + // if less than admin access then can not takedown an account + if (!access.moderator && action === TAKEDOWN && 'did' in subject) { + throw new AuthRequiredError( + 'Must be a full moderator to perform an account takedown', + ) + } + // if less than moderator access then can only take ack and escalation actions + if (!access.moderator && ![ACKNOWLEDGE, ESCALATE].includes(action)) { throw new AuthRequiredError( - 'Must be an admin to takedown or label content', + 'Must be a full moderator to take this type of action', ) } + // if less than moderator access then can not apply labels + if ( + !access.moderator && + (createLabelVals?.length || negateLabelVals?.length) + ) { + throw new AuthRequiredError('Must be a full moderator to label content') + } validateLabels([...(createLabelVals ?? []), ...(negateLabelVals ?? [])]) @@ -47,6 +109,7 @@ export default function (server: Server, ctx: AppContext) { negateLabelVals, createdBy, reason, + durationInHours, }) if ( diff --git a/packages/pds/src/api/com/atproto/admin/updateAccountEmail.ts b/packages/pds/src/api/com/atproto/admin/updateAccountEmail.ts index ad759473a20..851613a6bad 100644 --- a/packages/pds/src/api/com/atproto/admin/updateAccountEmail.ts +++ b/packages/pds/src/api/com/atproto/admin/updateAccountEmail.ts @@ -1,11 +1,14 @@ -import { InvalidRequestError } from '@atproto/xrpc-server' +import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.updateAccountEmail({ - auth: ctx.adminVerifier, - handler: async ({ input }) => { + auth: ctx.roleVerifier, + handler: async ({ input, auth }) => { + if (!auth.credentials.admin) { + throw new AuthRequiredError('Insufficient privileges') + } await ctx.db.transaction(async (dbTxn) => { const accntService = ctx.services.account(dbTxn) const account = await accntService.getAccount(input.body.account) diff --git a/packages/pds/src/api/com/atproto/admin/updateAccountHandle.ts b/packages/pds/src/api/com/atproto/admin/updateAccountHandle.ts index 1d9426b87af..44c2ee5d8b1 100644 --- a/packages/pds/src/api/com/atproto/admin/updateAccountHandle.ts +++ b/packages/pds/src/api/com/atproto/admin/updateAccountHandle.ts @@ -1,61 +1,62 @@ -import { InvalidRequestError } from '@atproto/xrpc-server' -import * as ident from '@atproto/identifier' +import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server' +import { normalizeAndValidateHandle } from '../../../../handle' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { UserAlreadyExistsError } from '../../../../services/account' +import { + HandleSequenceToken, + UserAlreadyExistsError, +} from '../../../../services/account' +import { httpLogger } from '../../../../logger' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.updateAccountHandle({ - auth: ctx.adminVerifier, - handler: async ({ input, req }) => { - const { did } = input.body - let handle: string - try { - handle = ident.normalizeAndEnsureValidHandle(input.body.handle) - } catch (err) { - if (err instanceof ident.InvalidHandleError) { - throw new InvalidRequestError(err.message, 'InvalidHandle') - } else { - throw err - } - } - try { - ident.ensureHandleServiceConstraints( - handle, - ctx.cfg.identity.serviceHandleDomains, - ) - } catch (err) { - if (err instanceof ident.UnsupportedDomainError) { - throw new InvalidRequestError( - 'Unsupported domain', - 'UnsupportedDomain', - ) - } else if (err instanceof ident.ReservedHandleError) { - // we allow this - req.log.info( - { did, handle: input.body }, - 'admin setting reserved handle', - ) - } else { - throw err - } + auth: ctx.roleVerifier, + handler: async ({ input, auth }) => { + if (!auth.credentials.admin) { + throw new AuthRequiredError('Insufficient privileges') } + const { did } = input.body + const handle = await normalizeAndValidateHandle({ + ctx, + handle: input.body.handle, + did, + allowReserved: true, + }) + const existingAccnt = await ctx.services.account(ctx.db).getAccount(did) if (!existingAccnt) { throw new InvalidRequestError(`Account not found: ${did}`) } - await ctx.db.transaction(async (dbTxn) => { - try { - await ctx.services.account(dbTxn).updateHandle(did, handle) - } catch (err) { - if (err instanceof UserAlreadyExistsError) { - throw new InvalidRequestError(`Handle already taken: ${handle}`) + let seqHandleTok: HandleSequenceToken + if (existingAccnt.handle === handle) { + seqHandleTok = { handle, did } + } else { + seqHandleTok = await ctx.db.transaction(async (dbTxn) => { + let tok: HandleSequenceToken + try { + tok = await ctx.services.account(dbTxn).updateHandle(did, handle) + } catch (err) { + if (err instanceof UserAlreadyExistsError) { + throw new InvalidRequestError(`Handle already taken: ${handle}`) + } + throw err } - throw err - } - await ctx.plcClient.updateHandle(did, ctx.plcRotationKey, handle) - }) + await ctx.plcClient.updateHandle(did, ctx.plcRotationKey, handle) + return tok + }) + } + + try { + await ctx.db.transaction(async (dbTxn) => { + await ctx.services.account(dbTxn).sequenceHandle(seqHandleTok) + }) + } catch (err) { + httpLogger.error( + { err, did, handle }, + 'failed to sequence handle update', + ) + } }, }) } diff --git a/packages/pds/src/api/com/atproto/admin/util.ts b/packages/pds/src/api/com/atproto/admin/util.ts new file mode 100644 index 00000000000..f8bab4460a5 --- /dev/null +++ b/packages/pds/src/api/com/atproto/admin/util.ts @@ -0,0 +1,41 @@ +import express from 'express' +import { + RepoView, + RepoViewDetail, +} from '../../../../lexicon/types/com/atproto/admin/defs' + +// Output designed to passed as second arg to AtpAgent methods. +// The encoding field here is a quirk of the AtpAgent. +export function authPassthru( + req: express.Request, + withEncoding?: false, +): { headers: { authorization: string }; encoding: undefined } | undefined + +export function authPassthru( + req: express.Request, + withEncoding: true, +): + | { headers: { authorization: string }; encoding: 'application/json' } + | undefined + +export function authPassthru(req: express.Request, withEncoding?: boolean) { + if (req.headers.authorization) { + return { + headers: { authorization: req.headers.authorization }, + encoding: withEncoding ? 'application/json' : undefined, + } + } +} + +// @NOTE mutates. +// merges-in details that the pds knows about the repo. +export function mergeRepoViewPdsDetails( + other: T, + pds: T, +) { + other.email ??= pds.email + other.invites ??= pds.invites + other.invitedBy ??= pds.invitedBy + other.invitesDisabled ??= pds.invitesDisabled + return other +} diff --git a/packages/pds/src/api/com/atproto/identity/resolveHandle.ts b/packages/pds/src/api/com/atproto/identity/resolveHandle.ts index f6831c6e350..a23f7131eb0 100644 --- a/packages/pds/src/api/com/atproto/identity/resolveHandle.ts +++ b/packages/pds/src/api/com/atproto/identity/resolveHandle.ts @@ -1,6 +1,6 @@ import { AtpAgent } from '@atproto/api' import { InvalidRequestError } from '@atproto/xrpc-server' -import * as ident from '@atproto/identifier' +import * as ident from '@atproto/syntax' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' diff --git a/packages/pds/src/api/com/atproto/identity/updateHandle.ts b/packages/pds/src/api/com/atproto/identity/updateHandle.ts index 9c021a87d80..6db63fab0c0 100644 --- a/packages/pds/src/api/com/atproto/identity/updateHandle.ts +++ b/packages/pds/src/api/com/atproto/identity/updateHandle.ts @@ -1,59 +1,78 @@ import { InvalidRequestError } from '@atproto/xrpc-server' -import * as ident from '@atproto/identifier' +import { normalizeAndValidateHandle } from '../../../../handle' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { UserAlreadyExistsError } from '../../../../services/account' +import { + HandleSequenceToken, + UserAlreadyExistsError, +} from '../../../../services/account' +import { httpLogger } from '../../../../logger' +import { DAY, MINUTE } from '@atproto/common' export default function (server: Server, ctx: AppContext) { server.com.atproto.identity.updateHandle({ auth: ctx.accessVerifierCheckTakedown, + rateLimit: [ + { + durationMs: 5 * MINUTE, + points: 10, + calcKey: ({ auth }) => auth.credentials.did, + }, + { + durationMs: DAY, + points: 50, + calcKey: ({ auth }) => auth.credentials.did, + }, + ], handler: async ({ auth, input }) => { const requester = auth.credentials.did - let handle: string - try { - handle = ident.normalizeAndEnsureValidHandle(input.body.handle) - } catch (err) { - if (err instanceof ident.InvalidHandleError) { - throw new InvalidRequestError(err.message, 'InvalidHandle') + const handle = await normalizeAndValidateHandle({ + ctx, + handle: input.body.handle, + did: requester, + }) + + // Pessimistic check to handle spam: also enforced by updateHandle() and the db. + const handleDid = await ctx.services.account(ctx.db).getHandleDid(handle) + + let seqHandleTok: HandleSequenceToken + if (handleDid) { + if (handleDid !== requester) { + throw new InvalidRequestError(`Handle already taken: ${handle}`) } - throw err + seqHandleTok = { did: requester, handle: handle } + } else { + seqHandleTok = await ctx.db.transaction(async (dbTxn) => { + let tok: HandleSequenceToken + try { + tok = await ctx.services + .account(dbTxn) + .updateHandle(requester, handle) + } catch (err) { + if (err instanceof UserAlreadyExistsError) { + throw new InvalidRequestError(`Handle already taken: ${handle}`) + } + throw err + } + await ctx.plcClient.updateHandle( + requester, + ctx.plcRotationKey, + handle, + ) + return tok + }) } - // test against our service constraints - // if not a supported domain, then we must check that the domain correctly links to the DID try { - ident.ensureHandleServiceConstraints( - handle, - ctx.cfg.identity.serviceHandleDomains, - ) + await ctx.db.transaction(async (dbTxn) => { + await ctx.services.account(dbTxn).sequenceHandle(seqHandleTok) + }) } catch (err) { - if (err instanceof ident.UnsupportedDomainError) { - const did = await ctx.idResolver.handle.resolve(handle) - if (did !== requester) { - throw new InvalidRequestError( - 'External handle did not resolve to DID', - ) - } - } else if (err instanceof ident.InvalidHandleError) { - throw new InvalidRequestError(err.message, 'InvalidHandle') - } else if (err instanceof ident.ReservedHandleError) { - throw new InvalidRequestError(err.message, 'HandleNotAvailable') - } else { - throw err - } + httpLogger.error( + { err, did: requester, handle }, + 'failed to sequence handle update', + ) } - - await ctx.db.transaction(async (dbTxn) => { - try { - await ctx.services.account(dbTxn).updateHandle(requester, handle) - } catch (err) { - if (err instanceof UserAlreadyExistsError) { - throw new InvalidRequestError(`Handle already taken: ${handle}`) - } - throw err - } - await ctx.plcClient.updateHandle(requester, ctx.plcRotationKey, handle) - }) }, }) } diff --git a/packages/pds/src/api/com/atproto/moderation/createReport.ts b/packages/pds/src/api/com/atproto/moderation/createReport.ts index af9f4fc3e9b..83cd5f454e0 100644 --- a/packages/pds/src/api/com/atproto/moderation/createReport.ts +++ b/packages/pds/src/api/com/atproto/moderation/createReport.ts @@ -6,9 +6,25 @@ export default function (server: Server, ctx: AppContext) { server.com.atproto.moderation.createReport({ auth: ctx.accessVerifierCheckTakedown, handler: async ({ input, auth }) => { + const requester = auth.credentials.did + + if (ctx.cfg.bskyAppView.proxyModeration) { + const { data: result } = + await ctx.appViewAgent.com.atproto.moderation.createReport( + input.body, + { + ...(await ctx.serviceAuthHeaders(requester)), + encoding: 'application/json', + }, + ) + return { + encoding: 'application/json', + body: result, + } + } + const { db, services } = ctx const { reasonType, reason, subject } = input.body - const requester = auth.credentials.did const moderationService = services.moderation(db) diff --git a/packages/pds/src/api/com/atproto/moderation/util.ts b/packages/pds/src/api/com/atproto/moderation/util.ts index b4e403f0fcc..89ee2f1ac92 100644 --- a/packages/pds/src/api/com/atproto/moderation/util.ts +++ b/packages/pds/src/api/com/atproto/moderation/util.ts @@ -1,5 +1,5 @@ import { InvalidRequestError } from '@atproto/xrpc-server' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { ModerationAction } from '../../../../db/tables/moderation' import { ModerationReport } from '../../../../db/tables/moderation' import { InputSchema as ReportInput } from '../../../../lexicon/types/com/atproto/moderation/createReport' diff --git a/packages/pds/src/api/com/atproto/repo/applyWrites.ts b/packages/pds/src/api/com/atproto/repo/applyWrites.ts index 1cee83bdbce..5dc65100855 100644 --- a/packages/pds/src/api/com/atproto/repo/applyWrites.ts +++ b/packages/pds/src/api/com/atproto/repo/applyWrites.ts @@ -1,6 +1,6 @@ import { CID } from 'multiformats/cid' import { InvalidRequestError, AuthRequiredError } from '@atproto/xrpc-server' -import { prepareCreate, prepareDelete } from '../../../../repo' +import { prepareCreate, prepareDelete, prepareUpdate } from '../../../../repo' import { Server } from '../../../../lexicon' import { isCreate, @@ -38,11 +38,6 @@ export default function (server: Server, ctx: AppContext) { throw new InvalidRequestError('Too many writes. Max: 200') } - const hasUpdate = tx.writes.some(isUpdate) - if (hasUpdate) { - throw new InvalidRequestError(`Updates are not yet supported.`) - } - let writes: PreparedWrite[] try { writes = await Promise.all( @@ -55,6 +50,14 @@ export default function (server: Server, ctx: AppContext) { rkey: write.rkey, validate, }) + } else if (isUpdate(write)) { + return prepareUpdate({ + did, + collection: write.collection, + record: write.value, + rkey: write.rkey, + validate, + }) } else if (isDelete(write)) { return prepareDelete({ did, diff --git a/packages/pds/src/api/com/atproto/repo/createRecord.ts b/packages/pds/src/api/com/atproto/repo/createRecord.ts index 978a3945253..08ff46ecf78 100644 --- a/packages/pds/src/api/com/atproto/repo/createRecord.ts +++ b/packages/pds/src/api/com/atproto/repo/createRecord.ts @@ -1,5 +1,4 @@ import { CID } from 'multiformats/cid' -import { TID } from '@atproto/common' import { InvalidRequestError, AuthRequiredError } from '@atproto/xrpc-server' import { prepareCreate } from '../../../../repo' import { Server } from '../../../../lexicon' @@ -33,7 +32,6 @@ export default function (server: Server, ctx: AppContext) { 'Unvalidated writes are not yet supported.', ) } - const swapCommitCid = swapCommit ? CID.parse(swapCommit) : undefined let write: PreparedCreate @@ -42,7 +40,7 @@ export default function (server: Server, ctx: AppContext) { did, collection, record, - rkey: rkey || TID.nextStr(), + rkey, validate, }) } catch (err) { diff --git a/packages/pds/src/api/com/atproto/repo/getRecord.ts b/packages/pds/src/api/com/atproto/repo/getRecord.ts index f62e64e16c0..cd5a06a48b2 100644 --- a/packages/pds/src/api/com/atproto/repo/getRecord.ts +++ b/packages/pds/src/api/com/atproto/repo/getRecord.ts @@ -1,4 +1,4 @@ -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' diff --git a/packages/pds/src/api/com/atproto/repo/index.ts b/packages/pds/src/api/com/atproto/repo/index.ts index a95a47c9ba7..ce13a10fe15 100644 --- a/packages/pds/src/api/com/atproto/repo/index.ts +++ b/packages/pds/src/api/com/atproto/repo/index.ts @@ -7,7 +7,6 @@ import describeRepo from './describeRepo' import getRecord from './getRecord' import listRecords from './listRecords' import putRecord from './putRecord' -import rebaseRepo from './rebaseRepo' import uploadBlob from './uploadBlob' export default function (server: Server, ctx: AppContext) { @@ -18,6 +17,5 @@ export default function (server: Server, ctx: AppContext) { getRecord(server, ctx) listRecords(server, ctx) putRecord(server, ctx) - rebaseRepo(server, ctx) uploadBlob(server, ctx) } diff --git a/packages/pds/src/api/com/atproto/repo/listRecords.ts b/packages/pds/src/api/com/atproto/repo/listRecords.ts index c99be4677b2..8c2669ff010 100644 --- a/packages/pds/src/api/com/atproto/repo/listRecords.ts +++ b/packages/pds/src/api/com/atproto/repo/listRecords.ts @@ -1,5 +1,5 @@ import { InvalidRequestError } from '@atproto/xrpc-server' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' diff --git a/packages/pds/src/api/com/atproto/repo/putRecord.ts b/packages/pds/src/api/com/atproto/repo/putRecord.ts index dc06efbb071..311083359f2 100644 --- a/packages/pds/src/api/com/atproto/repo/putRecord.ts +++ b/packages/pds/src/api/com/atproto/repo/putRecord.ts @@ -1,8 +1,7 @@ import { CID } from 'multiformats/cid' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' -import { ids } from '../../../../lexicon/lexicons' import { prepareUpdate, prepareCreate } from '../../../../repo' import AppContext from '../../../../context' import { @@ -14,12 +13,6 @@ import { } from '../../../../repo' import { ConcurrentWriteError } from '../../../../services/repo' -const ALLOWED_PUTS = [ - ids.AppBskyActorProfile, - ids.AppBskyGraphList, - ids.AppBskyFeedGenerator, -] - export default function (server: Server, ctx: AppContext) { server.com.atproto.repo.putRecord({ auth: ctx.accessVerifierCheckTakedown, @@ -41,14 +34,6 @@ export default function (server: Server, ctx: AppContext) { if (did !== auth.credentials.did) { throw new AuthRequiredError() } - if (!ALLOWED_PUTS.includes(collection)) { - // @TODO temporary - throw new InvalidRequestError( - `Temporarily only accepting puts for collections: ${ALLOWED_PUTS.join( - ', ', - )}`, - ) - } if (validate === false) { throw new InvalidRequestError( 'Unvalidated writes are not yet supported.', diff --git a/packages/pds/src/api/com/atproto/repo/rebaseRepo.ts b/packages/pds/src/api/com/atproto/repo/rebaseRepo.ts deleted file mode 100644 index ec3d48ce436..00000000000 --- a/packages/pds/src/api/com/atproto/repo/rebaseRepo.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { CID } from 'multiformats/cid' -import { InvalidRequestError, AuthRequiredError } from '@atproto/xrpc-server' -import { Server } from '../../../../lexicon' -import { BadCommitSwapError } from '../../../../repo' -import AppContext from '../../../../context' -import { ConcurrentWriteError } from '../../../../services/repo' - -export default function (server: Server, ctx: AppContext) { - server.com.atproto.repo.rebaseRepo({ - auth: ctx.accessVerifierNotAppPassword, - handler: async ({ input, auth }) => { - const { repo, swapCommit } = input.body - const did = await ctx.services.account(ctx.db).getDidForActor(repo) - - if (!did) { - throw new InvalidRequestError(`Could not find repo: ${repo}`) - } else if (did !== auth.credentials.did) { - throw new AuthRequiredError() - } - - const swapCommitCid = swapCommit ? CID.parse(swapCommit) : undefined - - try { - await ctx.services.repo(ctx.db).rebaseRepo(repo, swapCommitCid) - } catch (err) { - if (err instanceof BadCommitSwapError) { - throw new InvalidRequestError(err.message, 'InvalidSwap') - } else if (err instanceof ConcurrentWriteError) { - throw new InvalidRequestError(err.message, 'ConcurrentWrites') - } - throw err - } - }, - }) -} diff --git a/packages/pds/src/api/com/atproto/server/createAccount.ts b/packages/pds/src/api/com/atproto/server/createAccount.ts index 714edc329ae..a3436445b95 100644 --- a/packages/pds/src/api/com/atproto/server/createAccount.ts +++ b/packages/pds/src/api/com/atproto/server/createAccount.ts @@ -1,5 +1,5 @@ import { InvalidRequestError } from '@atproto/xrpc-server' -import * as ident from '@atproto/identifier' +import { normalizeAndValidateHandle } from '../../../../handle' import * as plc from '@did-plc/lib' import * as scrypt from '../../../../db/scrypt' import { Server } from '../../../../lexicon' @@ -9,107 +9,118 @@ import { UserAlreadyExistsError } from '../../../../services/account' import AppContext from '../../../../context' import Database from '../../../../db' import { AtprotoData } from '@atproto/identity' +import { MINUTE } from '@atproto/common' export default function (server: Server, ctx: AppContext) { - server.com.atproto.server.createAccount(async ({ input, req }) => { - const { email, password, inviteCode } = input.body - - if (ctx.cfg.invites.required && !inviteCode) { - throw new InvalidRequestError( - 'No invite code provided', - 'InvalidInviteCode', - ) - } - - // normalize & ensure valid handle - const handle = await ensureValidHandle(ctx, input.body) + server.com.atproto.server.createAccount({ + rateLimit: { + durationMs: 5 * MINUTE, + points: 100, + }, + handler: async ({ input, req }) => { + const { email, password, inviteCode } = input.body + + if (ctx.cfg.invites.required && !inviteCode) { + throw new InvalidRequestError( + 'No invite code provided', + 'InvalidInviteCode', + ) + } - // check that the invite code still has uses - if (ctx.cfg.invites.required && inviteCode) { - await ensureCodeIsAvailable(ctx.db, inviteCode) - } + // normalize & ensure valid handle + const handle = await normalizeAndValidateHandle({ + ctx, + handle: input.body.handle, + did: input.body.did, + }) - // determine the did & any plc ops we need to send - // if the provided did document is poorly setup, we throw - const { did, plcOp } = await getDidAndPlcOp(ctx, handle, input.body) + // check that the invite code still has uses + if (ctx.cfg.invites.required && inviteCode) { + await ensureCodeIsAvailable(ctx.db, inviteCode) + } - const now = new Date().toISOString() - const passwordScrypt = await scrypt.genSaltAndHash(password) + // determine the did & any plc ops we need to send + // if the provided did document is poorly setup, we throw + const { did, plcOp } = await getDidAndPlcOp(ctx, handle, input.body) - const result = await ctx.db.transaction(async (dbTxn) => { - const actorTxn = ctx.services.account(dbTxn) - const repoTxn = ctx.services.repo(dbTxn) + const now = new Date().toISOString() + const passwordScrypt = await scrypt.genSaltAndHash(password) - // it's a bit goofy that we run this logic twice, - // but we run it once for a sanity check before doing scrypt & plc ops - // & a second time for locking + integrity check - if (ctx.cfg.invites.required && inviteCode) { - await ensureCodeIsAvailable(dbTxn, inviteCode, true) - } + const result = await ctx.db.transaction(async (dbTxn) => { + const actorTxn = ctx.services.account(dbTxn) + const repoTxn = ctx.services.repo(dbTxn) - // Register user before going out to PLC to get a real did - try { - await actorTxn.registerUser({ email, handle, did, passwordScrypt }) - } catch (err) { - if (err instanceof UserAlreadyExistsError) { - const got = await actorTxn.getAccount(handle, true) - if (got) { - throw new InvalidRequestError(`Handle already taken: ${handle}`) - } else { - throw new InvalidRequestError(`Email already taken: ${email}`) - } + // it's a bit goofy that we run this logic twice, + // but we run it once for a sanity check before doing scrypt & plc ops + // & a second time for locking + integrity check + if (ctx.cfg.invites.required && inviteCode) { + await ensureCodeIsAvailable(dbTxn, inviteCode, true) } - throw err - } - // Generate a real did with PLC - if (plcOp) { + // Register user before going out to PLC to get a real did try { - await ctx.plcClient.sendOperation(did, plcOp) + await actorTxn.registerUser({ email, handle, did, passwordScrypt }) } catch (err) { - req.log.error( - { didKey: ctx.plcRotationKey.did(), handle }, - 'failed to create did:plc', - ) + if (err instanceof UserAlreadyExistsError) { + const got = await actorTxn.getAccount(handle, true) + if (got) { + throw new InvalidRequestError(`Handle already taken: ${handle}`) + } else { + throw new InvalidRequestError(`Email already taken: ${email}`) + } + } throw err } - } - // insert invite code use - if (ctx.cfg.invites.required && inviteCode) { - await dbTxn.db - .insertInto('invite_code_use') - .values({ - code: inviteCode, - usedBy: did, - usedAt: now, - }) - .execute() - } + // Generate a real did with PLC + if (plcOp) { + try { + await ctx.plcClient.sendOperation(did, plcOp) + } catch (err) { + req.log.error( + { didKey: ctx.plcRotationKey.did(), handle }, + 'failed to create did:plc', + ) + throw err + } + } + + // insert invite code use + if (ctx.cfg.invites.required && inviteCode) { + await dbTxn.db + .insertInto('invite_code_use') + .values({ + code: inviteCode, + usedBy: did, + usedAt: now, + }) + .execute() + } - const access = ctx.auth.createAccessToken({ did }) - const refresh = ctx.auth.createRefreshToken({ did }) - await ctx.services.auth(dbTxn).grantRefreshToken(refresh.payload, null) + const access = ctx.auth.createAccessToken({ did }) + const refresh = ctx.auth.createRefreshToken({ did }) + await ctx.services.auth(dbTxn).grantRefreshToken(refresh.payload, null) - // Setup repo root - await repoTxn.createRepo(did, [], now) + // Setup repo root + await repoTxn.createRepo(did, [], now) + + return { + did, + accessJwt: access.jwt, + refreshJwt: refresh.jwt, + } + }) return { - did, - accessJwt: access.jwt, - refreshJwt: refresh.jwt, + encoding: 'application/json', + body: { + handle, + did: result.did, + accessJwt: result.accessJwt, + refreshJwt: result.refreshJwt, + }, } - }) - - return { - encoding: 'application/json', - body: { - handle, - did: result.did, - accessJwt: result.accessJwt, - refreshJwt: result.refreshJwt, - }, - } + }, }) } @@ -118,20 +129,35 @@ export const ensureCodeIsAvailable = async ( inviteCode: string, withLock = false, ): Promise => { + const { ref } = db.db.dynamic const invite = await db.db .selectFrom('invite_code') .selectAll() + .whereNotExists((qb) => + qb + .selectFrom('repo_root') + .selectAll() + .where('takedownId', 'is not', null) + .whereRef('did', '=', ref('invite_code.forUser')), + ) .where('code', '=', inviteCode) .if(withLock && db.dialect === 'pg', (qb) => qb.forUpdate().skipLocked()) .executeTakeFirst() + if (!invite || invite.disabled) { + throw new InvalidRequestError( + 'Provided invite code not available', + 'InvalidInviteCode', + ) + } + const uses = await db.db .selectFrom('invite_code_use') .select(countAll.as('count')) .where('code', '=', inviteCode) .executeTakeFirstOrThrow() - if (!invite || invite.disabled || invite.availableUses <= uses.count) { + if (invite.availableUses <= uses.count) { throw new InvalidRequestError( 'Provided invite code not available', 'InvalidInviteCode', @@ -139,37 +165,6 @@ export const ensureCodeIsAvailable = async ( } } -const ensureValidHandle = async ( - ctx: AppContext, - input: CreateAccountInput, -): Promise => { - try { - const handle = ident.normalizeAndEnsureValidHandle(input.handle) - ident.ensureHandleServiceConstraints( - handle, - ctx.cfg.identity.serviceHandleDomains, - ) - return handle - } catch (err) { - if (err instanceof ident.InvalidHandleError) { - throw new InvalidRequestError(err.message, 'InvalidHandle') - } else if (err instanceof ident.ReservedHandleError) { - throw new InvalidRequestError(err.message, 'HandleNotAvailable') - } else if (err instanceof ident.UnsupportedDomainError) { - if (input.did === undefined) { - throw new InvalidRequestError(err.message, 'UnsupportedDomain') - } - const resolvedHandleDid = await ctx.idResolver.handle.resolve( - input.handle, - ) - if (input.did !== resolvedHandleDid) { - throw new InvalidRequestError('External handle did not resolve to DID') - } - } - throw err - } -} - const getDidAndPlcOp = async ( ctx: AppContext, handle: string, diff --git a/packages/pds/src/api/com/atproto/server/createInviteCode.ts b/packages/pds/src/api/com/atproto/server/createInviteCode.ts index 84a685e4b72..6c8e8b8b714 100644 --- a/packages/pds/src/api/com/atproto/server/createInviteCode.ts +++ b/packages/pds/src/api/com/atproto/server/createInviteCode.ts @@ -1,11 +1,15 @@ +import { AuthRequiredError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import { genInvCode } from './util' export default function (server: Server, ctx: AppContext) { server.com.atproto.server.createInviteCode({ - auth: ctx.adminVerifier, - handler: async ({ input, req }) => { + auth: ctx.roleVerifier, + handler: async ({ input, req, auth }) => { + if (!auth.credentials.admin) { + throw new AuthRequiredError('Insufficient privileges') + } const { useCount, forAccount = 'admin' } = input.body const code = genInvCode(ctx.cfg) diff --git a/packages/pds/src/api/com/atproto/server/createInviteCodes.ts b/packages/pds/src/api/com/atproto/server/createInviteCodes.ts index dddbf7c0893..d2d043e6ec7 100644 --- a/packages/pds/src/api/com/atproto/server/createInviteCodes.ts +++ b/packages/pds/src/api/com/atproto/server/createInviteCodes.ts @@ -1,14 +1,18 @@ +import { chunkArray } from '@atproto/common' +import { AuthRequiredError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import { genInvCodes } from './util' import { InviteCode } from '../../../../db/tables/invite-code' import { AccountCodes } from '../../../../lexicon/types/com/atproto/server/createInviteCodes' -import { chunkArray } from '@atproto/common' export default function (server: Server, ctx: AppContext) { server.com.atproto.server.createInviteCodes({ - auth: ctx.adminVerifier, - handler: async ({ input, req }) => { + auth: ctx.roleVerifier, + handler: async ({ input, req, auth }) => { + if (!auth.credentials.admin) { + throw new AuthRequiredError('Insufficient privileges') + } const { codeCount, useCount } = input.body const forAccounts = input.body.forAccounts ?? ['admin'] diff --git a/packages/pds/src/api/com/atproto/server/createSession.ts b/packages/pds/src/api/com/atproto/server/createSession.ts index 3604ded5a81..aee0063d86c 100644 --- a/packages/pds/src/api/com/atproto/server/createSession.ts +++ b/packages/pds/src/api/com/atproto/server/createSession.ts @@ -3,57 +3,75 @@ import AppContext from '../../../../context' import { softDeleted } from '../../../../db/util' import { Server } from '../../../../lexicon' import { AuthScope } from '../../../../auth' +import { DAY, MINUTE } from '@atproto/common' export default function (server: Server, ctx: AppContext) { - server.com.atproto.server.createSession(async ({ input }) => { - const { password } = input.body - const identifier = input.body.identifier.toLowerCase() - const authService = ctx.services.auth(ctx.db) - const actorService = ctx.services.account(ctx.db) - - const user = identifier.includes('@') - ? await actorService.getAccountByEmail(identifier, true) - : await actorService.getAccount(identifier, true) + server.com.atproto.server.createSession({ + rateLimit: [ + { + durationMs: DAY, + points: 300, + calcKey: ({ input }) => input.body.identifier, + }, + { + durationMs: 5 * MINUTE, + points: 30, + calcKey: ({ input }) => input.body.identifier, + }, + ], + handler: async ({ input }) => { + const { password } = input.body + const identifier = input.body.identifier.toLowerCase() + const authService = ctx.services.auth(ctx.db) + const actorService = ctx.services.account(ctx.db) - if (!user) { - throw new AuthRequiredError('Invalid identifier or password') - } + const user = identifier.includes('@') + ? await actorService.getAccountByEmail(identifier, true) + : await actorService.getAccount(identifier, true) - let appPasswordName: string | null = null - const validAccountPass = await actorService.verifyAccountPassword( - user.did, - password, - ) - if (!validAccountPass) { - appPasswordName = await actorService.verifyAppPassword(user.did, password) - if (appPasswordName === null) { + if (!user) { throw new AuthRequiredError('Invalid identifier or password') } - } - if (softDeleted(user)) { - throw new AuthRequiredError( - 'Account has been taken down', - 'AccountTakedown', + let appPasswordName: string | null = null + const validAccountPass = await actorService.verifyAccountPassword( + user.did, + password, ) - } + if (!validAccountPass) { + appPasswordName = await actorService.verifyAppPassword( + user.did, + password, + ) + if (appPasswordName === null) { + throw new AuthRequiredError('Invalid identifier or password') + } + } - const access = ctx.auth.createAccessToken({ - did: user.did, - scope: appPasswordName === null ? AuthScope.Access : AuthScope.AppPass, - }) - const refresh = ctx.auth.createRefreshToken({ did: user.did }) - await authService.grantRefreshToken(refresh.payload, appPasswordName) + if (softDeleted(user)) { + throw new AuthRequiredError( + 'Account has been taken down', + 'AccountTakedown', + ) + } - return { - encoding: 'application/json', - body: { + const access = ctx.auth.createAccessToken({ did: user.did, - handle: user.handle, - email: user.email, - accessJwt: access.jwt, - refreshJwt: refresh.jwt, - }, - } + scope: appPasswordName === null ? AuthScope.Access : AuthScope.AppPass, + }) + const refresh = ctx.auth.createRefreshToken({ did: user.did }) + await authService.grantRefreshToken(refresh.payload, appPasswordName) + + return { + encoding: 'application/json', + body: { + did: user.did, + handle: user.handle, + email: user.email, + accessJwt: access.jwt, + refreshJwt: refresh.jwt, + }, + } + }, }) } diff --git a/packages/pds/src/api/com/atproto/server/deleteAccount.ts b/packages/pds/src/api/com/atproto/server/deleteAccount.ts index dfdc3c47721..9ebfcfa7fdf 100644 --- a/packages/pds/src/api/com/atproto/server/deleteAccount.ts +++ b/packages/pds/src/api/com/atproto/server/deleteAccount.ts @@ -3,80 +3,91 @@ import { Server } from '../../../../lexicon' import { TAKEDOWN } from '../../../../lexicon/types/com/atproto/admin/defs' import AppContext from '../../../../context' import Database from '../../../../db' +import { MINUTE } from '@atproto/common' const REASON_ACCT_DELETION = 'ACCOUNT DELETION' export default function (server: Server, ctx: AppContext) { - server.com.atproto.server.deleteAccount(async ({ input, req }) => { - const { did, password, token } = input.body - const validPass = await ctx.services - .account(ctx.db) - .verifyAccountPassword(did, password) - if (!validPass) { - throw new AuthRequiredError('Invalid did or password') - } - - const tokenInfo = await ctx.db.db - .selectFrom('did_handle') - .innerJoin('delete_account_token as token', 'token.did', 'did_handle.did') - .where('did_handle.did', '=', did) - .where('token.token', '=', token.toUpperCase()) - .select([ - 'token.token as token', - 'token.requestedAt as requestedAt', - 'token.did as did', - ]) - .executeTakeFirst() + server.com.atproto.server.deleteAccount({ + rateLimit: { + durationMs: 5 * MINUTE, + points: 50, + }, + handler: async ({ input, req }) => { + const { did, password, token } = input.body + const validPass = await ctx.services + .account(ctx.db) + .verifyAccountPassword(did, password) + if (!validPass) { + throw new AuthRequiredError('Invalid did or password') + } - if (!tokenInfo) { - return createInvalidTokenError() - } + const tokenInfo = await ctx.db.db + .selectFrom('did_handle') + .innerJoin( + 'delete_account_token as token', + 'token.did', + 'did_handle.did', + ) + .where('did_handle.did', '=', did) + .where('token.token', '=', token.toUpperCase()) + .select([ + 'token.token as token', + 'token.requestedAt as requestedAt', + 'token.did as did', + ]) + .executeTakeFirst() - const now = new Date() - const requestedAt = new Date(tokenInfo.requestedAt) - const expiresAt = new Date(requestedAt.getTime() + 15 * minsToMs) - if (now > expiresAt) { - await removeDeleteToken(ctx.db, tokenInfo.did) - return createExpiredTokenError() - } + if (!tokenInfo) { + return createInvalidTokenError() + } - await ctx.db.transaction(async (dbTxn) => { - const moderationTxn = ctx.services.moderation(dbTxn) - const [currentAction] = await moderationTxn.getCurrentActions({ did }) - if (currentAction?.action === TAKEDOWN) { - // Do not disturb an existing takedown, continue with account deletion - return await removeDeleteToken(dbTxn, did) + const now = new Date() + const requestedAt = new Date(tokenInfo.requestedAt) + const expiresAt = new Date(requestedAt.getTime() + 15 * minsToMs) + if (now > expiresAt) { + await removeDeleteToken(ctx.db, tokenInfo.did) + return createExpiredTokenError() } - if (currentAction) { - // Reverse existing action to replace it with a self-takedown - await moderationTxn.logReverseAction({ - id: currentAction.id, + + await ctx.db.transaction(async (dbTxn) => { + const moderationTxn = ctx.services.moderation(dbTxn) + const [currentAction] = await moderationTxn.getCurrentActions({ did }) + if (currentAction?.action === TAKEDOWN) { + // Do not disturb an existing takedown, continue with account deletion + return await removeDeleteToken(dbTxn, did) + } + if (currentAction) { + // Reverse existing action to replace it with a self-takedown + await moderationTxn.logReverseAction({ + id: currentAction.id, + reason: REASON_ACCT_DELETION, + createdBy: did, + createdAt: now, + }) + } + const takedown = await moderationTxn.logAction({ + action: TAKEDOWN, + subject: { did }, reason: REASON_ACCT_DELETION, createdBy: did, createdAt: now, }) - } - const takedown = await moderationTxn.logAction({ - action: TAKEDOWN, - subject: { did }, - reason: REASON_ACCT_DELETION, - createdBy: did, - createdAt: now, + await moderationTxn.takedownRepo({ did, takedownId: takedown.id }) + await removeDeleteToken(dbTxn, did) }) - await moderationTxn.takedownRepo({ did, takedownId: takedown.id }) - await removeDeleteToken(dbTxn, did) - }) - ctx.backgroundQueue.add(async (db) => { - try { - // In the background perform the hard account deletion work - await ctx.services.record(db).deleteForActor(did) - await ctx.services.repo(db).deleteRepo(did) - await ctx.services.account(db).deleteAccount(did) - } catch (err) { - req.log.error({ did, err }, 'account deletion failed') - } - }) + ctx.backgroundQueue.add(async (db) => { + try { + // In the background perform the hard account deletion work + await ctx.services.record(db).deleteForActor(did) + await ctx.services.repo(db).deleteRepo(did) + await ctx.services.account(db).deleteAccount(did) + } catch (err) { + req.log.error({ did, err }, 'account deletion failed') + } + }) + }, }) } diff --git a/packages/pds/src/api/com/atproto/server/resetPassword.ts b/packages/pds/src/api/com/atproto/server/resetPassword.ts index 460fb144132..de8d10382c0 100644 --- a/packages/pds/src/api/com/atproto/server/resetPassword.ts +++ b/packages/pds/src/api/com/atproto/server/resetPassword.ts @@ -1,39 +1,48 @@ import AppContext from '../../../../context' import { Server } from '../../../../lexicon' import Database from '../../../../db' +import { MINUTE } from '@atproto/common' export default function (server: Server, ctx: AppContext) { - server.com.atproto.server.resetPassword(async ({ input }) => { - const { token, password } = input.body + server.com.atproto.server.resetPassword({ + rateLimit: [ + { + durationMs: 5 * MINUTE, + points: 50, + }, + ], + handler: async ({ input }) => { + const { token, password } = input.body - const tokenInfo = await ctx.db.db - .selectFrom('user_account') - .select(['did', 'passwordResetGrantedAt']) - .where('passwordResetToken', '=', token.toUpperCase()) - .executeTakeFirst() + const tokenInfo = await ctx.db.db + .selectFrom('user_account') + .select(['did', 'passwordResetGrantedAt']) + .where('passwordResetToken', '=', token.toUpperCase()) + .executeTakeFirst() - if (!tokenInfo?.passwordResetGrantedAt) { - return createInvalidTokenError() - } + if (!tokenInfo?.passwordResetGrantedAt) { + return createInvalidTokenError() + } - const now = new Date() - const grantedAt = new Date(tokenInfo.passwordResetGrantedAt) - const expiresAt = new Date(grantedAt.getTime() + 15 * minsToMs) + const now = new Date() + const grantedAt = new Date(tokenInfo.passwordResetGrantedAt) + const expiresAt = new Date(grantedAt.getTime() + 15 * minsToMs) - if (now > expiresAt) { - await unsetResetToken(ctx.db, tokenInfo.did) - return createExpiredTokenError() - } + if (now > expiresAt) { + await unsetResetToken(ctx.db, tokenInfo.did) + return createExpiredTokenError() + } - await ctx.db.transaction(async (dbTxn) => { - await unsetResetToken(dbTxn, tokenInfo.did) - await ctx.services - .account(dbTxn) - .updateUserPassword(tokenInfo.did, password) - await await ctx.services - .auth(dbTxn) - .revokeRefreshTokensByDid(tokenInfo.did) - }) + await ctx.db.transaction(async (dbTxn) => { + await unsetResetToken(dbTxn, tokenInfo.did) + await ctx.services + .account(dbTxn) + .updateUserPassword(tokenInfo.did, password) + await await ctx.services + .auth(dbTxn) + .revokeRefreshTokensByDid(tokenInfo.did) + }) + }, }) } diff --git a/packages/pds/src/api/com/atproto/sync/deprecated/getCheckout.ts b/packages/pds/src/api/com/atproto/sync/deprecated/getCheckout.ts new file mode 100644 index 00000000000..cbde7131c66 --- /dev/null +++ b/packages/pds/src/api/com/atproto/sync/deprecated/getCheckout.ts @@ -0,0 +1,42 @@ +import { InvalidRequestError } from '@atproto/xrpc-server' +import { byteIterableToStream } from '@atproto/common' +import { Server } from '../../../../../lexicon' +import SqlRepoStorage, { + RepoRootNotFoundError, +} from '../../../../../sql-repo-storage' +import AppContext from '../../../../../context' +import { isUserOrAdmin } from '../../../../../auth' + +export default function (server: Server, ctx: AppContext) { + server.com.atproto.sync.getCheckout({ + auth: ctx.optionalAccessOrRoleVerifier, + handler: async ({ params, auth }) => { + const { did } = params + // takedown check for anyone other than an admin or the user + if (!isUserOrAdmin(auth, did)) { + const available = await ctx.services + .account(ctx.db) + .isRepoAvailable(did) + if (!available) { + throw new InvalidRequestError(`Could not find repo for DID: ${did}`) + } + } + + const storage = new SqlRepoStorage(ctx.db, did) + let carStream: AsyncIterable + try { + carStream = await storage.getCarStream() + } catch (err) { + if (err instanceof RepoRootNotFoundError) { + throw new InvalidRequestError(`Could not find repo for DID: ${did}`) + } + throw err + } + + return { + encoding: 'application/vnd.ipld.car', + body: byteIterableToStream(carStream), + } + }, + }) +} diff --git a/packages/pds/src/api/com/atproto/sync/getHead.ts b/packages/pds/src/api/com/atproto/sync/deprecated/getHead.ts similarity index 76% rename from packages/pds/src/api/com/atproto/sync/getHead.ts rename to packages/pds/src/api/com/atproto/sync/deprecated/getHead.ts index 17740fa1e13..acde9cebc38 100644 --- a/packages/pds/src/api/com/atproto/sync/getHead.ts +++ b/packages/pds/src/api/com/atproto/sync/deprecated/getHead.ts @@ -1,12 +1,12 @@ import { InvalidRequestError } from '@atproto/xrpc-server' -import { Server } from '../../../../lexicon' -import SqlRepoStorage from '../../../../sql-repo-storage' -import AppContext from '../../../../context' -import { isUserOrAdmin } from '../../../../auth' +import { Server } from '../../../../../lexicon' +import SqlRepoStorage from '../../../../../sql-repo-storage' +import AppContext from '../../../../../context' +import { isUserOrAdmin } from '../../../../../auth' export default function (server: Server, ctx: AppContext) { server.com.atproto.sync.getHead({ - auth: ctx.optionalAccessOrAdminVerifier, + auth: ctx.optionalAccessOrRoleVerifier, handler: async ({ params, auth }) => { const { did } = params // takedown check for anyone other than an admin or the user @@ -22,7 +22,7 @@ export default function (server: Server, ctx: AppContext) { } } const storage = new SqlRepoStorage(ctx.db, did) - const root = await storage.getHead() + const root = await storage.getRoot() if (root === null) { throw new InvalidRequestError( `Could not find root for DID: ${did}`, diff --git a/packages/pds/src/api/com/atproto/sync/getBlob.ts b/packages/pds/src/api/com/atproto/sync/getBlob.ts index 65f95ac79bb..b92154af80f 100644 --- a/packages/pds/src/api/com/atproto/sync/getBlob.ts +++ b/packages/pds/src/api/com/atproto/sync/getBlob.ts @@ -8,7 +8,7 @@ import { BlobNotFoundError } from '@atproto/repo' export default function (server: Server, ctx: AppContext) { server.com.atproto.sync.getBlob({ - auth: ctx.optionalAccessOrAdminVerifier, + auth: ctx.optionalAccessOrRoleVerifier, handler: async ({ params, res, auth }) => { const { ref } = ctx.db.db.dynamic const found = await ctx.db.db diff --git a/packages/pds/src/api/com/atproto/sync/getBlocks.ts b/packages/pds/src/api/com/atproto/sync/getBlocks.ts index 56ac1b7f78a..a4a85355f13 100644 --- a/packages/pds/src/api/com/atproto/sync/getBlocks.ts +++ b/packages/pds/src/api/com/atproto/sync/getBlocks.ts @@ -1,15 +1,15 @@ import { CID } from 'multiformats/cid' import { InvalidRequestError } from '@atproto/xrpc-server' +import { byteIterableToStream } from '@atproto/common' +import { blocksToCarStream } from '@atproto/repo' import { Server } from '../../../../lexicon' import SqlRepoStorage from '../../../../sql-repo-storage' import AppContext from '../../../../context' -import { blocksToCarStream } from '@atproto/repo' -import { byteIterableToStream } from '@atproto/common' import { isUserOrAdmin } from '../../../../auth' export default function (server: Server, ctx: AppContext) { server.com.atproto.sync.getBlocks({ - auth: ctx.optionalAccessOrAdminVerifier, + auth: ctx.optionalAccessOrRoleVerifier, handler: async ({ params, auth }) => { const { did } = params // takedown check for anyone other than an admin or the user @@ -21,6 +21,7 @@ export default function (server: Server, ctx: AppContext) { throw new InvalidRequestError(`Could not find repo for DID: ${did}`) } } + const cids = params.cids.map((c) => CID.parse(c)) const storage = new SqlRepoStorage(ctx.db, did) const got = await storage.getBlocks(cids) diff --git a/packages/pds/src/api/com/atproto/sync/getCommitPath.ts b/packages/pds/src/api/com/atproto/sync/getCommitPath.ts deleted file mode 100644 index b8cbbd4ec02..00000000000 --- a/packages/pds/src/api/com/atproto/sync/getCommitPath.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { CID } from 'multiformats/cid' -import { InvalidRequestError } from '@atproto/xrpc-server' -import { Server } from '../../../../lexicon' -import SqlRepoStorage from '../../../../sql-repo-storage' -import AppContext from '../../../../context' -import { isUserOrAdmin } from '../../../../auth' - -export default function (server: Server, ctx: AppContext) { - server.com.atproto.sync.getCommitPath({ - auth: ctx.optionalAccessOrAdminVerifier, - handler: async ({ params, auth }) => { - const { did } = params - // takedown check for anyone other than an admin or the user - if (!isUserOrAdmin(auth, did)) { - const available = await ctx.services - .account(ctx.db) - .isRepoAvailable(did) - if (!available) { - throw new InvalidRequestError(`Could not find root for DID: ${did}`) - } - } - const storage = new SqlRepoStorage(ctx.db, did) - const earliest = params.earliest ? CID.parse(params.earliest) : null - const latest = params.latest - ? CID.parse(params.latest) - : await storage.getHead() - if (latest === null) { - throw new InvalidRequestError(`Could not find root for DID: ${did}`) - } - const commitPath = await storage.getCommitPath(latest, earliest) - if (commitPath === null) { - throw new InvalidRequestError( - `Could not find a valid commit path from ${latest.toString()} to ${earliest?.toString()}`, - ) - } - const commits = commitPath.map((c) => c.toString()) - return { - encoding: 'application/json', - body: { commits }, - } - }, - }) -} diff --git a/packages/pds/src/api/com/atproto/sync/getCheckout.ts b/packages/pds/src/api/com/atproto/sync/getLatestCommit.ts similarity index 53% rename from packages/pds/src/api/com/atproto/sync/getCheckout.ts rename to packages/pds/src/api/com/atproto/sync/getLatestCommit.ts index fd775e29c01..877db7806f4 100644 --- a/packages/pds/src/api/com/atproto/sync/getCheckout.ts +++ b/packages/pds/src/api/com/atproto/sync/getLatestCommit.ts @@ -1,15 +1,12 @@ -import { CID } from 'multiformats/cid' -import * as repo from '@atproto/repo' import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import SqlRepoStorage from '../../../../sql-repo-storage' import AppContext from '../../../../context' -import { byteIterableToStream } from '@atproto/common' import { isUserOrAdmin } from '../../../../auth' export default function (server: Server, ctx: AppContext) { - server.com.atproto.sync.getCheckout({ - auth: ctx.optionalAccessOrAdminVerifier, + server.com.atproto.sync.getLatestCommit({ + auth: ctx.optionalAccessOrRoleVerifier, handler: async ({ params, auth }) => { const { did } = params // takedown check for anyone other than an admin or the user @@ -18,20 +15,23 @@ export default function (server: Server, ctx: AppContext) { .account(ctx.db) .isRepoAvailable(did) if (!available) { - throw new InvalidRequestError(`Could not find root for DID: ${did}`) + throw new InvalidRequestError( + `Could not find root for DID: ${did}`, + 'RepoNotFound', + ) } } const storage = new SqlRepoStorage(ctx.db, did) - const commit = params.commit - ? CID.parse(params.commit) - : await storage.getHead() - if (!commit) { - throw new InvalidRequestError(`Could not find repo for DID: ${did}`) + const root = await storage.getRootDetailed() + if (root === null) { + throw new InvalidRequestError( + `Could not find root for DID: ${did}`, + 'RepoNotFound', + ) } - const checkout = repo.getCheckout(storage, commit) return { - encoding: 'application/vnd.ipld.car', - body: byteIterableToStream(checkout), + encoding: 'application/json', + body: { cid: root.cid.toString(), rev: root.rev }, } }, }) diff --git a/packages/pds/src/api/com/atproto/sync/getRecord.ts b/packages/pds/src/api/com/atproto/sync/getRecord.ts index 873cebbe86c..817d7850cb6 100644 --- a/packages/pds/src/api/com/atproto/sync/getRecord.ts +++ b/packages/pds/src/api/com/atproto/sync/getRecord.ts @@ -9,7 +9,7 @@ import { isUserOrAdmin } from '../../../../auth' export default function (server: Server, ctx: AppContext) { server.com.atproto.sync.getRecord({ - auth: ctx.optionalAccessOrAdminVerifier, + auth: ctx.optionalAccessOrRoleVerifier, handler: async ({ params, auth }) => { const { did, collection, rkey } = params // takedown check for anyone other than an admin or the user @@ -24,7 +24,7 @@ export default function (server: Server, ctx: AppContext) { const storage = new SqlRepoStorage(ctx.db, did) const commit = params.commit ? CID.parse(params.commit) - : await storage.getHead() + : await storage.getRoot() if (!commit) { throw new InvalidRequestError(`Could not find repo for DID: ${did}`) } diff --git a/packages/pds/src/api/com/atproto/sync/getRepo.ts b/packages/pds/src/api/com/atproto/sync/getRepo.ts index 431be8ee89a..9037a2a3a9c 100644 --- a/packages/pds/src/api/com/atproto/sync/getRepo.ts +++ b/packages/pds/src/api/com/atproto/sync/getRepo.ts @@ -1,17 +1,17 @@ -import { CID } from 'multiformats/cid' -import * as repo from '@atproto/repo' import { InvalidRequestError } from '@atproto/xrpc-server' +import { byteIterableToStream } from '@atproto/common' import { Server } from '../../../../lexicon' -import SqlRepoStorage from '../../../../sql-repo-storage' +import SqlRepoStorage, { + RepoRootNotFoundError, +} from '../../../../sql-repo-storage' import AppContext from '../../../../context' -import { byteIterableToStream, chunkArray } from '@atproto/common' import { isUserOrAdmin } from '../../../../auth' export default function (server: Server, ctx: AppContext) { server.com.atproto.sync.getRepo({ - auth: ctx.optionalAccessOrAdminVerifier, + auth: ctx.optionalAccessOrRoleVerifier, handler: async ({ params, auth }) => { - const { did } = params + const { did, since } = params // takedown check for anyone other than an admin or the user if (!isUserOrAdmin(auth, did)) { const available = await ctx.services @@ -21,28 +21,17 @@ export default function (server: Server, ctx: AppContext) { throw new InvalidRequestError(`Could not find repo for DID: ${did}`) } } - const storage = new SqlRepoStorage(ctx.db, did) - const earliest = params.earliest ? CID.parse(params.earliest) : null - const latest = params.latest - ? CID.parse(params.latest) - : await storage.getHead() - if (latest === null) { - throw new InvalidRequestError(`Could not find repo for DID: ${did}`) - } - const commitPath = await storage.getCommitPath(latest, earliest) - if (commitPath === null) { - throw new InvalidRequestError(`Could not find shared history`) - } - const commitChunks = chunkArray(commitPath, 25) - const carStream = repo.writeCar(latest, async (car) => { - for (const chunk of commitChunks) { - const blocks = await storage.getAllBlocksForCommits(chunk) - for (const block of blocks) { - await car.put({ cid: block.cid, bytes: block.bytes }) - } + const storage = new SqlRepoStorage(ctx.db, did) + let carStream: AsyncIterable + try { + carStream = await storage.getCarStream(since) + } catch (err) { + if (err instanceof RepoRootNotFoundError) { + throw new InvalidRequestError(`Could not find repo for DID: ${did}`) } - }) + throw err + } return { encoding: 'application/vnd.ipld.car', diff --git a/packages/pds/src/api/com/atproto/sync/index.ts b/packages/pds/src/api/com/atproto/sync/index.ts index 5da7090c8da..5931dbad761 100644 --- a/packages/pds/src/api/com/atproto/sync/index.ts +++ b/packages/pds/src/api/com/atproto/sync/index.ts @@ -2,24 +2,24 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import getBlob from './getBlob' import getBlocks from './getBlocks' -import getCheckout from './getCheckout' -import getCommitPath from './getCommitPath' -import getHead from './getHead' +import getLatestCommit from './getLatestCommit' import getRecord from './getRecord' import getRepo from './getRepo' import subscribeRepos from './subscribeRepos' import listBlobs from './listBlobs' import listRepos from './listRepos' +import getCheckout from './deprecated/getCheckout' +import getHead from './deprecated/getHead' export default function (server: Server, ctx: AppContext) { getBlob(server, ctx) getBlocks(server, ctx) - getCheckout(server, ctx) - getCommitPath(server, ctx) - getHead(server, ctx) + getLatestCommit(server, ctx) getRecord(server, ctx) getRepo(server, ctx) subscribeRepos(server, ctx) listBlobs(server, ctx) listRepos(server, ctx) + getCheckout(server, ctx) + getHead(server, ctx) } diff --git a/packages/pds/src/api/com/atproto/sync/listBlobs.ts b/packages/pds/src/api/com/atproto/sync/listBlobs.ts index c67bce4c0c8..5beb4d5a0fd 100644 --- a/packages/pds/src/api/com/atproto/sync/listBlobs.ts +++ b/packages/pds/src/api/com/atproto/sync/listBlobs.ts @@ -1,15 +1,13 @@ -import { CID } from 'multiformats/cid' import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' -import SqlRepoStorage from '../../../../sql-repo-storage' import AppContext from '../../../../context' import { isUserOrAdmin } from '../../../../auth' export default function (server: Server, ctx: AppContext) { server.com.atproto.sync.listBlobs({ - auth: ctx.optionalAccessOrAdminVerifier, + auth: ctx.optionalAccessOrRoleVerifier, handler: async ({ params, auth }) => { - const { did } = params + const { did, since, limit, cursor } = params // takedown check for anyone other than an admin or the user if (!isUserOrAdmin(auth, did)) { const available = await ctx.services @@ -19,28 +17,28 @@ export default function (server: Server, ctx: AppContext) { throw new InvalidRequestError(`Could not find root for DID: ${did}`) } } - const storage = new SqlRepoStorage(ctx.db, did) - const earliest = params.earliest ? CID.parse(params.earliest) : null - const latest = params.latest - ? CID.parse(params.latest) - : await storage.getHead() - if (latest === null) { - throw new InvalidRequestError(`Could not find root for DID: ${did}`) + + let builder = ctx.db.db + .selectFrom('repo_blob') + .where('did', '=', did) + .select('cid') + .orderBy('cid', 'asc') + .limit(limit) + if (since) { + builder = builder.where('repoRev', '>', since) } - const commitPath = await storage.getCommitPath(latest, earliest) - if (commitPath === null) { - throw new InvalidRequestError( - `Could not find a valid commit path from ${latest.toString()} to ${earliest?.toString()}`, - ) + + if (cursor) { + builder = builder.where('cid', '>', cursor) } - const cids = await ctx.services - .repo(ctx.db) - .blobs.listForCommits(did, commitPath) + + const res = await builder.execute() return { encoding: 'application/json', body: { - cids: cids.map((c) => c.toString()), + cursor: res.at(-1)?.cid, + cids: res.map((row) => row.cid), }, } }, diff --git a/packages/pds/src/api/com/atproto/sync/listRepos.ts b/packages/pds/src/api/com/atproto/sync/listRepos.ts index 739e861934a..597b949449f 100644 --- a/packages/pds/src/api/com/atproto/sync/listRepos.ts +++ b/packages/pds/src/api/com/atproto/sync/listRepos.ts @@ -8,22 +8,25 @@ export default function (server: Server, ctx: AppContext) { server.com.atproto.sync.listRepos(async ({ params }) => { const { limit, cursor } = params const { ref } = ctx.db.db.dynamic - const innerBuilder = ctx.db.db - .selectFrom('repo_root') - .innerJoin('user_account', 'user_account.did', 'repo_root.did') + let builder = ctx.db.db + .selectFrom('user_account') + .innerJoin('repo_root', 'repo_root.did', 'user_account.did') .where(notSoftDeletedClause(ref('repo_root'))) .select([ - 'repo_root.did as did', + 'user_account.did as did', 'repo_root.root as head', 'user_account.createdAt as createdAt', ]) - let builder = ctx.db.db.selectFrom(innerBuilder.as('repos')).selectAll() - const keyset = new TimeDidKeyset(ref('createdAt'), ref('did')) + const keyset = new TimeDidKeyset( + ref('user_account.createdAt'), + ref('user_account.did'), + ) builder = paginate(builder, { limit, cursor, keyset, direction: 'asc', + tryIndex: true, }) const res = await builder.execute() const repos = res.map((row) => ({ did: row.did, head: row.head })) diff --git a/packages/pds/src/auth.ts b/packages/pds/src/auth.ts index a877ae9b343..66c9b1f0780 100644 --- a/packages/pds/src/auth.ts +++ b/packages/pds/src/auth.ts @@ -13,6 +13,7 @@ export type ServerAuthOpts = { jwtSecret: string adminPass: string moderatorPass?: string + triagePass?: string } // @TODO sync-up with current method names, consider backwards compat. @@ -34,11 +35,13 @@ export class ServerAuth { private _secret: string private _adminPass: string private _moderatorPass?: string + private _triagePass?: string constructor(opts: ServerAuthOpts) { this._secret = opts.jwtSecret this._adminPass = opts.adminPass this._moderatorPass = opts.moderatorPass + this._triagePass = opts.triagePass } createAccessToken(opts: { @@ -110,18 +113,23 @@ export class ServerAuth { return authorized !== null && authorized.did === did } - verifyAdmin(req: express.Request) { + verifyRole(req: express.Request) { const parsed = parseBasicAuth(req.headers.authorization || '') + const { Missing, Valid, Invalid } = AuthStatus if (!parsed) { - return { admin: false, moderator: false, missing: true } + return { status: Missing, admin: false, moderator: false, triage: false } } const { username, password } = parsed - if (username !== 'admin') { - return { admin: false, moderator: false } + if (username === 'admin' && password === this._triagePass) { + return { status: Valid, admin: false, moderator: false, triage: true } } - const admin = password === this._adminPass - const moderator = admin || password === this._moderatorPass - return { admin, moderator } + if (username === 'admin' && password === this._moderatorPass) { + return { status: Valid, admin: false, moderator: true, triage: true } + } + if (username === 'admin' && password === this._adminPass) { + return { status: Valid, admin: true, moderator: true, triage: true } + } + return { status: Invalid, admin: false, moderator: false, triage: false } } getToken(req: express.Request) { @@ -219,7 +227,34 @@ export const accessVerifierCheckTakedown = } } -export const optionalAccessOrAdminVerifier = (auth: ServerAuth) => { +export const accessOrRoleVerifier = (auth: ServerAuth) => { + const verifyAccess = accessVerifier(auth) + const verifyRole = roleVerifier(auth) + return async (ctx: { req: express.Request; res: express.Response }) => { + // For non-admin tokens, we don't want to consider alternative verifiers and let it fail if it fails + const isRoleAuthToken = ctx.req.headers.authorization?.startsWith(BASIC) + if (isRoleAuthToken) { + const result = await verifyRole(ctx) + return { + ...result, + credentials: { + type: 'role' as const, + ...result.credentials, + }, + } + } + const result = await verifyAccess(ctx) + return { + ...result, + credentials: { + type: 'access' as const, + ...result.credentials, + }, + } + } +} + +export const optionalAccessOrRoleVerifier = (auth: ServerAuth) => { const verifyAccess = accessVerifier(auth) return async (ctx: { req: express.Request; res: express.Response }) => { try { @@ -230,8 +265,8 @@ export const optionalAccessOrAdminVerifier = (auth: ServerAuth) => { err.customErrorName === 'AuthMissing' ) { // Missing access bearer, move onto admin basic auth - const credentials = auth.verifyAdmin(ctx.req) - if (credentials.missing) { + const credentials = auth.verifyRole(ctx.req) + if (credentials.status === AuthStatus.Missing) { // If both are missing, passthrough: this auth scheme is optional return { credentials: null } } else if (credentials.admin) { @@ -270,21 +305,11 @@ export const refreshVerifier = } } -export const adminVerifier = - (auth: ServerAuth) => - async (ctx: { req: express.Request; res: express.Response }) => { - const credentials = auth.verifyAdmin(ctx.req) - if (!credentials.admin) { - throw new AuthRequiredError() - } - return { credentials } - } - -export const moderatorVerifier = +export const roleVerifier = (auth: ServerAuth) => async (ctx: { req: express.Request; res: express.Response }) => { - const credentials = auth.verifyAdmin(ctx.req) - if (!credentials.moderator) { + const credentials = auth.verifyRole(ctx.req) + if (credentials.status !== AuthStatus.Valid) { throw new AuthRequiredError() } return { credentials } @@ -293,3 +318,9 @@ export const moderatorVerifier = export const getRefreshTokenId = () => { return ui8.toString(crypto.randomBytes(32), 'base64') } + +export enum AuthStatus { + Valid, + Invalid, + Missing, +} diff --git a/packages/pds/src/background.ts b/packages/pds/src/background.ts index a66ecf887b8..65d8cbd473b 100644 --- a/packages/pds/src/background.ts +++ b/packages/pds/src/background.ts @@ -5,7 +5,7 @@ import { dbLogger } from './logger' // A simple queue for in-process, out-of-band/backgrounded work export class BackgroundQueue { - queue = new PQueue({ concurrency: 10 }) + queue = new PQueue({ concurrency: 5 }) destroyed = false constructor(public db: Database) {} diff --git a/packages/pds/src/config/config.ts b/packages/pds/src/config/config.ts index c103a418fe7..512f1de41a9 100644 --- a/packages/pds/src/config/config.ts +++ b/packages/pds/src/config/config.ts @@ -117,17 +117,48 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => { } } + let moderationEmailCfg: ServerConfig['moderationEmail'] + if (!env.moderationEmailAddress && !env.moderationEmailSmtpUrl) { + moderationEmailCfg = null + } else { + if (!env.moderationEmailAddress || !env.moderationEmailSmtpUrl) { + throw new Error('Partial email config') + } + moderationEmailCfg = { + smtpUrl: env.moderationEmailSmtpUrl, + fromAddress: env.moderationEmailAddress, + } + } + const subscriptionCfg: ServerConfig['subscription'] = { maxBuffer: env.maxSubscriptionBuffer ?? 500, repoBackfillLimitMs: env.repoBackfillLimitMs ?? DAY, + sequencerLeaderEnabled: env.sequencerLeaderEnabled ?? true, sequencerLeaderLockId: env.sequencerLeaderLockId ?? 1100, } const bskyAppViewCfg: ServerConfig['bskyAppView'] = { url: env.bskyAppViewUrl ?? 'https://api.bsky-sandbox.dev', did: env.bskyAppViewDid ?? 'did:plc:abc', // get real did + proxyModeration: env.bskyAppViewModeration ?? false, + cdnUrlPattern: env.bskyAppViewCdnUrlPattern, } + const redisCfg: ServerConfig['redis'] = env.redisScratchAddress + ? { + address: env.redisScratchAddress, + password: env.redisScratchPassword, + } + : null + + const rateLimitsCfg: ServerConfig['rateLimits'] = env.rateLimitsEnabled + ? { + enabled: true, + mode: redisCfg !== null ? 'redis' : 'memory', + bypassKey: env.rateLimitBypassKey, + } + : { enabled: false } + const crawlersCfg: ServerConfig['crawlers'] = env.crawlers ?? [] return { @@ -137,8 +168,11 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => { identity: identityCfg, invites: invitesCfg, email: emailCfg, + moderationEmail: moderationEmailCfg, subscription: subscriptionCfg, bskyAppView: bskyAppViewCfg, + redis: redisCfg, + rateLimits: rateLimitsCfg, crawlers: crawlersCfg, } } @@ -150,8 +184,11 @@ export type ServerConfig = { identity: IdentityConfig invites: InvitesConfig email: EmailConfig | null + moderationEmail: EmailConfig | null subscription: SubscriptionConfig bskyAppView: BksyAppViewConfig + redis: RedisScratchConfig | null + rateLimits: RateLimitsConfig crawlers: string[] } @@ -222,10 +259,26 @@ export type EmailConfig = { export type SubscriptionConfig = { maxBuffer: number repoBackfillLimitMs: number + sequencerLeaderEnabled: boolean sequencerLeaderLockId: number } +export type RedisScratchConfig = { + address: string + password?: string +} + +export type RateLimitsConfig = + | { + enabled: true + mode: 'memory' | 'redis' + bypassKey?: string + } + | { enabled: false } + export type BksyAppViewConfig = { url: string did: string + proxyModeration: boolean + cdnUrlPattern?: string } diff --git a/packages/pds/src/config/env.ts b/packages/pds/src/config/env.ts index dd17383c3e9..f8470369a8b 100644 --- a/packages/pds/src/config/env.ts +++ b/packages/pds/src/config/env.ts @@ -48,15 +48,30 @@ export const readEnv = (): ServerEnvironment => { // email emailSmtpUrl: envStr(process.env.PDS_EMAIL_SMTP_URL), emailFromAddress: envStr(process.env.PDS_EMAIL_FROM_ADDRESS), + moderationEmailSmtpUrl: envStr(process.env.PDS_MODERATION_EMAIL_SMTP_URL), + moderationEmailAddress: envStr(process.env.PDS_MODERATION_EMAIL_ADDRESS), // subscription maxSubscriptionBuffer: envInt(process.env.PDS_MAX_SUBSCRIPTION_BUFFER), repoBackfillLimitMs: envInt(process.env.PDS_REPO_BACKFILL_LIMIT_MS), + sequencerLeaderEnabled: envBool(process.env.PDS_SEQUENCER_LEADER_ENABLED), sequencerLeaderLockId: envInt(process.env.PDS_SEQUENCER_LEADER_LOCK_ID), // appview bskyAppViewUrl: envStr(process.env.PDS_BSKY_APP_VIEW_URL), bskyAppViewDid: envStr(process.env.PDS_BSKY_APP_VIEW_DID), + bskyAppViewModeration: envBool(process.env.PDS_BSKY_APP_VIEW_MODERATION), + bskyAppViewCdnUrlPattern: envStr( + process.env.PDS_BSKY_APP_VIEW_CDN_URL_PATTERN, + ), + + // rate limits + rateLimitsEnabled: envBool(process.env.PDS_RATE_LIMITS_ENABLED), + rateLimitBypassKey: envStr(process.env.PDS_RATE_LIMIT_BYPASS_KEY), + + // redis + redisScratchAddress: envStr(process.env.PDS_REDIS_SCRATCH_ADDRESS), + redisScratchPassword: envStr(process.env.PDS_REDIS_SCRATCH_PASSWORD), // crawlers crawlers: envList(process.env.PDS_CRAWLERS), @@ -65,6 +80,7 @@ export const readEnv = (): ServerEnvironment => { jwtSecret: envStr(process.env.PDS_JWT_SECRET), adminPassword: envStr(process.env.PDS_ADMIN_PASSWORD), moderatorPassword: envStr(process.env.PDS_MODERATOR_PASSWORD), + triagePassword: envStr(process.env.PDS_TRIAGE_PASSWORD), // keys: only one of each required // kms @@ -121,15 +137,28 @@ export type ServerEnvironment = { // email emailSmtpUrl?: string emailFromAddress?: string + moderationEmailSmtpUrl?: string + moderationEmailAddress?: string // subscription maxSubscriptionBuffer?: number repoBackfillLimitMs?: number + sequencerLeaderEnabled?: boolean sequencerLeaderLockId?: number // appview bskyAppViewUrl?: string bskyAppViewDid?: string + bskyAppViewModeration?: boolean + bskyAppViewCdnUrlPattern?: string + + // rate limits + rateLimitsEnabled?: boolean + rateLimitBypassKey?: string + + // redis + redisScratchAddress?: string + redisScratchPassword?: string // crawler crawlers?: string[] @@ -138,6 +167,7 @@ export type ServerEnvironment = { jwtSecret?: string adminPassword?: string moderatorPassword?: string + triagePassword?: string // keys repoSigningKeyKmsKeyId?: string diff --git a/packages/pds/src/config/secrets.ts b/packages/pds/src/config/secrets.ts index bb26b1bcd80..f0f876f1ccc 100644 --- a/packages/pds/src/config/secrets.ts +++ b/packages/pds/src/config/secrets.ts @@ -46,7 +46,9 @@ export const envToSecrets = (env: ServerEnvironment): ServerSecrets => { return { jwtSecret: env.jwtSecret, adminPassword: env.adminPassword, - moderatorPassword: env.moderatorPassword || env.adminPassword, + moderatorPassword: env.moderatorPassword ?? env.adminPassword, + triagePassword: + env.triagePassword ?? env.moderatorPassword ?? env.adminPassword, repoSigningKey, plcRotationKey, } @@ -56,6 +58,7 @@ export type ServerSecrets = { jwtSecret: string adminPassword: string moderatorPassword: string + triagePassword: string repoSigningKey: SigningKeyKms | SigningKeyMemory plcRotationKey: SigningKeyKms | SigningKeyMemory } diff --git a/packages/pds/src/context.ts b/packages/pds/src/context.ts index 35b4b86c6c4..71af4ae62dc 100644 --- a/packages/pds/src/context.ts +++ b/packages/pds/src/context.ts @@ -1,34 +1,41 @@ import * as nodemailer from 'nodemailer' +import { Redis } from 'ioredis' import * as plc from '@did-plc/lib' import * as crypto from '@atproto/crypto' import { IdResolver } from '@atproto/identity' import { AtpAgent } from '@atproto/api' +import { KmsKeypair, S3BlobStore } from '@atproto/aws' import { createServiceAuthHeaders } from '@atproto/xrpc-server' import { Database } from './db' import { ServerConfig, ServerSecrets } from './config' import * as auth from './auth' import { ServerAuth } from './auth' import { ServerMailer } from './mailer' +import { ModerationMailer } from './mailer/moderation' import { BlobStore } from '@atproto/repo' import { Services, createServices } from './services' import { Sequencer, SequencerLeader } from './sequencer' import { BackgroundQueue } from './background' import DidSqlCache from './did-cache' import { Crawlers } from './crawlers' -import { KmsKeypair, S3BlobStore } from '@atproto/aws' import { DiskBlobStore } from './storage' +import { getRedisClient } from './redis' +import { RuntimeFlags } from './runtime-flags' export type AppContextOptions = { db: Database blobstore: BlobStore mailer: ServerMailer + moderationMailer: ModerationMailer didCache: DidSqlCache idResolver: IdResolver plcClient: plc.Client services: Services sequencer: Sequencer - sequencerLeader: SequencerLeader + sequencerLeader?: SequencerLeader backgroundQueue: BackgroundQueue + runtimeFlags: RuntimeFlags + redisScratch?: Redis crawlers: Crawlers appViewAgent: AtpAgent auth: auth.ServerAuth @@ -41,13 +48,16 @@ export class AppContext { public db: Database public blobstore: BlobStore public mailer: ServerMailer + public moderationMailer: ModerationMailer public didCache: DidSqlCache public idResolver: IdResolver public plcClient: plc.Client public services: Services public sequencer: Sequencer - public sequencerLeader: SequencerLeader + public sequencerLeader?: SequencerLeader public backgroundQueue: BackgroundQueue + public runtimeFlags: RuntimeFlags + public redisScratch?: Redis public crawlers: Crawlers public appViewAgent: AtpAgent public auth: auth.ServerAuth @@ -59,6 +69,7 @@ export class AppContext { this.db = opts.db this.blobstore = opts.blobstore this.mailer = opts.mailer + this.moderationMailer = opts.moderationMailer this.didCache = opts.didCache this.idResolver = opts.idResolver this.plcClient = opts.plcClient @@ -66,6 +77,8 @@ export class AppContext { this.sequencer = opts.sequencer this.sequencerLeader = opts.sequencerLeader this.backgroundQueue = opts.backgroundQueue + this.runtimeFlags = opts.runtimeFlags + this.redisScratch = opts.redisScratch this.crawlers = opts.crawlers this.appViewAgent = opts.appViewAgent this.auth = opts.auth @@ -104,6 +117,13 @@ export class AppContext { const mailer = new ServerMailer(mailTransport, cfg) + const modMailTransport = + cfg.moderationEmail !== null + ? nodemailer.createTransport(cfg.moderationEmail.smtpUrl) + : nodemailer.createTransport({ jsonTransport: true }) + + const moderationMailer = new ModerationMailer(modMailTransport, cfg) + const didCache = new DidSqlCache( db, cfg.identity.cacheStaleTTL, @@ -117,12 +137,16 @@ export class AppContext { const plcClient = new plc.Client(cfg.identity.plcUrl) const sequencer = new Sequencer(db) - const sequencerLeader = new SequencerLeader( - db, - cfg.subscription.sequencerLeaderLockId, - ) + const sequencerLeader = cfg.subscription.sequencerLeaderEnabled + ? new SequencerLeader(db, cfg.subscription.sequencerLeaderLockId) + : undefined const backgroundQueue = new BackgroundQueue(db) + const runtimeFlags = new RuntimeFlags(db) + const redisScratch = cfg.redis + ? getRedisClient(cfg.redis.address, cfg.redis.password) + : undefined + const crawlers = new Crawlers(cfg.service.hostname, cfg.crawlers) const appViewAgent = new AtpAgent({ service: cfg.bskyAppView.url }) @@ -131,6 +155,7 @@ export class AppContext { jwtSecret: secrets.jwtSecret, adminPass: secrets.adminPassword, moderatorPass: secrets.moderatorPassword, + triagePass: secrets.triagePassword, }) const repoSigningKey = @@ -154,6 +179,9 @@ export class AppContext { const services = createServices({ repoSigningKey, blobstore, + appViewAgent, + appViewDid: cfg.bskyAppView.did, + appViewCdnUrlPattern: cfg.bskyAppView.cdnUrlPattern, backgroundQueue, crawlers, }) @@ -162,6 +190,7 @@ export class AppContext { db, blobstore, mailer, + moderationMailer, didCache, idResolver, plcClient, @@ -169,6 +198,8 @@ export class AppContext { sequencer, sequencerLeader, backgroundQueue, + runtimeFlags, + redisScratch, crawlers, appViewAgent, auth, @@ -195,16 +226,16 @@ export class AppContext { return auth.refreshVerifier(this.auth) } - get adminVerifier() { - return auth.adminVerifier(this.auth) + get roleVerifier() { + return auth.roleVerifier(this.auth) } - get moderatorVerifier() { - return auth.moderatorVerifier(this.auth) + get accessOrRoleVerifier() { + return auth.accessOrRoleVerifier(this.auth) } - get optionalAccessOrAdminVerifier() { - return auth.optionalAccessOrAdminVerifier(this.auth) + get optionalAccessOrRoleVerifier() { + return auth.optionalAccessOrRoleVerifier(this.auth) } async serviceAuthHeaders(did: string, audience?: string) { diff --git a/packages/pds/src/db/database-schema.ts b/packages/pds/src/db/database-schema.ts index 96ff092c13f..655408dad8c 100644 --- a/packages/pds/src/db/database-schema.ts +++ b/packages/pds/src/db/database-schema.ts @@ -8,8 +8,6 @@ import * as refreshToken from './tables/refresh-token' import * as appPassword from './tables/app-password' import * as record from './tables/record' import * as backlink from './tables/backlink' -import * as repoCommitBlock from './tables/repo-commit-block' -import * as repoCommitHistory from './tables/repo-commit-history' import * as ipldBlock from './tables/ipld-block' import * as inviteCode from './tables/invite-code' import * as blob from './tables/blob' @@ -18,8 +16,10 @@ import * as deleteAccountToken from './tables/delete-account-token' import * as moderation from './tables/moderation' import * as repoSeq from './tables/repo-seq' import * as appMigration from './tables/app-migration' +import * as runtimeFlag from './tables/runtime-flag' export type DatabaseSchemaType = appMigration.PartialDB & + runtimeFlag.PartialDB & userAccount.PartialDB & userPref.PartialDB & didHandle.PartialDB & @@ -29,8 +29,6 @@ export type DatabaseSchemaType = appMigration.PartialDB & didCache.PartialDB & record.PartialDB & backlink.PartialDB & - repoCommitBlock.PartialDB & - repoCommitHistory.PartialDB & ipldBlock.PartialDB & inviteCode.PartialDB & blob.PartialDB & diff --git a/packages/pds/src/db/index.ts b/packages/pds/src/db/index.ts index 6cf0486dca7..f6a1cc831a3 100644 --- a/packages/pds/src/db/index.ts +++ b/packages/pds/src/db/index.ts @@ -22,10 +22,12 @@ import { dummyDialect } from './util' import * as migrations from './migrations' import { CtxMigrationProvider } from './migrations/provider' import { dbLogger as log } from '../logger' +import { randomIntFromSeed } from '@atproto/crypto' export class Database { txEvt = new EventEmitter() as TxnEmitter txChannelEvts: ChannelEvt[] = [] + txLockNonce: string | undefined channels: Channels migrator: Migrator destroyed = false @@ -46,6 +48,7 @@ export class Database { new_repo_event: new EventEmitter() as ChannelEmitter, outgoing_repo_seq: new EventEmitter() as ChannelEmitter, } + this.txLockNonce = cfg.dialect === 'pg' ? cfg.txLockNonce : undefined } static sqlite(location: string): Database { @@ -58,7 +61,7 @@ export class Database { } static postgres(opts: PgOptions): Database { - const { schema, url } = opts + const { schema, url, txLockNonce } = opts const pool = opts.pool ?? new PgPool({ @@ -76,9 +79,11 @@ export class Database { throw new Error(`Postgres schema must only contain [A-Za-z_]: ${schema}`) } + pool.on('error', onPoolError) pool.on('connect', (client) => { + client.on('error', onClientError) // Used for trigram indexes, e.g. on actor search - client.query('SET pg_trgm.strict_word_similarity_threshold TO .1;') + client.query('SET pg_trgm.word_similarity_threshold TO .4;') if (schema) { // Shared objects such as extensions will go in the public schema client.query(`SET search_path TO "${schema}",public;`) @@ -89,7 +94,13 @@ export class Database { dialect: new PostgresDialect({ pool }), }) - return new Database(db, { dialect: 'pg', pool, schema, url }) + return new Database(db, { + dialect: 'pg', + pool, + schema, + url, + txLockNonce, + }) } static memory(): Database { @@ -182,6 +193,26 @@ export class Database { return txRes } + async takeTxAdvisoryLock(name: string): Promise { + this.assertTransaction() + return this.txAdvisoryLock(name) + } + + async checkTxAdvisoryLock(name: string): Promise { + this.assertNotTransaction() + return this.txAdvisoryLock(name) + } + + private async txAdvisoryLock(name: string): Promise { + assert(this.dialect === 'pg', 'Postgres required') + // any lock id < 10k is reserved for session locks + const id = await randomIntFromSeed(name, Number.MAX_SAFE_INTEGER, 10000) + const res = (await sql`SELECT pg_try_advisory_xact_lock(${sql.literal( + id, + )}) as acquired`.execute(this.db)) as TxLockRes + return res.rows[0]?.acquired === true + } + get schema(): string | undefined { return this.cfg.dialect === 'pg' ? this.cfg.schema : undefined } @@ -299,6 +330,7 @@ export type PgConfig = { pool: PgPool url: string schema?: string + txLockNonce?: string } export type SqliteConfig = { @@ -315,6 +347,7 @@ type PgOptions = { poolSize?: number poolMaxUses?: number poolIdleTimeoutMs?: number + txLockNonce?: string } type ChannelEvents = { @@ -356,3 +389,10 @@ class LeakyTxPlugin implements KyselyPlugin { return args.result } } + +type TxLockRes = { + rows: { acquired: true | false }[] +} + +const onPoolError = (err: Error) => log.error({ err }, 'db pool error') +const onClientError = (err: Error) => log.error({ err }, 'db client error') diff --git a/packages/pds/src/db/migrations/20230718T170914772Z-sequencer-leader-sequence.ts b/packages/pds/src/db/migrations/20230718T170914772Z-sequencer-leader-sequence.ts new file mode 100644 index 00000000000..aae6db339b9 --- /dev/null +++ b/packages/pds/src/db/migrations/20230718T170914772Z-sequencer-leader-sequence.ts @@ -0,0 +1,25 @@ +import { Kysely, sql } from 'kysely' +import { Dialect } from '..' + +export async function up(db: Kysely, dialect: Dialect): Promise { + if (dialect === 'sqlite') return + const res = await db + .selectFrom('repo_seq') + .select('seq') + .where('seq', 'is not', null) + .orderBy('seq', 'desc') + .limit(1) + .executeTakeFirst() + const startAt = res?.seq ? res.seq + 50000 : 1 + await sql`CREATE SEQUENCE repo_seq_sequence START ${sql.literal( + startAt, + )};`.execute(db) +} + +export async function down( + db: Kysely, + dialect: Dialect, +): Promise { + if (dialect === 'sqlite') return + await sql`DROP SEQUENCE repo_seq_sequence;`.execute(db) +} diff --git a/packages/pds/src/db/migrations/20230727T172043676Z-user-account-cursor-idx.ts b/packages/pds/src/db/migrations/20230727T172043676Z-user-account-cursor-idx.ts new file mode 100644 index 00000000000..15f38eafd65 --- /dev/null +++ b/packages/pds/src/db/migrations/20230727T172043676Z-user-account-cursor-idx.ts @@ -0,0 +1,13 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .createIndex('user_account_cursor_idx') + .on('user_account') + .columns(['createdAt', 'did']) + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.dropIndex('user_account_cursor_idx').execute() +} diff --git a/packages/pds/src/db/migrations/20230801T141349990Z-invite-note.ts b/packages/pds/src/db/migrations/20230801T141349990Z-invite-note.ts new file mode 100644 index 00000000000..c83a3030350 --- /dev/null +++ b/packages/pds/src/db/migrations/20230801T141349990Z-invite-note.ts @@ -0,0 +1,12 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .alterTable('user_account') + .addColumn('inviteNote', 'varchar') + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.alterTable('user_account').dropColumn('inviteNote').execute() +} diff --git a/packages/pds/src/db/migrations/20230807T035309811Z-feed-item-delete-invite-for-user-idx.ts b/packages/pds/src/db/migrations/20230807T035309811Z-feed-item-delete-invite-for-user-idx.ts new file mode 100644 index 00000000000..f93e277ffe4 --- /dev/null +++ b/packages/pds/src/db/migrations/20230807T035309811Z-feed-item-delete-invite-for-user-idx.ts @@ -0,0 +1,14 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + // supports listing user invites + await db.schema + .createIndex('invite_code_for_user_idx') + .on('invite_code') + .column('forUser') + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.dropIndex('invite_code_for_user_idx').execute() +} diff --git a/packages/pds/src/db/migrations/20230808T172813122Z-repo-rev.ts b/packages/pds/src/db/migrations/20230808T172813122Z-repo-rev.ts new file mode 100644 index 00000000000..e4c17d73291 --- /dev/null +++ b/packages/pds/src/db/migrations/20230808T172813122Z-repo-rev.ts @@ -0,0 +1,15 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema.alterTable('record').addColumn('repoRev', 'varchar').execute() + await db.schema + .createIndex('record_repo_rev_idx') + .on('record') + .columns(['did', 'repoRev']) + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.dropIndex('record_repo_rev_idx').execute() + await db.schema.alterTable('record').dropColumn('repoRev').execute() +} diff --git a/packages/pds/src/db/migrations/20230810T203412859Z-action-duration.ts b/packages/pds/src/db/migrations/20230810T203412859Z-action-duration.ts new file mode 100644 index 00000000000..0530d4d74fd --- /dev/null +++ b/packages/pds/src/db/migrations/20230810T203412859Z-action-duration.ts @@ -0,0 +1,23 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .alterTable('moderation_action') + .addColumn('durationInHours', 'integer') + .execute() + await db.schema + .alterTable('moderation_action') + .addColumn('expiresAt', 'varchar') + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema + .alterTable('moderation_action') + .dropColumn('durationInHours') + .execute() + await db.schema + .alterTable('moderation_action') + .dropColumn('expiresAt') + .execute() +} diff --git a/packages/pds/src/db/migrations/20230818T134357818Z-runtime-flags.ts b/packages/pds/src/db/migrations/20230818T134357818Z-runtime-flags.ts new file mode 100644 index 00000000000..c93ccd74158 --- /dev/null +++ b/packages/pds/src/db/migrations/20230818T134357818Z-runtime-flags.ts @@ -0,0 +1,13 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .createTable('runtime_flag') + .addColumn('name', 'varchar', (col) => col.primaryKey()) + .addColumn('value', 'varchar', (col) => col.notNull()) + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable('runtime_flag').execute() +} diff --git a/packages/pds/src/db/migrations/20230825T142507884Z-blob-tempkey-idx.ts b/packages/pds/src/db/migrations/20230825T142507884Z-blob-tempkey-idx.ts new file mode 100644 index 00000000000..42cb843661e --- /dev/null +++ b/packages/pds/src/db/migrations/20230825T142507884Z-blob-tempkey-idx.ts @@ -0,0 +1,13 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .createIndex('blob_tempkey_idx') + .on('blob') + .column('tempKey') + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.dropIndex('blob_tempkey_idx').execute() +} diff --git a/packages/pds/src/db/migrations/20230828T153013575Z-repo-history-rewrite.ts b/packages/pds/src/db/migrations/20230828T153013575Z-repo-history-rewrite.ts new file mode 100644 index 00000000000..368d7cbdbe5 --- /dev/null +++ b/packages/pds/src/db/migrations/20230828T153013575Z-repo-history-rewrite.ts @@ -0,0 +1,62 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema.alterTable('repo_root').addColumn('rev', 'varchar').execute() + await db.schema + .alterTable('ipld_block') + .addColumn('repoRev', 'varchar') + .execute() + await db.schema + .alterTable('repo_blob') + .addColumn('repoRev', 'varchar') + .execute() + await db.schema.alterTable('repo_blob').dropColumn('commit').execute() + + await db.schema + .createIndex('ipld_block_repo_rev_idx') + .on('ipld_block') + .columns(['creator', 'repoRev', 'cid']) + .execute() + + await db.schema + .createIndex('repo_blob_repo_rev_idx') + .on('repo_blob') + .columns(['did', 'repoRev']) + .execute() + + await db.schema.dropTable('repo_commit_history').execute() + await db.schema.dropTable('repo_commit_block').execute() +} + +export async function down(db: Kysely): Promise { + await db.schema + .createTable('repo_commit_block') + .addColumn('commit', 'varchar', (col) => col.notNull()) + .addColumn('block', 'varchar', (col) => col.notNull()) + .addColumn('creator', 'varchar', (col) => col.notNull()) + .addPrimaryKeyConstraint('repo_commit_block_pkey', [ + 'creator', + 'commit', + 'block', + ]) + .execute() + await db.schema + .createTable('repo_commit_history') + .addColumn('commit', 'varchar', (col) => col.notNull()) + .addColumn('prev', 'varchar') + .addColumn('creator', 'varchar', (col) => col.notNull()) + .addPrimaryKeyConstraint('repo_commit_history_pkey', ['creator', 'commit']) + .execute() + + await db.schema.dropIndex('ipld_block_repo_rev_idx').execute() + + await db.schema.dropIndex('repo_blob_repo_rev_idx').execute() + + await db.schema.alterTable('repo_root').dropColumn('rev').execute() + await db.schema.alterTable('ipld_block').dropColumn('repoRev').execute() + await db.schema.alterTable('repo_blob').dropColumn('repoRev').execute() + await db.schema + .alterTable('repo_blob') + .addColumn('commit', 'varchar') + .execute() +} diff --git a/packages/pds/src/db/migrations/index.ts b/packages/pds/src/db/migrations/index.ts index 9de245dda96..c8fd3d13b23 100644 --- a/packages/pds/src/db/migrations/index.ts +++ b/packages/pds/src/db/migrations/index.ts @@ -3,3 +3,12 @@ // this with kysely's FileMigrationProvider, but it doesn't play nicely with the build process. export * as _20230613T164932261Z from './20230613T164932261Z-init' +export * as _20230718T170914772Z from './20230718T170914772Z-sequencer-leader-sequence' +export * as _20230727T172043676Z from './20230727T172043676Z-user-account-cursor-idx' +export * as _20230801T141349990Z from './20230801T141349990Z-invite-note' +export * as _20230807T035309811Z from './20230807T035309811Z-feed-item-delete-invite-for-user-idx' +export * as _20230808T172813122Z from './20230808T172813122Z-repo-rev' +export * as _20230810T203412859Z from './20230810T203412859Z-action-duration' +export * as _20230818T134357818Z from './20230818T134357818Z-runtime-flags' +export * as _20230825T142507884Z from './20230825T142507884Z-blob-tempkey-idx' +export * as _20230828T153013575Z from './20230828T153013575Z-repo-history-rewrite' diff --git a/packages/pds/src/db/periodic-moderation-action-reversal.ts b/packages/pds/src/db/periodic-moderation-action-reversal.ts new file mode 100644 index 00000000000..b3b631de71d --- /dev/null +++ b/packages/pds/src/db/periodic-moderation-action-reversal.ts @@ -0,0 +1,88 @@ +import assert from 'assert' +import { wait } from '@atproto/common' +import { Leader } from './leader' +import { dbLogger } from '../logger' +import AppContext from '../context' +import { ModerationActionRow } from '../services/moderation' + +export const MODERATION_ACTION_REVERSAL_ID = 1011 + +export class PeriodicModerationActionReversal { + leader = new Leader(MODERATION_ACTION_REVERSAL_ID, this.appContext.db) + destroyed = false + + constructor(private appContext: AppContext) {} + + async revertAction(actionRow: ModerationActionRow) { + return this.appContext.db.transaction(async (dbTxn) => { + const moderationTxn = this.appContext.services.moderation(dbTxn) + await moderationTxn.revertAction({ + id: actionRow.id, + createdBy: actionRow.createdBy, + createdAt: new Date(), + reason: `[SCHEDULED_REVERSAL] Reverting action as originally scheduled`, + }) + }) + } + + async findAndRevertDueActions() { + const moderationService = this.appContext.services.moderation( + this.appContext.db, + ) + const actionsDueForReversal = + await moderationService.getActionsDueForReversal() + + // We shouldn't have too many actions due for reversal at any given time, so running in parallel is probably fine + // Internally, each reversal runs within its own transaction + await Promise.allSettled( + actionsDueForReversal.map(this.revertAction.bind(this)), + ) + } + + async run() { + assert( + this.appContext.db.dialect === 'pg', + 'Moderation action reversal can only be run by postgres', + ) + + while (!this.destroyed) { + try { + const { ran } = await this.leader.run(async ({ signal }) => { + while (!signal.aborted) { + // super basic synchronization by agreeing when the intervals land relative to unix timestamp + const now = Date.now() + const intervalMs = 1000 * 60 + const nextIteration = Math.ceil(now / intervalMs) + const nextInMs = nextIteration * intervalMs - now + await wait(nextInMs) + if (signal.aborted) break + await this.findAndRevertDueActions() + } + }) + if (ran && !this.destroyed) { + throw new Error('View maintainer completed, but should be persistent') + } + } catch (err) { + dbLogger.error( + { + err, + lockId: MODERATION_ACTION_REVERSAL_ID, + }, + 'moderation action reversal errored', + ) + } + if (!this.destroyed) { + await wait(10000 + jitter(2000)) + } + } + } + + destroy() { + this.destroyed = true + this.leader.destroy() + } +} + +function jitter(maxMs) { + return Math.round((Math.random() - 0.5) * maxMs * 2) +} diff --git a/packages/pds/src/db/tables/ipld-block.ts b/packages/pds/src/db/tables/ipld-block.ts index 7d888252156..ce7bd30a51a 100644 --- a/packages/pds/src/db/tables/ipld-block.ts +++ b/packages/pds/src/db/tables/ipld-block.ts @@ -1,6 +1,7 @@ export interface IpldBlock { cid: string creator: string + repoRev: string | null size: number content: Uint8Array } diff --git a/packages/pds/src/db/tables/moderation.ts b/packages/pds/src/db/tables/moderation.ts index 532661b1fd3..061b3981634 100644 --- a/packages/pds/src/db/tables/moderation.ts +++ b/packages/pds/src/db/tables/moderation.ts @@ -34,6 +34,8 @@ export interface ModerationAction { reversedAt: string | null reversedBy: string | null reversedReason: string | null + durationInHours: number | null + expiresAt: string | null } export interface ModerationActionSubjectBlob { diff --git a/packages/pds/src/db/tables/record.ts b/packages/pds/src/db/tables/record.ts index 6ce60928be3..fbde68b2219 100644 --- a/packages/pds/src/db/tables/record.ts +++ b/packages/pds/src/db/tables/record.ts @@ -5,6 +5,7 @@ export interface Record { did: string collection: string rkey: string + repoRev: string | null indexedAt: string // opaque identifier, though currently tends to reference a moderation_action takedownId: string | null diff --git a/packages/pds/src/db/tables/repo-blob.ts b/packages/pds/src/db/tables/repo-blob.ts index 2cfe40fb84d..67aa072454f 100644 --- a/packages/pds/src/db/tables/repo-blob.ts +++ b/packages/pds/src/db/tables/repo-blob.ts @@ -1,7 +1,7 @@ export interface RepoBlob { cid: string recordUri: string - commit: string + repoRev: string | null did: string // opaque identifier, though currently tends to reference a moderation_action takedownId: string | null diff --git a/packages/pds/src/db/tables/repo-commit-block.ts b/packages/pds/src/db/tables/repo-commit-block.ts deleted file mode 100644 index 2493a4a93cd..00000000000 --- a/packages/pds/src/db/tables/repo-commit-block.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface RepoCommitBlock { - commit: string - block: string - creator: string -} - -export const tableName = 'repo_commit_block' - -export type PartialDB = { [tableName]: RepoCommitBlock } diff --git a/packages/pds/src/db/tables/repo-commit-history.ts b/packages/pds/src/db/tables/repo-commit-history.ts deleted file mode 100644 index d92cb21f3f1..00000000000 --- a/packages/pds/src/db/tables/repo-commit-history.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface RepoCommitHistory { - commit: string - prev: string | null - creator: string -} - -export const tableName = 'repo_commit_history' - -export type PartialDB = { [tableName]: RepoCommitHistory } diff --git a/packages/pds/src/db/tables/repo-root.ts b/packages/pds/src/db/tables/repo-root.ts index c6b0ff1d76b..b68c6c37bea 100644 --- a/packages/pds/src/db/tables/repo-root.ts +++ b/packages/pds/src/db/tables/repo-root.ts @@ -2,6 +2,7 @@ export interface RepoRoot { did: string root: string + rev: string | null indexedAt: string // opaque identifier, though currently tends to reference a moderation_action takedownId: string | null diff --git a/packages/pds/src/db/tables/repo-seq.ts b/packages/pds/src/db/tables/repo-seq.ts index 7d1034048bc..ffd482c327a 100644 --- a/packages/pds/src/db/tables/repo-seq.ts +++ b/packages/pds/src/db/tables/repo-seq.ts @@ -2,6 +2,8 @@ import { Generated, GeneratedAlways, Insertable, Selectable } from 'kysely' export type EventType = 'append' | 'rebase' | 'handle' | 'migrate' | 'tombstone' +export const REPO_SEQ_SEQUENCE = 'repo_seq_sequence' + export interface RepoSeq { id: GeneratedAlways seq: number | null diff --git a/packages/pds/src/db/tables/runtime-flag.ts b/packages/pds/src/db/tables/runtime-flag.ts new file mode 100644 index 00000000000..f1e701a6914 --- /dev/null +++ b/packages/pds/src/db/tables/runtime-flag.ts @@ -0,0 +1,8 @@ +export interface RuntimeFlag { + name: string + value: string +} + +export const tableName = 'runtime_flag' + +export type PartialDB = { [tableName]: RuntimeFlag } diff --git a/packages/pds/src/db/tables/user-account.ts b/packages/pds/src/db/tables/user-account.ts index 5a0ab6e8389..665521efc08 100644 --- a/packages/pds/src/db/tables/user-account.ts +++ b/packages/pds/src/db/tables/user-account.ts @@ -8,6 +8,7 @@ export interface UserAccount { passwordResetToken: string | null passwordResetGrantedAt: string | null invitesDisabled: Generated<0 | 1> + inviteNote: string | null } export type UserAccountEntry = Selectable diff --git a/packages/pds/src/handle/explicit-slurs.ts b/packages/pds/src/handle/explicit-slurs.ts new file mode 100644 index 00000000000..534091366f6 --- /dev/null +++ b/packages/pds/src/handle/explicit-slurs.ts @@ -0,0 +1,21 @@ +// regexes taken from: https://github.com/Blank-Cheque/Slurs +/* eslint-disable no-misleading-character-class */ +const explicitSlurRegexes = [ + /\b[cĆćĈĉČčĊċÇçḈḉȻȼꞒꞓꟄꞔƇƈɕ][hĤĥȞȟḦḧḢḣḨḩḤḥḪḫH̱ẖĦħⱧⱨꞪɦꞕΗНн][iÍíi̇́Ììi̇̀ĬĭÎîǏǐÏïḮḯĨĩi̇̃ĮįĮ́į̇́Į̃į̇̃ĪīĪ̀ī̀ỈỉȈȉI̋i̋ȊȋỊịꞼꞽḬḭƗɨᶖİiIıIi1lĺľļḷḹl̃ḽḻłŀƚꝉⱡɫɬꞎꬷꬸꬹᶅɭȴLl][nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲŊŋꞐꞑꞤꞥᵰᶇɳȵꬻꬼИиПпNn][kḰḱǨǩĶķḲḳḴḵƘƙⱩⱪᶄꝀꝁꝂꝃꝄꝅꞢꞣ][sŚśṤṥŜŝŠšṦṧṠṡŞşṢṣṨṩȘșS̩s̩ꞨꞩⱾȿꟅʂᶊᵴ]?\b/, + /\b[cĆćĈĉČčĊċÇçḈḉȻȼꞒꞓꟄꞔƇƈɕ][ÓóÒòŎŏÔôỐốỒồỖỗỔổǑǒÖöȪȫŐőÕõṌṍṎṏȬȭȮȯO͘o͘ȰȱØøǾǿǪǫǬǭŌōṒṓṐṑỎỏȌȍȎȏƠơỚớỜờỠỡỞởỢợỌọỘộO̩o̩Ò̩ò̩Ó̩ó̩ƟɵꝊꝋꝌꝍⱺOo0]{2}[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲŊŋꞐꞑꞤꞥᵰᶇɳȵꬻꬼИиПпNn][sŚśṤṥŜŝŠšṦṧṠṡŞşṢṣṨṩȘșS̩s̩ꞨꞩⱾȿꟅʂᶊᵴ]?\b/, + /\b[fḞḟƑƒꞘꞙᵮᶂ][aÁáÀàĂăẮắẰằẴẵẲẳÂâẤấẦầẪẫẨẩǍǎÅåǺǻÄäǞǟÃãȦȧǠǡĄąĄ́ą́Ą̃ą̃ĀāĀ̀ā̀ẢảȀȁA̋a̋ȂȃẠạẶặẬậḀḁȺⱥꞺꞻᶏẚAa@4][gǴǵĞğĜĝǦǧĠġG̃g̃ĢģḠḡǤǥꞠꞡƓɠᶃꬶGgqꝖꝗꝘꝙɋʠ]{1,2}([ÓóÒòŎŏÔôỐốỒồỖỗỔổǑǒÖöȪȫŐőÕõṌṍṎṏȬȭȮȯO͘o͘ȰȱØøǾǿǪǫǬǭŌōṒṓṐṑỎỏȌȍȎȏƠơỚớỜờỠỡỞởỢợỌọỘộO̩o̩Ò̩ò̩Ó̩ó̩ƟɵꝊꝋꝌꝍⱺOo0e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEeiÍíi̇́Ììi̇̀ĬĭÎîǏǐÏïḮḯĨĩi̇̃ĮįĮ́į̇́Į̃į̇̃ĪīĪ̀ī̀ỈỉȈȉI̋i̋ȊȋỊịꞼꞽḬḭƗɨᶖİiIıIi1lĺľļḷḹl̃ḽḻłŀƚꝉⱡɫɬꞎꬷꬸꬹᶅɭȴLl][tŤťṪṫŢţṬṭȚțṰṱṮṯŦŧȾⱦƬƭƮʈT̈ẗᵵƫȶ]{1,2}([rŔŕŘřṘṙŖŗȐȑȒȓṚṛṜṝṞṟR̃r̃ɌɍꞦꞧⱤɽᵲᶉꭉ][yÝýỲỳŶŷY̊ẙŸÿỸỹẎẏȲȳỶỷỴỵɎɏƳƴỾỿ]|[rŔŕŘřṘṙŖŗȐȑȒȓṚṛṜṝṞṟR̃r̃ɌɍꞦꞧⱤɽᵲᶉꭉ][iÍíi̇́Ììi̇̀ĬĭÎîǏǐÏïḮḯĨĩi̇̃ĮįĮ́į̇́Į̃į̇̃ĪīĪ̀ī̀ỈỉȈȉI̋i̋ȊȋỊịꞼꞽḬḭƗɨᶖİiIıIi1lĺľļḷḹl̃ḽḻłŀƚꝉⱡɫɬꞎꬷꬸꬹᶅɭȴLl][e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEe])?)?[sŚśṤṥŜŝŠšṦṧṠṡŞşṢṣṨṩȘșS̩s̩ꞨꞩⱾȿꟅʂᶊᵴ]?\b/, + /\b[kḰḱǨǩĶķḲḳḴḵƘƙⱩⱪᶄꝀꝁꝂꝃꝄꝅꞢꞣ][iÍíi̇́Ììi̇̀ĬĭÎîǏǐÏïḮḯĨĩi̇̃ĮįĮ́į̇́Į̃į̇̃ĪīĪ̀ī̀ỈỉȈȉI̋i̋ȊȋỊịꞼꞽḬḭƗɨᶖİiIıIi1lĺľļḷḹl̃ḽḻłŀƚꝉⱡɫɬꞎꬷꬸꬹᶅɭȴLlyÝýỲỳŶŷY̊ẙŸÿỸỹẎẏȲȳỶỷỴỵɎɏƳƴỾỿ][kḰḱǨǩĶķḲḳḴḵƘƙⱩⱪᶄꝀꝁꝂꝃꝄꝅꞢꞣ][e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEe]([rŔŕŘřṘṙŖŗȐȑȒȓṚṛṜṝṞṟR̃r̃ɌɍꞦꞧⱤɽᵲᶉꭉ][yÝýỲỳŶŷY̊ẙŸÿỸỹẎẏȲȳỶỷỴỵɎɏƳƴỾỿ]|[rŔŕŘřṘṙŖŗȐȑȒȓṚṛṜṝṞṟR̃r̃ɌɍꞦꞧⱤɽᵲᶉꭉ][iÍíi̇́Ììi̇̀ĬĭÎîǏǐÏïḮḯĨĩi̇̃ĮįĮ́į̇́Į̃į̇̃ĪīĪ̀ī̀ỈỉȈȉI̋i̋ȊȋỊịꞼꞽḬḭƗɨᶖİiIıIi1lĺľļḷḹl̃ḽḻłŀƚꝉⱡɫɬꞎꬷꬸꬹᶅɭȴLl][e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEe])?[sŚśṤṥŜŝŠšṦṧṠṡŞşṢṣṨṩȘșS̩s̩ꞨꞩⱾȿꟅʂᶊᵴ]*\b/, + /\b[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲŊŋꞐꞑꞤꞥᵰᶇɳȵꬻꬼИиПпNn][iÍíi̇́Ììi̇̀ĬĭÎîǏǐÏïḮḯĨĩi̇̃ĮįĮ́į̇́Į̃į̇̃ĪīĪ̀ī̀ỈỉȈȉI̋i̋ȊȋỊịꞼꞽḬḭƗɨᶖİiIıIi1lĺľļḷḹl̃ḽḻłŀƚꝉⱡɫɬꞎꬷꬸꬹᶅɭȴLloÓóÒòŎŏÔôỐốỒồỖỗỔổǑǒÖöȪȫŐőÕõṌṍṎṏȬȭȮȯO͘o͘ȰȱØøǾǿǪǫǬǭŌōṒṓṐṑỎỏȌȍȎȏƠơỚớỜờỠỡỞởỢợỌọỘộO̩o̩Ò̩ò̩Ó̩ó̩ƟɵꝊꝋꝌꝍⱺOoІіa4ÁáÀàĂăẮắẰằẴẵẲẳÂâẤấẦầẪẫẨẩǍǎÅåǺǻÄäǞǟÃãȦȧǠǡĄąĄ́ą́Ą̃ą̃ĀāĀ̀ā̀ẢảȀȁA̋a̋ȂȃẠạẶặẬậḀḁȺⱥꞺꞻᶏẚAa][gǴǵĞğĜĝǦǧĠġG̃g̃ĢģḠḡǤǥꞠꞡƓɠᶃꬶGgqꝖꝗꝘꝙɋʠ]{2}(l[e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEe]t|[e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEeaÁáÀàĂăẮắẰằẴẵẲẳÂâẤấẦầẪẫẨẩǍǎÅåǺǻÄäǞǟÃãȦȧǠǡĄąĄ́ą́Ą̃ą̃ĀāĀ̀ā̀ẢảȀȁA̋a̋ȂȃẠạẶặẬậḀḁȺⱥꞺꞻᶏẚAa][rŔŕŘřṘṙŖŗȐȑȒȓṚṛṜṝṞṟR̃r̃ɌɍꞦꞧⱤɽᵲᶉꭉ]?|n[ÓóÒòŎŏÔôỐốỒồỖỗỔổǑǒÖöȪȫŐőÕõṌṍṎṏȬȭȮȯO͘o͘ȰȱØøǾǿǪǫǬǭŌōṒṓṐṑỎỏȌȍȎȏƠơỚớỜờỠỡỞởỢợỌọỘộO̩o̩Ò̩ò̩Ó̩ó̩ƟɵꝊꝋꝌꝍⱺOo0][gǴǵĞğĜĝǦǧĠġG̃g̃ĢģḠḡǤǥꞠꞡƓɠᶃꬶGgqꝖꝗꝘꝙɋʠ]|[a4ÁáÀàĂăẮắẰằẴẵẲẳÂâẤấẦầẪẫẨẩǍǎÅåǺǻÄäǞǟÃãȦȧǠǡĄąĄ́ą́Ą̃ą̃ĀāĀ̀ā̀ẢảȀȁA̋a̋ȂȃẠạẶặẬậḀḁȺⱥꞺꞻᶏẚAa]?)?[sŚśṤṥŜŝŠšṦṧṠṡŞşṢṣṨṩȘșS̩s̩ꞨꞩⱾȿꟅʂᶊᵴ]?\b/, + /[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲŊŋꞐꞑꞤꞥᵰᶇɳȵꬻꬼИиПпNn][iÍíi̇́Ììi̇̀ĬĭÎîǏǐÏïḮḯĨĩi̇̃ĮįĮ́į̇́Į̃į̇̃ĪīĪ̀ī̀ỈỉȈȉI̋i̋ȊȋỊịꞼꞽḬḭƗɨᶖİiIıIi1lĺľļḷḹl̃ḽḻłŀƚꝉⱡɫɬꞎꬷꬸꬹᶅɭȴLloÓóÒòŎŏÔôỐốỒồỖỗỔổǑǒÖöȪȫŐőÕõṌṍṎṏȬȭȮȯO͘o͘ȰȱØøǾǿǪǫǬǭŌōṒṓṐṑỎỏȌȍȎȏƠơỚớỜờỠỡỞởỢợỌọỘộO̩o̩Ò̩ò̩Ó̩ó̩ƟɵꝊꝋꝌꝍⱺOoІіa4ÁáÀàĂăẮắẰằẴẵẲẳÂâẤấẦầẪẫẨẩǍǎÅåǺǻÄäǞǟÃãȦȧǠǡĄąĄ́ą́Ą̃ą̃ĀāĀ̀ā̀ẢảȀȁA̋a̋ȂȃẠạẶặẬậḀḁȺⱥꞺꞻᶏẚAa][gǴǵĞğĜĝǦǧĠġG̃g̃ĢģḠḡǤǥꞠꞡƓɠᶃꬶGgqꝖꝗꝘꝙɋʠ]{2}(l[e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEe]t|[e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEe][rŔŕŘřṘṙŖŗȐȑȒȓṚṛṜṝṞṟR̃r̃ɌɍꞦꞧⱤɽᵲᶉꭉ])[sŚśṤṥŜŝŠšṦṧṠṡŞşṢṣṨṩȘșS̩s̩ꞨꞩⱾȿꟅʂᶊᵴ]?/, + /\b[tŤťṪṫŢţṬṭȚțṰṱṮṯŦŧȾⱦƬƭƮʈT̈ẗᵵƫȶ][rŔŕŘřṘṙŖŗȐȑȒȓṚṛṜṝṞṟR̃r̃ɌɍꞦꞧⱤɽᵲᶉꭉ][aÁáÀàĂăẮắẰằẴẵẲẳÂâẤấẦầẪẫẨẩǍǎÅåǺǻÄäǞǟÃãȦȧǠǡĄąĄ́ą́Ą̃ą̃ĀāĀ̀ā̀ẢảȀȁA̋a̋ȂȃẠạẶặẬậḀḁȺⱥꞺꞻᶏẚAa4]+[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲŊŋꞐꞑꞤꞥᵰᶇɳȵꬻꬼИиПпNn]{1,2}([iÍíi̇́Ììi̇̀ĬĭÎîǏǐÏïḮḯĨĩi̇̃ĮįĮ́į̇́Į̃į̇̃ĪīĪ̀ī̀ỈỉȈȉI̋i̋ȊȋỊịꞼꞽḬḭƗɨᶖİiIıIi1lĺľļḷḹl̃ḽḻłŀƚꝉⱡɫɬꞎꬷꬸꬹᶅɭȴLl][e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEe]|[yÝýỲỳŶŷY̊ẙŸÿỸỹẎẏȲȳỶỷỴỵɎɏƳƴỾỿ]|[e3ЄєЕеÉéÈèĔĕÊêẾếỀềỄễỂểÊ̄ê̄Ê̌ê̌ĚěËëẼẽĖėĖ́ė́Ė̃ė̃ȨȩḜḝĘęĘ́ę́Ę̃ę̃ĒēḖḗḔḕẺẻȄȅE̋e̋ȆȇẸẹỆệḘḙḚḛɆɇE̩e̩È̩è̩É̩é̩ᶒⱸꬴꬳEe][rŔŕŘřṘṙŖŗȐȑȒȓṚṛṜṝṞṟR̃r̃ɌɍꞦꞧⱤɽᵲᶉꭉ])[sŚśṤṥŜŝŠšṦṧṠṡŞşṢṣṨṩȘșS̩s̩ꞨꞩⱾȿꟅʂᶊᵴ]?\b/, +] + +export const hasExplicitSlur = (handle: string): boolean => { + return explicitSlurRegexes.some( + (reg) => + reg.test(handle) || + reg.test( + handle.replaceAll('.', '').replaceAll('-', '').replaceAll('_', ''), + ), + ) +} diff --git a/packages/pds/src/handle/index.ts b/packages/pds/src/handle/index.ts new file mode 100644 index 00000000000..ec531fcc80f --- /dev/null +++ b/packages/pds/src/handle/index.ts @@ -0,0 +1,95 @@ +import * as ident from '@atproto/syntax' +import { InvalidRequestError } from '@atproto/xrpc-server' +import { reservedSubdomains } from './reserved' +import { hasExplicitSlur } from './explicit-slurs' +import AppContext from '../context' + +export const normalizeAndValidateHandle = async (opts: { + ctx: AppContext + handle: string + did?: string + allowReserved?: boolean +}): Promise => { + const { ctx, did, allowReserved } = opts + // base formatting validation + const handle = baseNormalizeAndValidate(opts.handle) + // tld validation + if (!ident.isValidTld(handle)) { + throw new InvalidRequestError( + 'Handle TLD is invalid or disallowed', + 'InvalidHandle', + ) + } + // slur check + if (hasExplicitSlur(handle)) { + throw new InvalidRequestError( + 'Inappropriate language in handle', + 'InvalidHandle', + ) + } + if (isServiceDomain(handle, ctx.cfg.identity.serviceHandleDomains)) { + // verify constraints on a service domain + ensureHandleServiceConstraints( + handle, + ctx.cfg.identity.serviceHandleDomains, + allowReserved, + ) + } else { + if (opts.did === undefined) { + throw new InvalidRequestError( + 'Not a supported handle domain', + 'UnsupportedDomain', + ) + } + // verify resolution of a non-service domain + const resolvedDid = await ctx.idResolver.handle.resolve(handle) + if (resolvedDid !== did) { + throw new InvalidRequestError('External handle did not resolve to DID') + } + } + return handle +} + +export const baseNormalizeAndValidate = (handle: string) => { + try { + const normalized = ident.normalizeAndEnsureValidHandle(handle) + return normalized + } catch (err) { + if (err instanceof ident.InvalidHandleError) { + throw new InvalidRequestError(err.message, 'InvalidHandle') + } + throw err + } +} + +export const isServiceDomain = ( + handle: string, + availableUserDomains: string[], +): boolean => { + return availableUserDomains.some((domain) => handle.endsWith(domain)) +} + +export const ensureHandleServiceConstraints = ( + handle: string, + availableUserDomains: string[], + allowReserved = false, +): void => { + const supportedDomain = + availableUserDomains.find((domain) => handle.endsWith(domain)) ?? '' + const front = handle.slice(0, handle.length - supportedDomain.length) + if (front.includes('.')) { + throw new InvalidRequestError( + 'Invalid characters in handle', + 'InvalidHandle', + ) + } + if (front.length < 3) { + throw new InvalidRequestError('Handle too short', 'InvalidHandle') + } + if (handle.length > 30) { + throw new InvalidRequestError('Handle too long', 'InvalidHandle') + } + if (!allowReserved && reservedSubdomains[front]) { + throw new InvalidRequestError('Reserved handle', 'HandleNotAvailable') + } +} diff --git a/packages/identifier/src/reserved.ts b/packages/pds/src/handle/reserved.ts similarity index 99% rename from packages/identifier/src/reserved.ts rename to packages/pds/src/handle/reserved.ts index 83180160e61..c49c85f5378 100644 --- a/packages/identifier/src/reserved.ts +++ b/packages/pds/src/handle/reserved.ts @@ -864,6 +864,7 @@ const famousAccounts = [ // reserving some large twitter accounts (top 100 by followers according to wikidata dump) '10ronaldinho', '3gerardpique', + 'aclu', 'adele', 'akshaykumar', 'aliaa08', diff --git a/packages/pds/src/index.ts b/packages/pds/src/index.ts index bd90c62b70d..101b4944a46 100644 --- a/packages/pds/src/index.ts +++ b/packages/pds/src/index.ts @@ -8,15 +8,23 @@ import express from 'express' import cors from 'cors' import http from 'http' import events from 'events' +import { MINUTE } from '@atproto/common' +import { + RateLimiter, + RateLimiterCreator, + RateLimiterOpts, + Options as XrpcServerOptions, +} from '@atproto/xrpc-server' import API from './api' import * as basicRoutes from './basic-routes' import * as wellKnown from './well-known' import * as error from './error' -import { dbLogger, loggerMiddleware } from './logger' +import { dbLogger, loggerMiddleware, seqLogger } from './logger' import { ServerConfig, ServerSecrets } from './config' import { createServer } from './lexicon' import { createHttpTerminator, HttpTerminator } from 'http-terminator' import AppContext, { AppContextOptions } from './context' +import compression from './util/compression' export * from './config' export { Database } from './db' @@ -30,6 +38,7 @@ export class PDS { public server?: http.Server private terminator?: HttpTerminator private dbStatsInterval?: NodeJS.Timer + private sequencerStatsInterval?: NodeJS.Timer constructor(opts: { ctx: AppContext; app: express.Application }) { this.ctx = opts.ctx @@ -42,19 +51,52 @@ export class PDS { overrides?: Partial, ): Promise { const app = express() + app.set('trust proxy', true) app.use(cors()) app.use(loggerMiddleware) + app.use(compression()) const ctx = await AppContext.fromConfig(cfg, secrets, overrides) - let server = createServer({ + const xrpcOpts: XrpcServerOptions = { validateResponse: false, payload: { jsonLimit: 100 * 1024, // 100kb textLimit: 100 * 1024, // 100kb blobLimit: 5 * 1024 * 1024, // 5mb }, - }) + } + if (cfg.rateLimits.enabled) { + let rlCreator: RateLimiterCreator + if (cfg.rateLimits.mode === 'redis') { + if (!ctx.redisScratch) { + throw new Error('Redis not set up for ratelimiting mode: `redis`') + } + rlCreator = (opts: RateLimiterOpts) => + RateLimiter.redis(ctx.redisScratch, { + // bypassSecret: cfg.rateLimits., + ...opts, + }) + } else { + rlCreator = (opts: RateLimiterOpts) => + RateLimiter.memory({ + // bypassSecret: config.rateLimitBypassKey, + ...opts, + }) + } + xrpcOpts['rateLimits'] = { + creator: rlCreator, + global: [ + { + name: 'global-ip', + durationMs: 5 * MINUTE, + points: 3000, + }, + ], + } + } + + let server = createServer(xrpcOpts) server = API(server, ctx) @@ -91,9 +133,20 @@ export class PDS { ) }, 10000) } - this.ctx.sequencerLeader.run() + this.sequencerStatsInterval = setInterval(async () => { + if (this.ctx.sequencerLeader?.isLeader) { + try { + const seq = await this.ctx.sequencerLeader.lastSeq() + seqLogger.info({ seq }, 'sequencer leader stats') + } catch (err) { + seqLogger.error({ err }, 'error getting last seq') + } + } + }, 500) + this.ctx.sequencerLeader?.run() await this.ctx.sequencer.start() await this.ctx.db.startListeningToChannels() + await this.ctx.runtimeFlags.start() const server = this.app.listen(this.ctx.cfg.service.port) this.server = server this.server.keepAliveTimeout = 90000 @@ -103,11 +156,14 @@ export class PDS { } async destroy(): Promise { - await this.ctx.sequencerLeader.destroy() + await this.ctx.runtimeFlags.destroy() + await this.ctx.sequencerLeader?.destroy() await this.terminator?.terminate() await this.ctx.backgroundQueue.destroy() await this.ctx.db.close() + await this.ctx.redisScratch?.quit() clearInterval(this.dbStatsInterval) + clearInterval(this.sequencerStatsInterval) } } diff --git a/packages/pds/src/lexicon/index.ts b/packages/pds/src/lexicon/index.ts index 548011fc331..93435056503 100644 --- a/packages/pds/src/lexicon/index.ts +++ b/packages/pds/src/lexicon/index.ts @@ -19,10 +19,10 @@ import * as ComAtprotoAdminGetModerationReport from './types/com/atproto/admin/g import * as ComAtprotoAdminGetModerationReports from './types/com/atproto/admin/getModerationReports' import * as ComAtprotoAdminGetRecord from './types/com/atproto/admin/getRecord' import * as ComAtprotoAdminGetRepo from './types/com/atproto/admin/getRepo' -import * as ComAtprotoAdminRebaseRepo from './types/com/atproto/admin/rebaseRepo' import * as ComAtprotoAdminResolveModerationReports from './types/com/atproto/admin/resolveModerationReports' import * as ComAtprotoAdminReverseModerationAction from './types/com/atproto/admin/reverseModerationAction' import * as ComAtprotoAdminSearchRepos from './types/com/atproto/admin/searchRepos' +import * as ComAtprotoAdminSendEmail from './types/com/atproto/admin/sendEmail' import * as ComAtprotoAdminTakeModerationAction from './types/com/atproto/admin/takeModerationAction' import * as ComAtprotoAdminUpdateAccountEmail from './types/com/atproto/admin/updateAccountEmail' import * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/updateAccountHandle' @@ -38,7 +38,6 @@ import * as ComAtprotoRepoDescribeRepo from './types/com/atproto/repo/describeRe import * as ComAtprotoRepoGetRecord from './types/com/atproto/repo/getRecord' import * as ComAtprotoRepoListRecords from './types/com/atproto/repo/listRecords' import * as ComAtprotoRepoPutRecord from './types/com/atproto/repo/putRecord' -import * as ComAtprotoRepoRebaseRepo from './types/com/atproto/repo/rebaseRepo' import * as ComAtprotoRepoUploadBlob from './types/com/atproto/repo/uploadBlob' import * as ComAtprotoServerCreateAccount from './types/com/atproto/server/createAccount' import * as ComAtprotoServerCreateAppPassword from './types/com/atproto/server/createAppPassword' @@ -59,8 +58,8 @@ import * as ComAtprotoServerRevokeAppPassword from './types/com/atproto/server/r import * as ComAtprotoSyncGetBlob from './types/com/atproto/sync/getBlob' import * as ComAtprotoSyncGetBlocks from './types/com/atproto/sync/getBlocks' import * as ComAtprotoSyncGetCheckout from './types/com/atproto/sync/getCheckout' -import * as ComAtprotoSyncGetCommitPath from './types/com/atproto/sync/getCommitPath' import * as ComAtprotoSyncGetHead from './types/com/atproto/sync/getHead' +import * as ComAtprotoSyncGetLatestCommit from './types/com/atproto/sync/getLatestCommit' import * as ComAtprotoSyncGetRecord from './types/com/atproto/sync/getRecord' import * as ComAtprotoSyncGetRepo from './types/com/atproto/sync/getRepo' import * as ComAtprotoSyncListBlobs from './types/com/atproto/sync/listBlobs' @@ -77,6 +76,7 @@ import * as AppBskyActorSearchActors from './types/app/bsky/actor/searchActors' import * as AppBskyActorSearchActorsTypeahead from './types/app/bsky/actor/searchActorsTypeahead' import * as AppBskyFeedDescribeFeedGenerator from './types/app/bsky/feed/describeFeedGenerator' import * as AppBskyFeedGetActorFeeds from './types/app/bsky/feed/getActorFeeds' +import * as AppBskyFeedGetActorLikes from './types/app/bsky/feed/getActorLikes' import * as AppBskyFeedGetAuthorFeed from './types/app/bsky/feed/getAuthorFeed' import * as AppBskyFeedGetFeed from './types/app/bsky/feed/getFeed' import * as AppBskyFeedGetFeedGenerator from './types/app/bsky/feed/getFeedGenerator' @@ -86,23 +86,29 @@ import * as AppBskyFeedGetLikes from './types/app/bsky/feed/getLikes' import * as AppBskyFeedGetPostThread from './types/app/bsky/feed/getPostThread' import * as AppBskyFeedGetPosts from './types/app/bsky/feed/getPosts' import * as AppBskyFeedGetRepostedBy from './types/app/bsky/feed/getRepostedBy' +import * as AppBskyFeedGetSuggestedFeeds from './types/app/bsky/feed/getSuggestedFeeds' import * as AppBskyFeedGetTimeline from './types/app/bsky/feed/getTimeline' import * as AppBskyGraphGetBlocks from './types/app/bsky/graph/getBlocks' import * as AppBskyGraphGetFollowers from './types/app/bsky/graph/getFollowers' import * as AppBskyGraphGetFollows from './types/app/bsky/graph/getFollows' import * as AppBskyGraphGetList from './types/app/bsky/graph/getList' +import * as AppBskyGraphGetListBlocks from './types/app/bsky/graph/getListBlocks' import * as AppBskyGraphGetListMutes from './types/app/bsky/graph/getListMutes' import * as AppBskyGraphGetLists from './types/app/bsky/graph/getLists' import * as AppBskyGraphGetMutes from './types/app/bsky/graph/getMutes' +import * as AppBskyGraphGetSuggestedFollowsByActor from './types/app/bsky/graph/getSuggestedFollowsByActor' import * as AppBskyGraphMuteActor from './types/app/bsky/graph/muteActor' import * as AppBskyGraphMuteActorList from './types/app/bsky/graph/muteActorList' import * as AppBskyGraphUnmuteActor from './types/app/bsky/graph/unmuteActor' import * as AppBskyGraphUnmuteActorList from './types/app/bsky/graph/unmuteActorList' import * as AppBskyNotificationGetUnreadCount from './types/app/bsky/notification/getUnreadCount' import * as AppBskyNotificationListNotifications from './types/app/bsky/notification/listNotifications' +import * as AppBskyNotificationRegisterPush from './types/app/bsky/notification/registerPush' import * as AppBskyNotificationUpdateSeen from './types/app/bsky/notification/updateSeen' +import * as AppBskyUnspeccedApplyLabels from './types/app/bsky/unspecced/applyLabels' import * as AppBskyUnspeccedGetPopular from './types/app/bsky/unspecced/getPopular' import * as AppBskyUnspeccedGetPopularFeedGenerators from './types/app/bsky/unspecced/getPopularFeedGenerators' +import * as AppBskyUnspeccedGetTimelineSkeleton from './types/app/bsky/unspecced/getTimelineSkeleton' export const COM_ATPROTO_ADMIN = { DefsTakedown: 'com.atproto.admin.defs#takedown', @@ -180,7 +186,8 @@ export class AdminNS { disableAccountInvites( cfg: ConfigOf< AV, - ComAtprotoAdminDisableAccountInvites.Handler> + ComAtprotoAdminDisableAccountInvites.Handler>, + ComAtprotoAdminDisableAccountInvites.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.disableAccountInvites' // @ts-ignore @@ -190,7 +197,8 @@ export class AdminNS { disableInviteCodes( cfg: ConfigOf< AV, - ComAtprotoAdminDisableInviteCodes.Handler> + ComAtprotoAdminDisableInviteCodes.Handler>, + ComAtprotoAdminDisableInviteCodes.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.disableInviteCodes' // @ts-ignore @@ -200,7 +208,8 @@ export class AdminNS { enableAccountInvites( cfg: ConfigOf< AV, - ComAtprotoAdminEnableAccountInvites.Handler> + ComAtprotoAdminEnableAccountInvites.Handler>, + ComAtprotoAdminEnableAccountInvites.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.enableAccountInvites' // @ts-ignore @@ -208,7 +217,11 @@ export class AdminNS { } getInviteCodes( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoAdminGetInviteCodes.Handler>, + ComAtprotoAdminGetInviteCodes.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.admin.getInviteCodes' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -217,7 +230,8 @@ export class AdminNS { getModerationAction( cfg: ConfigOf< AV, - ComAtprotoAdminGetModerationAction.Handler> + ComAtprotoAdminGetModerationAction.Handler>, + ComAtprotoAdminGetModerationAction.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.getModerationAction' // @ts-ignore @@ -227,7 +241,8 @@ export class AdminNS { getModerationActions( cfg: ConfigOf< AV, - ComAtprotoAdminGetModerationActions.Handler> + ComAtprotoAdminGetModerationActions.Handler>, + ComAtprotoAdminGetModerationActions.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.getModerationActions' // @ts-ignore @@ -237,7 +252,8 @@ export class AdminNS { getModerationReport( cfg: ConfigOf< AV, - ComAtprotoAdminGetModerationReport.Handler> + ComAtprotoAdminGetModerationReport.Handler>, + ComAtprotoAdminGetModerationReport.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.getModerationReport' // @ts-ignore @@ -247,7 +263,8 @@ export class AdminNS { getModerationReports( cfg: ConfigOf< AV, - ComAtprotoAdminGetModerationReports.Handler> + ComAtprotoAdminGetModerationReports.Handler>, + ComAtprotoAdminGetModerationReports.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.getModerationReports' // @ts-ignore @@ -255,30 +272,32 @@ export class AdminNS { } getRecord( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoAdminGetRecord.Handler>, + ComAtprotoAdminGetRecord.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.admin.getRecord' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getRepo( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoAdminGetRepo.Handler>, + ComAtprotoAdminGetRepo.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.admin.getRepo' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } - rebaseRepo( - cfg: ConfigOf>>, - ) { - const nsid = 'com.atproto.admin.rebaseRepo' // @ts-ignore - return this._server.xrpc.method(nsid, cfg) - } - resolveModerationReports( cfg: ConfigOf< AV, - ComAtprotoAdminResolveModerationReports.Handler> + ComAtprotoAdminResolveModerationReports.Handler>, + ComAtprotoAdminResolveModerationReports.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.resolveModerationReports' // @ts-ignore @@ -288,7 +307,8 @@ export class AdminNS { reverseModerationAction( cfg: ConfigOf< AV, - ComAtprotoAdminReverseModerationAction.Handler> + ComAtprotoAdminReverseModerationAction.Handler>, + ComAtprotoAdminReverseModerationAction.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.reverseModerationAction' // @ts-ignore @@ -296,16 +316,32 @@ export class AdminNS { } searchRepos( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoAdminSearchRepos.Handler>, + ComAtprotoAdminSearchRepos.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.admin.searchRepos' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + sendEmail( + cfg: ConfigOf< + AV, + ComAtprotoAdminSendEmail.Handler>, + ComAtprotoAdminSendEmail.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.admin.sendEmail' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + takeModerationAction( cfg: ConfigOf< AV, - ComAtprotoAdminTakeModerationAction.Handler> + ComAtprotoAdminTakeModerationAction.Handler>, + ComAtprotoAdminTakeModerationAction.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.takeModerationAction' // @ts-ignore @@ -315,7 +351,8 @@ export class AdminNS { updateAccountEmail( cfg: ConfigOf< AV, - ComAtprotoAdminUpdateAccountEmail.Handler> + ComAtprotoAdminUpdateAccountEmail.Handler>, + ComAtprotoAdminUpdateAccountEmail.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.updateAccountEmail' // @ts-ignore @@ -325,7 +362,8 @@ export class AdminNS { updateAccountHandle( cfg: ConfigOf< AV, - ComAtprotoAdminUpdateAccountHandle.Handler> + ComAtprotoAdminUpdateAccountHandle.Handler>, + ComAtprotoAdminUpdateAccountHandle.HandlerReqCtx> >, ) { const nsid = 'com.atproto.admin.updateAccountHandle' // @ts-ignore @@ -341,14 +379,22 @@ export class IdentityNS { } resolveHandle( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoIdentityResolveHandle.Handler>, + ComAtprotoIdentityResolveHandle.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.identity.resolveHandle' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } updateHandle( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoIdentityUpdateHandle.Handler>, + ComAtprotoIdentityUpdateHandle.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.identity.updateHandle' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -363,14 +409,22 @@ export class LabelNS { } queryLabels( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoLabelQueryLabels.Handler>, + ComAtprotoLabelQueryLabels.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.label.queryLabels' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } subscribeLabels( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoLabelSubscribeLabels.Handler>, + ComAtprotoLabelSubscribeLabels.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.label.subscribeLabels' // @ts-ignore return this._server.xrpc.streamMethod(nsid, cfg) @@ -387,7 +441,8 @@ export class ModerationNS { createReport( cfg: ConfigOf< AV, - ComAtprotoModerationCreateReport.Handler> + ComAtprotoModerationCreateReport.Handler>, + ComAtprotoModerationCreateReport.HandlerReqCtx> >, ) { const nsid = 'com.atproto.moderation.createReport' // @ts-ignore @@ -403,63 +458,88 @@ export class RepoNS { } applyWrites( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoRepoApplyWrites.Handler>, + ComAtprotoRepoApplyWrites.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.repo.applyWrites' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } createRecord( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoRepoCreateRecord.Handler>, + ComAtprotoRepoCreateRecord.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.repo.createRecord' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } deleteRecord( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoRepoDeleteRecord.Handler>, + ComAtprotoRepoDeleteRecord.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.repo.deleteRecord' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } describeRepo( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoRepoDescribeRepo.Handler>, + ComAtprotoRepoDescribeRepo.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.repo.describeRepo' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getRecord( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoRepoGetRecord.Handler>, + ComAtprotoRepoGetRecord.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.repo.getRecord' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } listRecords( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoRepoListRecords.Handler>, + ComAtprotoRepoListRecords.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.repo.listRecords' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } putRecord( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoRepoPutRecord.Handler>, + ComAtprotoRepoPutRecord.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.repo.putRecord' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } - rebaseRepo( - cfg: ConfigOf>>, - ) { - const nsid = 'com.atproto.repo.rebaseRepo' // @ts-ignore - return this._server.xrpc.method(nsid, cfg) - } - uploadBlob( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoRepoUploadBlob.Handler>, + ComAtprotoRepoUploadBlob.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.repo.uploadBlob' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -474,7 +554,11 @@ export class ServerNS { } createAccount( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoServerCreateAccount.Handler>, + ComAtprotoServerCreateAccount.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.server.createAccount' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -483,7 +567,8 @@ export class ServerNS { createAppPassword( cfg: ConfigOf< AV, - ComAtprotoServerCreateAppPassword.Handler> + ComAtprotoServerCreateAppPassword.Handler>, + ComAtprotoServerCreateAppPassword.HandlerReqCtx> >, ) { const nsid = 'com.atproto.server.createAppPassword' // @ts-ignore @@ -493,7 +578,8 @@ export class ServerNS { createInviteCode( cfg: ConfigOf< AV, - ComAtprotoServerCreateInviteCode.Handler> + ComAtprotoServerCreateInviteCode.Handler>, + ComAtprotoServerCreateInviteCode.HandlerReqCtx> >, ) { const nsid = 'com.atproto.server.createInviteCode' // @ts-ignore @@ -503,7 +589,8 @@ export class ServerNS { createInviteCodes( cfg: ConfigOf< AV, - ComAtprotoServerCreateInviteCodes.Handler> + ComAtprotoServerCreateInviteCodes.Handler>, + ComAtprotoServerCreateInviteCodes.HandlerReqCtx> >, ) { const nsid = 'com.atproto.server.createInviteCodes' // @ts-ignore @@ -511,28 +598,44 @@ export class ServerNS { } createSession( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoServerCreateSession.Handler>, + ComAtprotoServerCreateSession.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.server.createSession' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } deleteAccount( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoServerDeleteAccount.Handler>, + ComAtprotoServerDeleteAccount.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.server.deleteAccount' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } deleteSession( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoServerDeleteSession.Handler>, + ComAtprotoServerDeleteSession.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.server.deleteSession' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } describeServer( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoServerDescribeServer.Handler>, + ComAtprotoServerDescribeServer.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.server.describeServer' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -541,7 +644,8 @@ export class ServerNS { getAccountInviteCodes( cfg: ConfigOf< AV, - ComAtprotoServerGetAccountInviteCodes.Handler> + ComAtprotoServerGetAccountInviteCodes.Handler>, + ComAtprotoServerGetAccountInviteCodes.HandlerReqCtx> >, ) { const nsid = 'com.atproto.server.getAccountInviteCodes' // @ts-ignore @@ -549,7 +653,11 @@ export class ServerNS { } getSession( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoServerGetSession.Handler>, + ComAtprotoServerGetSession.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.server.getSession' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -558,7 +666,8 @@ export class ServerNS { listAppPasswords( cfg: ConfigOf< AV, - ComAtprotoServerListAppPasswords.Handler> + ComAtprotoServerListAppPasswords.Handler>, + ComAtprotoServerListAppPasswords.HandlerReqCtx> >, ) { const nsid = 'com.atproto.server.listAppPasswords' // @ts-ignore @@ -566,7 +675,11 @@ export class ServerNS { } refreshSession( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoServerRefreshSession.Handler>, + ComAtprotoServerRefreshSession.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.server.refreshSession' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -575,7 +688,8 @@ export class ServerNS { requestAccountDelete( cfg: ConfigOf< AV, - ComAtprotoServerRequestAccountDelete.Handler> + ComAtprotoServerRequestAccountDelete.Handler>, + ComAtprotoServerRequestAccountDelete.HandlerReqCtx> >, ) { const nsid = 'com.atproto.server.requestAccountDelete' // @ts-ignore @@ -585,7 +699,8 @@ export class ServerNS { requestPasswordReset( cfg: ConfigOf< AV, - ComAtprotoServerRequestPasswordReset.Handler> + ComAtprotoServerRequestPasswordReset.Handler>, + ComAtprotoServerRequestPasswordReset.HandlerReqCtx> >, ) { const nsid = 'com.atproto.server.requestPasswordReset' // @ts-ignore @@ -593,7 +708,11 @@ export class ServerNS { } resetPassword( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoServerResetPassword.Handler>, + ComAtprotoServerResetPassword.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.server.resetPassword' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -602,7 +721,8 @@ export class ServerNS { revokeAppPassword( cfg: ConfigOf< AV, - ComAtprotoServerRevokeAppPassword.Handler> + ComAtprotoServerRevokeAppPassword.Handler>, + ComAtprotoServerRevokeAppPassword.HandlerReqCtx> >, ) { const nsid = 'com.atproto.server.revokeAppPassword' // @ts-ignore @@ -618,84 +738,132 @@ export class SyncNS { } getBlob( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncGetBlob.Handler>, + ComAtprotoSyncGetBlob.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.getBlob' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getBlocks( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncGetBlocks.Handler>, + ComAtprotoSyncGetBlocks.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.getBlocks' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getCheckout( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncGetCheckout.Handler>, + ComAtprotoSyncGetCheckout.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.getCheckout' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } - getCommitPath( - cfg: ConfigOf>>, + getHead( + cfg: ConfigOf< + AV, + ComAtprotoSyncGetHead.Handler>, + ComAtprotoSyncGetHead.HandlerReqCtx> + >, ) { - const nsid = 'com.atproto.sync.getCommitPath' // @ts-ignore + const nsid = 'com.atproto.sync.getHead' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } - getHead( - cfg: ConfigOf>>, + getLatestCommit( + cfg: ConfigOf< + AV, + ComAtprotoSyncGetLatestCommit.Handler>, + ComAtprotoSyncGetLatestCommit.HandlerReqCtx> + >, ) { - const nsid = 'com.atproto.sync.getHead' // @ts-ignore + const nsid = 'com.atproto.sync.getLatestCommit' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getRecord( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncGetRecord.Handler>, + ComAtprotoSyncGetRecord.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.getRecord' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getRepo( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncGetRepo.Handler>, + ComAtprotoSyncGetRepo.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.getRepo' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } listBlobs( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncListBlobs.Handler>, + ComAtprotoSyncListBlobs.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.listBlobs' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } listRepos( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncListRepos.Handler>, + ComAtprotoSyncListRepos.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.listRepos' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } notifyOfUpdate( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncNotifyOfUpdate.Handler>, + ComAtprotoSyncNotifyOfUpdate.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.notifyOfUpdate' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } requestCrawl( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncRequestCrawl.Handler>, + ComAtprotoSyncRequestCrawl.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.requestCrawl' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } subscribeRepos( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + ComAtprotoSyncSubscribeRepos.Handler>, + ComAtprotoSyncSubscribeRepos.HandlerReqCtx> + >, ) { const nsid = 'com.atproto.sync.subscribeRepos' // @ts-ignore return this._server.xrpc.streamMethod(nsid, cfg) @@ -742,42 +910,66 @@ export class ActorNS { } getPreferences( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyActorGetPreferences.Handler>, + AppBskyActorGetPreferences.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.actor.getPreferences' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getProfile( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyActorGetProfile.Handler>, + AppBskyActorGetProfile.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.actor.getProfile' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getProfiles( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyActorGetProfiles.Handler>, + AppBskyActorGetProfiles.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.actor.getProfiles' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getSuggestions( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyActorGetSuggestions.Handler>, + AppBskyActorGetSuggestions.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.actor.getSuggestions' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } putPreferences( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyActorPutPreferences.Handler>, + AppBskyActorPutPreferences.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.actor.putPreferences' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } searchActors( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyActorSearchActors.Handler>, + AppBskyActorSearchActors.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.actor.searchActors' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -786,7 +978,8 @@ export class ActorNS { searchActorsTypeahead( cfg: ConfigOf< AV, - AppBskyActorSearchActorsTypeahead.Handler> + AppBskyActorSearchActorsTypeahead.Handler>, + AppBskyActorSearchActorsTypeahead.HandlerReqCtx> >, ) { const nsid = 'app.bsky.actor.searchActorsTypeahead' // @ts-ignore @@ -812,7 +1005,8 @@ export class FeedNS { describeFeedGenerator( cfg: ConfigOf< AV, - AppBskyFeedDescribeFeedGenerator.Handler> + AppBskyFeedDescribeFeedGenerator.Handler>, + AppBskyFeedDescribeFeedGenerator.HandlerReqCtx> >, ) { const nsid = 'app.bsky.feed.describeFeedGenerator' // @ts-ignore @@ -820,77 +1014,143 @@ export class FeedNS { } getActorFeeds( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetActorFeeds.Handler>, + AppBskyFeedGetActorFeeds.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getActorFeeds' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + getActorLikes( + cfg: ConfigOf< + AV, + AppBskyFeedGetActorLikes.Handler>, + AppBskyFeedGetActorLikes.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.feed.getActorLikes' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + getAuthorFeed( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetAuthorFeed.Handler>, + AppBskyFeedGetAuthorFeed.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getAuthorFeed' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getFeed( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetFeed.Handler>, + AppBskyFeedGetFeed.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getFeed' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getFeedGenerator( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetFeedGenerator.Handler>, + AppBskyFeedGetFeedGenerator.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getFeedGenerator' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getFeedGenerators( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetFeedGenerators.Handler>, + AppBskyFeedGetFeedGenerators.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getFeedGenerators' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getFeedSkeleton( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetFeedSkeleton.Handler>, + AppBskyFeedGetFeedSkeleton.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getFeedSkeleton' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getLikes( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetLikes.Handler>, + AppBskyFeedGetLikes.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getLikes' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getPostThread( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetPostThread.Handler>, + AppBskyFeedGetPostThread.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getPostThread' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getPosts( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetPosts.Handler>, + AppBskyFeedGetPosts.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getPosts' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getRepostedBy( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetRepostedBy.Handler>, + AppBskyFeedGetRepostedBy.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getRepostedBy' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + getSuggestedFeeds( + cfg: ConfigOf< + AV, + AppBskyFeedGetSuggestedFeeds.Handler>, + AppBskyFeedGetSuggestedFeeds.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.feed.getSuggestedFeeds' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + getTimeline( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyFeedGetTimeline.Handler>, + AppBskyFeedGetTimeline.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.feed.getTimeline' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -905,77 +1165,143 @@ export class GraphNS { } getBlocks( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphGetBlocks.Handler>, + AppBskyGraphGetBlocks.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.getBlocks' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getFollowers( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphGetFollowers.Handler>, + AppBskyGraphGetFollowers.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.getFollowers' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getFollows( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphGetFollows.Handler>, + AppBskyGraphGetFollows.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.getFollows' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getList( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphGetList.Handler>, + AppBskyGraphGetList.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.getList' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + getListBlocks( + cfg: ConfigOf< + AV, + AppBskyGraphGetListBlocks.Handler>, + AppBskyGraphGetListBlocks.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.graph.getListBlocks' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + getListMutes( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphGetListMutes.Handler>, + AppBskyGraphGetListMutes.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.getListMutes' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getLists( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphGetLists.Handler>, + AppBskyGraphGetLists.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.getLists' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } getMutes( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphGetMutes.Handler>, + AppBskyGraphGetMutes.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.getMutes' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + getSuggestedFollowsByActor( + cfg: ConfigOf< + AV, + AppBskyGraphGetSuggestedFollowsByActor.Handler>, + AppBskyGraphGetSuggestedFollowsByActor.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.graph.getSuggestedFollowsByActor' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + muteActor( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphMuteActor.Handler>, + AppBskyGraphMuteActor.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.muteActor' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } muteActorList( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphMuteActorList.Handler>, + AppBskyGraphMuteActorList.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.muteActorList' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } unmuteActor( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphUnmuteActor.Handler>, + AppBskyGraphUnmuteActor.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.unmuteActor' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } unmuteActorList( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyGraphUnmuteActorList.Handler>, + AppBskyGraphUnmuteActorList.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.graph.unmuteActorList' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -992,7 +1318,8 @@ export class NotificationNS { getUnreadCount( cfg: ConfigOf< AV, - AppBskyNotificationGetUnreadCount.Handler> + AppBskyNotificationGetUnreadCount.Handler>, + AppBskyNotificationGetUnreadCount.HandlerReqCtx> >, ) { const nsid = 'app.bsky.notification.getUnreadCount' // @ts-ignore @@ -1002,15 +1329,31 @@ export class NotificationNS { listNotifications( cfg: ConfigOf< AV, - AppBskyNotificationListNotifications.Handler> + AppBskyNotificationListNotifications.Handler>, + AppBskyNotificationListNotifications.HandlerReqCtx> >, ) { const nsid = 'app.bsky.notification.listNotifications' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + registerPush( + cfg: ConfigOf< + AV, + AppBskyNotificationRegisterPush.Handler>, + AppBskyNotificationRegisterPush.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.notification.registerPush' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + updateSeen( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyNotificationUpdateSeen.Handler>, + AppBskyNotificationUpdateSeen.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.notification.updateSeen' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -1032,8 +1375,23 @@ export class UnspeccedNS { this._server = server } + applyLabels( + cfg: ConfigOf< + AV, + AppBskyUnspeccedApplyLabels.Handler>, + AppBskyUnspeccedApplyLabels.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.unspecced.applyLabels' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + getPopular( - cfg: ConfigOf>>, + cfg: ConfigOf< + AV, + AppBskyUnspeccedGetPopular.Handler>, + AppBskyUnspeccedGetPopular.HandlerReqCtx> + >, ) { const nsid = 'app.bsky.unspecced.getPopular' // @ts-ignore return this._server.xrpc.method(nsid, cfg) @@ -1042,18 +1400,43 @@ export class UnspeccedNS { getPopularFeedGenerators( cfg: ConfigOf< AV, - AppBskyUnspeccedGetPopularFeedGenerators.Handler> + AppBskyUnspeccedGetPopularFeedGenerators.Handler>, + AppBskyUnspeccedGetPopularFeedGenerators.HandlerReqCtx> >, ) { const nsid = 'app.bsky.unspecced.getPopularFeedGenerators' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + + getTimelineSkeleton( + cfg: ConfigOf< + AV, + AppBskyUnspeccedGetTimelineSkeleton.Handler>, + AppBskyUnspeccedGetTimelineSkeleton.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.unspecced.getTimelineSkeleton' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } } -type ConfigOf = +type SharedRateLimitOpts = { + name: string + calcKey?: (ctx: T) => string + calcPoints?: (ctx: T) => number +} +type RouteRateLimitOpts = { + durationMs: number + points: number + calcKey?: (ctx: T) => string + calcPoints?: (ctx: T) => number +} +type HandlerRateLimitOpts = SharedRateLimitOpts | RouteRateLimitOpts +type ConfigOf = | Handler | { auth?: Auth + rateLimit?: HandlerRateLimitOpts | HandlerRateLimitOpts[] handler: Handler } type ExtractAuth = Extract< diff --git a/packages/pds/src/lexicon/lexicons.ts b/packages/pds/src/lexicon/lexicons.ts index e7a5195328e..f3c93c5e805 100644 --- a/packages/pds/src/lexicon/lexicons.ts +++ b/packages/pds/src/lexicon/lexicons.ts @@ -28,6 +28,11 @@ export const schemaDict = { type: 'ref', ref: 'lex:com.atproto.admin.defs#actionType', }, + durationInHours: { + type: 'integer', + description: + 'Indicates how long this action was meant to be in effect before automatically expiring.', + }, subject: { type: 'union', refs: [ @@ -96,6 +101,11 @@ export const schemaDict = { type: 'ref', ref: 'lex:com.atproto.admin.defs#actionType', }, + durationInHours: { + type: 'integer', + description: + 'Indicates how long this action was meant to be in effect before automatically expiring.', + }, subject: { type: 'union', refs: [ @@ -159,6 +169,11 @@ export const schemaDict = { type: 'ref', ref: 'lex:com.atproto.admin.defs#actionType', }, + durationInHours: { + type: 'integer', + description: + 'Indicates how long this action was meant to be in effect before automatically expiring.', + }, }, }, actionReversal: { @@ -343,6 +358,9 @@ export const schemaDict = { invitesDisabled: { type: 'boolean', }, + inviteNote: { + type: 'string', + }, }, }, repoViewDetail: { @@ -401,6 +419,9 @@ export const schemaDict = { invitesDisabled: { type: 'boolean', }, + inviteNote: { + type: 'string', + }, }, }, repoViewNotFound: { @@ -530,7 +551,6 @@ export const schemaDict = { }, moderation: { type: 'object', - required: [], properties: { currentAction: { type: 'ref', @@ -640,6 +660,11 @@ export const schemaDict = { type: 'string', format: 'did', }, + note: { + type: 'string', + description: + 'Additionally add a note describing why the invites were disabled', + }, }, }, }, @@ -694,6 +719,11 @@ export const schemaDict = { type: 'string', format: 'did', }, + note: { + type: 'string', + description: + 'Additionally add a note describing why the invites were enabled', + }, }, }, }, @@ -865,6 +895,12 @@ export const schemaDict = { type: 'string', }, }, + actionedBy: { + type: 'string', + format: 'did', + description: + 'Get all reports that were actioned by a specific moderator', + }, reporters: { type: 'array', items: { @@ -990,44 +1026,6 @@ export const schemaDict = { }, }, }, - ComAtprotoAdminRebaseRepo: { - lexicon: 1, - id: 'com.atproto.admin.rebaseRepo', - defs: { - main: { - type: 'procedure', - description: "Administrative action to rebase an account's repo", - input: { - encoding: 'application/json', - schema: { - type: 'object', - required: ['repo'], - properties: { - repo: { - type: 'string', - format: 'at-identifier', - description: 'The handle or DID of the repo.', - }, - swapCommit: { - type: 'string', - format: 'cid', - description: - 'Compare and swap with the previous commit by cid.', - }, - }, - }, - }, - errors: [ - { - name: 'InvalidSwap', - }, - { - name: 'ConcurrentWrites', - }, - ], - }, - }, - }, ComAtprotoAdminResolveModerationReports: { lexicon: 1, id: 'com.atproto.admin.resolveModerationReports', @@ -1152,6 +1150,47 @@ export const schemaDict = { }, }, }, + ComAtprotoAdminSendEmail: { + lexicon: 1, + id: 'com.atproto.admin.sendEmail', + defs: { + main: { + type: 'procedure', + description: "Send email to a user's primary email address", + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['recipientDid', 'content'], + properties: { + recipientDid: { + type: 'string', + format: 'did', + }, + content: { + type: 'string', + }, + subject: { + type: 'string', + }, + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['sent'], + properties: { + sent: { + type: 'boolean', + }, + }, + }, + }, + }, + }, + }, ComAtprotoAdminTakeModerationAction: { lexicon: 1, id: 'com.atproto.admin.takeModerationAction', @@ -1202,6 +1241,11 @@ export const schemaDict = { reason: { type: 'string', }, + durationInHours: { + type: 'integer', + description: + 'Indicates how long this action was meant to be in effect before automatically expiring.', + }, createdBy: { type: 'string', format: 'did', @@ -1379,6 +1423,36 @@ export const schemaDict = { }, }, }, + selfLabels: { + type: 'object', + description: + 'Metadata tags on an atproto record, published by the author within the record.', + required: ['values'], + properties: { + values: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:com.atproto.label.defs#selfLabel', + }, + maxLength: 10, + }, + }, + }, + selfLabel: { + type: 'object', + description: + 'Metadata tag on an atproto record, published by the author within the record. Note -- schemas should use #selfLabels, not #selfLabel.', + required: ['val'], + properties: { + val: { + type: 'string', + maxLength: 128, + description: + 'the short string name of the value or type of this label', + }, + }, + }, }, }, ComAtprotoLabelQueryLabels: { @@ -1605,7 +1679,7 @@ export const schemaDict = { }, reasonSexual: { type: 'token', - description: 'Unwanted or mis-labeled sexual content', + description: 'Unwanted or mislabeled sexual content', }, reasonRude: { type: 'token', @@ -2117,44 +2191,6 @@ export const schemaDict = { }, }, }, - ComAtprotoRepoRebaseRepo: { - lexicon: 1, - id: 'com.atproto.repo.rebaseRepo', - defs: { - main: { - type: 'procedure', - description: 'Simple rebase of repo that deletes history', - input: { - encoding: 'application/json', - schema: { - type: 'object', - required: ['repo'], - properties: { - repo: { - type: 'string', - format: 'at-identifier', - description: 'The handle or DID of the repo.', - }, - swapCommit: { - type: 'string', - format: 'cid', - description: - 'Compare and swap with the previous commit by cid.', - }, - }, - }, - }, - errors: [ - { - name: 'InvalidSwap', - }, - { - name: 'ConcurrentWrites', - }, - ], - }, - }, - }, ComAtprotoRepoStrongRef: { lexicon: 1, id: 'com.atproto.repo.strongRef', @@ -2957,7 +2993,7 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Gets the repo state.', + description: 'DEPRECATED - please use com.atproto.sync.getRepo instead', parameters: { type: 'params', required: ['did'], @@ -2967,12 +3003,6 @@ export const schemaDict = { format: 'did', description: 'The DID of the repo.', }, - commit: { - type: 'string', - format: 'cid', - description: - 'The commit to get the checkout from. Defaults to current HEAD.', - }, }, }, output: { @@ -2981,13 +3011,14 @@ export const schemaDict = { }, }, }, - ComAtprotoSyncGetCommitPath: { + ComAtprotoSyncGetHead: { lexicon: 1, - id: 'com.atproto.sync.getCommitPath', + id: 'com.atproto.sync.getHead', defs: { main: { type: 'query', - description: 'Gets the path of repo commits', + description: + 'DEPRECATED - please use com.atproto.sync.getLatestCommit instead', parameters: { type: 'params', required: ['did'], @@ -2997,44 +3028,36 @@ export const schemaDict = { format: 'did', description: 'The DID of the repo.', }, - latest: { - type: 'string', - format: 'cid', - description: 'The most recent commit', - }, - earliest: { - type: 'string', - format: 'cid', - description: 'The earliest commit to start from', - }, }, }, output: { encoding: 'application/json', schema: { type: 'object', - required: ['commits'], + required: ['root'], properties: { - commits: { - type: 'array', - items: { - type: 'string', - format: 'cid', - }, + root: { + type: 'string', + format: 'cid', }, }, }, }, + errors: [ + { + name: 'HeadNotFound', + }, + ], }, }, }, - ComAtprotoSyncGetHead: { + ComAtprotoSyncGetLatestCommit: { lexicon: 1, - id: 'com.atproto.sync.getHead', + id: 'com.atproto.sync.getLatestCommit', defs: { main: { type: 'query', - description: 'Gets the current HEAD CID of a repo.', + description: 'Gets the current commit CID & revision of the repo.', parameters: { type: 'params', required: ['did'], @@ -3050,18 +3073,21 @@ export const schemaDict = { encoding: 'application/json', schema: { type: 'object', - required: ['root'], + required: ['cid', 'rev'], properties: { - root: { + cid: { type: 'string', format: 'cid', }, + rev: { + type: 'string', + }, }, }, }, errors: [ { - name: 'HeadNotFound', + name: 'RepoNotFound', }, ], }, @@ -3110,7 +3136,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Gets the repo state.', + description: + "Gets the did's repo, optionally catching up from a specific revision.", parameters: { type: 'params', required: ['did'], @@ -3120,16 +3147,9 @@ export const schemaDict = { format: 'did', description: 'The DID of the repo.', }, - earliest: { + since: { type: 'string', - format: 'cid', - description: - 'The earliest commit in the commit range (not inclusive)', - }, - latest: { - type: 'string', - format: 'cid', - description: 'The latest commit in the commit range (inclusive)', + description: 'The revision of the repo to catch up from.', }, }, }, @@ -3145,7 +3165,7 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'List blob cids for some range of commits', + description: 'List blob cids since some revision', parameters: { type: 'params', required: ['did'], @@ -3155,15 +3175,18 @@ export const schemaDict = { format: 'did', description: 'The DID of the repo.', }, - latest: { + since: { type: 'string', - format: 'cid', - description: 'The most recent commit', + description: 'Optional revision of the repo to list blobs since', + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 1000, + default: 500, }, - earliest: { + cursor: { type: 'string', - format: 'cid', - description: 'The earliest commit to start from', }, }, }, @@ -3173,6 +3196,9 @@ export const schemaDict = { type: 'object', required: ['cids'], properties: { + cursor: { + type: 'string', + }, cids: { type: 'array', items: { @@ -3337,13 +3363,14 @@ export const schemaDict = { 'tooBig', 'repo', 'commit', - 'prev', + 'rev', + 'since', 'blocks', 'ops', 'blobs', 'time', ], - nullable: ['prev'], + nullable: ['prev', 'since'], properties: { seq: { type: 'integer', @@ -3364,6 +3391,14 @@ export const schemaDict = { prev: { type: 'cid-link', }, + rev: { + type: 'string', + description: 'The rev of the emitted commit', + }, + since: { + type: 'string', + description: 'The rev of the last emitted commit from this repo', + }, blocks: { type: 'bytes', description: 'CAR file containing relevant blocks', @@ -3463,6 +3498,8 @@ export const schemaDict = { }, repoOp: { type: 'object', + description: + "A repo operation, ie a write of a single record. For creates and updates, cid is the record's CID as of this operation. For deletes, it's null.", required: ['action', 'path', 'cid'], nullable: ['cid'], properties: { @@ -3649,6 +3686,7 @@ export const schemaDict = { 'lex:app.bsky.actor.defs#adultContentPref', 'lex:app.bsky.actor.defs#contentLabelPref', 'lex:app.bsky.actor.defs#savedFeedsPref', + 'lex:app.bsky.actor.defs#personalDetailsPref', ], }, }, @@ -3695,6 +3733,16 @@ export const schemaDict = { }, }, }, + personalDetailsPref: { + type: 'object', + properties: { + birthDate: { + type: 'string', + format: 'datetime', + description: 'The birth date of the owner of the account.', + }, + }, + }, }, }, AppBskyActorGetPreferences: { @@ -3863,6 +3911,10 @@ export const schemaDict = { accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, + labels: { + type: 'union', + refs: ['lex:com.atproto.label.defs#selfLabels'], + }, }, }, }, @@ -4076,6 +4128,26 @@ export const schemaDict = { alt: { type: 'string', }, + aspectRatio: { + type: 'ref', + ref: 'lex:app.bsky.embed.images#aspectRatio', + }, + }, + }, + aspectRatio: { + type: 'object', + description: + 'width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit.', + required: ['width', 'height'], + properties: { + width: { + type: 'integer', + minimum: 1, + }, + height: { + type: 'integer', + minimum: 1, + }, }, }, view: { @@ -4105,6 +4177,10 @@ export const schemaDict = { alt: { type: 'string', }, + aspectRatio: { + type: 'ref', + ref: 'lex:app.bsky.embed.images#aspectRatio', + }, }, }, }, @@ -4187,22 +4263,34 @@ export const schemaDict = { }, viewNotFound: { type: 'object', - required: ['uri'], + required: ['uri', 'notFound'], properties: { uri: { type: 'string', format: 'at-uri', }, + notFound: { + type: 'boolean', + const: true, + }, }, }, viewBlocked: { type: 'object', - required: ['uri'], + required: ['uri', 'blocked', 'author'], properties: { uri: { type: 'string', format: 'at-uri', }, + blocked: { + type: 'boolean', + const: true, + }, + author: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#blockedAuthor', + }, }, }, }, @@ -4416,7 +4504,7 @@ export const schemaDict = { }, blockedPost: { type: 'object', - required: ['uri', 'blocked'], + required: ['uri', 'blocked', 'author'], properties: { uri: { type: 'string', @@ -4426,17 +4514,35 @@ export const schemaDict = { type: 'boolean', const: true, }, + author: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#blockedAuthor', + }, }, }, - generatorView: { + blockedAuthor: { type: 'object', - required: ['uri', 'cid', 'did', 'creator', 'displayName', 'indexedAt'], + required: ['did'], properties: { - uri: { + did: { type: 'string', - format: 'at-uri', + format: 'did', }, - cid: { + viewer: { + type: 'ref', + ref: 'lex:app.bsky.actor.defs#viewerState', + }, + }, + }, + generatorView: { + type: 'object', + required: ['uri', 'cid', 'did', 'creator', 'displayName', 'indexedAt'], + properties: { + uri: { + type: 'string', + format: 'at-uri', + }, + cid: { type: 'string', format: 'cid', }, @@ -4609,6 +4715,10 @@ export const schemaDict = { accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, + labels: { + type: 'union', + refs: ['lex:com.atproto.label.defs#selfLabels'], + }, createdAt: { type: 'string', format: 'datetime', @@ -4666,6 +4776,62 @@ export const schemaDict = { }, }, }, + AppBskyFeedGetActorLikes: { + lexicon: 1, + id: 'app.bsky.feed.getActorLikes', + defs: { + main: { + type: 'query', + description: 'A view of the posts liked by an actor.', + parameters: { + type: 'params', + required: ['actor'], + properties: { + actor: { + type: 'string', + format: 'at-identifier', + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['feed'], + properties: { + cursor: { + type: 'string', + }, + feed: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#feedViewPost', + }, + }, + }, + }, + }, + errors: [ + { + name: 'BlockedActor', + }, + { + name: 'BlockedByActor', + }, + ], + }, + }, + }, AppBskyFeedGetAuthorFeed: { lexicon: 1, id: 'app.bsky.feed.getAuthorFeed', @@ -4690,6 +4856,15 @@ export const schemaDict = { cursor: { type: 'string', }, + filter: { + type: 'string', + knownValues: [ + 'posts_with_replies', + 'posts_no_replies', + 'posts_with_media', + ], + default: 'posts_with_replies', + }, }, }, output: { @@ -5137,6 +5312,49 @@ export const schemaDict = { }, }, }, + AppBskyFeedGetSuggestedFeeds: { + lexicon: 1, + id: 'app.bsky.feed.getSuggestedFeeds', + defs: { + main: { + type: 'query', + description: 'Get a list of suggested feeds for the viewer.', + parameters: { + type: 'params', + properties: { + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['feeds'], + properties: { + cursor: { + type: 'string', + }, + feeds: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#generatorView', + }, + }, + }, + }, + }, + }, + }, + }, AppBskyFeedGetTimeline: { lexicon: 1, id: 'app.bsky.feed.getTimeline', @@ -5259,6 +5477,10 @@ export const schemaDict = { format: 'language', }, }, + labels: { + type: 'union', + refs: ['lex:com.atproto.label.defs#selfLabels'], + }, createdAt: { type: 'string', format: 'datetime', @@ -5478,6 +5700,10 @@ export const schemaDict = { muted: { type: 'boolean', }, + blocked: { + type: 'string', + format: 'at-uri', + }, }, }, }, @@ -5706,6 +5932,49 @@ export const schemaDict = { }, }, }, + AppBskyGraphGetListBlocks: { + lexicon: 1, + id: 'app.bsky.graph.getListBlocks', + defs: { + main: { + type: 'query', + description: "Which lists is the requester's account blocking?", + parameters: { + type: 'params', + properties: { + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['lists'], + properties: { + cursor: { + type: 'string', + }, + lists: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.graph.defs#listView', + }, + }, + }, + }, + }, + }, + }, + }, AppBskyGraphGetListMutes: { lexicon: 1, id: 'app.bsky.graph.getListMutes', @@ -5840,6 +6109,42 @@ export const schemaDict = { }, }, }, + AppBskyGraphGetSuggestedFollowsByActor: { + lexicon: 1, + id: 'app.bsky.graph.getSuggestedFollowsByActor', + defs: { + main: { + type: 'query', + description: 'Get suggested follows related to a given actor.', + parameters: { + type: 'params', + required: ['actor'], + properties: { + actor: { + type: 'string', + format: 'at-identifier', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['suggestions'], + properties: { + suggestions: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.actor.defs#profileView', + }, + }, + }, + }, + }, + }, + }, + }, AppBskyGraphList: { lexicon: 1, id: 'app.bsky.graph.list', @@ -5878,6 +6183,35 @@ export const schemaDict = { accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, + labels: { + type: 'union', + refs: ['lex:com.atproto.label.defs#selfLabels'], + }, + createdAt: { + type: 'string', + format: 'datetime', + }, + }, + }, + }, + }, + }, + AppBskyGraphListblock: { + lexicon: 1, + id: 'app.bsky.graph.listblock', + defs: { + main: { + type: 'record', + description: 'A block of an entire list of actors.', + key: 'tid', + record: { + type: 'object', + required: ['subject', 'createdAt'], + properties: { + subject: { + type: 'string', + format: 'at-uri', + }, createdAt: { type: 'string', format: 'datetime', @@ -6144,6 +6478,39 @@ export const schemaDict = { }, }, }, + AppBskyNotificationRegisterPush: { + lexicon: 1, + id: 'app.bsky.notification.registerPush', + defs: { + main: { + type: 'procedure', + description: 'Register for push notifications with a service', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['serviceDid', 'token', 'platform', 'appId'], + properties: { + serviceDid: { + type: 'string', + format: 'did', + }, + token: { + type: 'string', + }, + platform: { + type: 'string', + knownValues: ['ios', 'android', 'web'], + }, + appId: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, AppBskyNotificationUpdateSeen: { lexicon: 1, id: 'app.bsky.notification.updateSeen', @@ -6231,6 +6598,32 @@ export const schemaDict = { }, }, }, + AppBskyUnspeccedApplyLabels: { + lexicon: 1, + id: 'app.bsky.unspecced.applyLabels', + defs: { + main: { + type: 'procedure', + description: 'Allow a labeler to apply labels directly.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['labels'], + properties: { + labels: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:com.atproto.label.defs#label', + }, + }, + }, + }, + }, + }, + }, + }, AppBskyUnspeccedGetPopular: { lexicon: 1, id: 'app.bsky.unspecced.getPopular', @@ -6285,12 +6678,32 @@ export const schemaDict = { main: { type: 'query', description: 'An unspecced view of globally popular feed generators', + parameters: { + type: 'params', + properties: { + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + query: { + type: 'string', + }, + }, + }, output: { encoding: 'application/json', schema: { type: 'object', required: ['feeds'], properties: { + cursor: { + type: 'string', + }, feeds: { type: 'array', items: { @@ -6304,6 +6717,54 @@ export const schemaDict = { }, }, }, + AppBskyUnspeccedGetTimelineSkeleton: { + lexicon: 1, + id: 'app.bsky.unspecced.getTimelineSkeleton', + defs: { + main: { + type: 'query', + description: 'A skeleton of a timeline - UNSPECCED & WILL GO AWAY SOON', + parameters: { + type: 'params', + properties: { + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['feed'], + properties: { + cursor: { + type: 'string', + }, + feed: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#skeletonFeedPost', + }, + }, + }, + }, + }, + errors: [ + { + name: 'UnknownFeed', + }, + ], + }, + }, + }, } export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[] export const lexicons: Lexicons = new Lexicons(schemas) @@ -6320,12 +6781,12 @@ export const ids = { ComAtprotoAdminGetModerationReports: 'com.atproto.admin.getModerationReports', ComAtprotoAdminGetRecord: 'com.atproto.admin.getRecord', ComAtprotoAdminGetRepo: 'com.atproto.admin.getRepo', - ComAtprotoAdminRebaseRepo: 'com.atproto.admin.rebaseRepo', ComAtprotoAdminResolveModerationReports: 'com.atproto.admin.resolveModerationReports', ComAtprotoAdminReverseModerationAction: 'com.atproto.admin.reverseModerationAction', ComAtprotoAdminSearchRepos: 'com.atproto.admin.searchRepos', + ComAtprotoAdminSendEmail: 'com.atproto.admin.sendEmail', ComAtprotoAdminTakeModerationAction: 'com.atproto.admin.takeModerationAction', ComAtprotoAdminUpdateAccountEmail: 'com.atproto.admin.updateAccountEmail', ComAtprotoAdminUpdateAccountHandle: 'com.atproto.admin.updateAccountHandle', @@ -6343,7 +6804,6 @@ export const ids = { ComAtprotoRepoGetRecord: 'com.atproto.repo.getRecord', ComAtprotoRepoListRecords: 'com.atproto.repo.listRecords', ComAtprotoRepoPutRecord: 'com.atproto.repo.putRecord', - ComAtprotoRepoRebaseRepo: 'com.atproto.repo.rebaseRepo', ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef', ComAtprotoRepoUploadBlob: 'com.atproto.repo.uploadBlob', ComAtprotoServerCreateAccount: 'com.atproto.server.createAccount', @@ -6369,8 +6829,8 @@ export const ids = { ComAtprotoSyncGetBlob: 'com.atproto.sync.getBlob', ComAtprotoSyncGetBlocks: 'com.atproto.sync.getBlocks', ComAtprotoSyncGetCheckout: 'com.atproto.sync.getCheckout', - ComAtprotoSyncGetCommitPath: 'com.atproto.sync.getCommitPath', ComAtprotoSyncGetHead: 'com.atproto.sync.getHead', + ComAtprotoSyncGetLatestCommit: 'com.atproto.sync.getLatestCommit', ComAtprotoSyncGetRecord: 'com.atproto.sync.getRecord', ComAtprotoSyncGetRepo: 'com.atproto.sync.getRepo', ComAtprotoSyncListBlobs: 'com.atproto.sync.listBlobs', @@ -6395,6 +6855,7 @@ export const ids = { AppBskyFeedDescribeFeedGenerator: 'app.bsky.feed.describeFeedGenerator', AppBskyFeedGenerator: 'app.bsky.feed.generator', AppBskyFeedGetActorFeeds: 'app.bsky.feed.getActorFeeds', + AppBskyFeedGetActorLikes: 'app.bsky.feed.getActorLikes', AppBskyFeedGetAuthorFeed: 'app.bsky.feed.getAuthorFeed', AppBskyFeedGetFeed: 'app.bsky.feed.getFeed', AppBskyFeedGetFeedGenerator: 'app.bsky.feed.getFeedGenerator', @@ -6404,6 +6865,7 @@ export const ids = { AppBskyFeedGetPostThread: 'app.bsky.feed.getPostThread', AppBskyFeedGetPosts: 'app.bsky.feed.getPosts', AppBskyFeedGetRepostedBy: 'app.bsky.feed.getRepostedBy', + AppBskyFeedGetSuggestedFeeds: 'app.bsky.feed.getSuggestedFeeds', AppBskyFeedGetTimeline: 'app.bsky.feed.getTimeline', AppBskyFeedLike: 'app.bsky.feed.like', AppBskyFeedPost: 'app.bsky.feed.post', @@ -6415,10 +6877,14 @@ export const ids = { AppBskyGraphGetFollowers: 'app.bsky.graph.getFollowers', AppBskyGraphGetFollows: 'app.bsky.graph.getFollows', AppBskyGraphGetList: 'app.bsky.graph.getList', + AppBskyGraphGetListBlocks: 'app.bsky.graph.getListBlocks', AppBskyGraphGetListMutes: 'app.bsky.graph.getListMutes', AppBskyGraphGetLists: 'app.bsky.graph.getLists', AppBskyGraphGetMutes: 'app.bsky.graph.getMutes', + AppBskyGraphGetSuggestedFollowsByActor: + 'app.bsky.graph.getSuggestedFollowsByActor', AppBskyGraphList: 'app.bsky.graph.list', + AppBskyGraphListblock: 'app.bsky.graph.listblock', AppBskyGraphListitem: 'app.bsky.graph.listitem', AppBskyGraphMuteActor: 'app.bsky.graph.muteActor', AppBskyGraphMuteActorList: 'app.bsky.graph.muteActorList', @@ -6427,9 +6893,12 @@ export const ids = { AppBskyNotificationGetUnreadCount: 'app.bsky.notification.getUnreadCount', AppBskyNotificationListNotifications: 'app.bsky.notification.listNotifications', + AppBskyNotificationRegisterPush: 'app.bsky.notification.registerPush', AppBskyNotificationUpdateSeen: 'app.bsky.notification.updateSeen', AppBskyRichtextFacet: 'app.bsky.richtext.facet', + AppBskyUnspeccedApplyLabels: 'app.bsky.unspecced.applyLabels', AppBskyUnspeccedGetPopular: 'app.bsky.unspecced.getPopular', AppBskyUnspeccedGetPopularFeedGenerators: 'app.bsky.unspecced.getPopularFeedGenerators', + AppBskyUnspeccedGetTimelineSkeleton: 'app.bsky.unspecced.getTimelineSkeleton', } diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts b/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts index 3b338c06f3a..4446c1f7a03 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts @@ -108,6 +108,7 @@ export type Preferences = ( | AdultContentPref | ContentLabelPref | SavedFeedsPref + | PersonalDetailsPref | { $type: string; [k: string]: unknown } )[] @@ -163,3 +164,21 @@ export function isSavedFeedsPref(v: unknown): v is SavedFeedsPref { export function validateSavedFeedsPref(v: unknown): ValidationResult { return lexicons.validate('app.bsky.actor.defs#savedFeedsPref', v) } + +export interface PersonalDetailsPref { + /** The birth date of the owner of the account. */ + birthDate?: string + [k: string]: unknown +} + +export function isPersonalDetailsPref(v: unknown): v is PersonalDetailsPref { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.actor.defs#personalDetailsPref' + ) +} + +export function validatePersonalDetailsPref(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.actor.defs#personalDetailsPref', v) +} diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/getPreferences.ts b/packages/pds/src/lexicon/types/app/bsky/actor/getPreferences.ts index 9ba813a4313..88d78a57cba 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/getPreferences.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/getPreferences.ts @@ -32,10 +32,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/getProfile.ts b/packages/pds/src/lexicon/types/app/bsky/actor/getProfile.ts index 4a2ef252e7e..802afda5361 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/getProfile.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/getProfile.ts @@ -29,10 +29,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/getProfiles.ts b/packages/pds/src/lexicon/types/app/bsky/actor/getProfiles.ts index 89c014ebe0a..2549b264e33 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/getProfiles.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/getProfiles.ts @@ -34,10 +34,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/getSuggestions.ts b/packages/pds/src/lexicon/types/app/bsky/actor/getSuggestions.ts index 5e59cf571d5..a6d4d6102af 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/getSuggestions.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/getSuggestions.ts @@ -36,10 +36,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/profile.ts b/packages/pds/src/lexicon/types/app/bsky/actor/profile.ts index 2d4bf526601..7dbc4c1ccec 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/profile.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/profile.ts @@ -5,12 +5,16 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' +import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' export interface Record { displayName?: string description?: string avatar?: BlobRef banner?: BlobRef + labels?: + | ComAtprotoLabelDefs.SelfLabels + | { $type: string; [k: string]: unknown } [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/putPreferences.ts b/packages/pds/src/lexicon/types/app/bsky/actor/putPreferences.ts index ba0531cc3c0..1e5ee2d834e 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/putPreferences.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/putPreferences.ts @@ -27,10 +27,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/searchActors.ts b/packages/pds/src/lexicon/types/app/bsky/actor/searchActors.ts index 0b425ba4df7..f620a463cff 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/searchActors.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/searchActors.ts @@ -37,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts b/packages/pds/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts index 43a6be287b4..4f5bbb7c23c 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts @@ -35,10 +35,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/embed/images.ts b/packages/pds/src/lexicon/types/app/bsky/embed/images.ts index 422b036fa70..4864fad3dea 100644 --- a/packages/pds/src/lexicon/types/app/bsky/embed/images.ts +++ b/packages/pds/src/lexicon/types/app/bsky/embed/images.ts @@ -27,6 +27,7 @@ export function validateMain(v: unknown): ValidationResult { export interface Image { image: BlobRef alt: string + aspectRatio?: AspectRatio [k: string]: unknown } @@ -40,6 +41,25 @@ export function validateImage(v: unknown): ValidationResult { return lexicons.validate('app.bsky.embed.images#image', v) } +/** width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit. */ +export interface AspectRatio { + width: number + height: number + [k: string]: unknown +} + +export function isAspectRatio(v: unknown): v is AspectRatio { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.embed.images#aspectRatio' + ) +} + +export function validateAspectRatio(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.embed.images#aspectRatio', v) +} + export interface View { images: ViewImage[] [k: string]: unknown @@ -59,6 +79,7 @@ export interface ViewImage { thumb: string fullsize: string alt: string + aspectRatio?: AspectRatio [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/app/bsky/embed/record.ts b/packages/pds/src/lexicon/types/app/bsky/embed/record.ts index cdf38d6d7ad..cea5742a45e 100644 --- a/packages/pds/src/lexicon/types/app/bsky/embed/record.ts +++ b/packages/pds/src/lexicon/types/app/bsky/embed/record.ts @@ -84,6 +84,7 @@ export function validateViewRecord(v: unknown): ValidationResult { export interface ViewNotFound { uri: string + notFound: true [k: string]: unknown } @@ -101,6 +102,8 @@ export function validateViewNotFound(v: unknown): ValidationResult { export interface ViewBlocked { uri: string + blocked: true + author: AppBskyFeedDefs.BlockedAuthor [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/defs.ts b/packages/pds/src/lexicon/types/app/bsky/feed/defs.ts index 80069e4e412..463445fbd49 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/defs.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/defs.ts @@ -171,6 +171,7 @@ export function validateNotFoundPost(v: unknown): ValidationResult { export interface BlockedPost { uri: string blocked: true + author: BlockedAuthor [k: string]: unknown } @@ -186,6 +187,24 @@ export function validateBlockedPost(v: unknown): ValidationResult { return lexicons.validate('app.bsky.feed.defs#blockedPost', v) } +export interface BlockedAuthor { + did: string + viewer?: AppBskyActorDefs.ViewerState + [k: string]: unknown +} + +export function isBlockedAuthor(v: unknown): v is BlockedAuthor { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.feed.defs#blockedAuthor' + ) +} + +export function validateBlockedAuthor(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.feed.defs#blockedAuthor', v) +} + export interface GeneratorView { uri: string cid: string diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts b/packages/pds/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts index 9444257c905..d329bf20a5a 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts @@ -33,13 +33,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput export interface Feed { uri: string diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/generator.ts b/packages/pds/src/lexicon/types/app/bsky/feed/generator.ts index 5ad34318ee3..757e74db845 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/generator.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/generator.ts @@ -6,6 +6,7 @@ import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' import * as AppBskyRichtextFacet from '../richtext/facet' +import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' export interface Record { did: string @@ -13,6 +14,9 @@ export interface Record { description?: string descriptionFacets?: AppBskyRichtextFacet.Main[] avatar?: BlobRef + labels?: + | ComAtprotoLabelDefs.SelfLabels + | { $type: string; [k: string]: unknown } createdAt: string [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getActorFeeds.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getActorFeeds.ts index 2a1e9edb889..3e930cbe201 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getActorFeeds.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getActorFeeds.ts @@ -37,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getActorLikes.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getActorLikes.ts new file mode 100644 index 00000000000..df2f291e1a7 --- /dev/null +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getActorLikes.ts @@ -0,0 +1,50 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' +import * as AppBskyFeedDefs from './defs' + +export interface QueryParams { + actor: string + limit: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + feed: AppBskyFeedDefs.FeedViewPost[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string + error?: 'BlockedActor' | 'BlockedByActor' +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts index ce56c2667fd..cd66ef5c392 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts @@ -13,6 +13,11 @@ export interface QueryParams { actor: string limit: number cursor?: string + filter: + | 'posts_with_replies' + | 'posts_no_replies' + | 'posts_with_media' + | (string & {}) } export type InputSchema = undefined @@ -38,10 +43,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getFeed.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getFeed.ts index 837a6d9a892..e72b1010aea 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getFeed.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getFeed.ts @@ -38,10 +38,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts index 859f0c70b84..fab3b30c316 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts @@ -36,10 +36,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts index 85cbb544721..d7e082f2362 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts @@ -34,10 +34,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts index fe132e926f6..1c8f349b42b 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts @@ -38,10 +38,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getLikes.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getLikes.ts index deb95fb063a..d581f5bfa9c 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getLikes.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getLikes.ts @@ -40,13 +40,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput export interface Like { indexedAt: string diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getPostThread.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getPostThread.ts index e9f154a86a6..61de94b729d 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getPostThread.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getPostThread.ts @@ -41,10 +41,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getPosts.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getPosts.ts index 3cf5747ec1f..4282f5d349f 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getPosts.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getPosts.ts @@ -34,10 +34,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getRepostedBy.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getRepostedBy.ts index 7f7d51a77af..0b9c1a6f68b 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getRepostedBy.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getRepostedBy.ts @@ -40,10 +40,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getSuggestedFeeds.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getSuggestedFeeds.ts new file mode 100644 index 00000000000..9b271335466 --- /dev/null +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getSuggestedFeeds.ts @@ -0,0 +1,48 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' +import * as AppBskyFeedDefs from './defs' + +export interface QueryParams { + limit: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + feeds: AppBskyFeedDefs.GeneratorView[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getTimeline.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getTimeline.ts index 6be4293374c..832caf5c6f7 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getTimeline.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getTimeline.ts @@ -37,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/post.ts b/packages/pds/src/lexicon/types/app/bsky/feed/post.ts index b111c4783e6..8942bc724cd 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/post.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/post.ts @@ -10,6 +10,7 @@ import * as AppBskyEmbedImages from '../embed/images' import * as AppBskyEmbedExternal from '../embed/external' import * as AppBskyEmbedRecord from '../embed/record' import * as AppBskyEmbedRecordWithMedia from '../embed/recordWithMedia' +import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' export interface Record { @@ -25,6 +26,9 @@ export interface Record { | AppBskyEmbedRecordWithMedia.Main | { $type: string; [k: string]: unknown } langs?: string[] + labels?: + | ComAtprotoLabelDefs.SelfLabels + | { $type: string; [k: string]: unknown } createdAt: string [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/defs.ts b/packages/pds/src/lexicon/types/app/bsky/graph/defs.ts index e50338d488d..63c05b5faa3 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/defs.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/defs.ts @@ -81,6 +81,7 @@ export const MODLIST = 'app.bsky.graph.defs#modlist' export interface ListViewerState { muted?: boolean + blocked?: string [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getBlocks.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getBlocks.ts index f3a6eb4b55c..d380a14880a 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getBlocks.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getBlocks.ts @@ -36,10 +36,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getFollowers.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getFollowers.ts index e6bec023938..b337be52c1b 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getFollowers.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getFollowers.ts @@ -38,10 +38,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getFollows.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getFollows.ts index 96c18201e1b..71e9ca0270c 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getFollows.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getFollows.ts @@ -38,10 +38,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getList.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getList.ts index d0cfe398adf..fc45dd20985 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getList.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getList.ts @@ -38,10 +38,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getListBlocks.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getListBlocks.ts new file mode 100644 index 00000000000..04cca70b44d --- /dev/null +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getListBlocks.ts @@ -0,0 +1,48 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' +import * as AppBskyGraphDefs from './defs' + +export interface QueryParams { + limit: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + lists: AppBskyGraphDefs.ListView[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getListMutes.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getListMutes.ts index b0820a7ea53..04cca70b44d 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getListMutes.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getListMutes.ts @@ -36,10 +36,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getLists.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getLists.ts index b6626cbe096..8acf9362c00 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getLists.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getLists.ts @@ -37,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getMutes.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getMutes.ts index 32956284d7d..0034095b975 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getMutes.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getMutes.ts @@ -36,10 +36,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts new file mode 100644 index 00000000000..a2245846fd2 --- /dev/null +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts @@ -0,0 +1,46 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' +import * as AppBskyActorDefs from '../actor/defs' + +export interface QueryParams { + actor: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + suggestions: AppBskyActorDefs.ProfileView[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/list.ts b/packages/pds/src/lexicon/types/app/bsky/graph/list.ts index 4304ca98b03..36a7fb17a3f 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/list.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/list.ts @@ -7,6 +7,7 @@ import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' import * as AppBskyGraphDefs from './defs' import * as AppBskyRichtextFacet from '../richtext/facet' +import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' export interface Record { purpose: AppBskyGraphDefs.ListPurpose @@ -14,6 +15,9 @@ export interface Record { description?: string descriptionFacets?: AppBskyRichtextFacet.Main[] avatar?: BlobRef + labels?: + | ComAtprotoLabelDefs.SelfLabels + | { $type: string; [k: string]: unknown } createdAt: string [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/listblock.ts b/packages/pds/src/lexicon/types/app/bsky/graph/listblock.ts new file mode 100644 index 00000000000..59f2e057eb5 --- /dev/null +++ b/packages/pds/src/lexicon/types/app/bsky/graph/listblock.ts @@ -0,0 +1,26 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' + +export interface Record { + subject: string + createdAt: string + [k: string]: unknown +} + +export function isRecord(v: unknown): v is Record { + return ( + isObj(v) && + hasProp(v, '$type') && + (v.$type === 'app.bsky.graph.listblock#main' || + v.$type === 'app.bsky.graph.listblock') + ) +} + +export function validateRecord(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.graph.listblock#main', v) +} diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/muteActor.ts b/packages/pds/src/lexicon/types/app/bsky/graph/muteActor.ts index fd19f661533..52d1b864989 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/muteActor.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/muteActor.ts @@ -26,10 +26,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/muteActorList.ts b/packages/pds/src/lexicon/types/app/bsky/graph/muteActorList.ts index e099011b3f7..bf803f388af 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/muteActorList.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/muteActorList.ts @@ -26,10 +26,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/unmuteActor.ts b/packages/pds/src/lexicon/types/app/bsky/graph/unmuteActor.ts index fd19f661533..52d1b864989 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/unmuteActor.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/unmuteActor.ts @@ -26,10 +26,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/unmuteActorList.ts b/packages/pds/src/lexicon/types/app/bsky/graph/unmuteActorList.ts index e099011b3f7..bf803f388af 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/unmuteActorList.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/unmuteActorList.ts @@ -26,10 +26,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/notification/getUnreadCount.ts b/packages/pds/src/lexicon/types/app/bsky/notification/getUnreadCount.ts index 365becb061b..6cf3c84beb5 100644 --- a/packages/pds/src/lexicon/types/app/bsky/notification/getUnreadCount.ts +++ b/packages/pds/src/lexicon/types/app/bsky/notification/getUnreadCount.ts @@ -33,10 +33,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/notification/listNotifications.ts b/packages/pds/src/lexicon/types/app/bsky/notification/listNotifications.ts index 02df7ade82e..156ba349ec4 100644 --- a/packages/pds/src/lexicon/types/app/bsky/notification/listNotifications.ts +++ b/packages/pds/src/lexicon/types/app/bsky/notification/listNotifications.ts @@ -38,13 +38,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput export interface Notification { uri: string diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/rebaseRepo.ts b/packages/pds/src/lexicon/types/app/bsky/notification/registerPush.ts similarity index 70% rename from packages/bsky/src/lexicon/types/com/atproto/repo/rebaseRepo.ts rename to packages/pds/src/lexicon/types/app/bsky/notification/registerPush.ts index d94a817f47c..9923aeb058e 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/rebaseRepo.ts +++ b/packages/pds/src/lexicon/types/app/bsky/notification/registerPush.ts @@ -11,10 +11,10 @@ import { HandlerAuth } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ - repo: string - /** Compare and swap with the previous commit by cid. */ - swapCommit?: string + serviceDid: string + token: string + platform: 'ios' | 'android' | 'web' | (string & {}) + appId: string [k: string]: unknown } @@ -26,14 +26,16 @@ export interface HandlerInput { export interface HandlerError { status: number message?: string - error?: 'InvalidSwap' | 'ConcurrentWrites' } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/notification/updateSeen.ts b/packages/pds/src/lexicon/types/app/bsky/notification/updateSeen.ts index 177f1f4bdbc..136191edc40 100644 --- a/packages/pds/src/lexicon/types/app/bsky/notification/updateSeen.ts +++ b/packages/pds/src/lexicon/types/app/bsky/notification/updateSeen.ts @@ -26,10 +26,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/rebaseRepo.ts b/packages/pds/src/lexicon/types/app/bsky/unspecced/applyLabels.ts similarity index 70% rename from packages/pds/src/lexicon/types/com/atproto/admin/rebaseRepo.ts rename to packages/pds/src/lexicon/types/app/bsky/unspecced/applyLabels.ts index d94a817f47c..1d359a9547d 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/rebaseRepo.ts +++ b/packages/pds/src/lexicon/types/app/bsky/unspecced/applyLabels.ts @@ -7,14 +7,12 @@ import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' import { HandlerAuth } from '@atproto/xrpc-server' +import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ - repo: string - /** Compare and swap with the previous commit by cid. */ - swapCommit?: string + labels: ComAtprotoLabelDefs.Label[] [k: string]: unknown } @@ -26,14 +24,16 @@ export interface HandlerInput { export interface HandlerError { status: number message?: string - error?: 'InvalidSwap' | 'ConcurrentWrites' } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/unspecced/getPopular.ts b/packages/pds/src/lexicon/types/app/bsky/unspecced/getPopular.ts index 44cfe695920..8471ed77a6c 100644 --- a/packages/pds/src/lexicon/types/app/bsky/unspecced/getPopular.ts +++ b/packages/pds/src/lexicon/types/app/bsky/unspecced/getPopular.ts @@ -37,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts b/packages/pds/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts index 981f7d92519..97937e926c2 100644 --- a/packages/pds/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts +++ b/packages/pds/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts @@ -9,11 +9,16 @@ import { CID } from 'multiformats/cid' import { HandlerAuth } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from '../feed/defs' -export interface QueryParams {} +export interface QueryParams { + limit: number + cursor?: string + query?: string +} export type InputSchema = undefined export interface OutputSchema { + cursor?: string feeds: AppBskyFeedDefs.GeneratorView[] [k: string]: unknown } @@ -32,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/unspecced/getTimelineSkeleton.ts b/packages/pds/src/lexicon/types/app/bsky/unspecced/getTimelineSkeleton.ts new file mode 100644 index 00000000000..4ccad20c902 --- /dev/null +++ b/packages/pds/src/lexicon/types/app/bsky/unspecced/getTimelineSkeleton.ts @@ -0,0 +1,49 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' +import * as AppBskyFeedDefs from '../feed/defs' + +export interface QueryParams { + limit: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + feed: AppBskyFeedDefs.SkeletonFeedPost[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string + error?: 'UnknownFeed' +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/defs.ts b/packages/pds/src/lexicon/types/com/atproto/admin/defs.ts index c73be1be6e4..968252a4c2c 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/defs.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/defs.ts @@ -13,6 +13,8 @@ import * as ComAtprotoLabelDefs from '../label/defs' export interface ActionView { id: number action: ActionType + /** Indicates how long this action was meant to be in effect before automatically expiring. */ + durationInHours?: number subject: | RepoRef | ComAtprotoRepoStrongRef.Main @@ -43,6 +45,8 @@ export function validateActionView(v: unknown): ValidationResult { export interface ActionViewDetail { id: number action: ActionType + /** Indicates how long this action was meant to be in effect before automatically expiring. */ + durationInHours?: number subject: | RepoView | RepoViewNotFound @@ -75,6 +79,8 @@ export function validateActionViewDetail(v: unknown): ValidationResult { export interface ActionViewCurrent { id: number action: ActionType + /** Indicates how long this action was meant to be in effect before automatically expiring. */ + durationInHours?: number [k: string]: unknown } @@ -189,6 +195,7 @@ export interface RepoView { moderation: Moderation invitedBy?: ComAtprotoServerDefs.InviteCode invitesDisabled?: boolean + inviteNote?: string [k: string]: unknown } @@ -215,6 +222,7 @@ export interface RepoViewDetail { invitedBy?: ComAtprotoServerDefs.InviteCode invites?: ComAtprotoServerDefs.InviteCode[] invitesDisabled?: boolean + inviteNote?: string [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts b/packages/pds/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts index fdba69e2e4b..051fabb65e1 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts @@ -12,6 +12,8 @@ export interface QueryParams {} export interface InputSchema { account: string + /** Additionally add a note describing why the invites were disabled */ + note?: string [k: string]: unknown } @@ -26,10 +28,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts b/packages/pds/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts index 2e9d326afe9..2b64371f1ed 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts @@ -27,10 +27,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts b/packages/pds/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts index fdba69e2e4b..4a26d302333 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts @@ -12,6 +12,8 @@ export interface QueryParams {} export interface InputSchema { account: string + /** Additionally add a note describing why the invites were enabled */ + note?: string [k: string]: unknown } @@ -26,10 +28,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/getInviteCodes.ts b/packages/pds/src/lexicon/types/com/atproto/admin/getInviteCodes.ts index e3f3fb6e739..1eb099aae66 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/getInviteCodes.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/getInviteCodes.ts @@ -37,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/getModerationAction.ts b/packages/pds/src/lexicon/types/com/atproto/admin/getModerationAction.ts index f774416a083..2ab52f237cc 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/getModerationAction.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/getModerationAction.ts @@ -29,10 +29,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/getModerationActions.ts b/packages/pds/src/lexicon/types/com/atproto/admin/getModerationActions.ts index b8a1e683ec5..4c29f965df6 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/getModerationActions.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/getModerationActions.ts @@ -37,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/getModerationReport.ts b/packages/pds/src/lexicon/types/com/atproto/admin/getModerationReport.ts index 63a1a551f97..28d714453f2 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/getModerationReport.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/getModerationReport.ts @@ -29,10 +29,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/getModerationReports.ts b/packages/pds/src/lexicon/types/com/atproto/admin/getModerationReports.ts index 93ec8bc879d..d50af44c757 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/getModerationReports.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/getModerationReports.ts @@ -12,6 +12,8 @@ import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { subject?: string ignoreSubjects?: string[] + /** Get all reports that were actioned by a specific moderator */ + actionedBy?: string /** Filter reports made by one or more DIDs */ reporters?: string[] resolved?: boolean @@ -49,10 +51,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/getRecord.ts b/packages/pds/src/lexicon/types/com/atproto/admin/getRecord.ts index 77895570d96..48222d9d819 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/getRecord.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/getRecord.ts @@ -31,10 +31,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/getRepo.ts b/packages/pds/src/lexicon/types/com/atproto/admin/getRepo.ts index 90aca8bdce5..19911baa90a 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/getRepo.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/getRepo.ts @@ -30,10 +30,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/resolveModerationReports.ts b/packages/pds/src/lexicon/types/com/atproto/admin/resolveModerationReports.ts index b8558d4e2b7..e3f4d028202 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/resolveModerationReports.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/resolveModerationReports.ts @@ -37,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/reverseModerationAction.ts b/packages/pds/src/lexicon/types/com/atproto/admin/reverseModerationAction.ts index 8f17d275761..17dcb5085de 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/reverseModerationAction.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/reverseModerationAction.ts @@ -37,10 +37,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/searchRepos.ts b/packages/pds/src/lexicon/types/com/atproto/admin/searchRepos.ts index 4dc0570276b..c79cd046ca0 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/searchRepos.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/searchRepos.ts @@ -38,10 +38,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/sendEmail.ts b/packages/pds/src/lexicon/types/com/atproto/admin/sendEmail.ts new file mode 100644 index 00000000000..87e7ceec172 --- /dev/null +++ b/packages/pds/src/lexicon/types/com/atproto/admin/sendEmail.ts @@ -0,0 +1,51 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export interface InputSchema { + recipientDid: string + content: string + subject?: string + [k: string]: unknown +} + +export interface OutputSchema { + sent: boolean + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/takeModerationAction.ts b/packages/pds/src/lexicon/types/com/atproto/admin/takeModerationAction.ts index 737fe446fb7..fbbf14dff0f 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/takeModerationAction.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/takeModerationAction.ts @@ -26,6 +26,8 @@ export interface InputSchema { createLabelVals?: string[] negateLabelVals?: string[] reason: string + /** Indicates how long this action was meant to be in effect before automatically expiring. */ + durationInHours?: number createdBy: string [k: string]: unknown } @@ -50,10 +52,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts b/packages/pds/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts index eb00af00727..9e6140256ef 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts @@ -28,10 +28,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts b/packages/pds/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts index b9bb76a9c06..c378f421926 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts @@ -27,10 +27,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/identity/resolveHandle.ts b/packages/pds/src/lexicon/types/com/atproto/identity/resolveHandle.ts index bdcd0ac45d9..ef90e99bb30 100644 --- a/packages/pds/src/lexicon/types/com/atproto/identity/resolveHandle.ts +++ b/packages/pds/src/lexicon/types/com/atproto/identity/resolveHandle.ts @@ -34,10 +34,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/identity/updateHandle.ts b/packages/pds/src/lexicon/types/com/atproto/identity/updateHandle.ts index c0c3cf523c5..1f639c344e9 100644 --- a/packages/pds/src/lexicon/types/com/atproto/identity/updateHandle.ts +++ b/packages/pds/src/lexicon/types/com/atproto/identity/updateHandle.ts @@ -26,10 +26,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/label/defs.ts b/packages/pds/src/lexicon/types/com/atproto/label/defs.ts index 17a8480b9d6..a01ad78e254 100644 --- a/packages/pds/src/lexicon/types/com/atproto/label/defs.ts +++ b/packages/pds/src/lexicon/types/com/atproto/label/defs.ts @@ -34,3 +34,40 @@ export function isLabel(v: unknown): v is Label { export function validateLabel(v: unknown): ValidationResult { return lexicons.validate('com.atproto.label.defs#label', v) } + +/** Metadata tags on an atproto record, published by the author within the record. */ +export interface SelfLabels { + values: SelfLabel[] + [k: string]: unknown +} + +export function isSelfLabels(v: unknown): v is SelfLabels { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'com.atproto.label.defs#selfLabels' + ) +} + +export function validateSelfLabels(v: unknown): ValidationResult { + return lexicons.validate('com.atproto.label.defs#selfLabels', v) +} + +/** Metadata tag on an atproto record, published by the author within the record. Note -- schemas should use #selfLabels, not #selfLabel. */ +export interface SelfLabel { + /** the short string name of the value or type of this label */ + val: string + [k: string]: unknown +} + +export function isSelfLabel(v: unknown): v is SelfLabel { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'com.atproto.label.defs#selfLabel' + ) +} + +export function validateSelfLabel(v: unknown): ValidationResult { + return lexicons.validate('com.atproto.label.defs#selfLabel', v) +} diff --git a/packages/pds/src/lexicon/types/com/atproto/label/queryLabels.ts b/packages/pds/src/lexicon/types/com/atproto/label/queryLabels.ts index 791e474c50b..72cf5c52be6 100644 --- a/packages/pds/src/lexicon/types/com/atproto/label/queryLabels.ts +++ b/packages/pds/src/lexicon/types/com/atproto/label/queryLabels.ts @@ -40,10 +40,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/label/subscribeLabels.ts b/packages/pds/src/lexicon/types/com/atproto/label/subscribeLabels.ts index 758407e0437..9d4b4441ae0 100644 --- a/packages/pds/src/lexicon/types/com/atproto/label/subscribeLabels.ts +++ b/packages/pds/src/lexicon/types/com/atproto/label/subscribeLabels.ts @@ -20,12 +20,15 @@ export type OutputSchema = | { $type: string; [k: string]: unknown } export type HandlerError = ErrorFrame<'FutureCursor'> export type HandlerOutput = HandlerError | OutputSchema -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams req: IncomingMessage signal: AbortSignal -}) => AsyncIterable +} +export type Handler = ( + ctx: HandlerReqCtx, +) => AsyncIterable export interface Labels { seq: number diff --git a/packages/pds/src/lexicon/types/com/atproto/moderation/createReport.ts b/packages/pds/src/lexicon/types/com/atproto/moderation/createReport.ts index a1be0464bf3..96aaf4a9c29 100644 --- a/packages/pds/src/lexicon/types/com/atproto/moderation/createReport.ts +++ b/packages/pds/src/lexicon/types/com/atproto/moderation/createReport.ts @@ -53,10 +53,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/moderation/defs.ts b/packages/pds/src/lexicon/types/com/atproto/moderation/defs.ts index 9ab6e60f9bd..81697226189 100644 --- a/packages/pds/src/lexicon/types/com/atproto/moderation/defs.ts +++ b/packages/pds/src/lexicon/types/com/atproto/moderation/defs.ts @@ -21,7 +21,7 @@ export const REASONSPAM = 'com.atproto.moderation.defs#reasonSpam' export const REASONVIOLATION = 'com.atproto.moderation.defs#reasonViolation' /** Misleading identity, affiliation, or content */ export const REASONMISLEADING = 'com.atproto.moderation.defs#reasonMisleading' -/** Unwanted or mis-labeled sexual content */ +/** Unwanted or mislabeled sexual content */ export const REASONSEXUAL = 'com.atproto.moderation.defs#reasonSexual' /** Rude, harassing, explicit, or otherwise unwelcoming behavior */ export const REASONRUDE = 'com.atproto.moderation.defs#reasonRude' diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/applyWrites.ts b/packages/pds/src/lexicon/types/com/atproto/repo/applyWrites.ts index 6fba024d715..53f2972e116 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/applyWrites.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/applyWrites.ts @@ -32,13 +32,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput /** Create a new record. */ export interface Create { diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/createRecord.ts b/packages/pds/src/lexicon/types/com/atproto/repo/createRecord.ts index f731dee536a..e069f8caf74 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/createRecord.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/createRecord.ts @@ -50,10 +50,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/deleteRecord.ts b/packages/pds/src/lexicon/types/com/atproto/repo/deleteRecord.ts index 016547e4075..5ee016cbed1 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/deleteRecord.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/deleteRecord.ts @@ -36,10 +36,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/describeRepo.ts b/packages/pds/src/lexicon/types/com/atproto/repo/describeRepo.ts index 6c1300daba4..7b8a2b995eb 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/describeRepo.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/describeRepo.ts @@ -38,10 +38,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/getRecord.ts b/packages/pds/src/lexicon/types/com/atproto/repo/getRecord.ts index ecabf517539..35c9b4b7166 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/getRecord.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/getRecord.ts @@ -42,10 +42,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/listRecords.ts b/packages/pds/src/lexicon/types/com/atproto/repo/listRecords.ts index 76c3429833c..e58d9714e33 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/listRecords.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/listRecords.ts @@ -46,13 +46,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput export interface Record { uri: string diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/putRecord.ts b/packages/pds/src/lexicon/types/com/atproto/repo/putRecord.ts index a56b6ca25f1..364eb59f6f1 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/putRecord.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/putRecord.ts @@ -52,10 +52,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/uploadBlob.ts b/packages/pds/src/lexicon/types/com/atproto/repo/uploadBlob.ts index b3f06f30f39..ad6002df925 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/uploadBlob.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/uploadBlob.ts @@ -35,10 +35,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts b/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts index 646091fde1e..c67e7445bf9 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts @@ -53,10 +53,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/server/createAppPassword.ts b/packages/pds/src/lexicon/types/com/atproto/server/createAppPassword.ts index 39eb73140c6..8e4a0a519e0 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/createAppPassword.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/createAppPassword.ts @@ -35,13 +35,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput export interface AppPassword { name: string diff --git a/packages/pds/src/lexicon/types/com/atproto/server/createInviteCode.ts b/packages/pds/src/lexicon/types/com/atproto/server/createInviteCode.ts index 10fef7ca0e4..acfac56ba76 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/createInviteCode.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/createInviteCode.ts @@ -38,10 +38,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/server/createInviteCodes.ts b/packages/pds/src/lexicon/types/com/atproto/server/createInviteCodes.ts index 98d6585ea06..5887d77fada 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/createInviteCodes.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/createInviteCodes.ts @@ -39,13 +39,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput export interface AccountCodes { account: string diff --git a/packages/pds/src/lexicon/types/com/atproto/server/createSession.ts b/packages/pds/src/lexicon/types/com/atproto/server/createSession.ts index 1b184da071f..b836551f301 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/createSession.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/createSession.ts @@ -44,10 +44,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/server/deleteAccount.ts b/packages/pds/src/lexicon/types/com/atproto/server/deleteAccount.ts index 6fde5f17e90..37ddbba13e0 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/deleteAccount.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/deleteAccount.ts @@ -29,10 +29,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/server/deleteSession.ts b/packages/pds/src/lexicon/types/com/atproto/server/deleteSession.ts index 909c2be8cab..e4244870425 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/deleteSession.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/deleteSession.ts @@ -19,10 +19,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/server/describeServer.ts b/packages/pds/src/lexicon/types/com/atproto/server/describeServer.ts index 9d65f932ec2..bc73d541a04 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/describeServer.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/describeServer.ts @@ -33,13 +33,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput export interface Links { privacyPolicy?: string diff --git a/packages/pds/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts b/packages/pds/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts index d0a9dc34307..e387a5e38e4 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts @@ -36,10 +36,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/server/getSession.ts b/packages/pds/src/lexicon/types/com/atproto/server/getSession.ts index c0f868ca0c1..388fb5eae9d 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/getSession.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/getSession.ts @@ -33,10 +33,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/server/listAppPasswords.ts b/packages/pds/src/lexicon/types/com/atproto/server/listAppPasswords.ts index ca0d4e80839..ebd74da9d39 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/listAppPasswords.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/listAppPasswords.ts @@ -32,13 +32,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput export interface AppPassword { name: string diff --git a/packages/pds/src/lexicon/types/com/atproto/server/refreshSession.ts b/packages/pds/src/lexicon/types/com/atproto/server/refreshSession.ts index 56e34eaa71f..e47bf09fbc2 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/refreshSession.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/refreshSession.ts @@ -35,10 +35,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/server/requestAccountDelete.ts b/packages/pds/src/lexicon/types/com/atproto/server/requestAccountDelete.ts index 909c2be8cab..e4244870425 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/requestAccountDelete.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/requestAccountDelete.ts @@ -19,10 +19,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/server/requestPasswordReset.ts b/packages/pds/src/lexicon/types/com/atproto/server/requestPasswordReset.ts index d1973cd4825..47fb4bb62f3 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/requestPasswordReset.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/requestPasswordReset.ts @@ -26,10 +26,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/server/resetPassword.ts b/packages/pds/src/lexicon/types/com/atproto/server/resetPassword.ts index 5e0148284a1..9e6ece3e4c4 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/resetPassword.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/resetPassword.ts @@ -28,10 +28,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/server/revokeAppPassword.ts b/packages/pds/src/lexicon/types/com/atproto/server/revokeAppPassword.ts index e6bdcd09801..4627f68eaa2 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/revokeAppPassword.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/revokeAppPassword.ts @@ -26,10 +26,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/getBlob.ts b/packages/pds/src/lexicon/types/com/atproto/sync/getBlob.ts index 8644d8ce5d9..60750902472 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/getBlob.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/getBlob.ts @@ -31,10 +31,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/getBlocks.ts b/packages/pds/src/lexicon/types/com/atproto/sync/getBlocks.ts index 9c20c12fc29..e73410efb41 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/getBlocks.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/getBlocks.ts @@ -30,10 +30,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/getCheckout.ts b/packages/pds/src/lexicon/types/com/atproto/sync/getCheckout.ts index 5b4fd2a12d8..63a657e56b9 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/getCheckout.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/getCheckout.ts @@ -12,8 +12,6 @@ import { HandlerAuth } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ did: string - /** The commit to get the checkout from. Defaults to current HEAD. */ - commit?: string } export type InputSchema = undefined @@ -31,10 +29,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/getHead.ts b/packages/pds/src/lexicon/types/com/atproto/sync/getHead.ts index ce8cf66cfb4..586ae1a4189 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/getHead.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/getHead.ts @@ -35,10 +35,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/getCommitPath.ts b/packages/pds/src/lexicon/types/com/atproto/sync/getLatestCommit.ts similarity index 78% rename from packages/bsky/src/lexicon/types/com/atproto/sync/getCommitPath.ts rename to packages/pds/src/lexicon/types/com/atproto/sync/getLatestCommit.ts index cf3bd997264..9b91e878724 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/getCommitPath.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/getLatestCommit.ts @@ -11,16 +11,13 @@ import { HandlerAuth } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ did: string - /** The most recent commit */ - latest?: string - /** The earliest commit to start from */ - earliest?: string } export type InputSchema = undefined export interface OutputSchema { - commits: string[] + cid: string + rev: string [k: string]: unknown } @@ -35,13 +32,17 @@ export interface HandlerSuccess { export interface HandlerError { status: number message?: string + error?: 'RepoNotFound' } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/getRecord.ts b/packages/pds/src/lexicon/types/com/atproto/sync/getRecord.ts index da29fb675ff..297f0ac7794 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/getRecord.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/getRecord.ts @@ -33,10 +33,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/getRepo.ts b/packages/pds/src/lexicon/types/com/atproto/sync/getRepo.ts index 98074484cbd..495d31a1a22 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/getRepo.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/getRepo.ts @@ -12,10 +12,8 @@ import { HandlerAuth } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ did: string - /** The earliest commit in the commit range (not inclusive) */ - earliest?: string - /** The latest commit in the commit range (inclusive) */ - latest?: string + /** The revision of the repo to catch up from. */ + since?: string } export type InputSchema = undefined @@ -33,10 +31,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/listBlobs.ts b/packages/pds/src/lexicon/types/com/atproto/sync/listBlobs.ts index 024a703440c..936b08a69f8 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/listBlobs.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/listBlobs.ts @@ -11,15 +11,16 @@ import { HandlerAuth } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ did: string - /** The most recent commit */ - latest?: string - /** The earliest commit to start from */ - earliest?: string + /** Optional revision of the repo to list blobs since */ + since?: string + limit: number + cursor?: string } export type InputSchema = undefined export interface OutputSchema { + cursor?: string cids: string[] [k: string]: unknown } @@ -38,10 +39,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/listRepos.ts b/packages/pds/src/lexicon/types/com/atproto/sync/listRepos.ts index 506328d6f60..afbc9df8475 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/listRepos.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/listRepos.ts @@ -35,13 +35,16 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | HandlerSuccess -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput export interface Repo { did: string diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts b/packages/pds/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts index df921fb8bc2..3d310c1139a 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts @@ -27,10 +27,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/requestCrawl.ts b/packages/pds/src/lexicon/types/com/atproto/sync/requestCrawl.ts index c634409f6ba..87ef20d7297 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/requestCrawl.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/requestCrawl.ts @@ -27,10 +27,13 @@ export interface HandlerError { } export type HandlerOutput = HandlerError | void -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams input: HandlerInput req: express.Request res: express.Response -}) => Promise | HandlerOutput +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/subscribeRepos.ts b/packages/pds/src/lexicon/types/com/atproto/sync/subscribeRepos.ts index b0bf73edaca..ae9cf01f8f2 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/subscribeRepos.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/subscribeRepos.ts @@ -22,12 +22,15 @@ export type OutputSchema = | { $type: string; [k: string]: unknown } export type HandlerError = ErrorFrame<'FutureCursor' | 'ConsumerTooSlow'> export type HandlerOutput = HandlerError | OutputSchema -export type Handler = (ctx: { +export type HandlerReqCtx = { auth: HA params: QueryParams req: IncomingMessage signal: AbortSignal -}) => AsyncIterable +} +export type Handler = ( + ctx: HandlerReqCtx, +) => AsyncIterable export interface Commit { seq: number @@ -35,7 +38,11 @@ export interface Commit { tooBig: boolean repo: string commit: CID - prev: CID | null + prev?: CID | null + /** The rev of the emitted commit */ + rev: string + /** The rev of the last emitted commit from this repo */ + since: string | null /** CAR file containing relevant blocks */ blocks: Uint8Array ops: RepoOp[] @@ -133,6 +140,7 @@ export function validateInfo(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#info', v) } +/** A repo operation, ie a write of a single record. For creates and updates, cid is the record's CID as of this operation. For deletes, it's null. */ export interface RepoOp { action: 'create' | 'update' | 'delete' | (string & {}) path: string diff --git a/packages/pds/src/logger.ts b/packages/pds/src/logger.ts index 498da256a28..a4277e41669 100644 --- a/packages/pds/src/logger.ts +++ b/packages/pds/src/logger.ts @@ -5,6 +5,8 @@ import * as jwt from 'jsonwebtoken' import { parseBasicAuth } from './auth' export const dbLogger = subsystemLogger('pds:db') +export const readStickyLogger = subsystemLogger('pds:read-sticky') +export const redisLogger = subsystemLogger('pds:redis') export const seqLogger = subsystemLogger('pds:sequencer') export const mailerLogger = subsystemLogger('pds:mailer') export const labelerLogger = subsystemLogger('pds:labler') diff --git a/packages/pds/src/mailer/moderation.ts b/packages/pds/src/mailer/moderation.ts new file mode 100644 index 00000000000..eaf7456eb3e --- /dev/null +++ b/packages/pds/src/mailer/moderation.ts @@ -0,0 +1,34 @@ +import { Transporter } from 'nodemailer' +import Mail from 'nodemailer/lib/mailer' +import SMTPTransport from 'nodemailer/lib/smtp-transport' +import { ServerConfig } from '../config' +import { mailerLogger } from '../logger' + +export class ModerationMailer { + private config: ServerConfig + transporter: Transporter + + constructor( + transporter: Transporter, + config: ServerConfig, + ) { + this.config = config + this.transporter = transporter + } + + async send({ content }: { content: string }, mailOpts: Mail.Options) { + const res = await this.transporter.sendMail({ + ...mailOpts, + text: content, + from: this.config.moderationEmail?.fromAddress, + }) + + if (!this.config.moderationEmail?.smtpUrl) { + mailerLogger.debug( + 'Moderation email auth is not configured. Intended to send email:\n' + + JSON.stringify(res, null, 2), + ) + } + return res + } +} diff --git a/packages/pds/src/mailer/templates/delete-account.hbs b/packages/pds/src/mailer/templates/delete-account.hbs index 67f444dcd6d..f7fbf07a539 100644 --- a/packages/pds/src/mailer/templates/delete-account.hbs +++ b/packages/pds/src/mailer/templates/delete-account.hbs @@ -169,7 +169,10 @@ class="text-red-700" style="color: #b91c1c; padding-top: 0; padding-bottom: 0; font-weight: 500; vertical-align: baseline; font-size: 20px; line-height: 24px; margin: 0;" align="left" - >This will permanently delete your Bluesky account. If you did not issue this request, please update your password. + >This will permanently delete your + Bluesky account. If you did not + issue this request, please update + your password. To permanently delete your account, please - enter the above "reset code" in - the app along with your password.

+ >To + permanently delete + your account, please enter the + above "reset code" in the app + along with your password.

diff --git a/packages/pds/src/redis.ts b/packages/pds/src/redis.ts new file mode 100644 index 00000000000..67528a11833 --- /dev/null +++ b/packages/pds/src/redis.ts @@ -0,0 +1,25 @@ +import assert from 'assert' +import { Redis } from 'ioredis' +import { redisLogger } from './logger' + +export const getRedisClient = (host: string, password?: string): Redis => { + const redisAddr = redisAddressParts(host) + const redis = new Redis({ + ...redisAddr, + password, + }) + redis.on('error', (err) => { + redisLogger.error({ host, err }, 'redis error') + }) + return redis +} + +export const redisAddressParts = ( + addr: string, + defaultPort = 6379, +): { host: string; port: number } => { + const [host, portStr, ...others] = addr.split(':') + const port = portStr ? parseInt(portStr, 10) : defaultPort + assert(host && !isNaN(port) && !others.length, `invalid address: ${addr}`) + return { host, port } +} diff --git a/packages/pds/src/repo/prepare.ts b/packages/pds/src/repo/prepare.ts index 875b83e76bf..60fbe2d81cd 100644 --- a/packages/pds/src/repo/prepare.ts +++ b/packages/pds/src/repo/prepare.ts @@ -1,5 +1,5 @@ import { CID } from 'multiformats/cid' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { TID, dataToCborBlock } from '@atproto/common' import { LexiconDefNotFoundError, @@ -33,6 +33,8 @@ import { } from '../lexicon/types/app/bsky/feed/post' import { isRecord as isList } from '../lexicon/types/app/bsky/graph/list' import { isRecord as isProfile } from '../lexicon/types/app/bsky/actor/profile' +import { hasExplicitSlur } from '../handle/explicit-slurs' +import { InvalidRequestError } from '@atproto/xrpc-server' // @TODO do this dynamically off of schemas export const blobsForWrite = (record: unknown): PreparedBlobRef[] => { @@ -153,7 +155,15 @@ export const prepareCreate = async (opts: { if (validate) { assertValidRecord(record) } + if (collection === lex.ids.AppBskyFeedPost && opts.rkey) { + // @TODO temporary + throw new InvalidRequestError( + 'Custom rkeys for post records are not currently supported.', + ) + } + const rkey = opts.rkey || TID.nextStr() + assertNoExplicitSlurs(rkey, record) return { action: WriteOpAction.Create, uri: AtUri.make(did, collection, rkey), @@ -164,6 +174,13 @@ export const prepareCreate = async (opts: { } } +// only allow PUTs to certain collections +const ALLOWED_PUTS = [ + lex.ids.AppBskyActorProfile, + lex.ids.AppBskyGraphList, + lex.ids.AppBskyFeedGenerator, +] + export const prepareUpdate = async (opts: { did: string collection: string @@ -173,10 +190,20 @@ export const prepareUpdate = async (opts: { validate?: boolean }): Promise => { const { did, collection, rkey, swapCid, validate = true } = opts + if (!ALLOWED_PUTS.includes(collection)) { + // @TODO temporary + throw new InvalidRequestError( + `Temporarily only accepting updates for collections: ${ALLOWED_PUTS.join( + ', ', + )}`, + ) + } + const record = setCollectionName(collection, opts.record, validate) if (validate) { assertValidRecord(record) } + assertNoExplicitSlurs(rkey, record) return { action: WriteOpAction.Update, uri: AtUri.make(did, collection, rkey), @@ -256,3 +283,18 @@ async function cidForSafeRecord(record: RepoRecord) { throw badRecordErr } } + +function assertNoExplicitSlurs(rkey: string, record: RepoRecord) { + let toCheck = '' + if (isProfile(record)) { + toCheck += ' ' + record.displayName + } else if (isList(record)) { + toCheck += ' ' + record.name + } else if (isFeedGenerator(record)) { + toCheck += ' ' + rkey + toCheck += ' ' + record.displayName + } + if (hasExplicitSlur(toCheck)) { + throw new InvalidRecordError('Unacceptable slur in record') + } +} diff --git a/packages/pds/src/repo/types.ts b/packages/pds/src/repo/types.ts index 611f459da0a..a66d3aa0f65 100644 --- a/packages/pds/src/repo/types.ts +++ b/packages/pds/src/repo/types.ts @@ -1,5 +1,5 @@ import { CID } from 'multiformats/cid' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { WriteOpAction } from '@atproto/repo' import { RepoRecord } from '@atproto/lexicon' diff --git a/packages/pds/src/runtime-flags.ts b/packages/pds/src/runtime-flags.ts new file mode 100644 index 00000000000..052ed4973f6 --- /dev/null +++ b/packages/pds/src/runtime-flags.ts @@ -0,0 +1,90 @@ +import { BailableWait, bailableWait } from '@atproto/common' +import { randomIntFromSeed } from '@atproto/crypto' +import { LRUCache } from 'lru-cache' +import Database from './db' +import { dbLogger as log } from './logger' + +type AppviewProxyFlagName = `appview-proxy:${string}` + +export type FlagName = AppviewProxyFlagName + +export class RuntimeFlags { + destroyed = false + private flags = new Map() + private pollWait: BailableWait | undefined = undefined + public appviewProxy = new AppviewProxyFlags(this) + + constructor(public db: Database) {} + + async start() { + await this.refresh() + this.poll() + } + + async destroy() { + this.destroyed = true + this.pollWait?.bail() + await this.pollWait?.wait() + } + + get(flag: FlagName) { + return this.flags.get(flag) || null + } + + async refresh() { + const flags = await this.db.db + .selectFrom('runtime_flag') + .selectAll() + .execute() + this.flags = new Map() + for (const flag of flags) { + this.flags.set(flag.name, flag.value) + } + } + + async poll() { + try { + if (this.destroyed) return + await this.refresh() + } catch (err) { + log.error({ err }, 'runtime flags failed to refresh') + } + this.pollWait = bailableWait(5000) + await this.pollWait.wait() + this.poll() + } +} + +class AppviewProxyFlags { + private partitionCache = new LRUCache({ + max: 50000, + fetchMethod(did: string) { + return randomIntFromSeed(did, 10) + }, + }) + + constructor(private runtimeFlags: RuntimeFlags) {} + + getThreshold(endpoint: string) { + const val = this.runtimeFlags.get(`appview-proxy:${endpoint}`) || '0' + const threshold = parseInt(val, 10) + return appviewFlagIsValid(threshold) ? threshold : 0 + } + + async shouldProxy(endpoint: string, did: string) { + const threshold = this.getThreshold(endpoint) + if (threshold === 0) { + return false + } + if (threshold === 10) { + return true + } + // threshold is 0 to 10 inclusive, partitions are 0 to 9 inclusive. + const partition = await this.partitionCache.fetch(did) + return partition !== undefined && partition < threshold + } +} + +const appviewFlagIsValid = (val: number) => { + return 0 <= val && val <= 10 +} diff --git a/packages/pds/src/sequencer/events.ts b/packages/pds/src/sequencer/events.ts index fc6e98729cd..eb7bbee5b04 100644 --- a/packages/pds/src/sequencer/events.ts +++ b/packages/pds/src/sequencer/events.ts @@ -6,7 +6,6 @@ import { blocksToCarFile, CidSet, CommitData, - RebaseData, WriteOpAction, } from '@atproto/repo' import { PreparedWrite } from '../repo' @@ -50,11 +49,11 @@ export const formatSeqCommit = async ( let carSlice: Uint8Array // max 200 ops or 1MB of data - if (writes.length > 200 || commitData.blocks.byteSize > 1000000) { + if (writes.length > 200 || commitData.newBlocks.byteSize > 1000000) { tooBig = true const justRoot = new BlockMap() - justRoot.add(commitData.blocks.get(commitData.commit)) - carSlice = await blocksToCarFile(commitData.commit, justRoot) + justRoot.add(commitData.newBlocks.get(commitData.cid)) + carSlice = await blocksToCarFile(commitData.cid, justRoot) } else { tooBig = false for (const w of writes) { @@ -70,15 +69,17 @@ export const formatSeqCommit = async ( } ops.push({ action: w.action, path, cid }) } - carSlice = await blocksToCarFile(commitData.commit, commitData.blocks) + carSlice = await blocksToCarFile(commitData.cid, commitData.newBlocks) } const evt: CommitEvt = { rebase: false, tooBig, repo: did, - commit: commitData.commit, + commit: commitData.cid, prev: commitData.prev, + rev: commitData.rev, + since: commitData.since, ops, blocks: carSlice, blobs: blobs.toList(), @@ -91,30 +92,6 @@ export const formatSeqCommit = async ( } } -export const formatSeqRebase = async ( - did: string, - rebaseData: RebaseData, -): Promise => { - const carSlice = await blocksToCarFile(rebaseData.commit, rebaseData.blocks) - - const evt: CommitEvt = { - rebase: true, - tooBig: false, - repo: did, - commit: rebaseData.commit, - prev: rebaseData.rebased, - ops: [], - blocks: carSlice, - blobs: [], - } - return { - did, - eventType: 'rebase', - event: cborEncode(evt), - sequencedAt: new Date().toISOString(), - } -} - export const formatSeqHandleUpdate = async ( did: string, handle: string, @@ -185,6 +162,8 @@ export const commitEvt = z.object({ repo: z.string(), commit: schema.cid, prev: schema.cid.nullable(), + rev: z.string(), + since: z.string().nullable(), blocks: schema.bytes, ops: z.array(commitEvtOp), blobs: z.array(schema.cid), diff --git a/packages/pds/src/sequencer/outbox.ts b/packages/pds/src/sequencer/outbox.ts index 335cd33cc3c..d248099138c 100644 --- a/packages/pds/src/sequencer/outbox.ts +++ b/packages/pds/src/sequencer/outbox.ts @@ -104,18 +104,19 @@ export class Outbox { // yields only historical events async *getBackfill(backfillCursor: number, backfillTime?: string) { + const PAGE_SIZE = 500 while (true) { const evts = await this.sequencer.requestSeqRange({ earliestTime: backfillTime, earliestSeq: this.lastSeen > -1 ? this.lastSeen : backfillCursor, - limit: 10, + limit: PAGE_SIZE, }) for (const evt of evts) { yield evt } - // if we're within 50 of the sequencer, we call it good & switch to cutover + // if we're within half a pagesize of the sequencer, we call it good & switch to cutover const seqCursor = this.sequencer.lastSeen ?? -1 - if (seqCursor - this.lastSeen < 10) break + if (seqCursor - this.lastSeen < PAGE_SIZE / 2) break if (evts.length < 1) break } } diff --git a/packages/pds/src/sequencer/sequencer-leader.ts b/packages/pds/src/sequencer/sequencer-leader.ts index 58eb9aaee5f..34afbacea13 100644 --- a/packages/pds/src/sequencer/sequencer-leader.ts +++ b/packages/pds/src/sequencer/sequencer-leader.ts @@ -1,8 +1,11 @@ +import { sql } from 'kysely' import { DisconnectError } from '@atproto/xrpc-server' import { jitter, wait } from '@atproto/common' import { Leader } from '../db/leader' import { seqLogger as log } from '../logger' import Database from '../db' +import { REPO_SEQ_SEQUENCE } from '../db/tables/repo-seq' +import { countAll } from '../db/util' export const SEQUENCER_LEADER_ID = 1100 @@ -12,15 +15,13 @@ export class SequencerLeader { destroyed = false polling = false queued = false - lastSeq: number constructor(public db: Database, lockId = SEQUENCER_LEADER_ID) { this.leader = new Leader(lockId, this.db) } - nextSeqVal(): number { - this.lastSeq++ - return this.lastSeq + get isLeader() { + return !!this.leader.session } async run() { @@ -29,15 +30,6 @@ export class SequencerLeader { while (!this.destroyed) { try { const { ran } = await this.leader.run(async ({ signal }) => { - const res = await this.db.db - .selectFrom('repo_seq') - .select('seq') - .where('seq', 'is not', null) - .orderBy('seq', 'desc') - .limit(1) - .executeTakeFirst() - this.lastSeq = res?.seq ?? 0 - const seqListener = () => { if (this.polling) { this.queued = true @@ -103,29 +95,52 @@ export class SequencerLeader { } async sequenceOutgoing() { - const unsequenced = await this.getUnsequenced() - for (const row of unsequenced) { - await this.db.db - .updateTable('repo_seq') - .set({ seq: this.nextSeqVal() }) - .where('id', '=', row.id) - .execute() - await this.db.notify('outgoing_repo_seq') - } + await this.db.db + .updateTable('repo_seq') + .from((qb) => + qb + .selectFrom('repo_seq') + .select([ + 'id as update_id', + sql`nextval(${sql.literal(REPO_SEQ_SEQUENCE)})`.as( + 'update_seq', + ), + ]) + .where('seq', 'is', null) + .orderBy('id', 'asc') + .as('update'), + ) + .set({ seq: sql`update_seq::bigint` }) + .whereRef('id', '=', 'update_id') + .execute() + + await this.db.notify('outgoing_repo_seq') } - async getUnsequenced() { - return this.db.db + async getUnsequencedCount() { + const res = await this.db.db .selectFrom('repo_seq') .where('seq', 'is', null) - .select('id') - .orderBy('id', 'asc') - .execute() + .select(countAll.as('count')) + .executeTakeFirst() + return res?.count ?? 0 } async isCaughtUp(): Promise { - const unsequenced = await this.getUnsequenced() - return unsequenced.length === 0 + if (this.db.dialect === 'sqlite') return true + const count = await this.getUnsequencedCount() + return count === 0 + } + + async lastSeq(): Promise { + const res = await this.db.db + .selectFrom('repo_seq') + .select('seq') + .where('seq', 'is not', null) + .orderBy('seq', 'desc') + .limit(1) + .executeTakeFirst() + return res?.seq ?? 0 } destroy() { diff --git a/packages/pds/src/sequencer/sequencer.ts b/packages/pds/src/sequencer/sequencer.ts index 4ea3b4ac836..7c678bcc711 100644 --- a/packages/pds/src/sequencer/sequencer.ts +++ b/packages/pds/src/sequencer/sequencer.ts @@ -3,8 +3,8 @@ import TypedEmitter from 'typed-emitter' import Database from '../db' import { seqLogger as log } from '../logger' import { RepoSeqEntry } from '../db/tables/repo-seq' -import { cborDecode, check } from '@atproto/common' -import { commitEvt, handleEvt, SeqEvt, tombstoneEvt } from './events' +import { cborDecode } from '@atproto/common' +import { CommitEvt, HandleEvt, SeqEvt, TombstoneEvt } from './events' export * from './events' @@ -24,11 +24,10 @@ export class Sequencer extends (EventEmitter as new () => SequencerEmitter) { this.lastSeen = curr.seq ?? 0 } this.db.channels.outgoing_repo_seq.addListener('message', () => { - if (this.polling) { - this.queued = true - } else { - this.polling = true + if (!this.polling) { this.pollDb() + } else { + this.queued = true // poll again once current poll completes } }) } @@ -95,26 +94,26 @@ export class Sequencer extends (EventEmitter as new () => SequencerEmitter) { continue } const evt = cborDecode(row.event) - if (check.is(evt, commitEvt)) { + if (row.eventType === 'append' || row.eventType === 'rebase') { seqEvts.push({ type: 'commit', seq: row.seq, time: row.sequencedAt, - evt, + evt: evt as CommitEvt, }) - } else if (check.is(evt, handleEvt)) { + } else if (row.eventType === 'handle') { seqEvts.push({ type: 'handle', seq: row.seq, time: row.sequencedAt, - evt, + evt: evt as HandleEvt, }) - } else if (check.is(evt, tombstoneEvt)) { + } else if (row.eventType === 'tombstone') { seqEvts.push({ type: 'tombstone', seq: row.seq, time: row.sequencedAt, - evt, + evt: evt as TombstoneEvt, }) } } @@ -124,21 +123,22 @@ export class Sequencer extends (EventEmitter as new () => SequencerEmitter) { async pollDb() { try { + this.polling = true const evts = await this.requestSeqRange({ earliestSeq: this.lastSeen, - limit: 10, + limit: 1000, }) if (evts.length > 0) { + this.queued = true // should poll again immediately this.emit('events', evts) this.lastSeen = evts.at(-1)?.seq ?? this.lastSeen } } catch (err) { log.error({ err, lastSeen: this.lastSeen }, 'sequencer failed to poll db') } finally { - // check if we should continue polling - if (this.queued === false) { - this.polling = false - } else { + this.polling = false + if (this.queued) { + // if queued, poll again this.queued = false this.pollDb() } diff --git a/packages/pds/src/services/account/index.ts b/packages/pds/src/services/account/index.ts index 51eaa86b647..91c0b09f8c9 100644 --- a/packages/pds/src/services/account/index.ts +++ b/packages/pds/src/services/account/index.ts @@ -1,3 +1,4 @@ +import { sql } from 'kysely' import { randomStr } from '@atproto/crypto' import { InvalidRequestError } from '@atproto/xrpc-server' import { dbLogger as log } from '../../logger' @@ -34,7 +35,13 @@ export class AccountService { if (handleOrDid.startsWith('did:')) { return qb.where('did_handle.did', '=', handleOrDid) } else { - return qb.where('did_handle.handle', '=', handleOrDid) + // lower() is a little hack to avoid using the handle trgm index here, which is slow. not sure why it was preferring + // the handle trgm index over the handle unique index. in any case, we end-up using did_handle_handle_lower_idx instead, which is fast. + return qb.where( + sql`lower(${ref('did_handle.handle')})`, + '=', + handleOrDid, + ) } }) .selectAll('user_account') @@ -79,7 +86,13 @@ export class AccountService { handleOrDid: string, includeSoftDeleted = false, ): Promise { - if (handleOrDid.startsWith('did:')) return handleOrDid + if (handleOrDid.startsWith('did:')) { + if (includeSoftDeleted) { + return handleOrDid + } + const available = await this.isRepoAvailable(handleOrDid) + return available ? handleOrDid : null + } const { ref } = this.db.db.dynamic const found = await this.db.db .selectFrom('did_handle') @@ -130,12 +143,18 @@ export class AccountService { log.info({ handle, email, did }, 'registered user') } - async updateHandle(did: string, handle: string) { + // @NOTE should always be paired with a sequenceHandle(). + // the token output from this method should be passed to sequenceHandle(). + async updateHandle( + did: string, + handle: string, + ): Promise { const res = await this.db.db .updateTable('did_handle') .set({ handle }) .where('did', '=', did) .whereNotExists( + // @NOTE see also condition in isHandleAvailable() this.db.db .selectFrom('did_handle') .where('handle', '=', handle) @@ -145,10 +164,25 @@ export class AccountService { if (res.numUpdatedRows < 1) { throw new UserAlreadyExistsError() } - const seqEvt = await sequencer.formatSeqHandleUpdate(did, handle) + return { did, handle } + } + + async sequenceHandle(tok: HandleSequenceToken) { + this.db.assertTransaction() + const seqEvt = await sequencer.formatSeqHandleUpdate(tok.did, tok.handle) await sequencer.sequenceEvt(this.db, seqEvt) } + async getHandleDid(handle: string): Promise { + // @NOTE see also condition in updateHandle() + const found = await this.db.db + .selectFrom('did_handle') + .where('handle', '=', handle) + .selectAll() + .executeTakeFirst() + return found?.did ?? null + } + async updateEmail(did: string, email: string) { await this.db.db .updateTable('user_account') @@ -446,6 +480,15 @@ export class AccountService { `Some preferences are not in the ${namespace} namespace`, ) } + // short-held row lock to prevent races + if (this.db.dialect === 'pg') { + await this.db.db + .selectFrom('user_account') + .selectAll() + .forUpdate() + .where('did', '=', did) + .executeTakeFirst() + } // get all current prefs for user and prep new pref rows const allPrefs = await this.db.db .selectFrom('user_pref') @@ -507,3 +550,5 @@ export class ListKeyset extends TimeCidKeyset<{ const matchNamespace = (namespace: string, fullname: string) => { return fullname === namespace || fullname.startsWith(`${namespace}.`) } + +export type HandleSequenceToken = { did: string; handle: string } diff --git a/packages/pds/src/services/index.ts b/packages/pds/src/services/index.ts index c10c130a5c9..30dffedf061 100644 --- a/packages/pds/src/services/index.ts +++ b/packages/pds/src/services/index.ts @@ -1,3 +1,4 @@ +import { AtpAgent } from '@atproto/api' import * as crypto from '@atproto/crypto' import { BlobStore } from '@atproto/repo' import Database from '../db' @@ -8,14 +9,26 @@ import { RepoService } from './repo' import { ModerationService } from './moderation' import { BackgroundQueue } from '../background' import { Crawlers } from '../crawlers' +import { LocalService } from './local' export function createServices(resources: { repoSigningKey: crypto.Keypair blobstore: BlobStore + appViewAgent?: AtpAgent + appViewDid?: string + appViewCdnUrlPattern?: string backgroundQueue: BackgroundQueue crawlers: Crawlers }): Services { - const { repoSigningKey, blobstore, backgroundQueue, crawlers } = resources + const { + repoSigningKey, + blobstore, + appViewAgent, + appViewDid, + appViewCdnUrlPattern, + backgroundQueue, + crawlers, + } = resources return { account: AccountService.creator(), auth: AuthService.creator(), @@ -26,6 +39,12 @@ export function createServices(resources: { backgroundQueue, crawlers, ), + local: LocalService.creator( + repoSigningKey, + appViewAgent, + appViewDid, + appViewCdnUrlPattern, + ), moderation: ModerationService.creator(blobstore), } } @@ -35,6 +54,7 @@ export type Services = { auth: FromDb record: FromDb repo: FromDb + local: FromDb moderation: FromDb } diff --git a/packages/pds/src/services/local/index.ts b/packages/pds/src/services/local/index.ts new file mode 100644 index 00000000000..867f3baf4e7 --- /dev/null +++ b/packages/pds/src/services/local/index.ts @@ -0,0 +1,363 @@ +import util from 'util' +import { CID } from 'multiformats/cid' +import { AtUri } from '@atproto/syntax' +import { cborToLexRecord } from '@atproto/repo' +import Database from '../../db' +import { Record as PostRecord } from '../../lexicon/types/app/bsky/feed/post' +import { Record as ProfileRecord } from '../../lexicon/types/app/bsky/actor/profile' +import { ids } from '../../lexicon/lexicons' +import { + ProfileViewBasic, + ProfileView, + ProfileViewDetailed, +} from '../../lexicon/types/app/bsky/actor/defs' +import { FeedViewPost, PostView } from '../../lexicon/types/app/bsky/feed/defs' +import { + Main as EmbedImages, + isMain as isEmbedImages, +} from '../../lexicon/types/app/bsky/embed/images' +import { + Main as EmbedExternal, + isMain as isEmbedExternal, +} from '../../lexicon/types/app/bsky/embed/external' +import { + Main as EmbedRecord, + isMain as isEmbedRecord, + View as EmbedRecordView, +} from '../../lexicon/types/app/bsky/embed/record' +import { + Main as EmbedRecordWithMedia, + isMain as isEmbedRecordWithMedia, +} from '../../lexicon/types/app/bsky/embed/recordWithMedia' +import { AtpAgent } from '@atproto/api' +import { Keypair } from '@atproto/crypto' +import { createServiceAuthHeaders } from '@atproto/xrpc-server' + +type CommonSignedUris = 'avatar' | 'banner' | 'feed_thumbnail' | 'feed_fullsize' + +export class LocalService { + constructor( + public db: Database, + public signingKey: Keypair, + public appviewAgent?: AtpAgent, + public appviewDid?: string, + public appviewCdnUrlPattern?: string, + ) {} + + static creator( + signingKey: Keypair, + appviewAgent?: AtpAgent, + appviewDid?: string, + appviewCdnUrlPattern?: string, + ) { + return (db: Database) => + new LocalService( + db, + signingKey, + appviewAgent, + appviewDid, + appviewCdnUrlPattern, + ) + } + + getImageUrl(pattern: CommonSignedUris, did: string, cid: string) { + if (!this.appviewCdnUrlPattern) { + return '' + } + return util.format(this.appviewCdnUrlPattern, pattern, did, cid) + } + + async serviceAuthHeaders(did: string) { + if (!this.appviewDid) { + throw new Error('Could not find bsky appview did') + } + return createServiceAuthHeaders({ + iss: did, + aud: this.appviewDid, + keypair: this.signingKey, + }) + } + + async getRecordsSinceRev(did: string, rev: string): Promise { + const res = await this.db.db + .selectFrom('record') + .innerJoin('ipld_block', (join) => + join + .onRef('record.did', '=', 'ipld_block.creator') + .onRef('record.cid', '=', 'ipld_block.cid'), + ) + .select([ + 'ipld_block.content', + 'uri', + 'ipld_block.cid', + 'record.indexedAt', + ]) + .where('did', '=', did) + .where('record.repoRev', '>', rev) + .orderBy('record.repoRev', 'asc') + .execute() + return res.reduce( + (acc, cur) => { + const descript = { + uri: new AtUri(cur.uri), + cid: CID.parse(cur.cid), + indexedAt: cur.indexedAt, + record: cborToLexRecord(cur.content), + } + if ( + descript.uri.collection === ids.AppBskyActorProfile && + descript.uri.rkey === 'self' + ) { + acc.profile = descript as RecordDescript + } else if (descript.uri.collection === ids.AppBskyFeedPost) { + acc.posts.push(descript as RecordDescript) + } + return acc + }, + { profile: null, posts: [] } as LocalRecords, + ) + } + + async getProfileBasic(did: string): Promise { + const res = await this.db.db + .selectFrom('did_handle') + .leftJoin('record', 'record.did', 'did_handle.did') + .leftJoin('ipld_block', (join) => + join + .onRef('record.did', '=', 'ipld_block.creator') + .onRef('record.cid', '=', 'ipld_block.cid'), + ) + .where('did_handle.did', '=', did) + .where('record.collection', '=', ids.AppBskyActorProfile) + .where('record.rkey', '=', 'self') + .selectAll() + .executeTakeFirst() + if (!res) return null + const record = res.content + ? (cborToLexRecord(res.content) as ProfileRecord) + : null + return { + did, + handle: res.handle, + displayName: record?.displayName, + avatar: record?.avatar + ? this.getImageUrl('avatar', did, record.avatar.ref.toString()) + : undefined, + } + } + + async formatAndInsertPostsInFeed( + feed: FeedViewPost[], + posts: RecordDescript[], + ): Promise { + if (posts.length === 0) { + return feed + } + const lastTime = feed.at(-1)?.post.indexedAt ?? new Date(0).toISOString() + const inFeed = posts.filter((p) => p.indexedAt > lastTime) + const newestToOldest = inFeed.reverse() + const maybeFormatted = await Promise.all( + newestToOldest.map((p) => this.getPost(p)), + ) + const formatted = maybeFormatted.filter((p) => p !== null) as PostView[] + for (const post of formatted) { + const idx = feed.findIndex((fi) => fi.post.indexedAt < post.indexedAt) + if (idx >= 0) { + feed.splice(idx, 0, { post }) + } else { + feed.push({ post }) + } + } + return feed + } + + async getPost( + descript: RecordDescript, + ): Promise { + const { uri, cid, indexedAt, record } = descript + const author = await this.getProfileBasic(uri.hostname) + if (!author) return null + const embed = record.embed + ? await this.formatPostEmbed(author.did, record) + : undefined + return { + uri: uri.toString(), + cid: cid.toString(), + author, + record, + embed: embed ?? undefined, + indexedAt, + } + } + + async formatPostEmbed(did: string, post: PostRecord) { + const embed = post.embed + if (!embed) return null + if (isEmbedImages(embed) || isEmbedExternal(embed)) { + return this.formatSimpleEmbed(did, embed) + } else if (isEmbedRecord(embed)) { + return this.formatRecordEmbed(did, embed) + } else if (isEmbedRecordWithMedia(embed)) { + return this.formatRecordWithMediaEmbed(did, embed) + } else { + return null + } + } + + async formatSimpleEmbed(did: string, embed: EmbedImages | EmbedExternal) { + if (isEmbedImages(embed)) { + const images = embed.images.map((img) => ({ + thumb: this.getImageUrl( + 'feed_thumbnail', + did, + img.image.ref.toString(), + ), + fullsize: this.getImageUrl( + 'feed_fullsize', + did, + img.image.ref.toString(), + ), + alt: img.alt, + })) + return { + $type: 'app.bsky.embed.images#view', + images, + } + } else { + const { uri, title, description, thumb } = embed.external + return { + $type: 'app.bsky.embed.external#view', + uri, + title, + description, + thumb: thumb + ? this.getImageUrl('feed_thumbnail', did, thumb.ref.toString()) + : undefined, + } + } + } + + async formatRecordEmbed( + did: string, + embed: EmbedRecord, + ): Promise { + const view = await this.formatRecordEmbedInternal(did, embed) + return { + $type: 'app.bsky.embed.record#view', + record: + view === null + ? { + $type: 'app.bsky.embed.record#viewNotFound', + uri: embed.record.uri, + } + : view, + } + } + + async formatRecordEmbedInternal(did: string, embed: EmbedRecord) { + if (!this.appviewAgent || !this.appviewDid) { + return null + } + const collection = new AtUri(embed.record.uri).collection + if (collection === ids.AppBskyFeedPost) { + const res = await this.appviewAgent.api.app.bsky.feed.getPosts( + { + uris: [embed.record.uri], + }, + await this.serviceAuthHeaders(did), + ) + const post = res.data.posts[0] + if (!post) return null + return { + $type: 'app.bsky.embed.record#viewRecord', + uri: post.uri, + cid: post.cid, + author: post.author, + value: post.record, + labels: post.labels, + embeds: post.embed ? [post.embed] : undefined, + indexedAt: post.indexedAt, + } + } else if (collection === ids.AppBskyFeedGenerator) { + const res = await this.appviewAgent.api.app.bsky.feed.getFeedGenerator( + { + feed: embed.record.uri, + }, + await this.serviceAuthHeaders(did), + ) + return { + $type: 'app.bsaky.feed.defs#generatorView', + ...res.data.view, + } + } else if (collection === ids.AppBskyGraphList) { + const res = await this.appviewAgent.api.app.bsky.graph.getList( + { + list: embed.record.uri, + }, + await this.serviceAuthHeaders(did), + ) + return { + $type: 'app.bsaky.graph.defs#listView', + ...res.data.list, + } + } + return null + } + + async formatRecordWithMediaEmbed(did: string, embed: EmbedRecordWithMedia) { + if (!isEmbedImages(embed.media) && !isEmbedExternal(embed.media)) { + return null + } + const media = this.formatSimpleEmbed(did, embed.media) + const record = await this.formatRecordEmbed(did, embed.record) + return { + $type: 'app.bsky.embed.recordWithMedia#view', + record, + media, + } + } + + updateProfileViewBasic( + view: ProfileViewBasic, + record: ProfileRecord, + ): ProfileViewBasic { + return { + ...view, + displayName: record.displayName, + avatar: record.avatar + ? this.getImageUrl('avatar', view.did, record.avatar.ref.toString()) + : undefined, + } + } + + updateProfileView(view: ProfileView, record: ProfileRecord): ProfileView { + return { + ...this.updateProfileViewBasic(view, record), + description: record.description, + } + } + + updateProfileDetailed( + view: ProfileViewDetailed, + record: ProfileRecord, + ): ProfileViewDetailed { + return { + ...this.updateProfileView(view, record), + banner: record.banner + ? this.getImageUrl('banner', view.did, record.banner.ref.toString()) + : undefined, + } + } +} + +export type LocalRecords = { + profile: RecordDescript | null + posts: RecordDescript[] +} + +export type RecordDescript = { + uri: AtUri + cid: CID + indexedAt: string + record: T +} diff --git a/packages/pds/src/services/moderation/index.ts b/packages/pds/src/services/moderation/index.ts index d1342a0058e..622fee2a11c 100644 --- a/packages/pds/src/services/moderation/index.ts +++ b/packages/pds/src/services/moderation/index.ts @@ -1,13 +1,15 @@ import { Selectable, sql } from 'kysely' import { CID } from 'multiformats/cid' import { BlobStore } from '@atproto/repo' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { InvalidRequestError } from '@atproto/xrpc-server' import Database from '../../db' import { ModerationAction, ModerationReport } from '../../db/tables/moderation' import { RecordService } from '../record' import { ModerationViews } from './views' import SqlRepoStorage from '../../sql-repo-storage' +import { TAKEDOWN } from '../../lexicon/types/com/atproto/admin/defs' +import { addHoursToDate } from '../../util/date' export class ModerationService { constructor(public db: Database, public blobstore: BlobStore) {} @@ -81,6 +83,7 @@ export class ModerationService { ignoreSubjects?: string[] reverse?: boolean reporters?: string[] + actionedBy?: string }): Promise { const { subject, @@ -91,6 +94,7 @@ export class ModerationService { ignoreSubjects, reverse = false, reporters, + actionedBy, } = opts const { ref } = this.db.db.dynamic let builder = this.db.db.selectFrom('moderation_report') @@ -103,11 +107,29 @@ export class ModerationService { } if (ignoreSubjects?.length) { - builder = builder.where((qb) => { - return qb - .where('subjectDid', 'not in', ignoreSubjects) - .where('subjectUri', 'not in', ignoreSubjects) + const ignoreUris: string[] = [] + const ignoreDids: string[] = [] + + ignoreSubjects.forEach((subject) => { + if (subject.startsWith('at://')) { + ignoreUris.push(subject) + } else if (subject.startsWith('did:')) { + ignoreDids.push(subject) + } }) + + if (ignoreDids.length) { + builder = builder.where('subjectDid', 'not in', ignoreDids) + } + if (ignoreUris.length) { + builder = builder.where((qb) => { + // Without the null condition, postgres will ignore all reports where `subjectUri` is null + // which will make all the account reports be ignored as well + return qb + .where('subjectUri', 'not in', ignoreUris) + .orWhere('subjectUri', 'is', null) + }) + } } if (reporters?.length) { @@ -127,8 +149,8 @@ export class ModerationService { ? builder.whereExists(resolutionsQuery) : builder.whereNotExists(resolutionsQuery) } - if (actionType !== undefined) { - const resolutionActionsQuery = this.db.db + if (actionType !== undefined || actionedBy !== undefined) { + let resolutionActionsQuery = this.db.db .selectFrom('moderation_report_resolution') .innerJoin( 'moderation_action', @@ -140,10 +162,22 @@ export class ModerationService { '=', ref('moderation_report.id'), ) - .where('moderation_action.action', '=', sql`${actionType}`) - .where('moderation_action.reversedAt', 'is', null) - .selectAll() - builder = builder.whereExists(resolutionActionsQuery) + + if (actionType) { + resolutionActionsQuery = resolutionActionsQuery + .where('moderation_action.action', '=', sql`${actionType}`) + .where('moderation_action.reversedAt', 'is', null) + } + + if (actionedBy) { + resolutionActionsQuery = resolutionActionsQuery.where( + 'moderation_action.createdBy', + '=', + actionedBy, + ) + } + + builder = builder.whereExists(resolutionActionsQuery.selectAll()) } if (cursor) { @@ -208,6 +242,7 @@ export class ModerationService { negateLabelVals?: string[] createdBy: string createdAt?: Date + durationInHours?: number }): Promise { this.db.assertTransaction() const { @@ -216,6 +251,7 @@ export class ModerationService { reason, subject, subjectBlobCids, + durationInHours, createdAt = new Date(), } = info const createLabelVals = @@ -282,6 +318,11 @@ export class ModerationService { createdBy, createLabelVals, negateLabelVals, + durationInHours, + expiresAt: + durationInHours !== undefined + ? addHoursToDate(durationInHours, createdAt).toISOString() + : undefined, ...subjectInfo, }) .returningAll() @@ -313,6 +354,60 @@ export class ModerationService { return actionResult } + async getActionsDueForReversal(): Promise> { + const actionsDueForReversal = await this.db.db + .selectFrom('moderation_action') + // Get entries that have an durationInHours that has passed and have not been reversed + .where('durationInHours', 'is not', null) + .where('expiresAt', '<', new Date().toISOString()) + .where('reversedAt', 'is', null) + .selectAll() + .execute() + + return actionsDueForReversal + } + + async revertAction({ + id, + createdAt, + createdBy, + reason, + }: { + id: number + createdAt: Date + createdBy: string + reason: string + }) { + const result = await this.logReverseAction({ + id, + createdAt, + createdBy, + reason, + }) + + if ( + result.action === TAKEDOWN && + result.subjectType === 'com.atproto.admin.defs#repoRef' && + result.subjectDid + ) { + await this.reverseTakedownRepo({ + did: result.subjectDid, + }) + } + + if ( + result.action === TAKEDOWN && + result.subjectType === 'com.atproto.repo.strongRef' && + result.subjectUri + ) { + await this.reverseTakedownRecord({ + uri: new AtUri(result.subjectUri), + }) + } + + return result + } + async logReverseAction(info: { id: number reason: string @@ -477,7 +572,7 @@ export class ModerationService { // Resolve subject info let subjectInfo: SubjectInfo if ('did' in subject) { - const repo = await new SqlRepoStorage(this.db, subject.did).getHead() + const repo = await new SqlRepoStorage(this.db, subject.did).getRoot() if (!repo) throw new InvalidRequestError('Repo not found') subjectInfo = { subjectType: 'com.atproto.admin.defs#repoRef', diff --git a/packages/pds/src/services/moderation/views.ts b/packages/pds/src/services/moderation/views.ts index e1da1383dd1..b38b9fa987e 100644 --- a/packages/pds/src/services/moderation/views.ts +++ b/packages/pds/src/services/moderation/views.ts @@ -1,6 +1,6 @@ import { Selectable } from 'kysely' -import { ArrayEl } from '@atproto/common' -import { AtUri } from '@atproto/uri' +import { ArrayEl, cborBytesToRecord } from '@atproto/common' +import { AtUri } from '@atproto/syntax' import Database from '../../db' import { DidHandle } from '../../db/tables/did-handle' import { RepoRoot } from '../../db/tables/repo-root' @@ -20,6 +20,7 @@ import { ModerationAction } from '../../db/tables/moderation' import { AccountService } from '../account' import { RecordService } from '../record' import { ModerationReportRowWithHandle } from '.' +import { ids } from '../../lexicon/lexicons' export class ModerationViews { constructor(private db: Database) {} @@ -29,10 +30,11 @@ export class ModerationViews { record: RecordService.creator(), } - repo(result: RepoResult): Promise - repo(result: RepoResult[]): Promise + repo(result: RepoResult, opts: ModViewOptions): Promise + repo(result: RepoResult[], opts: ModViewOptions): Promise async repo( result: RepoResult | RepoResult[], + opts: ModViewOptions, ): Promise { const results = Array.isArray(result) ? result : [result] if (results.length === 0) return [] @@ -41,6 +43,17 @@ export class ModerationViews { await this.db.db .selectFrom('did_handle') .leftJoin('user_account', 'user_account.did', 'did_handle.did') + .leftJoin('record as profile_record', (join) => + join + .onRef('profile_record.did', '=', 'did_handle.did') + .on('profile_record.collection', '=', ids.AppBskyActorProfile) + .on('profile_record.rkey', '=', 'self'), + ) + .leftJoin('ipld_block as profile_block', (join) => + join + .onRef('profile_block.cid', '=', 'profile_record.cid') + .onRef('profile_block.creator', '=', 'did_handle.did'), + ) .where( 'did_handle.did', 'in', @@ -50,6 +63,8 @@ export class ModerationViews { 'did_handle.did as did', 'user_account.email as email', 'user_account.invitesDisabled as invitesDisabled', + 'user_account.inviteNote as inviteNote', + 'profile_block.content as profileBytes', ]) .execute(), this.db.db @@ -61,7 +76,7 @@ export class ModerationViews { 'in', results.map((r) => r.did), ) - .select(['id', 'action', 'subjectDid']) + .select(['id', 'action', 'durationInHours', 'subjectDid']) .execute(), this.services .account(this.db) @@ -78,29 +93,42 @@ export class ModerationViews { ) const views = results.map((r) => { - const { email, invitesDisabled } = infoByDid[r.did] ?? {} + const { email, invitesDisabled, profileBytes, inviteNote } = + infoByDid[r.did] ?? {} const action = actionByDid[r.did] + const relatedRecords: object[] = [] + if (profileBytes) { + relatedRecords.push(cborBytesToRecord(profileBytes)) + } return { did: r.did, handle: r.handle, - email: email ?? undefined, - relatedRecords: [], + email: opts.includeEmails && email ? email : undefined, + relatedRecords, indexedAt: r.indexedAt, moderation: { currentAction: action - ? { id: action.id, action: action.action } + ? { + id: action.id, + action: action.action, + durationInHours: action.durationInHours ?? undefined, + } : undefined, }, invitedBy: invitedBy[r.did], invitesDisabled: invitesDisabled === 1, + inviteNote: inviteNote ?? undefined, } }) return Array.isArray(result) ? views : views[0] } - async repoDetail(result: RepoResult): Promise { - const repo = await this.repo(result) + async repoDetail( + result: RepoResult, + opts: ModViewOptions, + ): Promise { + const repo = await this.repo(result, opts) const [reportResults, actionResults, inviteCodes] = await Promise.all([ this.db.db .selectFrom('moderation_report') @@ -133,10 +161,11 @@ export class ModerationViews { } } - record(result: RecordResult): Promise - record(result: RecordResult[]): Promise + record(result: RecordResult, opts: ModViewOptions): Promise + record(result: RecordResult[], opts: ModViewOptions): Promise async record( result: RecordResult | RecordResult[], + opts: ModViewOptions, ): Promise { const results = Array.isArray(result) ? result : [result] if (results.length === 0) return [] @@ -171,10 +200,10 @@ export class ModerationViews { 'in', results.map((r) => r.uri), ) - .select(['id', 'action', 'subjectUri']) + .select(['id', 'action', 'durationInHours', 'subjectUri']) .execute(), ]) - const repos = await this.repo(repoResults) + const repos = await this.repo(repoResults, opts) const reposByDid = repos.reduce( (acc, cur) => Object.assign(acc, { [cur.did]: cur }), @@ -203,7 +232,11 @@ export class ModerationViews { repo, moderation: { currentAction: action - ? { id: action.id, action: action.action } + ? { + id: action.id, + action: action.action, + durationInHours: action.durationInHours ?? undefined, + } : undefined, }, } @@ -212,9 +245,12 @@ export class ModerationViews { return Array.isArray(result) ? views : views[0] } - async recordDetail(result: RecordResult): Promise { + async recordDetail( + result: RecordResult, + opts: ModViewOptions, + ): Promise { const [record, reportResults, actionResults] = await Promise.all([ - this.record(result), + this.record(result, opts), this.db.db .selectFrom('moderation_report') .where('subjectType', '=', 'com.atproto.repo.strongRef') @@ -295,6 +331,7 @@ export class ModerationViews { const views = results.map((res) => ({ id: res.id, action: res.action, + durationInHours: res.durationInHours ?? undefined, subject: res.subjectType === 'com.atproto.admin.defs#repoRef' ? { @@ -334,7 +371,10 @@ export class ModerationViews { return Array.isArray(result) ? views : views[0] } - async actionDetail(result: ActionResult): Promise { + async actionDetail( + result: ActionResult, + opts: ModViewOptions, + ): Promise { const action = await this.action(result) const reportResults = action.resolvedReportIds.length ? await this.db.db @@ -345,13 +385,14 @@ export class ModerationViews { .execute() : [] const [subject, resolvedReports, subjectBlobs] = await Promise.all([ - this.subject(result), + this.subject(result, opts), this.report(reportResults), this.blob(action.subjectBlobCids), ]) return { id: action.id, action: action.action, + durationInHours: action.durationInHours, subject, subjectBlobs, createLabelVals: action.createLabelVals, @@ -441,7 +482,10 @@ export class ModerationViews { } } - async reportDetail(result: ReportResult): Promise { + async reportDetail( + result: ReportResult, + opts: ModViewOptions, + ): Promise { const report = await this.report(result) const actionResults = report.resolvedByActionIds.length ? await this.db.db @@ -452,7 +496,7 @@ export class ModerationViews { .execute() : [] const [subject, resolvedByActions] = await Promise.all([ - this.subject(result), + this.subject(result, opts), this.action(actionResults), ]) return { @@ -468,14 +512,17 @@ export class ModerationViews { // Partial view for subjects - async subject(result: SubjectResult): Promise { + async subject( + result: SubjectResult, + opts: ModViewOptions, + ): Promise { let subject: SubjectView if (result.subjectType === 'com.atproto.admin.defs#repoRef') { const repoResult = await this.services .account(this.db) .getAccount(result.subjectDid, true) if (repoResult) { - subject = await this.repo(repoResult) + subject = await this.repo(repoResult, opts) subject.$type = 'com.atproto.admin.defs#repoView' } else { subject = { did: result.subjectDid } @@ -489,7 +536,7 @@ export class ModerationViews { .record(this.db) .getRecord(new AtUri(result.subjectUri), null, true) if (recordResult) { - subject = await this.record(recordResult) + subject = await this.record(recordResult, opts) subject.$type = 'com.atproto.admin.defs#recordView' } else { subject = { uri: result.subjectUri } @@ -519,7 +566,7 @@ export class ModerationViews { 'subject_blob.actionId', 'moderation_action.id', ) - .select(['id', 'action', 'cid']) + .select(['id', 'action', 'durationInHours', 'cid']) .execute(), ]) const actionByCid = actionResults.reduce( @@ -546,7 +593,11 @@ export class ModerationViews { : undefined, moderation: { currentAction: action - ? { id: action.id, action: action.action } + ? { + id: action.id, + action: action.action, + durationInHours: action.durationInHours ?? undefined, + } : undefined, }, } @@ -578,3 +629,5 @@ type SubjectView = ActionViewDetail['subject'] & ReportViewDetail['subject'] function didFromUri(uri: string) { return new AtUri(uri).host } + +export type ModViewOptions = { includeEmails: boolean } diff --git a/packages/pds/src/services/record/index.ts b/packages/pds/src/services/record/index.ts index 114def95e5c..fe8a142f82e 100644 --- a/packages/pds/src/services/record/index.ts +++ b/packages/pds/src/services/record/index.ts @@ -1,6 +1,6 @@ import { CID } from 'multiformats/cid' -import { AtUri, ensureValidAtUri } from '@atproto/uri' -import * as ident from '@atproto/identifier' +import { AtUri, ensureValidAtUri } from '@atproto/syntax' +import * as ident from '@atproto/syntax' import { cborToLexRecord, WriteOpAction } from '@atproto/repo' import { dbLogger as log } from '../../logger' import Database from '../../db' @@ -20,6 +20,7 @@ export class RecordService { cid: CID, obj: unknown, action: WriteOpAction.Create | WriteOpAction.Update = WriteOpAction.Create, + repoRev?: string, timestamp?: string, ) { this.db.assertTransaction() @@ -30,6 +31,7 @@ export class RecordService { did: uri.host, collection: uri.collection, rkey: uri.rkey, + repoRev: repoRev ?? null, indexedAt: timestamp || new Date().toISOString(), } if (!record.did.startsWith('did:')) { @@ -45,9 +47,11 @@ export class RecordService { .insertInto('record') .values(record) .onConflict((oc) => - oc - .column('uri') - .doUpdateSet({ cid: record.cid, indexedAt: record.indexedAt }), + oc.column('uri').doUpdateSet({ + cid: record.cid, + repoRev: repoRev ?? null, + indexedAt: record.indexedAt, + }), ) .execute() diff --git a/packages/pds/src/services/repo/blobs.ts b/packages/pds/src/services/repo/blobs.ts index 71d8c68b8dc..2bedb88ecfd 100644 --- a/packages/pds/src/services/repo/blobs.ts +++ b/packages/pds/src/services/repo/blobs.ts @@ -4,7 +4,7 @@ import { CID } from 'multiformats/cid' import bytes from 'bytes' import { fromStream as fileTypeFromStream } from 'file-type' import { BlobStore, CidSet, WriteOpAction } from '@atproto/repo' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { cloneStream, sha256RawToCid, streamSize } from '@atproto/common' import { InvalidRequestError } from '@atproto/xrpc-server' import { BlobRef } from '@atproto/lexicon' @@ -60,7 +60,7 @@ export class RepoBlobs { return new BlobRef(cid, mimeType, size) } - async processWriteBlobs(did: string, commit: CID, writes: PreparedWrite[]) { + async processWriteBlobs(did: string, rev: string, writes: PreparedWrite[]) { await this.deleteDereferencedBlobs(did, writes) const blobPromises: Promise[] = [] @@ -71,7 +71,7 @@ export class RepoBlobs { ) { for (const blob of write.blobs) { blobPromises.push(this.verifyBlobAndMakePermanent(did, blob)) - blobPromises.push(this.associateBlob(blob, write.uri, commit, did)) + blobPromises.push(this.associateBlob(blob, write.uri, rev, did)) } } } @@ -186,7 +186,7 @@ export class RepoBlobs { async associateBlob( blob: PreparedBlobRef, recordUri: AtUri, - commit: CID, + repoRev: string, did: string, ): Promise { await this.db.db @@ -194,30 +194,22 @@ export class RepoBlobs { .values({ cid: blob.cid.toString(), recordUri: recordUri.toString(), - commit: commit.toString(), + repoRev, did, }) .onConflict((oc) => oc.doNothing()) .execute() } - async processRebaseBlobs(did: string, newRoot: CID) { - await this.db.db - .updateTable('repo_blob') - .set({ commit: newRoot.toString() }) - .where('did', '=', did) - .execute() - } - - async listForCommits(did: string, commits: CID[]): Promise { - if (commits.length < 1) return [] - const commitStrs = commits.map((c) => c.toString()) - const res = await this.db.db + async listSinceRev(did: string, rev?: string): Promise { + let builder = this.db.db .selectFrom('repo_blob') .where('did', '=', did) - .where('commit', 'in', commitStrs) .select('cid') - .execute() + if (rev) { + builder = builder.where('repoRev', '>', rev) + } + const res = await builder.execute() const cids = res.map((row) => CID.parse(row.cid)) return new CidSet(cids).toList() } diff --git a/packages/pds/src/services/repo/index.ts b/packages/pds/src/services/repo/index.ts index 8e5067427b9..406635b736d 100644 --- a/packages/pds/src/services/repo/index.ts +++ b/packages/pds/src/services/repo/index.ts @@ -1,17 +1,8 @@ import { CID } from 'multiformats/cid' import * as crypto from '@atproto/crypto' -import { - BlobStore, - MemoryBlockstore, - BlockMap, - CommitData, - RebaseData, - Repo, - WriteOpAction, -} from '@atproto/repo' -import * as repo from '@atproto/repo' -import { AtUri } from '@atproto/uri' +import { BlobStore, CommitData, Repo, WriteOpAction } from '@atproto/repo' import { InvalidRequestError } from '@atproto/xrpc-server' +import { AtUri } from '@atproto/syntax' import Database from '../../db' import SqlRepoStorage from '../../sql-repo-storage' import { @@ -26,7 +17,6 @@ import { RecordService } from '../record' import * as sequencer from '../../sequencer' import { wait } from '@atproto/common' import { BackgroundQueue } from '../../background' -import { countAll } from '../../db/util' import { Crawlers } from '../../crawlers' export class RepoService { @@ -85,7 +75,7 @@ export class RepoService { await Promise.all([ storage.applyCommit(commit), this.indexWrites(writes, now), - this.blobs.processWriteBlobs(did, commit.commit, writes), + this.blobs.processWriteBlobs(did, commit.rev, writes), ]) await this.afterWriteProcessing(did, commit, writes) } @@ -98,17 +88,17 @@ export class RepoService { ) { this.db.assertTransaction() const storage = new SqlRepoStorage(this.db, did, now) - const locked = await storage.lockHead() - if (!locked || !locked.equals(commitData.prev)) { + const obtained = await storage.lockRepo() + if (!obtained) { throw new ConcurrentWriteError() } await Promise.all([ // persist the commit to repo storage storage.applyCommit(commitData), // & send to indexing - this.indexWrites(writes, now), + this.indexWrites(writes, now, commitData.rev), // process blobs - this.blobs.processWriteBlobs(did, commitData.commit, writes), + this.blobs.processWriteBlobs(did, commitData.rev, writes), // do any other processing needed after write ]) await this.afterWriteProcessing(did, commitData, writes) @@ -118,12 +108,19 @@ export class RepoService { toWrite: { did: string; writes: PreparedWrite[]; swapCommitCid?: CID }, times: number, timeout = 100, + prevStorage?: SqlRepoStorage, ) { this.db.assertNotTransaction() const { did, writes, swapCommitCid } = toWrite - const storage = new SqlRepoStorage(this.db, did) - const commit = await this.formatCommit(storage, did, writes, swapCommitCid) + // we may have some useful cached blocks in the storage, so re-use the previous instance + const storage = prevStorage ?? new SqlRepoStorage(this.db, did) try { + const commit = await this.formatCommit( + storage, + did, + writes, + swapCommitCid, + ) await this.serviceTx(async (srvcTx) => srvcTx.processCommit(did, writes, commit, new Date().toISOString()), ) @@ -133,7 +130,7 @@ export class RepoService { throw err } await wait(timeout) - return this.processWrites(toWrite, times - 1, timeout) + return this.processWrites(toWrite, times - 1, timeout, storage) } else { throw err } @@ -146,18 +143,34 @@ export class RepoService { writes: PreparedWrite[], swapCommit?: CID, ): Promise { - const currRoot = await storage.getHead() + // this is not in a txn, so this won't actually hold the lock, + // we just check if it is currently held by another txn + const available = await storage.lockAvailable() + if (!available) { + throw new ConcurrentWriteError() + } + const currRoot = await storage.getRootDetailed() if (!currRoot) { throw new InvalidRequestError( `${did} is not a registered repo on this server`, ) } - if (swapCommit && !currRoot.equals(swapCommit)) { - throw new BadCommitSwapError(currRoot) + if (swapCommit && !currRoot.cid.equals(swapCommit)) { + throw new BadCommitSwapError(currRoot.cid) } + // cache last commit since there's likely overlap + await storage.cacheRev(currRoot.rev) const recordTxn = this.services.record(this.db) + const newRecordCids: CID[] = [] + const delAndUpdateUris: AtUri[] = [] for (const write of writes) { const { action, uri, swapCid } = write + if (action !== WriteOpAction.Delete) { + newRecordCids.push(write.cid) + } + if (action !== WriteOpAction.Create) { + delAndUpdateUris.push(uri) + } if (swapCid === undefined) { continue } @@ -176,12 +189,43 @@ export class RepoService { throw new BadRecordSwapError(currRecord) } } - const writeOps = writes.map(writeToOp) - const repo = await Repo.load(storage, currRoot) - return repo.formatCommit(writeOps, this.repoSigningKey) + + let commit: CommitData + try { + const repo = await Repo.load(storage, currRoot.cid) + const writeOps = writes.map(writeToOp) + commit = await repo.formatCommit(writeOps, this.repoSigningKey) + } catch (err) { + // if an error occurs, check if it is attributable to a concurrent write + const curr = await storage.getRoot() + if (!currRoot.cid.equals(curr)) { + throw new ConcurrentWriteError() + } else { + throw err + } + } + + // find blocks that would be deleted but are referenced by another record + const dupeRecordCids = await this.getDuplicateRecordCids( + did, + commit.removedCids.toList(), + delAndUpdateUris, + ) + for (const cid of dupeRecordCids) { + commit.removedCids.delete(cid) + } + + // find blocks that are relevant to ops but not included in diff + // (for instance a record that was moved but cid stayed the same) + const newRecordBlocks = commit.newBlocks.getMany(newRecordCids) + if (newRecordBlocks.missing.length > 0) { + const missingBlocks = await storage.getBlocks(newRecordBlocks.missing) + commit.newBlocks.addMap(missingBlocks.blocks) + } + return commit } - async indexWrites(writes: PreparedWrite[], now: string) { + async indexWrites(writes: PreparedWrite[], now: string, rev?: string) { this.db.assertTransaction() const recordTxn = this.services.record(this.db) await Promise.all( @@ -195,6 +239,7 @@ export class RepoService { write.cid, write.record, write.action, + rev, now, ) } else if (write.action === WriteOpAction.Delete) { @@ -204,6 +249,26 @@ export class RepoService { ) } + async getDuplicateRecordCids( + did: string, + cids: CID[], + touchedUris: AtUri[], + ): Promise { + if (touchedUris.length === 0 || cids.length === 0) { + return [] + } + const cidStrs = cids.map((c) => c.toString()) + const uriStrs = touchedUris.map((u) => u.toString()) + const res = await this.db.db + .selectFrom('record') + .where('did', '=', did) + .where('cid', 'in', cidStrs) + .where('uri', 'not in', uriStrs) + .select('cid') + .execute() + return res.map((row) => CID.parse(row.cid)) + } + async afterWriteProcessing( did: string, commitData: CommitData, @@ -219,119 +284,12 @@ export class RepoService { await sequencer.sequenceEvt(this.db, seqEvt) } - async rebaseRepo(did: string, swapCommit?: CID) { - this.db.assertNotTransaction() - const rebaseData = await this.formatRebase(did, swapCommit) - - // rebases are expensive & should be done rarely, we don't try to re-process on concurrent writes - await this.serviceTx(async (srvcTx) => - srvcTx.processRebase(did, rebaseData), - ) - } - - async formatRebase(did: string, swapCommit?: CID): Promise { - const storage = new SqlRepoStorage(this.db, did, new Date().toISOString()) - const currRoot = await storage.getHead() - if (!currRoot) { - throw new InvalidRequestError( - `${did} is not a registered repo on this server`, - ) - } - if (swapCommit && !currRoot.equals(swapCommit)) { - throw new BadCommitSwapError(currRoot) - } - - const records = await this.db.db - .selectFrom('record') - .where('did', '=', did) - .select(['uri', 'cid']) - .execute() - const memoryStore = new MemoryBlockstore() - let data = await repo.MST.create(memoryStore) - for (const record of records) { - const uri = new AtUri(record.uri) - const cid = CID.parse(record.cid) - const dataKey = repo.formatDataKey(uri.collection, uri.rkey) - data = await data.add(dataKey, cid) - } - const commit = await repo.signCommit( - { - did, - version: 2, - prev: null, - data: await data.getPointer(), - }, - this.repoSigningKey, - ) - const currCids = await data.allCids() - const newBlocks = new BlockMap() - const commitCid = await newBlocks.add(commit) - return { - commit: commitCid, - rebased: currRoot, - blocks: newBlocks, - preservedCids: currCids.toList(), - } - } - - async processRebase(did: string, rebaseData: RebaseData) { - this.db.assertTransaction() - const storage = new SqlRepoStorage(this.db, did) - const lockedHead = await storage.lockHead() - if (!rebaseData.rebased.equals(lockedHead)) { - throw new ConcurrentWriteError() - } - - const recordCountBefore = await this.countRecordBlocks(did) - await Promise.all([ - storage.applyRebase(rebaseData), - this.blobs.processRebaseBlobs(did, rebaseData.commit), - ]) - const recordCountAfter = await this.countRecordBlocks(did) - // This is purely a dummy check on a very sensitive operation - if (recordCountBefore !== recordCountAfter) { - throw new Error( - `Record blocks deleted during rebase. Rolling back: ${did}`, - ) - } - - await this.afterRebaseProcessing(did, rebaseData) - } - - async afterRebaseProcessing(did: string, rebaseData: RebaseData) { - const seqEvt = await sequencer.formatSeqRebase(did, rebaseData) - await sequencer.sequenceEvt(this.db, seqEvt) - } - - // used for integrity check - private async countRecordBlocks(did: string): Promise { - const res = await this.db.db - .selectFrom('record') - .where('record.did', '=', did) - .innerJoin('ipld_block', (join) => - join - .onRef('ipld_block.creator', '=', 'record.did') - .onRef('ipld_block.cid', '=', 'record.cid'), - ) - .select(countAll.as('count')) - .executeTakeFirst() - return res?.count ?? 0 - } - async deleteRepo(did: string) { // Not done in transaction because it would be too long, prone to contention. // Also, this can safely be run multiple times if it fails. // delete all blocks from this did & no other did await this.db.db.deleteFrom('repo_root').where('did', '=', did).execute() await this.db.db.deleteFrom('repo_seq').where('did', '=', did).execute() - await this.db.db - .deleteFrom('repo_commit_block') - .where('creator', '=', did) - .execute() - await this.db.db - .deleteFrom('repo_commit_history') - .where('creator', '=', did) - .execute() await this.db.db .deleteFrom('ipld_block') .where('creator', '=', did) diff --git a/packages/pds/src/sql-repo-storage.ts b/packages/pds/src/sql-repo-storage.ts index b3ce8a8cb05..a7b6a5ae1ea 100644 --- a/packages/pds/src/sql-repo-storage.ts +++ b/packages/pds/src/sql-repo-storage.ts @@ -3,18 +3,17 @@ import { RepoStorage, BlockMap, CidSet, - RebaseData, - CommitCidData, + ReadableBlockstore, + writeCarStream, } from '@atproto/repo' import { chunkArray } from '@atproto/common' import { CID } from 'multiformats/cid' import Database from './db' -import { valuesList } from './db/util' import { IpldBlock } from './db/tables/ipld-block' -import { RepoCommitBlock } from './db/tables/repo-commit-block' -import { RepoCommitHistory } from './db/tables/repo-commit-history' +import { ConcurrentWriteError } from './services/repo' +import { sql } from 'kysely' -export class SqlRepoStorage extends RepoStorage { +export class SqlRepoStorage extends ReadableBlockstore implements RepoStorage { cache: BlockMap = new BlockMap() constructor( @@ -25,28 +24,51 @@ export class SqlRepoStorage extends RepoStorage { super() } - // note this method will return null if the repo has a lock on it currently - async lockHead(): Promise { - let builder = this.db.db + async lockRepo(): Promise { + if (this.db.dialect === 'sqlite') return true + return this.db.takeTxAdvisoryLock(this.did) + } + + async lockAvailable(): Promise { + if (this.db.dialect === 'sqlite') return true + return this.db.checkTxAdvisoryLock(this.did) + } + + async getRoot(): Promise { + const res = await this.db.db .selectFrom('repo_root') .selectAll() .where('did', '=', this.did) - if (this.db.dialect !== 'sqlite') { - builder = builder.forUpdate().skipLocked() - } - const res = await builder.executeTakeFirst() + .executeTakeFirst() if (!res) return null return CID.parse(res.root) } - async getHead(): Promise { + async getRootDetailed(): Promise<{ cid: CID; rev: string } | null> { const res = await this.db.db .selectFrom('repo_root') .selectAll() .where('did', '=', this.did) .executeTakeFirst() if (!res) return null - return CID.parse(res.root) + return { + cid: CID.parse(res.root), + rev: res.rev ?? '', // @TODO add not-null constraint to rev + } + } + + // proactively cache all blocks from a particular commit (to prevent multiple roundtrips) + async cacheRev(rev: string): Promise { + const res = await this.db.db + .selectFrom('ipld_block') + .where('creator', '=', this.did) + .where('repoRev', '=', rev) + .select(['ipld_block.cid', 'ipld_block.content']) + .limit(15) + .execute() + for (const row of res) { + this.cache.set(CID.parse(row.cid), row.content) + } } async getBytes(cid: CID): Promise { @@ -94,13 +116,14 @@ export class SqlRepoStorage extends RepoStorage { return { blocks, missing: missing.toList() } } - async putBlock(cid: CID, block: Uint8Array): Promise { + async putBlock(cid: CID, block: Uint8Array, rev: string): Promise { this.db.assertTransaction() await this.db.db .insertInto('ipld_block') .values({ cid: cid.toString(), creator: this.did, + repoRev: rev, size: block.length, content: block, }) @@ -109,13 +132,14 @@ export class SqlRepoStorage extends RepoStorage { this.cache.set(cid, block) } - async putMany(toPut: BlockMap): Promise { + async putMany(toPut: BlockMap, rev: string): Promise { this.db.assertTransaction() const blocks: IpldBlock[] = [] toPut.forEach((bytes, cid) => { blocks.push({ cid: cid.toString(), creator: this.did, + repoRev: rev, size: bytes.length, content: bytes, }) @@ -132,108 +156,26 @@ export class SqlRepoStorage extends RepoStorage { ) } - async applyRebase(rebase: RebaseData): Promise { - this.db.assertTransaction() - await Promise.all([ - this.db.db - .deleteFrom('repo_commit_block') - .where('creator', '=', this.did) - .execute(), - this.db.db - .deleteFrom('repo_commit_history') - .where('creator', '=', this.did) - .execute(), - this.putMany(rebase.blocks), - ]) - - const allCids = [...rebase.preservedCids, ...rebase.blocks.cids()] - await this.indexCommitCids([ - { commit: rebase.commit, prev: null, cids: allCids }, - ]) + async deleteMany(cids: CID[]) { + if (cids.length < 1) return + const cidStrs = cids.map((c) => c.toString()) await this.db.db .deleteFrom('ipld_block') - .where('ipld_block.creator', '=', this.did) - .whereNotExists((qb) => - qb - .selectFrom('repo_commit_block') - .selectAll() - .where('repo_commit_block.creator', '=', this.did) - .where('repo_commit_block.commit', '=', rebase.commit.toString()) - .whereRef('repo_commit_block.block', '=', 'ipld_block.cid'), - ) + .where('creator', '=', this.did) + .where('cid', 'in', cidStrs) .execute() - await this.updateHead(rebase.commit, rebase.rebased) } - async indexCommits(commits: CommitData[]): Promise { - this.db.assertTransaction() - const allBlocks = new BlockMap() - const cidData: CommitCidData[] = [] - for (const commit of commits) { - const commitCids: CID[] = [] - for (const block of commit.blocks.entries()) { - commitCids.push(block.cid) - allBlocks.set(block.cid, block.bytes) - } - cidData.push({ - commit: commit.commit, - prev: commit.prev, - cids: commitCids, - }) - } - await Promise.all([this.putMany(allBlocks), this.indexCommitCids(cidData)]) - } - - async indexCommitCids(commits: CommitCidData[]): Promise { - this.db.assertTransaction() - const commitBlocks: RepoCommitBlock[] = [] - const commitHistory: RepoCommitHistory[] = [] - for (const commit of commits) { - for (const cid of commit.cids) { - commitBlocks.push({ - commit: commit.commit.toString(), - block: cid.toString(), - creator: this.did, - }) - } - commitHistory.push({ - commit: commit.commit.toString(), - prev: commit.prev ? commit.prev.toString() : null, - creator: this.did, - }) - } - const insertCommitBlocks = Promise.all( - chunkArray(commitBlocks, 500).map((batch) => - this.db.db - .insertInto('repo_commit_block') - .values(batch) - .onConflict((oc) => oc.doNothing()) - .execute(), - ), - ) - const insertCommitHistory = Promise.all( - chunkArray(commitHistory, 500).map((batch) => - this.db.db - .insertInto('repo_commit_history') - .values(batch) - .onConflict((oc) => oc.doNothing()) - .execute(), - ), - ) - await Promise.all([insertCommitBlocks, insertCommitHistory]) + async applyCommit(commit: CommitData) { + await Promise.all([ + this.updateRoot(commit.cid, commit.prev ?? undefined), + this.putMany(commit.newBlocks, commit.rev), + this.deleteMany(commit.removedCids.toList()), + ]) } - async updateHead(cid: CID, prev: CID | null): Promise { - if (prev === null) { - await this.db.db - .insertInto('repo_root') - .values({ - did: this.did, - root: cid.toString(), - indexedAt: this.getTimestamp(), - }) - .execute() - } else { + async updateRoot(cid: CID, ensureSwap?: CID): Promise { + if (ensureSwap) { const res = await this.db.db .updateTable('repo_root') .set({ @@ -241,85 +183,81 @@ export class SqlRepoStorage extends RepoStorage { indexedAt: this.getTimestamp(), }) .where('did', '=', this.did) - .where('root', '=', prev.toString()) + .where('root', '=', ensureSwap.toString()) .executeTakeFirst() if (res.numUpdatedRows < 1) { - throw new Error('failed to update repo root: misordered') + throw new ConcurrentWriteError() } + } else { + await this.db.db + .insertInto('repo_root') + .values({ + did: this.did, + root: cid.toString(), + indexedAt: this.getTimestamp(), + }) + .onConflict((oc) => + oc.column('did').doUpdateSet({ + root: cid.toString(), + indexedAt: this.getTimestamp(), + }), + ) + .execute() } } - private getTimestamp(): string { - return this.timestamp || new Date().toISOString() - } - - async getCommitPath( - latest: CID, - earliest: CID | null, - ): Promise { - const res = await this.db.db - .withRecursive('ancestor(commit, prev)', (cte) => - cte - .selectFrom('repo_commit_history as commit') - .select(['commit.commit as commit', 'commit.prev as prev']) - .where('commit', '=', latest.toString()) - .where('creator', '=', this.did) - .unionAll( - cte - .selectFrom('repo_commit_history as commit') - .select(['commit.commit as commit', 'commit.prev as prev']) - .innerJoin('ancestor', (join) => - join - .onRef('ancestor.prev', '=', 'commit.commit') - .on('commit.creator', '=', this.did), - ) - .if(earliest !== null, (qb) => - // @ts-ignore - qb.where('commit.commit', '!=', earliest?.toString() as string), - ), - ), - ) - .selectFrom('ancestor') - .select('commit') - .execute() - return res.map((row) => CID.parse(row.commit)).reverse() + async getCarStream(since?: string) { + const root = await this.getRoot() + if (!root) { + throw new RepoRootNotFoundError() + } + return writeCarStream(root, async (car) => { + let cursor: RevCursor | undefined = undefined + do { + const res = await this.getBlockRange(since, cursor) + for (const row of res) { + await car.put({ + cid: CID.parse(row.cid), + bytes: row.content, + }) + } + const lastRow = res.at(-1) + if (lastRow && lastRow.repoRev) { + cursor = { + cid: CID.parse(lastRow.cid), + rev: lastRow.repoRev, + } + } else { + cursor = undefined + } + } while (cursor) + }) } - async getAllBlocksForCommits(commits: CID[]): Promise { - if (commits.length === 0) return [] - const commitStrs = commits.map((commit) => commit.toString()) - const res = await this.db.db - .selectFrom('repo_commit_block') - .where('repo_commit_block.creator', '=', this.did) - .whereRef('repo_commit_block.commit', 'in', valuesList(commitStrs)) - .innerJoin('ipld_block', (join) => - join - .onRef('ipld_block.cid', '=', 'repo_commit_block.block') - .onRef('ipld_block.creator', '=', 'repo_commit_block.creator'), + async getBlockRange(since?: string, cursor?: RevCursor) { + const { ref } = this.db.db.dynamic + let builder = this.db.db + .selectFrom('ipld_block') + .where('creator', '=', this.did) + .select(['cid', 'repoRev', 'content']) + .orderBy('repoRev', 'asc') + .orderBy('cid', 'asc') + .limit(500) + if (cursor) { + // use this syntax to ensure we hit the index + builder = builder.where( + sql`((${ref('repoRev')}, ${ref('cid')}) > (${ + cursor.rev + }, ${cursor.cid.toString()}))`, ) - .select([ - 'repo_commit_block.commit', - 'ipld_block.cid', - 'ipld_block.content', - ]) - .execute() - return res.map((row) => ({ - cid: CID.parse(row.cid), - bytes: row.content, - commit: row.commit, - })) + } else if (since) { + builder = builder.where('repoRev', '>', since) + } + return builder.execute() } - async getBlocksForCommits( - commits: CID[], - ): Promise<{ [commit: string]: BlockMap }> { - const allBlocks = await this.getAllBlocksForCommits(commits) - return allBlocks.reduce((acc, cur) => { - acc[cur.commit] ??= new BlockMap() - acc[cur.commit].set(cur.cid, cur.bytes) - this.cache.set(cur.cid, cur.bytes) - return acc - }, {}) + getTimestamp(): string { + return this.timestamp || new Date().toISOString() } async destroy(): Promise { @@ -327,10 +265,11 @@ export class SqlRepoStorage extends RepoStorage { } } -type BlockForCommit = { +type RevCursor = { cid: CID - bytes: Uint8Array - commit: string + rev: string } export default SqlRepoStorage + +export class RepoRootNotFoundError extends Error {} diff --git a/packages/pds/src/util/compression.ts b/packages/pds/src/util/compression.ts new file mode 100644 index 00000000000..f79a672f2f5 --- /dev/null +++ b/packages/pds/src/util/compression.ts @@ -0,0 +1,16 @@ +import express from 'express' +import compression from 'compression' + +export default function () { + return compression({ + filter, + }) +} + +function filter(_req: express.Request, res: express.Response) { + const contentType = res.getHeader('Content-type') + if (contentType === 'application/vnd.ipld.car') { + return true + } + return compression.filter(_req, res) +} diff --git a/packages/pds/src/util/date.ts b/packages/pds/src/util/date.ts new file mode 100644 index 00000000000..af9767a0f7f --- /dev/null +++ b/packages/pds/src/util/date.ts @@ -0,0 +1,14 @@ +/** + * This function takes a number as input and returns a Date object, + * which is the current date and time plus the input number of hours. + * + * @param {number} hours - The number of hours to add to the current date and time. + * @param {Date} startingDate - If provided, the function will add `hours` to the provided date instead of the current date. + * @returns {Date} - The new Date object, which is the current date and time plus the input number of hours. + */ +export function addHoursToDate(hours: number, startingDate?: Date): Date { + // When date is passe, let's clone before calling `setHours()` so that we are not mutating the original date + const currentDate = startingDate ? new Date(startingDate) : new Date() + currentDate.setHours(currentDate.getHours() + hours) + return currentDate +} diff --git a/packages/pds/tests/_util.ts b/packages/pds/tests/_util.ts index df845a254a9..69ea84d4826 100644 --- a/packages/pds/tests/_util.ts +++ b/packages/pds/tests/_util.ts @@ -4,7 +4,7 @@ import path from 'path' import getPort from 'get-port' import * as crypto from '@atproto/crypto' import { PlcServer, Database as PlcDatabase } from '@did-plc/server' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { randomStr } from '@atproto/crypto' import { uniqueLockId } from '@atproto/dev-env' import { CID } from 'multiformats/cid' @@ -17,12 +17,14 @@ import { ServerEnvironment, envToCfg, envToSecrets } from '../src/config' const ADMIN_PASSWORD = 'admin-pass' const MODERATOR_PASSWORD = 'moderator-pass' +const TRIAGE_PASSWORD = 'triage-pass' export type CloseFn = () => Promise export type TestServerInfo = { url: string ctx: AppContext close: CloseFn + processAll: () => Promise } export type TestServerOpts = { @@ -81,6 +83,7 @@ export const runTestServer = async ( jwtSecret: 'jwt-secret', inviteRequired: false, inviteEpoch: Date.now(), + triagePassword: TRIAGE_PASSWORD, ...params, } @@ -117,6 +120,9 @@ export const runTestServer = async ( await pds.destroy() await plcServer.destroy() }, + processAll: async () => { + await pds.ctx.backgroundQueue.processAll() + }, } } @@ -128,6 +134,10 @@ export const moderatorAuth = () => { return basicAuth('admin', MODERATOR_PASSWORD) } +export const triageAuth = () => { + return basicAuth('admin', TRIAGE_PASSWORD) +} + const basicAuth = (username: string, password: string) => { return ( 'Basic ' + @@ -178,8 +188,12 @@ export const forSnapshot = (obj: unknown) => { if (str.match(/^\d+::bafy/)) { return constantKeysetCursor } + + if (str.match(/^\d+::did:plc/)) { + return constantDidCursor + } if (str.match(/\/image\/[^/]+\/.+\/did:plc:[^/]+\/[^/]+@[\w]+$/)) { - // Match image urls + // Match image urls (pds) const match = str.match( /\/image\/([^/]+)\/.+\/(did:plc:[^/]+)\/([^/]+)@[\w]+$/, ) @@ -190,7 +204,16 @@ export const forSnapshot = (obj: unknown) => { .replace(did, take(users, did)) .replace(cid, take(cids, cid)) } - if (str.startsWith('pds-public-url-')) { + if (str.match(/\/img\/[^/]+\/.+\/did:plc:[^/]+\/[^/]+@[\w]+$/)) { + // Match image urls (bsky w/ presets) + const match = str.match( + /\/img\/[^/]+\/.+\/(did:plc:[^/]+)\/([^/]+)@[\w]+$/, + ) + if (!match) return str + const [, did, cid] = match + return str.replace(did, take(users, did)).replace(cid, take(cids, cid)) + } + if (str.startsWith('localhost-')) { return 'invite-code' } if (str.match(/^\d+::pds-public-url-/)) { @@ -246,6 +269,7 @@ export function take( export const constantDate = new Date(0).toISOString() export const constantKeysetCursor = '0000000000000::bafycid' +export const constantDidCursor = '0000000000000::did' const mapLeafValues = (obj: unknown, fn: (val: unknown) => unknown) => { if (Array.isArray(obj)) { diff --git a/packages/pds/tests/account-deletion.test.ts b/packages/pds/tests/account-deletion.test.ts index 213cf47f1ad..2abcdf5f9b8 100644 --- a/packages/pds/tests/account-deletion.test.ts +++ b/packages/pds/tests/account-deletion.test.ts @@ -13,8 +13,6 @@ import { UserAccount } from '../src/db/tables/user-account' import { IpldBlock } from '../src/db/tables/ipld-block' import { RepoBlob } from '../src/db/tables/repo-blob' import { Blob } from '../src/db/tables/blob' -import { RepoCommitHistory } from '../src/db/tables/repo-commit-history' -import { RepoCommitBlock } from '../src/db/tables/repo-commit-block' import { Record } from '../src/db/tables/record' import { RepoSeq } from '../src/db/tables/repo-seq' import { ACKNOWLEDGE } from '../src/lexicon/types/com/atproto/admin/defs' @@ -133,7 +131,7 @@ describe('account deletion', () => { did: carol.did, password: carol.password, }) - await server.ctx.backgroundQueue.processAll() // Finish background hard-deletions + await server.processAll() // Finish background hard-deletions }) it('no longer lets the user log in', async () => { @@ -165,14 +163,9 @@ describe('account deletion', () => { (row) => row.did === carol.did && row.eventType === 'tombstone', ).length, ).toEqual(1) - expect(updatedDbContents.commitBlocks).toEqual( - initialDbContents.commitBlocks.filter((row) => row.creator !== carol.did), - ) - expect(updatedDbContents.commitHistories).toEqual( - initialDbContents.commitHistories.filter( - (row) => row.creator !== carol.did, - ), - ) + }) + + it('no longer stores indexed records from the user', async () => { expect(updatedDbContents.records).toEqual( initialDbContents.records.filter((row) => row.did !== carol.did), ) @@ -227,64 +220,38 @@ type DbContents = { users: Selectable[] blocks: IpldBlock[] seqs: Selectable[] - commitHistories: RepoCommitHistory[] - commitBlocks: RepoCommitBlock[] records: Record[] repoBlobs: RepoBlob[] blobs: Blob[] } const getDbContents = async (db: Database): Promise => { - const [ - roots, - users, - blocks, - seqs, - commitHistories, - commitBlocks, - records, - repoBlobs, - blobs, - ] = await Promise.all([ - db.db.selectFrom('repo_root').orderBy('did').selectAll().execute(), - db.db.selectFrom('user_account').orderBy('did').selectAll().execute(), - db.db - .selectFrom('ipld_block') - .orderBy('creator') - .orderBy('cid') - .selectAll() - .execute(), - db.db.selectFrom('repo_seq').orderBy('id').selectAll().execute(), - db.db - .selectFrom('repo_commit_history') - .orderBy('creator') - .orderBy('commit') - .selectAll() - .execute(), - db.db - .selectFrom('repo_commit_block') - .orderBy('creator') - .orderBy('commit') - .orderBy('block') - .selectAll() - .execute(), - db.db.selectFrom('record').orderBy('uri').selectAll().execute(), - db.db - .selectFrom('repo_blob') - .orderBy('did') - .orderBy('cid') - .selectAll() - .execute(), - db.db.selectFrom('blob').orderBy('cid').selectAll().execute(), - ]) + const [roots, users, blocks, seqs, records, repoBlobs, blobs] = + await Promise.all([ + db.db.selectFrom('repo_root').orderBy('did').selectAll().execute(), + db.db.selectFrom('user_account').orderBy('did').selectAll().execute(), + db.db + .selectFrom('ipld_block') + .orderBy('creator') + .orderBy('cid') + .selectAll() + .execute(), + db.db.selectFrom('repo_seq').orderBy('id').selectAll().execute(), + db.db.selectFrom('record').orderBy('uri').selectAll().execute(), + db.db + .selectFrom('repo_blob') + .orderBy('did') + .orderBy('cid') + .selectAll() + .execute(), + db.db.selectFrom('blob').orderBy('cid').selectAll().execute(), + ]) return { roots, users, blocks, seqs, - commitHistories, - commitBlocks, records, repoBlobs, blobs, diff --git a/packages/pds/tests/account.test.ts b/packages/pds/tests/account.test.ts index 23c600450c4..25549205f3f 100644 --- a/packages/pds/tests/account.test.ts +++ b/packages/pds/tests/account.test.ts @@ -246,7 +246,7 @@ describe('account', () => { }) it('disallows non-admin moderators to perform email updates', async () => { - const attemptUpdate = agent.api.com.atproto.admin.updateAccountEmail( + const attemptUpdateMod = agent.api.com.atproto.admin.updateAccountEmail( { account: handle, email: 'new@email.com', @@ -256,7 +256,18 @@ describe('account', () => { headers: { authorization: util.moderatorAuth() }, }, ) - await expect(attemptUpdate).rejects.toThrow('Authentication Required') + await expect(attemptUpdateMod).rejects.toThrow('Insufficient privileges') + const attemptUpdateTriage = agent.api.com.atproto.admin.updateAccountEmail( + { + account: handle, + email: 'new@email.com', + }, + { + encoding: 'application/json', + headers: { authorization: util.triageAuth() }, + }, + ) + await expect(attemptUpdateTriage).rejects.toThrow('Insufficient privileges') }) it('disallows duplicate email addresses and handles', async () => { diff --git a/packages/pds/tests/admin/__snapshots__/get-moderation-action.test.ts.snap b/packages/pds/tests/admin/__snapshots__/get-moderation-action.test.ts.snap index 16a4eb16000..aedd7a5a7ea 100644 --- a/packages/pds/tests/admin/__snapshots__/get-moderation-action.test.ts.snap +++ b/packages/pds/tests/admin/__snapshots__/get-moderation-action.test.ts.snap @@ -43,12 +43,45 @@ Object { "indexedAt": "1970-01-01T00:00:00.000Z", "invitesDisabled": false, "moderation": Object {}, - "relatedRecords": Array [], + "relatedRecords": Array [ + Object { + "$type": "app.bsky.actor.profile", + "avatar": Object { + "$type": "blob", + "mimeType": "image/jpeg", + "ref": Object { + "$link": "cids(1)", + }, + "size": 3976, + }, + "description": "its me!", + "displayName": "ali", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label-a", + }, + Object { + "val": "self-label-b", + }, + ], + }, + }, + ], }, "uri": "record(0)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label", + }, + ], + }, "text": "hey there", }, }, @@ -76,7 +109,7 @@ Object { ], "subject": Object { "$type": "com.atproto.repo.strongRef", - "cid": "cids(0)", + "cid": "cids(1)", "uri": "record(0)", }, }, @@ -107,7 +140,32 @@ Object { "indexedAt": "1970-01-01T00:00:00.000Z", "invitesDisabled": false, "moderation": Object {}, - "relatedRecords": Array [], + "relatedRecords": Array [ + Object { + "$type": "app.bsky.actor.profile", + "avatar": Object { + "$type": "blob", + "mimeType": "image/jpeg", + "ref": Object { + "$link": "cids(0)", + }, + "size": 3976, + }, + "description": "its me!", + "displayName": "ali", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label-a", + }, + Object { + "val": "self-label-b", + }, + ], + }, + }, + ], }, "subjectBlobs": Array [], } diff --git a/packages/pds/tests/admin/__snapshots__/get-moderation-report.test.ts.snap b/packages/pds/tests/admin/__snapshots__/get-moderation-report.test.ts.snap index 4ef1f8e812b..70e829d0ab0 100644 --- a/packages/pds/tests/admin/__snapshots__/get-moderation-report.test.ts.snap +++ b/packages/pds/tests/admin/__snapshots__/get-moderation-report.test.ts.snap @@ -64,12 +64,45 @@ Object { "indexedAt": "1970-01-01T00:00:00.000Z", "invitesDisabled": false, "moderation": Object {}, - "relatedRecords": Array [], + "relatedRecords": Array [ + Object { + "$type": "app.bsky.actor.profile", + "avatar": Object { + "$type": "blob", + "mimeType": "image/jpeg", + "ref": Object { + "$link": "cids(1)", + }, + "size": 3976, + }, + "description": "its me!", + "displayName": "ali", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label-a", + }, + Object { + "val": "self-label-b", + }, + ], + }, + }, + ], }, "uri": "record(0)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label", + }, + ], + }, "text": "hey there", }, }, @@ -113,7 +146,32 @@ Object { "indexedAt": "1970-01-01T00:00:00.000Z", "invitesDisabled": false, "moderation": Object {}, - "relatedRecords": Array [], + "relatedRecords": Array [ + Object { + "$type": "app.bsky.actor.profile", + "avatar": Object { + "$type": "blob", + "mimeType": "image/jpeg", + "ref": Object { + "$link": "cids(0)", + }, + "size": 3976, + }, + "description": "its me!", + "displayName": "ali", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label-a", + }, + Object { + "val": "self-label-b", + }, + ], + }, + }, + ], }, } `; diff --git a/packages/pds/tests/admin/__snapshots__/get-moderation-reports.test.ts.snap b/packages/pds/tests/admin/__snapshots__/get-moderation-reports.test.ts.snap index c9e984bfb1a..9cfb5ae3c34 100644 --- a/packages/pds/tests/admin/__snapshots__/get-moderation-reports.test.ts.snap +++ b/packages/pds/tests/admin/__snapshots__/get-moderation-reports.test.ts.snap @@ -1,5 +1,45 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`pds admin get moderation reports view gets all moderation reports actioned by a certain moderator. 1`] = ` +Array [ + Object { + "createdAt": "1970-01-01T00:00:00.000Z", + "id": 1, + "reasonType": "com.atproto.moderation.defs#reasonOther", + "reportedBy": "user(0)", + "resolvedByActionIds": Array [ + 2, + ], + "subject": Object { + "$type": "com.atproto.repo.strongRef", + "cid": "cids(0)", + "uri": "record(0)", + }, + "subjectRepoHandle": "alice.test", + }, +] +`; + +exports[`pds admin get moderation reports view gets all moderation reports actioned by a certain moderator. 2`] = ` +Array [ + Object { + "createdAt": "1970-01-01T00:00:00.000Z", + "id": 3, + "reasonType": "com.atproto.moderation.defs#reasonOther", + "reportedBy": "user(0)", + "resolvedByActionIds": Array [ + 4, + ], + "subject": Object { + "$type": "com.atproto.repo.strongRef", + "cid": "cids(0)", + "uri": "record(0)", + }, + "subjectRepoHandle": "bob.test", + }, +] +`; + exports[`pds admin get moderation reports view gets all moderation reports by active resolution action type. 1`] = ` Array [ Object { diff --git a/packages/pds/tests/admin/__snapshots__/get-record.test.ts.snap b/packages/pds/tests/admin/__snapshots__/get-record.test.ts.snap index 31a9de9a436..00fbc5bda1c 100644 --- a/packages/pds/tests/admin/__snapshots__/get-record.test.ts.snap +++ b/packages/pds/tests/admin/__snapshots__/get-record.test.ts.snap @@ -83,12 +83,45 @@ Object { "indexedAt": "1970-01-01T00:00:00.000Z", "invitesDisabled": false, "moderation": Object {}, - "relatedRecords": Array [], + "relatedRecords": Array [ + Object { + "$type": "app.bsky.actor.profile", + "avatar": Object { + "$type": "blob", + "mimeType": "image/jpeg", + "ref": Object { + "$link": "cids(1)", + }, + "size": 3976, + }, + "description": "its me!", + "displayName": "ali", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label-a", + }, + Object { + "val": "self-label-b", + }, + ], + }, + }, + ], }, "uri": "record(0)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label", + }, + ], + }, "text": "hey there", }, } @@ -177,12 +210,45 @@ Object { "indexedAt": "1970-01-01T00:00:00.000Z", "invitesDisabled": false, "moderation": Object {}, - "relatedRecords": Array [], + "relatedRecords": Array [ + Object { + "$type": "app.bsky.actor.profile", + "avatar": Object { + "$type": "blob", + "mimeType": "image/jpeg", + "ref": Object { + "$link": "cids(1)", + }, + "size": 3976, + }, + "description": "its me!", + "displayName": "ali", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label-a", + }, + Object { + "val": "self-label-b", + }, + ], + }, + }, + ], }, "uri": "record(0)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label", + }, + ], + }, "text": "hey there", }, } diff --git a/packages/pds/tests/admin/__snapshots__/get-repo.test.ts.snap b/packages/pds/tests/admin/__snapshots__/get-repo.test.ts.snap index bce55507d24..c90b1a070b2 100644 --- a/packages/pds/tests/admin/__snapshots__/get-repo.test.ts.snap +++ b/packages/pds/tests/admin/__snapshots__/get-repo.test.ts.snap @@ -72,6 +72,31 @@ Object { }, ], }, - "relatedRecords": Array [], + "relatedRecords": Array [ + Object { + "$type": "app.bsky.actor.profile", + "avatar": Object { + "$type": "blob", + "mimeType": "image/jpeg", + "ref": Object { + "$link": "cids(0)", + }, + "size": 3976, + }, + "description": "its me!", + "displayName": "ali", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label-a", + }, + Object { + "val": "self-label-b", + }, + ], + }, + }, + ], } `; diff --git a/packages/pds/tests/admin/get-moderation-reports.test.ts b/packages/pds/tests/admin/get-moderation-reports.test.ts index b6b083d0b17..20f1c97f781 100644 --- a/packages/pds/tests/admin/get-moderation-reports.test.ts +++ b/packages/pds/tests/admin/get-moderation-reports.test.ts @@ -85,6 +85,7 @@ describe('pds admin get moderation reports view', () => { uri: report.subject.uri, cid: report.subject.cid, }, + createdBy: `did:example:admin${i}`, }) if (ab) { await sc.resolveReports({ @@ -136,15 +137,40 @@ describe('pds admin get moderation reports view', () => { const ignoreSubjects = getDids(allReports).slice(0, 2) - const filteredReports = + const filteredReportsByDid = await agent.api.com.atproto.admin.getModerationReports( { ignoreSubjects }, { headers: { authorization: adminAuth() } }, ) - getDids(filteredReports).forEach((resultDid) => + // Validate that when ignored by DID, all reports for that DID is ignored + getDids(filteredReportsByDid).forEach((resultDid) => expect(ignoreSubjects).not.toContain(resultDid), ) + + const ignoredAtUriSubjects: string[] = [ + `${ + allReports.data.reports.find(({ subject }) => !!subject.uri)?.subject + ?.uri + }`, + ] + const filteredReportsByAtUri = + await agent.api.com.atproto.admin.getModerationReports( + { + ignoreSubjects: ignoredAtUriSubjects, + }, + { headers: { authorization: adminAuth() } }, + ) + + // Validate that when ignored by at uri, only the reports for that at uri is ignored + expect(filteredReportsByAtUri.data.reports.length).toEqual( + allReports.data.reports.length - 1, + ) + expect( + filteredReportsByAtUri.data.reports + .map(({ subject }) => subject.uri) + .filter(Boolean), + ).not.toContain(ignoredAtUriSubjects[0]) }) it('gets all moderation reports.', async () => { @@ -216,6 +242,40 @@ describe('pds admin get moderation reports view', () => { expect(forSnapshot(reportsWithTakedown.data.reports)).toMatchSnapshot() }) + it('gets all moderation reports actioned by a certain moderator.', async () => { + const adminDidOne = 'did:example:admin0' + const adminDidTwo = 'did:example:admin2' + const [actionedByAdminOne, actionedByAdminTwo] = await Promise.all([ + agent.api.com.atproto.admin.getModerationReports( + { actionedBy: adminDidOne }, + { headers: { authorization: adminAuth() } }, + ), + agent.api.com.atproto.admin.getModerationReports( + { actionedBy: adminDidTwo }, + { headers: { authorization: adminAuth() } }, + ), + ]) + const [fullReportOne, fullReportTwo] = await Promise.all([ + agent.api.com.atproto.admin.getModerationReport( + { id: actionedByAdminOne.data.reports[0].id }, + { headers: { authorization: adminAuth() } }, + ), + agent.api.com.atproto.admin.getModerationReport( + { id: actionedByAdminTwo.data.reports[0].id }, + { headers: { authorization: adminAuth() } }, + ), + ]) + + expect(forSnapshot(actionedByAdminOne.data.reports)).toMatchSnapshot() + expect(fullReportOne.data.resolvedByActions[0].createdBy).toEqual( + adminDidOne, + ) + expect(forSnapshot(actionedByAdminTwo.data.reports)).toMatchSnapshot() + expect(fullReportTwo.data.resolvedByActions[0].createdBy).toEqual( + adminDidTwo, + ) + }) + it('paginates.', async () => { const results = (results) => results.flatMap((res) => res.reports) const paginator = async (cursor?: string) => { diff --git a/packages/pds/tests/admin/get-record.test.ts b/packages/pds/tests/admin/get-record.test.ts index b40391eb28e..6c38419612e 100644 --- a/packages/pds/tests/admin/get-record.test.ts +++ b/packages/pds/tests/admin/get-record.test.ts @@ -1,5 +1,5 @@ import AtpAgent from '@atproto/api' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { ACKNOWLEDGE, TAKEDOWN, diff --git a/packages/pds/tests/admin/get-repo.test.ts b/packages/pds/tests/admin/get-repo.test.ts index 5ffd9a3be32..9cd38ae101f 100644 --- a/packages/pds/tests/admin/get-repo.test.ts +++ b/packages/pds/tests/admin/get-repo.test.ts @@ -13,6 +13,8 @@ import { CloseFn, adminAuth, TestServerInfo, + moderatorAuth, + triageAuth, } from '../_util' import { SeedClient } from '../seeds/client' import basicSeed from '../seeds/basic' @@ -80,6 +82,25 @@ describe('pds admin get repo view', () => { expect(forSnapshot(result.data)).toMatchSnapshot() }) + it('does not include account emails for triage mods.', async () => { + const { data: admin } = await agent.api.com.atproto.admin.getRepo( + { did: sc.dids.bob }, + { headers: { authorization: adminAuth() } }, + ) + const { data: moderator } = await agent.api.com.atproto.admin.getRepo( + { did: sc.dids.bob }, + { headers: { authorization: moderatorAuth() } }, + ) + const { data: triage } = await agent.api.com.atproto.admin.getRepo( + { did: sc.dids.bob }, + { headers: { authorization: triageAuth() } }, + ) + expect(admin.email).toEqual('bob@test.com') + expect(moderator.email).toEqual('bob@test.com') + expect(triage.email).toBeUndefined() + expect(triage).toEqual({ ...admin, email: undefined }) + }) + it('fails when repo does not exist.', async () => { const promise = agent.api.com.atproto.admin.getRepo( { did: 'did:plc:doesnotexist' }, diff --git a/packages/pds/tests/admin/invites.test.ts b/packages/pds/tests/admin/invites.test.ts index 6a598666038..a64c473783a 100644 --- a/packages/pds/tests/admin/invites.test.ts +++ b/packages/pds/tests/admin/invites.test.ts @@ -203,7 +203,7 @@ describe('pds admin invite views', () => { }, ) await expect(attemptDisableInvites).rejects.toThrow( - 'Authentication Required', + 'Insufficient privileges', ) }) @@ -215,12 +215,13 @@ describe('pds admin invite views', () => { headers: { authorization: moderatorAuth() }, }, ) - await expect(attemptCreateInvite).rejects.toThrow('Authentication Required') + await expect(attemptCreateInvite).rejects.toThrow('Insufficient privileges') }) it('disables an account from getting additional invite codes', async () => { + const reasonForDisabling = 'User is selling invites' await agent.api.com.atproto.admin.disableAccountInvites( - { account: carol }, + { account: carol, note: reasonForDisabling }, { encoding: 'application/json', headers: { authorization: adminAuth() } }, ) @@ -229,6 +230,7 @@ describe('pds admin invite views', () => { { headers: { authorization: adminAuth() } }, ) expect(repoRes.data.invitesDisabled).toBe(true) + expect(repoRes.data.inviteNote).toBe(reasonForDisabling) const invRes = await agent.api.com.atproto.server.getAccountInviteCodes( {}, @@ -237,6 +239,34 @@ describe('pds admin invite views', () => { expect(invRes.data.codes.length).toBe(0) }) + it('allows setting reason when enabling and disabling invite codes', async () => { + const reasonForEnabling = 'User is confirmed they will play nice' + const reasonForDisabling = 'User is selling invites' + await agent.api.com.atproto.admin.enableAccountInvites( + { account: carol, note: reasonForEnabling }, + { encoding: 'application/json', headers: { authorization: adminAuth() } }, + ) + + const afterEnable = await agent.api.com.atproto.admin.getRepo( + { did: carol }, + { headers: { authorization: adminAuth() } }, + ) + expect(afterEnable.data.invitesDisabled).toBe(false) + expect(afterEnable.data.inviteNote).toBe(reasonForEnabling) + + await agent.api.com.atproto.admin.disableAccountInvites( + { account: carol, note: reasonForDisabling }, + { encoding: 'application/json', headers: { authorization: adminAuth() } }, + ) + + const afterDisable = await agent.api.com.atproto.admin.getRepo( + { did: carol }, + { headers: { authorization: adminAuth() } }, + ) + expect(afterDisable.data.invitesDisabled).toBe(true) + expect(afterDisable.data.inviteNote).toBe(reasonForDisabling) + }) + it('creates codes in the background but disables them', async () => { const res = await server.ctx.db.db .selectFrom('invite_code') @@ -255,7 +285,7 @@ describe('pds admin invite views', () => { headers: { authorization: moderatorAuth() }, }, ) - await expect(attempt).rejects.toThrow('Authentication Required') + await expect(attempt).rejects.toThrow('Insufficient privileges') }) it('re-enables an accounts invites', async () => { @@ -285,6 +315,6 @@ describe('pds admin invite views', () => { headers: { authorization: moderatorAuth() }, }, ) - await expect(attempt).rejects.toThrow('Authentication Required') + await expect(attempt).rejects.toThrow('Insufficient privileges') }) }) diff --git a/packages/pds/tests/admin/moderation.test.ts b/packages/pds/tests/admin/moderation.test.ts index 92bd30c199f..4b5dac66352 100644 --- a/packages/pds/tests/admin/moderation.test.ts +++ b/packages/pds/tests/admin/moderation.test.ts @@ -1,5 +1,6 @@ import AtpAgent from '@atproto/api' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' +import { BlobNotFoundError } from '@atproto/repo' import { adminAuth, CloseFn, @@ -7,11 +8,14 @@ import { moderatorAuth, runTestServer, TestServerInfo, + triageAuth, } from '../_util' +import { PeriodicModerationActionReversal } from '../../src/db/periodic-moderation-action-reversal' import { ImageRef, RecordRef, SeedClient } from '../seeds/client' import basicSeed from '../seeds/basic' import { ACKNOWLEDGE, + ESCALATE, FLAG, TAKEDOWN, } from '../../src/lexicon/types/com/atproto/admin/defs' @@ -19,7 +23,6 @@ import { REASONOTHER, REASONSPAM, } from '../../src/lexicon/types/com/atproto/moderation/defs' -import { BlobNotFoundError } from '@atproto/repo' describe('moderation', () => { let server: TestServerInfo @@ -292,7 +295,7 @@ describe('moderation', () => { password: 'password', token: deletionToken, }) - await server.ctx.backgroundQueue.processAll() + await server.processAll() // Take action on deleted content const { data: action } = await agent.api.com.atproto.admin.takeModerationAction( @@ -493,13 +496,13 @@ describe('moderation', () => { ) }) - it('supports flagging and acknowledging.', async () => { + it('supports escalating and acknowledging for triage.', async () => { const postRef1 = sc.posts[sc.dids.alice][0].ref const postRef2 = sc.posts[sc.dids.bob][0].ref const { data: action1 } = await agent.api.com.atproto.admin.takeModerationAction( { - action: FLAG, + action: ESCALATE, subject: { $type: 'com.atproto.repo.strongRef', uri: postRef1.uri.toString(), @@ -510,12 +513,12 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: { authorization: moderatorAuth() }, // As moderator + headers: { authorization: triageAuth() }, }, ) expect(action1).toEqual( expect.objectContaining({ - action: FLAG, + action: ESCALATE, subject: { $type: 'com.atproto.repo.strongRef', uri: postRef1.uriStr, @@ -537,7 +540,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: { authorization: adminAuth() }, + headers: { authorization: triageAuth() }, }, ) expect(action2).toEqual( @@ -559,7 +562,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: { authorization: adminAuth() }, + headers: { authorization: triageAuth() }, }, ) await agent.api.com.atproto.admin.reverseModerationAction( @@ -570,7 +573,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: { authorization: adminAuth() }, + headers: { authorization: triageAuth() }, }, ) }) @@ -824,24 +827,95 @@ describe('moderation', () => { ) }) - it('does not allow non-admin moderators to takedown.', async () => { - const attemptTakedown = agent.api.com.atproto.admin.takeModerationAction( + it('allows full moderators to takedown.', async () => { + const { data: action } = + await agent.api.com.atproto.admin.takeModerationAction( + { + action: TAKEDOWN, + createdBy: 'did:example:moderator', + reason: 'Y', + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: sc.dids.bob, + }, + }, + { + encoding: 'application/json', + headers: { authorization: moderatorAuth() }, + }, + ) + // cleanup + await agent.api.com.atproto.admin.reverseModerationAction( { - action: TAKEDOWN, - createdBy: 'did:example:moderator', + id: action.id, + createdBy: 'did:example:admin', reason: 'Y', - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: sc.dids.bob, - }, }, { encoding: 'application/json', - headers: { authorization: moderatorAuth() }, + headers: { authorization: adminAuth() }, }, ) - await expect(attemptTakedown).rejects.toThrow( - 'Must be an admin to takedown or label content', + }) + + it('automatically reverses actions marked with duration', async () => { + const { data: action } = + await agent.api.com.atproto.admin.takeModerationAction( + { + action: TAKEDOWN, + createdBy: 'did:example:moderator', + reason: 'Y', + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: sc.dids.bob, + }, + // Use negative value to set the expiry time in the past so that the action is automatically reversed + // right away without having to wait n number of hours for a successful assertion + durationInHours: -1, + }, + { + encoding: 'application/json', + headers: { authorization: moderatorAuth() }, + }, + ) + + // In the actual app, this will be instantiated and run on server startup + const periodicReversal = new PeriodicModerationActionReversal(server.ctx) + await periodicReversal.findAndRevertDueActions() + + const { data: reversedAction } = + await agent.api.com.atproto.admin.getModerationAction( + { id: action.id }, + { headers: { authorization: adminAuth() } }, + ) + + // Verify that the automatic reversal is attributed to the original moderator of the temporary action + // and that the reason is set to indicate that the action was automatically reversed. + expect(reversedAction.reversal).toMatchObject({ + createdBy: action.createdBy, + reason: '[SCHEDULED_REVERSAL] Reverting action as originally scheduled', + }) + }) + + it('does not allow non-full moderators to takedown.', async () => { + const attemptTakedownTriage = + agent.api.com.atproto.admin.takeModerationAction( + { + action: TAKEDOWN, + createdBy: 'did:example:moderator', + reason: 'Y', + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: sc.dids.bob, + }, + }, + { + encoding: 'application/json', + headers: { authorization: triageAuth() }, + }, + ) + await expect(attemptTakedownTriage).rejects.toThrow( + 'Must be a full moderator to perform an account takedown', ) }) }) diff --git a/packages/pds/tests/blob-deletes.test.ts b/packages/pds/tests/blob-deletes.test.ts index a2effc21ea8..8ec97139d63 100644 --- a/packages/pds/tests/blob-deletes.test.ts +++ b/packages/pds/tests/blob-deletes.test.ts @@ -58,7 +58,7 @@ describe('blob deletes', () => { ) const post = await sc.post(alice, 'test', undefined, [img]) await sc.deletePost(alice, post.ref.uri) - await server.ctx.backgroundQueue.processAll() + await server.processAll() const dbBlobs = await getDbBlobsForDid(alice) expect(dbBlobs.length).toBe(0) @@ -80,7 +80,7 @@ describe('blob deletes', () => { ) await updateProfile(sc, alice, img.image, img.image) await updateProfile(sc, alice, img2.image, img2.image) - await server.ctx.backgroundQueue.processAll() + await server.processAll() const dbBlobs = await getDbBlobsForDid(alice) expect(dbBlobs.length).toBe(1) @@ -109,7 +109,7 @@ describe('blob deletes', () => { ) await updateProfile(sc, alice, img.image, img.image) await updateProfile(sc, alice, img.image, img2.image) - await server.ctx.backgroundQueue.processAll() + await server.processAll() const dbBlobs = await getDbBlobsForDid(alice) expect(dbBlobs.length).toBe(2) @@ -160,7 +160,7 @@ describe('blob deletes', () => { }, { encoding: 'application/json', headers: sc.getHeaders(alice) }, ) - await server.ctx.backgroundQueue.processAll() + await server.processAll() const dbBlobs = await getDbBlobsForDid(alice) expect(dbBlobs.length).toBe(1) diff --git a/packages/pds/tests/crud.test.ts b/packages/pds/tests/crud.test.ts index af4aedb81c0..00dc3a623fa 100644 --- a/packages/pds/tests/crud.test.ts +++ b/packages/pds/tests/crud.test.ts @@ -1,11 +1,11 @@ import fs from 'fs/promises' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import AtpAgent from '@atproto/api' import * as createRecord from '@atproto/api/src/client/types/com/atproto/repo/createRecord' import * as putRecord from '@atproto/api/src/client/types/com/atproto/repo/putRecord' import * as deleteRecord from '@atproto/api/src/client/types/com/atproto/repo/deleteRecord' import * as applyWrites from '@atproto/api/src/client/types/com/atproto/repo/applyWrites' -import { cidForCbor, TID } from '@atproto/common' +import { cidForCbor, TID, ui8ToArrayBuffer } from '@atproto/common' import { BlobNotFoundError } from '@atproto/repo' import { defaultFetchHandler } from '@atproto/xrpc' import * as Post from '../src/lexicon/types/app/bsky/feed/post' @@ -375,6 +375,35 @@ describe('crud operations', () => { }) await expect(attemptDelete).resolves.toBeDefined() }) + + it('does not delete the underlying block if it is referenced elsewhere', async () => { + const { repo } = aliceAgent.api.com.atproto + const record = { text: 'post', createdAt: new Date().toISOString() } + const { data: post1 } = await repo.createRecord({ + repo: alice.did, + collection: ids.AppBskyFeedPost, + record, + }) + const { data: post2 } = await repo.createRecord({ + repo: alice.did, + collection: ids.AppBskyFeedPost, + record, + }) + const uri1 = new AtUri(post1.uri) + await repo.deleteRecord({ + repo: uri1.host, + collection: uri1.collection, + rkey: uri1.rkey, + }) + const uri2 = new AtUri(post2.uri) + const checkPost2 = await repo.getRecord({ + repo: uri2.host, + collection: uri2.collection, + rkey: uri2.rkey, + }) + expect(checkPost2).toBeDefined() + expect(checkPost2.data.value).toMatchObject(record) + }) }) describe('putRecord', () => { @@ -430,9 +459,9 @@ describe('crud operations', () => { }) }) - it('temporarily only puts profile records', async () => { + it('temporarily only allows updates to profile', async () => { const { repo } = bobAgent.api.com.atproto - const put = repo.putRecord({ + const put = await repo.putRecord({ repo: bob.did, collection: ids.AppBskyGraphFollow, rkey: TID.nextStr(), @@ -441,8 +470,18 @@ describe('crud operations', () => { createdAt: new Date().toISOString(), }, }) - await expect(put).rejects.toThrow( - 'Temporarily only accepting puts for collections: app.bsky.actor.profile, app.bsky.graph.list', + const edit = repo.putRecord({ + repo: bob.did, + collection: ids.AppBskyGraphFollow, + rkey: new AtUri(put.data.uri).rkey, + record: { + subject: bob.did, + createdAt: new Date().toISOString(), + }, + }) + + await expect(edit).rejects.toThrow( + 'Temporarily only accepting updates for collections: app.bsky.actor.profile, app.bsky.graph.list, app.bsky.feed.generator', ) }) @@ -553,11 +592,11 @@ describe('crud operations', () => { it('createRecord succeeds on proper commit cas', async () => { const { repo, sync } = aliceAgent.api.com.atproto - const { data: head } = await sync.getHead({ did: alice.did }) + const { data: commit } = await sync.getLatestCommit({ did: alice.did }) const { data: post } = await repo.createRecord({ repo: alice.did, collection: ids.AppBskyFeedPost, - swapCommit: head.root, + swapCommit: commit.cid, record: postRecord(), }) const uri = new AtUri(post.uri) @@ -571,7 +610,9 @@ describe('crud operations', () => { it('createRecord fails on bad commit cas', async () => { const { repo, sync } = aliceAgent.api.com.atproto - const { data: staleHead } = await sync.getHead({ did: alice.did }) + const { data: staleCommit } = await sync.getLatestCommit({ + did: alice.did, + }) // Update repo, change head await repo.createRecord({ repo: alice.did, @@ -581,7 +622,7 @@ describe('crud operations', () => { const attemptCreate = repo.createRecord({ repo: alice.did, collection: ids.AppBskyFeedPost, - swapCommit: staleHead.root, + swapCommit: staleCommit.cid, record: postRecord(), }) await expect(attemptCreate).rejects.toThrow(createRecord.InvalidSwapError) @@ -594,13 +635,13 @@ describe('crud operations', () => { collection: ids.AppBskyFeedPost, record: postRecord(), }) - const { data: head } = await sync.getHead({ did: alice.did }) + const { data: commit } = await sync.getLatestCommit({ did: alice.did }) const uri = new AtUri(post.uri) await repo.deleteRecord({ repo: uri.host, collection: uri.collection, rkey: uri.rkey, - swapCommit: head.root, + swapCommit: commit.cid, }) const checkPost = repo.getRecord({ repo: uri.host, @@ -612,20 +653,22 @@ describe('crud operations', () => { it('deleteRecord fails on bad commit cas', async () => { const { repo, sync } = aliceAgent.api.com.atproto - const { data: staleHead } = await sync.getHead({ did: alice.did }) + const { data: staleCommit } = await sync.getLatestCommit({ + did: alice.did, + }) const { data: post } = await repo.createRecord({ repo: alice.did, collection: ids.AppBskyFeedPost, record: postRecord(), }) const uri = new AtUri(post.uri) - const attempDelete = repo.deleteRecord({ + const attemptDelete = repo.deleteRecord({ repo: uri.host, collection: uri.collection, rkey: uri.rkey, - swapCommit: staleHead.root, + swapCommit: staleCommit.cid, }) - await expect(attempDelete).rejects.toThrow(deleteRecord.InvalidSwapError) + await expect(attemptDelete).rejects.toThrow(deleteRecord.InvalidSwapError) const checkPost = repo.getRecord({ repo: uri.host, collection: uri.collection, @@ -664,13 +707,13 @@ describe('crud operations', () => { record: postRecord(), }) const uri = new AtUri(post.uri) - const attempDelete = repo.deleteRecord({ + const attemptDelete = repo.deleteRecord({ repo: uri.host, collection: uri.collection, rkey: uri.rkey, swapRecord: (await cidForCbor({})).toString(), }) - await expect(attempDelete).rejects.toThrow(deleteRecord.InvalidSwapError) + await expect(attemptDelete).rejects.toThrow(deleteRecord.InvalidSwapError) const checkPost = repo.getRecord({ repo: uri.host, collection: uri.collection, @@ -681,12 +724,12 @@ describe('crud operations', () => { it('putRecord succeeds on proper commit cas', async () => { const { repo, sync } = aliceAgent.api.com.atproto - const { data: head } = await sync.getHead({ did: alice.did }) + const { data: commit } = await sync.getLatestCommit({ did: alice.did }) const { data: profile } = await repo.putRecord({ repo: alice.did, collection: ids.AppBskyActorProfile, rkey: 'self', - swapCommit: head.root, + swapCommit: commit.cid, record: profileRecord(), }) const { data: checkProfile } = await repo.getRecord({ @@ -699,7 +742,9 @@ describe('crud operations', () => { it('putRecord fails on bad commit cas', async () => { const { repo, sync } = aliceAgent.api.com.atproto - const { data: staleHead } = await sync.getHead({ did: alice.did }) + const { data: staleCommit } = await sync.getLatestCommit({ + did: alice.did, + }) // Update repo, change head await repo.createRecord({ repo: alice.did, @@ -710,7 +755,7 @@ describe('crud operations', () => { repo: alice.did, collection: ids.AppBskyActorProfile, rkey: 'self', - swapCommit: staleHead.root, + swapCommit: staleCommit.cid, record: profileRecord(), }) await expect(attemptPut).rejects.toThrow(putRecord.InvalidSwapError) @@ -778,33 +823,26 @@ describe('crud operations', () => { it('applyWrites succeeds on proper commit cas', async () => { const { repo, sync } = aliceAgent.api.com.atproto - const { data: head } = await sync.getHead({ did: alice.did }) - const rkey = TID.nextStr() + const { data: commit } = await sync.getLatestCommit({ did: alice.did }) await repo.applyWrites({ repo: alice.did, - swapCommit: head.root, + swapCommit: commit.cid, writes: [ { $type: `${ids.ComAtprotoRepoApplyWrites}#create`, action: 'create', collection: ids.AppBskyFeedPost, - rkey, value: { $type: ids.AppBskyFeedPost, ...postRecord() }, }, ], }) - const uri = AtUri.make(alice.did, ids.AppBskyFeedPost, rkey) - const checkPost = repo.getRecord({ - repo: uri.host, - collection: uri.collection, - rkey: uri.rkey, - }) - await expect(checkPost).resolves.toBeDefined() }) it('applyWrites fails on bad commit cas', async () => { const { repo, sync } = aliceAgent.api.com.atproto - const { data: staleHead } = await sync.getHead({ did: alice.did }) + const { data: staleCommit } = await sync.getLatestCommit({ + did: alice.did, + }) // Update repo, change head await repo.createRecord({ repo: alice.did, @@ -813,13 +851,12 @@ describe('crud operations', () => { }) const attemptApplyWrite = repo.applyWrites({ repo: alice.did, - swapCommit: staleHead.root, + swapCommit: staleCommit.cid, writes: [ { $type: `${ids.ComAtprotoRepoApplyWrites}#create`, action: 'create', collection: ids.AppBskyFeedPost, - rkey: TID.nextStr(), value: { $type: ids.AppBskyFeedPost, ...postRecord() }, }, ], @@ -831,7 +868,7 @@ describe('crud operations', () => { it("writes fail on values that can't reliably transform between cbor to lex", async () => { const passthroughBody = (data: unknown) => - typedArrayToBuffer(new TextEncoder().encode(JSON.stringify(data))) + ui8ToArrayBuffer(new TextEncoder().encode(JSON.stringify(data))) const result = await defaultFetchHandler( aliceAgent.service.origin + `/xrpc/com.atproto.repo.createRecord`, 'post', @@ -1158,6 +1195,46 @@ describe('crud operations', () => { }, ) }) + + it("doesn't serve taken-down actor", async () => { + const posts = await agent.api.app.bsky.feed.post.list({ repo: alice.did }) + expect(posts.records.length).toBeGreaterThan(0) + + const { data: action } = + await agent.api.com.atproto.admin.takeModerationAction( + { + action: TAKEDOWN, + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: alice.did, + }, + createdBy: 'did:example:admin', + reason: 'Y', + }, + { + encoding: 'application/json', + headers: { authorization: adminAuth() }, + }, + ) + + const tryListPosts = agent.api.app.bsky.feed.post.list({ + repo: alice.did, + }) + await expect(tryListPosts).rejects.toThrow(/Could not find repo/) + + // Cleanup + await agent.api.com.atproto.admin.reverseModerationAction( + { + id: action.id, + createdBy: 'did:example:admin', + reason: 'Y', + }, + { + encoding: 'application/json', + headers: { authorization: adminAuth() }, + }, + ) + }) }) function createDeepObject(depth: number) { @@ -1169,10 +1246,3 @@ function createDeepObject(depth: number) { } return obj } - -function typedArrayToBuffer(array: Uint8Array): ArrayBuffer { - return array.buffer.slice( - array.byteOffset, - array.byteLength + array.byteOffset, - ) -} diff --git a/packages/pds/tests/db.test.ts b/packages/pds/tests/db.test.ts index eb200f4d489..6e4192cfac8 100644 --- a/packages/pds/tests/db.test.ts +++ b/packages/pds/tests/db.test.ts @@ -1,6 +1,6 @@ import { sql } from 'kysely' import { once } from 'events' -import { wait } from '@atproto/common' +import { createDeferrable, wait } from '@atproto/common' import { Database } from '../src' import { Leader, appMigration } from '../src/db/leader' import { runTestServer, CloseFn } from './_util' @@ -31,6 +31,7 @@ describe('db', () => { .values({ did: 'x', root: 'x', + rev: 'x', indexedAt: 'bad-date', }) .returning('did') @@ -52,6 +53,7 @@ describe('db', () => { expect(row).toEqual({ did: 'x', root: 'x', + rev: 'x', indexedAt: 'bad-date', takedownId: null, }) @@ -130,7 +132,7 @@ describe('db', () => { expect(res.length).toBe(0) }) - it('ensures all inflight querys are rolled back', async () => { + it('ensures all inflight queries are rolled back', async () => { let promise: Promise | undefined = undefined const names: string[] = [] try { @@ -168,6 +170,39 @@ describe('db', () => { }) }) + describe('transaction advisory locks', () => { + it('allows locks in txs to run sequentially', async () => { + if (db.dialect !== 'pg') return + for (let i = 0; i < 100; i++) { + await db.transaction(async (dbTxn) => { + const locked = await dbTxn.takeTxAdvisoryLock('asfd') + expect(locked).toBe(true) + }) + } + }) + + it('locks block between txns', async () => { + if (db.dialect !== 'pg') return + const deferable = createDeferrable() + const tx1 = db.transaction(async (dbTxn) => { + const locked = await dbTxn.takeTxAdvisoryLock('asdf') + expect(locked).toBe(true) + await deferable.complete + }) + // give it just a second to ensure it gets the lock + await wait(10) + const tx2 = db.transaction(async (dbTxn) => { + const locked = await dbTxn.takeTxAdvisoryLock('asdf') + expect(locked).toBe(false) + deferable.resolve() + await tx1 + const locked2 = await dbTxn.takeTxAdvisoryLock('asdf') + expect(locked2).toBe(true) + }) + await tx2 + }) + }) + describe('Leader', () => { it('allows leaders to run sequentially.', async () => { const task = async () => { @@ -178,11 +213,11 @@ describe('db', () => { const leader2 = new Leader(777, db) const leader3 = new Leader(777, db) const result1 = await leader1.run(task) - await wait(1) // Short grace period for pg to close session + await wait(5) // Short grace period for pg to close session const result2 = await leader2.run(task) - await wait(1) + await wait(5) const result3 = await leader3.run(task) - await wait(1) + await wait(5) const result4 = await leader3.run(task) expect([result1, result2, result3, result4]).toEqual([ { ran: true, result: 'complete' }, @@ -193,7 +228,7 @@ describe('db', () => { }) it('only allows one leader at a time.', async () => { - await wait(1) + await wait(5) const task = async () => { await wait(25) return 'complete' @@ -212,7 +247,7 @@ describe('db', () => { }) it('leaders with different ids do not conflict.', async () => { - await wait(1) + await wait(5) const task = async () => { await wait(25) return 'complete' diff --git a/packages/pds/tests/handle-validation.test.ts b/packages/pds/tests/handle-validation.test.ts new file mode 100644 index 00000000000..c39f7db18de --- /dev/null +++ b/packages/pds/tests/handle-validation.test.ts @@ -0,0 +1,28 @@ +import { isValidTld } from '@atproto/syntax' +import { ensureHandleServiceConstraints } from '../src/handle' + +describe('handle validation', () => { + it('validates service constraints', () => { + const domains = ['.bsky.app', '.test'] + const expectThrow = (handle: string, err: string) => { + expect(() => ensureHandleServiceConstraints(handle, domains)).toThrow(err) + } + expectThrow('john.bsky.io', 'Invalid characters in handle') + expectThrow('john.com', 'Invalid characters in handle') + expectThrow('j.test', 'Handle too short') + expectThrow('uk.test', 'Handle too short') + expectThrow('john.test.bsky.app', 'Invalid characters in handle') + expectThrow('about.test', 'Reserved handle') + expectThrow('atp.test', 'Reserved handle') + expectThrow('barackobama.test', 'Reserved handle') + }) + + it('handles bad tlds', () => { + expect(isValidTld('atproto.local')).toBe(false) + expect(isValidTld('atproto.arpa')).toBe(false) + expect(isValidTld('atproto.invalid')).toBe(false) + expect(isValidTld('atproto.localhost')).toBe(false) + expect(isValidTld('atproto.onion')).toBe(false) + expect(isValidTld('atproto.internal')).toBe(false) + }) +}) diff --git a/packages/pds/tests/handles.test.ts b/packages/pds/tests/handles.test.ts index 57976f2d9dd..1461b1b4f10 100644 --- a/packages/pds/tests/handles.test.ts +++ b/packages/pds/tests/handles.test.ts @@ -4,7 +4,6 @@ import { SeedClient } from './seeds/client' import basicSeed from './seeds/basic' import * as util from './_util' import { AppContext } from '../src' -import { moderatorAuth } from './_util' // outside of suite so they can be used in mock let alice: string @@ -111,6 +110,13 @@ describe('handles', () => { await expect(attempt).rejects.toThrow('Handle already taken: bob.test') }) + it('handle updates are idempotent', async () => { + await agent.api.com.atproto.identity.updateHandle( + { handle: 'Bob.test' }, + { headers: sc.getHeaders(bob), encoding: 'application/json' }, + ) + }) + it('if handle update fails, it does not update their did document', async () => { const data = await idResolver.did.resolveAtprotoData(alice) expect(data.handle).toBe(newHandle) @@ -231,20 +237,6 @@ describe('handles', () => { expect(handle).toBe('dril.test') }) - it('disallows setting handle to an off-service domain', async () => { - const attempt = agent.api.com.atproto.admin.updateAccountHandle( - { - did: bob, - handle: 'bob.external', - }, - { - headers: { authorization: util.adminAuth() }, - encoding: 'application/json', - }, - ) - await expect(attempt).rejects.toThrow('Unsupported domain') - }) - it('requires admin auth', async () => { const attempt = agent.api.com.atproto.admin.updateAccountHandle( { @@ -268,10 +260,21 @@ describe('handles', () => { handle: 'bob-alt.test', }, { - headers: { authorization: moderatorAuth() }, + headers: { authorization: util.moderatorAuth() }, + encoding: 'application/json', + }, + ) + await expect(attempt3).rejects.toThrow('Insufficient privileges') + const attempt4 = agent.api.com.atproto.admin.updateAccountHandle( + { + did: bob, + handle: 'bob-alt.test', + }, + { + headers: { authorization: util.triageAuth() }, encoding: 'application/json', }, ) - await expect(attempt3).rejects.toThrow('Authentication Required') + await expect(attempt4).rejects.toThrow('Insufficient privileges') }) }) diff --git a/packages/pds/tests/invite-codes.test.ts b/packages/pds/tests/invite-codes.test.ts index c67459134c4..31892cfeedd 100644 --- a/packages/pds/tests/invite-codes.test.ts +++ b/packages/pds/tests/invite-codes.test.ts @@ -4,6 +4,7 @@ import { AppContext } from '../src' import * as util from './_util' import { DAY } from '@atproto/common' import { genInvCodes } from '../src/api/com/atproto/server/util' +import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/defs' describe('account', () => { let serverUrl: string @@ -45,6 +46,49 @@ describe('account', () => { ) }) + it('fails on invite code from takendown account', async () => { + const account = await makeLoggedInAccount(agent) + // assign an invite code to the user + const code = await createInviteCode(agent, 1, account.did) + // takedown the user's account + const { data: takedownAction } = + await agent.api.com.atproto.admin.takeModerationAction( + { + action: TAKEDOWN, + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: account.did, + }, + createdBy: 'did:example:admin', + reason: 'Y', + }, + { + encoding: 'application/json', + headers: { authorization: util.adminAuth() }, + }, + ) + // attempt to create account with the previously generated invite code + const promise = createAccountWithInvite(agent, code) + await expect(promise).rejects.toThrow( + ComAtprotoServerCreateAccount.InvalidInviteCodeError, + ) + + // double check that reversing the takedown action makes the invite code valid again + await agent.api.com.atproto.admin.reverseModerationAction( + { + id: takedownAction.id, + createdBy: 'did:example:admin', + reason: 'Y', + }, + { + encoding: 'application/json', + headers: { authorization: util.adminAuth() }, + }, + ) + // attempt to create account with the previously generated invite code + await createAccountWithInvite(agent, code) + }) + it('fails on used up invite code', async () => { const code = await createInviteCode(agent, 2) await createAccountsWithInvite(agent, code, 2) @@ -106,7 +150,7 @@ describe('account', () => { expect(res3.data.codes.length).toBe(2) }) - it('admin gifted codes to not impact a users avilable codes', async () => { + it('admin gifted codes to not impact a users available codes', async () => { const account = await makeLoggedInAccount(agent) // again, pretend account was made 2 days ago diff --git a/packages/pds/tests/proxied/__snapshots__/admin.test.ts.snap b/packages/pds/tests/proxied/__snapshots__/admin.test.ts.snap new file mode 100644 index 00000000000..02a0a420403 --- /dev/null +++ b/packages/pds/tests/proxied/__snapshots__/admin.test.ts.snap @@ -0,0 +1,528 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`proxies admin requests creates reports of a repo. 1`] = ` +Array [ + Object { + "createdAt": "1970-01-01T00:00:00.000Z", + "id": 1, + "reasonType": "com.atproto.moderation.defs#reasonSpam", + "reportedBy": "user(0)", + "subject": Object { + "$type": "com.atproto.admin.defs#repoRef", + "did": "user(1)", + }, + }, + Object { + "createdAt": "1970-01-01T00:00:00.000Z", + "id": 2, + "reason": "impersonation", + "reasonType": "com.atproto.moderation.defs#reasonOther", + "reportedBy": "user(2)", + "subject": Object { + "$type": "com.atproto.admin.defs#repoRef", + "did": "user(1)", + }, + }, +] +`; + +exports[`proxies admin requests fetches a list of actions. 1`] = ` +Object { + "actions": Array [ + Object { + "action": "com.atproto.admin.defs#acknowledge", + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "did:example:admin", + "id": 3, + "reason": "Y", + "resolvedReportIds": Array [], + "reversal": Object { + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "did:example:admin", + "reason": "X", + }, + "subject": Object { + "$type": "com.atproto.admin.defs#repoRef", + "did": "user(0)", + }, + "subjectBlobCids": Array [], + }, + Object { + "action": "com.atproto.admin.defs#flag", + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "did:example:admin", + "id": 2, + "reason": "Y", + "resolvedReportIds": Array [ + 2, + 1, + ], + "subject": Object { + "$type": "com.atproto.repo.strongRef", + "cid": "cids(0)", + "uri": "record(0)", + }, + "subjectBlobCids": Array [], + }, + ], + "cursor": "2", +} +`; + +exports[`proxies admin requests fetches a list of reports. 1`] = ` +Object { + "cursor": "2", + "reports": Array [ + Object { + "createdAt": "1970-01-01T00:00:00.000Z", + "id": 1, + "reasonType": "com.atproto.moderation.defs#reasonSpam", + "reportedBy": "user(0)", + "resolvedByActionIds": Array [ + 2, + ], + "subject": Object { + "$type": "com.atproto.admin.defs#repoRef", + "did": "user(1)", + }, + "subjectRepoHandle": "bob.test", + }, + Object { + "createdAt": "1970-01-01T00:00:00.000Z", + "id": 2, + "reason": "impersonation", + "reasonType": "com.atproto.moderation.defs#reasonOther", + "reportedBy": "user(2)", + "resolvedByActionIds": Array [ + 2, + ], + "subject": Object { + "$type": "com.atproto.admin.defs#repoRef", + "did": "user(1)", + }, + "subjectRepoHandle": "bob.test", + }, + ], +} +`; + +exports[`proxies admin requests fetches action details. 1`] = ` +Object { + "action": "com.atproto.admin.defs#acknowledge", + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "did:example:admin", + "id": 3, + "reason": "Y", + "resolvedReports": Array [], + "reversal": Object { + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "did:example:admin", + "reason": "X", + }, + "subject": Object { + "$type": "com.atproto.admin.defs#repoView", + "did": "user(0)", + "email": "bob@test.com", + "handle": "bob.test", + "indexedAt": "1970-01-01T00:00:00.000Z", + "invitedBy": Object { + "available": 10, + "code": "invite-code", + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "admin", + "disabled": false, + "forAccount": "admin", + "uses": Array [ + Object { + "usedAt": "1970-01-01T00:00:00.000Z", + "usedBy": "user(1)", + }, + Object { + "usedAt": "1970-01-01T00:00:00.000Z", + "usedBy": "user(0)", + }, + Object { + "usedAt": "1970-01-01T00:00:00.000Z", + "usedBy": "user(2)", + }, + Object { + "usedAt": "1970-01-01T00:00:00.000Z", + "usedBy": "user(3)", + }, + ], + }, + "invitesDisabled": true, + "moderation": Object {}, + "relatedRecords": Array [ + Object { + "$type": "app.bsky.actor.profile", + "avatar": Object { + "$type": "blob", + "mimeType": "image/jpeg", + "ref": Object { + "$link": "cids(0)", + }, + "size": 3976, + }, + "description": "hi im bob label_me", + "displayName": "bobby", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label-a", + }, + Object { + "val": "self-label-b", + }, + ], + }, + }, + ], + }, + "subjectBlobs": Array [], +} +`; + +exports[`proxies admin requests fetches record details. 1`] = ` +Object { + "blobCids": Array [], + "blobs": Array [], + "cid": "cids(0)", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [], + "moderation": Object { + "actions": Array [ + Object { + "action": "com.atproto.admin.defs#flag", + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "did:example:admin", + "id": 2, + "reason": "Y", + "resolvedReportIds": Array [ + 2, + 1, + ], + "subject": Object { + "$type": "com.atproto.repo.strongRef", + "cid": "cids(0)", + "uri": "record(0)", + }, + "subjectBlobCids": Array [], + }, + ], + "currentAction": Object { + "action": "com.atproto.admin.defs#flag", + "id": 2, + }, + "reports": Array [], + }, + "repo": Object { + "did": "user(0)", + "email": "bob@test.com", + "handle": "bob.test", + "indexedAt": "1970-01-01T00:00:00.000Z", + "invitedBy": Object { + "available": 10, + "code": "invite-code", + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "admin", + "disabled": false, + "forAccount": "admin", + "uses": Array [ + Object { + "usedAt": "1970-01-01T00:00:00.000Z", + "usedBy": "user(1)", + }, + Object { + "usedAt": "1970-01-01T00:00:00.000Z", + "usedBy": "user(0)", + }, + Object { + "usedAt": "1970-01-01T00:00:00.000Z", + "usedBy": "user(2)", + }, + Object { + "usedAt": "1970-01-01T00:00:00.000Z", + "usedBy": "user(3)", + }, + ], + }, + "invitesDisabled": true, + "moderation": Object { + "currentAction": Object { + "action": "com.atproto.admin.defs#acknowledge", + "id": 3, + }, + }, + "relatedRecords": Array [ + Object { + "$type": "app.bsky.actor.profile", + "avatar": Object { + "$type": "blob", + "mimeType": "image/jpeg", + "ref": Object { + "$link": "cids(1)", + }, + "size": 3976, + }, + "description": "hi im bob label_me", + "displayName": "bobby", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label-a", + }, + Object { + "val": "self-label-b", + }, + ], + }, + }, + ], + }, + "uri": "record(0)", + "value": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000+00:00", + "text": "bobby boy here", + }, +} +`; + +exports[`proxies admin requests fetches repo details. 1`] = ` +Object { + "did": "user(0)", + "email": "eve@test.com", + "handle": "eve.test", + "indexedAt": "1970-01-01T00:00:00.000Z", + "invitedBy": Object { + "available": 1, + "code": "invite-code", + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "admin", + "disabled": false, + "forAccount": "user(1)", + "uses": Array [ + Object { + "usedAt": "1970-01-01T00:00:00.000Z", + "usedBy": "user(0)", + }, + ], + }, + "invites": Array [], + "invitesDisabled": false, + "labels": Array [], + "moderation": Object { + "actions": Array [], + "reports": Array [], + }, + "relatedRecords": Array [], +} +`; + +exports[`proxies admin requests fetches report details. 1`] = ` +Object { + "createdAt": "1970-01-01T00:00:00.000Z", + "id": 1, + "reasonType": "com.atproto.moderation.defs#reasonSpam", + "reportedBy": "user(0)", + "resolvedByActions": Array [ + Object { + "action": "com.atproto.admin.defs#flag", + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "did:example:admin", + "id": 2, + "reason": "Y", + "resolvedReportIds": Array [ + 2, + 1, + ], + "subject": Object { + "$type": "com.atproto.repo.strongRef", + "cid": "cids(1)", + "uri": "record(0)", + }, + "subjectBlobCids": Array [], + }, + ], + "subject": Object { + "$type": "com.atproto.admin.defs#repoView", + "did": "user(1)", + "email": "bob@test.com", + "handle": "bob.test", + "indexedAt": "1970-01-01T00:00:00.000Z", + "invitedBy": Object { + "available": 10, + "code": "invite-code", + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "admin", + "disabled": false, + "forAccount": "admin", + "uses": Array [ + Object { + "usedAt": "1970-01-01T00:00:00.000Z", + "usedBy": "user(0)", + }, + Object { + "usedAt": "1970-01-01T00:00:00.000Z", + "usedBy": "user(1)", + }, + Object { + "usedAt": "1970-01-01T00:00:00.000Z", + "usedBy": "user(2)", + }, + Object { + "usedAt": "1970-01-01T00:00:00.000Z", + "usedBy": "user(3)", + }, + ], + }, + "invitesDisabled": true, + "moderation": Object { + "currentAction": Object { + "action": "com.atproto.admin.defs#acknowledge", + "id": 3, + }, + }, + "relatedRecords": Array [ + Object { + "$type": "app.bsky.actor.profile", + "avatar": Object { + "$type": "blob", + "mimeType": "image/jpeg", + "ref": Object { + "$link": "cids(0)", + }, + "size": 3976, + }, + "description": "hi im bob label_me", + "displayName": "bobby", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label-a", + }, + Object { + "val": "self-label-b", + }, + ], + }, + }, + ], + }, +} +`; + +exports[`proxies admin requests reverses action. 1`] = ` +Object { + "action": "com.atproto.admin.defs#acknowledge", + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "did:example:admin", + "id": 3, + "reason": "Y", + "resolvedReportIds": Array [], + "reversal": Object { + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "did:example:admin", + "reason": "X", + }, + "subject": Object { + "$type": "com.atproto.admin.defs#repoRef", + "did": "user(0)", + }, + "subjectBlobCids": Array [], +} +`; + +exports[`proxies admin requests searches repos. 1`] = ` +Array [ + Object { + "did": "user(0)", + "handle": "alice.test", + "indexedAt": "1970-01-01T00:00:00.000Z", + "moderation": Object {}, + "relatedRecords": Array [ + Object { + "$type": "app.bsky.actor.profile", + "avatar": Object { + "$type": "blob", + "mimeType": "image/jpeg", + "ref": Object { + "$link": "cids(0)", + }, + "size": 3976, + }, + "description": "its me!", + "displayName": "ali", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label-a", + }, + Object { + "val": "self-label-b", + }, + ], + }, + }, + ], + }, +] +`; + +exports[`proxies admin requests takes actions and resolves reports 1`] = ` +Object { + "action": "com.atproto.admin.defs#flag", + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "did:example:admin", + "id": 2, + "reason": "Y", + "resolvedReportIds": Array [], + "subject": Object { + "$type": "com.atproto.repo.strongRef", + "cid": "cids(0)", + "uri": "record(0)", + }, + "subjectBlobCids": Array [], +} +`; + +exports[`proxies admin requests takes actions and resolves reports 2`] = ` +Object { + "action": "com.atproto.admin.defs#acknowledge", + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "did:example:admin", + "id": 3, + "reason": "Y", + "resolvedReportIds": Array [], + "subject": Object { + "$type": "com.atproto.admin.defs#repoRef", + "did": "user(0)", + }, + "subjectBlobCids": Array [], +} +`; + +exports[`proxies admin requests takes actions and resolves reports 3`] = ` +Object { + "action": "com.atproto.admin.defs#flag", + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "did:example:admin", + "id": 2, + "reason": "Y", + "resolvedReportIds": Array [ + 2, + 1, + ], + "subject": Object { + "$type": "com.atproto.repo.strongRef", + "cid": "cids(0)", + "uri": "record(0)", + }, + "subjectBlobCids": Array [], +} +`; diff --git a/packages/pds/tests/proxied/__snapshots__/feedgen.test.ts.snap b/packages/pds/tests/proxied/__snapshots__/feedgen.test.ts.snap index 22729693359..d928dc99923 100644 --- a/packages/pds/tests/proxied/__snapshots__/feedgen.test.ts.snap +++ b/packages/pds/tests/proxied/__snapshots__/feedgen.test.ts.snap @@ -7,11 +7,28 @@ Object { Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -26,18 +43,18 @@ Object { "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(3)", - "uri": "record(2)", + "cid": "cids(4)", + "uri": "record(3)", }, "root": Object { - "cid": "cids(2)", - "uri": "record(1)", + "cid": "cids(3)", + "uri": "record(2)", }, }, "text": "thanks bob", }, "replyCount": 0, - "repostCount": 0, + "repostCount": 1, "uri": "record(0)", "viewer": Object {}, }, @@ -45,45 +62,62 @@ Object { "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "bobby", "handle": "bob.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(6)", + "val": "self-label-a", + }, + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(6)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, - "followedBy": "record(4)", - "following": "record(3)", + "followedBy": "record(5)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(4)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/sample-img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(3)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(3)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(6)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(6)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(3)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(2)", + "uri": "record(3)", "val": "test-label", }, Object { - "cid": "cids(3)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(2)", + "uri": "record(3)", "val": "test-label-2", }, ], @@ -100,7 +134,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(6)", }, "size": 4114, }, @@ -109,35 +143,52 @@ Object { }, "reply": Object { "parent": Object { - "cid": "cids(2)", - "uri": "record(1)", + "cid": "cids(3)", + "uri": "record(2)", }, "root": Object { - "cid": "cids(2)", - "uri": "record(1)", + "cid": "cids(3)", + "uri": "record(2)", }, }, "text": "hear that label_me label_me_2", }, "replyCount": 1, "repostCount": 0, - "uri": "record(2)", + "uri": "record(3)", "viewer": Object {}, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -148,7 +199,7 @@ Object { }, "replyCount": 2, "repostCount": 1, - "uri": "record(1)", + "uri": "record(2)", "viewer": Object {}, }, }, @@ -161,12 +212,12 @@ Object { "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(7)", - "following": "record(6)", + "followedBy": "record(9)", + "following": "record(8)", "muted": false, }, }, - "cid": "cids(5)", + "cid": "cids(7)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -175,36 +226,53 @@ Object { "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(2)", - "uri": "record(1)", + "cid": "cids(3)", + "uri": "record(2)", }, "root": Object { - "cid": "cids(2)", - "uri": "record(1)", + "cid": "cids(3)", + "uri": "record(2)", }, }, "text": "of course", }, "replyCount": 0, "repostCount": 0, - "uri": "record(5)", + "uri": "record(7)", "viewer": Object {}, }, "reply": Object { "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -215,23 +283,40 @@ Object { }, "replyCount": 2, "repostCount": 1, - "uri": "record(1)", + "uri": "record(2)", "viewer": Object {}, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -242,7 +327,7 @@ Object { }, "replyCount": 2, "repostCount": 1, - "uri": "record(1)", + "uri": "record(2)", "viewer": Object {}, }, }, @@ -250,45 +335,62 @@ Object { Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "bobby", "handle": "bob.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(6)", + "val": "self-label-a", + }, + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(6)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, - "followedBy": "record(4)", - "following": "record(3)", + "followedBy": "record(5)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(4)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/sample-img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(3)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(3)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(6)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(6)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(3)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(2)", + "uri": "record(3)", "val": "test-label", }, Object { - "cid": "cids(3)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(2)", + "uri": "record(3)", "val": "test-label-2", }, ], @@ -305,7 +407,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(6)", }, "size": 4114, }, @@ -314,36 +416,53 @@ Object { }, "reply": Object { "parent": Object { - "cid": "cids(2)", - "uri": "record(1)", + "cid": "cids(3)", + "uri": "record(2)", }, "root": Object { - "cid": "cids(2)", - "uri": "record(1)", + "cid": "cids(3)", + "uri": "record(2)", }, }, "text": "hear that label_me label_me_2", }, "replyCount": 1, "repostCount": 0, - "uri": "record(2)", + "uri": "record(3)", "viewer": Object {}, }, "reply": Object { "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -354,23 +473,40 @@ Object { }, "replyCount": 2, "repostCount": 1, - "uri": "record(1)", + "uri": "record(2)", "viewer": Object {}, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -381,7 +517,7 @@ Object { }, "replyCount": 2, "repostCount": 1, - "uri": "record(1)", + "uri": "record(2)", "viewer": Object {}, }, }, @@ -389,17 +525,34 @@ Object { Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(8)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { @@ -407,14 +560,22 @@ Object { "author": Object { "did": "user(5)", "handle": "dan.test", - "labels": Array [], + "labels": Array [ + Object { + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "user(5)", + "val": "repo-action-label", + }, + ], "viewer": Object { "blockedBy": false, - "following": "record(10)", + "following": "record(12)", "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(9)", "embeds": Array [ Object { "$type": "app.bsky.embed.record#view", @@ -426,15 +587,15 @@ Object { "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(7)", - "following": "record(6)", + "followedBy": "record(9)", + "following": "record(8)", "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(10)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(11)", + "uri": "record(13)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -449,7 +610,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(6)", }, "size": 4114, }, @@ -460,7 +621,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(9)", + "$link": "cids(11)", }, "size": 12736, }, @@ -469,8 +630,8 @@ Object { }, "record": Object { "record": Object { - "cid": "cids(10)", - "uri": "record(12)", + "cid": "cids(12)", + "uri": "record(14)", }, }, }, @@ -481,15 +642,15 @@ Object { ], "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(9)", + "uri": "record(11)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(8)", - "uri": "record(11)", + "cid": "cids(10)", + "uri": "record(13)", }, }, "facets": Array [ @@ -513,11 +674,11 @@ Object { "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(6)", + "cid": "cids(8)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(8)", + "uri": "record(10)", "val": "test-label", }, ], @@ -528,34 +689,51 @@ Object { "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(7)", - "uri": "record(9)", + "cid": "cids(9)", + "uri": "record(11)", }, }, "text": "yoohoo label_me", }, "replyCount": 0, "repostCount": 0, - "uri": "record(8)", + "uri": "record(10)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "bobby", "handle": "bob.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(6)", + "val": "self-label-a", + }, + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(6)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, - "followedBy": "record(4)", - "following": "record(3)", + "followedBy": "record(5)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(11)", + "cid": "cids(13)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -566,24 +744,41 @@ Object { }, "replyCount": 0, "repostCount": 0, - "uri": "record(13)", + "uri": "record(15)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -594,7 +789,7 @@ Object { }, "replyCount": 2, "repostCount": 1, - "uri": "record(1)", + "uri": "record(2)", "viewer": Object {}, }, }, @@ -606,12 +801,12 @@ Object { "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(7)", - "following": "record(6)", + "followedBy": "record(9)", + "following": "record(8)", "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(10)", "embed": Object { "$type": "app.bsky.embed.recordWithMedia#view", "media": Object { @@ -619,13 +814,13 @@ Object { "images": Array [ Object { "alt": "tests/sample-img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(6)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(6)@jpeg", }, Object { "alt": "tests/sample-img/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(9)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(9)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(11)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(11)@jpeg", }, ], }, @@ -633,23 +828,40 @@ Object { "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "bobby", "handle": "bob.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(6)", + "val": "self-label-a", + }, + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(6)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, - "followedBy": "record(4)", - "following": "record(3)", + "followedBy": "record(5)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(10)", + "cid": "cids(12)", "embeds": Array [], "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(12)", + "uri": "record(14)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -679,7 +891,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(6)", }, "size": 4114, }, @@ -690,7 +902,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(9)", + "$link": "cids(11)", }, "size": 12736, }, @@ -699,8 +911,8 @@ Object { }, "record": Object { "record": Object { - "cid": "cids(10)", - "uri": "record(12)", + "cid": "cids(12)", + "uri": "record(14)", }, }, }, @@ -708,28 +920,45 @@ Object { }, "replyCount": 0, "repostCount": 0, - "uri": "record(11)", + "uri": "record(13)", "viewer": Object { - "like": "record(14)", + "like": "record(16)", }, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "bobby", "handle": "bob.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(6)", + "val": "self-label-a", + }, + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(6)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, - "followedBy": "record(4)", - "following": "record(3)", + "followedBy": "record(5)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(10)", + "cid": "cids(12)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -744,35 +973,69 @@ Object { }, "replyCount": 0, "repostCount": 0, - "uri": "record(12)", + "uri": "record(14)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(12)", + "cid": "cids(14)", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(14)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(17)", + "val": "self-label", + }, + ], "likeCount": 0, "record": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label", + }, + ], + }, "text": "hey there", }, "replyCount": 0, "repostCount": 0, - "uri": "record(15)", + "uri": "record(17)", "viewer": Object {}, }, }, diff --git a/packages/pds/tests/proxied/__snapshots__/views.test.ts.snap b/packages/pds/tests/proxied/__snapshots__/views.test.ts.snap index d47563e2632..63c047c6e7f 100644 --- a/packages/pds/tests/proxied/__snapshots__/views.test.ts.snap +++ b/packages/pds/tests/proxied/__snapshots__/views.test.ts.snap @@ -2,7 +2,7 @@ exports[`proxies view requests actor.getProfile 1`] = ` Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "description": "hi im bob label_me", "did": "user(0)", "displayName": "bobby", @@ -10,7 +10,24 @@ Object { "followsCount": 2, "handle": "bob.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], "postsCount": 3, "viewer": Object { "blockedBy": false, @@ -25,7 +42,7 @@ exports[`proxies view requests actor.getProfiles 1`] = ` Object { "profiles": Array [ Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "description": "its me!", "did": "user(0)", "displayName": "ali", @@ -33,7 +50,24 @@ Object { "followsCount": 3, "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(0)", + "val": "self-label-a", + }, + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(0)", + "val": "self-label-b", + }, + ], "postsCount": 4, "viewer": Object { "blockedBy": false, @@ -41,7 +75,7 @@ Object { }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)@jpeg", "description": "hi im bob label_me", "did": "user(2)", "displayName": "bobby", @@ -49,12 +83,29 @@ Object { "followsCount": 2, "handle": "bob.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "postsCount": 3, "viewer": Object { "blockedBy": false, - "followedBy": "record(1)", - "following": "record(0)", + "followedBy": "record(2)", + "following": "record(1)", "muted": false, }, }, @@ -66,13 +117,30 @@ exports[`proxies view requests actor.getSuggestions 1`] = ` Object { "actors": Array [ Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(2)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "description": "hi im bob label_me", - "did": "user(1)", + "did": "user(0)", "displayName": "bobby", "handle": "bob.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(0)", @@ -80,122 +148,167 @@ Object { }, }, Object { - "did": "user(0)", + "did": "user(2)", "handle": "dan.test", - "labels": Array [], + "labels": Array [ + Object { + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "user(2)", + "val": "repo-action-label", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, ], - "cursor": "user(0)", + "cursor": "user(2)", } `; exports[`proxies view requests actor.searchActor 1`] = ` -Object { - "actors": Array [ - Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", - "description": "its me!", - "did": "user(0)", - "displayName": "ali", - "handle": "alice.test", - "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], - "viewer": Object { - "blockedBy": false, - "muted": false, +Array [ + Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", + "description": "its me!", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [ + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(0)", + "val": "self-label-a", }, - }, - Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(0)@jpeg", - "description": "hi im bob label_me", - "did": "user(2)", - "displayName": "bobby", - "handle": "bob.test", - "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], - "viewer": Object { - "blockedBy": false, - "followedBy": "record(1)", - "following": "record(0)", - "muted": false, + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(0)", + "val": "self-label-b", }, + ], + "viewer": Object { + "blockedBy": false, + "muted": false, }, - Object { - "did": "user(4)", - "handle": "carol.test", - "labels": Array [], - "viewer": Object { - "blockedBy": false, - "followedBy": "record(3)", - "following": "record(2)", - "muted": false, + }, + Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)@jpeg", + "description": "hi im bob label_me", + "did": "user(2)", + "displayName": "bobby", + "handle": "bob.test", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(3)", + "val": "self-label-b", }, + ], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(2)", + "following": "record(1)", + "muted": false, }, - Object { - "did": "user(5)", - "handle": "dan.test", - "labels": Array [], - "viewer": Object { - "blockedBy": false, - "following": "record(4)", - "muted": false, + }, + Object { + "did": "user(4)", + "handle": "carol.test", + "labels": Array [], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(5)", + "following": "record(4)", + "muted": false, + }, + }, + Object { + "did": "user(5)", + "handle": "dan.test", + "labels": Array [ + Object { + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "user(5)", + "val": "repo-action-label", }, + ], + "viewer": Object { + "blockedBy": false, + "following": "record(6)", + "muted": false, }, - ], - "cursor": "0::dan.test", -} + }, +] `; exports[`proxies view requests actor.searchActorTypeahead 1`] = ` -Object { - "actors": Array [ - Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", - "did": "user(0)", - "displayName": "ali", - "handle": "alice.test", - "viewer": Object { - "blockedBy": false, - "muted": false, - }, +Array [ + Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "viewer": Object { + "blockedBy": false, + "muted": false, }, - Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(0)@jpeg", - "did": "user(2)", - "displayName": "bobby", - "handle": "bob.test", - "viewer": Object { - "blockedBy": false, - "followedBy": "record(1)", - "following": "record(0)", - "muted": false, - }, + }, + Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)@jpeg", + "did": "user(2)", + "displayName": "bobby", + "handle": "bob.test", + "viewer": Object { + "blockedBy": false, + "followedBy": "record(1)", + "following": "record(0)", + "muted": false, }, - Object { - "did": "user(4)", - "handle": "carol.test", - "viewer": Object { - "blockedBy": false, - "followedBy": "record(3)", - "following": "record(2)", - "muted": false, - }, + }, + Object { + "did": "user(4)", + "handle": "carol.test", + "viewer": Object { + "blockedBy": false, + "followedBy": "record(3)", + "following": "record(2)", + "muted": false, }, - Object { - "did": "user(5)", - "handle": "dan.test", - "viewer": Object { - "blockedBy": false, - "following": "record(4)", - "muted": false, - }, + }, + Object { + "did": "user(5)", + "handle": "dan.test", + "viewer": Object { + "blockedBy": false, + "following": "record(4)", + "muted": false, }, - ], -} + }, +] `; exports[`proxies view requests feed.getAuthorFeed 1`] = ` @@ -205,11 +318,28 @@ Object { Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "bobby", "handle": "bob.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -223,8 +353,8 @@ Object { "images": Array [ Object { "alt": "tests/sample-img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(1)/cids(2)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(1)/cids(2)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(1)/cids(3)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(1)/cids(3)@jpeg", }, ], }, @@ -260,7 +390,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(2)", + "$link": "cids(3)", }, "size": 4114, }, @@ -269,12 +399,12 @@ Object { }, "reply": Object { "parent": Object { - "cid": "cids(3)", - "uri": "record(3)", + "cid": "cids(4)", + "uri": "record(4)", }, "root": Object { - "cid": "cids(3)", - "uri": "record(3)", + "cid": "cids(4)", + "uri": "record(4)", }, }, "text": "hear that label_me label_me_2", @@ -288,17 +418,34 @@ Object { "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(5)", + "val": "self-label-a", + }, + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(5)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(4)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -309,23 +456,40 @@ Object { }, "replyCount": 2, "repostCount": 1, - "uri": "record(3)", + "uri": "record(4)", "viewer": Object {}, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "did": "user(2)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(5)", + "val": "self-label-a", + }, + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(5)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(4)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -336,7 +500,7 @@ Object { }, "replyCount": 2, "repostCount": 1, - "uri": "record(3)", + "uri": "record(4)", "viewer": Object {}, }, }, @@ -344,11 +508,28 @@ Object { Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "bobby", "handle": "bob.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -356,7 +537,7 @@ Object { "muted": false, }, }, - "cid": "cids(4)", + "cid": "cids(6)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -367,18 +548,35 @@ Object { }, "replyCount": 0, "repostCount": 0, - "uri": "record(4)", + "uri": "record(6)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "bobby", "handle": "bob.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -386,7 +584,7 @@ Object { "muted": false, }, }, - "cid": "cids(5)", + "cid": "cids(7)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -401,7 +599,7 @@ Object { }, "replyCount": 0, "repostCount": 0, - "uri": "record(5)", + "uri": "record(7)", "viewer": Object {}, }, }, @@ -416,11 +614,28 @@ Object { "view": Object { "cid": "cids(0)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(2)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -442,11 +657,28 @@ Object { Object { "cid": "cids(0)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(2)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -469,17 +701,34 @@ Object { "likes": Array [ Object { "actor": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", "description": "hi im bob label_me", "did": "user(0)", "displayName": "bobby", "handle": "bob.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, - "followedBy": "record(2)", - "following": "record(1)", + "followedBy": "record(1)", + "following": "record(0)", "muted": false, }, }, @@ -488,13 +737,30 @@ Object { }, Object { "actor": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)@jpeg", "description": "its me!", "did": "user(2)", "displayName": "ali", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(2)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -504,7 +770,7 @@ Object { "indexedAt": "1970-01-01T00:00:00.000Z", }, ], - "uri": "record(0)", + "uri": "record(4)", } `; @@ -513,11 +779,28 @@ Object { "posts": Array [ Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "bobby", "handle": "bob.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -550,12 +833,12 @@ Object { "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(5)", - "following": "record(4)", + "followedBy": "record(6)", + "following": "record(5)", "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(3)", "embed": Object { "$type": "app.bsky.embed.recordWithMedia#view", "media": Object { @@ -563,13 +846,13 @@ Object { "images": Array [ Object { "alt": "tests/sample-img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(3)/cids(3)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(3)/cids(3)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(4)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(4)@jpeg", }, Object { "alt": "tests/sample-img/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(3)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(3)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(5)@jpeg", }, ], }, @@ -577,11 +860,28 @@ Object { "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "bobby", "handle": "bob.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -623,7 +923,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(3)", + "$link": "cids(4)", }, "size": 4114, }, @@ -634,7 +934,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(5)", }, "size": 12736, }, @@ -648,57 +948,296 @@ Object { }, }, }, - "text": "hi im carol", - }, - "replyCount": 0, - "repostCount": 0, - "uri": "record(3)", - "viewer": Object { - "like": "record(6)", - }, - }, - ], -} -`; - -exports[`proxies view requests feed.getRepostedBy 1`] = ` -Object { - "cursor": "0000000000000::bafycid", - "repostedBy": Array [ - Object { - "did": "user(0)", - "handle": "carol.test", - "labels": Array [], - "viewer": Object { - "blockedBy": false, - "followedBy": "record(2)", - "following": "record(1)", - "muted": false, + "text": "hi im carol", + }, + "replyCount": 0, + "repostCount": 0, + "uri": "record(4)", + "viewer": Object { + "like": "record(7)", + }, + }, + ], +} +`; + +exports[`proxies view requests feed.getRepostedBy 1`] = ` +Object { + "cursor": "0000000000000::bafycid", + "repostedBy": Array [ + Object { + "did": "user(0)", + "handle": "carol.test", + "labels": Array [], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(1)", + "following": "record(0)", + "muted": false, + }, + }, + ], + "uri": "record(2)", +} +`; + +exports[`proxies view requests feed.getTimeline 1`] = ` +Object { + "cursor": "0000000000000::bafycid", + "feed": Array [ + Object { + "post": Object { + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "muted": false, + }, + }, + "cid": "cids(0)", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [], + "likeCount": 0, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000Z", + "reply": Object { + "parent": Object { + "cid": "cids(4)", + "uri": "record(3)", + }, + "root": Object { + "cid": "cids(3)", + "uri": "record(2)", + }, + }, + "text": "thanks bob", + }, + "replyCount": 0, + "repostCount": 1, + "uri": "record(0)", + "viewer": Object {}, + }, + "reason": Object { + "$type": "app.bsky.feed.defs#reasonRepost", + "by": Object { + "did": "user(2)", + "handle": "dan.test", + "labels": Array [ + Object { + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "user(2)", + "val": "repo-action-label", + }, + ], + "viewer": Object { + "blockedBy": false, + "following": "record(4)", + "muted": false, + }, + }, + "indexedAt": "1970-01-01T00:00:00.000Z", + }, + "reply": Object { + "parent": Object { + "$type": "app.bsky.feed.defs#postView", + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", + "displayName": "bobby", + "handle": "bob.test", + "labels": Array [ + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(7)", + "val": "self-label-a", + }, + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(7)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "followedBy": "record(6)", + "following": "record(5)", + "muted": false, + }, + }, + "cid": "cids(4)", + "embed": Object { + "$type": "app.bsky.embed.images#view", + "images": Array [ + Object { + "alt": "tests/sample-img/key-landscape-small.jpg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(6)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(6)@jpeg", + }, + ], + }, + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [ + Object { + "cid": "cids(4)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "record(3)", + "val": "test-label", + }, + Object { + "cid": "cids(4)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "record(3)", + "val": "test-label-2", + }, + ], + "likeCount": 0, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000Z", + "embed": Object { + "$type": "app.bsky.embed.images", + "images": Array [ + Object { + "alt": "tests/sample-img/key-landscape-small.jpg", + "image": Object { + "$type": "blob", + "mimeType": "image/jpeg", + "ref": Object { + "$link": "cids(6)", + }, + "size": 4114, + }, + }, + ], + }, + "reply": Object { + "parent": Object { + "cid": "cids(3)", + "uri": "record(2)", + }, + "root": Object { + "cid": "cids(3)", + "uri": "record(2)", + }, + }, + "text": "hear that label_me label_me_2", + }, + "replyCount": 1, + "repostCount": 0, + "uri": "record(3)", + "viewer": Object {}, + }, + "root": Object { + "$type": "app.bsky.feed.defs#postView", + "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "did": "user(0)", + "displayName": "ali", + "handle": "alice.test", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "muted": false, + }, + }, + "cid": "cids(3)", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [], + "likeCount": 3, + "record": Object { + "$type": "app.bsky.feed.post", + "createdAt": "1970-01-01T00:00:00.000000Z", + "text": "again", + }, + "replyCount": 2, + "repostCount": 1, + "uri": "record(2)", + "viewer": Object {}, + }, }, }, - ], - "uri": "record(0)", -} -`; - -exports[`proxies view requests feed.getTimeline 1`] = ` -Object { - "cursor": "0000000000000::bafycid", - "feed": Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -709,7 +1248,7 @@ Object { }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(2)", "viewer": Object {}, }, "reason": Object { @@ -717,10 +1256,18 @@ Object { "by": Object { "did": "user(2)", "handle": "dan.test", - "labels": Array [], + "labels": Array [ + Object { + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "user(2)", + "val": "repo-action-label", + }, + ], "viewer": Object { "blockedBy": false, - "following": "record(1)", + "following": "record(4)", "muted": false, }, }, @@ -732,30 +1279,38 @@ Object { "author": Object { "did": "user(2)", "handle": "dan.test", - "labels": Array [], + "labels": Array [ + Object { + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "user(2)", + "val": "repo-action-label", + }, + ], "viewer": Object { "blockedBy": false, - "following": "record(1)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(7)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(3)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(5)", - "following": "record(4)", + "followedBy": "record(11)", + "following": "record(10)", "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(8)", "embeds": Array [ Object { "$type": "app.bsky.embed.recordWithMedia#view", @@ -764,13 +1319,13 @@ Object { "images": Array [ Object { "alt": "tests/sample-img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(6)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(6)@jpeg", }, Object { "alt": "tests/sample-img/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(9)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(9)@jpeg", }, ], }, @@ -778,22 +1333,39 @@ Object { "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(7)", + "val": "self-label-a", + }, + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(7)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, - "followedBy": "record(8)", - "following": "record(7)", + "followedBy": "record(6)", + "following": "record(5)", "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(10)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(6)", + "uri": "record(12)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -809,7 +1381,7 @@ Object { ], "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(3)", + "uri": "record(9)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -824,7 +1396,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(6)", }, "size": 4114, }, @@ -835,7 +1407,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(5)", + "$link": "cids(9)", }, "size": 12736, }, @@ -844,8 +1416,8 @@ Object { }, "record": Object { "record": Object { - "cid": "cids(6)", - "uri": "record(6)", + "cid": "cids(10)", + "uri": "record(12)", }, }, }, @@ -862,8 +1434,8 @@ Object { "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(3)", - "uri": "record(3)", + "cid": "cids(8)", + "uri": "record(9)", }, }, "facets": Array [ @@ -884,19 +1456,19 @@ Object { }, "replyCount": 0, "repostCount": 1, - "uri": "record(2)", + "uri": "record(8)", "viewer": Object {}, }, "reason": Object { "$type": "app.bsky.feed.defs#reasonRepost", "by": Object { - "did": "user(3)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(5)", - "following": "record(4)", + "followedBy": "record(11)", + "following": "record(10)", "muted": false, }, }, @@ -906,17 +1478,34 @@ Object { Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(0)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -925,64 +1514,81 @@ Object { "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(8)", - "uri": "record(10)", + "cid": "cids(4)", + "uri": "record(3)", }, "root": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(2)", }, }, "text": "thanks bob", }, "replyCount": 0, - "repostCount": 0, - "uri": "record(9)", + "repostCount": 1, + "uri": "record(0)", "viewer": Object {}, }, "reply": Object { "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(7)", + "val": "self-label-a", + }, + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(7)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, - "followedBy": "record(8)", - "following": "record(7)", + "followedBy": "record(6)", + "following": "record(5)", "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(4)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/sample-img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(6)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(6)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(8)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(10)", + "uri": "record(3)", "val": "test-label", }, Object { - "cid": "cids(8)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(10)", + "uri": "record(3)", "val": "test-label-2", }, ], @@ -999,7 +1605,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(6)", }, "size": 4114, }, @@ -1008,35 +1614,52 @@ Object { }, "reply": Object { "parent": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(2)", }, "root": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(2)", }, }, "text": "hear that label_me label_me_2", }, "replyCount": 1, "repostCount": 0, - "uri": "record(10)", + "uri": "record(3)", "viewer": Object {}, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -1047,7 +1670,7 @@ Object { }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(2)", "viewer": Object {}, }, }, @@ -1055,17 +1678,17 @@ Object { Object { "post": Object { "author": Object { - "did": "user(3)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(5)", - "following": "record(4)", + "followedBy": "record(11)", + "following": "record(10)", "muted": false, }, }, - "cid": "cids(9)", + "cid": "cids(11)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -1074,36 +1697,53 @@ Object { "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(2)", }, "root": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(2)", }, }, "text": "of course", }, "replyCount": 0, "repostCount": 0, - "uri": "record(11)", + "uri": "record(13)", "viewer": Object {}, }, "reply": Object { "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -1114,23 +1754,40 @@ Object { }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(2)", "viewer": Object {}, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -1141,7 +1798,7 @@ Object { }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(2)", "viewer": Object {}, }, }, @@ -1149,45 +1806,62 @@ Object { Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(7)", + "val": "self-label-a", + }, + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(7)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, - "followedBy": "record(8)", - "following": "record(7)", + "followedBy": "record(6)", + "following": "record(5)", "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(4)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "tests/sample-img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(5)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(5)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(6)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(6)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(8)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(10)", + "uri": "record(3)", "val": "test-label", }, Object { - "cid": "cids(8)", + "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(10)", + "uri": "record(3)", "val": "test-label-2", }, ], @@ -1204,7 +1878,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(6)", }, "size": 4114, }, @@ -1213,36 +1887,53 @@ Object { }, "reply": Object { "parent": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(2)", }, "root": Object { - "cid": "cids(0)", - "uri": "record(0)", + "cid": "cids(3)", + "uri": "record(2)", }, }, "text": "hear that label_me label_me_2", }, "replyCount": 1, "repostCount": 0, - "uri": "record(10)", + "uri": "record(3)", "viewer": Object {}, }, "reply": Object { "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -1253,23 +1944,40 @@ Object { }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(2)", "viewer": Object {}, }, "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -1280,7 +1988,7 @@ Object { }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(2)", "viewer": Object {}, }, }, @@ -1288,17 +1996,34 @@ Object { Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(10)", + "cid": "cids(12)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { @@ -1306,34 +2031,42 @@ Object { "author": Object { "did": "user(2)", "handle": "dan.test", - "labels": Array [], + "labels": Array [ + Object { + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "user(2)", + "val": "repo-action-label", + }, + ], "viewer": Object { "blockedBy": false, - "following": "record(1)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(7)", "embeds": Array [ Object { "$type": "app.bsky.embed.record#view", "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(3)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(5)", - "following": "record(4)", + "followedBy": "record(11)", + "following": "record(10)", "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(3)", + "uri": "record(9)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -1348,7 +2081,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(6)", }, "size": 4114, }, @@ -1359,7 +2092,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(5)", + "$link": "cids(9)", }, "size": 12736, }, @@ -1368,8 +2101,8 @@ Object { }, "record": Object { "record": Object { - "cid": "cids(6)", - "uri": "record(6)", + "cid": "cids(10)", + "uri": "record(12)", }, }, }, @@ -1380,15 +2113,15 @@ Object { ], "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(2)", + "uri": "record(8)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(3)", - "uri": "record(3)", + "cid": "cids(8)", + "uri": "record(9)", }, }, "facets": Array [ @@ -1412,11 +2145,11 @@ Object { "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(10)", + "cid": "cids(12)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, "src": "did:example:labeler", - "uri": "record(12)", + "uri": "record(14)", "val": "test-label", }, ], @@ -1427,34 +2160,51 @@ Object { "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(2)", - "uri": "record(2)", + "cid": "cids(7)", + "uri": "record(8)", }, }, "text": "yoohoo label_me", }, "replyCount": 0, "repostCount": 0, - "uri": "record(12)", + "uri": "record(14)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(7)", + "val": "self-label-a", + }, + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(7)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, - "followedBy": "record(8)", - "following": "record(7)", + "followedBy": "record(6)", + "following": "record(5)", "muted": false, }, }, - "cid": "cids(11)", + "cid": "cids(13)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -1465,24 +2215,41 @@ Object { }, "replyCount": 0, "repostCount": 0, - "uri": "record(13)", + "uri": "record(15)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(0)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -1493,7 +2260,7 @@ Object { }, "replyCount": 2, "repostCount": 1, - "uri": "record(0)", + "uri": "record(2)", "viewer": Object {}, }, }, @@ -1502,30 +2269,38 @@ Object { "author": Object { "did": "user(2)", "handle": "dan.test", - "labels": Array [], + "labels": Array [ + Object { + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "user(2)", + "val": "repo-action-label", + }, + ], "viewer": Object { "blockedBy": false, - "following": "record(1)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(7)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(3)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(5)", - "following": "record(4)", + "followedBy": "record(11)", + "following": "record(10)", "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(8)", "embeds": Array [ Object { "$type": "app.bsky.embed.recordWithMedia#view", @@ -1534,13 +2309,13 @@ Object { "images": Array [ Object { "alt": "tests/sample-img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(6)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(6)@jpeg", }, Object { "alt": "tests/sample-img/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(9)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(9)@jpeg", }, ], }, @@ -1548,22 +2323,39 @@ Object { "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(7)", + "val": "self-label-a", + }, + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(7)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, - "followedBy": "record(8)", - "following": "record(7)", + "followedBy": "record(6)", + "following": "record(5)", "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(10)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(6)", + "uri": "record(12)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -1579,7 +2371,7 @@ Object { ], "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(3)", + "uri": "record(9)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -1594,7 +2386,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(6)", }, "size": 4114, }, @@ -1605,7 +2397,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(5)", + "$link": "cids(9)", }, "size": 12736, }, @@ -1614,8 +2406,8 @@ Object { }, "record": Object { "record": Object { - "cid": "cids(6)", - "uri": "record(6)", + "cid": "cids(10)", + "uri": "record(12)", }, }, }, @@ -1632,8 +2424,8 @@ Object { "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(3)", - "uri": "record(3)", + "cid": "cids(8)", + "uri": "record(9)", }, }, "facets": Array [ @@ -1654,7 +2446,7 @@ Object { }, "replyCount": 0, "repostCount": 1, - "uri": "record(2)", + "uri": "record(8)", "viewer": Object {}, }, }, @@ -1663,14 +2455,22 @@ Object { "author": Object { "did": "user(2)", "handle": "dan.test", - "labels": Array [], + "labels": Array [ + Object { + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "user(2)", + "val": "repo-action-label", + }, + ], "viewer": Object { "blockedBy": false, - "following": "record(1)", + "following": "record(4)", "muted": false, }, }, - "cid": "cids(12)", + "cid": "cids(14)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -1681,24 +2481,24 @@ Object { }, "replyCount": 0, "repostCount": 0, - "uri": "record(14)", + "uri": "record(16)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "did": "user(3)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(5)", - "following": "record(4)", + "followedBy": "record(11)", + "following": "record(10)", "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(8)", "embed": Object { "$type": "app.bsky.embed.recordWithMedia#view", "media": Object { @@ -1706,13 +2506,13 @@ Object { "images": Array [ Object { "alt": "tests/sample-img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(6)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(6)@jpeg", }, Object { "alt": "tests/sample-img/key-alt.jpg", - "fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(6)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(6)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(9)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(9)@jpeg", }, ], }, @@ -1720,23 +2520,40 @@ Object { "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(7)", + "val": "self-label-a", + }, + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(7)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, - "followedBy": "record(8)", - "following": "record(7)", + "followedBy": "record(6)", + "following": "record(5)", "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(10)", "embeds": Array [], "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], - "uri": "record(6)", + "uri": "record(12)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -1766,7 +2583,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(4)", + "$link": "cids(6)", }, "size": 4114, }, @@ -1777,7 +2594,7 @@ Object { "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(5)", + "$link": "cids(9)", }, "size": 12736, }, @@ -1786,8 +2603,8 @@ Object { }, "record": Object { "record": Object { - "cid": "cids(6)", - "uri": "record(6)", + "cid": "cids(10)", + "uri": "record(12)", }, }, }, @@ -1795,28 +2612,45 @@ Object { }, "replyCount": 0, "repostCount": 0, - "uri": "record(3)", + "uri": "record(9)", "viewer": Object { - "like": "record(15)", + "like": "record(17)", }, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(7)", + "val": "self-label-a", + }, + Object { + "cid": "cids(5)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(7)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, - "followedBy": "record(8)", - "following": "record(7)", + "followedBy": "record(6)", + "following": "record(5)", "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(10)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -1831,35 +2665,69 @@ Object { }, "replyCount": 0, "repostCount": 0, - "uri": "record(6)", + "uri": "record(12)", "viewer": Object {}, }, }, Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(13)", + "cid": "cids(15)", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(15)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(18)", + "val": "self-label", + }, + ], "likeCount": 0, "record": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label", + }, + ], + }, "text": "hey there", }, "replyCount": 0, "repostCount": 0, - "uri": "record(16)", + "uri": "record(18)", "viewer": Object {}, }, }, @@ -1883,13 +2751,30 @@ Object { }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(2)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(0)@jpeg", "description": "hi im bob label_me", "did": "user(1)", "displayName": "bobby", "handle": "bob.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(6)", + "val": "self-label-a", + }, + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(6)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "blocking": "record(3)", @@ -1908,23 +2793,48 @@ Object { "cursor": "0000000000000::bafycid", "followers": Array [ Object { - "did": "user(2)", + "did": "user(0)", "handle": "dan.test", - "labels": Array [], + "labels": Array [ + Object { + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "did:example:labeler", + "uri": "user(0)", + "val": "repo-action-label", + }, + ], "viewer": Object { "blockedBy": false, - "following": "record(2)", + "following": "record(0)", "muted": false, }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(0)@jpeg", "description": "its me!", - "did": "user(3)", + "did": "user(1)", "displayName": "ali", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -1932,17 +2842,34 @@ Object { }, ], "subject": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(0)@jpeg", "description": "hi im bob label_me", - "did": "user(0)", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(4)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(4)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, - "followedBy": "record(1)", - "following": "record(0)", + "followedBy": "record(3)", + "following": "record(2)", "muted": false, }, }, @@ -1954,24 +2881,41 @@ Object { "cursor": "0000000000000::bafycid", "follows": Array [ Object { - "did": "user(2)", + "did": "user(0)", "handle": "carol.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(3)", - "following": "record(2)", + "followedBy": "record(1)", + "following": "record(0)", "muted": false, }, }, Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(0)@jpeg", "description": "its me!", - "did": "user(3)", + "did": "user(1)", "displayName": "ali", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(1)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(1)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -1979,17 +2923,34 @@ Object { }, ], "subject": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(0)@jpeg", "description": "hi im bob label_me", - "did": "user(0)", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(5)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(5)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, - "followedBy": "record(1)", - "following": "record(0)", + "followedBy": "record(4)", + "following": "record(3)", "muted": false, }, }, @@ -2002,26 +2963,43 @@ Object { "items": Array [ Object { "subject": Object { - "did": "user(0)", + "did": "user(2)", "handle": "carol.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(1)", - "following": "record(0)", + "followedBy": "record(5)", + "following": "record(4)", "muted": false, }, }, }, Object { "subject": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(2)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", "description": "its me!", - "did": "user(1)", + "did": "user(3)", "displayName": "ali", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(3)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(6)", + "val": "self-label-a", + }, + Object { + "cid": "cids(3)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(3)", + "uri": "record(6)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -2030,19 +3008,36 @@ Object { }, ], "list": Object { - "cid": "cids(1)", + "cid": "cids(0)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "description": "hi im bob label_me", - "did": "user(3)", + "did": "user(0)", "displayName": "bobby", "handle": "bob.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, - "followedBy": "record(4)", - "following": "record(3)", + "followedBy": "record(2)", + "following": "record(1)", "muted": false, }, }, @@ -2050,7 +3045,7 @@ Object { "indexedAt": "1970-01-01T00:00:00.000Z", "name": "bob mutes", "purpose": "app.bsky.graph.defs#modlist", - "uri": "record(2)", + "uri": "record(0)", "viewer": Object { "muted": false, }, @@ -2058,6 +3053,56 @@ Object { } `; +exports[`proxies view requests graph.getListBlocks 1`] = ` +Object { + "cursor": "0000000000000::bafycid", + "lists": Array [ + Object { + "cid": "cids(0)", + "creator": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "description": "hi im bob label_me", + "did": "user(0)", + "displayName": "bobby", + "handle": "bob.test", + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(2)", + "val": "self-label-b", + }, + ], + "viewer": Object { + "blockedBy": false, + "muted": false, + }, + }, + "description": "bob's list of mutes", + "indexedAt": "1970-01-01T00:00:00.000Z", + "name": "bob mutes", + "purpose": "app.bsky.graph.defs#modlist", + "uri": "record(0)", + "viewer": Object { + "blocked": "record(1)", + "muted": false, + }, + }, + ], +} +`; + exports[`proxies view requests graph.getLists 1`] = ` Object { "cursor": "0000000000000::bafycid", @@ -2065,13 +3110,30 @@ Object { Object { "cid": "cids(0)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "description": "hi im bob label_me", "did": "user(0)", "displayName": "bobby", "handle": "bob.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -2089,15 +3151,32 @@ Object { }, }, Object { - "cid": "cids(2)", + "cid": "cids(3)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", "description": "hi im bob label_me", "did": "user(0)", "displayName": "bobby", "handle": "bob.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(3)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "followedBy": "record(2)", @@ -2109,7 +3188,7 @@ Object { "indexedAt": "1970-01-01T00:00:00.000Z", "name": "bob mutes", "purpose": "app.bsky.graph.defs#modlist", - "uri": "record(3)", + "uri": "record(4)", "viewer": Object { "muted": false, }, @@ -2120,15 +3199,35 @@ Object { exports[`proxies view requests unspecced.getPopularFeedGenerators 1`] = ` Object { + "cursor": "0000000000000::bafycid", "feeds": Array [ Object { "cid": "cids(0)", "creator": Object { - "avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "description": "its me!", "did": "user(0)", "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "indexedAt": "1970-01-01T00:00:00.000Z", + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "neg": false, + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, diff --git a/packages/pds/tests/proxied/admin.test.ts b/packages/pds/tests/proxied/admin.test.ts new file mode 100644 index 00000000000..8b3d0bb2dcf --- /dev/null +++ b/packages/pds/tests/proxied/admin.test.ts @@ -0,0 +1,379 @@ +import AtpAgent from '@atproto/api' +import { TestNetwork } from '@atproto/dev-env' +import { SeedClient } from '../seeds/client' +import basicSeed from '../seeds/basic' +import { + REASONOTHER, + REASONSPAM, +} from '@atproto/api/src/client/types/com/atproto/moderation/defs' +import { forSnapshot } from '../_util' +import { + ACKNOWLEDGE, + FLAG, + TAKEDOWN, +} from '@atproto/api/src/client/types/com/atproto/admin/defs' +import { NotFoundError } from '@atproto/api/src/client/types/app/bsky/feed/getPostThread' + +describe('proxies admin requests', () => { + let network: TestNetwork + let agent: AtpAgent + let sc: SeedClient + + beforeAll(async () => { + network = await TestNetwork.create({ + dbPostgresSchema: 'proxy_admin', + pds: { + // @NOTE requires admin pass be the same on pds and appview, which TestNetwork is handling for us. + bskyAppViewModeration: true, + inviteRequired: true, + }, + }) + agent = network.pds.getClient() + sc = new SeedClient(agent) + const { data: invite } = + await agent.api.com.atproto.server.createInviteCode( + { useCount: 10 }, + { + encoding: 'application/json', + headers: network.pds.adminAuthHeaders(), + }, + ) + await basicSeed(sc, invite) + await network.processAll() + }) + + beforeAll(async () => { + const { data: invite } = + await agent.api.com.atproto.server.createInviteCode( + { useCount: 1, forAccount: sc.dids.alice }, + { + headers: network.pds.adminAuthHeaders(), + encoding: 'application/json', + }, + ) + await agent.api.com.atproto.admin.disableAccountInvites( + { account: sc.dids.bob }, + { headers: network.pds.adminAuthHeaders(), encoding: 'application/json' }, + ) + await sc.createAccount('eve', { + handle: 'eve.test', + email: 'eve@test.com', + password: 'password', + inviteCode: invite.code, + }) + await network.processAll() + }) + + afterAll(async () => { + await network.close() + }) + + it('creates reports of a repo.', async () => { + const { data: reportA } = + await agent.api.com.atproto.moderation.createReport( + { + reasonType: REASONSPAM, + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: sc.dids.bob, + }, + }, + { + headers: sc.getHeaders(sc.dids.alice), + encoding: 'application/json', + }, + ) + const { data: reportB } = + await agent.api.com.atproto.moderation.createReport( + { + reasonType: REASONOTHER, + reason: 'impersonation', + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: sc.dids.bob, + }, + }, + { + headers: sc.getHeaders(sc.dids.carol), + encoding: 'application/json', + }, + ) + expect(forSnapshot([reportA, reportB])).toMatchSnapshot() + }) + + it('takes actions and resolves reports', async () => { + const post = sc.posts[sc.dids.bob][1] + const { data: actionA } = + await agent.api.com.atproto.admin.takeModerationAction( + { + action: FLAG, + subject: { + $type: 'com.atproto.repo.strongRef', + uri: post.ref.uriStr, + cid: post.ref.cidStr, + }, + createdBy: 'did:example:admin', + reason: 'Y', + }, + { + headers: network.pds.adminAuthHeaders(), + encoding: 'application/json', + }, + ) + expect(forSnapshot(actionA)).toMatchSnapshot() + const { data: actionB } = + await agent.api.com.atproto.admin.takeModerationAction( + { + action: ACKNOWLEDGE, + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: sc.dids.bob, + }, + createdBy: 'did:example:admin', + reason: 'Y', + }, + { + headers: network.pds.adminAuthHeaders(), + encoding: 'application/json', + }, + ) + expect(forSnapshot(actionB)).toMatchSnapshot() + const { data: resolved } = + await agent.api.com.atproto.admin.resolveModerationReports( + { + actionId: actionA.id, + reportIds: [1, 2], + createdBy: 'did:example:admin', + }, + { + headers: network.pds.adminAuthHeaders(), + encoding: 'application/json', + }, + ) + expect(forSnapshot(resolved)).toMatchSnapshot() + }) + + it('fetches report details.', async () => { + const { data: result } = + await agent.api.com.atproto.admin.getModerationReport( + { id: 1 }, + { headers: network.pds.adminAuthHeaders() }, + ) + expect(forSnapshot(result)).toMatchSnapshot() + }) + + it('fetches a list of reports.', async () => { + const { data: result } = + await agent.api.com.atproto.admin.getModerationReports( + { reverse: true }, + { headers: network.pds.adminAuthHeaders() }, + ) + expect(forSnapshot(result)).toMatchSnapshot() + }) + + it('fetches repo details.', async () => { + const { data: result } = await agent.api.com.atproto.admin.getRepo( + { did: sc.dids.eve }, + { headers: network.pds.adminAuthHeaders() }, + ) + expect(forSnapshot(result)).toMatchSnapshot() + }) + + it('fetches record details.', async () => { + const post = sc.posts[sc.dids.bob][1] + const { data: result } = await agent.api.com.atproto.admin.getRecord( + { uri: post.ref.uriStr }, + { headers: network.pds.adminAuthHeaders() }, + ) + expect(forSnapshot(result)).toMatchSnapshot() + }) + + it('reverses action.', async () => { + const { data: result } = + await agent.api.com.atproto.admin.reverseModerationAction( + { id: 3, createdBy: 'did:example:admin', reason: 'X' }, + { + headers: network.pds.adminAuthHeaders(), + encoding: 'application/json', + }, + ) + expect(forSnapshot(result)).toMatchSnapshot() + }) + + it('fetches action details.', async () => { + const { data: result } = + await agent.api.com.atproto.admin.getModerationAction( + { id: 3 }, + { headers: network.pds.adminAuthHeaders() }, + ) + expect(forSnapshot(result)).toMatchSnapshot() + }) + + it('fetches a list of actions.', async () => { + const { data: result } = + await agent.api.com.atproto.admin.getModerationActions( + { subject: sc.dids.bob }, + { headers: network.pds.adminAuthHeaders() }, + ) + expect(forSnapshot(result)).toMatchSnapshot() + }) + + it('searches repos.', async () => { + const { data: result } = await agent.api.com.atproto.admin.searchRepos( + { term: 'alice' }, + { headers: network.pds.adminAuthHeaders() }, + ) + expect(forSnapshot(result.repos)).toMatchSnapshot() + }) + + it('passes through errors.', async () => { + const tryGetReport = agent.api.com.atproto.admin.getModerationReport( + { id: 1000 }, + { headers: network.pds.adminAuthHeaders() }, + ) + await expect(tryGetReport).rejects.toThrow('Report not found') + const tryGetRepo = agent.api.com.atproto.admin.getRepo( + { did: 'did:does:not:exist' }, + { headers: network.pds.adminAuthHeaders() }, + ) + await expect(tryGetRepo).rejects.toThrow('Repo not found') + const tryGetRecord = agent.api.com.atproto.admin.getRecord( + { uri: 'at://did:does:not:exist/bad.collection.name/badrkey' }, + { headers: network.pds.adminAuthHeaders() }, + ) + await expect(tryGetRecord).rejects.toThrow('Record not found') + }) + + it('takesdown and labels repos, and reverts.', async () => { + // takedown repo + const { data: action } = + await agent.api.com.atproto.admin.takeModerationAction( + { + action: TAKEDOWN, + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: sc.dids.alice, + }, + createdBy: 'did:example:admin', + reason: 'Y', + createLabelVals: ['dogs'], + negateLabelVals: ['cats'], + }, + { + headers: network.pds.adminAuthHeaders(), + encoding: 'application/json', + }, + ) + // check profile and labels + const tryGetProfilePds = agent.api.app.bsky.actor.getProfile( + { actor: sc.dids.alice }, + { headers: sc.getHeaders(sc.dids.carol) }, + ) + const tryGetProfileAppview = agent.api.app.bsky.actor.getProfile( + { actor: sc.dids.alice }, + { + headers: { ...sc.getHeaders(sc.dids.carol) }, + }, + ) + await expect(tryGetProfilePds).rejects.toThrow( + 'Account has been taken down', + ) + await expect(tryGetProfileAppview).rejects.toThrow( + 'Account has been taken down', + ) + // reverse action + await agent.api.com.atproto.admin.reverseModerationAction( + { id: action.id, createdBy: 'did:example:admin', reason: 'X' }, + { + headers: network.pds.adminAuthHeaders(), + encoding: 'application/json', + }, + ) + // check profile and labels + const { data: profilePds } = await agent.api.app.bsky.actor.getProfile( + { actor: sc.dids.alice }, + { headers: sc.getHeaders(sc.dids.carol) }, + ) + const { data: profileAppview } = await agent.api.app.bsky.actor.getProfile( + { actor: sc.dids.alice }, + { + headers: { ...sc.getHeaders(sc.dids.carol) }, + }, + ) + expect(profilePds).toEqual( + expect.objectContaining({ did: sc.dids.alice, handle: 'alice.test' }), + ) + expect(profileAppview).toEqual( + expect.objectContaining({ did: sc.dids.alice, handle: 'alice.test' }), + ) + }) + + it('takesdown and labels records, and reverts.', async () => { + const post = sc.posts[sc.dids.alice][0] + // takedown post + const { data: action } = + await agent.api.com.atproto.admin.takeModerationAction( + { + action: TAKEDOWN, + subject: { + $type: 'com.atproto.repo.strongRef', + uri: post.ref.uriStr, + cid: post.ref.cidStr, + }, + createdBy: 'did:example:admin', + reason: 'Y', + createLabelVals: ['dogs'], + negateLabelVals: ['cats'], + }, + { + headers: network.pds.adminAuthHeaders(), + encoding: 'application/json', + }, + ) + // check thread and labels + const tryGetPost = agent.api.app.bsky.feed.getPostThread( + { uri: post.ref.uriStr, depth: 0 }, + { headers: sc.getHeaders(sc.dids.carol) }, + ) + await expect(tryGetPost).rejects.toThrow(NotFoundError) + // reverse action + await agent.api.com.atproto.admin.reverseModerationAction( + { id: action.id, createdBy: 'did:example:admin', reason: 'X' }, + { + headers: network.pds.adminAuthHeaders(), + encoding: 'application/json', + }, + ) + // check thread and labels + const { data: threadPds } = await agent.api.app.bsky.feed.getPostThread( + { uri: post.ref.uriStr, depth: 0 }, + { headers: sc.getHeaders(sc.dids.carol) }, + ) + const { data: threadAppview } = await agent.api.app.bsky.feed.getPostThread( + { uri: post.ref.uriStr, depth: 0 }, + { + headers: { ...sc.getHeaders(sc.dids.carol) }, + }, + ) + expect(threadPds.thread.post).toEqual( + expect.objectContaining({ uri: post.ref.uriStr, cid: post.ref.cidStr }), + ) + expect(threadAppview.thread.post).toEqual( + expect.objectContaining({ uri: post.ref.uriStr, cid: post.ref.cidStr }), + ) + }) + + it('does not persist actions and reports on pds.', async () => { + const { db } = network.pds.ctx + const actions = await db.db + .selectFrom('moderation_action') + .selectAll() + .execute() + const reports = await db.db + .selectFrom('moderation_report') + .selectAll() + .execute() + expect(actions).toEqual([]) + expect(reports).toEqual([]) + }) +}) diff --git a/packages/pds/tests/proxied/feedgen.test.ts b/packages/pds/tests/proxied/feedgen.test.ts index 2914813750d..305b12fc230 100644 --- a/packages/pds/tests/proxied/feedgen.test.ts +++ b/packages/pds/tests/proxied/feedgen.test.ts @@ -36,7 +36,6 @@ describe('feedgen proxy view', () => { sc.getHeaders(sc.dids.alice), ) await network.processAll() - await network.bsky.ctx.backgroundQueue.processAll() // mock getFeedGenerator() for use by pds's getFeed since we don't have a proper feedGenDid or feed publisher FeedNS.prototype.getFeedGenerator = async function (params, opts) { if (params?.feed === feedUri.toString()) { @@ -70,7 +69,7 @@ describe('feedgen proxy view', () => { const { data: feed } = await agent.api.app.bsky.feed.getFeed( { feed: feedUri.toString() }, { - headers: { ...sc.getHeaders(sc.dids.alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(sc.dids.alice) }, }, ) expect(forSnapshot(feed)).toMatchSnapshot() diff --git a/packages/pds/tests/proxied/notif.test.ts b/packages/pds/tests/proxied/notif.test.ts new file mode 100644 index 00000000000..106620d4bcd --- /dev/null +++ b/packages/pds/tests/proxied/notif.test.ts @@ -0,0 +1,91 @@ +import { once } from 'events' +import http from 'http' +import { AddressInfo } from 'net' +import express from 'express' +import AtpAgent from '@atproto/api' +import { TestNetworkNoAppView } from '@atproto/dev-env' +import { verifyJwt } from '@atproto/xrpc-server' +import { SeedClient } from '../seeds/client' +import usersSeed from '../seeds/users' +import { createServer } from '../../src/lexicon' + +describe('notif service proxy', () => { + let network: TestNetworkNoAppView + let notifServer: http.Server + let notifDid: string + let agent: AtpAgent + let sc: SeedClient + const spy: { current: unknown } = { current: null } + + beforeAll(async () => { + network = await TestNetworkNoAppView.create({ + dbPostgresSchema: 'proxy_notifs', + }) + network.pds.server.app.get + const plc = network.plc.getClient() + agent = network.pds.getClient() + sc = new SeedClient(agent) + await usersSeed(sc) + await network.processAll() + // piggybacking existing plc did, turn it into a notif service + notifServer = await createMockNotifService(spy) + notifDid = sc.dids.dan + await plc.updateData(notifDid, network.pds.ctx.plcRotationKey, (x) => { + const addr = notifServer.address() as AddressInfo + x.services['bsky_notif'] = { + type: 'BskyNotificationService', + endpoint: `http://localhost:${addr.port}`, + } + return x + }) + }) + + afterAll(async () => { + await network.close() + notifServer.close() + await once(notifServer, 'close') + }) + + it('proxies to notif service.', async () => { + await agent.api.app.bsky.notification.registerPush( + { + serviceDid: notifDid, + token: 'tok1', + platform: 'web', + appId: 'app1', + }, + { + headers: sc.getHeaders(sc.dids.bob), + encoding: 'application/json', + }, + ) + expect(spy.current?.['input']).toEqual({ + serviceDid: notifDid, + token: 'tok1', + platform: 'web', + appId: 'app1', + }) + + const auth = await verifyJwt( + spy.current?.['jwt'] as string, + notifDid, + async () => network.pds.ctx.repoSigningKey.did(), + ) + expect(auth).toEqual(sc.dids.bob) + }) +}) + +async function createMockNotifService(ref: { current: unknown }) { + const app = express() + const svc = createServer() + svc.app.bsky.notification.registerPush(({ input, req }) => { + ref.current = { + input: input.body, + jwt: req.headers.authorization?.replace('Bearer ', ''), + } + }) + app.use(svc.xrpc.router) + const server = app.listen() + await once(server, 'listening') + return server +} diff --git a/packages/pds/tests/proxied/procedures.test.ts b/packages/pds/tests/proxied/procedures.test.ts index 74d1ea1704b..8fee0b35a5c 100644 --- a/packages/pds/tests/proxied/procedures.test.ts +++ b/packages/pds/tests/proxied/procedures.test.ts @@ -20,7 +20,6 @@ describe('proxies appview procedures', () => { sc = new SeedClient(agent) await basicSeed(sc) await network.processAll() - agent.api.setHeader('x-appview-proxy', 'true') alice = sc.dids.alice bob = sc.dids.bob carol = sc.dids.carol @@ -110,6 +109,7 @@ describe('proxies appview procedures', () => { encoding: 'application/json', }, ) + await network.processAll() // check const { data: result1 } = await agent.api.app.bsky.graph.getListMutes( {}, diff --git a/packages/pds/tests/proxied/read-after-write.test.ts b/packages/pds/tests/proxied/read-after-write.test.ts new file mode 100644 index 00000000000..06df91fa53f --- /dev/null +++ b/packages/pds/tests/proxied/read-after-write.test.ts @@ -0,0 +1,178 @@ +import util from 'util' +import AtpAgent from '@atproto/api' +import { TestNetwork } from '@atproto/dev-env' +import { RecordRef, SeedClient } from '../seeds/client' +import basicSeed from '../seeds/basic' +import { ThreadViewPost } from '../../src/lexicon/types/app/bsky/feed/defs' +import { View as RecordEmbedView } from '../../src/lexicon/types/app/bsky/embed/record' + +describe('proxy read after write', () => { + let network: TestNetwork + let agent: AtpAgent + let sc: SeedClient + + let alice: string + let carol: string + + beforeAll(async () => { + network = await TestNetwork.create({ + dbPostgresSchema: 'proxy_read_after_write', + }) + agent = network.pds.getClient() + sc = new SeedClient(agent) + await basicSeed(sc) + await network.processAll() + alice = sc.dids.alice + carol = sc.dids.carol + await network.bsky.sub.destroy() + }) + + afterAll(async () => { + await network.close() + }) + + it('handles read after write on profiles', async () => { + await sc.updateProfile(alice, { displayName: 'blah' }) + const res = await agent.api.app.bsky.actor.getProfile( + { actor: alice }, + { headers: { ...sc.getHeaders(alice) } }, + ) + expect(res.data.displayName).toEqual('blah') + expect(res.data.description).toBeUndefined() + }) + + it('handles image formatting', async () => { + const blob = await sc.uploadFile( + alice, + 'tests/sample-img/key-landscape-small.jpg', + 'image/jpeg', + ) + await sc.updateProfile(alice, { displayName: 'blah', avatar: blob.image }) + + const res = await agent.api.app.bsky.actor.getProfile( + { actor: alice }, + { headers: { ...sc.getHeaders(alice) } }, + ) + expect(res.data.avatar).toEqual( + util.format( + network.pds.ctx.cfg.bskyAppView.cdnUrlPattern, + 'avatar', + alice, + blob.image.ref.toString(), + ), + ) + }) + + it('handles read after write on getAuthorFeed', async () => { + const res = await agent.api.app.bsky.feed.getAuthorFeed( + { actor: alice }, + { headers: { ...sc.getHeaders(alice) } }, + ) + for (const item of res.data.feed) { + if (item.post.author.did === alice) { + expect(item.post.author.displayName).toEqual('blah') + } + } + }) + + let replyRef1: RecordRef + let replyRef2: RecordRef + + it('handles read after write on threads', async () => { + const reply1 = await sc.reply( + alice, + sc.posts[alice][0].ref, + sc.posts[alice][0].ref, + 'another reply', + ) + const reply2 = await sc.reply( + alice, + sc.posts[alice][0].ref, + reply1.ref, + 'another another reply', + ) + replyRef1 = reply1.ref + replyRef2 = reply2.ref + const res = await agent.api.app.bsky.feed.getPostThread( + { uri: sc.posts[alice][0].ref.uriStr }, + { headers: { ...sc.getHeaders(alice) } }, + ) + const layerOne = res.data.thread.replies as ThreadViewPost[] + expect(layerOne.length).toBe(1) + expect(layerOne[0].post.uri).toEqual(reply1.ref.uriStr) + const layerTwo = layerOne[0].replies as ThreadViewPost[] + expect(layerTwo.length).toBe(1) + expect(layerTwo[0].post.uri).toEqual(reply2.ref.uriStr) + }) + + it('handles read after write on a thread that is not found on appview', async () => { + const res = await agent.api.app.bsky.feed.getPostThread( + { uri: replyRef1.uriStr }, + { headers: { ...sc.getHeaders(alice) } }, + ) + const thread = res.data.thread as ThreadViewPost + expect(thread.post.uri).toEqual(replyRef1.uriStr) + expect((thread.parent as ThreadViewPost).post.uri).toEqual( + sc.posts[alice][0].ref.uriStr, + ) + expect(thread.replies?.length).toEqual(1) + expect((thread.replies?.at(0) as ThreadViewPost).post.uri).toEqual( + replyRef2.uriStr, + ) + }) + + it('handles read after write on threads with record embeds', async () => { + const replyRes = await agent.api.app.bsky.feed.post.create( + { repo: alice }, + { + text: 'blah', + reply: { + root: sc.posts[carol][0].ref.raw, + parent: sc.posts[carol][0].ref.raw, + }, + embed: { + $type: 'app.bsky.embed.record', + record: sc.posts[alice][0].ref.raw, + }, + createdAt: new Date().toISOString(), + }, + sc.getHeaders(alice), + ) + const res = await agent.api.app.bsky.feed.getPostThread( + { uri: sc.posts[carol][0].ref.uriStr }, + { headers: { ...sc.getHeaders(alice) } }, + ) + const replies = res.data.thread.replies as ThreadViewPost[] + expect(replies.length).toBe(1) + expect(replies[0].post.uri).toEqual(replyRes.uri) + const embed = replies[0].post.embed as RecordEmbedView + expect(embed.record.uri).toEqual(sc.posts[alice][0].ref.uriStr) + }) + + it('handles read after write on getTimeline', async () => { + const postRes = await agent.api.app.bsky.feed.post.create( + { repo: alice }, + { + text: 'poast', + createdAt: new Date().toISOString(), + }, + sc.getHeaders(alice), + ) + const res = await agent.api.app.bsky.feed.getTimeline( + {}, + { headers: { ...sc.getHeaders(alice) } }, + ) + expect(res.data.feed[0].post.uri).toEqual(postRes.uri) + }) + + it('returns lag headers', async () => { + const res = await agent.api.app.bsky.feed.getTimeline( + {}, + { headers: { ...sc.getHeaders(alice) } }, + ) + const lag = res.headers['atproto-upstream-lag'] + expect(lag).toBeDefined() + const parsed = parseInt(lag) + expect(parsed > 0).toBe(true) + }) +}) diff --git a/packages/pds/tests/proxied/views.test.ts b/packages/pds/tests/proxied/views.test.ts index f45384dc449..43ea29e5d23 100644 --- a/packages/pds/tests/proxied/views.test.ts +++ b/packages/pds/tests/proxied/views.test.ts @@ -52,7 +52,7 @@ describe('proxies view requests', () => { actor: bob, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect(forSnapshot(res.data)).toMatchSnapshot() @@ -64,7 +64,7 @@ describe('proxies view requests', () => { actors: [alice, bob], }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect(forSnapshot(res.data)).toMatchSnapshot() @@ -77,15 +77,16 @@ describe('proxies view requests', () => { { did: sc.dids.carol, order: 2 }, { did: sc.dids.dan, order: 3 }, ] - await network.bsky.ctx.db.db - .insertInto('suggested_follow') + await network.bsky.ctx.db + .getPrimary() + .db.insertInto('suggested_follow') .values(suggestions) .execute() const res = await agent.api.app.bsky.actor.getSuggestions( {}, { - headers: { ...sc.getHeaders(carol), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(carol) }, }, ) expect(forSnapshot(res.data)).toMatchSnapshot() @@ -94,7 +95,7 @@ describe('proxies view requests', () => { limit: 1, }, { - headers: { ...sc.getHeaders(carol), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(carol) }, }, ) const pt2 = await agent.api.app.bsky.actor.getSuggestions( @@ -102,7 +103,7 @@ describe('proxies view requests', () => { cursor: pt1.data.cursor, }, { - headers: { ...sc.getHeaders(carol), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(carol) }, }, ) expect([...pt1.data.actors, ...pt2.data.actors]).toEqual(res.data.actors) @@ -114,17 +115,21 @@ describe('proxies view requests', () => { term: '.test', }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) - expect(forSnapshot(res.data)).toMatchSnapshot() + // sort because pagination is done off of did + const sortedFull = res.data.actors.sort((a, b) => + a.handle > b.handle ? 1 : -1, + ) + expect(forSnapshot(sortedFull)).toMatchSnapshot() const pt1 = await agent.api.app.bsky.actor.searchActors( { term: '.test', limit: 1, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) const pt2 = await agent.api.app.bsky.actor.searchActors( @@ -133,10 +138,13 @@ describe('proxies view requests', () => { cursor: pt1.data.cursor, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) - expect([...pt1.data.actors, ...pt2.data.actors]).toEqual(res.data.actors) + const sortedPaginated = [...pt1.data.actors, ...pt2.data.actors].sort( + (a, b) => (a.handle > b.handle ? 1 : -1), + ) + expect(sortedPaginated).toEqual(sortedFull) }) it('actor.searchActorTypeahead', async () => { @@ -145,10 +153,13 @@ describe('proxies view requests', () => { term: '.test', }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) - expect(forSnapshot(res.data)).toMatchSnapshot() + const sorted = res.data.actors.sort((a, b) => + a.handle > b.handle ? 1 : -1, + ) + expect(forSnapshot(sorted)).toMatchSnapshot() }) it('feed.getAuthorFeed', async () => { @@ -157,7 +168,7 @@ describe('proxies view requests', () => { actor: bob, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect(forSnapshot(res.data)).toMatchSnapshot() @@ -167,7 +178,7 @@ describe('proxies view requests', () => { limit: 1, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) const pt2 = await agent.api.app.bsky.feed.getAuthorFeed( @@ -176,7 +187,7 @@ describe('proxies view requests', () => { cursor: pt1.data.cursor, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect([...pt1.data.feed, ...pt2.data.feed]).toEqual(res.data.feed) @@ -189,7 +200,7 @@ describe('proxies view requests', () => { uri: postUri, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect(forSnapshot(res.data)).toMatchSnapshot() @@ -199,7 +210,7 @@ describe('proxies view requests', () => { limit: 1, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) const pt2 = await agent.api.app.bsky.feed.getLikes( @@ -208,7 +219,7 @@ describe('proxies view requests', () => { cursor: pt1.data.cursor, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect([...pt1.data.likes, ...pt2.data.likes]).toEqual(res.data.likes) @@ -221,7 +232,7 @@ describe('proxies view requests', () => { uri: postUri, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect(forSnapshot(res.data)).toMatchSnapshot() @@ -231,7 +242,7 @@ describe('proxies view requests', () => { limit: 1, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) const pt2 = await agent.api.app.bsky.feed.getRepostedBy( @@ -240,7 +251,7 @@ describe('proxies view requests', () => { cursor: pt1.data.cursor, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect([...pt1.data.repostedBy, ...pt2.data.repostedBy]).toEqual( @@ -255,7 +266,7 @@ describe('proxies view requests', () => { uris, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect(forSnapshot(res.data)).toMatchSnapshot() @@ -265,16 +276,17 @@ describe('proxies view requests', () => { const res = await agent.api.app.bsky.feed.getTimeline( {}, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) + expect(forSnapshot(res.data)).toMatchSnapshot() const pt1 = await agent.api.app.bsky.feed.getTimeline( { limit: 2, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) const pt2 = await agent.api.app.bsky.feed.getTimeline( @@ -282,7 +294,7 @@ describe('proxies view requests', () => { cursor: pt1.data.cursor, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect([...pt1.data.feed, ...pt2.data.feed]).toEqual(res.data.feed) @@ -292,7 +304,7 @@ describe('proxies view requests', () => { const res = await agent.api.app.bsky.unspecced.getPopularFeedGenerators( {}, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect(forSnapshot(res.data)).toMatchSnapshot() @@ -326,7 +338,7 @@ describe('proxies view requests', () => { const res = await agent.api.app.bsky.feed.getFeedGenerator( { feed: feedUri }, { - headers: { ...sc.getHeaders(sc.dids.alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(sc.dids.alice) }, }, ) expect(forSnapshot(res.data)).toMatchSnapshot() @@ -336,7 +348,7 @@ describe('proxies view requests', () => { const res = await agent.api.app.bsky.feed.getFeedGenerators( { feeds: [feedUri.toString()] }, { - headers: { ...sc.getHeaders(sc.dids.alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(sc.dids.alice) }, }, ) expect(forSnapshot(res.data)).toMatchSnapshot() @@ -349,7 +361,7 @@ describe('proxies view requests', () => { const res = await agent.api.app.bsky.graph.getBlocks( {}, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect(forSnapshot(res.data)).toMatchSnapshot() @@ -358,7 +370,7 @@ describe('proxies view requests', () => { limit: 1, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) const pt2 = await agent.api.app.bsky.graph.getBlocks( @@ -366,7 +378,7 @@ describe('proxies view requests', () => { cursor: pt1.data.cursor, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect([...pt1.data.blocks, ...pt2.data.blocks]).toEqual(res.data.blocks) @@ -379,7 +391,7 @@ describe('proxies view requests', () => { const res = await agent.api.app.bsky.graph.getFollows( { actor: bob }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect(forSnapshot(res.data)).toMatchSnapshot() @@ -389,7 +401,7 @@ describe('proxies view requests', () => { limit: 1, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) const pt2 = await agent.api.app.bsky.graph.getFollows( @@ -398,7 +410,7 @@ describe('proxies view requests', () => { cursor: pt1.data.cursor, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect([...pt1.data.follows, ...pt2.data.follows]).toEqual(res.data.follows) @@ -408,7 +420,7 @@ describe('proxies view requests', () => { const res = await agent.api.app.bsky.graph.getFollowers( { actor: bob }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect(forSnapshot(res.data)).toMatchSnapshot() @@ -418,7 +430,7 @@ describe('proxies view requests', () => { limit: 1, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) const pt2 = await agent.api.app.bsky.graph.getFollowers( @@ -427,7 +439,7 @@ describe('proxies view requests', () => { cursor: pt1.data.cursor, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect([...pt1.data.followers, ...pt2.data.followers]).toEqual( @@ -482,7 +494,7 @@ describe('proxies view requests', () => { const res = await agent.api.app.bsky.graph.getList( { list: listUri }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect(forSnapshot(res.data)).toMatchSnapshot() @@ -492,7 +504,7 @@ describe('proxies view requests', () => { limit: 1, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) const pt2 = await agent.api.app.bsky.graph.getList( @@ -501,7 +513,7 @@ describe('proxies view requests', () => { cursor: pt1.data.cursor, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect([...pt1.data.items, ...pt2.data.items]).toEqual(res.data.items) @@ -511,7 +523,7 @@ describe('proxies view requests', () => { const res = await agent.api.app.bsky.graph.getLists( { actor: bob }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect(forSnapshot(res.data)).toMatchSnapshot() @@ -521,7 +533,7 @@ describe('proxies view requests', () => { limit: 1, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) const pt2 = await agent.api.app.bsky.graph.getLists( @@ -530,9 +542,32 @@ describe('proxies view requests', () => { cursor: pt1.data.cursor, }, { - headers: { ...sc.getHeaders(alice), 'x-appview-proxy': 'true' }, + headers: { ...sc.getHeaders(alice) }, }, ) expect([...pt1.data.lists, ...pt2.data.lists]).toEqual(res.data.lists) }) + + it('graph.getListBlocks', async () => { + await agent.api.app.bsky.graph.listblock.create( + { repo: bob }, + { + subject: listUri, + createdAt: new Date().toISOString(), + }, + sc.getHeaders(bob), + ) + await network.processAll() + const pt1 = await agent.api.app.bsky.graph.getListBlocks( + {}, + { headers: sc.getHeaders(bob) }, + ) + expect(forSnapshot(pt1.data)).toMatchSnapshot() + const pt2 = await agent.api.app.bsky.graph.getListBlocks( + { cursor: pt1.data.cursor }, + { headers: sc.getHeaders(bob) }, + ) + expect(pt2.data.lists).toEqual([]) + expect(pt2.data.cursor).not.toBeDefined() + }) }) diff --git a/packages/pds/tests/races.test.ts b/packages/pds/tests/races.test.ts index 90680ad5ec6..7f276e61147 100644 --- a/packages/pds/tests/races.test.ts +++ b/packages/pds/tests/races.test.ts @@ -2,9 +2,9 @@ import AtpAgent from '@atproto/api' import { CloseFn, runTestServer } from './_util' import AppContext from '../src/context' import { PreparedWrite, prepareCreate } from '../src/repo' -import { TID, wait } from '@atproto/common' +import { wait } from '@atproto/common' import SqlRepoStorage from '../src/sql-repo-storage' -import { CommitData, MemoryBlockstore, loadFullRepo } from '@atproto/repo' +import { CommitData, readCarWithRoot, verifyRepo } from '@atproto/repo' import { ConcurrentWriteError } from '../src/services/repo' describe('crud operations', () => { @@ -40,7 +40,6 @@ describe('crud operations', () => { text: 'one', createdAt: new Date().toISOString(), }, - rkey: TID.nextStr(), validate: true, }) const storage = new SqlRepoStorage(ctx.db, did) @@ -59,8 +58,8 @@ describe('crud operations', () => { const now = new Date().toISOString() await ctx.db.transaction(async (dbTxn) => { const storage = new SqlRepoStorage(dbTxn, did, now) - const locked = await storage.lockHead() - if (!locked || !locked.equals(commitData.prev)) { + const locked = await storage.lockRepo() + if (!locked) { throw new ConcurrentWriteError() } await wait(waitMs) @@ -90,19 +89,17 @@ describe('crud operations', () => { const listed = await agent.api.app.bsky.feed.post.list({ repo: did }) expect(listed.records.length).toBe(2) - const repoCar = await agent.api.com.atproto.sync.getRepo({ did }) - const storage = new MemoryBlockstore() - const verified = await loadFullRepo( - storage, - repoCar.data, + const carRes = await agent.api.com.atproto.sync.getRepo({ did }) + const car = await readCarWithRoot(carRes.data) + const verified = await verifyRepo( + car.blocks, + car.root, did, ctx.repoSigningKey.did(), ) - // it split writes over 2 commits - expect(verified.writeLog[1].length).toBe(1) - expect(verified.writeLog[2].length).toBe(1) - expect(verified.writeLog[1][0].cid.equals(write.cid)).toBeTruthy() - expect(verified.writeLog[2][0].cid.toString()).toEqual( + expect(verified.creates.length).toBe(2) + expect(verified.creates[0].cid.equals(write.cid)).toBeTruthy() + expect(verified.creates[1].cid.toString()).toEqual( createdPost.cid.toString(), ) }) diff --git a/packages/pds/tests/rate-limits.test.ts b/packages/pds/tests/rate-limits.test.ts new file mode 100644 index 00000000000..6f7cd77cbb8 --- /dev/null +++ b/packages/pds/tests/rate-limits.test.ts @@ -0,0 +1,69 @@ +import { runTestServer, TestServerInfo } from './_util' +import { SeedClient } from './seeds/client' +import userSeed from './seeds/basic' +import { AtpAgent } from '@atproto/api' +import { randomStr } from '@atproto/crypto' + +describe('rate limits', () => { + let server: TestServerInfo + let agent: AtpAgent + let sc: SeedClient + let alice: string + let bob: string + + beforeAll(async () => { + server = await runTestServer({ + dbPostgresSchema: 'rate_limits', + redisScratchAddress: process.env.REDIS_HOST, + redisScratchPassword: process.env.REDIS_PASSWORD, + rateLimitsEnabled: true, + }) + agent = new AtpAgent({ service: server.url }) + sc = new SeedClient(agent) + await userSeed(sc) + alice = sc.dids.alice + bob = sc.dids.bob + }) + + afterAll(async () => { + await server.close() + }) + + it('rate limits by ip', async () => { + const attempt = () => + agent.api.com.atproto.server.resetPassword({ + token: randomStr(4, 'base32'), + password: 'asdf1234', + }) + for (let i = 0; i < 50; i++) { + try { + await attempt() + } catch (err) { + // do nothing + } + } + await expect(attempt).rejects.toThrow('Rate Limit Exceeded') + }) + + it('rate limits by a custom key', async () => { + const attempt = () => + agent.api.com.atproto.server.createSession({ + identifier: sc.accounts[alice].handle, + password: 'asdf1234', + }) + for (let i = 0; i < 30; i++) { + try { + await attempt() + } catch (err) { + // do nothing + } + } + await expect(attempt).rejects.toThrow('Rate Limit Exceeded') + + // does not rate limit for another key + await agent.api.com.atproto.server.createSession({ + identifier: sc.accounts[bob].handle, + password: sc.accounts[bob].password, + }) + }) +}) diff --git a/packages/pds/tests/rebase.test.ts b/packages/pds/tests/rebase.test.ts deleted file mode 100644 index 7d771064cf2..00000000000 --- a/packages/pds/tests/rebase.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import AtpAgent from '@atproto/api' -import * as repo from '@atproto/repo' -import { MemoryBlockstore } from '@atproto/repo' -import { AppContext } from '../src' -import { CloseFn, runTestServer } from './_util' -import { SeedClient } from './seeds/client' - -describe('repo rebases', () => { - let agent: AtpAgent - let sc: SeedClient - let alice: string - - let ctx: AppContext - - let close: CloseFn - - beforeAll(async () => { - const server = await runTestServer({ - dbPostgresSchema: 'repo_rebase', - }) - ctx = server.ctx - close = server.close - agent = new AtpAgent({ service: server.url }) - sc = new SeedClient(agent) - await sc.createAccount('alice', { - email: 'alice@test.com', - handle: 'alice.test', - password: 'alice-pass', - }) - alice = sc.dids.alice - }) - - afterAll(async () => { - await close() - }) - - it('handles rebases', async () => { - for (let i = 0; i < 40; i++) { - await sc.post(alice, `post-${i}`) - } - - const carBefore = await agent.api.com.atproto.sync.getRepo({ did: alice }) - - await agent.api.com.atproto.repo.rebaseRepo( - { repo: alice }, - { encoding: 'application/json', headers: sc.getHeaders(alice) }, - ) - - const commitPath = await agent.api.com.atproto.sync.getCommitPath({ - did: alice, - }) - expect(commitPath.data.commits.length).toBe(1) - const carAfter = await agent.api.com.atproto.sync.getRepo({ did: alice }) - - const before = await repo.loadFullRepo( - new MemoryBlockstore(), - carBefore.data, - alice, - ctx.repoSigningKey.did(), - ) - const after = await repo.loadFullRepo( - new MemoryBlockstore(), - carAfter.data, - alice, - ctx.repoSigningKey.did(), - ) - const contentsBefore = await before.repo.getContents() - const contentsAfter = await after.repo.getContents() - expect(contentsAfter).toEqual(contentsBefore) - expect(after.repo.commit.prev).toBe(null) - - const repoBlobs = await ctx.db.db - .selectFrom('repo_blob') - .where('did', '=', alice) - .selectAll() - .execute() - expect( - repoBlobs.every((row) => row.commit === commitPath[0].tostring()), - ).toBeTruthy() - }) -}) diff --git a/packages/pds/tests/seeds/basic.ts b/packages/pds/tests/seeds/basic.ts index bfaa62406f8..cf3bd83bbc3 100644 --- a/packages/pds/tests/seeds/basic.ts +++ b/packages/pds/tests/seeds/basic.ts @@ -4,8 +4,8 @@ import { adminAuth } from '../_util' import { SeedClient } from './client' import usersSeed from './users' -export default async (sc: SeedClient) => { - await usersSeed(sc) +export default async (sc: SeedClient, invite?: { code: string }) => { + await usersSeed(sc, invite) const alice = sc.dids.alice const bob = sc.dids.bob @@ -25,7 +25,12 @@ export default async (sc: SeedClient) => { await sc.follow(bob, alice) await sc.follow(bob, carol, createdAtMicroseconds()) await sc.follow(dan, bob, createdAtTimezone()) - await sc.post(alice, posts.alice[0]) + await sc.post(alice, posts.alice[0], undefined, undefined, undefined, { + labels: { + $type: 'com.atproto.label.defs#selfLabels', + values: [{ val: 'self-label' }], + }, + }) await sc.post(bob, posts.bob[0], undefined, undefined, undefined, { langs: ['en-US', 'i-klingon'], }) @@ -114,7 +119,7 @@ export default async (sc: SeedClient) => { sc.posts[alice][1].ref, replies.carol[0], ) - await sc.reply( + const alicesReplyToBob = await sc.reply( alice, sc.posts[alice][1].ref, sc.replies[bob][0].ref, @@ -122,6 +127,7 @@ export default async (sc: SeedClient) => { ) await sc.repost(carol, sc.posts[dan][1].ref) await sc.repost(dan, sc.posts[alice][1].ref) + await sc.repost(dan, alicesReplyToBob.ref) await sc.agent.com.atproto.admin.takeModerationAction( { diff --git a/packages/pds/tests/seeds/client.ts b/packages/pds/tests/seeds/client.ts index 639db353a45..0f6680f6a52 100644 --- a/packages/pds/tests/seeds/client.ts +++ b/packages/pds/tests/seeds/client.ts @@ -7,9 +7,10 @@ import { InputSchema as CreateReportInput } from '@atproto/api/src/client/types/ import { Record as PostRecord } from '@atproto/api/src/client/types/app/bsky/feed/post' import { Record as LikeRecord } from '@atproto/api/src/client/types/app/bsky/feed/like' import { Record as FollowRecord } from '@atproto/api/src/client/types/app/bsky/graph/follow' -import { AtUri } from '@atproto/uri' +import { AtUri } from '@atproto/syntax' import { BlobRef } from '@atproto/lexicon' import { adminAuth } from '../_util' +import { ids } from '../../src/lexicon/lexicons' // Makes it simple to create data via the XRPC client, // and keeps track of all created data in memory for convenience. @@ -121,7 +122,7 @@ export class SeedClient { by: string, displayName: string, description: string, - fromUser?: string, + selfLabels?: string[], ) { AVATAR_IMG ??= await fs.readFile('tests/sample-img/key-portrait-small.jpg') @@ -129,7 +130,7 @@ export class SeedClient { { const res = await this.agent.api.com.atproto.repo.uploadBlob(AVATAR_IMG, { encoding: 'image/jpeg', - headers: this.getHeaders(fromUser || by), + headers: this.getHeaders(by), } as any) avatarBlob = res.data.blob } @@ -141,8 +142,14 @@ export class SeedClient { displayName, description, avatar: avatarBlob, + labels: selfLabels + ? { + $type: 'com.atproto.label.defs#selfLabels', + values: selfLabels.map((val) => ({ val })), + } + : undefined, }, - this.getHeaders(fromUser || by), + this.getHeaders(by), ) this.profiles[by] = { displayName, @@ -154,6 +161,24 @@ export class SeedClient { return this.profiles[by] } + async updateProfile(by: string, record: Record) { + const res = await this.agent.api.com.atproto.repo.putRecord( + { + repo: by, + collection: ids.AppBskyActorProfile, + rkey: 'self', + record, + }, + { headers: this.getHeaders(by), encoding: 'application/json' }, + ) + this.profiles[by] = { + ...(this.profiles[by] ?? {}), + ...record, + ref: new RecordRef(res.data.uri, res.data.cid), + } + return this.profiles[by] + } + async follow(from: string, to: string, overrides?: Partial) { const res = await this.agent.api.app.bsky.graph.follow.create( { repo: from }, diff --git a/packages/pds/tests/seeds/users.ts b/packages/pds/tests/seeds/users.ts index 0a7d530d998..2ef9a74864f 100644 --- a/packages/pds/tests/seeds/users.ts +++ b/packages/pds/tests/seeds/users.ts @@ -1,20 +1,22 @@ import { SeedClient } from './client' -export default async (sc: SeedClient) => { - await sc.createAccount('alice', users.alice) - await sc.createAccount('bob', users.bob) - await sc.createAccount('carol', users.carol) - await sc.createAccount('dan', users.dan) +export default async (sc: SeedClient, invite?: { code: string }) => { + await sc.createAccount('alice', { ...users.alice, inviteCode: invite?.code }) + await sc.createAccount('bob', { ...users.bob, inviteCode: invite?.code }) + await sc.createAccount('carol', { ...users.carol, inviteCode: invite?.code }) + await sc.createAccount('dan', { ...users.dan, inviteCode: invite?.code }) await sc.createProfile( sc.dids.alice, users.alice.displayName, users.alice.description, + users.alice.selfLabels, ) await sc.createProfile( sc.dids.bob, users.bob.displayName, users.bob.description, + users.alice.selfLabels, ) return sc @@ -27,6 +29,7 @@ const users = { password: 'alice-pass', displayName: 'ali', description: 'its me!', + selfLabels: ['self-label-a', 'self-label-b'], }, bob: { email: 'bob@test.com', @@ -34,6 +37,7 @@ const users = { password: 'bob-pass', displayName: 'bobby', description: 'hi im bob label_me', + selfLabels: undefined, }, carol: { email: 'carol@test.com', @@ -41,6 +45,7 @@ const users = { password: 'carol-pass', displayName: undefined, description: undefined, + selfLabels: undefined, }, dan: { email: 'dan@test.com', @@ -48,5 +53,6 @@ const users = { password: 'dan-pass', displayName: undefined, description: undefined, + selfLabels: undefined, }, } diff --git a/packages/pds/tests/sequencer.test.ts b/packages/pds/tests/sequencer.test.ts index dab71fba74d..c41cedafb58 100644 --- a/packages/pds/tests/sequencer.test.ts +++ b/packages/pds/tests/sequencer.test.ts @@ -81,7 +81,7 @@ describe('sequencer', () => { const caughtUp = (outbox: Outbox): (() => Promise) => { return async () => { - const leaderCaughtUp = await server.ctx.sequencerLeader.isCaughtUp() + const leaderCaughtUp = await server.ctx.sequencerLeader?.isCaughtUp() if (!leaderCaughtUp) return false const lastEvt = await outbox.sequencer.curr() if (!lastEvt) return true diff --git a/packages/pds/tests/server.test.ts b/packages/pds/tests/server.test.ts index 3036ddcf43a..c4ca73c72c7 100644 --- a/packages/pds/tests/server.test.ts +++ b/packages/pds/tests/server.test.ts @@ -2,39 +2,39 @@ import { AddressInfo } from 'net' import express from 'express' import axios, { AxiosError } from 'axios' import AtpAgent from '@atproto/api' -import { CloseFn, runTestServer, TestServerInfo } from './_util' import { handler as errorHandler } from '../src/error' import { SeedClient } from './seeds/client' -import usersSeed from './seeds/users' +import basicSeed from './seeds/basic' import { Database } from '../src' +import { TestNetwork } from '@atproto/dev-env' describe('server', () => { - let server: TestServerInfo - let close: CloseFn + let network: TestNetwork let db: Database let agent: AtpAgent let sc: SeedClient let alice: string beforeAll(async () => { - server = await runTestServer({ + network = await TestNetwork.create({ dbPostgresSchema: 'server', - version: '0.0.0', + pds: { + version: '0.0.0', + }, }) - close = server.close - db = server.ctx.db - agent = new AtpAgent({ service: server.url }) + db = network.pds.ctx.db + agent = network.pds.getClient() sc = new SeedClient(agent) - await usersSeed(sc) + await basicSeed(sc) alice = sc.dids.alice }) afterAll(async () => { - await close() + await network.close() }) it('preserves 404s.', async () => { - const promise = axios.get(`${server.url}/unknown`) + const promise = axios.get(`${network.pds.url}/unknown`) await expect(promise).rejects.toThrow('failed with status code 404') }) @@ -61,17 +61,11 @@ describe('server', () => { } }) - it('healthcheck succeeds when database is available.', async () => { - const { data, status } = await axios.get(`${server.url}/xrpc/_health`) - expect(status).toEqual(200) - expect(data).toEqual({ version: '0.0.0' }) - }) - it('limits size of json input.', async () => { let error: AxiosError try { await axios.post( - `${server.url}/xrpc/com.atproto.repo.createRecord`, + `${network.pds.url}/xrpc/com.atproto.repo.createRecord`, { data: 'x'.repeat(100 * 1024), // 100kb }, @@ -92,14 +86,48 @@ describe('server', () => { }) }) + it('compresses large json responses', async () => { + const res = await axios.get( + `${network.pds.url}/xrpc/app.bsky.feed.getTimeline`, + { + decompress: false, + headers: { ...sc.getHeaders(alice), 'accept-encoding': 'gzip' }, + }, + ) + + expect(res.headers['content-encoding']).toEqual('gzip') + }) + + it('compresses large car file responses', async () => { + const res = await axios.get( + `${network.pds.url}/xrpc/com.atproto.sync.getRepo?did=${alice}`, + { decompress: false, headers: { 'accept-encoding': 'gzip' } }, + ) + expect(res.headers['content-encoding']).toEqual('gzip') + }) + + it('does not compress small payloads', async () => { + const res = await axios.get(`${network.pds.url}/xrpc/_health`, { + decompress: false, + headers: { 'accept-encoding': 'gzip' }, + }) + expect(res.headers['content-encoding']).toBeUndefined() + }) + + it('healthcheck succeeds when database is available.', async () => { + const { data, status } = await axios.get(`${network.pds.url}/xrpc/_health`) + expect(status).toEqual(200) + expect(data).toEqual({ version: '0.0.0' }) + }) + it('healthcheck fails when database is unavailable.', async () => { // destroy to release lock & allow db to close - await server.ctx.sequencerLeader.destroy() + await network.pds.ctx.sequencerLeader?.destroy() await db.close() let error: AxiosError try { - await axios.get(`${server.url}/xrpc/_health`) + await axios.get(`${network.pds.url}/xrpc/_health`) throw new Error('Healthcheck should have failed') } catch (err) { if (axios.isAxiosError(err)) { diff --git a/packages/pds/tests/sql-repo-storage.test.ts b/packages/pds/tests/sql-repo-storage.test.ts index 6f5069c615f..c19a8b41805 100644 --- a/packages/pds/tests/sql-repo-storage.test.ts +++ b/packages/pds/tests/sql-repo-storage.test.ts @@ -1,9 +1,10 @@ -import { range, dataToCborBlock } from '@atproto/common' -import { def } from '@atproto/repo' +import { range, dataToCborBlock, TID } from '@atproto/common' +import { CidSet, def } from '@atproto/repo' import BlockMap from '@atproto/repo/src/block-map' import { Database } from '../src' import SqlRepoStorage from '../src/sql-repo-storage' import { CloseFn, runTestServer } from './_util' +import { CID } from 'multiformats/cid' describe('sql repo storage', () => { let db: Database @@ -75,41 +76,48 @@ describe('sql repo storage', () => { blocks.slice(5, 10).forEach((block) => { blocks1.set(block.cid, block.bytes) }) + const toRemoveList = blocks0 + .entries() + .slice(0, 2) + .map((b) => b.cid) + const toRemove = new CidSet(toRemoveList) await db.transaction(async (dbTxn) => { const storage = new SqlRepoStorage(dbTxn, did) await storage.applyCommit({ - commit: commits[0].cid, + cid: commits[0].cid, + rev: TID.nextStr(), prev: null, - blocks: blocks0, + newBlocks: blocks0, + removedCids: new CidSet(), }) await storage.applyCommit({ - commit: commits[1].cid, + cid: commits[1].cid, prev: commits[0].cid, - blocks: blocks1, + rev: TID.nextStr(), + newBlocks: blocks1, + removedCids: toRemove, }) }) const storage = new SqlRepoStorage(db, did) - const head = await storage.getHead() + const head = await storage.getRoot() if (!head) { throw new Error('could not get repo head') } expect(head.toString()).toEqual(commits[1].cid.toString()) - const commitPath = await storage.getCommitPath(head, null) - if (!commitPath) { - throw new Error('could not get commit path') + + const cidsRes = await db.db + .selectFrom('ipld_block') + .where('creator', '=', did) + .select('cid') + .execute() + const allCids = new CidSet(cidsRes.map((row) => CID.parse(row.cid))) + for (const entry of blocks1.entries()) { + expect(allCids.has(entry.cid)).toBe(true) } - expect(commitPath.length).toBe(2) - expect(commitPath[0].equals(commits[0].cid)).toBeTruthy() - expect(commitPath[1].equals(commits[1].cid)).toBeTruthy() - const commitData = await storage.getCommits(head, null) - if (!commitData) { - throw new Error('could not get commit data') + for (const entry of blocks0.entries()) { + const shouldHave = !toRemove.has(entry.cid) + expect(allCids.has(entry.cid)).toBe(shouldHave) } - expect(commitData.length).toBe(2) - expect(commitData[0].commit.equals(commits[0].cid)).toBeTruthy() - expect(commitData[0].blocks.equals(blocks0)).toBeTruthy() - expect(commitData[1].commit.equals(commits[1].cid)).toBeTruthy() - expect(commitData[1].blocks.equals(blocks1)).toBeTruthy() }) }) diff --git a/packages/pds/tests/sync/subscribe-repos.test.ts b/packages/pds/tests/sync/subscribe-repos.test.ts index 2b9f3b59958..e9e008ae91d 100644 --- a/packages/pds/tests/sync/subscribe-repos.test.ts +++ b/packages/pds/tests/sync/subscribe-repos.test.ts @@ -8,12 +8,7 @@ import { } from '@atproto/common' import { randomStr } from '@atproto/crypto' import * as repo from '@atproto/repo' -import { - getWriteLog, - MemoryBlockstore, - readCar, - WriteOpAction, -} from '@atproto/repo' +import { readCar } from '@atproto/repo' import { byFrame, ErrorFrame, Frame, MessageFrame } from '@atproto/xrpc-server' import { WebSocket } from 'ws' import { @@ -64,16 +59,10 @@ describe('repo subscribe repos', () => { await close() }) - const getRepo = async (did: string) => { - const car = await agent.api.com.atproto.sync.getRepo({ did }) - const storage = new MemoryBlockstore() - const synced = await repo.loadFullRepo( - storage, - new Uint8Array(car.data), - did, - ctx.repoSigningKey.did(), - ) - return repo.Repo.load(storage, synced.root) + const getRepo = async (did: string): Promise => { + const carRes = await agent.api.com.atproto.sync.getRepo({ did }) + const car = await repo.readCarWithRoot(carRes.data) + return repo.verifyRepo(car.blocks, car.root, did, ctx.repoSigningKey.did()) } const getHandleEvts = (frames: Frame[]): HandleEvt[] => { @@ -149,33 +138,46 @@ describe('repo subscribe repos', () => { } const verifyRepo = async (did: string, evts: CommitEvt[]) => { - const didRepo = await getRepo(did) - const writeLog = await getWriteLog(didRepo.storage, didRepo.cid, null) - const commits = await didRepo.storage.getCommits(didRepo.cid, null) - if (!commits) { - return expect(commits !== null) + const fromRpc = await getRepo(did) + const contents = {} as Record> + const allBlocks = new repo.BlockMap() + for (const evt of evts) { + const car = await readCar(evt.blocks) + allBlocks.addMap(car.blocks) + for (const op of evt.ops) { + const { collection, rkey } = repo.parseDataKey(op.path) + if (op.action === 'delete') { + delete contents[collection][rkey] + } else { + if (op.cid) { + contents[collection] ??= {} + contents[collection][rkey] ??= op.cid + } + } + } + } + for (const write of fromRpc.creates) { + expect(contents[write.collection][write.rkey].equals(write.cid)).toBe( + true, + ) + } + const lastCommit = evts.at(-1)?.commit + if (!lastCommit) { + throw new Error('no last commit') } - expect(evts.length).toBe(commits.length) - expect(evts.length).toBe(writeLog.length) - for (let i = 0; i < commits.length; i++) { - const commit = commits[i] - const evt = evts[i] - expect(evt.repo).toEqual(did) - expect(evt.commit.toString()).toEqual(commit.commit.toString()) - expect(evt.prev?.toString()).toEqual(commits[i - 1]?.commit?.toString()) - const car = await repo.readCarWithRoot(evt.blocks as Uint8Array) - expect(car.root.equals(commit.commit)) - expect(car.blocks.equals(commit.blocks)) - const writes = writeLog[i].map((w) => ({ - action: w.action, - path: w.collection + '/' + w.rkey, - cid: w.action === WriteOpAction.Delete ? null : w.cid.toString(), - })) - const sortedOps = evt.ops - .sort((a, b) => a.path.localeCompare(b.path)) - .map((op) => ({ ...op, cid: op.cid?.toString() ?? null })) - const sortedWrites = writes.sort((a, b) => a.path.localeCompare(b.path)) - expect(sortedOps).toEqual(sortedWrites) + const fromStream = await repo.verifyRepo( + allBlocks, + lastCommit, + did, + ctx.repoSigningKey.did(), + ) + const fromRpcOps = fromRpc.creates + const fromStreamOps = fromStream.creates + expect(fromStreamOps.length).toEqual(fromRpcOps.length) + for (let i = 0; i < fromRpcOps.length; i++) { + expect(fromStreamOps[i].collection).toEqual(fromRpcOps[i].collection) + expect(fromStreamOps[i].rkey).toEqual(fromRpcOps[i].rkey) + expect(fromStreamOps[i].cid).toEqual(fromRpcOps[i].cid) } } @@ -198,7 +200,7 @@ describe('repo subscribe repos', () => { const isDone = async (evt: any) => { if (evt === undefined) return false if (evt instanceof ErrorFrame) return true - const caughtUp = await ctx.sequencerLeader.isCaughtUp() + const caughtUp = await ctx.sequencerLeader?.isCaughtUp() if (!caughtUp) return false const curr = await db.db .selectFrom('repo_seq') @@ -299,6 +301,7 @@ describe('repo subscribe repos', () => { it('syncs handle changes', async () => { await sc.updateHandle(alice, 'alice2.test') await sc.updateHandle(bob, 'bob2.test') + await sc.updateHandle(bob, 'bob2.test') // idempotent update re-sends const ws = new WebSocket( `ws://${serverHost}/xrpc/com.atproto.sync.subscribeRepos?cursor=${-1}`, @@ -309,11 +312,26 @@ describe('repo subscribe repos', () => { ws.terminate() await verifyCommitEvents(evts) - const handleEvts = getHandleEvts(evts.slice(-2)) + const handleEvts = getHandleEvts(evts.slice(-3)) verifyHandleEvent(handleEvts[0], alice, 'alice2.test') verifyHandleEvent(handleEvts[1], bob, 'bob2.test') }) + it('resends handle events on idempotent updates', async () => { + const update = sc.updateHandle(bob, 'bob2.test') + + const ws = new WebSocket( + `ws://${serverHost}/xrpc/com.atproto.sync.subscribeRepos`, + ) + + const gen = byFrame(ws) + const evts = await readTillCaughtUp(gen, update) + ws.terminate() + + const handleEvts = getHandleEvts(evts.slice(-1)) + verifyHandleEvent(handleEvts[0], bob, 'bob2.test') + }) + it('syncs tombstones', async () => { const baddie1 = ( await sc.createAccount('baddie1.test', { @@ -400,43 +418,6 @@ describe('repo subscribe repos', () => { verifyHandleEvent(handleEvts[1], bob, 'bob5.test') }) - it('sync rebases', async () => { - const prevHead = await agent.api.com.atproto.sync.getHead({ did: alice }) - - await agent.api.com.atproto.repo.rebaseRepo( - { repo: alice }, - { encoding: 'application/json', headers: sc.getHeaders(alice) }, - ) - - const currHead = await agent.api.com.atproto.sync.getHead({ did: alice }) - - const ws = new WebSocket( - `ws://${serverHost}/xrpc/com.atproto.sync.subscribeRepos?cursor=${-1}`, - ) - - const gen = byFrame(ws) - const frames = await readTillCaughtUp(gen) - ws.terminate() - - const aliceEvts = getCommitEvents(alice, frames) - expect(aliceEvts.length).toBe(1) - const evt = aliceEvts[0] - expect(evt.rebase).toBe(true) - expect(evt.tooBig).toBe(false) - expect(evt.commit.toString()).toEqual(currHead.data.root) - expect(evt.prev?.toString()).toEqual(prevHead.data.root) - expect(evt.ops).toEqual([]) - expect(evt.blobs).toEqual([]) - const car = await readCar(evt.blocks) - expect(car.blocks.size).toBe(1) - expect(car.roots.length).toBe(1) - expect(car.roots[0].toString()).toEqual(currHead.data.root) - - // did not affect other users - const bobEvts = getCommitEvents(bob, frames) - expect(bobEvts.length).toBeGreaterThan(10) - }) - it('sends info frame on out of date cursor', async () => { // we rewrite the sequenceAt time for existing seqs to be past the backfill cutoff // then we create some new posts diff --git a/packages/pds/tests/sync/sync.test.ts b/packages/pds/tests/sync/sync.test.ts index 9b5ca80d9b7..27311cbf81d 100644 --- a/packages/pds/tests/sync/sync.test.ts +++ b/packages/pds/tests/sync/sync.test.ts @@ -1,10 +1,9 @@ -import assert from 'assert' import AtpAgent from '@atproto/api' -import { cidForCbor, TID } from '@atproto/common' +import { TID } from '@atproto/common' import { randomStr } from '@atproto/crypto' import * as repo from '@atproto/repo' -import { collapseWriteLog, MemoryBlockstore, readCar } from '@atproto/repo' -import { AtUri } from '@atproto/uri' +import { MemoryBlockstore } from '@atproto/repo' +import { AtUri } from '@atproto/syntax' import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/defs' import { CID } from 'multiformats/cid' import { AppContext } from '../../src' @@ -56,21 +55,21 @@ describe('repo sync', () => { uris.push(uri) } - const car = await agent.api.com.atproto.sync.getRepo({ did }) - const synced = await repo.loadFullRepo( - storage, - new Uint8Array(car.data), + const carRes = await agent.api.com.atproto.sync.getRepo({ did }) + const car = await repo.readCarWithRoot(carRes.data) + const synced = await repo.verifyRepo( + car.blocks, + car.root, did, ctx.repoSigningKey.did(), ) - expect(synced.writeLog.length).toBe(ADD_COUNT + 1) // +1 because of repo - const ops = await collapseWriteLog(synced.writeLog) - expect(ops.length).toBe(ADD_COUNT) // Does not include empty initial commit - const loaded = await repo.Repo.load(storage, synced.root) + await storage.applyCommit(synced.commit) + expect(synced.creates.length).toBe(ADD_COUNT) + const loaded = await repo.Repo.load(storage, car.root) const contents = await loaded.getContents() expect(contents).toEqual(repoData) - currRoot = synced.root + currRoot = car.root }) it('syncs creates and deletes', async () => { @@ -95,102 +94,61 @@ describe('repo sync', () => { delete repoData[uri.collection][uri.rkey] } - const car = await agent.api.com.atproto.sync.getRepo({ - did, - earliest: currRoot?.toString(), - }) + const carRes = await agent.api.com.atproto.sync.getRepo({ did }) + const car = await repo.readCarWithRoot(carRes.data) const currRepo = await repo.Repo.load(storage, currRoot) - const synced = await repo.loadDiff( + const synced = await repo.verifyDiff( currRepo, - new Uint8Array(car.data), + car.blocks, + car.root, did, ctx.repoSigningKey.did(), ) - expect(synced.writeLog.length).toBe(ADD_COUNT + DEL_COUNT) - const ops = await collapseWriteLog(synced.writeLog) - expect(ops.length).toBe(ADD_COUNT) // -2 because of dels of new records, +2 because of dels of old records - const loaded = await repo.Repo.load(storage, synced.root) + expect(synced.writes.length).toBe(ADD_COUNT) // -2 because of dels of new records, +2 because of dels of old records + await storage.applyCommit(synced.commit) + const loaded = await repo.Repo.load(storage, car.root) const contents = await loaded.getContents() expect(contents).toEqual(repoData) - currRoot = synced.root + currRoot = car.root }) - it('syncs current root', async () => { - const root = await agent.api.com.atproto.sync.getHead({ did }) - expect(root.data.root).toEqual(currRoot?.toString()) + it('syncs latest repo commit', async () => { + const commit = await agent.api.com.atproto.sync.getLatestCommit({ did }) + expect(commit.data.cid).toEqual(currRoot?.toString()) }) - it('syncs commit path', async () => { - const local = await storage.getCommitPath(currRoot as CID, null) - if (!local) { - throw new Error('Could not get local commit path') - } - const localStr = local.map((c) => c.toString()) - const commitPath = await agent.api.com.atproto.sync.getCommitPath({ did }) - expect(commitPath.data.commits).toEqual(localStr) - - const partialCommitPath = await agent.api.com.atproto.sync.getCommitPath({ - did, - earliest: localStr[2], - latest: localStr[15], - }) - expect(partialCommitPath.data.commits).toEqual(localStr.slice(3, 16)) - }) + it('syncs `since` a given rev', async () => { + const repoBefore = await repo.Repo.load(storage, currRoot) - it('syncs commit range', async () => { - const local = await storage.getCommits(currRoot as CID, null) - if (!local) { - throw new Error('Could not get local commit path') + // add a post + const { obj, uri } = await makePost(sc, did) + if (!repoData[uri.collection]) { + repoData[uri.collection] = {} } - const memoryStore = new MemoryBlockstore() - // first we load some baseline data (needed for parsing range) - const first = await agent.api.com.atproto.sync.getRepo({ - did, - latest: local[2].commit.toString(), - }) - const firstParsed = await repo.readCar(new Uint8Array(first.data)) - memoryStore.putMany(firstParsed.blocks) + repoData[uri.collection][uri.rkey] = obj + uris.push(uri) - // then we load some commit range - const second = await agent.api.com.atproto.sync.getRepo({ + const carRes = await agent.api.com.atproto.sync.getRepo({ did, - earliest: local[2].commit.toString(), - latest: local[15].commit.toString(), + since: repoBefore.commit.rev, }) - const secondParsed = await repo.readCar(new Uint8Array(second.data)) - memoryStore.putMany(secondParsed.blocks) - - // then we verify we have all the commits in the range - const commits = await memoryStore.getCommits( - local[15].commit, - local[2].commit, - ) - if (!commits) { - throw new Error('expected commits to be defined') - } - const localSlice = local.slice(2, 15) - expect(commits.length).toBe(localSlice.length) - for (let i = 0; i < commits.length; i++) { - const fromRemote = commits[i] - const fromLocal = localSlice[i] - expect(fromRemote.commit.equals(fromLocal.commit)) - expect(fromRemote.blocks.equals(fromLocal.blocks)) - } - }) - - it('sync a repo checkout', async () => { - const car = await agent.api.com.atproto.sync.getCheckout({ did }) - const checkoutStorage = new MemoryBlockstore() - const loaded = await repo.loadCheckout( - checkoutStorage, - new Uint8Array(car.data), + const car = await repo.readCarWithRoot(carRes.data) + expect(car.blocks.size).toBeLessThan(10) // should only contain new blocks + const synced = await repo.verifyDiff( + repoBefore, + car.blocks, + car.root, did, ctx.repoSigningKey.did(), ) - expect(loaded.contents).toEqual(repoData) - const loadedRepo = await repo.Repo.load(checkoutStorage, loaded.root) - expect(await loadedRepo.getContents()).toEqual(repoData) + expect(synced.writes.length).toBe(1) + await storage.applyCommit(synced.commit) + const loaded = await repo.Repo.load(storage, car.root) + const contents = await loaded.getContents() + expect(contents).toEqual(repoData) + + currRoot = car.root }) it('sync a record proof', async () => { @@ -246,74 +204,6 @@ describe('repo sync', () => { expect(result.unverified.length).toBe(0) }) - it('sync blocks', async () => { - // let's just get some cids to reference - const collection = Object.keys(repoData)[0] - const rkey = Object.keys(repoData[collection])[0] - const proofCar = await agent.api.com.atproto.sync.getRecord({ - did, - collection, - rkey, - }) - const proofBlocks = await readCar(new Uint8Array(proofCar.data)) - const cids = proofBlocks.blocks.entries().map((e) => e.cid.toString()) - const res = await agent.api.com.atproto.sync.getBlocks({ - did, - cids, - }) - const car = await readCar(new Uint8Array(res.data)) - expect(car.roots.length).toBe(0) - expect(car.blocks.equals(proofBlocks.blocks)) - }) - - it('syncs images', async () => { - const img1 = await sc.uploadFile( - did, - 'tests/sample-img/key-landscape-small.jpg', - 'image/jpeg', - ) - const img2 = await sc.uploadFile( - did, - 'tests/sample-img/key-portrait-small.jpg', - 'image/jpeg', - ) - await sc.post(did, 'blah', undefined, [img1]) - await sc.post(did, 'blah', undefined, [img1, img2]) - await sc.post(did, 'blah', undefined, [img2]) - const res = await agent.api.com.atproto.sync.getCommitPath({ did }) - const commits = res.data.commits - const blobsForFirst = await agent.api.com.atproto.sync.listBlobs({ - did, - earliest: commits.at(-4), - latest: commits.at(-3), - }) - const blobsForSecond = await agent.api.com.atproto.sync.listBlobs({ - did, - earliest: commits.at(-3), - latest: commits.at(-2), - }) - const blobsForThird = await agent.api.com.atproto.sync.listBlobs({ - did, - earliest: commits.at(-2), - latest: commits.at(-1), - }) - const blobsForRange = await agent.api.com.atproto.sync.listBlobs({ - did, - earliest: commits.at(-4), - }) - const blobsForRepo = await agent.api.com.atproto.sync.listBlobs({ - did, - }) - const cid1 = img1.image.ref.toString() - const cid2 = img2.image.ref.toString() - - expect(blobsForFirst.data.cids).toEqual([cid1]) - expect(blobsForSecond.data.cids.sort()).toEqual([cid1, cid2].sort()) - expect(blobsForThird.data.cids).toEqual([cid2]) - expect(blobsForRange.data.cids.sort()).toEqual([cid1, cid2].sort()) - expect(blobsForRepo.data.cids.sort()).toEqual([cid1, cid2].sort()) - }) - describe('repo takedown', () => { beforeAll(async () => { await sc.takeModerationAction({ @@ -344,23 +234,9 @@ describe('repo sync', () => { await expect(tryGetRepoAdmin).resolves.toBeDefined() }) - it('does not sync current root unauthed', async () => { - const tryGetHead = agent.api.com.atproto.sync.getHead({ did }) - await expect(tryGetHead).rejects.toThrow(/Could not find root for DID/) - }) - - it('does not sync commit path unauthed', async () => { - const tryGetCommitPath = agent.api.com.atproto.sync.getCommitPath({ did }) - await expect(tryGetCommitPath).rejects.toThrow( - /Could not find root for DID/, - ) - }) - - it('does not sync a repo checkout unauthed', async () => { - const tryGetCheckout = agent.api.com.atproto.sync.getCheckout({ did }) - await expect(tryGetCheckout).rejects.toThrow( - /Could not find root for DID/, - ) + it('does not sync latest commit unauthed', async () => { + const tryGetLatest = agent.api.com.atproto.sync.getLatestCommit({ did }) + await expect(tryGetLatest).rejects.toThrow(/Could not find root for DID/) }) it('does not sync a record proof unauthed', async () => { @@ -373,29 +249,6 @@ describe('repo sync', () => { }) await expect(tryGetRecord).rejects.toThrow(/Could not find repo for DID/) }) - - it('does not sync blocks unauthed', async () => { - const cid = await cidForCbor({}) - const tryGetBlocks = agent.api.com.atproto.sync.getBlocks({ - did, - cids: [cid.toString()], - }) - await expect(tryGetBlocks).rejects.toThrow(/Could not find repo for DID/) - }) - - it('does not sync images unauthed', async () => { - // list blobs - const tryListBlobs = agent.api.com.atproto.sync.listBlobs({ did }) - await expect(tryListBlobs).rejects.toThrow(/Could not find root for DID/) - // get blob - const imageCid = sc.posts[did].at(-1)?.images[0].image.ref.toString() - assert(imageCid) - const tryGetBlob = agent.api.com.atproto.sync.getBlob({ - did, - cid: imageCid, - }) - await expect(tryGetBlob).rejects.toThrow('Blob not found') - }) }) }) diff --git a/packages/pds/tsconfig.build.json b/packages/pds/tsconfig.build.json index 27df65b89e2..02a84823b65 100644 --- a/packages/pds/tsconfig.build.json +++ b/packages/pds/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", "exclude": ["**/*.spec.ts", "**/*.test.ts"] -} \ No newline at end of file +} diff --git a/packages/pds/tsconfig.json b/packages/pds/tsconfig.json index 25920c22d24..daf8ee1a04a 100644 --- a/packages/pds/tsconfig.json +++ b/packages/pds/tsconfig.json @@ -3,10 +3,10 @@ "compilerOptions": { "rootDir": "./src", "outDir": "./dist", // Your outDir, - "emitDeclarationOnly": true, + "emitDeclarationOnly": true }, "module": "nodenext", - "include": ["./src","__tests__/**/**.ts"], + "include": ["./src", "__tests__/**/**.ts"], "references": [ { "path": "../api/tsconfig.build.json" }, { "path": "../aws/tsconfig.build.json" }, @@ -20,4 +20,4 @@ { "path": "../uri/tsconfig.build.json" }, { "path": "../xrpc-server/tsconfig.build.json" } ] -} \ No newline at end of file +} diff --git a/packages/pds/update-pkg.js b/packages/pds/update-pkg.js deleted file mode 100644 index 15d70bdab33..00000000000 --- a/packages/pds/update-pkg.js +++ /dev/null @@ -1,14 +0,0 @@ -const pkgJson = require('@npmcli/package-json') - -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} -if (process.argv.includes('--update-main-to-src')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'src/index.ts' })) - .then((pkg) => pkg.save()) -} diff --git a/packages/pg/with-test-db.sh b/packages/pg/with-test-db.sh deleted file mode 100755 index 9d8f1b06577..00000000000 --- a/packages/pg/with-test-db.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env sh - -# Example usage: -# ./with-test-db.sh psql postgresql://pg:password@localhost:5433/postgres -c 'select 1;' - -dir=$(dirname $0) -compose_file="$dir/docker-compose.yaml" - -# whether this particular script started the container or if it was running beforehand -started_container=false - -trap on_sigint INT -on_sigint() { - echo # newline - if $started_container; then - docker compose -f $compose_file rm -f --stop --volumes db_test - fi - exit $? -} - -# check if the container is already running -container_id=$(docker compose -f $compose_file ps --format json --status running | jq --raw-output '.[0]."ID"') -if [ -z $container_id ] || [ -z `docker ps -q --no-trunc | grep $container_id` ]; then - docker compose -f $compose_file up --wait --force-recreate db_test - started_container=true - echo # newline -else - echo "db_test already running" -fi - -# Based on creds in compose.yaml -export PGPORT=5433 -export PGHOST=localhost -export PGUSER=pg -export PGPASSWORD=password -export PGDATABASE=postgres -export DB_POSTGRES_URL="postgresql://pg:password@localhost:5433/postgres" -"$@" -code=$? - -echo # newline -if $started_container; then - docker compose -f $compose_file rm -f --stop --volumes db_test -fi - -exit $code diff --git a/packages/repo/README.md b/packages/repo/README.md index f17f7c0235f..53c83a3071c 100644 --- a/packages/repo/README.md +++ b/packages/repo/README.md @@ -1,3 +1,3 @@ # ATP Repo -The "ATP repository" core implementation (a Merkle Search Tree). \ No newline at end of file +The "ATP repository" core implementation (a Merkle Search Tree). diff --git a/packages/repo/build.js b/packages/repo/build.js index 5628aa4f4eb..e880ae9930b 100644 --- a/packages/repo/build.js +++ b/packages/repo/build.js @@ -1,16 +1,8 @@ -const pkgJson = require('@npmcli/package-json') const { nodeExternalsPlugin } = require('esbuild-node-externals') const buildShallow = process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true' -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} - require('esbuild').build({ logLevel: 'info', entryPoints: ['src/index.ts'], diff --git a/packages/repo/package.json b/packages/repo/package.json index efb689d85b2..9c37695f1d8 100644 --- a/packages/repo/package.json +++ b/packages/repo/package.json @@ -1,7 +1,11 @@ { "name": "@atproto/repo", - "version": "0.1.0", + "version": "0.3.0", "main": "src/index.ts", + "publishConfig": { + "main": "dist/index.js", + "types": "dist/index.d.ts" + }, "license": "MIT", "repository": { "type": "git", @@ -13,28 +17,20 @@ "test:profile": "node --inspect ../../node_modules/.bin/jest", "bench": "jest --config jest.bench.config.js", "bench:profile": "node --inspect-brk ../../node_modules/.bin/jest --config jest.bench.config.js", - "prettier": "prettier --check src/ tests/", - "prettier:fix": "prettier --write src/ tests/", - "lint": "eslint . --ext .ts,.tsx", - "lint:fix": "yarn lint --fix", - "verify": "run-p prettier lint", - "verify:fix": "yarn prettier:fix && yarn lint:fix", "build": "node ./build.js", "postbuild": "tsc --build tsconfig.build.json", - "update-main-to-dist": "node ./update-pkg.js --update-main-to-dist", - "update-main-to-src": "node ./update-pkg.js --update-main-to-src", - "prepublish": "npm run update-main-to-dist", - "postpublish": "npm run update-main-to-src" + "update-main-to-dist": "node ../../update-main-to-dist.js packages/repo" }, "dependencies": { - "@atproto/common": "*", - "@atproto/crypto": "*", - "@atproto/identity": "*", - "@atproto/lexicon": "*", - "@atproto/nsid": "*", + "@atproto/common": "workspace:^", + "@atproto/common-web": "workspace:^", + "@atproto/crypto": "workspace:^", + "@atproto/identity": "workspace:^", + "@atproto/lexicon": "workspace:^", + "@atproto/syntax": "workspace:^", "@ipld/car": "^3.2.3", "@ipld/dag-cbor": "^7.0.0", - "multiformats": "^9.6.4", + "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.21.4" } diff --git a/packages/repo/src/data-diff.ts b/packages/repo/src/data-diff.ts index bc88ca7efa6..bbf6fcf46b7 100644 --- a/packages/repo/src/data-diff.ts +++ b/packages/repo/src/data-diff.ts @@ -1,77 +1,83 @@ import { CID } from 'multiformats' import CidSet from './cid-set' -import { MST, mstDiff } from './mst' +import { MST, NodeEntry, mstDiff } from './mst' +import BlockMap from './block-map' export class DataDiff { adds: Record = {} updates: Record = {} deletes: Record = {} - newCids: CidSet = new CidSet() + newMstBlocks: BlockMap = new BlockMap() + newLeafCids: CidSet = new CidSet() removedCids: CidSet = new CidSet() static async of(curr: MST, prev: MST | null): Promise { return mstDiff(curr, prev) } - recordAdd(key: string, cid: CID): void { + async nodeAdd(node: NodeEntry) { + if (node.isLeaf()) { + this.leafAdd(node.key, node.value) + } else { + const data = await node.serialize() + this.treeAdd(data.cid, data.bytes) + } + } + + async nodeDelete(node: NodeEntry) { + if (node.isLeaf()) { + const key = node.key + const cid = node.value + this.deletes[key] = { key, cid } + this.removedCids.add(cid) + } else { + const cid = await node.getPointer() + this.treeDelete(cid) + } + } + + leafAdd(key: string, cid: CID) { this.adds[key] = { key, cid } - this.newCids.add(cid) + if (this.removedCids.has(cid)) { + this.removedCids.delete(cid) + } else { + this.newLeafCids.add(cid) + } } - recordUpdate(key: string, prev: CID, cid: CID): void { + leafUpdate(key: string, prev: CID, cid: CID) { + if (prev.equals(cid)) return this.updates[key] = { key, prev, cid } - this.newCids.add(cid) + this.removedCids.add(prev) + this.newLeafCids.add(cid) } - recordDelete(key: string, cid: CID): void { + leafDelete(key: string, cid: CID) { this.deletes[key] = { key, cid } + if (this.newLeafCids.has(cid)) { + this.newLeafCids.delete(cid) + } else { + this.removedCids.add(cid) + } } - recordNewCid(cid: CID): void { + treeAdd(cid: CID, bytes: Uint8Array) { if (this.removedCids.has(cid)) { this.removedCids.delete(cid) } else { - this.newCids.add(cid) + this.newMstBlocks.set(cid, bytes) } } - recordRemovedCid(cid: CID): void { - if (this.newCids.has(cid)) { - this.newCids.delete(cid) + treeDelete(cid: CID) { + if (this.newMstBlocks.has(cid)) { + this.newMstBlocks.delete(cid) } else { this.removedCids.add(cid) } } - addDiff(diff: DataDiff) { - for (const add of diff.addList()) { - if (this.deletes[add.key]) { - const del = this.deletes[add.key] - if (del.cid !== add.cid) { - this.recordUpdate(add.key, del.cid, add.cid) - } - delete this.deletes[add.key] - } else { - this.recordAdd(add.key, add.cid) - } - } - for (const update of diff.updateList()) { - this.recordUpdate(update.key, update.prev, update.cid) - delete this.adds[update.key] - delete this.deletes[update.key] - } - for (const del of diff.deleteList()) { - if (this.adds[del.key]) { - delete this.adds[del.key] - } else { - delete this.updates[del.key] - this.recordDelete(del.key, del.cid) - } - } - this.newCids.addSet(diff.newCids) - } - addList(): DataAdd[] { return Object.values(this.adds) } @@ -84,10 +90,6 @@ export class DataDiff { return Object.values(this.deletes) } - newCidList(): CID[] { - return this.newCids.toList() - } - updatedKeys(): string[] { const keys = [ ...Object.keys(this.adds), diff --git a/packages/repo/src/index.ts b/packages/repo/src/index.ts index 79a45f4720f..82ed2115ad9 100644 --- a/packages/repo/src/index.ts +++ b/packages/repo/src/index.ts @@ -5,6 +5,5 @@ export * from './mst' export * from './storage' export * from './sync' export * from './types' -export * from './verify' export * from './data-diff' export * from './util' diff --git a/packages/repo/src/logger.ts b/packages/repo/src/logger.ts index c4dabaac451..d4d1fdb9936 100644 --- a/packages/repo/src/logger.ts +++ b/packages/repo/src/logger.ts @@ -1,5 +1,6 @@ import { subsystemLogger } from '@atproto/common' -export const logger = subsystemLogger('repo') +export const logger: ReturnType = + subsystemLogger('repo') export default logger diff --git a/packages/repo/src/mst/diff.ts b/packages/repo/src/mst/diff.ts index 1c903e8c5f0..20bd215fb33 100644 --- a/packages/repo/src/mst/diff.ts +++ b/packages/repo/src/mst/diff.ts @@ -5,11 +5,7 @@ import MstWalker from './walker' export const nullDiff = async (tree: MST): Promise => { const diff = new DataDiff() for await (const entry of tree.walk()) { - if (entry.isLeaf()) { - diff.recordAdd(entry.key, entry.value) - } else { - diff.recordNewCid(entry.pointer) - } + await diff.nodeAdd(entry) } return diff } @@ -31,21 +27,11 @@ export const mstDiff = async ( while (!leftWalker.status.done || !rightWalker.status.done) { // if one walker is finished, continue walking the other & logging all nodes if (leftWalker.status.done && !rightWalker.status.done) { - const node = rightWalker.status.curr - if (node.isLeaf()) { - diff.recordAdd(node.key, node.value) - } else { - diff.recordNewCid(node.pointer) - } + await diff.nodeAdd(rightWalker.status.curr) await rightWalker.advance() continue } else if (!leftWalker.status.done && rightWalker.status.done) { - const node = leftWalker.status.curr - if (node.isLeaf()) { - diff.recordDelete(node.key, node.value) - } else { - diff.recordRemovedCid(node.pointer) - } + await diff.nodeDelete(leftWalker.status.curr) await leftWalker.advance() continue } @@ -58,15 +44,15 @@ export const mstDiff = async ( if (left.isLeaf() && right.isLeaf()) { if (left.key === right.key) { if (!left.value.equals(right.value)) { - diff.recordUpdate(left.key, left.value, right.value) + diff.leafUpdate(left.key, left.value, right.value) } await leftWalker.advance() await rightWalker.advance() } else if (left.key < right.key) { - diff.recordDelete(left.key, left.value) + diff.leafDelete(left.key, left.value) await leftWalker.advance() } else { - diff.recordAdd(right.key, right.value) + diff.leafAdd(right.key, right.value) await rightWalker.advance() } continue @@ -78,27 +64,19 @@ export const mstDiff = async ( // if the higher walker is pointed at a leaf, then advance the lower walker to try to catch up the higher if (leftWalker.layer() > rightWalker.layer()) { if (left.isLeaf()) { - if (right.isLeaf()) { - diff.recordAdd(right.key, right.value) - } else { - diff.recordNewCid(right.pointer) - } + await diff.nodeAdd(right) await rightWalker.advance() } else { - diff.recordRemovedCid(left.pointer) + await diff.nodeDelete(left) await leftWalker.stepInto() } continue } else if (leftWalker.layer() < rightWalker.layer()) { if (right.isLeaf()) { - if (left.isLeaf()) { - diff.recordDelete(left.key, left.value) - } else { - diff.recordRemovedCid(left.pointer) - } + await diff.nodeDelete(left) await leftWalker.advance() } else { - diff.recordNewCid(right.pointer) + await diff.nodeAdd(right) await rightWalker.stepInto() } continue @@ -111,8 +89,8 @@ export const mstDiff = async ( await leftWalker.stepOver() await rightWalker.stepOver() } else { - diff.recordNewCid(right.pointer) - diff.recordRemovedCid(left.pointer) + await diff.nodeAdd(right) + await diff.nodeDelete(left) await leftWalker.stepInto() await rightWalker.stepInto() } @@ -121,11 +99,11 @@ export const mstDiff = async ( // finally, if one pointer is a tree and the other is a leaf, simply step into the tree if (left.isLeaf() && right.isTree()) { - diff.recordNewCid(right.pointer) + await diff.nodeAdd(right) await rightWalker.stepInto() continue } else if (left.isTree() && right.isLeaf()) { - diff.recordRemovedCid(left.pointer) + await diff.nodeDelete(left) await leftWalker.stepInto() continue } diff --git a/packages/repo/src/mst/mst.ts b/packages/repo/src/mst/mst.ts index ad918644121..80458d9dc61 100644 --- a/packages/repo/src/mst/mst.ts +++ b/packages/repo/src/mst/mst.ts @@ -2,7 +2,7 @@ import z from 'zod' import { CID } from 'multiformats' import { ReadableBlockstore } from '../storage' -import { schema as common, cidForCbor } from '@atproto/common' +import { schema as common, cidForCbor, dataToCborBlock } from '@atproto/common' import { BlockWriter } from '@ipld/car/api' import * as util from './util' import BlockMap from '../block-map' @@ -153,6 +153,13 @@ export class MST { // Instead we keep track of whether the pointer is outdated and only (recursively) calculate when needed async getPointer(): Promise { if (!this.outdatedPointer) return this.pointer + const { cid } = await this.serialize() + this.pointer = cid + this.outdatedPointer = false + return this.pointer + } + + async serialize(): Promise<{ cid: CID; bytes: Uint8Array }> { let entries = await this.getEntries() const outdated = entries.filter( (e) => e.isTree() && e.outdatedPointer, @@ -161,9 +168,12 @@ export class MST { await Promise.all(outdated.map((e) => e.getPointer())) entries = await this.getEntries() } - this.pointer = await util.cidForEntries(entries) - this.outdatedPointer = false - return this.pointer + const data = util.serializeNodeData(entries) + const block = await dataToCborBlock(data) + return { + cid: block.cid, + bytes: block.bytes, + } } // In most cases, we get the layer of a node from a hint on creation @@ -694,17 +704,9 @@ export class MST { // Sync Protocol async writeToCarStream(car: BlockWriter): Promise { - const entries = await this.getEntries() const leaves = new CidSet() let toFetch = new CidSet() toFetch.add(await this.getPointer()) - for (const entry of entries) { - if (entry.isLeaf()) { - leaves.add(entry.value) - } else { - toFetch.add(await entry.getPointer()) - } - } while (toFetch.size() > 0) { const nextLayer = new CidSet() const fetched = await this.storage.getBlocks(toFetch.toList()) diff --git a/packages/repo/src/readable-repo.ts b/packages/repo/src/readable-repo.ts index 0ec530bb7fd..4381bd5e0e4 100644 --- a/packages/repo/src/readable-repo.ts +++ b/packages/repo/src/readable-repo.ts @@ -1,5 +1,5 @@ import { CID } from 'multiformats/cid' -import { Commit, def, RepoContents } from './types' +import { def, RepoContents, Commit } from './types' import { ReadableBlockstore } from './storage' import { MST } from './mst' import log from './logger' @@ -28,13 +28,13 @@ export class ReadableRepo { } static async load(storage: ReadableBlockstore, commitCid: CID) { - const commit = await storage.readObj(commitCid, def.commit) + const commit = await storage.readObj(commitCid, def.versionedCommit) const data = await MST.load(storage, commit.data) log.info({ did: commit.did }, 'loaded repo for') return new ReadableRepo({ storage, data, - commit, + commit: util.ensureV3Commit(commit), cid: commitCid, }) } diff --git a/packages/repo/src/repo.ts b/packages/repo/src/repo.ts index 78dbe7409d4..49e8ef24810 100644 --- a/packages/repo/src/repo.ts +++ b/packages/repo/src/repo.ts @@ -1,13 +1,13 @@ import { CID } from 'multiformats/cid' +import { TID } from '@atproto/common' import * as crypto from '@atproto/crypto' import { Commit, + CommitData, def, RecordCreateOp, RecordWriteOp, - CommitData, WriteOpAction, - RebaseData, } from './types' import { RepoStorage } from './storage' import { MST } from './mst' @@ -46,25 +46,29 @@ export class Repo extends ReadableRepo { const dataKey = util.formatDataKey(record.collection, record.rkey) data = await data.add(dataKey, cid) } + const dataCid = await data.getPointer() + const diff = await DataDiff.of(data, null) + newBlocks.addMap(diff.newMstBlocks) - const unstoredData = await data.getUnstoredBlocks() - newBlocks.addMap(unstoredData.blocks) - + const rev = TID.nextStr() const commit = await util.signCommit( { did, - version: 2, - prev: null, - data: unstoredData.root, + version: 3, + rev, + prev: null, // added for backwards compatibility with v2 + data: dataCid, }, keypair, ) const commitCid = await newBlocks.add(commit) - return { - commit: commitCid, + cid: commitCid, + rev, + since: null, prev: null, - blocks: newBlocks, + newBlocks, + removedCids: diff.removedCids, } } @@ -73,7 +77,7 @@ export class Repo extends ReadableRepo { commit: CommitData, ): Promise { await storage.applyCommit(commit) - return Repo.load(storage, commit.commit) + return Repo.load(storage, commit.cid) } static async create( @@ -92,17 +96,17 @@ export class Repo extends ReadableRepo { } static async load(storage: RepoStorage, cid?: CID) { - const commitCid = cid || (await storage.getHead()) + const commitCid = cid || (await storage.getRoot()) if (!commitCid) { throw new Error('No cid provided and none in storage') } - const commit = await storage.readObj(commitCid, def.commit) + const commit = await storage.readObj(commitCid, def.versionedCommit) const data = await MST.load(storage, commit.data) log.info({ did: commit.did }, 'loaded repo for') return new Repo({ storage, data, - commit, + commit: util.ensureV3Commit(commit), cid: commitCid, }) } @@ -112,16 +116,16 @@ export class Repo extends ReadableRepo { keypair: crypto.Keypair, ): Promise { const writes = Array.isArray(toWrite) ? toWrite : [toWrite] - const commitBlocks = new BlockMap() + const leaves = new BlockMap() let data = this.data for (const write of writes) { if (write.action === WriteOpAction.Create) { - const cid = await commitBlocks.add(write.record) + const cid = await leaves.add(write.record) const dataKey = write.collection + '/' + write.rkey data = await data.add(dataKey, cid) } else if (write.action === WriteOpAction.Update) { - const cid = await commitBlocks.add(write.record) + const cid = await leaves.add(write.record) const dataKey = write.collection + '/' + write.rkey data = await data.update(dataKey, cid) } else if (write.action === WriteOpAction.Delete) { @@ -130,44 +134,50 @@ export class Repo extends ReadableRepo { } } - const unstoredData = await data.getUnstoredBlocks() - commitBlocks.addMap(unstoredData.blocks) - - // ensure we're not missing any blocks that were removed and then readded in this commit + const dataCid = await data.getPointer() const diff = await DataDiff.of(data, this.data) - const found = commitBlocks.getMany(diff.newCidList()) - if (found.missing.length > 0) { - const fromStorage = await this.storage.getBlocks(found.missing) - if (fromStorage.missing.length > 0) { - // this shouldn't ever happen - throw new Error( - 'Could not find block for commit in Datastore or storage', - ) - } - commitBlocks.addMap(fromStorage.blocks) + const newBlocks = diff.newMstBlocks + const removedCids = diff.removedCids + + const addedLeaves = leaves.getMany(diff.newLeafCids.toList()) + if (addedLeaves.missing.length > 0) { + throw new Error(`Missing leaf blocks: ${addedLeaves.missing}`) } + newBlocks.addMap(addedLeaves.blocks) + const rev = TID.nextStr(this.commit.rev) const commit = await util.signCommit( { did: this.did, - version: 2, - prev: this.cid, - data: unstoredData.root, + version: 3, + rev, + prev: null, // added for backwards compatibility with v2 + data: dataCid, }, keypair, ) - const commitCid = await commitBlocks.add(commit) + const commitCid = await newBlocks.add(commit) + + // ensure the commit cid actually changed + if (commitCid.equals(this.cid)) { + newBlocks.delete(commitCid) + } else { + removedCids.add(this.cid) + } return { - commit: commitCid, + cid: commitCid, + rev, + since: this.commit.rev, prev: this.cid, - blocks: commitBlocks, + newBlocks, + removedCids, } } async applyCommit(commitData: CommitData): Promise { await this.storage.applyCommit(commitData) - return Repo.load(this.storage, commitData.commit) + return Repo.load(this.storage, commitData.cid) } async applyWrites( @@ -177,37 +187,6 @@ export class Repo extends ReadableRepo { const commit = await this.formatCommit(toWrite, keypair) return this.applyCommit(commit) } - - async formatRebase(keypair: crypto.Keypair): Promise { - const preservedCids = await this.data.allCids() - const blocks = new BlockMap() - const commit = await util.signCommit( - { - did: this.did, - version: 2, - prev: null, - data: this.commit.data, - }, - keypair, - ) - const commitCid = await blocks.add(commit) - return { - commit: commitCid, - rebased: this.cid, - blocks, - preservedCids: preservedCids.toList(), - } - } - - async applyRebase(rebase: RebaseData): Promise { - await this.storage.applyRebase(rebase) - return Repo.load(this.storage, rebase.commit) - } - - async rebase(keypair: crypto.Keypair): Promise { - const rebaseData = await this.formatRebase(keypair) - return this.applyRebase(rebaseData) - } } export default Repo diff --git a/packages/repo/src/storage/index.ts b/packages/repo/src/storage/index.ts index c5eb715d59a..15d39822a51 100644 --- a/packages/repo/src/storage/index.ts +++ b/packages/repo/src/storage/index.ts @@ -1,5 +1,4 @@ export * from './readable-blockstore' -export * from './repo-storage' export * from './memory-blockstore' export * from './sync-storage' export * from './types' diff --git a/packages/repo/src/storage/memory-blockstore.ts b/packages/repo/src/storage/memory-blockstore.ts index cb8c32a8c75..5f91311c345 100644 --- a/packages/repo/src/storage/memory-blockstore.ts +++ b/packages/repo/src/storage/memory-blockstore.ts @@ -1,15 +1,15 @@ import { CID } from 'multiformats/cid' -import { CommitData, def, RebaseData } from '../types' +import { CommitData } from '../types' import BlockMap from '../block-map' -import { MST } from '../mst' -import DataDiff from '../data-diff' -import { MissingCommitBlocksError } from '../error' -import RepoStorage from './repo-storage' -import CidSet from '../cid-set' +import ReadableBlockstore from './readable-blockstore' +import { RepoStorage } from './types' -export class MemoryBlockstore extends RepoStorage { +export class MemoryBlockstore + extends ReadableBlockstore + implements RepoStorage +{ blocks: BlockMap - head: CID | null = null + root: CID | null = null constructor(blocks?: BlockMap) { super() @@ -19,8 +19,8 @@ export class MemoryBlockstore extends RepoStorage { } } - async getHead(): Promise { - return this.head + async getRoot(): Promise { + return this.root } async getBytes(cid: CID): Promise { @@ -43,78 +43,19 @@ export class MemoryBlockstore extends RepoStorage { this.blocks.addMap(blocks) } - async indexCommits(commits: CommitData[]): Promise { - commits.forEach((commit) => { - this.blocks.addMap(commit.blocks) - }) - } - - async updateHead(cid: CID, _prev: CID | null): Promise { - this.head = cid + async updateRoot(cid: CID): Promise { + this.root = cid } async applyCommit(commit: CommitData): Promise { - this.blocks.addMap(commit.blocks) - this.head = commit.commit - } - - async getCommitPath( - latest: CID, - earliest: CID | null, - ): Promise { - let curr: CID | null = latest - const path: CID[] = [] - while (curr !== null) { - path.push(curr) - const commit = await this.readObj(curr, def.commit) - if (!earliest && commit.prev === null) { - return path.reverse() - } else if (earliest && commit.prev.equals(earliest)) { - return path.reverse() - } - curr = commit.prev - } - return null - } - - async getBlocksForCommits( - commits: CID[], - ): Promise<{ [commit: string]: BlockMap }> { - const commitData: { [commit: string]: BlockMap } = {} - let prevData: MST | null = null - for (const commitCid of commits) { - const commit = await this.readObj(commitCid, def.commit) - const data = await MST.load(this, commit.data) - const diff = await DataDiff.of(data, prevData) - const { blocks, missing } = await this.getBlocks([ - commitCid, - ...diff.newCidList(), - ]) - if (missing.length > 0) { - throw new MissingCommitBlocksError(commitCid, missing) - } - commitData[commitCid.toString()] = blocks - prevData = data - } - return commitData - } - - async applyRebase(rebase: RebaseData) { - this.putMany(rebase.blocks) - const allCids = new CidSet([ - ...rebase.preservedCids, - ...rebase.blocks.cids(), - ]) - const toDelete: CID[] = [] - this.blocks.forEach((_bytes, cid) => { - if (!allCids.has(cid)) { - toDelete.push(cid) - } - }) - for (const cid of toDelete) { + this.root = commit.cid + const rmCids = commit.removedCids.toList() + for (const cid of rmCids) { this.blocks.delete(cid) } - await this.updateHead(rebase.commit, null) + commit.newBlocks.forEach((bytes, cid) => { + this.blocks.set(cid, bytes) + }) } async sizeInBytes(): Promise { diff --git a/packages/repo/src/storage/repo-storage.ts b/packages/repo/src/storage/repo-storage.ts deleted file mode 100644 index 93a51eaaab3..00000000000 --- a/packages/repo/src/storage/repo-storage.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { CID } from 'multiformats/cid' -import BlockMap from '../block-map' -import { CommitBlockData, CommitData, RebaseData } from '../types' -import ReadableBlockstore from './readable-blockstore' - -export abstract class RepoStorage extends ReadableBlockstore { - abstract getHead(forUpdate?: boolean): Promise - abstract getCommitPath( - latest: CID, - earliest: CID | null, - ): Promise - abstract getBlocksForCommits( - commits: CID[], - ): Promise<{ [commit: string]: BlockMap }> - - abstract putBlock(cid: CID, block: Uint8Array): Promise - abstract putMany(blocks: BlockMap): Promise - abstract updateHead(cid: CID, prev: CID | null): Promise - abstract indexCommits(commit: CommitData[]): Promise - abstract applyRebase(rebase: RebaseData): Promise - - async applyCommit(commit: CommitData): Promise { - await Promise.all([ - this.indexCommits([commit]), - this.updateHead(commit.commit, commit.prev), - ]) - } - - async getCommits( - latest: CID, - earliest: CID | null, - ): Promise { - const commitPath = await this.getCommitPath(latest, earliest) - if (!commitPath) return null - const blocksByCommit = await this.getBlocksForCommits(commitPath) - return commitPath.map((commit) => ({ - commit, - blocks: blocksByCommit[commit.toString()] || new BlockMap(), - })) - } -} - -export default RepoStorage diff --git a/packages/repo/src/storage/types.ts b/packages/repo/src/storage/types.ts index 49c58225714..804be48cbc8 100644 --- a/packages/repo/src/storage/types.ts +++ b/packages/repo/src/storage/types.ts @@ -1,5 +1,34 @@ import stream from 'stream' import { CID } from 'multiformats/cid' +import { RepoRecord } from '@atproto/lexicon' +import { check } from '@atproto/common' +import BlockMap from '../block-map' +import { CommitData } from '../types' + +export interface RepoStorage { + // Writable + getRoot(): Promise + putBlock(cid: CID, block: Uint8Array, rev: string): Promise + putMany(blocks: BlockMap, rev: string): Promise + updateRoot(cid: CID): Promise + applyCommit(commit: CommitData) + + // Readable + getBytes(cid: CID): Promise + has(cid: CID): Promise + getBlocks(cids: CID[]): Promise<{ blocks: BlockMap; missing: CID[] }> + attemptRead( + cid: CID, + def: check.Def, + ): Promise<{ obj: T; bytes: Uint8Array } | null> + readObjAndBytes( + cid: CID, + def: check.Def, + ): Promise<{ obj: T; bytes: Uint8Array }> + readObj(cid: CID, def: check.Def): Promise + attemptReadRecord(cid: CID): Promise + readRecord(cid: CID): Promise +} export interface BlobStore { putTemp(bytes: Uint8Array | stream.Readable): Promise diff --git a/packages/repo/src/sync/consumer.ts b/packages/repo/src/sync/consumer.ts index abdd40ff71a..08ca98195f2 100644 --- a/packages/repo/src/sync/consumer.ts +++ b/packages/repo/src/sync/consumer.ts @@ -1,140 +1,194 @@ import { CID } from 'multiformats/cid' -import { MemoryBlockstore, RepoStorage } from '../storage' -import Repo from '../repo' -import * as verify from '../verify' +import { MemoryBlockstore, ReadableBlockstore, SyncStorage } from '../storage' +import DataDiff from '../data-diff' +import ReadableRepo from '../readable-repo' import * as util from '../util' -import { CommitData, RepoContents, WriteLog } from '../types' -import CidSet from '../cid-set' -import { MissingBlocksError } from '../error' - -// Checkouts -// ------------- - -export const loadCheckout = async ( - storage: RepoStorage, - repoCar: Uint8Array, - did: string, - signingKey: string, -): Promise<{ root: CID; contents: RepoContents }> => { - const { root, blocks } = await util.readCarWithRoot(repoCar) - const updateStorage = new MemoryBlockstore(blocks) - const checkout = await verify.verifyCheckout( - updateStorage, - root, - did, - signingKey, - ) - - const checkoutBlocks = await updateStorage.getBlocks( - checkout.newCids.toList(), - ) - if (checkoutBlocks.missing.length > 0) { - throw new MissingBlocksError('sync', checkoutBlocks.missing) - } - await Promise.all([ - storage.putMany(checkoutBlocks.blocks), - storage.updateHead(root, null), - ]) +import { RecordClaim, VerifiedDiff, VerifiedRepo } from '../types' +import { def } from '../types' +import { MST } from '../mst' +import { cidForCbor } from '@atproto/common' +import BlockMap from '../block-map' + +export const verifyRepoCar = async ( + carBytes: Uint8Array, + did?: string, + signingKey?: string, +): Promise => { + const car = await util.readCarWithRoot(carBytes) + return verifyRepo(car.blocks, car.root, did, signingKey) +} +export const verifyRepo = async ( + blocks: BlockMap, + head: CID, + did?: string, + signingKey?: string, +): Promise => { + const diff = await verifyDiff(null, blocks, head, did, signingKey) + const creates = util.ensureCreates(diff.writes) return { - root, - contents: checkout.contents, + creates, + commit: diff.commit, } } -// Diffs -// ------------- +export const verifyDiffCar = async ( + repo: ReadableRepo | null, + carBytes: Uint8Array, + did?: string, + signingKey?: string, +): Promise => { + const car = await util.readCarWithRoot(carBytes) + return verifyDiff(repo, car.blocks, car.root, did, signingKey) +} -export const loadFullRepo = async ( - storage: RepoStorage, - repoCar: Uint8Array, - did: string, - signingKey: string, -): Promise<{ root: CID; writeLog: WriteLog; repo: Repo }> => { - const { root, blocks } = await util.readCarWithRoot(repoCar) - const updateStorage = new MemoryBlockstore(blocks) - const updates = await verify.verifyFullHistory( +export const verifyDiff = async ( + repo: ReadableRepo | null, + updateBlocks: BlockMap, + updateRoot: CID, + did?: string, + signingKey?: string, +): Promise => { + const stagedStorage = new MemoryBlockstore(updateBlocks) + const updateStorage = repo + ? new SyncStorage(stagedStorage, repo.storage) + : stagedStorage + const updated = await verifyRepoRoot( updateStorage, - root, + updateRoot, did, signingKey, ) - - const [writeLog] = await Promise.all([ - persistUpdates(storage, updateStorage, updates), - storage.updateHead(root, null), - ]) - - const repo = await Repo.load(storage, root) - + const diff = await DataDiff.of(updated.data, repo?.data ?? null) + const writes = await util.diffToWriteDescripts(diff, updateBlocks) + const newBlocks = diff.newMstBlocks + const leaves = updateBlocks.getMany(diff.newLeafCids.toList()) + if (leaves.missing.length > 0) { + throw new Error(`missing leaf blocks: ${leaves.missing}`) + } + newBlocks.addMap(leaves.blocks) + const removedCids = diff.removedCids + const commitCid = await newBlocks.add(updated.commit) + // ensure the commit cid actually changed + if (repo) { + if (commitCid.equals(repo.cid)) { + newBlocks.delete(commitCid) + } else { + removedCids.add(repo.cid) + } + } return { - root, - writeLog, - repo, + writes, + commit: { + cid: updated.cid, + rev: updated.commit.rev, + prev: repo?.cid ?? null, + since: repo?.commit.rev ?? null, + newBlocks, + removedCids, + }, } } -export const loadDiff = async ( - repo: Repo, - diffCar: Uint8Array, - did: string, - signingKey: string, -): Promise<{ root: CID; writeLog: WriteLog }> => { - const { root, blocks } = await util.readCarWithRoot(diffCar) - const updateStorage = new MemoryBlockstore(blocks) - const updates = await verify.verifyUpdates( - repo, - updateStorage, - root, - did, - signingKey, - ) - - const [writeLog] = await Promise.all([ - persistUpdates(repo.storage, updateStorage, updates), - repo.storage.updateHead(root, repo.cid), - ]) - - return { - root, - writeLog, +// @NOTE only verifies the root, not the repo contents +const verifyRepoRoot = async ( + storage: ReadableBlockstore, + head: CID, + did?: string, + signingKey?: string, +): Promise => { + const repo = await ReadableRepo.load(storage, head) + if (did !== undefined && repo.did !== did) { + throw new RepoVerificationError(`Invalid repo did: ${repo.did}`) } + if (signingKey !== undefined) { + const validSig = await util.verifyCommitSig(repo.commit, signingKey) + if (!validSig) { + throw new RepoVerificationError( + `Invalid signature on commit: ${repo.cid.toString()}`, + ) + } + } + return repo } -// Helpers -// ------------- - -export const persistUpdates = async ( - storage: RepoStorage, - updateStorage: RepoStorage, - updates: verify.VerifiedUpdate[], -): Promise => { - const newCids = new CidSet() - for (const update of updates) { - newCids.addSet(update.newCids) +export const verifyProofs = async ( + proofs: Uint8Array, + claims: RecordClaim[], + did: string, + didKey: string, +): Promise<{ verified: RecordClaim[]; unverified: RecordClaim[] }> => { + const car = await util.readCarWithRoot(proofs) + const blockstore = new MemoryBlockstore(car.blocks) + const commit = await blockstore.readObj(car.root, def.commit) + if (commit.did !== did) { + throw new RepoVerificationError(`Invalid repo did: ${commit.did}`) } - - const diffBlocks = await updateStorage.getBlocks(newCids.toList()) - if (diffBlocks.missing.length > 0) { - throw new MissingBlocksError('sync', diffBlocks.missing) + const validSig = await util.verifyCommitSig(commit, didKey) + if (!validSig) { + throw new RepoVerificationError( + `Invalid signature on commit: ${car.root.toString()}`, + ) } - const commits: CommitData[] = updates.map((update) => { - const forCommit = diffBlocks.blocks.getMany(update.newCids.toList()) - if (forCommit.missing.length > 0) { - throw new MissingBlocksError('sync', forCommit.missing) - } - return { - commit: update.commit, - prev: update.prev, - blocks: forCommit.blocks, + const mst = MST.load(blockstore, commit.data) + const verified: RecordClaim[] = [] + const unverified: RecordClaim[] = [] + for (const claim of claims) { + const found = await mst.get( + util.formatDataKey(claim.collection, claim.rkey), + ) + const record = found ? await blockstore.readObj(found, def.map) : null + if (claim.record === null) { + if (record === null) { + verified.push(claim) + } else { + unverified.push(claim) + } + } else { + const expected = await cidForCbor(claim.record) + if (expected.equals(found)) { + verified.push(claim) + } else { + unverified.push(claim) + } } - }) - - await storage.indexCommits(commits) + } + return { verified, unverified } +} - return Promise.all( - updates.map((upd) => - util.diffToWriteDescripts(upd.diff, diffBlocks.blocks), - ), - ) +export const verifyRecords = async ( + proofs: Uint8Array, + did: string, + signingKey: string, +): Promise => { + const car = await util.readCarWithRoot(proofs) + const blockstore = new MemoryBlockstore(car.blocks) + const commit = await blockstore.readObj(car.root, def.commit) + if (commit.did !== did) { + throw new RepoVerificationError(`Invalid repo did: ${commit.did}`) + } + const validSig = await util.verifyCommitSig(commit, signingKey) + if (!validSig) { + throw new RepoVerificationError( + `Invalid signature on commit: ${car.root.toString()}`, + ) + } + const mst = MST.load(blockstore, commit.data) + + const records: RecordClaim[] = [] + const leaves = await mst.reachableLeaves() + for (const leaf of leaves) { + const { collection, rkey } = util.parseDataKey(leaf.key) + const record = await blockstore.attemptReadRecord(leaf.value) + if (record) { + records.push({ + collection, + rkey, + record, + }) + } + } + return records } + +export class RepoVerificationError extends Error {} diff --git a/packages/repo/src/sync/provider.ts b/packages/repo/src/sync/provider.ts index 7fb2dc4abf7..ef6a586b15a 100644 --- a/packages/repo/src/sync/provider.ts +++ b/packages/repo/src/sync/provider.ts @@ -7,10 +7,10 @@ import { RepoStorage } from '../storage' import * as util from '../util' import { MST } from '../mst' -// Checkouts +// Full Repo // ------------- -export const getCheckout = ( +export const getFullRepo = ( storage: RepoStorage, commitCid: CID, ): AsyncIterable => { @@ -22,44 +22,6 @@ export const getCheckout = ( }) } -// Commits -// ------------- - -export const getCommits = ( - storage: RepoStorage, - latest: CID, - earliest: CID | null, -): AsyncIterable => { - return util.writeCar(latest, (car: BlockWriter) => { - return writeCommitsToCarStream(storage, car, latest, earliest) - }) -} - -export const getFullRepo = ( - storage: RepoStorage, - cid: CID, -): AsyncIterable => { - return getCommits(storage, cid, null) -} - -export const writeCommitsToCarStream = async ( - storage: RepoStorage, - car: BlockWriter, - latest: CID, - earliest: CID | null, -): Promise => { - const commits = await storage.getCommits(latest, earliest) - if (commits === null) { - throw new Error('Could not find shared history') - } - if (commits.length === 0) return - for (const commit of commits) { - for (const entry of commit.blocks.entries()) { - await car.put(entry) - } - } -} - // Narrow slices // ------------- diff --git a/packages/repo/src/types.ts b/packages/repo/src/types.ts index 24871288295..4b796193ef3 100644 --- a/packages/repo/src/types.ts +++ b/packages/repo/src/types.ts @@ -1,32 +1,55 @@ import { z } from 'zod' -import { schema as common, def as commonDef } from '@atproto/common' +import { def as commonDef } from '@atproto/common-web' +import { schema as common } from '@atproto/common' import { CID } from 'multiformats' import BlockMap from './block-map' import { RepoRecord } from '@atproto/lexicon' +import CidSet from './cid-set' // Repo nodes // --------------- const unsignedCommit = z.object({ did: z.string(), - version: z.number(), - prev: common.cid.nullable(), + version: z.literal(3), data: common.cid, + rev: z.string(), + // `prev` added for backwards compatibility with v2, no requirement of keeping around history + prev: common.cid.nullable().optional(), }) export type UnsignedCommit = z.infer & { sig?: never } const commit = z.object({ did: z.string(), - version: z.number(), - prev: common.cid.nullable(), + version: z.literal(3), data: common.cid, + rev: z.string(), + prev: common.cid.nullable().optional(), sig: common.bytes, }) export type Commit = z.infer +const legacyV2Commit = z.object({ + did: z.string(), + version: z.literal(2), + data: common.cid, + rev: z.string().optional(), + prev: common.cid.nullable(), + sig: common.bytes, +}) +export type LegacyV2Commit = z.infer + +const versionedCommit = z.discriminatedUnion('version', [ + commit, + legacyV2Commit, +]) +export type VersionedCommit = z.infer + export const schema = { ...common, commit, + legacyV2Commit, + versionedCommit, } export const def = { @@ -35,6 +58,10 @@ export const def = { name: 'commit', schema: schema.commit, }, + versionedCommit: { + name: 'versioned_commit', + schema: schema.versionedCommit, + }, } // Repo Operations @@ -91,26 +118,13 @@ export type WriteLog = RecordWriteDescript[][] // Updates/Commits // --------------- -export type CommitBlockData = { - commit: CID - blocks: BlockMap -} - -export type CommitData = CommitBlockData & { - prev: CID | null -} - -export type RebaseData = { - commit: CID - rebased: CID - blocks: BlockMap - preservedCids: CID[] -} - -export type CommitCidData = { - commit: CID +export type CommitData = { + cid: CID + rev: string + since: string | null prev: CID | null - cids: CID[] + newBlocks: BlockMap + removedCids: CidSet } export type RepoUpdate = CommitData & { @@ -136,3 +150,16 @@ export type RecordClaim = { rkey: string record: RepoRecord | null } + +// Sync +// --------------- + +export type VerifiedDiff = { + writes: RecordWriteDescript[] + commit: CommitData +} + +export type VerifiedRepo = { + creates: RecordCreateDescript[] + commit: CommitData +} diff --git a/packages/repo/src/util.ts b/packages/repo/src/util.ts index 8ec5239fa4f..563a848d4ae 100644 --- a/packages/repo/src/util.ts +++ b/packages/repo/src/util.ts @@ -10,29 +10,28 @@ import { check, schema, cidForCbor, + byteIterableToStream, + TID, } from '@atproto/common' import { ipldToLex, lexToIpld, LexValue, RepoRecord } from '@atproto/lexicon' import * as crypto from '@atproto/crypto' -import Repo from './repo' -import { MST } from './mst' import DataDiff from './data-diff' -import { RepoStorage } from './storage' import { Commit, + LegacyV2Commit, RecordCreateDescript, RecordDeleteDescript, RecordPath, RecordUpdateDescript, RecordWriteDescript, UnsignedCommit, - WriteLog, WriteOpAction, } from './types' import BlockMap from './block-map' -import { MissingBlocksError } from './error' import * as parse from './parse' import { Keypair } from '@atproto/crypto' +import { Readable } from 'stream' export async function* verifyIncomingCarBlocks( car: AsyncIterable, @@ -43,16 +42,31 @@ export async function* verifyIncomingCarBlocks( } } -export const writeCar = ( +// we have to turn the car writer output into a stream in order to properly handle errors +export function writeCarStream( root: CID | null, fn: (car: BlockWriter) => Promise, -): AsyncIterable => { +): Readable { const { writer, out } = root !== null ? CarWriter.create(root) : CarWriter.create() - fn(writer).finally(() => writer.close()) + const stream = byteIterableToStream(out) + fn(writer) + .catch((err) => { + stream.destroy(err) + }) + .finally(() => writer.close()) + return stream +} - return out +export async function* writeCar( + root: CID | null, + fn: (car: BlockWriter) => Promise, +): AsyncIterable { + const stream = writeCarStream(root, fn) + for await (const chunk of stream) { + yield chunk + } } export const blocksToCarStream = ( @@ -103,33 +117,6 @@ export const readCarWithRoot = async ( } } -export const getWriteLog = async ( - storage: RepoStorage, - latest: CID, - earliest: CID | null, -): Promise => { - const commits = await storage.getCommitPath(latest, earliest) - if (!commits) throw new Error('Could not find shared history') - const heads = await Promise.all(commits.map((c) => Repo.load(storage, c))) - // Turn commit path into list of diffs - let prev = await MST.create(storage) // Empty - const msts = heads.map((h) => h.data) - const diffs: DataDiff[] = [] - for (const mst of msts) { - diffs.push(await DataDiff.of(mst, prev)) - prev = mst - } - const fullDiff = collapseDiffs(diffs) - const diffBlocks = await storage.getBlocks(fullDiff.newCidList()) - if (diffBlocks.missing.length > 0) { - throw new MissingBlocksError('write op log', diffBlocks.missing) - } - // Map MST diffs to write ops - return Promise.all( - diffs.map((diff) => diffToWriteDescripts(diff, diffBlocks.blocks)), - ) -} - export const diffToWriteDescripts = ( diff: DataDiff, blocks: BlockMap, @@ -170,55 +157,18 @@ export const diffToWriteDescripts = ( ]) } -export const collapseWriteLog = (log: WriteLog): RecordWriteDescript[] => { - const creates: Record = {} - const updates: Record = {} - const deletes: Record = {} - for (const commit of log) { - for (const op of commit) { - const key = op.collection + '/' + op.rkey - if (op.action === WriteOpAction.Create) { - const del = deletes[key] - if (del) { - if (del.cid !== op.cid) { - updates[key] = { - ...op, - action: WriteOpAction.Update, - prev: del.cid, - } - } - delete deletes[key] - } else { - creates[key] = op - } - } else if (op.action === WriteOpAction.Update) { - updates[key] = op - delete creates[key] - delete deletes[key] - } else if (op.action === WriteOpAction.Delete) { - if (creates[key]) { - delete creates[key] - } else { - delete updates[key] - deletes[key] = op - } - } else { - throw new Error(`unknown action: ${op}`) - } +export const ensureCreates = ( + descripts: RecordWriteDescript[], +): RecordCreateDescript[] => { + const creates: RecordCreateDescript[] = [] + for (const descript of descripts) { + if (descript.action !== WriteOpAction.Create) { + throw new Error(`Unexpected action: ${descript.action}`) + } else { + creates.push(descript) } } - return [ - ...Object.values(creates), - ...Object.values(updates), - ...Object.values(deletes), - ] -} - -export const collapseDiffs = (diffs: DataDiff[]): DataDiff => { - return diffs.reduce((acc, cur) => { - acc.addDiff(cur) - return acc - }, new DataDiff()) + return creates } export const parseDataKey = (key: string): RecordPath => { @@ -271,3 +221,15 @@ export const cborToLexRecord = (val: Uint8Array): RepoRecord => { export const cidForRecord = async (val: LexValue) => { return cidForCbor(lexToIpld(val)) } + +export const ensureV3Commit = (commit: LegacyV2Commit | Commit): Commit => { + if (commit.version === 3) { + return commit + } else { + return { + ...commit, + version: 3, + rev: commit.rev ?? TID.nextStr(), + } + } +} diff --git a/packages/repo/src/verify.ts b/packages/repo/src/verify.ts deleted file mode 100644 index 28400a957bb..00000000000 --- a/packages/repo/src/verify.ts +++ /dev/null @@ -1,268 +0,0 @@ -import { CID } from 'multiformats/cid' -import { MemoryBlockstore, ReadableBlockstore, RepoStorage } from './storage' -import DataDiff from './data-diff' -import SyncStorage from './storage/sync-storage' -import ReadableRepo from './readable-repo' -import Repo from './repo' -import CidSet from './cid-set' -import * as util from './util' -import { RecordClaim, RepoContents, RepoContentsWithCids } from './types' -import { def } from './types' -import { MST } from './mst' -import { cidForCbor } from '@atproto/common' - -export type VerifiedCheckout = { - contents: RepoContents - newCids: CidSet -} - -export type VerifiedCheckoutWithCids = { - commit: CID - contents: RepoContentsWithCids -} - -export const verifyCheckout = async ( - storage: ReadableBlockstore, - head: CID, - did: string, - signingKey: string, -): Promise => { - const repo = await verifyRepoRoot(storage, head, did, signingKey) - const diff = await DataDiff.of(repo.data, null) - const newCids = new CidSet([repo.cid]).addSet(diff.newCids) - - const contents: RepoContents = {} - for (const add of diff.addList()) { - const { collection, rkey } = util.parseDataKey(add.key) - if (!contents[collection]) { - contents[collection] = {} - } - const record = await storage.readRecord(add.cid) - contents[collection][rkey] = record - } - - return { - contents, - newCids, - } -} - -export const verifyCheckoutWithCids = async ( - storage: ReadableBlockstore, - head: CID, - did: string, - signingKey: string, -): Promise => { - const repo = await verifyRepoRoot(storage, head, did, signingKey) - const diff = await DataDiff.of(repo.data, null) - - const contents: RepoContentsWithCids = {} - for (const add of diff.addList()) { - const { collection, rkey } = util.parseDataKey(add.key) - contents[collection] ??= {} - contents[collection][rkey] = { - cid: add.cid, - value: await storage.readRecord(add.cid), - } - } - - return { - commit: repo.cid, - contents, - } -} - -// @NOTE only verifies the root, not the repo contents -const verifyRepoRoot = async ( - storage: ReadableBlockstore, - head: CID, - did: string, - signingKey: string, -): Promise => { - const repo = await ReadableRepo.load(storage, head) - if (repo.did !== did) { - throw new RepoVerificationError(`Invalid repo did: ${repo.did}`) - } - const validSig = await util.verifyCommitSig(repo.commit, signingKey) - if (!validSig) { - throw new RepoVerificationError( - `Invalid signature on commit: ${repo.cid.toString()}`, - ) - } - return repo -} - -export type VerifiedUpdate = { - commit: CID - prev: CID | null - diff: DataDiff - newCids: CidSet -} - -export const verifyFullHistory = async ( - storage: RepoStorage, - head: CID, - did: string, - signingKey: string, -): Promise => { - const commitPath = await storage.getCommitPath(head, null) - if (commitPath === null) { - throw new RepoVerificationError('Could not find shared history') - } else if (commitPath.length < 1) { - throw new RepoVerificationError('Expected at least one commit') - } - const baseRepo = await Repo.load(storage, commitPath[0]) - const baseDiff = await DataDiff.of(baseRepo.data, null) - const baseRepoCids = new CidSet([baseRepo.cid]).addSet(baseDiff.newCids) - const init: VerifiedUpdate = { - commit: baseRepo.cid, - prev: null, - diff: baseDiff, - newCids: baseRepoCids, - } - const updates = await verifyCommitPath( - baseRepo, - storage, - commitPath.slice(1), - did, - signingKey, - ) - return [init, ...updates] -} - -export const verifyUpdates = async ( - repo: ReadableRepo, - updateStorage: RepoStorage, - updateRoot: CID, - did: string, - signingKey: string, -): Promise => { - const commitPath = await updateStorage.getCommitPath(updateRoot, repo.cid) - if (commitPath === null) { - throw new RepoVerificationError('Could not find shared history') - } - const syncStorage = new SyncStorage(updateStorage, repo.storage) - return verifyCommitPath(repo, syncStorage, commitPath, did, signingKey) -} - -export const verifyCommitPath = async ( - baseRepo: ReadableRepo, - storage: ReadableBlockstore, - commitPath: CID[], - did: string, - signingKey: string, -): Promise => { - const updates: VerifiedUpdate[] = [] - if (commitPath.length === 0) return updates - let prevRepo = baseRepo - for (const commit of commitPath) { - const nextRepo = await ReadableRepo.load(storage, commit) - const diff = await DataDiff.of(nextRepo.data, prevRepo.data) - - if (nextRepo.did !== did) { - throw new RepoVerificationError(`Invalid repo did: ${nextRepo.did}`) - } - if (!util.metaEqual(nextRepo.commit, prevRepo.commit)) { - throw new RepoVerificationError('Not supported: repo metadata updated') - } - - const validSig = await util.verifyCommitSig(nextRepo.commit, signingKey) - if (!validSig) { - throw new RepoVerificationError( - `Invalid signature on commit: ${nextRepo.cid.toString()}`, - ) - } - - const newCids = new CidSet([nextRepo.cid]).addSet(diff.newCids) - - updates.push({ - commit: nextRepo.cid, - prev: prevRepo.cid, - diff, - newCids, - }) - prevRepo = nextRepo - } - return updates -} - -export const verifyProofs = async ( - proofs: Uint8Array, - claims: RecordClaim[], - did: string, - didKey: string, -): Promise<{ verified: RecordClaim[]; unverified: RecordClaim[] }> => { - const car = await util.readCarWithRoot(proofs) - const blockstore = new MemoryBlockstore(car.blocks) - const commit = await blockstore.readObj(car.root, def.commit) - if (commit.did !== did) { - throw new RepoVerificationError(`Invalid repo did: ${commit.did}`) - } - const validSig = await util.verifyCommitSig(commit, didKey) - if (!validSig) { - throw new RepoVerificationError( - `Invalid signature on commit: ${car.root.toString()}`, - ) - } - const mst = MST.load(blockstore, commit.data) - const verified: RecordClaim[] = [] - const unverified: RecordClaim[] = [] - for (const claim of claims) { - const found = await mst.get( - util.formatDataKey(claim.collection, claim.rkey), - ) - const record = found ? await blockstore.readObj(found, def.map) : null - if (claim.record === null) { - if (record === null) { - verified.push(claim) - } else { - unverified.push(claim) - } - } else { - const expected = await cidForCbor(claim.record) - if (expected.equals(found)) { - verified.push(claim) - } else { - unverified.push(claim) - } - } - } - return { verified, unverified } -} - -export const verifyRecords = async ( - proofs: Uint8Array, - did: string, - signingKey: string, -): Promise => { - const car = await util.readCarWithRoot(proofs) - const blockstore = new MemoryBlockstore(car.blocks) - const commit = await blockstore.readObj(car.root, def.commit) - if (commit.did !== did) { - throw new RepoVerificationError(`Invalid repo did: ${commit.did}`) - } - const validSig = await util.verifyCommitSig(commit, signingKey) - if (!validSig) { - throw new RepoVerificationError( - `Invalid signature on commit: ${car.root.toString()}`, - ) - } - const mst = MST.load(blockstore, commit.data) - - const records: RecordClaim[] = [] - const leaves = await mst.reachableLeaves() - for (const leaf of leaves) { - const { collection, rkey } = util.parseDataKey(leaf.key) - const record = await blockstore.attemptReadRecord(leaf.value) - if (record) { - records.push({ - collection, - rkey, - record, - }) - } - } - return records -} - -export class RepoVerificationError extends Error {} diff --git a/packages/repo/tests/_util.ts b/packages/repo/tests/_util.ts index 5e173ba361b..9942cff7568 100644 --- a/packages/repo/tests/_util.ts +++ b/packages/repo/tests/_util.ts @@ -7,15 +7,15 @@ import { RepoStorage } from '../src/storage' import { MST } from '../src/mst' import { BlockMap, - collapseWriteLog, CollectionContents, RecordWriteOp, RepoContents, RecordPath, - WriteLog, WriteOpAction, RecordClaim, Commit, + DataDiff, + CommitData, } from '../src' import { Keypair, randomBytes } from '@atproto/crypto' @@ -109,7 +109,7 @@ export const fillRepo = async ( } } -export const editRepo = async ( +export const formatEdit = async ( repo: Repo, prevData: RepoContents, keypair: crypto.Keypair, @@ -118,91 +118,58 @@ export const editRepo = async ( updates?: number deletes?: number }, -): Promise<{ repo: Repo; data: RepoContents }> => { +): Promise<{ commit: CommitData; data: RepoContents }> => { const { adds = 0, updates = 0, deletes = 0 } = params const repoData: RepoContents = {} + const writes: RecordWriteOp[] = [] for (const collName of testCollections) { - const collData = prevData[collName] + const collData = { ...(prevData[collName] ?? {}) } const shuffled = shuffle(Object.entries(collData)) for (let i = 0; i < adds; i++) { const object = generateObject() const rkey = TID.nextStr() collData[rkey] = object - repo = await repo.applyWrites( - { - action: WriteOpAction.Create, - collection: collName, - rkey, - record: object, - }, - keypair, - ) + writes.push({ + action: WriteOpAction.Create, + collection: collName, + rkey, + record: object, + }) } const toUpdate = shuffled.slice(0, updates) for (let i = 0; i < toUpdate.length; i++) { const object = generateObject() const rkey = toUpdate[i][0] - repo = await repo.applyWrites( - { - action: WriteOpAction.Update, - collection: collName, - rkey, - record: object, - }, - keypair, - ) collData[rkey] = object + writes.push({ + action: WriteOpAction.Update, + collection: collName, + rkey, + record: object, + }) } const toDelete = shuffled.slice(updates, deletes) for (let i = 0; i < toDelete.length; i++) { const rkey = toDelete[i][0] - repo = await repo.applyWrites( - { - action: WriteOpAction.Delete, - collection: collName, - rkey, - }, - keypair, - ) delete collData[rkey] + writes.push({ + action: WriteOpAction.Delete, + collection: collName, + rkey, + }) } repoData[collName] = collData } + const commit = await repo.formatCommit(writes, keypair) return { - repo, + commit, data: repoData, } } -export const verifyRepoDiff = async ( - writeLog: WriteLog, - before: RepoContents, - after: RepoContents, -): Promise => { - const getVal = (op: RecordWriteOp, data: RepoContents) => { - return (data[op.collection] || {})[op.rkey] - } - const ops = await collapseWriteLog(writeLog) - - for (const op of ops) { - if (op.action === WriteOpAction.Create) { - expect(getVal(op, before)).toBeUndefined() - expect(getVal(op, after)).toEqual(op.record) - } else if (op.action === WriteOpAction.Update) { - expect(getVal(op, before)).toBeDefined() - expect(getVal(op, after)).toEqual(op.record) - } else if (op.action === WriteOpAction.Delete) { - expect(getVal(op, before)).toBeDefined() - expect(getVal(op, after)).toBeUndefined() - } else { - throw new Error('unexpected op type') - } - } -} - export const contentsToClaims = (contents: RepoContents): RecordClaim[] => { const claims: RecordClaim[] = [] for (const coll of Object.keys(contents)) { @@ -233,23 +200,27 @@ export const addBadCommit = async ( keypair: Keypair, ): Promise => { const obj = generateObject() - const blocks = new BlockMap() - const cid = await blocks.add(obj) + const newBlocks = new BlockMap() + const cid = await newBlocks.add(obj) const updatedData = await repo.data.add(`com.example.test/${TID.next()}`, cid) - const unstoredData = await updatedData.getUnstoredBlocks() - blocks.addMap(unstoredData.blocks) + const dataCid = await updatedData.getPointer() + const diff = await DataDiff.of(updatedData, repo.data) + newBlocks.addMap(diff.newMstBlocks) // we generate a bad sig by signing some other data + const rev = TID.nextStr(repo.commit.rev) const commit: Commit = { ...repo.commit, - prev: repo.cid, - data: unstoredData.root, + rev, + data: dataCid, sig: await keypair.sign(randomBytes(256)), } - const commitCid = await blocks.add(commit) + const commitCid = await newBlocks.add(commit) await repo.storage.applyCommit({ - commit: commitCid, + cid: commitCid, + rev, prev: repo.cid, - blocks: blocks, + newBlocks, + removedCids: diff.removedCids, }) return await Repo.load(repo.storage, commitCid) } diff --git a/packages/repo/tests/mst.test.ts b/packages/repo/tests/mst.test.ts index 109c6538ffa..5a39d977417 100644 --- a/packages/repo/tests/mst.test.ts +++ b/packages/repo/tests/mst.test.ts @@ -150,7 +150,10 @@ describe('Merkle Search Tree', () => { } else { cid = entry.value } - const found = (await blockstore.has(cid)) || diff.newCids.has(cid) + const found = + (await blockstore.has(cid)) || + diff.newMstBlocks.has(cid) || + diff.newLeafCids.has(cid) expect(found).toBeTruthy() } }) diff --git a/packages/repo/tests/sync/narrow.test.ts b/packages/repo/tests/proofs.test.ts similarity index 80% rename from packages/repo/tests/sync/narrow.test.ts rename to packages/repo/tests/proofs.test.ts index 1bdeb10b0f2..9591c07131a 100644 --- a/packages/repo/tests/sync/narrow.test.ts +++ b/packages/repo/tests/proofs.test.ts @@ -1,13 +1,12 @@ import { TID, streamToBuffer } from '@atproto/common' import * as crypto from '@atproto/crypto' -import { RecordClaim, Repo, RepoContents } from '../../src' -import { MemoryBlockstore } from '../../src/storage' -import * as verify from '../../src/verify' -import * as sync from '../../src/sync' +import { RecordClaim, Repo, RepoContents } from '../src' +import { MemoryBlockstore } from '../src/storage' +import * as sync from '../src/sync' -import * as util from '../_util' +import * as util from './_util' -describe('Narrow Sync', () => { +describe('Repo Proofs', () => { let storage: MemoryBlockstore let repo: Repo let keypair: crypto.Keypair @@ -29,7 +28,7 @@ describe('Narrow Sync', () => { } const doVerify = (proofs: Uint8Array, claims: RecordClaim[]) => { - return verify.verifyProofs(proofs, claims, repoDid, keypair.did()) + return sync.verifyProofs(proofs, claims, repoDid, keypair.did()) } it('verifies valid records', async () => { @@ -112,7 +111,7 @@ describe('Narrow Sync', () => { possible[8], ] const proofs = await getProofs(claims) - const records = await verify.verifyRecords(proofs, repoDid, keypair.did()) + const records = await sync.verifyRecords(proofs, repoDid, keypair.did()) for (const record of records) { const foundClaim = claims.find( (claim) => @@ -127,23 +126,13 @@ describe('Narrow Sync', () => { } }) - it('verifyRecords throws on a bad signature', async () => { - const badRepo = await util.addBadCommit(repo, keypair) - const claims = util.contentsToClaims(repoData) - const proofs = await streamToBuffer( - sync.getRecords(storage, badRepo.cid, claims), - ) - const fn = verify.verifyRecords(proofs, repoDid, keypair.did()) - await expect(fn).rejects.toThrow(verify.RepoVerificationError) - }) - it('verifyProofs throws on a bad signature', async () => { const badRepo = await util.addBadCommit(repo, keypair) const claims = util.contentsToClaims(repoData) const proofs = await streamToBuffer( sync.getRecords(storage, badRepo.cid, claims), ) - const fn = verify.verifyProofs(proofs, claims, repoDid, keypair.did()) - await expect(fn).rejects.toThrow(verify.RepoVerificationError) + const fn = sync.verifyProofs(proofs, claims, repoDid, keypair.did()) + await expect(fn).rejects.toThrow(sync.RepoVerificationError) }) }) diff --git a/packages/repo/tests/rebase.test.ts b/packages/repo/tests/rebase.test.ts deleted file mode 100644 index e5afcb1cec5..00000000000 --- a/packages/repo/tests/rebase.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import * as crypto from '@atproto/crypto' -import { Repo } from '../src/repo' -import { MemoryBlockstore } from '../src/storage' -import * as util from './_util' -import { Secp256k1Keypair } from '@atproto/crypto' - -describe('Rebases', () => { - let storage: MemoryBlockstore - let keypair: crypto.Keypair - let repo: Repo - - it('fills a repo with data', async () => { - storage = new MemoryBlockstore() - keypair = await Secp256k1Keypair.create() - repo = await Repo.create(storage, keypair.did(), keypair) - const filled = await util.fillRepo(repo, keypair, 100) - repo = filled.repo - }) - - it('rebases the repo & preserves contents', async () => { - const dataCidBefore = await repo.data.getPointer() - const contents = await repo.getContents() - repo = await repo.rebase(keypair) - const rebasedContents = await repo.getContents() - expect(rebasedContents).toEqual(contents) - const dataCidAfter = await repo.data.getPointer() - expect(dataCidAfter.equals(dataCidBefore)).toBeTruthy() - }) - - it('only keeps around relevant cids', async () => { - const allCids = await repo.data.allCids() - allCids.add(repo.cid) - for (const cid of storage.blocks.cids()) { - expect(allCids.has(cid)).toBeTruthy() - } - }) -}) diff --git a/packages/repo/tests/repo.test.ts b/packages/repo/tests/repo.test.ts index c08d0dae23f..75d7ef23a14 100644 --- a/packages/repo/tests/repo.test.ts +++ b/packages/repo/tests/repo.test.ts @@ -22,7 +22,7 @@ describe('Repo', () => { it('has proper metadata', async () => { expect(repo.did).toEqual(keypair.did()) - expect(repo.version).toBe(2) + expect(repo.version).toBe(3) }) it('does basic operations', async () => { @@ -75,12 +75,13 @@ describe('Repo', () => { }) it('edits and deletes content', async () => { - const edited = await util.editRepo(repo, repoData, keypair, { + const edit = await util.formatEdit(repo, repoData, keypair, { adds: 20, updates: 20, deletes: 20, }) - repo = edited.repo + repo = await repo.applyCommit(edit.commit) + repoData = edit.data const contents = await repo.getContents() expect(contents).toEqual(repoData) }) @@ -100,6 +101,6 @@ describe('Repo', () => { const contents = await reloadedRepo.getContents() expect(contents).toEqual(repoData) expect(repo.did).toEqual(keypair.did()) - expect(repo.version).toBe(2) + expect(repo.version).toBe(3) }) }) diff --git a/packages/repo/tests/sync.test.ts b/packages/repo/tests/sync.test.ts new file mode 100644 index 00000000000..9c8597a0228 --- /dev/null +++ b/packages/repo/tests/sync.test.ts @@ -0,0 +1,97 @@ +import * as crypto from '@atproto/crypto' +import { + CidSet, + Repo, + RepoContents, + RepoVerificationError, + readCarWithRoot, +} from '../src' +import { MemoryBlockstore } from '../src/storage' +import * as sync from '../src/sync' + +import * as util from './_util' +import { streamToBuffer } from '@atproto/common' +import { CarReader } from '@ipld/car/reader' + +describe('Repo Sync', () => { + let storage: MemoryBlockstore + let repo: Repo + let keypair: crypto.Keypair + let repoData: RepoContents + + const repoDid = 'did:example:test' + + beforeAll(async () => { + storage = new MemoryBlockstore() + keypair = await crypto.Secp256k1Keypair.create() + repo = await Repo.create(storage, repoDid, keypair) + const filled = await util.fillRepo(repo, keypair, 20) + repo = filled.repo + repoData = filled.data + }) + + it('sync a full repo', async () => { + const carBytes = await streamToBuffer(sync.getFullRepo(storage, repo.cid)) + const car = await readCarWithRoot(carBytes) + const verified = await sync.verifyRepo( + car.blocks, + car.root, + repoDid, + keypair.did(), + ) + const syncStorage = new MemoryBlockstore() + await syncStorage.applyCommit(verified.commit) + const loadedRepo = await Repo.load(syncStorage, car.root) + const contents = await loadedRepo.getContents() + expect(contents).toEqual(repoData) + const contentsFromOps: RepoContents = {} + for (const write of verified.creates) { + contentsFromOps[write.collection] ??= {} + contentsFromOps[write.collection][write.rkey] = write.record + } + expect(contentsFromOps).toEqual(repoData) + }) + + it('does not sync duplicate blocks', async () => { + const carBytes = await streamToBuffer(sync.getFullRepo(storage, repo.cid)) + const car = await CarReader.fromBytes(carBytes) + const cids = new CidSet() + for await (const block of car.blocks()) { + if (cids.has(block.cid)) { + throw new Error(`duplicate block: :${block.cid.toString()}`) + } + cids.add(block.cid) + } + }) + + it('syncs a repo that is behind', async () => { + // add more to providers's repo & have consumer catch up + const edit = await util.formatEdit(repo, repoData, keypair, { + adds: 10, + updates: 10, + deletes: 10, + }) + const verified = await sync.verifyDiff( + repo, + edit.commit.newBlocks, + edit.commit.cid, + repoDid, + keypair.did(), + ) + await storage.applyCommit(verified.commit) + repo = await Repo.load(storage, verified.commit.cid) + const contents = await repo.getContents() + expect(contents).toEqual(edit.data) + }) + + it('throws on a bad signature', async () => { + const badRepo = await util.addBadCommit(repo, keypair) + const carBytes = await streamToBuffer( + sync.getFullRepo(storage, badRepo.cid), + ) + const car = await readCarWithRoot(carBytes) + await expect( + sync.verifyRepo(car.blocks, car.root, repoDid, keypair.did()), + ).rejects.toThrow(RepoVerificationError) + }) +}) diff --git a/packages/repo/tests/sync/checkout.test.ts b/packages/repo/tests/sync/checkout.test.ts deleted file mode 100644 index 1c213087268..00000000000 --- a/packages/repo/tests/sync/checkout.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import * as crypto from '@atproto/crypto' -import { Repo, RepoContents, RepoVerificationError } from '../../src' -import { MemoryBlockstore } from '../../src/storage' -import * as sync from '../../src/sync' - -import * as util from '../_util' -import { streamToBuffer } from '@atproto/common' - -describe('Checkout Sync', () => { - let storage: MemoryBlockstore - let syncStorage: MemoryBlockstore - let repo: Repo - let keypair: crypto.Keypair - let repoData: RepoContents - - const repoDid = 'did:example:test' - - beforeAll(async () => { - storage = new MemoryBlockstore() - keypair = await crypto.Secp256k1Keypair.create() - repo = await Repo.create(storage, repoDid, keypair) - syncStorage = new MemoryBlockstore() - const filled = await util.fillRepo(repo, keypair, 20) - repo = filled.repo - repoData = filled.data - }) - - it('sync a non-historical repo checkout', async () => { - const checkoutCar = await streamToBuffer( - sync.getCheckout(storage, repo.cid), - ) - const checkout = await sync.loadCheckout( - syncStorage, - checkoutCar, - repoDid, - keypair.did(), - ) - const checkoutRepo = await Repo.load(syncStorage, checkout.root) - const contents = await checkoutRepo.getContents() - expect(contents).toEqual(repoData) - expect(checkout.contents).toEqual(repoData) - }) - - it('does not sync unneeded blocks during checkout', async () => { - const commitPath = await storage.getCommitPath(repo.cid, null) - if (!commitPath) { - throw new Error('Could not get commitPath') - } - const hasGenesisCommit = await syncStorage.has(commitPath[0]) - expect(hasGenesisCommit).toBeFalsy() - }) - - it('throws on a bad signature', async () => { - const badRepo = await util.addBadCommit(repo, keypair) - const checkoutCar = await streamToBuffer( - sync.getCheckout(storage, badRepo.cid), - ) - await expect( - sync.loadCheckout(syncStorage, checkoutCar, repoDid, keypair.did()), - ).rejects.toThrow(RepoVerificationError) - }) -}) diff --git a/packages/repo/tests/sync/diff.test.ts b/packages/repo/tests/sync/diff.test.ts deleted file mode 100644 index c686ec7142c..00000000000 --- a/packages/repo/tests/sync/diff.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import * as crypto from '@atproto/crypto' -import { Repo, RepoContents } from '../../src' -import { MemoryBlockstore } from '../../src/storage' -import * as sync from '../../src/sync' - -import * as util from '../_util' -import { streamToBuffer } from '@atproto/common' - -describe('Diff Sync', () => { - let storage: MemoryBlockstore - let syncStorage: MemoryBlockstore - let repo: Repo - let keypair: crypto.Keypair - let repoData: RepoContents - - const repoDid = 'did:example:test' - - beforeAll(async () => { - storage = new MemoryBlockstore() - keypair = await crypto.Secp256k1Keypair.create() - repo = await Repo.create(storage, repoDid, keypair) - syncStorage = new MemoryBlockstore() - }) - - let syncRepo: Repo - - it('syncs an empty repo', async () => { - const car = await streamToBuffer(sync.getFullRepo(storage, repo.cid)) - const loaded = await sync.loadFullRepo( - syncStorage, - car, - repoDid, - keypair.did(), - ) - syncRepo = await Repo.load(syncStorage, loaded.root) - const data = await syncRepo.data.list(10) - expect(data.length).toBe(0) - }) - - it('syncs a repo that is starting from scratch', async () => { - const filled = await util.fillRepo(repo, keypair, 100) - repo = filled.repo - repoData = filled.data - - const car = await streamToBuffer(sync.getFullRepo(storage, repo.cid)) - const loaded = await sync.loadFullRepo( - syncStorage, - car, - repoDid, - keypair.did(), - ) - syncRepo = await Repo.load(syncStorage, loaded.root) - const contents = await syncRepo.getContents() - expect(contents).toEqual(repoData) - await util.verifyRepoDiff(loaded.writeLog, {}, repoData) - }) - - it('syncs a repo that is behind', async () => { - // add more to providers's repo & have consumer catch up - const beforeData = structuredClone(repoData) - const edited = await util.editRepo(repo, repoData, keypair, { - adds: 20, - updates: 20, - deletes: 20, - }) - repo = edited.repo - repoData = edited.data - const diffCar = await streamToBuffer( - sync.getCommits(storage, repo.cid, syncRepo.cid), - ) - const loaded = await sync.loadDiff( - syncRepo, - diffCar, - repoDid, - keypair.did(), - ) - syncRepo = await Repo.load(syncStorage, loaded.root) - const contents = await syncRepo.getContents() - expect(contents).toEqual(repoData) - await util.verifyRepoDiff(loaded.writeLog, beforeData, repoData) - }) - - it('throws on a bad signature', async () => { - const badRepo = await util.addBadCommit(repo, keypair) - const diffCar = await streamToBuffer( - sync.getCommits(storage, badRepo.cid, syncRepo.cid), - ) - await expect( - sync.loadDiff(syncRepo, diffCar, repoDid, keypair.did()), - ).rejects.toThrow('Invalid signature on commit') - }) -}) diff --git a/packages/repo/tests/util.test.ts b/packages/repo/tests/util.test.ts new file mode 100644 index 00000000000..f341cadfea9 --- /dev/null +++ b/packages/repo/tests/util.test.ts @@ -0,0 +1,21 @@ +import { dataToCborBlock, wait } from '@atproto/common' +import { writeCar } from '../src' + +describe('Utils', () => { + describe('writeCar()', () => { + it('propagates errors', async () => { + const iterate = async () => { + const iter = writeCar(null, async (car) => { + await wait(1) + const block = await dataToCborBlock({ test: 1 }) + await car.put(block) + throw new Error('Oops!') + }) + for await (const bytes of iter) { + // no-op + } + } + await expect(iterate).rejects.toThrow('Oops!') + }) + }) +}) diff --git a/packages/repo/tsconfig.build.json b/packages/repo/tsconfig.build.json index 27df65b89e2..02a84823b65 100644 --- a/packages/repo/tsconfig.build.json +++ b/packages/repo/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", "exclude": ["**/*.spec.ts", "**/*.test.ts"] -} \ No newline at end of file +} diff --git a/packages/repo/tsconfig.json b/packages/repo/tsconfig.json index 855d51d8af6..07c54d2c4d7 100644 --- a/packages/repo/tsconfig.json +++ b/packages/repo/tsconfig.json @@ -5,11 +5,11 @@ "outDir": "./dist", // Your outDir, "emitDeclarationOnly": true }, - "include": ["./src","__tests__/**/**.ts"], + "include": ["./src", "__tests__/**/**.ts"], "references": [ { "path": "../common/tsconfig.build.json" }, { "path": "../crypto/tsconfig.build.json" }, { "path": "../identity/tsconfig.build.json" }, - { "path": "../nsid/tsconfig.build.json" }, + { "path": "../nsid/tsconfig.build.json" } ] -} \ No newline at end of file +} diff --git a/packages/repo/update-pkg.js b/packages/repo/update-pkg.js deleted file mode 100644 index 15d70bdab33..00000000000 --- a/packages/repo/update-pkg.js +++ /dev/null @@ -1,14 +0,0 @@ -const pkgJson = require('@npmcli/package-json') - -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} -if (process.argv.includes('--update-main-to-src')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'src/index.ts' })) - .then((pkg) => pkg.save()) -} diff --git a/packages/syntax/README.md b/packages/syntax/README.md new file mode 100644 index 00000000000..5fd557fa323 --- /dev/null +++ b/packages/syntax/README.md @@ -0,0 +1,61 @@ +# Syntax + +Validation logic for AT identifiers - DIDs, Handles, NSIDs, and AT URIs + +## Usage + +```typescript +import * as identifier from '@atproto/syntax' + +isValidHandle('alice.test') // returns true +ensureValidHandle('alice.test') // returns void + +isValidHandle('al!ce.test') // returns false +ensureValidHandle('al!ce.test') // throws + +ensureValidDid('did:method:val') // returns void +ensureValidDid(':did:method:val') // throws +``` + +## NameSpaced IDs (NSID) + +```typescript +import { NSID } from '@atproto/syntax' + +const id1 = NSID.parse('com.example.foo') +id1.authority // => 'example.com' +id1.name // => 'foo' +id1.toString() // => 'com.example.foo' + +const id2 = NSID.create('example.com', 'foo') +id2.authority // => 'example.com' +id2.name // => 'foo' +id2.toString() // => 'com.example.foo' + +const id3 = NSID.create('example.com', 'someRecord') +id3.authority // => 'example.com' +id3.name // => 'someRecord' +id3.toString() // => 'com.example.someRecord' + +NSID.isValid('com.example.foo') // => true +NSID.isValid('com.example.someRecord') // => true +NSID.isValid('example.com/foo') // => false +NSID.isValid('foo') // => false +``` + +## AT URI + +```typescript +import { AtUri } from '@atproto/syntax' + +const uri = new AtUri('at://bob.com/com.example.post/1234') +uri.protocol // => 'at:' +uri.origin // => 'at://bob.com' +uri.hostname // => 'bob.com' +uri.collection // => 'com.example.post' +uri.rkey // => '1234' +``` + +## License + +MIT diff --git a/packages/syntax/babel.config.js b/packages/syntax/babel.config.js new file mode 100644 index 00000000000..0126e9dbaa6 --- /dev/null +++ b/packages/syntax/babel.config.js @@ -0,0 +1 @@ +module.exports = require('../../babel.config.js') diff --git a/packages/syntax/build.js b/packages/syntax/build.js new file mode 100644 index 00000000000..5628aa4f4eb --- /dev/null +++ b/packages/syntax/build.js @@ -0,0 +1,22 @@ +const pkgJson = require('@npmcli/package-json') +const { nodeExternalsPlugin } = require('esbuild-node-externals') + +const buildShallow = + process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true' + +if (process.argv.includes('--update-main-to-dist')) { + return pkgJson + .load(__dirname) + .then((pkg) => pkg.update({ main: 'dist/index.js' })) + .then((pkg) => pkg.save()) +} + +require('esbuild').build({ + logLevel: 'info', + entryPoints: ['src/index.ts'], + bundle: true, + sourcemap: true, + outdir: 'dist', + platform: 'node', + plugins: buildShallow ? [nodeExternalsPlugin()] : [], +}) diff --git a/packages/syntax/jest.config.js b/packages/syntax/jest.config.js new file mode 100644 index 00000000000..096d01562c4 --- /dev/null +++ b/packages/syntax/jest.config.js @@ -0,0 +1,6 @@ +const base = require('../../jest.config.base.js') + +module.exports = { + ...base, + displayName: 'Identifier', +} diff --git a/packages/syntax/package.json b/packages/syntax/package.json new file mode 100644 index 00000000000..60fabdfe8d2 --- /dev/null +++ b/packages/syntax/package.json @@ -0,0 +1,27 @@ +{ + "name": "@atproto/syntax", + "version": "0.1.0", + "main": "src/index.ts", + "publishConfig": { + "main": "dist/index.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "test": "jest", + "build": "node ./build.js", + "postbuild": "tsc --build tsconfig.build.json", + "update-main-to-dist": "node ../../update-main-to-dist.js packages/syntax" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto.git", + "directory": "packages/syntax" + }, + "dependencies": { + "@atproto/common-web": "workspace:^" + }, + "browser": { + "dns/promises": false + } +} diff --git a/packages/syntax/src/aturi.ts b/packages/syntax/src/aturi.ts new file mode 100644 index 00000000000..516d3fc61ab --- /dev/null +++ b/packages/syntax/src/aturi.ts @@ -0,0 +1,136 @@ +export * from './aturi_validation' + +export const ATP_URI_REGEX = + // proto- --did-------------- --name---------------- --path---- --query-- --hash-- + /^(at:\/\/)?((?:did:[a-z0-9:%-]+)|(?:[a-z0-9][a-z0-9.:-]*))(\/[^?#\s]*)?(\?[^#\s]+)?(#[^\s]+)?$/i +// --path----- --query-- --hash-- +const RELATIVE_REGEX = /^(\/[^?#\s]*)?(\?[^#\s]+)?(#[^\s]+)?$/i + +export class AtUri { + hash: string + host: string + pathname: string + searchParams: URLSearchParams + + constructor(uri: string, base?: string) { + let parsed + if (base) { + parsed = parse(base) + if (!parsed) { + throw new Error(`Invalid at uri: ${base}`) + } + const relativep = parseRelative(uri) + if (!relativep) { + throw new Error(`Invalid path: ${uri}`) + } + Object.assign(parsed, relativep) + } else { + parsed = parse(uri) + if (!parsed) { + throw new Error(`Invalid at uri: ${uri}`) + } + } + + this.hash = parsed.hash + this.host = parsed.host + this.pathname = parsed.pathname + this.searchParams = parsed.searchParams + } + + static make(handleOrDid: string, collection?: string, rkey?: string) { + let str = handleOrDid + if (collection) str += '/' + collection + if (rkey) str += '/' + rkey + return new AtUri(str) + } + + get protocol() { + return 'at:' + } + + get origin() { + return `at://${this.host}` + } + + get hostname() { + return this.host + } + + set hostname(v: string) { + this.host = v + } + + get search() { + return this.searchParams.toString() + } + + set search(v: string) { + this.searchParams = new URLSearchParams(v) + } + + get collection() { + return this.pathname.split('/').filter(Boolean)[0] || '' + } + + set collection(v: string) { + const parts = this.pathname.split('/').filter(Boolean) + parts[0] = v + this.pathname = parts.join('/') + } + + get rkey() { + return this.pathname.split('/').filter(Boolean)[1] || '' + } + + set rkey(v: string) { + const parts = this.pathname.split('/').filter(Boolean) + if (!parts[0]) parts[0] = 'undefined' + parts[1] = v + this.pathname = parts.join('/') + } + + get href() { + return this.toString() + } + + toString() { + let path = this.pathname || '/' + if (!path.startsWith('/')) { + path = `/${path}` + } + let qs = this.searchParams.toString() + if (qs && !qs.startsWith('?')) { + qs = `?${qs}` + } + let hash = this.hash + if (hash && !hash.startsWith('#')) { + hash = `#${hash}` + } + return `at://${this.host}${path}${qs}${hash}` + } +} + +function parse(str: string) { + const match = ATP_URI_REGEX.exec(str) + if (match) { + return { + hash: match[5] || '', + host: match[2] || '', + pathname: match[3] || '', + searchParams: new URLSearchParams(match[4] || ''), + } + } + return undefined +} + +function parseRelative(str: string) { + const match = RELATIVE_REGEX.exec(str) + if (match) { + return { + hash: match[3] || '', + pathname: match[1] || '', + searchParams: new URLSearchParams(match[2] || ''), + } + } + return undefined +} diff --git a/packages/uri/src/validation.ts b/packages/syntax/src/aturi_validation.ts similarity index 90% rename from packages/uri/src/validation.ts rename to packages/syntax/src/aturi_validation.ts index af0c359ca6e..a272b15a082 100644 --- a/packages/uri/src/validation.ts +++ b/packages/syntax/src/aturi_validation.ts @@ -1,5 +1,6 @@ -import * as id from '@atproto/identifier' -import * as nsid from '@atproto/nsid' +import { ensureValidHandle, ensureValidHandleRegex } from './handle' +import { ensureValidDid, ensureValidDidRegex } from './did' +import { ensureValidNsid, ensureValidNsidRegex } from './nsid' // Human-readable constraints on ATURI: // - following regular URLs, a 8KByte hard total length limit @@ -37,10 +38,10 @@ export const ensureValidAtUri = (uri: string) => { } try { - id.ensureValidHandle(parts[2]) + ensureValidHandle(parts[2]) } catch { try { - id.ensureValidDid(parts[2]) + ensureValidDid(parts[2]) } catch { throw new Error('ATURI authority must be a valid handle or DID') } @@ -53,7 +54,7 @@ export const ensureValidAtUri = (uri: string) => { ) } try { - nsid.ensureValidNsid(parts[3]) + ensureValidNsid(parts[3]) } catch { throw new Error( 'ATURI requires first path segment (if supplied) to be valid NSID', @@ -107,10 +108,10 @@ export const ensureValidAtUriRegex = (uri: string): void => { const groups = rm.groups try { - id.ensureValidHandleRegex(groups.authority) + ensureValidHandleRegex(groups.authority) } catch { try { - id.ensureValidDidRegex(groups.authority) + ensureValidDidRegex(groups.authority) } catch { throw new Error('ATURI authority must be a valid handle or DID') } @@ -118,7 +119,7 @@ export const ensureValidAtUriRegex = (uri: string): void => { if (groups.collection) { try { - nsid.ensureValidNsidRegex(groups.collection) + ensureValidNsidRegex(groups.collection) } catch { throw new Error('ATURI collection path segment must be a valid NSID') } diff --git a/packages/identifier/src/did.ts b/packages/syntax/src/did.ts similarity index 100% rename from packages/identifier/src/did.ts rename to packages/syntax/src/did.ts diff --git a/packages/identifier/src/handle.ts b/packages/syntax/src/handle.ts similarity index 71% rename from packages/identifier/src/handle.ts rename to packages/syntax/src/handle.ts index 8eb029c0cff..c42dc8b79dc 100644 --- a/packages/identifier/src/handle.ts +++ b/packages/syntax/src/handle.ts @@ -1,10 +1,10 @@ -import { reservedSubdomains } from './reserved' +export const INVALID_HANDLE = 'handle.invalid' // Currently these are registration-time restrictions, not protocol-level // restrictions. We have a couple accounts in the wild that we need to clean up // before hard-disallow. // See also: https://en.wikipedia.org/wiki/Top-level_domain#Reserved_domains -const DISALLOWED_TLDS = [ +export const DISALLOWED_TLDS = [ '.local', '.arpa', '.invalid', @@ -104,60 +104,12 @@ export const isValidHandle = (handle: string): boolean => { } throw err } - return true -} -export const ensureHandleServiceConstraints = ( - handle: string, - availableUserDomains: string[], - reserved = reservedSubdomains, -): void => { - const disallowedTld = DISALLOWED_TLDS.find((domain) => - handle.endsWith(domain), - ) - if (disallowedTld) { - throw new DisallowedDomainError('Handle TLD is invalid or disallowed') - } - const supportedDomain = availableUserDomains.find((domain) => - handle.endsWith(domain), - ) - if (!supportedDomain) { - throw new UnsupportedDomainError('Not a supported handle domain') - } - const front = handle.slice(0, handle.length - supportedDomain.length) - if (front.indexOf('.') > -1) { - throw new InvalidHandleError('Invalid characters in handle') - } - if (front.length < 3) { - throw new InvalidHandleError('Handle too short') - } - if (handle.length > 30) { - throw new InvalidHandleError('Handle too long') - } - if (reserved[front]) { - throw new ReservedHandleError('Reserved handle') - } + return true } -export const fulfillsHandleServiceConstraints = ( - handle: string, - availableUserDomains: string[], - reserved = reservedSubdomains, -): boolean => { - try { - ensureHandleServiceConstraints(handle, availableUserDomains, reserved) - } catch (err) { - if ( - err instanceof InvalidHandleError || - err instanceof ReservedHandleError || - err instanceof UnsupportedDomainError || - err instanceof DisallowedDomainError - ) { - return false - } - throw err - } - return true +export const isValidTld = (handle: string): boolean => { + return !DISALLOWED_TLDS.some((domain) => handle.endsWith(domain)) } export class InvalidHandleError extends Error {} diff --git a/packages/syntax/src/index.ts b/packages/syntax/src/index.ts new file mode 100644 index 00000000000..0b056b995ae --- /dev/null +++ b/packages/syntax/src/index.ts @@ -0,0 +1,4 @@ +export * from './handle' +export * from './did' +export * from './nsid' +export * from './aturi' diff --git a/packages/syntax/src/nsid.ts b/packages/syntax/src/nsid.ts new file mode 100644 index 00000000000..b436912bf7d --- /dev/null +++ b/packages/syntax/src/nsid.ts @@ -0,0 +1,111 @@ +/* +Grammar: + +alpha = "a" / "b" / "c" / "d" / "e" / "f" / "g" / "h" / "i" / "j" / "k" / "l" / "m" / "n" / "o" / "p" / "q" / "r" / "s" / "t" / "u" / "v" / "w" / "x" / "y" / "z" / "A" / "B" / "C" / "D" / "E" / "F" / "G" / "H" / "I" / "J" / "K" / "L" / "M" / "N" / "O" / "P" / "Q" / "R" / "S" / "T" / "U" / "V" / "W" / "X" / "Y" / "Z" +number = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / "0" +delim = "." +segment = alpha *( alpha / number / "-" ) +authority = segment *( delim segment ) +name = alpha *( alpha ) +nsid = authority delim name + +*/ + +export class NSID { + segments: string[] = [] + + static parse(nsid: string): NSID { + return new NSID(nsid) + } + + static create(authority: string, name: string): NSID { + const segments = [...authority.split('.').reverse(), name].join('.') + return new NSID(segments) + } + + static isValid(nsid: string): boolean { + try { + NSID.parse(nsid) + return true + } catch (e) { + return false + } + } + + constructor(nsid: string) { + ensureValidNsid(nsid) + this.segments = nsid.split('.') + } + + get authority() { + return this.segments + .slice(0, this.segments.length - 1) + .reverse() + .join('.') + } + + get name() { + return this.segments.at(this.segments.length - 1) + } + + toString() { + return this.segments.join('.') + } +} + +// Human readable constraints on NSID: +// - a valid domain in reversed notation +// - followed by an additional period-separated name, which is camel-case letters +export const ensureValidNsid = (nsid: string): void => { + const toCheck = nsid + + // check that all chars are boring ASCII + if (!/^[a-zA-Z0-9.-]*$/.test(toCheck)) { + throw new InvalidNsidError( + 'Disallowed characters in NSID (ASCII letters, digits, dashes, periods only)', + ) + } + + if (toCheck.length > 253 + 1 + 63) { + throw new InvalidNsidError('NSID is too long (317 chars max)') + } + const labels = toCheck.split('.') + if (labels.length < 3) { + throw new InvalidNsidError('NSID needs at least three parts') + } + for (let i = 0; i < labels.length; i++) { + const l = labels[i] + if (l.length < 1) { + throw new InvalidNsidError('NSID parts can not be empty') + } + if (l.length > 63) { + throw new InvalidNsidError('NSID part too long (max 63 chars)') + } + if (l.endsWith('-') || l.startsWith('-')) { + throw new InvalidNsidError('NSID parts can not start or end with hyphen') + } + if (/^[0-9]/.test(l) && i == 0) { + throw new InvalidNsidError('NSID first part may not start with a digit') + } + if (!/^[a-zA-Z]+$/.test(l) && i + 1 == labels.length) { + throw new InvalidNsidError('NSID name part must be only letters') + } + } +} + +export const ensureValidNsidRegex = (nsid: string): void => { + // simple regex to enforce most constraints via just regex and length. + // hand wrote this regex based on above constraints + if ( + !/^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\.[a-zA-Z]([a-zA-Z]{0,61}[a-zA-Z])?)$/.test( + nsid, + ) + ) { + throw new InvalidNsidError("NSID didn't validate via regex") + } + if (nsid.length > 253 + 1 + 63) { + throw new InvalidNsidError('NSID is too long (317 chars max)') + } +} + +export class InvalidNsidError extends Error {} diff --git a/packages/uri/tests/uri.test.ts b/packages/syntax/tests/aturi.test.ts similarity index 96% rename from packages/uri/tests/uri.test.ts rename to packages/syntax/tests/aturi.test.ts index 7506feb8364..dbc31652403 100644 --- a/packages/uri/tests/uri.test.ts +++ b/packages/syntax/tests/aturi.test.ts @@ -1,4 +1,6 @@ import { AtUri, ensureValidAtUri, ensureValidAtUriRegex } from '../src/index' +import * as readline from 'readline' +import * as fs from 'fs' describe('At Uris', () => { it('parses valid at uris', () => { @@ -503,4 +505,21 @@ describe('AtUri validation', () => { expectValid('at://did:plc:asdf123#/;') expectValid('at://did:plc:asdf123#/,') }) + + it('conforms to interop valid ATURIs', () => { + const lineReader = readline.createInterface({ + input: fs.createReadStream( + `${__dirname}/interop-files/aturi_syntax_valid.txt`, + ), + terminal: false, + }) + lineReader.on('line', (line) => { + if (line.startsWith('#') || line.length == 0) { + return + } + expectValid(line) + }) + }) + + // NOTE: this package is currently more permissive than spec about AT URIs, so invalid cases are not errors }) diff --git a/packages/identifier/tests/did.test.ts b/packages/syntax/tests/did.test.ts similarity index 72% rename from packages/identifier/tests/did.test.ts rename to packages/syntax/tests/did.test.ts index 5da98d703cb..d3408945828 100644 --- a/packages/identifier/tests/did.test.ts +++ b/packages/syntax/tests/did.test.ts @@ -1,4 +1,6 @@ import { ensureValidDid, ensureValidDidRegex, InvalidDidError } from '../src' +import * as readline from 'readline' +import * as fs from 'fs' describe('DID permissive validation', () => { const expectValid = (h: string) => { @@ -64,4 +66,34 @@ describe('DID permissive validation', () => { expectValid('did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N') expectValid('did:ethr:0xb9c5714089478a327f09197987f16f9e5d936e8a') }) + + it('conforms to interop valid DIDs', () => { + const lineReader = readline.createInterface({ + input: fs.createReadStream( + `${__dirname}/interop-files/did_syntax_valid.txt`, + ), + terminal: false, + }) + lineReader.on('line', (line) => { + if (line.startsWith('#') || line.length == 0) { + return + } + expectValid(line) + }) + }) + + it('conforms to interop invalid DIDs', () => { + const lineReader = readline.createInterface({ + input: fs.createReadStream( + `${__dirname}/interop-files/did_syntax_invalid.txt`, + ), + terminal: false, + }) + lineReader.on('line', (line) => { + if (line.startsWith('#') || line.length == 0) { + return + } + expectInvalid(line) + }) + }) }) diff --git a/packages/identifier/tests/handle.test.ts b/packages/syntax/tests/handle.test.ts similarity index 83% rename from packages/identifier/tests/handle.test.ts rename to packages/syntax/tests/handle.test.ts index 1ce36c7a3de..d3eac90be2c 100644 --- a/packages/identifier/tests/handle.test.ts +++ b/packages/syntax/tests/handle.test.ts @@ -1,10 +1,11 @@ import { ensureValidHandle, - ensureHandleServiceConstraints, normalizeAndEnsureValidHandle, ensureValidHandleRegex, InvalidHandleError, } from '../src' +import * as readline from 'readline' +import * as fs from 'fs' describe('handle validation', () => { const expectValid = (h: string) => { @@ -191,32 +192,39 @@ describe('handle validation', () => { ] badStackoverflow.forEach(expectInvalid) }) -}) -describe('service constraints & normalization', () => { - const domains = ['.bsky.app', '.test'] - it('throw on handles that violate service constraints', () => { - const expectThrow = (handle: string, err: string) => { - expect(() => ensureHandleServiceConstraints(handle, domains)).toThrow(err) - } - - expectThrow('john.bsky.io', 'Not a supported handle domain') - expectThrow('john.com', 'Not a supported handle domain') - expectThrow('j.test', 'Handle too short') - expectThrow('uk.test', 'Handle too short') - expectThrow('john.test.bsky.app', 'Invalid characters in handle') - expectThrow('about.test', 'Reserved handle') - expectThrow('atp.test', 'Reserved handle') - expectThrow('barackobama.test', 'Reserved handle') - - expectThrow('atproto.local', 'Handle TLD is invalid or disallowed') - expectThrow('atproto.arpa', 'Handle TLD is invalid or disallowed') - expectThrow('atproto.invalid', 'Handle TLD is invalid or disallowed') - expectThrow('atproto.localhost', 'Handle TLD is invalid or disallowed') - expectThrow('atproto.onion', 'Handle TLD is invalid or disallowed') - expectThrow('atproto.internal', 'Handle TLD is invalid or disallowed') + it('conforms to interop valid handles', () => { + const lineReader = readline.createInterface({ + input: fs.createReadStream( + `${__dirname}/interop-files/handle_syntax_valid.txt`, + ), + terminal: false, + }) + lineReader.on('line', (line) => { + if (line.startsWith('#') || line.length == 0) { + return + } + expectValid(line) + }) + }) + + it('conforms to interop invalid handles', () => { + const lineReader = readline.createInterface({ + input: fs.createReadStream( + `${__dirname}/interop-files/handle_syntax_invalid.txt`, + ), + terminal: false, + }) + lineReader.on('line', (line) => { + if (line.startsWith('#') || line.length == 0) { + return + } + expectInvalid(line) + }) }) +}) +describe('normalization', () => { it('normalizes handles', () => { const normalized = normalizeAndEnsureValidHandle('JoHn.TeST') expect(normalized).toBe('john.test') diff --git a/packages/syntax/tests/interop-files/aturi_syntax_invalid.txt b/packages/syntax/tests/interop-files/aturi_syntax_invalid.txt new file mode 120000 index 00000000000..6e1cd7942fb --- /dev/null +++ b/packages/syntax/tests/interop-files/aturi_syntax_invalid.txt @@ -0,0 +1 @@ +../../../../interop-test-files/syntax/aturi_syntax_invalid.txt \ No newline at end of file diff --git a/packages/syntax/tests/interop-files/aturi_syntax_valid.txt b/packages/syntax/tests/interop-files/aturi_syntax_valid.txt new file mode 120000 index 00000000000..6dd011dcb6b --- /dev/null +++ b/packages/syntax/tests/interop-files/aturi_syntax_valid.txt @@ -0,0 +1 @@ +../../../../interop-test-files/syntax/aturi_syntax_valid.txt \ No newline at end of file diff --git a/packages/syntax/tests/interop-files/did_syntax_invalid.txt b/packages/syntax/tests/interop-files/did_syntax_invalid.txt new file mode 120000 index 00000000000..723fbf8091a --- /dev/null +++ b/packages/syntax/tests/interop-files/did_syntax_invalid.txt @@ -0,0 +1 @@ +../../../../interop-test-files/syntax/did_syntax_invalid.txt \ No newline at end of file diff --git a/packages/syntax/tests/interop-files/did_syntax_valid.txt b/packages/syntax/tests/interop-files/did_syntax_valid.txt new file mode 120000 index 00000000000..045a569d192 --- /dev/null +++ b/packages/syntax/tests/interop-files/did_syntax_valid.txt @@ -0,0 +1 @@ +../../../../interop-test-files/syntax/did_syntax_valid.txt \ No newline at end of file diff --git a/packages/syntax/tests/interop-files/handle_syntax_invalid.txt b/packages/syntax/tests/interop-files/handle_syntax_invalid.txt new file mode 120000 index 00000000000..1842714c308 --- /dev/null +++ b/packages/syntax/tests/interop-files/handle_syntax_invalid.txt @@ -0,0 +1 @@ +../../../../interop-test-files/syntax/handle_syntax_invalid.txt \ No newline at end of file diff --git a/packages/syntax/tests/interop-files/handle_syntax_valid.txt b/packages/syntax/tests/interop-files/handle_syntax_valid.txt new file mode 120000 index 00000000000..d8f700d05fb --- /dev/null +++ b/packages/syntax/tests/interop-files/handle_syntax_valid.txt @@ -0,0 +1 @@ +../../../../interop-test-files/syntax/handle_syntax_valid.txt \ No newline at end of file diff --git a/packages/syntax/tests/interop-files/nsid_syntax_invalid.txt b/packages/syntax/tests/interop-files/nsid_syntax_invalid.txt new file mode 120000 index 00000000000..62a0824ae1c --- /dev/null +++ b/packages/syntax/tests/interop-files/nsid_syntax_invalid.txt @@ -0,0 +1 @@ +../../../../interop-test-files/syntax/nsid_syntax_invalid.txt \ No newline at end of file diff --git a/packages/syntax/tests/interop-files/nsid_syntax_valid.txt b/packages/syntax/tests/interop-files/nsid_syntax_valid.txt new file mode 120000 index 00000000000..95a800d2d15 --- /dev/null +++ b/packages/syntax/tests/interop-files/nsid_syntax_valid.txt @@ -0,0 +1 @@ +../../../../interop-test-files/syntax/nsid_syntax_valid.txt \ No newline at end of file diff --git a/packages/nsid/tests/nsid.test.ts b/packages/syntax/tests/nsid.test.ts similarity index 83% rename from packages/nsid/tests/nsid.test.ts rename to packages/syntax/tests/nsid.test.ts index c2d7f74ddf9..a57448ccabb 100644 --- a/packages/nsid/tests/nsid.test.ts +++ b/packages/syntax/tests/nsid.test.ts @@ -4,6 +4,8 @@ import { InvalidNsidError, NSID, } from '../src' +import * as readline from 'readline' +import * as fs from 'fs' describe('NSID parsing & creation', () => { it('parses valid NSIDs', () => { @@ -123,4 +125,34 @@ describe('NSID validation', () => { 'onion.2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.lex.deleteThing', ) }) + + it('conforms to interop valid NSIDs', () => { + const lineReader = readline.createInterface({ + input: fs.createReadStream( + `${__dirname}/interop-files/nsid_syntax_valid.txt`, + ), + terminal: false, + }) + lineReader.on('line', (line) => { + if (line.startsWith('#') || line.length == 0) { + return + } + expectValid(line) + }) + }) + + it('conforms to interop invalid NSIDs', () => { + const lineReader = readline.createInterface({ + input: fs.createReadStream( + `${__dirname}/interop-files/nsid_syntax_invalid.txt`, + ), + terminal: false, + }) + lineReader.on('line', (line) => { + if (line.startsWith('#') || line.length == 0) { + return + } + expectInvalid(line) + }) + }) }) diff --git a/packages/syntax/tsconfig.build.json b/packages/syntax/tsconfig.build.json new file mode 100644 index 00000000000..02a84823b65 --- /dev/null +++ b/packages/syntax/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["**/*.spec.ts", "**/*.test.ts"] +} diff --git a/packages/syntax/tsconfig.json b/packages/syntax/tsconfig.json new file mode 100644 index 00000000000..db7a7c4ad35 --- /dev/null +++ b/packages/syntax/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", // Your outDir, + "emitDeclarationOnly": true + }, + "include": ["./src", "__tests__/**/**.ts"], + "references": [{ "path": "../common/tsconfig.build.json" }] +} diff --git a/packages/uri/README.md b/packages/uri/README.md index 43c8bc9e435..8aaee47f7f2 100644 --- a/packages/uri/README.md +++ b/packages/uri/README.md @@ -6,13 +6,13 @@ import { AtUri } from '@atproto/uri' const uri = new AtUri('at://bob.com/com.example.post/1234') -uri.protocol // => 'at:' -uri.origin // => 'at://bob.com' -uri.hostname // => 'bob.com' +uri.protocol // => 'at:' +uri.origin // => 'at://bob.com' +uri.hostname // => 'bob.com' uri.collection // => 'com.example.post' -uri.rkey // => '1234' +uri.rkey // => '1234' ``` ## License -MIT \ No newline at end of file +MIT diff --git a/packages/uri/build.js b/packages/uri/build.js index 5628aa4f4eb..e880ae9930b 100644 --- a/packages/uri/build.js +++ b/packages/uri/build.js @@ -1,16 +1,8 @@ -const pkgJson = require('@npmcli/package-json') const { nodeExternalsPlugin } = require('esbuild-node-externals') const buildShallow = process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true' -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} - require('esbuild').build({ logLevel: 'info', entryPoints: ['src/index.ts'], diff --git a/packages/uri/package.json b/packages/uri/package.json index 67dfec18ad8..1060dda6890 100644 --- a/packages/uri/package.json +++ b/packages/uri/package.json @@ -1,21 +1,16 @@ { "name": "@atproto/uri", - "version": "0.0.2", + "version": "0.1.0", "main": "src/index.ts", + "publishConfig": { + "main": "dist/index.js", + "types": "dist/index.d.ts" + }, "scripts": { - "test": "jest", - "prettier": "prettier --check src/ tests/", - "prettier:fix": "prettier --write src/ tests/", - "lint": "eslint . --ext .ts,.tsx", - "lint:fix": "yarn lint --fix", - "verify": "run-p prettier lint", - "verify:fix": "yarn prettier:fix && yarn lint:fix", + "test": "true", "build": "node ./build.js", "postbuild": "tsc --build tsconfig.build.json", - "update-main-to-dist": "node ./update-pkg.js --update-main-to-dist", - "update-main-to-src": "node ./update-pkg.js --update-main-to-src", - "prepublish": "npm run update-main-to-dist", - "postpublish": "npm run update-main-to-src" + "update-main-to-dist": "node ../../update-main-to-dist.js packages/uri" }, "license": "MIT", "repository": { @@ -24,7 +19,6 @@ "directory": "packages/uri" }, "dependencies": { - "@atproto/identifier": "*", - "@atproto/nsid": "*" + "@atproto/syntax": "workspace:^" } } diff --git a/packages/uri/src/index.ts b/packages/uri/src/index.ts index 538b1ad736f..1c657b16049 100644 --- a/packages/uri/src/index.ts +++ b/packages/uri/src/index.ts @@ -1,136 +1,6 @@ -export * from './validation' - -export const ATP_URI_REGEX = - // proto- --did-------------- --name---------------- --path---- --query-- --hash-- - /^(at:\/\/)?((?:did:[a-z0-9:%-]+)|(?:[a-z0-9][a-z0-9.:-]*))(\/[^?#\s]*)?(\?[^#\s]+)?(#[^\s]+)?$/i -// --path----- --query-- --hash-- -const RELATIVE_REGEX = /^(\/[^?#\s]*)?(\?[^#\s]+)?(#[^\s]+)?$/i - -export class AtUri { - hash: string - host: string - pathname: string - searchParams: URLSearchParams - - constructor(uri: string, base?: string) { - let parsed - if (base) { - parsed = parse(base) - if (!parsed) { - throw new Error(`Invalid at uri: ${base}`) - } - const relativep = parseRelative(uri) - if (!relativep) { - throw new Error(`Invalid path: ${uri}`) - } - Object.assign(parsed, relativep) - } else { - parsed = parse(uri) - if (!parsed) { - throw new Error(`Invalid at uri: ${uri}`) - } - } - - this.hash = parsed.hash - this.host = parsed.host - this.pathname = parsed.pathname - this.searchParams = parsed.searchParams - } - - static make(handleOrDid: string, collection?: string, rkey?: string) { - let str = handleOrDid - if (collection) str += '/' + collection - if (rkey) str += '/' + rkey - return new AtUri(str) - } - - get protocol() { - return 'at:' - } - - get origin() { - return `at://${this.host}` - } - - get hostname() { - return this.host - } - - set hostname(v: string) { - this.host = v - } - - get search() { - return this.searchParams.toString() - } - - set search(v: string) { - this.searchParams = new URLSearchParams(v) - } - - get collection() { - return this.pathname.split('/').filter(Boolean)[0] || '' - } - - set collection(v: string) { - const parts = this.pathname.split('/').filter(Boolean) - parts[0] = v - this.pathname = parts.join('/') - } - - get rkey() { - return this.pathname.split('/').filter(Boolean)[1] || '' - } - - set rkey(v: string) { - const parts = this.pathname.split('/').filter(Boolean) - if (!parts[0]) parts[0] = 'undefined' - parts[1] = v - this.pathname = parts.join('/') - } - - get href() { - return this.toString() - } - - toString() { - let path = this.pathname || '/' - if (!path.startsWith('/')) { - path = `/${path}` - } - let qs = this.searchParams.toString() - if (qs && !qs.startsWith('?')) { - qs = `?${qs}` - } - let hash = this.hash - if (hash && !hash.startsWith('#')) { - hash = `#${hash}` - } - return `at://${this.host}${path}${qs}${hash}` - } -} - -function parse(str: string) { - const match = ATP_URI_REGEX.exec(str) - if (match) { - return { - hash: match[5] || '', - host: match[2] || '', - pathname: match[3] || '', - searchParams: new URLSearchParams(match[4] || ''), - } - } - return undefined -} - -function parseRelative(str: string) { - const match = RELATIVE_REGEX.exec(str) - if (match) { - return { - hash: match[3] || '', - pathname: match[1] || '', - searchParams: new URLSearchParams(match[2] || ''), - } - } - return undefined -} +export { + ATP_URI_REGEX, + AtUri, + ensureValidAtUri, + ensureValidAtUriRegex, +} from '@atproto/syntax' diff --git a/packages/uri/tsconfig.build.json b/packages/uri/tsconfig.build.json index 27df65b89e2..02a84823b65 100644 --- a/packages/uri/tsconfig.build.json +++ b/packages/uri/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", "exclude": ["**/*.spec.ts", "**/*.test.ts"] -} \ No newline at end of file +} diff --git a/packages/uri/tsconfig.json b/packages/uri/tsconfig.json index b678da34064..4faf3966f41 100644 --- a/packages/uri/tsconfig.json +++ b/packages/uri/tsconfig.json @@ -5,9 +5,9 @@ "outDir": "./dist", // Your outDir, "emitDeclarationOnly": true }, - "include": ["./src","__tests__/**/**.ts"], + "include": ["./src", "__tests__/**/**.ts"], "references": [ { "path": "../identifier/tsconfig.build.json" }, - { "path": "../nsid/tsconfig.build.json" }, + { "path": "../nsid/tsconfig.build.json" } ] -} \ No newline at end of file +} diff --git a/packages/uri/update-pkg.js b/packages/uri/update-pkg.js deleted file mode 100644 index 15d70bdab33..00000000000 --- a/packages/uri/update-pkg.js +++ /dev/null @@ -1,14 +0,0 @@ -const pkgJson = require('@npmcli/package-json') - -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} -if (process.argv.includes('--update-main-to-src')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'src/index.ts' })) - .then((pkg) => pkg.save()) -} diff --git a/packages/xrpc-server/README.md b/packages/xrpc-server/README.md index 65e930e4e33..2c297043fdf 100644 --- a/packages/xrpc-server/README.md +++ b/packages/xrpc-server/README.md @@ -7,7 +7,8 @@ import * as xrpc from '@atproto/xrpc-server' import express from 'express' // create xrpc server -const server = xrpc.createServer([{ +const server = xrpc.createServer([ + { lexicon: 1, id: 'io.example.ping', defs: { @@ -22,11 +23,17 @@ const server = xrpc.createServer([{ }, }, }, - } + }, ]) -function ping(ctx: {auth: xrpc.HandlerAuth | undefined, params: xrpc.Params, input: xrpc.HandlerInput | undefined, req: express.Request, res: express.Response}) { - return { encoding: 'application/json', body: {message: ctx.params.message }} +function ping(ctx: { + auth: xrpc.HandlerAuth | undefined + params: xrpc.Params + input: xrpc.HandlerInput | undefined + req: express.Request + res: express.Response +}) { + return { encoding: 'application/json', body: { message: ctx.params.message } } } server.method('io.example.ping', ping) diff --git a/packages/xrpc-server/build.js b/packages/xrpc-server/build.js index 5628aa4f4eb..e880ae9930b 100644 --- a/packages/xrpc-server/build.js +++ b/packages/xrpc-server/build.js @@ -1,16 +1,8 @@ -const pkgJson = require('@npmcli/package-json') const { nodeExternalsPlugin } = require('esbuild-node-externals') const buildShallow = process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true' -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} - require('esbuild').build({ logLevel: 'info', entryPoints: ['src/index.ts'], diff --git a/packages/xrpc-server/package.json b/packages/xrpc-server/package.json index 4ae5eed391e..e51db0f0a0f 100644 --- a/packages/xrpc-server/package.json +++ b/packages/xrpc-server/package.json @@ -1,21 +1,16 @@ { "name": "@atproto/xrpc-server", - "version": "0.2.0", + "version": "0.3.0", "main": "src/index.ts", + "publishConfig": { + "main": "dist/index.js", + "types": "dist/index.d.ts" + }, "scripts": { "test": "jest", - "prettier": "prettier --check src/ tests/", - "prettier:fix": "prettier --write src/ tests/", - "lint": "eslint . --ext .ts,.tsx", - "lint:fix": "yarn lint --fix", - "verify": "run-p prettier lint", - "verify:fix": "yarn prettier:fix && yarn lint:fix", "build": "node ./build.js", "postbuild": "tsc --build tsconfig.build.json", - "update-main-to-dist": "node ./update-pkg.js --update-main-to-dist", - "update-main-to-src": "node ./update-pkg.js --update-main-to-src", - "prepublish": "npm run update-main-to-dist", - "postpublish": "npm run update-main-to-src" + "update-main-to-dist": "node ../../update-main-to-dist.js packages/xrpc-server" }, "license": "MIT", "repository": { @@ -24,24 +19,26 @@ "directory": "packages/xrpc-server" }, "dependencies": { - "@atproto/common": "*", - "@atproto/crypto": "*", - "@atproto/lexicon": "*", + "@atproto/common": "workspace:^", + "@atproto/crypto": "workspace:^", + "@atproto/lexicon": "workspace:^", "cbor-x": "^1.5.1", "express": "^4.17.2", "http-errors": "^2.0.0", "mime-types": "^2.1.35", + "rate-limiter-flexible": "^2.4.1", "uint8arrays": "3.0.0", "ws": "^8.12.0", "zod": "^3.21.4" }, "devDependencies": { - "@atproto/crypto": "*", - "@atproto/xrpc": "*", + "@atproto/crypto": "workspace:^", + "@atproto/xrpc": "workspace:^", "@types/express": "^4.17.13", + "@types/express-serve-static-core": "^4.17.36", "@types/http-errors": "^2.0.1", "@types/ws": "^8.5.4", "get-port": "^6.1.2", - "multiformats": "^9.6.4" + "multiformats": "^9.9.0" } } diff --git a/packages/xrpc-server/src/index.ts b/packages/xrpc-server/src/index.ts index a1bd22ab183..1458d2ba070 100644 --- a/packages/xrpc-server/src/index.ts +++ b/packages/xrpc-server/src/index.ts @@ -2,6 +2,7 @@ export * from './types' export * from './auth' export * from './server' export * from './stream' +export * from './rate-limiter' export type { ServerTiming } from './util' export { serverTimingHeader, ServerTimer } from './util' diff --git a/packages/xrpc-server/src/logger.ts b/packages/xrpc-server/src/logger.ts index 87ec0bd1ae0..1e8599637e0 100644 --- a/packages/xrpc-server/src/logger.ts +++ b/packages/xrpc-server/src/logger.ts @@ -1,5 +1,6 @@ import { subsystemLogger } from '@atproto/common' -export const logger = subsystemLogger('xrpc-server') +export const logger: ReturnType = + subsystemLogger('xrpc-server') export default logger diff --git a/packages/xrpc-server/src/rate-limiter.ts b/packages/xrpc-server/src/rate-limiter.ts new file mode 100644 index 00000000000..82719101674 --- /dev/null +++ b/packages/xrpc-server/src/rate-limiter.ts @@ -0,0 +1,160 @@ +import { + RateLimiterAbstract, + RateLimiterMemory, + RateLimiterRedis, + RateLimiterRes, +} from 'rate-limiter-flexible' +import { logger } from './logger' +import { + CalcKeyFn, + CalcPointsFn, + RateLimitExceededError, + RateLimiterConsume, + RateLimiterI, + RateLimiterStatus, + XRPCReqContext, +} from './types' + +export type RateLimiterOpts = { + keyPrefix: string + durationMs: number + points: number + bypassSecret?: string + calcKey?: CalcKeyFn + calcPoints?: CalcPointsFn + failClosed?: boolean +} + +export class RateLimiter implements RateLimiterI { + public limiter: RateLimiterAbstract + private byPassSecret?: string + private failClosed?: boolean + public calcKey: CalcKeyFn + public calcPoints: CalcPointsFn + + constructor(limiter: RateLimiterAbstract, opts: RateLimiterOpts) { + this.limiter = limiter + this.byPassSecret = opts.bypassSecret + this.calcKey = opts.calcKey ?? defaultKey + this.calcPoints = opts.calcPoints ?? defaultPoints + } + + static memory(opts: RateLimiterOpts): RateLimiter { + const limiter = new RateLimiterMemory({ + keyPrefix: opts.keyPrefix, + duration: Math.floor(opts.durationMs / 1000), + points: opts.points, + }) + return new RateLimiter(limiter, opts) + } + + static redis(storeClient: unknown, opts: RateLimiterOpts): RateLimiter { + const limiter = new RateLimiterRedis({ + storeClient, + keyPrefix: opts.keyPrefix, + duration: Math.floor(opts.durationMs / 1000), + points: opts.points, + }) + return new RateLimiter(limiter, opts) + } + + async consume( + ctx: XRPCReqContext, + opts?: { calcKey?: CalcKeyFn; calcPoints?: CalcPointsFn }, + ): Promise { + if ( + this.byPassSecret && + ctx.req.header('x-ratelimit-bypass') === this.byPassSecret + ) { + return null + } + const key = opts?.calcKey ? opts.calcKey(ctx) : this.calcKey(ctx) + const points = opts?.calcPoints + ? opts.calcPoints(ctx) + : this.calcPoints(ctx) + if (points < 1) { + return null + } + try { + const res = await this.limiter.consume(key, points) + return formatLimiterStatus(this.limiter, res) + } catch (err) { + // yes this library rejects with a res not an error + if (err instanceof RateLimiterRes) { + const status = formatLimiterStatus(this.limiter, err) + throw new RateLimitExceededError(status) + } else { + if (this.failClosed) { + throw err + } + logger.error( + { + err, + keyPrefix: this.limiter.keyPrefix, + points: this.limiter.points, + duration: this.limiter.duration, + }, + 'rate limiter failed to consume points', + ) + return null + } + } + } +} + +export const formatLimiterStatus = ( + limiter: RateLimiterAbstract, + res: RateLimiterRes, +): RateLimiterStatus => { + return { + limit: limiter.points, + duration: limiter.duration, + remainingPoints: res.remainingPoints, + msBeforeNext: res.msBeforeNext, + consumedPoints: res.consumedPoints, + isFirstInDuration: res.isFirstInDuration, + } +} + +export const consumeMany = async ( + ctx: XRPCReqContext, + fns: RateLimiterConsume[], +): Promise => { + if (fns.length === 0) return + const results = await Promise.all(fns.map((fn) => fn(ctx))) + const tightestLimit = getTightestLimit(results) + if (tightestLimit !== null) { + setResHeaders(ctx, tightestLimit) + } +} + +export const setResHeaders = ( + ctx: XRPCReqContext, + status: RateLimiterStatus, +) => { + ctx.res.setHeader('RateLimit-Limit', status.limit) + ctx.res.setHeader('RateLimit-Remaining', status.remainingPoints) + ctx.res.setHeader( + 'RateLimit-Reset', + Math.floor((Date.now() + status.msBeforeNext) / 1000), + ) + ctx.res.setHeader('RateLimit-Policy', `${status.limit};w=${status.duration}`) +} + +export const getTightestLimit = ( + resps: (RateLimiterStatus | null)[], +): RateLimiterStatus | null => { + let lowest: RateLimiterStatus | null = null + for (const resp of resps) { + if (resp === null) continue + if (lowest === null || resp.remainingPoints < lowest.remainingPoints) { + lowest = resp + } + } + return lowest +} + +// when using a proxy, ensure headers are getting forwarded correctly: `app.set('trust proxy', true)` +// https://expressjs.com/en/guide/behind-proxies.html +const defaultKey: CalcKeyFn = (ctx: XRPCReqContext) => ctx.req.ip +const defaultPoints: CalcPointsFn = () => 1 diff --git a/packages/xrpc-server/src/server.ts b/packages/xrpc-server/src/server.ts index 2c30ae8a440..40f4baac771 100644 --- a/packages/xrpc-server/src/server.ts +++ b/packages/xrpc-server/src/server.ts @@ -30,6 +30,10 @@ import { XRPCStreamHandler, Params, InternalServerError, + XRPCReqContext, + RateLimiterI, + RateLimiterConsume, + isShared, } from './types' import { decodeQueryParams, @@ -38,6 +42,7 @@ import { validateOutput, } from './util' import log from './logger' +import { consumeMany } from './rate-limiter' export function createServer(lexicons?: unknown[], options?: Options) { return new Server(lexicons, options) @@ -50,6 +55,9 @@ export class Server { lex = new Lexicons() options: Options middleware: Record<'json' | 'text', RequestHandler> + globalRateLimiters: RateLimiterI[] + sharedRateLimiters: Record + routeRateLimiterFns: Record constructor(lexicons?: unknown[], opts?: Options) { if (lexicons) { @@ -66,6 +74,27 @@ export class Server { json: express.json({ limit: opts?.payload?.jsonLimit }), text: express.text({ limit: opts?.payload?.textLimit }), } + this.globalRateLimiters = [] + this.sharedRateLimiters = {} + this.routeRateLimiterFns = {} + if (opts?.rateLimits?.global) { + for (const limit of opts.rateLimits.global) { + const rateLimiter = opts.rateLimits.creator({ + ...limit, + keyPrefix: `rl-${limit.name}`, + }) + this.globalRateLimiters.push(rateLimiter) + } + } + if (opts?.rateLimits?.shared) { + for (const limit of opts.rateLimits.shared) { + const rateLimiter = opts.rateLimits.creator({ + ...limit, + keyPrefix: `rl-${limit.name}`, + }) + this.sharedRateLimiters[limit.name] = rateLimiter + } + } } // handlers @@ -138,6 +167,7 @@ export class Server { middleware.push(this.middleware.json) middleware.push(this.middleware.text) } + this.setupRouteRateLimits(nsid, config) this.routes[verb]( `/xrpc/${nsid}`, ...middleware, @@ -185,6 +215,10 @@ export class Server { validateOutput(nsid, def, output, this.lex) const assertValidXrpcParams = (params: unknown) => this.lex.assertValidXrpcParams(nsid, params) + const rlFns = this.routeRateLimiterFns[nsid] ?? [] + const consumeRateLimit = (reqCtx: XRPCReqContext) => + consumeMany(reqCtx, rlFns) + return async function (req, res, next) { try { // validate request @@ -203,14 +237,21 @@ export class Server { const locals: RequestLocals = req[kRequestLocals] - // run the handler - const outputUnvalidated = await handler({ + const reqCtx: XRPCReqContext = { params, input, auth: locals.auth, req, res, - }) + } + + // handle rate limits + if (consumeRateLimit) { + await consumeRateLimit(reqCtx) + } + + // run the handler + const outputUnvalidated = await handler(reqCtx) if (isHandlerError(outputUnvalidated)) { throw XRPCError.fromError(outputUnvalidated) @@ -235,6 +276,7 @@ export class Server { } else if (output?.body instanceof Readable) { res.header('Content-Type', output.encoding) res.status(200) + res.once('error', (err) => res.destroy(err)) forwardStreamErrors(output.body, res) output.body.pipe(res) } else if (output) { @@ -344,6 +386,55 @@ export class Server { return httpServer } } + + private setupRouteRateLimits(nsid: string, config: XRPCHandlerConfig) { + this.routeRateLimiterFns[nsid] = [] + for (const limit of this.globalRateLimiters) { + const consumeFn = async (ctx: XRPCReqContext) => { + return limit.consume(ctx) + } + this.routeRateLimiterFns[nsid].push(consumeFn) + } + + if (config.rateLimit) { + const limits = Array.isArray(config.rateLimit) + ? config.rateLimit + : [config.rateLimit] + this.routeRateLimiterFns[nsid] = [] + for (const limit of limits) { + const { calcKey, calcPoints } = limit + if (isShared(limit)) { + const rateLimiter = this.sharedRateLimiters[limit.name] + if (rateLimiter) { + const consumeFn = (ctx: XRPCReqContext) => + rateLimiter.consume(ctx, { + calcKey, + calcPoints, + }) + this.routeRateLimiterFns[nsid].push(consumeFn) + } + } else { + const { durationMs, points } = limit + const rateLimiter = this.options.rateLimits?.creator({ + keyPrefix: nsid, + durationMs, + points, + calcKey, + calcPoints, + }) + if (rateLimiter) { + this.sharedRateLimiters[nsid] = rateLimiter + const consumeFn = (ctx: XRPCReqContext) => + rateLimiter.consume(ctx, { + calcKey, + calcPoints, + }) + this.routeRateLimiterFns[nsid].push(consumeFn) + } + } + } + } + } } function isHandlerSuccess(v: HandlerOutput): v is HandlerSuccess { diff --git a/packages/xrpc-server/src/stream/logger.ts b/packages/xrpc-server/src/stream/logger.ts index 952d2cb8fc9..7cff29b9ade 100644 --- a/packages/xrpc-server/src/stream/logger.ts +++ b/packages/xrpc-server/src/stream/logger.ts @@ -1,5 +1,6 @@ import { subsystemLogger } from '@atproto/common' -export const logger = subsystemLogger('xrpc-stream') +export const logger: ReturnType = + subsystemLogger('xrpc-stream') export default logger diff --git a/packages/xrpc-server/src/stream/stream.ts b/packages/xrpc-server/src/stream/stream.ts index 356d8835caf..d2475365e0d 100644 --- a/packages/xrpc-server/src/stream/stream.ts +++ b/packages/xrpc-server/src/stream/stream.ts @@ -1,26 +1,39 @@ import { XRPCError, ResponseType } from '@atproto/xrpc' import { DuplexOptions } from 'stream' import { createWebSocketStream, WebSocket } from 'ws' -import { Frame } from './frames' +import { Frame, MessageFrame } from './frames' -export async function* byFrame(ws: WebSocket, options?: DuplexOptions) { - const wsStream = createWebSocketStream(ws, { +export function streamByteChunks(ws: WebSocket, options?: DuplexOptions) { + return createWebSocketStream(ws, { ...options, readableObjectMode: true, // Ensures frame bytes don't get buffered/combined together }) +} + +export async function* byFrame(ws: WebSocket, options?: DuplexOptions) { + const wsStream = streamByteChunks(ws, options) for await (const chunk of wsStream) { yield Frame.fromBytes(chunk) } } export async function* byMessage(ws: WebSocket, options?: DuplexOptions) { - for await (const frame of byFrame(ws, options)) { - if (frame.isMessage()) { - yield frame - } else if (frame.isError()) { - throw new XRPCError(-1, frame.code, frame.message) - } else { - throw new XRPCError(ResponseType.Unknown, undefined, 'Unknown frame type') - } + const wsStream = streamByteChunks(ws, options) + for await (const chunk of wsStream) { + const msg = ensureChunkIsMessage(chunk) + yield msg + } +} + +export function ensureChunkIsMessage(chunk: Uint8Array): MessageFrame { + const frame = Frame.fromBytes(chunk) + if (frame.isMessage()) { + return frame + } else if (frame.isError()) { + // @TODO work -1 error code into XRPCError + // @ts-ignore + throw new XRPCError(-1, frame.code, frame.message) + } else { + throw new XRPCError(ResponseType.Unknown, undefined, 'Unknown frame type') } } diff --git a/packages/xrpc-server/src/stream/subscription.ts b/packages/xrpc-server/src/stream/subscription.ts index c4d6c9eec0e..50fdb804017 100644 --- a/packages/xrpc-server/src/stream/subscription.ts +++ b/packages/xrpc-server/src/stream/subscription.ts @@ -1,7 +1,6 @@ -import { wait } from '@atproto/common' -import { WebSocket, ClientOptions } from 'ws' -import { byMessage } from './stream' -import { CloseCode, DisconnectError } from './types' +import { ClientOptions } from 'ws' +import { WebSocketKeepAlive } from './websocket-keepalive' +import { ensureChunkIsMessage } from './stream' export class Subscription { constructor( @@ -9,6 +8,7 @@ export class Subscription { service: string method: string maxReconnectSeconds?: number + heartbeatIntervalMs?: number signal?: AbortSignal validate: (obj: unknown) => T | undefined onReconnectError?: ( @@ -24,107 +24,31 @@ export class Subscription { ) {} async *[Symbol.asyncIterator](): AsyncGenerator { - let initialSetup = true - let reconnects: number | null = null - const maxReconnectMs = 1000 * (this.opts.maxReconnectSeconds ?? 64) - while (true) { - if (reconnects !== null) { - const duration = initialSetup - ? Math.min(1000, maxReconnectMs) - : backoffMs(reconnects++, maxReconnectMs) - await wait(duration) - } - const ws = await this.getSocket() - const ac = new AbortController() - if (this.opts.signal) { - forwardSignal(this.opts.signal, ac) + const ws = new WebSocketKeepAlive({ + ...this.opts, + getUrl: async () => { + const params = (await this.opts.getParams?.()) ?? {} + const query = encodeQueryParams(params) + return `${this.opts.service}/xrpc/${this.opts.method}?${query}` + }, + }) + for await (const chunk of ws) { + const message = await ensureChunkIsMessage(chunk) + const t = message.header.t + const clone = message.body !== undefined ? { ...message.body } : undefined + if (clone !== undefined && t !== undefined) { + clone['$type'] = t.startsWith('#') ? this.opts.method + t : t } - ws.once('open', () => { - initialSetup = false - reconnects = 0 - }) - ws.once('close', (code, reason) => { - if (code === CloseCode.Abnormal) { - // Forward into an error to distinguish from a clean close - ac.abort( - new AbnormalCloseError(`Abnormal ws close: ${reason.toString()}`), - ) - } - }) - try { - const cancelable = { signal: ac.signal } - for await (const message of byMessage(ws, cancelable)) { - const t = message.header.t - const clone = - message.body !== undefined ? { ...message.body } : undefined - if (clone !== undefined && t !== undefined) { - clone['$type'] = t.startsWith('#') ? this.opts.method + t : t - } - const result = this.opts.validate(clone) - if (result !== undefined) { - yield result - } - } - } catch (_err) { - const err = _err?.['code'] === 'ABORT_ERR' ? _err['cause'] : _err - if (err instanceof DisconnectError) { - // We cleanly end the connection - ws.close(err.wsCode) - break - } - ws.close() // No-ops if already closed or closing - if (isReconnectable(err)) { - reconnects ??= 0 // Never reconnect with a null - this.opts.onReconnectError?.(err, reconnects, initialSetup) - continue - } else { - throw err - } + const result = this.opts.validate(clone) + if (result !== undefined) { + yield result } - break // Other side cleanly ended stream and disconnected } } - - private async getSocket() { - const params = (await this.opts.getParams?.()) ?? {} - const query = encodeQueryParams(params) - const url = `${this.opts.service}/xrpc/${this.opts.method}?${query}` - return new WebSocket(url, this.opts) - } } export default Subscription -class AbnormalCloseError extends Error { - code = 'EWSABNORMALCLOSE' -} - -function isReconnectable(err: unknown): boolean { - // Network errors are reconnectable. - // AuthenticationRequired and InvalidRequest XRPCErrors are not reconnectable. - // @TODO method-specific XRPCErrors may be reconnectable, need to consider. Receiving - // an invalid message is not current reconnectable, but the user can decide to skip them. - if (!err || typeof err['code'] !== 'string') return false - return networkErrorCodes.includes(err['code']) -} - -const networkErrorCodes = [ - 'EWSABNORMALCLOSE', - 'ECONNRESET', - 'ECONNREFUSED', - 'ECONNABORTED', - 'EPIPE', - 'ETIMEDOUT', - 'ECANCELED', -] - -function backoffMs(n: number, maxMs: number) { - const baseSec = Math.pow(2, n) // 1, 2, 4, ... - const randSec = Math.random() - 0.5 // Random jitter between -.5 and .5 seconds - const ms = 1000 * (baseSec + randSec) - return Math.min(ms, maxMs) -} - function encodeQueryParams(obj: Record): string { const params = new URLSearchParams() Object.entries(obj).forEach(([key, value]) => { @@ -163,13 +87,3 @@ function encodeQueryParam(value: unknown): string | string[] { } throw new Error(`Cannot encode ${typeof value}s into query params`) } - -function forwardSignal(signal: AbortSignal, ac: AbortController) { - if (signal.aborted) { - return ac.abort(signal.reason) - } else { - signal.addEventListener('abort', () => ac.abort(signal.reason), { - signal: ac.signal, - }) - } -} diff --git a/packages/xrpc-server/src/stream/websocket-keepalive.ts b/packages/xrpc-server/src/stream/websocket-keepalive.ts new file mode 100644 index 00000000000..696a058df5c --- /dev/null +++ b/packages/xrpc-server/src/stream/websocket-keepalive.ts @@ -0,0 +1,151 @@ +import { SECOND, wait } from '@atproto/common' +import { WebSocket, ClientOptions } from 'ws' +import { streamByteChunks } from './stream' +import { CloseCode, DisconnectError } from './types' + +export class WebSocketKeepAlive { + public ws: WebSocket | null = null + public initialSetup = true + public reconnects: number | null = null + + constructor( + public opts: ClientOptions & { + getUrl: () => Promise + maxReconnectSeconds?: number + signal?: AbortSignal + heartbeatIntervalMs?: number + onReconnectError?: ( + error: unknown, + n: number, + initialSetup: boolean, + ) => void + }, + ) {} + + async *[Symbol.asyncIterator](): AsyncGenerator { + const maxReconnectMs = 1000 * (this.opts.maxReconnectSeconds ?? 64) + while (true) { + if (this.reconnects !== null) { + const duration = this.initialSetup + ? Math.min(1000, maxReconnectMs) + : backoffMs(this.reconnects++, maxReconnectMs) + await wait(duration) + } + const url = await this.opts.getUrl() + this.ws = new WebSocket(url, this.opts) + const ac = new AbortController() + if (this.opts.signal) { + forwardSignal(this.opts.signal, ac) + } + this.ws.once('open', () => { + this.initialSetup = false + this.reconnects = 0 + if (this.ws) { + this.startHeartbeat(this.ws) + } + }) + this.ws.once('close', (code, reason) => { + if (code === CloseCode.Abnormal) { + // Forward into an error to distinguish from a clean close + ac.abort( + new AbnormalCloseError(`Abnormal ws close: ${reason.toString()}`), + ) + } + }) + + try { + const wsStream = streamByteChunks(this.ws, { signal: ac.signal }) + for await (const chunk of wsStream) { + yield chunk + } + } catch (_err) { + const err = _err?.['code'] === 'ABORT_ERR' ? _err['cause'] : _err + if (err instanceof DisconnectError) { + // We cleanly end the connection + this.ws?.close(err.wsCode) + break + } + this.ws?.close() // No-ops if already closed or closing + if (isReconnectable(err)) { + this.reconnects ??= 0 // Never reconnect with a null + this.opts.onReconnectError?.(err, this.reconnects, this.initialSetup) + continue + } else { + throw err + } + } + break // Other side cleanly ended stream and disconnected + } + } + + startHeartbeat(ws: WebSocket) { + let isAlive = true + let heartbeatInterval: NodeJS.Timer | null = null + + const checkAlive = () => { + if (!isAlive) { + return ws.terminate() + } + isAlive = false // expect websocket to no longer be alive unless we receive a "pong" within the interval + ws.ping() + } + + checkAlive() + heartbeatInterval = setInterval( + checkAlive, + this.opts.heartbeatIntervalMs ?? 10 * SECOND, + ) + + ws.on('pong', () => { + isAlive = true + }) + ws.once('close', () => { + if (heartbeatInterval) { + clearInterval(heartbeatInterval) + heartbeatInterval = null + } + }) + } +} + +export default WebSocketKeepAlive + +class AbnormalCloseError extends Error { + code = 'EWSABNORMALCLOSE' +} + +function isReconnectable(err: unknown): boolean { + // Network errors are reconnectable. + // AuthenticationRequired and InvalidRequest XRPCErrors are not reconnectable. + // @TODO method-specific XRPCErrors may be reconnectable, need to consider. Receiving + // an invalid message is not current reconnectable, but the user can decide to skip them. + if (!err || typeof err['code'] !== 'string') return false + return networkErrorCodes.includes(err['code']) +} + +const networkErrorCodes = [ + 'EWSABNORMALCLOSE', + 'ECONNRESET', + 'ECONNREFUSED', + 'ECONNABORTED', + 'EPIPE', + 'ETIMEDOUT', + 'ECANCELED', +] + +function backoffMs(n: number, maxMs: number) { + const baseSec = Math.pow(2, n) // 1, 2, 4, ... + const randSec = Math.random() - 0.5 // Random jitter between -.5 and .5 seconds + const ms = 1000 * (baseSec + randSec) + return Math.min(ms, maxMs) +} + +function forwardSignal(signal: AbortSignal, ac: AbortController) { + if (signal.aborted) { + return ac.abort(signal.reason) + } else { + signal.addEventListener('abort', () => ac.abort(signal.reason), { + signal: ac.signal, + }) + } +} diff --git a/packages/xrpc-server/src/types.ts b/packages/xrpc-server/src/types.ts index 58c93339f0b..801c8baa6f2 100644 --- a/packages/xrpc-server/src/types.ts +++ b/packages/xrpc-server/src/types.ts @@ -15,6 +15,11 @@ export type Options = { blobLimit?: number textLimit?: number } + rateLimits?: { + creator: RateLimiterCreator + global?: ServerRateLimitDescription[] + shared?: ServerRateLimitDescription[] + } } export type UndecodedParams = typeof express.request['query'] @@ -50,13 +55,17 @@ export type HandlerError = zod.infer export type HandlerOutput = HandlerSuccess | HandlerError -export type XRPCHandler = (ctx: { +export type XRPCReqContext = { auth: HandlerAuth | undefined params: Params input: HandlerInput | undefined req: express.Request res: express.Response -}) => Promise | HandlerOutput | undefined +} + +export type XRPCHandler = ( + ctx: XRPCReqContext, +) => Promise | HandlerOutput | undefined export type XRPCStreamHandler = (ctx: { auth: HandlerAuth | undefined @@ -76,7 +85,66 @@ export type StreamAuthVerifier = (ctx: { req: IncomingMessage }) => Promise | AuthOutput +export type CalcKeyFn = (ctx: XRPCReqContext) => string +export type CalcPointsFn = (ctx: XRPCReqContext) => number + +export interface RateLimiterI { + consume: RateLimiterConsume +} + +export type RateLimiterConsume = ( + ctx: XRPCReqContext, + opts?: { calcKey?: CalcKeyFn; calcPoints?: CalcPointsFn }, +) => Promise + +export type RateLimiterCreator = (opts: { + keyPrefix: string + durationMs: number + points: number + calcKey?: (ctx: XRPCReqContext) => string + calcPoints?: (ctx: XRPCReqContext) => number +}) => RateLimiterI + +export type ServerRateLimitDescription = { + name: string + durationMs: number + points: number + calcKey?: (ctx: XRPCReqContext) => string + calcPoints?: (ctx: XRPCReqContext) => number +} + +export type SharedRateLimitOpts = { + name: string + calcKey?: (ctx: XRPCReqContext) => string + calcPoints?: (ctx: XRPCReqContext) => number +} + +export type RouteRateLimitOpts = { + durationMs: number + points: number + calcKey?: (ctx: XRPCReqContext) => string + calcPoints?: (ctx: XRPCReqContext) => number +} + +export type HandlerRateLimitOpts = SharedRateLimitOpts | RouteRateLimitOpts + +export const isShared = ( + opts: HandlerRateLimitOpts, +): opts is SharedRateLimitOpts => { + return typeof opts['name'] === 'string' +} + +export type RateLimiterStatus = { + limit: number + duration: number + remainingPoints: number + msBeforeNext: number + consumedPoints: number + isFirstInDuration: boolean +} + export type XRPCHandlerConfig = { + rateLimit?: HandlerRateLimitOpts | HandlerRateLimitOpts[] auth?: AuthVerifier handler: XRPCHandler } @@ -154,6 +222,16 @@ export class ForbiddenError extends XRPCError { } } +export class RateLimitExceededError extends XRPCError { + constructor( + status: RateLimiterStatus, + errorMessage?: string, + customErrorName?: string, + ) { + super(ResponseType.RateLimitExceeded, errorMessage, customErrorName) + } +} + export class InternalServerError extends XRPCError { constructor(errorMessage?: string, customErrorName?: string) { super(ResponseType.InternalServerError, errorMessage, customErrorName) @@ -166,9 +244,9 @@ export class UpstreamFailureError extends XRPCError { } } -export class NotEnoughResoucesError extends XRPCError { +export class NotEnoughResourcesError extends XRPCError { constructor(errorMessage?: string, customErrorName?: string) { - super(ResponseType.NotEnoughResouces, errorMessage, customErrorName) + super(ResponseType.NotEnoughResources, errorMessage, customErrorName) } } diff --git a/packages/xrpc-server/tests/rate-limiter.test.ts b/packages/xrpc-server/tests/rate-limiter.test.ts new file mode 100644 index 00000000000..09a235a38b9 --- /dev/null +++ b/packages/xrpc-server/tests/rate-limiter.test.ts @@ -0,0 +1,249 @@ +import * as http from 'http' +import getPort from 'get-port' +import xrpc, { ServiceClient } from '@atproto/xrpc' +import { createServer, closeServer } from './_util' +import * as xrpcServer from '../src' +import { RateLimiter } from '../src' +import { MINUTE } from '@atproto/common' + +const LEXICONS = [ + { + lexicon: 1, + id: 'io.example.routeLimit', + defs: { + main: { + type: 'query', + parameters: { + type: 'params', + required: ['str'], + properties: { + str: { type: 'string' }, + }, + }, + output: { + encoding: 'application/json', + }, + }, + }, + }, + { + lexicon: 1, + id: 'io.example.sharedLimitOne', + defs: { + main: { + type: 'query', + parameters: { + type: 'params', + required: ['points'], + properties: { + points: { type: 'integer' }, + }, + }, + output: { + encoding: 'application/json', + }, + }, + }, + }, + { + lexicon: 1, + id: 'io.example.sharedLimitTwo', + defs: { + main: { + type: 'query', + parameters: { + type: 'params', + required: ['points'], + properties: { + points: { type: 'integer' }, + }, + }, + output: { + encoding: 'application/json', + }, + }, + }, + }, + { + lexicon: 1, + id: 'io.example.toggleLimit', + defs: { + main: { + type: 'query', + parameters: { + type: 'params', + properties: { + shouldCount: { type: 'boolean' }, + }, + }, + output: { + encoding: 'application/json', + }, + }, + }, + }, + { + lexicon: 1, + id: 'io.example.noLimit', + defs: { + main: { + type: 'query', + output: { + encoding: 'application/json', + }, + }, + }, + }, +] + +describe('Parameters', () => { + let s: http.Server + const server = xrpcServer.createServer(LEXICONS, { + rateLimits: { + creator: (opts: xrpcServer.RateLimiterOpts) => + RateLimiter.memory({ + bypassSecret: 'bypass', + ...opts, + }), + shared: [ + { + name: 'shared-limit', + durationMs: 5 * MINUTE, + points: 6, + }, + ], + global: [ + { + name: 'global-ip', + durationMs: 5 * MINUTE, + points: 100, + }, + ], + }, + }) + server.method('io.example.routeLimit', { + rateLimit: { + durationMs: 5 * MINUTE, + points: 5, + calcKey: ({ params }) => params.str as string, + }, + handler: (ctx: { params: xrpcServer.Params }) => ({ + encoding: 'json', + body: ctx.params, + }), + }) + + server.method('io.example.sharedLimitOne', { + rateLimit: { + name: 'shared-limit', + calcPoints: ({ params }) => params.points as number, + }, + handler: (ctx: { params: xrpcServer.Params }) => ({ + encoding: 'json', + body: ctx.params, + }), + }) + server.method('io.example.sharedLimitTwo', { + rateLimit: { + name: 'shared-limit', + calcPoints: ({ params }) => params.points as number, + }, + handler: (ctx: { params: xrpcServer.Params }) => ({ + encoding: 'json', + body: ctx.params, + }), + }) + server.method('io.example.toggleLimit', { + rateLimit: [ + { + durationMs: 5 * MINUTE, + points: 5, + calcPoints: ({ params }) => (params.shouldCount ? 1 : 0), + }, + { + durationMs: 5 * MINUTE, + points: 10, + }, + ], + handler: (ctx: { params: xrpcServer.Params }) => ({ + encoding: 'json', + body: ctx.params, + }), + }) + server.method('io.example.noLimit', { + handler: () => ({ + encoding: 'json', + body: {}, + }), + }) + + xrpc.addLexicons(LEXICONS) + + let client: ServiceClient + beforeAll(async () => { + const port = await getPort() + s = await createServer(port, server) + client = xrpc.service(`http://localhost:${port}`) + }) + afterAll(async () => { + await closeServer(s) + }) + + it('rate limits a given route', async () => { + const makeCall = () => client.call('io.example.routeLimit', { str: 'test' }) + for (let i = 0; i < 5; i++) { + await makeCall() + } + await expect(makeCall).rejects.toThrow('Rate Limit Exceeded') + }) + + it('rate limits on a shared route', async () => { + await client.call('io.example.sharedLimitOne', { points: 1 }) + await client.call('io.example.sharedLimitTwo', { points: 1 }) + await client.call('io.example.sharedLimitOne', { points: 2 }) + await client.call('io.example.sharedLimitTwo', { points: 2 }) + await expect( + client.call('io.example.sharedLimitOne', { points: 1 }), + ).rejects.toThrow('Rate Limit Exceeded') + await expect( + client.call('io.example.sharedLimitTwo', { points: 1 }), + ).rejects.toThrow('Rate Limit Exceeded') + }) + + it('applies multiple rate-limits', async () => { + const makeCall = (shouldCount: boolean) => + client.call('io.example.toggleLimit', { shouldCount }) + for (let i = 0; i < 5; i++) { + await makeCall(true) + } + await expect(() => makeCall(true)).rejects.toThrow('Rate Limit Exceeded') + for (let i = 0; i < 4; i++) { + await makeCall(false) + } + await expect(() => makeCall(false)).rejects.toThrow('Rate Limit Exceeded') + }) + + it('applies global limits', async () => { + const makeCall = () => client.call('io.example.noLimit') + const calls: Promise[] = [] + for (let i = 0; i < 110; i++) { + calls.push(makeCall()) + } + await expect(Promise.all(calls)).rejects.toThrow('Rate Limit Exceeded') + }) + + it('can bypass rate limits', async () => { + const makeCall = () => + client.call( + 'io.example.noLimit', + {}, + {}, + { headers: { 'X-RateLimit-Bypass': 'bypass' } }, + ) + const calls: Promise[] = [] + for (let i = 0; i < 110; i++) { + calls.push(makeCall()) + } + await Promise.all(calls) + }) +}) diff --git a/packages/xrpc-server/tests/responses.test.ts b/packages/xrpc-server/tests/responses.test.ts new file mode 100644 index 00000000000..0eaccba0633 --- /dev/null +++ b/packages/xrpc-server/tests/responses.test.ts @@ -0,0 +1,77 @@ +import * as http from 'http' +import getPort from 'get-port' +import xrpc, { ServiceClient } from '@atproto/xrpc' +import { byteIterableToStream } from '@atproto/common' +import { createServer, closeServer } from './_util' +import * as xrpcServer from '../src' + +const LEXICONS = [ + { + lexicon: 1, + id: 'io.example.readableStream', + defs: { + main: { + type: 'query', + parameters: { + type: 'params', + properties: { + shouldErr: { type: 'boolean' }, + }, + }, + output: { + encoding: 'application/vnd.ipld.car', + }, + }, + }, + }, +] + +describe('Responses', () => { + let s: http.Server + const server = xrpcServer.createServer(LEXICONS) + server.method( + 'io.example.readableStream', + async (ctx: { params: xrpcServer.Params }) => { + async function* iter(): AsyncIterable { + for (let i = 0; i < 5; i++) { + yield new Uint8Array([i]) + } + if (ctx.params.shouldErr) { + throw new Error('error') + } + } + return { + encoding: 'application/vnd.ipld.car', + body: byteIterableToStream(iter()), + } + }, + ) + xrpc.addLexicons(LEXICONS) + + let client: ServiceClient + let url: string + beforeAll(async () => { + const port = await getPort() + s = await createServer(port, server) + url = `http://localhost:${port}` + client = xrpc.service(url) + }) + afterAll(async () => { + await closeServer(s) + }) + + it('returns readable streams of bytes', async () => { + const res = await client.call('io.example.readableStream', { + shouldErr: false, + }) + const expected = new Uint8Array([0, 1, 2, 3, 4]) + expect(res.data).toEqual(expected) + }) + + it('handles errs on readable streams of bytes', async () => { + const attempt = client.call('io.example.readableStream', { + shouldErr: true, + }) + await expect(attempt).rejects.toThrow() + }) +}) diff --git a/packages/xrpc-server/tests/subscriptions.test.ts b/packages/xrpc-server/tests/subscriptions.test.ts index ca0e46c4386..13b0301ca87 100644 --- a/packages/xrpc-server/tests/subscriptions.test.ts +++ b/packages/xrpc-server/tests/subscriptions.test.ts @@ -1,5 +1,5 @@ import * as http from 'http' -import { WebSocket, createWebSocketStream } from 'ws' +import { WebSocket, WebSocketServer, createWebSocketStream } from 'ws' import getPort from 'get-port' import { wait } from '@atproto/common' import { byFrame, MessageFrame, ErrorFrame, Frame, Subscription } from '../src' @@ -95,6 +95,7 @@ describe('Subscriptions', () => { server.streamMethod('io.example.streamTwo', async function* ({ params }) { const countdown = Number(params.countdown ?? 0) for (let i = countdown; i >= 0; i--) { + await wait(200) yield { $type: i % 2 === 0 ? '#even' : 'io.example.streamTwo#odd', count: i, @@ -344,4 +345,59 @@ describe('Subscriptions', () => { ]) }) }) + + it('uses a heartbeat to reconnect if a connection is dropped', async () => { + // we run a server that, on first connection, pauses for longer than the heartbeat interval (doesn't return "pong"s) + // on second connection, it returns a message frame and then closes + const port = await getPort() + const server = new WebSocketServer({ port }) + let firstConnection = true + let firstWasClosed = false + server.on('connection', async (socket) => { + if (firstConnection === true) { + firstConnection = false + socket.pause() + await wait(600) + // shouldn't send this message because the socket would be closed + const frame = new ErrorFrame({ + error: 'AuthenticationRequired', + message: 'Authentication Required', + }) + socket.send(frame.toBytes(), { binary: true }, (err) => { + if (err) throw err + socket.close(xrpcServer.CloseCode.Normal) + }) + socket.on('close', () => { + firstWasClosed = true + }) + } else { + const frame = new MessageFrame({ count: 1 }) + socket.send(frame.toBytes(), { binary: true }, (err) => { + if (err) throw err + socket.close(xrpcServer.CloseCode.Normal) + }) + } + }) + + const subscription = new Subscription({ + service: `ws://localhost:${port}`, + method: '', + heartbeatIntervalMs: 500, + validate: (obj) => { + return lex.assertValidXrpcMessage<{ count: number }>( + 'io.example.streamOne', + obj, + ) + }, + }) + + const messages: { count: number }[] = [] + for await (const msg of subscription) { + messages.push(msg) + } + + expect(messages).toEqual([{ count: 1 }]) + expect(firstWasClosed).toBe(true) + server.close() + }) }) diff --git a/packages/xrpc-server/tsconfig.build.json b/packages/xrpc-server/tsconfig.build.json index 27df65b89e2..02a84823b65 100644 --- a/packages/xrpc-server/tsconfig.build.json +++ b/packages/xrpc-server/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", "exclude": ["**/*.spec.ts", "**/*.test.ts"] -} \ No newline at end of file +} diff --git a/packages/xrpc-server/tsconfig.json b/packages/xrpc-server/tsconfig.json index 8ef03980175..6a9622be37f 100644 --- a/packages/xrpc-server/tsconfig.json +++ b/packages/xrpc-server/tsconfig.json @@ -5,11 +5,11 @@ "outDir": "./dist", // Your outDir, "emitDeclarationOnly": true }, - "include": ["./src","__tests__/**/**.ts"], + "include": ["./src", "__tests__/**/**.ts"], "references": [ { "path": "../common/tsconfig.build.json" }, { "path": "../crypto/tsconfig.build.json" }, { "path": "../lexicon/tsconfig.build.json" }, - { "path": "../xrpc/tsconfig.build.json" }, + { "path": "../xrpc/tsconfig.build.json" } ] -} \ No newline at end of file +} diff --git a/packages/xrpc-server/update-pkg.js b/packages/xrpc-server/update-pkg.js deleted file mode 100644 index 15d70bdab33..00000000000 --- a/packages/xrpc-server/update-pkg.js +++ /dev/null @@ -1,14 +0,0 @@ -const pkgJson = require('@npmcli/package-json') - -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} -if (process.argv.includes('--update-main-to-src')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'src/index.ts' })) - .then((pkg) => pkg.save()) -} diff --git a/packages/xrpc/README.md b/packages/xrpc/README.md index 4b48e052cae..bfb0b8778c3 100644 --- a/packages/xrpc/README.md +++ b/packages/xrpc/README.md @@ -14,24 +14,28 @@ xrpc.addLexicon({ description: 'Ping the server', parameters: { type: 'params', - properties: {message: { type: 'string' }} + properties: { message: { type: 'string' } }, }, output: { encoding: 'application/json', schema: { type: 'object', required: ['message'], - properties: {message: { type: 'string' }}, + properties: { message: { type: 'string' } }, }, - } + }, }, }, }) -const res1 = await xrpc.call('https://example.com', 'io.example.ping', {message: 'hello world'}) +const res1 = await xrpc.call('https://example.com', 'io.example.ping', { + message: 'hello world', +}) res1.encoding // => 'application/json' res1.body // => {message: 'hello world'} -const res2 = await xrpc.service('https://example.com').call('io.example.ping', {message: 'hello world'}) +const res2 = await xrpc + .service('https://example.com') + .call('io.example.ping', { message: 'hello world' }) res2.encoding // => 'application/json' res2.body // => {message: 'hello world'} @@ -44,21 +48,20 @@ xrpc.addLexicon({ description: 'Write a JSON file', parameters: { type: 'params', - properties: {fileName: { type: 'string' }}, + properties: { fileName: { type: 'string' } }, }, input: { - encoding: 'application/json' + encoding: 'application/json', }, }, }, }) -const res3 = await xrpc - .service('https://example.com') - .call('io.example.writeJsonFile', - {fileName: 'foo.json'}, // query parameters - {hello: 'world', thisIs: 'the file to write'} // input body - ) +const res3 = await xrpc.service('https://example.com').call( + 'io.example.writeJsonFile', + { fileName: 'foo.json' }, // query parameters + { hello: 'world', thisIs: 'the file to write' }, // input body +) ``` ## License diff --git a/packages/xrpc/build.js b/packages/xrpc/build.js index 5628aa4f4eb..e880ae9930b 100644 --- a/packages/xrpc/build.js +++ b/packages/xrpc/build.js @@ -1,16 +1,8 @@ -const pkgJson = require('@npmcli/package-json') const { nodeExternalsPlugin } = require('esbuild-node-externals') const buildShallow = process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true' -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} - require('esbuild').build({ logLevel: 'info', entryPoints: ['src/index.ts'], diff --git a/packages/xrpc/package.json b/packages/xrpc/package.json index 58658647f46..4fd825acd06 100644 --- a/packages/xrpc/package.json +++ b/packages/xrpc/package.json @@ -1,20 +1,15 @@ { "name": "@atproto/xrpc", - "version": "0.1.0", + "version": "0.3.0", "main": "src/index.ts", + "publishConfig": { + "main": "dist/index.js", + "types": "dist/index.d.ts" + }, "scripts": { - "prettier": "prettier --check src/", - "prettier:fix": "prettier --write src/", - "lint": "eslint . --ext .ts,.tsx", - "lint:fix": "yarn lint --fix", - "verify": "run-p prettier lint", - "verify:fix": "yarn prettier:fix && yarn lint:fix", "build": "node ./build.js", "postbuild": "tsc --build tsconfig.build.json", - "update-main-to-dist": "node ./update-pkg.js --update-main-to-dist", - "update-main-to-src": "node ./update-pkg.js --update-main-to-src", - "prepublish": "npm run update-main-to-dist", - "postpublish": "npm run update-main-to-src" + "update-main-to-dist": "node ../../update-main-to-dist.js packages/xrpc" }, "license": "MIT", "repository": { @@ -23,7 +18,7 @@ "directory": "packages/xrpc" }, "dependencies": { - "@atproto/lexicon": "*", + "@atproto/lexicon": "workspace:^", "zod": "^3.21.4" } } diff --git a/packages/xrpc/src/client.ts b/packages/xrpc/src/client.ts index c6953d1a415..fbb5d33662c 100644 --- a/packages/xrpc/src/client.ts +++ b/packages/xrpc/src/client.ts @@ -123,7 +123,12 @@ export class ServiceClient { return new XRPCResponse(res.body, res.headers) } else { if (res.body && isErrorResponseBody(res.body)) { - throw new XRPCError(resCode, res.body.error, res.body.message) + throw new XRPCError( + resCode, + res.body.error, + res.body.message, + res.headers, + ) } else { throw new XRPCError(resCode) } diff --git a/packages/xrpc/src/types.ts b/packages/xrpc/src/types.ts index e803e452b6e..9b79f3cd21c 100644 --- a/packages/xrpc/src/types.ts +++ b/packages/xrpc/src/types.ts @@ -41,7 +41,7 @@ export enum ResponseType { InternalServerError = 500, MethodNotImplemented = 501, UpstreamFailure = 502, - NotEnoughResouces = 503, + NotEnoughResources = 503, UpstreamTimeout = 504, } @@ -57,7 +57,7 @@ export const ResponseTypeNames = { [ResponseType.InternalServerError]: 'InternalServerError', [ResponseType.MethodNotImplemented]: 'MethodNotImplemented', [ResponseType.UpstreamFailure]: 'UpstreamFailure', - [ResponseType.NotEnoughResouces]: 'NotEnoughResouces', + [ResponseType.NotEnoughResources]: 'NotEnoughResources', [ResponseType.UpstreamTimeout]: 'UpstreamTimeout', } @@ -73,7 +73,7 @@ export const ResponseTypeStrings = { [ResponseType.InternalServerError]: 'Internal Server Error', [ResponseType.MethodNotImplemented]: 'Method Not Implemented', [ResponseType.UpstreamFailure]: 'Upstream Failure', - [ResponseType.NotEnoughResouces]: 'Not Enough Resouces', + [ResponseType.NotEnoughResources]: 'Not Enough Resources', [ResponseType.UpstreamTimeout]: 'Upstream Timeout', } @@ -85,16 +85,19 @@ export class XRPCResponse { export class XRPCError extends Error { success = false + headers?: Headers constructor( public status: ResponseType, public error?: string, message?: string, + headers?: Headers, ) { super(message || error || ResponseTypeStrings[status]) if (!this.error) { this.error = ResponseTypeNames[status] } + this.headers = headers } } diff --git a/packages/xrpc/tsconfig.build.json b/packages/xrpc/tsconfig.build.json index 27df65b89e2..02a84823b65 100644 --- a/packages/xrpc/tsconfig.build.json +++ b/packages/xrpc/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", "exclude": ["**/*.spec.ts", "**/*.test.ts"] -} \ No newline at end of file +} diff --git a/packages/xrpc/tsconfig.json b/packages/xrpc/tsconfig.json index 0b51fb2835b..72f1ae0e0f3 100644 --- a/packages/xrpc/tsconfig.json +++ b/packages/xrpc/tsconfig.json @@ -5,9 +5,9 @@ "outDir": "./dist", // Your outDir, "emitDeclarationOnly": true }, - "include": ["./src","__tests__/**/**.ts"], + "include": ["./src", "__tests__/**/**.ts"], "references": [ { "path": "../common/tsconfig.build.json" }, { "path": "../lexicon/tsconfig.build.json" } ] -} \ No newline at end of file +} diff --git a/packages/xrpc/update-pkg.js b/packages/xrpc/update-pkg.js deleted file mode 100644 index 15d70bdab33..00000000000 --- a/packages/xrpc/update-pkg.js +++ /dev/null @@ -1,14 +0,0 @@ -const pkgJson = require('@npmcli/package-json') - -if (process.argv.includes('--update-main-to-dist')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'dist/index.js' })) - .then((pkg) => pkg.save()) -} -if (process.argv.includes('--update-main-to-src')) { - return pkgJson - .load(__dirname) - .then((pkg) => pkg.update({ main: 'src/index.ts' })) - .then((pkg) => pkg.save()) -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000000..5456bfda698 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,11309 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@babel/core': + specifier: ^7.18.6 + version: 7.18.6 + '@babel/preset-env': + specifier: ^7.18.6 + version: 7.18.6(@babel/core@7.18.6) + '@changesets/changelog-github': + specifier: ^0.4.8 + version: 0.4.8 + '@changesets/cli': + specifier: ^2.26.2 + version: 2.26.2 + '@npmcli/package-json': + specifier: ^3.0.0 + version: 3.0.0 + '@swc/core': + specifier: ^1.3.42 + version: 1.3.42 + '@swc/jest': + specifier: ^0.2.24 + version: 0.2.24(@swc/core@1.3.42) + '@types/jest': + specifier: ^28.1.4 + version: 28.1.4 + '@types/node': + specifier: ^18.0.0 + version: 18.0.0 + '@typescript-eslint/eslint-plugin': + specifier: ^5.38.1 + version: 5.38.1(@typescript-eslint/parser@5.38.1)(eslint@8.24.0)(typescript@4.8.4) + '@typescript-eslint/parser': + specifier: ^5.38.1 + version: 5.38.1(eslint@8.24.0)(typescript@4.8.4) + babel-eslint: + specifier: ^10.1.0 + version: 10.1.0(eslint@8.24.0) + dotenv: + specifier: ^16.0.3 + version: 16.0.3 + esbuild: + specifier: ^0.14.48 + version: 0.14.48 + esbuild-node-externals: + specifier: ^1.5.0 + version: 1.5.0(esbuild@0.14.48) + esbuild-plugin-copy: + specifier: ^1.6.0 + version: 1.6.0(esbuild@0.14.48) + eslint: + specifier: ^8.24.0 + version: 8.24.0 + eslint-config-prettier: + specifier: ^8.5.0 + version: 8.5.0(eslint@8.24.0) + eslint-plugin-prettier: + specifier: ^4.2.1 + version: 4.2.1(eslint-config-prettier@8.5.0)(eslint@8.24.0)(prettier@2.7.1) + get-port: + specifier: ^6.1.2 + version: 6.1.2 + jest: + specifier: ^28.1.2 + version: 28.1.2(@types/node@18.0.0)(ts-node@10.8.2) + node-gyp: + specifier: ^9.3.1 + version: 9.3.1 + pino-pretty: + specifier: ^9.1.0 + version: 9.1.0 + prettier: + specifier: ^2.7.1 + version: 2.7.1 + prettier-config-standard: + specifier: ^5.0.0 + version: 5.0.0(prettier@2.7.1) + ts-node: + specifier: ^10.8.2 + version: 10.8.2(@swc/core@1.3.42)(@types/node@18.0.0)(typescript@4.8.4) + typescript: + specifier: ^4.8.4 + version: 4.8.4 + + packages/api: + dependencies: + '@atproto/common-web': + specifier: workspace:^ + version: link:../common-web + '@atproto/lexicon': + specifier: workspace:^ + version: link:../lexicon + '@atproto/syntax': + specifier: workspace:^ + version: link:../syntax + '@atproto/xrpc': + specifier: workspace:^ + version: link:../xrpc + multiformats: + specifier: ^9.9.0 + version: 9.9.0 + tlds: + specifier: ^1.234.0 + version: 1.234.0 + typed-emitter: + specifier: ^2.1.0 + version: 2.1.0 + devDependencies: + '@atproto/lex-cli': + specifier: workspace:^ + version: link:../lex-cli + '@atproto/pds': + specifier: workspace:^ + version: link:../pds + common-tags: + specifier: ^1.8.2 + version: 1.8.2 + + packages/aws: + dependencies: + '@atproto/crypto': + specifier: workspace:^ + version: link:../crypto + '@atproto/repo': + specifier: workspace:^ + version: link:../repo + '@aws-sdk/client-cloudfront': + specifier: ^3.261.0 + version: 3.261.0 + '@aws-sdk/client-kms': + specifier: ^3.196.0 + version: 3.196.0 + '@aws-sdk/client-s3': + specifier: ^3.224.0 + version: 3.224.0 + '@aws-sdk/lib-storage': + specifier: ^3.226.0 + version: 3.226.0(@aws-sdk/abort-controller@3.374.0)(@aws-sdk/client-s3@3.224.0) + '@noble/curves': + specifier: ^1.1.0 + version: 1.1.0 + key-encoder: + specifier: ^2.0.3 + version: 2.0.3 + multiformats: + specifier: ^9.9.0 + version: 9.9.0 + uint8arrays: + specifier: 3.0.0 + version: 3.0.0 + + packages/bsky: + dependencies: + '@atproto/api': + specifier: workspace:^ + version: link:../api + '@atproto/common': + specifier: workspace:^ + version: link:../common + '@atproto/crypto': + specifier: workspace:^ + version: link:../crypto + '@atproto/identity': + specifier: workspace:^ + version: link:../identity + '@atproto/lexicon': + specifier: workspace:^ + version: link:../lexicon + '@atproto/repo': + specifier: workspace:^ + version: link:../repo + '@atproto/syntax': + specifier: workspace:^ + version: link:../syntax + '@atproto/xrpc-server': + specifier: workspace:^ + version: link:../xrpc-server + '@did-plc/lib': + specifier: ^0.0.1 + version: 0.0.1 + '@isaacs/ttlcache': + specifier: ^1.4.1 + version: 1.4.1 + compression: + specifier: ^1.7.4 + version: 1.7.4 + cors: + specifier: ^2.8.5 + version: 2.8.5 + dotenv: + specifier: ^16.0.0 + version: 16.0.3 + express: + specifier: ^4.17.2 + version: 4.18.2 + express-async-errors: + specifier: ^3.1.1 + version: 3.1.1(express@4.18.2) + form-data: + specifier: ^4.0.0 + version: 4.0.0 + http-errors: + specifier: ^2.0.0 + version: 2.0.0 + http-terminator: + specifier: ^3.2.0 + version: 3.2.0 + ioredis: + specifier: ^5.3.2 + version: 5.3.2 + iso-datestring-validator: + specifier: ^2.2.2 + version: 2.2.2 + kysely: + specifier: ^0.22.0 + version: 0.22.0 + multiformats: + specifier: ^9.9.0 + version: 9.9.0 + p-queue: + specifier: ^6.6.2 + version: 6.6.2 + pg: + specifier: ^8.10.0 + version: 8.10.0 + pino: + specifier: ^8.15.0 + version: 8.15.0 + pino-http: + specifier: ^8.2.1 + version: 8.2.1 + sharp: + specifier: ^0.31.2 + version: 0.31.2 + typed-emitter: + specifier: ^2.1.0 + version: 2.1.0 + uint8arrays: + specifier: 3.0.0 + version: 3.0.0 + devDependencies: + '@atproto/dev-env': + specifier: workspace:^ + version: link:../dev-env + '@atproto/lex-cli': + specifier: workspace:^ + version: link:../lex-cli + '@atproto/pds': + specifier: workspace:^ + version: link:../pds + '@atproto/xrpc': + specifier: workspace:^ + version: link:../xrpc + '@did-plc/server': + specifier: ^0.0.1 + version: 0.0.1 + '@types/cors': + specifier: ^2.8.12 + version: 2.8.12 + '@types/express': + specifier: ^4.17.13 + version: 4.17.13 + '@types/express-serve-static-core': + specifier: ^4.17.36 + version: 4.17.36 + '@types/pg': + specifier: ^8.6.6 + version: 8.6.6 + '@types/qs': + specifier: ^6.9.7 + version: 6.9.7 + '@types/sharp': + specifier: ^0.31.0 + version: 0.31.0 + axios: + specifier: ^0.27.2 + version: 0.27.2 + + packages/common: + dependencies: + '@atproto/common-web': + specifier: workspace:^ + version: link:../common-web + '@ipld/dag-cbor': + specifier: ^7.0.3 + version: 7.0.3 + cbor-x: + specifier: ^1.5.1 + version: 1.5.1 + multiformats: + specifier: ^9.9.0 + version: 9.9.0 + pino: + specifier: ^8.15.0 + version: 8.15.0 + zod: + specifier: 3.21.4 + version: 3.21.4 + + packages/common-web: + dependencies: + graphemer: + specifier: ^1.4.0 + version: 1.4.0 + multiformats: + specifier: ^9.9.0 + version: 9.9.0 + uint8arrays: + specifier: 3.0.0 + version: 3.0.0 + zod: + specifier: ^3.21.4 + version: 3.21.4 + + packages/crypto: + dependencies: + '@noble/curves': + specifier: ^1.1.0 + version: 1.1.0 + '@noble/hashes': + specifier: ^1.3.1 + version: 1.3.1 + uint8arrays: + specifier: 3.0.0 + version: 3.0.0 + + packages/dev-env: + dependencies: + '@atproto/api': + specifier: workspace:^ + version: link:../api + '@atproto/bsky': + specifier: workspace:^ + version: link:../bsky + '@atproto/common-web': + specifier: workspace:^ + version: link:../common-web + '@atproto/crypto': + specifier: workspace:^ + version: link:../crypto + '@atproto/identity': + specifier: workspace:^ + version: link:../identity + '@atproto/pds': + specifier: workspace:^ + version: link:../pds + '@atproto/syntax': + specifier: workspace:^ + version: link:../syntax + '@atproto/xrpc-server': + specifier: workspace:^ + version: link:../xrpc-server + '@did-plc/lib': + specifier: ^0.0.1 + version: 0.0.1 + '@did-plc/server': + specifier: ^0.0.1 + version: 0.0.1 + better-sqlite3: + specifier: ^7.6.2 + version: 7.6.2 + chalk: + specifier: ^5.0.1 + version: 5.1.1 + dotenv: + specifier: ^16.0.1 + version: 16.0.3 + express: + specifier: ^4.18.2 + version: 4.18.2 + get-port: + specifier: ^6.1.2 + version: 6.1.2 + sharp: + specifier: ^0.31.2 + version: 0.31.2 + uint8arrays: + specifier: 3.0.0 + version: 3.0.0 + devDependencies: + ts-node: + specifier: ^10.8.1 + version: 10.8.2(@swc/core@1.3.42)(@types/node@18.17.8)(typescript@4.9.5) + + packages/identifier: + dependencies: + '@atproto/syntax': + specifier: workspace:^ + version: link:../syntax + + packages/identity: + dependencies: + '@atproto/common-web': + specifier: workspace:^ + version: link:../common-web + '@atproto/crypto': + specifier: workspace:^ + version: link:../crypto + axios: + specifier: ^0.27.2 + version: 0.27.2 + zod: + specifier: ^3.21.4 + version: 3.21.4 + devDependencies: + '@did-plc/lib': + specifier: ^0.0.1 + version: 0.0.1 + '@did-plc/server': + specifier: ^0.0.1 + version: 0.0.1 + cors: + specifier: ^2.8.5 + version: 2.8.5 + express: + specifier: ^4.18.2 + version: 4.18.2 + get-port: + specifier: ^6.1.2 + version: 6.1.2 + + packages/lex-cli: + dependencies: + '@atproto/lexicon': + specifier: workspace:^ + version: link:../lexicon + '@atproto/syntax': + specifier: workspace:^ + version: link:../syntax + chalk: + specifier: ^5.1.1 + version: 5.1.1 + commander: + specifier: ^9.4.0 + version: 9.4.0 + ts-morph: + specifier: ^16.0.0 + version: 16.0.0 + yesno: + specifier: ^0.4.0 + version: 0.4.0 + zod: + specifier: ^3.21.4 + version: 3.21.4 + + packages/lexicon: + dependencies: + '@atproto/common-web': + specifier: workspace:^ + version: link:../common-web + '@atproto/syntax': + specifier: workspace:^ + version: link:../syntax + iso-datestring-validator: + specifier: ^2.2.2 + version: 2.2.2 + multiformats: + specifier: ^9.9.0 + version: 9.9.0 + zod: + specifier: ^3.21.4 + version: 3.21.4 + + packages/nsid: + dependencies: + '@atproto/syntax': + specifier: workspace:^ + version: link:../syntax + + packages/pds: + dependencies: + '@atproto/api': + specifier: workspace:^ + version: link:../api + '@atproto/aws': + specifier: workspace:^ + version: link:../aws + '@atproto/common': + specifier: workspace:^ + version: link:../common + '@atproto/crypto': + specifier: workspace:^ + version: link:../crypto + '@atproto/identity': + specifier: workspace:^ + version: link:../identity + '@atproto/lexicon': + specifier: workspace:^ + version: link:../lexicon + '@atproto/repo': + specifier: workspace:^ + version: link:../repo + '@atproto/syntax': + specifier: workspace:^ + version: link:../syntax + '@atproto/xrpc': + specifier: workspace:^ + version: link:../xrpc + '@atproto/xrpc-server': + specifier: workspace:^ + version: link:../xrpc-server + '@did-plc/lib': + specifier: ^0.0.1 + version: 0.0.1 + better-sqlite3: + specifier: ^7.6.2 + version: 7.6.2 + bytes: + specifier: ^3.1.2 + version: 3.1.2 + compression: + specifier: ^1.7.4 + version: 1.7.4 + cors: + specifier: ^2.8.5 + version: 2.8.5 + dotenv: + specifier: ^16.0.0 + version: 16.0.3 + express: + specifier: ^4.17.2 + version: 4.18.2 + express-async-errors: + specifier: ^3.1.1 + version: 3.1.1(express@4.18.2) + file-type: + specifier: ^16.5.4 + version: 16.5.4 + form-data: + specifier: ^4.0.0 + version: 4.0.0 + handlebars: + specifier: ^4.7.7 + version: 4.7.7 + http-errors: + specifier: ^2.0.0 + version: 2.0.0 + http-terminator: + specifier: ^3.2.0 + version: 3.2.0 + ioredis: + specifier: ^5.3.2 + version: 5.3.2 + iso-datestring-validator: + specifier: ^2.2.2 + version: 2.2.2 + jsonwebtoken: + specifier: ^8.5.1 + version: 8.5.1 + kysely: + specifier: ^0.22.0 + version: 0.22.0 + lru-cache: + specifier: ^10.0.1 + version: 10.0.1 + multiformats: + specifier: ^9.9.0 + version: 9.9.0 + nodemailer: + specifier: ^6.8.0 + version: 6.8.0 + nodemailer-html-to-text: + specifier: ^3.2.0 + version: 3.2.0 + p-queue: + specifier: ^6.6.2 + version: 6.6.2 + pg: + specifier: ^8.10.0 + version: 8.10.0 + pino: + specifier: ^8.15.0 + version: 8.15.0 + pino-http: + specifier: ^8.2.1 + version: 8.2.1 + sharp: + specifier: ^0.31.2 + version: 0.31.2 + typed-emitter: + specifier: ^2.1.0 + version: 2.1.0 + uint8arrays: + specifier: 3.0.0 + version: 3.0.0 + zod: + specifier: ^3.21.4 + version: 3.21.4 + devDependencies: + '@atproto/bsky': + specifier: workspace:^ + version: link:../bsky + '@atproto/dev-env': + specifier: workspace:^ + version: link:../dev-env + '@atproto/lex-cli': + specifier: workspace:^ + version: link:../lex-cli + '@did-plc/server': + specifier: ^0.0.1 + version: 0.0.1 + '@types/cors': + specifier: ^2.8.12 + version: 2.8.12 + '@types/express': + specifier: ^4.17.13 + version: 4.17.13 + '@types/express-serve-static-core': + specifier: ^4.17.36 + version: 4.17.36 + '@types/jsonwebtoken': + specifier: ^8.5.9 + version: 8.5.9 + '@types/nodemailer': + specifier: ^6.4.6 + version: 6.4.6 + '@types/pg': + specifier: ^8.6.6 + version: 8.6.6 + '@types/qs': + specifier: ^6.9.7 + version: 6.9.7 + '@types/sharp': + specifier: ^0.31.0 + version: 0.31.0 + axios: + specifier: ^0.27.2 + version: 0.27.2 + + packages/repo: + dependencies: + '@atproto/common': + specifier: workspace:^ + version: link:../common + '@atproto/common-web': + specifier: workspace:^ + version: link:../common-web + '@atproto/crypto': + specifier: workspace:^ + version: link:../crypto + '@atproto/identity': + specifier: workspace:^ + version: link:../identity + '@atproto/lexicon': + specifier: workspace:^ + version: link:../lexicon + '@atproto/syntax': + specifier: workspace:^ + version: link:../syntax + '@ipld/car': + specifier: ^3.2.3 + version: 3.2.3 + '@ipld/dag-cbor': + specifier: ^7.0.0 + version: 7.0.3 + multiformats: + specifier: ^9.9.0 + version: 9.9.0 + uint8arrays: + specifier: 3.0.0 + version: 3.0.0 + zod: + specifier: ^3.21.4 + version: 3.21.4 + + packages/syntax: + dependencies: + '@atproto/common-web': + specifier: workspace:^ + version: link:../common-web + + packages/uri: + dependencies: + '@atproto/syntax': + specifier: workspace:^ + version: link:../syntax + + packages/xrpc: + dependencies: + '@atproto/lexicon': + specifier: workspace:^ + version: link:../lexicon + zod: + specifier: ^3.21.4 + version: 3.21.4 + + packages/xrpc-server: + dependencies: + '@atproto/common': + specifier: workspace:^ + version: link:../common + '@atproto/crypto': + specifier: workspace:^ + version: link:../crypto + '@atproto/lexicon': + specifier: workspace:^ + version: link:../lexicon + cbor-x: + specifier: ^1.5.1 + version: 1.5.1 + express: + specifier: ^4.17.2 + version: 4.18.2 + http-errors: + specifier: ^2.0.0 + version: 2.0.0 + mime-types: + specifier: ^2.1.35 + version: 2.1.35 + rate-limiter-flexible: + specifier: ^2.4.1 + version: 2.4.1 + uint8arrays: + specifier: 3.0.0 + version: 3.0.0 + ws: + specifier: ^8.12.0 + version: 8.12.0 + zod: + specifier: ^3.21.4 + version: 3.21.4 + devDependencies: + '@atproto/xrpc': + specifier: workspace:^ + version: link:../xrpc + '@types/express': + specifier: ^4.17.13 + version: 4.17.13 + '@types/express-serve-static-core': + specifier: ^4.17.36 + version: 4.17.36 + '@types/http-errors': + specifier: ^2.0.1 + version: 2.0.1 + '@types/ws': + specifier: ^8.5.4 + version: 8.5.4 + get-port: + specifier: ^6.1.2 + version: 6.1.2 + multiformats: + specifier: ^9.9.0 + version: 9.9.0 + + services/bsky: + dependencies: + '@atproto/aws': + specifier: workspace:^ + version: link:../../packages/aws + '@atproto/bsky': + specifier: workspace:^ + version: link:../../packages/bsky + dd-trace: + specifier: 3.13.2 + version: 3.13.2 + + services/pds: + dependencies: + '@atproto/aws': + specifier: workspace:^ + version: link:../../packages/aws + '@atproto/crypto': + specifier: workspace:^ + version: link:../../packages/crypto + '@atproto/pds': + specifier: workspace:^ + version: link:../../packages/pds + dd-trace: + specifier: 3.13.2 + version: 3.13.2 + +packages: + + /@aashutoshrathi/word-wrap@1.2.6: + resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} + engines: {node: '>=0.10.0'} + dev: true + + /@ampproject/remapping@2.2.1: + resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.19 + dev: true + + /@atproto/common@0.1.0: + resolution: {integrity: sha512-OB5tWE2R19jwiMIs2IjQieH5KTUuMb98XGCn9h3xuu6NanwjlmbCYMv08fMYwIp3UQ6jcq//84cDT3Bu6fJD+A==} + dependencies: + '@ipld/dag-cbor': 7.0.3 + multiformats: 9.9.0 + pino: 8.15.0 + zod: 3.21.4 + + /@atproto/crypto@0.1.0: + resolution: {integrity: sha512-9xgFEPtsCiJEPt9o3HtJT30IdFTGw5cQRSJVIy5CFhqBA4vDLcdXiRDLCjkzHEVbtNCsHUW6CrlfOgbeLPcmcg==} + dependencies: + '@noble/secp256k1': 1.7.1 + big-integer: 1.6.51 + multiformats: 9.9.0 + one-webcrypto: 1.0.3 + uint8arrays: 3.0.0 + + /@aws-crypto/crc32@2.0.0: + resolution: {integrity: sha512-TvE1r2CUueyXOuHdEigYjIZVesInd9KN+K/TFFNfkkxRThiNxO6i4ZqqAVMoEjAamZZ1AA8WXJkjCz7YShHPQA==} + dependencies: + '@aws-crypto/util': 2.0.2 + '@aws-sdk/types': 3.224.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/crc32c@2.0.0: + resolution: {integrity: sha512-vF0eMdMHx3O3MoOXUfBZry8Y4ZDtcuskjjKgJz8YfIDjLStxTZrYXk+kZqtl6A0uCmmiN/Eb/JbC/CndTV1MHg==} + dependencies: + '@aws-crypto/util': 2.0.2 + '@aws-sdk/types': 3.224.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/ie11-detection@2.0.2: + resolution: {integrity: sha512-5XDMQY98gMAf/WRTic5G++jfmS/VLM0rwpiOpaainKi4L0nqWMSB1SzsrEG5rjFZGYN6ZAefO+/Yta2dFM0kMw==} + dependencies: + tslib: 1.14.1 + dev: false + + /@aws-crypto/ie11-detection@3.0.0: + resolution: {integrity: sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==} + dependencies: + tslib: 1.14.1 + dev: false + + /@aws-crypto/sha1-browser@2.0.0: + resolution: {integrity: sha512-3fIVRjPFY8EG5HWXR+ZJZMdWNRpwbxGzJ9IH9q93FpbgCH8u8GHRi46mZXp3cYD7gealmyqpm3ThZwLKJjWJhA==} + dependencies: + '@aws-crypto/ie11-detection': 2.0.2 + '@aws-crypto/supports-web-crypto': 2.0.2 + '@aws-sdk/types': 3.224.0 + '@aws-sdk/util-locate-window': 3.310.0 + '@aws-sdk/util-utf8-browser': 3.188.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/sha256-browser@2.0.0: + resolution: {integrity: sha512-rYXOQ8BFOaqMEHJrLHul/25ckWH6GTJtdLSajhlqGMx0PmSueAuvboCuZCTqEKlxR8CQOwRarxYMZZSYlhRA1A==} + dependencies: + '@aws-crypto/ie11-detection': 2.0.2 + '@aws-crypto/sha256-js': 2.0.0 + '@aws-crypto/supports-web-crypto': 2.0.2 + '@aws-crypto/util': 2.0.2 + '@aws-sdk/types': 3.193.0 + '@aws-sdk/util-locate-window': 3.310.0 + '@aws-sdk/util-utf8-browser': 3.188.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/sha256-browser@3.0.0: + resolution: {integrity: sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==} + dependencies: + '@aws-crypto/ie11-detection': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-crypto/supports-web-crypto': 3.0.0 + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.257.0 + '@aws-sdk/util-locate-window': 3.310.0 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/sha256-js@2.0.0: + resolution: {integrity: sha512-VZY+mCY4Nmrs5WGfitmNqXzaE873fcIZDu54cbaDaaamsaTOP1DBImV9F4pICc3EHjQXujyE8jig+PFCaew9ig==} + dependencies: + '@aws-crypto/util': 2.0.2 + '@aws-sdk/types': 3.193.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/sha256-js@3.0.0: + resolution: {integrity: sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==} + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.257.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/supports-web-crypto@2.0.2: + resolution: {integrity: sha512-6mbSsLHwZ99CTOOswvCRP3C+VCWnzBf+1SnbWxzzJ9lR0mA0JnY2JEAhp8rqmTE0GPFy88rrM27ffgp62oErMQ==} + dependencies: + tslib: 1.14.1 + dev: false + + /@aws-crypto/supports-web-crypto@3.0.0: + resolution: {integrity: sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==} + dependencies: + tslib: 1.14.1 + dev: false + + /@aws-crypto/util@2.0.2: + resolution: {integrity: sha512-Lgu5v/0e/BcrZ5m/IWqzPUf3UYFTy/PpeED+uc9SWUR1iZQL8XXbGQg10UfllwwBryO3hFF5dizK+78aoXC1eA==} + dependencies: + '@aws-sdk/types': 3.193.0 + '@aws-sdk/util-utf8-browser': 3.188.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/util@3.0.0: + resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==} + dependencies: + '@aws-sdk/types': 3.257.0 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + dev: false + + /@aws-sdk/abort-controller@3.193.0: + resolution: {integrity: sha512-MYPBm5PWyKP+Tq37mKs5wDbyAyVMocF5iYmx738LYXBSj8A1V4LTFrvfd4U16BRC/sM0DYB9fBFJUQ9ISFRVYw==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/abort-controller@3.224.0: + resolution: {integrity: sha512-6DxaHnSDc2V5WiwtDaRwJJb2fkmDTyGr1svIM9H671aXIwe+q17mtpm5IooKL8bW5mLJoB1pT/5ntLkfxDQgSQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/abort-controller@3.257.0: + resolution: {integrity: sha512-ekWy391lOerS0ZECdhp/c+X7AToJIpfNrCPjuj3bKr+GMQYckGsYsdbm6AUD4sxBmfvuaQmVniSXWovaxwcFcQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/abort-controller@3.374.0: + resolution: {integrity: sha512-pO1pqFBdIF28ZvnJmg58Erj35RLzXsTrjvHghdc/xgtSvodFFCNrUsPg6AP3On8eiw9elpHoS4P8jMx1pHDXEw==} + engines: {node: '>=14.0.0'} + deprecated: This package has moved to @smithy/abort-controller + dependencies: + '@smithy/abort-controller': 1.1.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/chunked-blob-reader-native@3.208.0: + resolution: {integrity: sha512-JeOZ95PW+fJ6bbuqPySYqLqHk1n4+4ueEEraJsiUrPBV0S1ZtyvOGHcnGztKUjr2PYNaiexmpWuvUve9K12HRA==} + dependencies: + '@aws-sdk/util-base64': 3.208.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/chunked-blob-reader@3.188.0: + resolution: {integrity: sha512-zkPRFZZPL3eH+kH86LDYYXImiClA1/sW60zYOjse9Pgka+eDJlvBN6hcYxwDEKjcwATYiSRR1aVQHcfCinlGXg==} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/client-cloudfront@3.261.0: + resolution: {integrity: sha512-7JOpLfgYdQ+CDA3McsAmzcCO+rZj3wVicNTF7Kpl0JaZ0NB0NShifMb4OAGuh2RNh+OYV6k3mtjsXh9ZIQ08PQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sts': 3.261.0 + '@aws-sdk/config-resolver': 3.259.0 + '@aws-sdk/credential-provider-node': 3.261.0 + '@aws-sdk/fetch-http-handler': 3.257.0 + '@aws-sdk/hash-node': 3.257.0 + '@aws-sdk/invalid-dependency': 3.257.0 + '@aws-sdk/middleware-content-length': 3.257.0 + '@aws-sdk/middleware-endpoint': 3.257.0 + '@aws-sdk/middleware-host-header': 3.257.0 + '@aws-sdk/middleware-logger': 3.257.0 + '@aws-sdk/middleware-recursion-detection': 3.257.0 + '@aws-sdk/middleware-retry': 3.259.0 + '@aws-sdk/middleware-serde': 3.257.0 + '@aws-sdk/middleware-signing': 3.257.0 + '@aws-sdk/middleware-stack': 3.257.0 + '@aws-sdk/middleware-user-agent': 3.257.0 + '@aws-sdk/node-config-provider': 3.259.0 + '@aws-sdk/node-http-handler': 3.257.0 + '@aws-sdk/protocol-http': 3.257.0 + '@aws-sdk/smithy-client': 3.261.0 + '@aws-sdk/types': 3.257.0 + '@aws-sdk/url-parser': 3.257.0 + '@aws-sdk/util-base64': 3.208.0 + '@aws-sdk/util-body-length-browser': 3.188.0 + '@aws-sdk/util-body-length-node': 3.208.0 + '@aws-sdk/util-defaults-mode-browser': 3.261.0 + '@aws-sdk/util-defaults-mode-node': 3.261.0 + '@aws-sdk/util-endpoints': 3.257.0 + '@aws-sdk/util-retry': 3.257.0 + '@aws-sdk/util-user-agent-browser': 3.257.0 + '@aws-sdk/util-user-agent-node': 3.259.0 + '@aws-sdk/util-utf8': 3.254.0 + '@aws-sdk/util-waiter': 3.257.0 + '@aws-sdk/xml-builder': 3.201.0 + fast-xml-parser: 4.0.11 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-kms@3.196.0: + resolution: {integrity: sha512-mR5jxfvHnv71FLd87PJ0KNgVXcZzNvKiI3i3JyLmukapnN5Kz2n0cG/jruo9d29zYQS60kfIPjdHddzOxNHH4A==} + engines: {node: '>=12.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 2.0.0 + '@aws-crypto/sha256-js': 2.0.0 + '@aws-sdk/client-sts': 3.196.0 + '@aws-sdk/config-resolver': 3.193.0 + '@aws-sdk/credential-provider-node': 3.196.0 + '@aws-sdk/fetch-http-handler': 3.193.0 + '@aws-sdk/hash-node': 3.193.0 + '@aws-sdk/invalid-dependency': 3.193.0 + '@aws-sdk/middleware-content-length': 3.193.0 + '@aws-sdk/middleware-endpoint': 3.193.0 + '@aws-sdk/middleware-host-header': 3.193.0 + '@aws-sdk/middleware-logger': 3.193.0 + '@aws-sdk/middleware-recursion-detection': 3.193.0 + '@aws-sdk/middleware-retry': 3.193.0 + '@aws-sdk/middleware-serde': 3.193.0 + '@aws-sdk/middleware-signing': 3.193.0 + '@aws-sdk/middleware-stack': 3.193.0 + '@aws-sdk/middleware-user-agent': 3.193.0 + '@aws-sdk/node-config-provider': 3.193.0 + '@aws-sdk/node-http-handler': 3.193.0 + '@aws-sdk/protocol-http': 3.193.0 + '@aws-sdk/smithy-client': 3.193.0 + '@aws-sdk/types': 3.193.0 + '@aws-sdk/url-parser': 3.193.0 + '@aws-sdk/util-base64-browser': 3.188.0 + '@aws-sdk/util-base64-node': 3.188.0 + '@aws-sdk/util-body-length-browser': 3.188.0 + '@aws-sdk/util-body-length-node': 3.188.0 + '@aws-sdk/util-defaults-mode-browser': 3.193.0 + '@aws-sdk/util-defaults-mode-node': 3.193.0 + '@aws-sdk/util-endpoints': 3.196.0 + '@aws-sdk/util-user-agent-browser': 3.193.0 + '@aws-sdk/util-user-agent-node': 3.193.0 + '@aws-sdk/util-utf8-browser': 3.188.0 + '@aws-sdk/util-utf8-node': 3.188.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-s3@3.224.0: + resolution: {integrity: sha512-CPU1sG4xr+fJ+OFpqz9Oum7cJwas0mA9YFvPLkgKLvNC2rhmmn0kbjwawtc6GUDu6xygeV8koBL2gz7OJHQ7fQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha1-browser': 2.0.0 + '@aws-crypto/sha256-browser': 2.0.0 + '@aws-crypto/sha256-js': 2.0.0 + '@aws-sdk/client-sts': 3.224.0 + '@aws-sdk/config-resolver': 3.224.0 + '@aws-sdk/credential-provider-node': 3.224.0 + '@aws-sdk/eventstream-serde-browser': 3.224.0 + '@aws-sdk/eventstream-serde-config-resolver': 3.224.0 + '@aws-sdk/eventstream-serde-node': 3.224.0 + '@aws-sdk/fetch-http-handler': 3.224.0 + '@aws-sdk/hash-blob-browser': 3.224.0 + '@aws-sdk/hash-node': 3.224.0 + '@aws-sdk/hash-stream-node': 3.224.0 + '@aws-sdk/invalid-dependency': 3.224.0 + '@aws-sdk/md5-js': 3.224.0 + '@aws-sdk/middleware-bucket-endpoint': 3.224.0 + '@aws-sdk/middleware-content-length': 3.224.0 + '@aws-sdk/middleware-endpoint': 3.224.0 + '@aws-sdk/middleware-expect-continue': 3.224.0 + '@aws-sdk/middleware-flexible-checksums': 3.224.0 + '@aws-sdk/middleware-host-header': 3.224.0 + '@aws-sdk/middleware-location-constraint': 3.224.0 + '@aws-sdk/middleware-logger': 3.224.0 + '@aws-sdk/middleware-recursion-detection': 3.224.0 + '@aws-sdk/middleware-retry': 3.224.0 + '@aws-sdk/middleware-sdk-s3': 3.224.0 + '@aws-sdk/middleware-serde': 3.224.0 + '@aws-sdk/middleware-signing': 3.224.0 + '@aws-sdk/middleware-ssec': 3.224.0 + '@aws-sdk/middleware-stack': 3.224.0 + '@aws-sdk/middleware-user-agent': 3.224.0 + '@aws-sdk/node-config-provider': 3.224.0 + '@aws-sdk/node-http-handler': 3.224.0 + '@aws-sdk/protocol-http': 3.224.0 + '@aws-sdk/signature-v4-multi-region': 3.224.0 + '@aws-sdk/smithy-client': 3.224.0 + '@aws-sdk/types': 3.224.0 + '@aws-sdk/url-parser': 3.224.0 + '@aws-sdk/util-base64': 3.208.0 + '@aws-sdk/util-body-length-browser': 3.188.0 + '@aws-sdk/util-body-length-node': 3.208.0 + '@aws-sdk/util-defaults-mode-browser': 3.224.0 + '@aws-sdk/util-defaults-mode-node': 3.224.0 + '@aws-sdk/util-endpoints': 3.224.0 + '@aws-sdk/util-stream-browser': 3.224.0 + '@aws-sdk/util-stream-node': 3.224.0 + '@aws-sdk/util-user-agent-browser': 3.224.0 + '@aws-sdk/util-user-agent-node': 3.224.0 + '@aws-sdk/util-utf8-browser': 3.188.0 + '@aws-sdk/util-utf8-node': 3.208.0 + '@aws-sdk/util-waiter': 3.224.0 + '@aws-sdk/xml-builder': 3.201.0 + fast-xml-parser: 4.0.11 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/signature-v4-crt' + - aws-crt + dev: false + + /@aws-sdk/client-sso-oidc@3.224.0: + resolution: {integrity: sha512-r7QAqinMvuZvGlfC4ltEBIq3gJ1AI4tTqEi8lG06+gDoiwnqTWii0+OrZJQiaeLc3PqDHwxmRpEmjFlr/f5TKg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 2.0.0 + '@aws-crypto/sha256-js': 2.0.0 + '@aws-sdk/config-resolver': 3.224.0 + '@aws-sdk/fetch-http-handler': 3.224.0 + '@aws-sdk/hash-node': 3.224.0 + '@aws-sdk/invalid-dependency': 3.224.0 + '@aws-sdk/middleware-content-length': 3.224.0 + '@aws-sdk/middleware-endpoint': 3.224.0 + '@aws-sdk/middleware-host-header': 3.224.0 + '@aws-sdk/middleware-logger': 3.224.0 + '@aws-sdk/middleware-recursion-detection': 3.224.0 + '@aws-sdk/middleware-retry': 3.224.0 + '@aws-sdk/middleware-serde': 3.224.0 + '@aws-sdk/middleware-stack': 3.224.0 + '@aws-sdk/middleware-user-agent': 3.224.0 + '@aws-sdk/node-config-provider': 3.224.0 + '@aws-sdk/node-http-handler': 3.224.0 + '@aws-sdk/protocol-http': 3.224.0 + '@aws-sdk/smithy-client': 3.224.0 + '@aws-sdk/types': 3.224.0 + '@aws-sdk/url-parser': 3.224.0 + '@aws-sdk/util-base64': 3.208.0 + '@aws-sdk/util-body-length-browser': 3.188.0 + '@aws-sdk/util-body-length-node': 3.208.0 + '@aws-sdk/util-defaults-mode-browser': 3.224.0 + '@aws-sdk/util-defaults-mode-node': 3.224.0 + '@aws-sdk/util-endpoints': 3.224.0 + '@aws-sdk/util-user-agent-browser': 3.224.0 + '@aws-sdk/util-user-agent-node': 3.224.0 + '@aws-sdk/util-utf8-browser': 3.188.0 + '@aws-sdk/util-utf8-node': 3.208.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sso-oidc@3.261.0: + resolution: {integrity: sha512-ItgRT/BThv2UxEeGJ5/GCF6JY1Rzk39IcDIPZAfBA8HbYcznXGDsBTRf45MErS+uollwNFX0T/WNlTbmjEDE7g==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/config-resolver': 3.259.0 + '@aws-sdk/fetch-http-handler': 3.257.0 + '@aws-sdk/hash-node': 3.257.0 + '@aws-sdk/invalid-dependency': 3.257.0 + '@aws-sdk/middleware-content-length': 3.257.0 + '@aws-sdk/middleware-endpoint': 3.257.0 + '@aws-sdk/middleware-host-header': 3.257.0 + '@aws-sdk/middleware-logger': 3.257.0 + '@aws-sdk/middleware-recursion-detection': 3.257.0 + '@aws-sdk/middleware-retry': 3.259.0 + '@aws-sdk/middleware-serde': 3.257.0 + '@aws-sdk/middleware-stack': 3.257.0 + '@aws-sdk/middleware-user-agent': 3.257.0 + '@aws-sdk/node-config-provider': 3.259.0 + '@aws-sdk/node-http-handler': 3.257.0 + '@aws-sdk/protocol-http': 3.257.0 + '@aws-sdk/smithy-client': 3.261.0 + '@aws-sdk/types': 3.257.0 + '@aws-sdk/url-parser': 3.257.0 + '@aws-sdk/util-base64': 3.208.0 + '@aws-sdk/util-body-length-browser': 3.188.0 + '@aws-sdk/util-body-length-node': 3.208.0 + '@aws-sdk/util-defaults-mode-browser': 3.261.0 + '@aws-sdk/util-defaults-mode-node': 3.261.0 + '@aws-sdk/util-endpoints': 3.257.0 + '@aws-sdk/util-retry': 3.257.0 + '@aws-sdk/util-user-agent-browser': 3.257.0 + '@aws-sdk/util-user-agent-node': 3.259.0 + '@aws-sdk/util-utf8': 3.254.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sso@3.196.0: + resolution: {integrity: sha512-u+UnxrVHLjLDdfCZft1AuyIhyv+77/inCHR4LcKsGASRA+jAg3z+OY+B7Q9hWHNcVt5ECMw7rxe4jA9BLf42sw==} + engines: {node: '>=12.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 2.0.0 + '@aws-crypto/sha256-js': 2.0.0 + '@aws-sdk/config-resolver': 3.193.0 + '@aws-sdk/fetch-http-handler': 3.193.0 + '@aws-sdk/hash-node': 3.193.0 + '@aws-sdk/invalid-dependency': 3.193.0 + '@aws-sdk/middleware-content-length': 3.193.0 + '@aws-sdk/middleware-endpoint': 3.193.0 + '@aws-sdk/middleware-host-header': 3.193.0 + '@aws-sdk/middleware-logger': 3.193.0 + '@aws-sdk/middleware-recursion-detection': 3.193.0 + '@aws-sdk/middleware-retry': 3.193.0 + '@aws-sdk/middleware-serde': 3.193.0 + '@aws-sdk/middleware-stack': 3.193.0 + '@aws-sdk/middleware-user-agent': 3.193.0 + '@aws-sdk/node-config-provider': 3.193.0 + '@aws-sdk/node-http-handler': 3.193.0 + '@aws-sdk/protocol-http': 3.193.0 + '@aws-sdk/smithy-client': 3.193.0 + '@aws-sdk/types': 3.193.0 + '@aws-sdk/url-parser': 3.193.0 + '@aws-sdk/util-base64-browser': 3.188.0 + '@aws-sdk/util-base64-node': 3.188.0 + '@aws-sdk/util-body-length-browser': 3.188.0 + '@aws-sdk/util-body-length-node': 3.188.0 + '@aws-sdk/util-defaults-mode-browser': 3.193.0 + '@aws-sdk/util-defaults-mode-node': 3.193.0 + '@aws-sdk/util-endpoints': 3.196.0 + '@aws-sdk/util-user-agent-browser': 3.193.0 + '@aws-sdk/util-user-agent-node': 3.193.0 + '@aws-sdk/util-utf8-browser': 3.188.0 + '@aws-sdk/util-utf8-node': 3.188.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sso@3.224.0: + resolution: {integrity: sha512-ZfqjGGBhv+sKxYN9FHbepaL+ucFbAFndvNdalGj4mZsv5AqxgemkFoRofNJk4nu79JVf5cdrj7zL+BDW3KwEGg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 2.0.0 + '@aws-crypto/sha256-js': 2.0.0 + '@aws-sdk/config-resolver': 3.224.0 + '@aws-sdk/fetch-http-handler': 3.224.0 + '@aws-sdk/hash-node': 3.224.0 + '@aws-sdk/invalid-dependency': 3.224.0 + '@aws-sdk/middleware-content-length': 3.224.0 + '@aws-sdk/middleware-endpoint': 3.224.0 + '@aws-sdk/middleware-host-header': 3.224.0 + '@aws-sdk/middleware-logger': 3.224.0 + '@aws-sdk/middleware-recursion-detection': 3.224.0 + '@aws-sdk/middleware-retry': 3.224.0 + '@aws-sdk/middleware-serde': 3.224.0 + '@aws-sdk/middleware-stack': 3.224.0 + '@aws-sdk/middleware-user-agent': 3.224.0 + '@aws-sdk/node-config-provider': 3.224.0 + '@aws-sdk/node-http-handler': 3.224.0 + '@aws-sdk/protocol-http': 3.224.0 + '@aws-sdk/smithy-client': 3.224.0 + '@aws-sdk/types': 3.224.0 + '@aws-sdk/url-parser': 3.224.0 + '@aws-sdk/util-base64': 3.208.0 + '@aws-sdk/util-body-length-browser': 3.188.0 + '@aws-sdk/util-body-length-node': 3.208.0 + '@aws-sdk/util-defaults-mode-browser': 3.224.0 + '@aws-sdk/util-defaults-mode-node': 3.224.0 + '@aws-sdk/util-endpoints': 3.224.0 + '@aws-sdk/util-user-agent-browser': 3.224.0 + '@aws-sdk/util-user-agent-node': 3.224.0 + '@aws-sdk/util-utf8-browser': 3.188.0 + '@aws-sdk/util-utf8-node': 3.208.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sso@3.261.0: + resolution: {integrity: sha512-tq5hu1WXa9BKsCH9zOBOykyiaoZQvaFHKdOamw5SZ69niyO3AG4xR1TkLqXj/9mDYMLgAIVObKZDGWtBLFTdiQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/config-resolver': 3.259.0 + '@aws-sdk/fetch-http-handler': 3.257.0 + '@aws-sdk/hash-node': 3.257.0 + '@aws-sdk/invalid-dependency': 3.257.0 + '@aws-sdk/middleware-content-length': 3.257.0 + '@aws-sdk/middleware-endpoint': 3.257.0 + '@aws-sdk/middleware-host-header': 3.257.0 + '@aws-sdk/middleware-logger': 3.257.0 + '@aws-sdk/middleware-recursion-detection': 3.257.0 + '@aws-sdk/middleware-retry': 3.259.0 + '@aws-sdk/middleware-serde': 3.257.0 + '@aws-sdk/middleware-stack': 3.257.0 + '@aws-sdk/middleware-user-agent': 3.257.0 + '@aws-sdk/node-config-provider': 3.259.0 + '@aws-sdk/node-http-handler': 3.257.0 + '@aws-sdk/protocol-http': 3.257.0 + '@aws-sdk/smithy-client': 3.261.0 + '@aws-sdk/types': 3.257.0 + '@aws-sdk/url-parser': 3.257.0 + '@aws-sdk/util-base64': 3.208.0 + '@aws-sdk/util-body-length-browser': 3.188.0 + '@aws-sdk/util-body-length-node': 3.208.0 + '@aws-sdk/util-defaults-mode-browser': 3.261.0 + '@aws-sdk/util-defaults-mode-node': 3.261.0 + '@aws-sdk/util-endpoints': 3.257.0 + '@aws-sdk/util-retry': 3.257.0 + '@aws-sdk/util-user-agent-browser': 3.257.0 + '@aws-sdk/util-user-agent-node': 3.259.0 + '@aws-sdk/util-utf8': 3.254.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sts@3.196.0: + resolution: {integrity: sha512-ChzK8606CugwnRLm7iwerXzeMqOsjGLe3j1j1HtQShzXZu4/ysQ3mUBBPAt2Lltx+1ep8MoI9vaQVyfw5h35ww==} + engines: {node: '>=12.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 2.0.0 + '@aws-crypto/sha256-js': 2.0.0 + '@aws-sdk/config-resolver': 3.193.0 + '@aws-sdk/credential-provider-node': 3.196.0 + '@aws-sdk/fetch-http-handler': 3.193.0 + '@aws-sdk/hash-node': 3.193.0 + '@aws-sdk/invalid-dependency': 3.193.0 + '@aws-sdk/middleware-content-length': 3.193.0 + '@aws-sdk/middleware-endpoint': 3.193.0 + '@aws-sdk/middleware-host-header': 3.193.0 + '@aws-sdk/middleware-logger': 3.193.0 + '@aws-sdk/middleware-recursion-detection': 3.193.0 + '@aws-sdk/middleware-retry': 3.193.0 + '@aws-sdk/middleware-sdk-sts': 3.193.0 + '@aws-sdk/middleware-serde': 3.193.0 + '@aws-sdk/middleware-signing': 3.193.0 + '@aws-sdk/middleware-stack': 3.193.0 + '@aws-sdk/middleware-user-agent': 3.193.0 + '@aws-sdk/node-config-provider': 3.193.0 + '@aws-sdk/node-http-handler': 3.193.0 + '@aws-sdk/protocol-http': 3.193.0 + '@aws-sdk/smithy-client': 3.193.0 + '@aws-sdk/types': 3.193.0 + '@aws-sdk/url-parser': 3.193.0 + '@aws-sdk/util-base64-browser': 3.188.0 + '@aws-sdk/util-base64-node': 3.188.0 + '@aws-sdk/util-body-length-browser': 3.188.0 + '@aws-sdk/util-body-length-node': 3.188.0 + '@aws-sdk/util-defaults-mode-browser': 3.193.0 + '@aws-sdk/util-defaults-mode-node': 3.193.0 + '@aws-sdk/util-endpoints': 3.196.0 + '@aws-sdk/util-user-agent-browser': 3.193.0 + '@aws-sdk/util-user-agent-node': 3.193.0 + '@aws-sdk/util-utf8-browser': 3.188.0 + '@aws-sdk/util-utf8-node': 3.188.0 + fast-xml-parser: 4.0.11 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sts@3.224.0: + resolution: {integrity: sha512-ao3jyjwk2fozk1d4PtrNf0BNsucPWAbALv8CCsPTC3r9g2Lg/TOi3pxmsfd69ddw89XSyP6zZATEHaWO+tk0CQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 2.0.0 + '@aws-crypto/sha256-js': 2.0.0 + '@aws-sdk/config-resolver': 3.224.0 + '@aws-sdk/credential-provider-node': 3.224.0 + '@aws-sdk/fetch-http-handler': 3.224.0 + '@aws-sdk/hash-node': 3.224.0 + '@aws-sdk/invalid-dependency': 3.224.0 + '@aws-sdk/middleware-content-length': 3.224.0 + '@aws-sdk/middleware-endpoint': 3.224.0 + '@aws-sdk/middleware-host-header': 3.224.0 + '@aws-sdk/middleware-logger': 3.224.0 + '@aws-sdk/middleware-recursion-detection': 3.224.0 + '@aws-sdk/middleware-retry': 3.224.0 + '@aws-sdk/middleware-sdk-sts': 3.224.0 + '@aws-sdk/middleware-serde': 3.224.0 + '@aws-sdk/middleware-signing': 3.224.0 + '@aws-sdk/middleware-stack': 3.224.0 + '@aws-sdk/middleware-user-agent': 3.224.0 + '@aws-sdk/node-config-provider': 3.224.0 + '@aws-sdk/node-http-handler': 3.224.0 + '@aws-sdk/protocol-http': 3.224.0 + '@aws-sdk/smithy-client': 3.224.0 + '@aws-sdk/types': 3.224.0 + '@aws-sdk/url-parser': 3.224.0 + '@aws-sdk/util-base64': 3.208.0 + '@aws-sdk/util-body-length-browser': 3.188.0 + '@aws-sdk/util-body-length-node': 3.208.0 + '@aws-sdk/util-defaults-mode-browser': 3.224.0 + '@aws-sdk/util-defaults-mode-node': 3.224.0 + '@aws-sdk/util-endpoints': 3.224.0 + '@aws-sdk/util-user-agent-browser': 3.224.0 + '@aws-sdk/util-user-agent-node': 3.224.0 + '@aws-sdk/util-utf8-browser': 3.188.0 + '@aws-sdk/util-utf8-node': 3.208.0 + fast-xml-parser: 4.0.11 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sts@3.261.0: + resolution: {integrity: sha512-jnCKBjuHEMgwCmR9bXDVpl/WzpUQyU9DL3Mk65XYyZwRxgHSaw5D90zRouoZMUneNA2OnKZQnjk6oyL47mb7oA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/config-resolver': 3.259.0 + '@aws-sdk/credential-provider-node': 3.261.0 + '@aws-sdk/fetch-http-handler': 3.257.0 + '@aws-sdk/hash-node': 3.257.0 + '@aws-sdk/invalid-dependency': 3.257.0 + '@aws-sdk/middleware-content-length': 3.257.0 + '@aws-sdk/middleware-endpoint': 3.257.0 + '@aws-sdk/middleware-host-header': 3.257.0 + '@aws-sdk/middleware-logger': 3.257.0 + '@aws-sdk/middleware-recursion-detection': 3.257.0 + '@aws-sdk/middleware-retry': 3.259.0 + '@aws-sdk/middleware-sdk-sts': 3.257.0 + '@aws-sdk/middleware-serde': 3.257.0 + '@aws-sdk/middleware-signing': 3.257.0 + '@aws-sdk/middleware-stack': 3.257.0 + '@aws-sdk/middleware-user-agent': 3.257.0 + '@aws-sdk/node-config-provider': 3.259.0 + '@aws-sdk/node-http-handler': 3.257.0 + '@aws-sdk/protocol-http': 3.257.0 + '@aws-sdk/smithy-client': 3.261.0 + '@aws-sdk/types': 3.257.0 + '@aws-sdk/url-parser': 3.257.0 + '@aws-sdk/util-base64': 3.208.0 + '@aws-sdk/util-body-length-browser': 3.188.0 + '@aws-sdk/util-body-length-node': 3.208.0 + '@aws-sdk/util-defaults-mode-browser': 3.261.0 + '@aws-sdk/util-defaults-mode-node': 3.261.0 + '@aws-sdk/util-endpoints': 3.257.0 + '@aws-sdk/util-retry': 3.257.0 + '@aws-sdk/util-user-agent-browser': 3.257.0 + '@aws-sdk/util-user-agent-node': 3.259.0 + '@aws-sdk/util-utf8': 3.254.0 + fast-xml-parser: 4.0.11 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/config-resolver@3.193.0: + resolution: {integrity: sha512-HIjuv2A1glgkXy9g/A8bfsiz3jTFaRbwGZheoHFZod6iEQQEbbeAsBe3u2AZyzOrVLgs8lOvBtgU8XKSJWjDkw==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/signature-v4': 3.193.0 + '@aws-sdk/types': 3.193.0 + '@aws-sdk/util-config-provider': 3.188.0 + '@aws-sdk/util-middleware': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/config-resolver@3.224.0: + resolution: {integrity: sha512-jS53QvF2jdv7d6cpPUH6N85i1WNHik1eGvxqSndsNbLf0keEGXYyN4pBLNB0xK1nk0ZG+8slRsXgWvWTCcFYKA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/signature-v4': 3.224.0 + '@aws-sdk/types': 3.224.0 + '@aws-sdk/util-config-provider': 3.208.0 + '@aws-sdk/util-middleware': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/config-resolver@3.259.0: + resolution: {integrity: sha512-gViMRsc4Ye6+nzJ0OYTZIT8m4glIAdtugN2Sr/t6P2iJW5X0bSL/EcbcHBgsve1lHjeGPeyzVkT7UnyGOZ5Z/A==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/signature-v4': 3.257.0 + '@aws-sdk/types': 3.257.0 + '@aws-sdk/util-config-provider': 3.208.0 + '@aws-sdk/util-middleware': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/credential-provider-env@3.193.0: + resolution: {integrity: sha512-pRqZoIaqCdWB4JJdR6DqDn3u+CwKJchwiCPnRtChwC8KXCMkT4njq9J1bWG3imYeTxP/G06O1PDONEuD4pPtNQ==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/property-provider': 3.193.0 + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/credential-provider-env@3.224.0: + resolution: {integrity: sha512-WUicVivCne9Ela2Nuufohy8+UV/W6GwanlpK9trJqrqHt2/zqdNYHqZbWL0zDNO8dvFN3+MC2a8boYPyR+cFRg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/property-provider': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/credential-provider-env@3.257.0: + resolution: {integrity: sha512-GsmBi5Di6hk1JAi1iB6/LCY8o+GmlCvJoB7wuoVmXI3VxRVwptUVjuj8EtJbIrVGrF9dSuIRPCzUoSuzEzYGlg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/property-provider': 3.257.0 + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/credential-provider-imds@3.193.0: + resolution: {integrity: sha512-jC7uT7uVpO/iitz49toHMGFKXQ2igWQQG2SKirREqDRaz5HSXwEP1V3rcOlNNyGIBPMggDjZnxYgJHqBXSq9Ag==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/node-config-provider': 3.193.0 + '@aws-sdk/property-provider': 3.193.0 + '@aws-sdk/types': 3.193.0 + '@aws-sdk/url-parser': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/credential-provider-imds@3.224.0: + resolution: {integrity: sha512-n7uVR5Z9EUfVbg0gSNrJvu1g0cM/HqhRt+kaRJBGNf4q1tEbnCukKj+qUZbT1qdbDTyu9NTRphMvuIyN3RBDtQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/node-config-provider': 3.224.0 + '@aws-sdk/property-provider': 3.224.0 + '@aws-sdk/types': 3.224.0 + '@aws-sdk/url-parser': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/credential-provider-imds@3.259.0: + resolution: {integrity: sha512-yCxoYWZAaDrCUEWxRfrpB0Mp1cFgJEMYW8T6GIb/+DQ5QLpZmorgaVD/j90QXupqFrR5tlxwuskBIkdD2E9YNg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/node-config-provider': 3.259.0 + '@aws-sdk/property-provider': 3.257.0 + '@aws-sdk/types': 3.257.0 + '@aws-sdk/url-parser': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/credential-provider-ini@3.196.0: + resolution: {integrity: sha512-3lL+YLBQ9KwQxG4AdRm4u2cvBNZeBmS/i3BWnCPomg96lNGPMrTEloVaVEpnrzOff6sgFxRtjkbLkVxmdipIrw==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.193.0 + '@aws-sdk/credential-provider-imds': 3.193.0 + '@aws-sdk/credential-provider-sso': 3.196.0 + '@aws-sdk/credential-provider-web-identity': 3.193.0 + '@aws-sdk/property-provider': 3.193.0 + '@aws-sdk/shared-ini-file-loader': 3.193.0 + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-ini@3.224.0: + resolution: {integrity: sha512-YaAHoHJVspqy5f8C6EXBifMfodKXl88IHuL6eBComigTPR3s1Ed1+3AJdjA1X7SjAHfrYna/WvZEH3e8NCSzFA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.224.0 + '@aws-sdk/credential-provider-imds': 3.224.0 + '@aws-sdk/credential-provider-sso': 3.224.0 + '@aws-sdk/credential-provider-web-identity': 3.224.0 + '@aws-sdk/property-provider': 3.224.0 + '@aws-sdk/shared-ini-file-loader': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-ini@3.261.0: + resolution: {integrity: sha512-638jTnvFbGO0G0So+FijdC1vjn/dhw3l8nJwLq9PYOBJUKhjXDR/fpOhZkUJ+Zwfuqp9SlDDo/yfFa6j2L+F1g==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.257.0 + '@aws-sdk/credential-provider-imds': 3.259.0 + '@aws-sdk/credential-provider-process': 3.257.0 + '@aws-sdk/credential-provider-sso': 3.261.0 + '@aws-sdk/credential-provider-web-identity': 3.257.0 + '@aws-sdk/property-provider': 3.257.0 + '@aws-sdk/shared-ini-file-loader': 3.257.0 + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-node@3.196.0: + resolution: {integrity: sha512-PGY7pkmqgfEwTHsuUH6fGrXWri93jqKkMbhq/QJafMGtsVupfvXvE37Rl+qgjsZjRfROrEaeLw2DGrPPmVh2cg==} + engines: {node: '>=12.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.193.0 + '@aws-sdk/credential-provider-imds': 3.193.0 + '@aws-sdk/credential-provider-ini': 3.196.0 + '@aws-sdk/credential-provider-process': 3.193.0 + '@aws-sdk/credential-provider-sso': 3.196.0 + '@aws-sdk/credential-provider-web-identity': 3.193.0 + '@aws-sdk/property-provider': 3.193.0 + '@aws-sdk/shared-ini-file-loader': 3.193.0 + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-node@3.224.0: + resolution: {integrity: sha512-n/gijJAA3uVFl1b3+hp2E3lPaiajsPLHqH+mMxNxPkGo39HV1v9RAyOVW4Y3AH1QcT7sURevjGoF2Eemcro88g==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.224.0 + '@aws-sdk/credential-provider-imds': 3.224.0 + '@aws-sdk/credential-provider-ini': 3.224.0 + '@aws-sdk/credential-provider-process': 3.224.0 + '@aws-sdk/credential-provider-sso': 3.224.0 + '@aws-sdk/credential-provider-web-identity': 3.224.0 + '@aws-sdk/property-provider': 3.224.0 + '@aws-sdk/shared-ini-file-loader': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-node@3.261.0: + resolution: {integrity: sha512-7T25a7jbHsXPe7XvIekzhR50b7PTlISKqHdE8LNVUSzFQbSjVXulFk3vyQVIhmt5HKNkSBcMPDr6hKrSl7OLBw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.257.0 + '@aws-sdk/credential-provider-imds': 3.259.0 + '@aws-sdk/credential-provider-ini': 3.261.0 + '@aws-sdk/credential-provider-process': 3.257.0 + '@aws-sdk/credential-provider-sso': 3.261.0 + '@aws-sdk/credential-provider-web-identity': 3.257.0 + '@aws-sdk/property-provider': 3.257.0 + '@aws-sdk/shared-ini-file-loader': 3.257.0 + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-process@3.193.0: + resolution: {integrity: sha512-zpXxtQzQqkaUuFqmHW9dSkh9p/1k+XNKlwEkG8FTwAJNUWmy2ZMJv+8NTVn4s4vaRu7xJ1er9chspYr7mvxHlA==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/property-provider': 3.193.0 + '@aws-sdk/shared-ini-file-loader': 3.193.0 + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/credential-provider-process@3.224.0: + resolution: {integrity: sha512-0nc8vGmv6vDfFlVyKREwAa4namfuGqKg3TTM0nW2vE10fpDXZM/DGVAs5HInX+27QQNLVVh3/OHHgti9wMkYkw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/property-provider': 3.224.0 + '@aws-sdk/shared-ini-file-loader': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/credential-provider-process@3.257.0: + resolution: {integrity: sha512-xK8uYeNXaclaBCGrLi4z2pxPRngqLf5BM5jg2fn57zqvlL9V5gJF972FehrVBL0bfp1/laG0ZJtD2K2sapyWAw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/property-provider': 3.257.0 + '@aws-sdk/shared-ini-file-loader': 3.257.0 + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/credential-provider-sso@3.196.0: + resolution: {integrity: sha512-hJV4LDVfvPfj5zC0ysHx3zkwwJOyF+BaMGaMzaScrHyijv5e3qZzdoBLbOQFmrqVnt7DjCU02NvRSS8amLpmSw==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/client-sso': 3.196.0 + '@aws-sdk/property-provider': 3.193.0 + '@aws-sdk/shared-ini-file-loader': 3.193.0 + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-sso@3.224.0: + resolution: {integrity: sha512-Qx5w8MCGAwT5cqimA3ZgtY1jSrC7QGPzZfNflY75PWQIaYgjUNNqdAW0jipr4M/dgVjvo1j/Ek+atNf/niTOsQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-sso': 3.224.0 + '@aws-sdk/property-provider': 3.224.0 + '@aws-sdk/shared-ini-file-loader': 3.224.0 + '@aws-sdk/token-providers': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-sso@3.261.0: + resolution: {integrity: sha512-Ofj7m85/RuxcZMtghhD+U2GGszrU5tB2kxXcnkcHCudOER6bcOOEXnSfmdZnIv4xG+vma3VFwiWk2JkQo5zB5w==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-sso': 3.261.0 + '@aws-sdk/property-provider': 3.257.0 + '@aws-sdk/shared-ini-file-loader': 3.257.0 + '@aws-sdk/token-providers': 3.261.0 + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-web-identity@3.193.0: + resolution: {integrity: sha512-MIQY9KwLCBnRyIt7an4EtMrFQZz2HC1E8vQDdKVzmeQBBePhW61fnX9XDP9bfc3Ypg1NggLG00KBPEC88twLFg==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/property-provider': 3.193.0 + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/credential-provider-web-identity@3.224.0: + resolution: {integrity: sha512-Z/xRFTm9pBVyuIAkYohisb3KPJowPVng7ZuZiblU0PaESoJBTkhAFOblpPv/ZWwb6fT85ANUKrvl4858zLpk/Q==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/property-provider': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/credential-provider-web-identity@3.257.0: + resolution: {integrity: sha512-Cm0uvRv4JuIbD0Kp3W0J/vwjADIyCx8HoZi5yg+QIi5nilocuTQ3ajvLeuPVSvFvdy+yaxSc5FxNXquWt7Mngw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/property-provider': 3.257.0 + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/eventstream-codec@3.224.0: + resolution: {integrity: sha512-p8DePCwvgrrlYK7r3euI5aX/VVxrCl+DClHy0TV6/Eq8WCgWqYfZ5TSl5kbrxIc4U7pDlNIBkTiQMIl/ilEiQg==} + dependencies: + '@aws-crypto/crc32': 2.0.0 + '@aws-sdk/types': 3.224.0 + '@aws-sdk/util-hex-encoding': 3.201.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/eventstream-serde-browser@3.224.0: + resolution: {integrity: sha512-QeyGmKipZsbVkezI5OKe0Xad7u1JPkZWNm1m7uqjd9vTK3A+/fw7eNxOWYVdSKs/kHyAWr9PG+fASBtr3gesPA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/eventstream-serde-universal': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/eventstream-serde-config-resolver@3.224.0: + resolution: {integrity: sha512-zl8YUa+JZV9Dj304pc2HovMuUsz3qzo8HHj+FjIHxVsNfFL4U/NX/eDhkiWNUwVMlQIWvjksoJZ75kIzDyWGKQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/eventstream-serde-node@3.224.0: + resolution: {integrity: sha512-o0PXQwyyqeBk+kkn9wIPVIdzwp28EmRt2UqEH/UO6XzpmZTghuY4ZWkQTx9n+vMZ0e/EbqIlh2BPAhELwYzMug==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/eventstream-serde-universal': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/eventstream-serde-universal@3.224.0: + resolution: {integrity: sha512-sI9WKnaKfpVamLCESHDOg8SkMtkjjYX3awny5PJC3/Jx9zOFN9AnvGtnIJrOGFxs5kBmQNj7c4sKCAPiTCcITw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/eventstream-codec': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/fetch-http-handler@3.193.0: + resolution: {integrity: sha512-UhIS2LtCK9hqBzYVon6BI8WebJW1KC0GGIL/Gse5bqzU9iAGgFLAe66qg9k+/h3Jjc5LNAYzqXNVizMwn7689Q==} + dependencies: + '@aws-sdk/protocol-http': 3.193.0 + '@aws-sdk/querystring-builder': 3.193.0 + '@aws-sdk/types': 3.193.0 + '@aws-sdk/util-base64-browser': 3.188.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/fetch-http-handler@3.224.0: + resolution: {integrity: sha512-IO1Je6ZM0fwT5YYPwQwwXcD4LlsYmP52pwit8AAI4ppz6AkSfs0747uDK0DYnqls7sevBQzUSqBSt6XjcMKjYQ==} + dependencies: + '@aws-sdk/protocol-http': 3.224.0 + '@aws-sdk/querystring-builder': 3.224.0 + '@aws-sdk/types': 3.224.0 + '@aws-sdk/util-base64': 3.208.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/fetch-http-handler@3.257.0: + resolution: {integrity: sha512-zOF+RzQ+wfF7tq7tGUdPcqUTh3+k2f8KCVJE07A8kCopVq4nBu4NH6Eq29Tjpwdya3YlKvE+kFssuQRRRRex+Q==} + dependencies: + '@aws-sdk/protocol-http': 3.257.0 + '@aws-sdk/querystring-builder': 3.257.0 + '@aws-sdk/types': 3.257.0 + '@aws-sdk/util-base64': 3.208.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/hash-blob-browser@3.224.0: + resolution: {integrity: sha512-nUBRZzxbq6mU8FIK6OizC5jIeRkVn5tB2ZYxPd7P2IDhh1OVod6gXkq9EKTf3kecNvYgWUVHcOezZF1qLMDLHg==} + dependencies: + '@aws-sdk/chunked-blob-reader': 3.188.0 + '@aws-sdk/chunked-blob-reader-native': 3.208.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/hash-node@3.193.0: + resolution: {integrity: sha512-O2SLPVBjrCUo+4ouAdRUoHBYsyurO9LcjNZNYD7YQOotBTbVFA3cx7kTZu+K4B6kX7FDaGbqbE1C/T1/eg/r+w==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/types': 3.193.0 + '@aws-sdk/util-buffer-from': 3.188.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/hash-node@3.224.0: + resolution: {integrity: sha512-y7TXMDOSy5E2VZPvmsvRfyXkcQWcjTLFTd85yc70AAeFZiffff1nvZifQSzD78bW6ELJsWHXA2O8yxdBURyoBg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.224.0 + '@aws-sdk/util-buffer-from': 3.208.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/hash-node@3.257.0: + resolution: {integrity: sha512-W/USUuea5Ep3OJ2U7Ve8/5KN1YsDun2WzOFUxc1PyxXP5pW6OgC15/op0e+bmWPG851clvp5S8ZuroUr3aKi3Q==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.257.0 + '@aws-sdk/util-buffer-from': 3.208.0 + '@aws-sdk/util-utf8': 3.254.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/hash-stream-node@3.224.0: + resolution: {integrity: sha512-5RDwzB2C4Zjn4M2kZYntkc2LJdqe8CH9xmudu3ZYESkZToN5Rd3JyqobW9KPbm//R43VR4ml2qUhYHFzK6jvgg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/invalid-dependency@3.193.0: + resolution: {integrity: sha512-54DCknekLwJAI1os76XJ8XCzfAH7BGkBGtlWk5WCNkZTfj3rf5RUiXz4uoKUMWE1rZmyMDoDDS1PBo+yTVKW5w==} + dependencies: + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/invalid-dependency@3.224.0: + resolution: {integrity: sha512-6huV8LBYQYx84uMhQ2SS7nqEkhTkAufwhKceXnysrcrLDuUmyth09Y7fcFblFIDTr4wTgSI0mf6DKVF4nqYCwQ==} + dependencies: + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/invalid-dependency@3.257.0: + resolution: {integrity: sha512-T68SAPRNMEhpke0wlxURgogL7q0B8dfqZsSeS20BVR/lksJxLse9+pbmCDxiu1RrXoEIsEwl5rbLN+Hw8BFFYw==} + dependencies: + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/is-array-buffer@3.188.0: + resolution: {integrity: sha512-n69N4zJZCNd87Rf4NzufPzhactUeM877Y0Tp/F3KiHqGeTnVjYUa4Lv1vLBjqtfjYb2HWT3NKlYn5yzrhaEwiQ==} + engines: {node: '>= 12.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/is-array-buffer@3.201.0: + resolution: {integrity: sha512-UPez5qLh3dNgt0DYnPD/q0mVJY84rA17QE26hVNOW3fAji8W2wrwrxdacWOxyXvlxWsVRcKmr+lay1MDqpAMfg==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/lib-storage@3.226.0(@aws-sdk/abort-controller@3.374.0)(@aws-sdk/client-s3@3.224.0): + resolution: {integrity: sha512-pTPQlZqYhonkaSpdD582fKKfUtQv+80vcyJdmAelUC4hZIyT98XT0wzZLp5N8etAFAgVj7Lxh59qxPB4Qz8MCw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@aws-sdk/abort-controller': ^3.0.0 + '@aws-sdk/client-s3': ^3.0.0 + dependencies: + '@aws-sdk/abort-controller': 3.374.0 + '@aws-sdk/client-s3': 3.224.0 + '@aws-sdk/middleware-endpoint': 3.226.0 + '@aws-sdk/smithy-client': 3.226.0 + buffer: 5.6.0 + events: 3.3.0 + stream-browserify: 3.0.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/md5-js@3.224.0: + resolution: {integrity: sha512-DT9hKzBYJUcPvGxTXwoug5Ac4zJ7q5pwOVF/PFCsN3TiXHHfDAIA0/GJjA6pZwPEi/qVy0iNhGKQK8/0i5JeWw==} + dependencies: + '@aws-sdk/types': 3.224.0 + '@aws-sdk/util-utf8-browser': 3.188.0 + '@aws-sdk/util-utf8-node': 3.208.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-bucket-endpoint@3.224.0: + resolution: {integrity: sha512-cAmrSmVjBCENM9ojUBRhIsuQ2mPH4WxnqE5wxloHdP8BD7usNE/dMtGMhot3Dnf8WZEFpTMfhtrZrmSTCaANTQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/protocol-http': 3.224.0 + '@aws-sdk/types': 3.224.0 + '@aws-sdk/util-arn-parser': 3.208.0 + '@aws-sdk/util-config-provider': 3.208.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-content-length@3.193.0: + resolution: {integrity: sha512-em0Sqo7O7DFOcVXU460pbcYuIjblDTZqK2YE62nQ0T+5Nbj+MSjuoite+rRRdRww9VqBkUROGKON45bUNjogtQ==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/protocol-http': 3.193.0 + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-content-length@3.224.0: + resolution: {integrity: sha512-L9b84b7X/BH+sFZaXg5hQQv0TRqZIGuOIiWJ8CkYeju7OQV03DzbCoNCAgZdI28SSevfrrVK/hwjEQrv+A6x1Q==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/protocol-http': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-content-length@3.257.0: + resolution: {integrity: sha512-yiawbV2azm6QnMY1L2ypG8PDRdjOcEIvFmT0T7y0F49rfbKJOu21j1ONAoCkLrINK6kMqcD5JSQLVCoURxiTxQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/protocol-http': 3.257.0 + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-endpoint@3.193.0: + resolution: {integrity: sha512-Inbpt7jcHGvzF7UOJOCxx9wih0+eAQYERikokidWJa7M405EJpVYq1mGbeOcQUPANU3uWF1AObmUUFhbkriHQw==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/middleware-serde': 3.193.0 + '@aws-sdk/protocol-http': 3.193.0 + '@aws-sdk/signature-v4': 3.193.0 + '@aws-sdk/types': 3.193.0 + '@aws-sdk/url-parser': 3.193.0 + '@aws-sdk/util-config-provider': 3.188.0 + '@aws-sdk/util-middleware': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-endpoint@3.224.0: + resolution: {integrity: sha512-Y+FkQmRyhQUX1E1tviodFwTrfAVjgteoALkFgIb7bxT7fmyQ/AQvdAytkDqIApTgkR61niNDSsAu7lHekDxQgg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/middleware-serde': 3.224.0 + '@aws-sdk/protocol-http': 3.224.0 + '@aws-sdk/signature-v4': 3.224.0 + '@aws-sdk/types': 3.224.0 + '@aws-sdk/url-parser': 3.224.0 + '@aws-sdk/util-config-provider': 3.208.0 + '@aws-sdk/util-middleware': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-endpoint@3.226.0: + resolution: {integrity: sha512-EvLFafjtUxTT0AC9p3aBQu1/fjhWdIeK58jIXaNFONfZ3F8QbEYUPuF/SqZvJM6cWfOO9qwYKkRDbCSTYhprIg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/middleware-serde': 3.226.0 + '@aws-sdk/protocol-http': 3.226.0 + '@aws-sdk/signature-v4': 3.226.0 + '@aws-sdk/types': 3.226.0 + '@aws-sdk/url-parser': 3.226.0 + '@aws-sdk/util-config-provider': 3.208.0 + '@aws-sdk/util-middleware': 3.226.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-endpoint@3.257.0: + resolution: {integrity: sha512-RQNQe/jeVuWZtXXfcOm+e3qMFICY6ERsXUrbt0rjHgvajZCklcrRJgxJSCwrcS7Le3nl9azFPMAMj9L7uSK28g==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/middleware-serde': 3.257.0 + '@aws-sdk/protocol-http': 3.257.0 + '@aws-sdk/signature-v4': 3.257.0 + '@aws-sdk/types': 3.257.0 + '@aws-sdk/url-parser': 3.257.0 + '@aws-sdk/util-config-provider': 3.208.0 + '@aws-sdk/util-middleware': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-expect-continue@3.224.0: + resolution: {integrity: sha512-xgihNtu5dXzRqL0QrOuMLmSoji7BsKJ+rCXjW+X+Z1flYFV5UDY5PI0dgAlgWQDWZDyu17n4R5IIZUzb/aAI1g==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/protocol-http': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-flexible-checksums@3.224.0: + resolution: {integrity: sha512-8umP3a1YNg5+sowQgzKNiq//vSVC53iTBzg8/oszstwIMYE9aNf4RKd/X/H9biBF/G05xdTjqNAQrAh54UbKrQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/crc32': 2.0.0 + '@aws-crypto/crc32c': 2.0.0 + '@aws-sdk/is-array-buffer': 3.201.0 + '@aws-sdk/protocol-http': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-host-header@3.193.0: + resolution: {integrity: sha512-aegzj5oRWd//lmfmkzRmgG2b4l3140v8Ey4QkqCxcowvAEX5a7rh23yuKaGtmiePwv2RQalCKz+tN6JXCm8g6Q==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/protocol-http': 3.193.0 + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-host-header@3.224.0: + resolution: {integrity: sha512-4eL8EVhgxTjvdVs+P3SSEkoMXBte7hSQ/+kOZVNR5ze8QPnUiDpJMS2BQrMoA2INxX9tSqp6zTrDNMc3LNvKbQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/protocol-http': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-host-header@3.257.0: + resolution: {integrity: sha512-gEi9AJdJfRfU8Qr6HK1hfhxTzyV3Giq4B/h7um99hIFAT/GCg9xiPvAOKPo6UeuiKEv3b7RpSL4s6cBvnJMJBA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/protocol-http': 3.257.0 + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-location-constraint@3.224.0: + resolution: {integrity: sha512-FpgKNGzImgmHTbz4Hjc41GEH4/dASxz6sTtn5T+kFDsT1j7o21tpWlS6psoazTz9Yi3ichBo2yzYUaY3QxOFew==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-logger@3.193.0: + resolution: {integrity: sha512-D/h1pU5tAcyJpJ8ZeD1Sta0S9QZPcxERYRBiJdEl8VUrYwfy3Cl1WJedVOmd5nG73ZLRSyHeXHewb/ohge3yKQ==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-logger@3.224.0: + resolution: {integrity: sha512-AmvuezI1vGgKZDsA2slHZJ6nQMqogUyzK27wM03458a2JgFqZvWCUPSY/P+OZ0FpnFEC34/kvvF4bI54T0C5jA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-logger@3.257.0: + resolution: {integrity: sha512-8RDXW/VbMKBsXDfcCLmROZcWKyrekyiPa3J1aIaBy0tq9o4xpGoXw/lwwIrNVvISAFslb57rteup34bfn6ta6w==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-recursion-detection@3.193.0: + resolution: {integrity: sha512-fMWP76Q1GOb/9OzS1arizm6Dbfo02DPZ6xp7OoAN3PS6ybH3Eb47s/gP3jzgBPAITQacFj4St/4a06YWYrN3NA==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/protocol-http': 3.193.0 + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-recursion-detection@3.224.0: + resolution: {integrity: sha512-ySTGlMvNaH5J77jYVVgwOF1ozz3Kp6f/wjTvivOcBR1zlRv0FXa1y033QMnrAAtKSNkzClXtNOycBM463QImJw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/protocol-http': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-recursion-detection@3.257.0: + resolution: {integrity: sha512-rUCih6zHh8k9Edf5N5Er4s508FYbwLM0MWTD2axzlj9TjLqEQ9OKED3wHaLffXSDzodd3oTAfJCLPbWQyoZ3ZQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/protocol-http': 3.257.0 + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-retry@3.193.0: + resolution: {integrity: sha512-zTQkHLBQBJi6ns655WYcYLyLPc1tgbEYU080Oc8zlveLUqoDn1ogkcmNhG7XMeQuBvWZBYN7J3/wFaXlDzeCKg==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/protocol-http': 3.193.0 + '@aws-sdk/service-error-classification': 3.193.0 + '@aws-sdk/types': 3.193.0 + '@aws-sdk/util-middleware': 3.193.0 + tslib: 2.6.2 + uuid: 8.3.2 + dev: false + + /@aws-sdk/middleware-retry@3.224.0: + resolution: {integrity: sha512-zwl8rZZb5OWLzOnEW58RRklbehDfcdtD98qtgm0NLM9ErBALEEb2Y4MM5zhRiMtVjzrDw71+Mhk5+4TAlwJyXA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/protocol-http': 3.224.0 + '@aws-sdk/service-error-classification': 3.224.0 + '@aws-sdk/types': 3.224.0 + '@aws-sdk/util-middleware': 3.224.0 + tslib: 2.6.2 + uuid: 8.3.2 + dev: false + + /@aws-sdk/middleware-retry@3.259.0: + resolution: {integrity: sha512-pVh1g8e84MAi7eVtWLiiiCtn82LzxOP7+LxTRHatmgIeN22yGQBZILliPDJypUPvDYlwxI1ekiK+oPTcte0Uww==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/protocol-http': 3.257.0 + '@aws-sdk/service-error-classification': 3.257.0 + '@aws-sdk/types': 3.257.0 + '@aws-sdk/util-middleware': 3.257.0 + '@aws-sdk/util-retry': 3.257.0 + tslib: 2.6.2 + uuid: 8.3.2 + dev: false + + /@aws-sdk/middleware-sdk-s3@3.224.0: + resolution: {integrity: sha512-SDyFandByU9UBQOxqFk8TCE0e9FPA/nr0FRjANxkIm24/zxk2yZbk3OUx/Zr7ibo28b5BqcQV69IClBOukPiEw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/middleware-bucket-endpoint': 3.224.0 + '@aws-sdk/protocol-http': 3.224.0 + '@aws-sdk/types': 3.224.0 + '@aws-sdk/util-arn-parser': 3.208.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-sdk-sts@3.193.0: + resolution: {integrity: sha512-TafiDkeflUsnbNa89TLkDnAiRRp1gAaZLDAjt75AzriRKZnhtFfYUXWb+qAuN50T+CkJ/gZI9LHDZL5ogz/HxQ==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/middleware-signing': 3.193.0 + '@aws-sdk/property-provider': 3.193.0 + '@aws-sdk/protocol-http': 3.193.0 + '@aws-sdk/signature-v4': 3.193.0 + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-sdk-sts@3.224.0: + resolution: {integrity: sha512-rUoPPejj4N8S+P39ap9Iqbprl9L7LBlkuMHwMCqgeRJBhdI+1YeDfUekegJxceJv/BDXaoI2aSE0tCUS8rK0Ug==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/middleware-signing': 3.224.0 + '@aws-sdk/property-provider': 3.224.0 + '@aws-sdk/protocol-http': 3.224.0 + '@aws-sdk/signature-v4': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-sdk-sts@3.257.0: + resolution: {integrity: sha512-d6IJCLRi3O2tm4AFK60WNhIwmMmspj1WzKR1q1TaoPzoREPG2xg+Am18wZBRkCyYuRPPrbizmkvAmAJiUolMAw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/middleware-signing': 3.257.0 + '@aws-sdk/property-provider': 3.257.0 + '@aws-sdk/protocol-http': 3.257.0 + '@aws-sdk/signature-v4': 3.257.0 + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-serde@3.193.0: + resolution: {integrity: sha512-dH93EJYVztY+ZDPzSMRi9LfAZfKO+luH62raNy49hlNa4jiyE1Tc/+qwlmOEpfGsrtcZ9TgsON1uFF9sgBXXaA==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-serde@3.224.0: + resolution: {integrity: sha512-4wHJ4DyhvyqQ853zfIw6sRw909VB+hFEqatmXYvO5OYap03Eed92wslsR2Gtfw1B2/zjDscPpwPyHoCIk30sHA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-serde@3.226.0: + resolution: {integrity: sha512-nPuOOAkSfx9TxzdKFx0X2bDlinOxGrqD7iof926K/AEflxGD1DBdcaDdjlYlPDW2CVE8LV/rAgbYuLxh/E/1VA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.226.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-serde@3.257.0: + resolution: {integrity: sha512-/JasfXPWFq24mnCrx9fxW/ISBSp07RJwhsF14qzm8Qy3Z0z470C+QRM6otTwAkYuuVt1wuLjja5agq3Jtzq7dQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-signing@3.193.0: + resolution: {integrity: sha512-obBoELGPf5ikvHYZwbzllLeuODiokdDfe92Ve2ufeOa/d8+xsmbqNzNdCTLNNTmr1tEIaEE7ngZVTOiHqAVhyw==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/property-provider': 3.193.0 + '@aws-sdk/protocol-http': 3.193.0 + '@aws-sdk/signature-v4': 3.193.0 + '@aws-sdk/types': 3.193.0 + '@aws-sdk/util-middleware': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-signing@3.224.0: + resolution: {integrity: sha512-6T+dybVn5EYsxkNc4eVKAeoj6x6FfRXkZWMRxkepDoOJufMUNTfpoDEl6PcgJU6Wq4odbqV737x/3j53VZc6dA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/property-provider': 3.224.0 + '@aws-sdk/protocol-http': 3.224.0 + '@aws-sdk/signature-v4': 3.224.0 + '@aws-sdk/types': 3.224.0 + '@aws-sdk/util-middleware': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-signing@3.257.0: + resolution: {integrity: sha512-hCH3D83LHmm6nqmtNrGTWZCVjsQXrGHIXbd17/qrw7aPFvcAhsiiCncGFP+XsUXEKa2ZqcSNMUyPrx69ofNRZQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/property-provider': 3.257.0 + '@aws-sdk/protocol-http': 3.257.0 + '@aws-sdk/signature-v4': 3.257.0 + '@aws-sdk/types': 3.257.0 + '@aws-sdk/util-middleware': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-ssec@3.224.0: + resolution: {integrity: sha512-S9a3fvF0Lv/NnXKbh0cbqhzfVcCOU1pPeGKuDB/p7AWCoql/KSG52MGBU6jKcevCtWVUKpSkgJfs+xkKmSiXIA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-stack@3.193.0: + resolution: {integrity: sha512-Ix5d7gE6bZwFNIVf0dGnjYuymz1gjitNoAZDPpv1nEZlUMek/jcno5lmzWFzUZXY/azpbIyaPwq/wm/c69au5A==} + engines: {node: '>= 12.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-stack@3.224.0: + resolution: {integrity: sha512-8mBrc3nj4h6FnDWnxbjfFXUPr/7UIAaGAG15D27Z/KNFnMjOqNTtpkbcoh3QQHRLX3PjTuvzT5WCqXmgD2/oiw==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-stack@3.226.0: + resolution: {integrity: sha512-85wF29LvPvpoed60fZGDYLwv1Zpd/cM0C22WSSFPw1SSJeqO4gtFYyCg2squfT3KI6kF43IIkOCJ+L7GtryPug==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-stack@3.257.0: + resolution: {integrity: sha512-awg2F0SvwACBaw4HIObK8pQGfSqAc4Vy+YFzWSfZNVC35oRO6RsRdKHVU99lRC0LrT2Ptmfghl2DMPSrRDbvlQ==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-user-agent@3.193.0: + resolution: {integrity: sha512-0vT6F9NwYQK7ARUUJeHTUIUPnupsO3IbmjHSi1+clkssFlJm2UfmSGeafiWe4AYH3anATTvZEtcxX5DZT/ExbA==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/protocol-http': 3.193.0 + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-user-agent@3.224.0: + resolution: {integrity: sha512-YXHC/n8k4qeIkqFVACPmF/QfJyKSOMD1HjM7iUZmJ9yGqDRFeGgn4o2Jktd0dor7sTv6pfUDkLqspxURAsokzA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/protocol-http': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-user-agent@3.257.0: + resolution: {integrity: sha512-37rt75LZyD0UWpbcFuxEGqwF3DZKSixQPl7AsDe6q3KtrO5gGQB+diH5vbY0txNNYyv5IK9WMwvY73mVmoWRmw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/protocol-http': 3.257.0 + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/node-config-provider@3.193.0: + resolution: {integrity: sha512-5RLdjQLH69ISRG8TX9klSLOpEySXxj+z9E9Em39HRvw0/rDcd8poCTADvjYIOqRVvMka0z/hm+elvUTIVn/DRw==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/property-provider': 3.193.0 + '@aws-sdk/shared-ini-file-loader': 3.193.0 + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/node-config-provider@3.224.0: + resolution: {integrity: sha512-ULv0Ao95vNEiwCreN9ZbZ5vntaGjdMLolCiyt3B2FDWbuOorZJR5QXFydPBpo4AQOh1y/S2MIUWLhz00DY364g==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/property-provider': 3.224.0 + '@aws-sdk/shared-ini-file-loader': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/node-config-provider@3.259.0: + resolution: {integrity: sha512-DUOqr71oonBvM6yKPdhDBmraqgXHCFrVWFw7hc5ZNxL2wS/EsbKfGPJp+C+SUgpn1upIWPNnh/bNoLAbBkcLsA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/property-provider': 3.257.0 + '@aws-sdk/shared-ini-file-loader': 3.257.0 + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/node-http-handler@3.193.0: + resolution: {integrity: sha512-DP4BmFw64HOShgpAPEEMZedVnRmKKjHOwMEoXcnNlAkMXnYUFHiKvudYq87Q2AnSlT6OHkyMviB61gEvIk73dA==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/abort-controller': 3.193.0 + '@aws-sdk/protocol-http': 3.193.0 + '@aws-sdk/querystring-builder': 3.193.0 + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/node-http-handler@3.224.0: + resolution: {integrity: sha512-8h4jWsfVRUcJKkqZ9msSN4LhldBpXdNlMcA8ku8IVEBHf5waxqpIhupwR0uCMmV3FDINLqkf/8EwEYAODeRjrw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/abort-controller': 3.224.0 + '@aws-sdk/protocol-http': 3.224.0 + '@aws-sdk/querystring-builder': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/node-http-handler@3.257.0: + resolution: {integrity: sha512-8KnWHVVwaGKyTlkTU9BSOAiSovNDoagxemU2l10QqBbzUCVpljCUMUkABEGRJ1yoQCl6DJ7RtNkAyZ8Ne/E15A==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/abort-controller': 3.257.0 + '@aws-sdk/protocol-http': 3.257.0 + '@aws-sdk/querystring-builder': 3.257.0 + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/property-provider@3.193.0: + resolution: {integrity: sha512-IaDR/PdZjKlAeSq2E/6u6nkPsZF9wvhHZckwH7uumq4ocWsWXFzaT+hKpV4YZPHx9n+K2YV4Gn/bDedpz99W1Q==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/property-provider@3.224.0: + resolution: {integrity: sha512-1F1Hepndlmj6wykNv0ynlS9YTaT3LRF/mqXhCRGLbCWSmCiaW9BUH/ddMdBZJiSw7kcPePKid5ueW84fAO/nKg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/property-provider@3.257.0: + resolution: {integrity: sha512-3rUbRAcF0GZ5PhDiXhS4yREfZ5hOEtvYEa9S/19OdM5eoypOaLU5XnFcCKfnccSP8SkdgpJujzxOMRWNWadlAQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/protocol-http@3.193.0: + resolution: {integrity: sha512-r0wbTwFJyXq0uiImI6giqG3g/RO1N/y4wwPA7qr7OC+KXJ0NkyVxIf6e7Vx8h06aM1ATtngbwJaMP59kVCp85A==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/protocol-http@3.224.0: + resolution: {integrity: sha512-myp31UkADbktZtIZLc4cNfr5zSNVJjPReoH37NPpvgREKOGg7ZB6Lb3UyKbjzrmIv985brMOunlMgIBIJhuPIg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/protocol-http@3.226.0: + resolution: {integrity: sha512-zWkVqiTA9RXL6y0hhfZc9bcU4DX2NI6Hw9IhQmSPeM59mdbPjJlY4bLlMr5YxywqO3yQ/ylNoAfrEzrDjlOSRg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.226.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/protocol-http@3.257.0: + resolution: {integrity: sha512-xt7LGOgZIvbLS3418AYQLacOqx+mo5j4mPiIMz7f6AaUg+/fBUgESVsncKDqxbEJVwwCXSka8Ca0cntJmoeMSw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/querystring-builder@3.193.0: + resolution: {integrity: sha512-PRaK6649iw0UO45UjUoiUzFcOKXZb8pMjjFJpqALpEvdZT3twxqhlPXujT7GWPKrSwO4uPLNnyYEtPY82wx2vw==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/types': 3.193.0 + '@aws-sdk/util-uri-escape': 3.188.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/querystring-builder@3.224.0: + resolution: {integrity: sha512-Fwzt42wWRhf04TetQPqDL03jX5W2cAkRFQewOkIRYVFV17b72z4BFhKID6bpLEtNb4YagyllCWosNg1xooDURQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.224.0 + '@aws-sdk/util-uri-escape': 3.201.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/querystring-builder@3.257.0: + resolution: {integrity: sha512-mZHWLP7XIkzx1GIXO5WfX/iJ+aY9TWs02RE9FkdL2+by0HEMR65L3brQTbU1mIBJ7BjaPwYH24dljUOSABX7yg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.257.0 + '@aws-sdk/util-uri-escape': 3.201.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/querystring-parser@3.193.0: + resolution: {integrity: sha512-dGEPCe8SK4/td5dSpiaEI3SvT5eHXrbJWbLGyD4FL3n7WCGMy2xVWAB/yrgzD0GdLDjDa8L5vLVz6yT1P9i+hA==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/querystring-parser@3.224.0: + resolution: {integrity: sha512-UIJZ76ClFtALXRIQS3Za4R76JTsjCYReSBEQ7ag7RF1jwVZLAggdfED9w3XDrN7jbaK6i+aI3Y+eFeq0sB2fcA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/querystring-parser@3.226.0: + resolution: {integrity: sha512-FzB+VrQ47KAFxiPt2YXrKZ8AOLZQqGTLCKHzx4bjxGmwgsjV8yIbtJiJhZLMcUQV4LtGeIY9ixIqQhGvnZHE4A==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.226.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/querystring-parser@3.257.0: + resolution: {integrity: sha512-UDrE1dEwWrWT8dG2VCrGYrPxCWOkZ1fPTPkjpkR4KZEdQDZBqU5gYZF2xPj8Nz7pjQVHFuW2wFm3XYEk56GEjg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/service-error-classification@3.193.0: + resolution: {integrity: sha512-bPnXVu8ErE1RfWVVQKc2TE7EuoImUi4dSPW9g80fGRzJdQNwXb636C+7OUuWvSDzmFwuBYqZza8GZjVd+rz2zQ==} + engines: {node: '>= 12.0.0'} + dev: false + + /@aws-sdk/service-error-classification@3.224.0: + resolution: {integrity: sha512-0bnbYtCe+vqtaGItL+1UzQPt+yZLbU8G/aIXPQUL7555jdnjnbAtczCbIcLAJUqlE/OLwRhQVGLKbau8QAdxgQ==} + engines: {node: '>=14.0.0'} + dev: false + + /@aws-sdk/service-error-classification@3.257.0: + resolution: {integrity: sha512-FAyR0XsueGkkqDtkP03cTJQk52NdQ9sZelLynmmlGPUP75LApRPvFe1riKrou6+LsDbwVNVffj6mbDfIcOhaOw==} + engines: {node: '>=14.0.0'} + dev: false + + /@aws-sdk/shared-ini-file-loader@3.193.0: + resolution: {integrity: sha512-hnvZup8RSpFXfah7Rrn6+lQJnAOCO+OiDJ2R/iMgZQh475GRQpLbu3cPhCOkjB14vVLygJtW8trK/0+zKq93bQ==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/shared-ini-file-loader@3.224.0: + resolution: {integrity: sha512-6a/XP3lRRcX5ic+bXzF2f644KERVqMx+s0JRrGsPAwTMaMiV0A7Ifl4HKggx6dnxh8j/MXUMsWMtuxt/kCu86A==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/shared-ini-file-loader@3.257.0: + resolution: {integrity: sha512-HNjC1+Wx3xHiJc+CP14GhIdVhfQGSjroAsWseRxAhONocA9Fl1ZX4hx7+sA5c9nOoMVOovi6ivJ/6lCRPTDRrQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/signature-v4-multi-region@3.224.0: + resolution: {integrity: sha512-xOW8rtEH2Rcadr+CFfiISZwcbf4jPdc4OvL6OiPsv+arndOhxk+4ZaRT2xic1FrVdYQypmSToRITYlZc9N7PjQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@aws-sdk/signature-v4-crt': ^3.118.0 + peerDependenciesMeta: + '@aws-sdk/signature-v4-crt': + optional: true + dependencies: + '@aws-sdk/protocol-http': 3.224.0 + '@aws-sdk/signature-v4': 3.224.0 + '@aws-sdk/types': 3.224.0 + '@aws-sdk/util-arn-parser': 3.208.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/signature-v4@3.193.0: + resolution: {integrity: sha512-JEqqOB8wQZz6g1ERNUOIBFDFt8OJtz5G5Uh1CdkS5W66gyWnJEz/dE1hA2VTqqQwHGGEsIEV/hlzruU1lXsvFA==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/is-array-buffer': 3.188.0 + '@aws-sdk/types': 3.193.0 + '@aws-sdk/util-hex-encoding': 3.188.0 + '@aws-sdk/util-middleware': 3.193.0 + '@aws-sdk/util-uri-escape': 3.188.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/signature-v4@3.224.0: + resolution: {integrity: sha512-+oq1iylYQOvdXXO7r18SEhXIZpLd3GvJhmoReX+yjvVq8mGevDAmQiw6lwFZ6748sOmH4CREWD5H9Snrj+zLMg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/is-array-buffer': 3.201.0 + '@aws-sdk/types': 3.224.0 + '@aws-sdk/util-hex-encoding': 3.201.0 + '@aws-sdk/util-middleware': 3.224.0 + '@aws-sdk/util-uri-escape': 3.201.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/signature-v4@3.226.0: + resolution: {integrity: sha512-/R5q5agdPd7HJB68XMzpxrNPk158EHUvkFkuRu5Qf3kkkHebEzWEBlWoVpUe6ss4rP9Tqcue6xPuaftEmhjpYw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/is-array-buffer': 3.201.0 + '@aws-sdk/types': 3.226.0 + '@aws-sdk/util-hex-encoding': 3.201.0 + '@aws-sdk/util-middleware': 3.226.0 + '@aws-sdk/util-uri-escape': 3.201.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/signature-v4@3.257.0: + resolution: {integrity: sha512-aLQQN59X/D0+ShzPD3Anj5ntdMA/RFeNLOUCDyDvremViGi6yxUS98usQ/8bG5Rq0sW2GGMdbFUFmrDvqdiqEQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/is-array-buffer': 3.201.0 + '@aws-sdk/types': 3.257.0 + '@aws-sdk/util-hex-encoding': 3.201.0 + '@aws-sdk/util-middleware': 3.257.0 + '@aws-sdk/util-uri-escape': 3.201.0 + '@aws-sdk/util-utf8': 3.254.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/smithy-client@3.193.0: + resolution: {integrity: sha512-BY0jhfW76vyXr7ODMaKO3eyS98RSrZgOMl6DTQV9sk7eFP/MPVlG7p7nfX/CDIgPBIO1z0A0i2CVIzYur9uGgQ==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/middleware-stack': 3.193.0 + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/smithy-client@3.224.0: + resolution: {integrity: sha512-KXXzzrCBv8ewWdtm/aolZHr2f9NRZOcDutFaWXbfSptEsK50Zi9PNzB9ZVKUHyAXYjwJHb2Sl18WRrwIxH6H4g==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/middleware-stack': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/smithy-client@3.226.0: + resolution: {integrity: sha512-BWr1FhWSUhkSBp0TLzliD5AQBjA2Jmo9FlOOt+cBwd9BKkSGlGj+HgATYJ83Sjjg2+J6qvEZBxB78LKVHhorBw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/middleware-stack': 3.226.0 + '@aws-sdk/types': 3.226.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/smithy-client@3.261.0: + resolution: {integrity: sha512-j8XQEa3caZUVFVZfhJjaskw80O/tB+IXu84HMN44N7UkXaCFHirUsNjTDztJhnVXf/gKXzIqUqprfRnOvwLtIg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/middleware-stack': 3.257.0 + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/token-providers@3.224.0: + resolution: {integrity: sha512-cswWqA4n1v3JIALYRA8Tq/4uHcFpBg5cgi2khNHBCF/H09Hu3dynGup6Ji8cCzf3fTak4eBQipcWaWUGE0hTGw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-sso-oidc': 3.224.0 + '@aws-sdk/property-provider': 3.224.0 + '@aws-sdk/shared-ini-file-loader': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/token-providers@3.261.0: + resolution: {integrity: sha512-Vi/GOnx8rPvQz5TdJJl5CwpTX6uRsSE3fzh94O4FEAIxIFtb4P5juqg92+2CJ81C7iNduB6eEeSHtwWUylypXQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-sso-oidc': 3.261.0 + '@aws-sdk/property-provider': 3.257.0 + '@aws-sdk/shared-ini-file-loader': 3.257.0 + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/types@3.193.0: + resolution: {integrity: sha512-LV/wcPolRZKORrcHwkH59QMCkiDR5sM+9ZtuTxvyUGG2QFW/kjoxs08fUF10OWNJMrotBI+czDc5QJRgN8BlAw==} + engines: {node: '>= 12.0.0'} + dev: false + + /@aws-sdk/types@3.224.0: + resolution: {integrity: sha512-7te9gRondKPjEebyiPYn59Kr5LZOL48HXC05TzFIN/JXwWPJbQpROBPeKd53V1aRdr3vSQhDY01a+vDOBBrEUQ==} + engines: {node: '>=14.0.0'} + dev: false + + /@aws-sdk/types@3.226.0: + resolution: {integrity: sha512-MmmNHrWeO4man7wpOwrAhXlevqtOV9ZLcH4RhnG5LmRce0RFOApx24HoKENfFCcOyCm5LQBlsXCqi0dZWDWU0A==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/types@3.257.0: + resolution: {integrity: sha512-LmqXuBQBGeaGi/3Rp7XiEX1B5IPO2UUfBVvu0wwGqVsmstT0SbOVDZGPmxygACbm64n+PRx3uTSDefRfoiWYZg==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/url-parser@3.193.0: + resolution: {integrity: sha512-hwD1koJlOu2a6GvaSbNbdo7I6a3tmrsNTZr8bCjAcbqpc5pDThcpnl/Uaz3zHmMPs92U8I6BvWoK6pH8By06qw==} + dependencies: + '@aws-sdk/querystring-parser': 3.193.0 + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/url-parser@3.224.0: + resolution: {integrity: sha512-DGQoiOxRVq9eEbmcGF7oz/htcHxFtLlUTzKbaX1gFuh1kmhRQwJIzz6vkrMdxOgPjvUYMJuMEcYnsHolDNWbMg==} + dependencies: + '@aws-sdk/querystring-parser': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/url-parser@3.226.0: + resolution: {integrity: sha512-p5RLE0QWyP0OcTOLmFcLdVgUcUEzmEfmdrnOxyNzomcYb0p3vUagA5zfa1HVK2azsQJFBv28GfvMnba9bGhObg==} + dependencies: + '@aws-sdk/querystring-parser': 3.226.0 + '@aws-sdk/types': 3.226.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/url-parser@3.257.0: + resolution: {integrity: sha512-Qe/AcFe/NFZHa6cN2afXEQn9ehXxh57dWGdRjfjd2lQqNV4WW1R2pl2Tm1ZJ1dwuCNLJi4NHLMk8lrD3QQ8rdg==} + dependencies: + '@aws-sdk/querystring-parser': 3.257.0 + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-arn-parser@3.208.0: + resolution: {integrity: sha512-QV4af+kscova9dv4VuHOgH8wEr/IIYHDGcnyVtkUEqahCejWr1Kuk+SBK0xMwnZY5LSycOtQ8aeqHOn9qOjZtA==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-base64-browser@3.188.0: + resolution: {integrity: sha512-qlH+5NZBLiyKziL335BEPedYxX6j+p7KFRWXvDQox9S+s+gLCayednpK+fteOhBenCcR9fUZOVuAPScy1I8qCg==} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-base64-node@3.188.0: + resolution: {integrity: sha512-r1dccRsRjKq+OhVRUfqFiW3sGgZBjHbMeHLbrAs9jrOjU2PTQ8PSzAXLvX/9lmp7YjmX17Qvlsg0NCr1tbB9OA==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/util-buffer-from': 3.188.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-base64@3.208.0: + resolution: {integrity: sha512-PQniZph5A6N7uuEOQi+1hnMz/FSOK/8kMFyFO+4DgA1dZ5pcKcn5wiFwHkcTb/BsgVqQa3Jx0VHNnvhlS8JyTg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/util-buffer-from': 3.208.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-body-length-browser@3.188.0: + resolution: {integrity: sha512-8VpnwFWXhnZ/iRSl9mTf+VKOX9wDE8QtN4bj9pBfxwf90H1X7E8T6NkiZD3k+HubYf2J94e7DbeHs7fuCPW5Qg==} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-body-length-node@3.188.0: + resolution: {integrity: sha512-XwqP3vxk60MKp4YDdvDeCD6BPOiG2e+/Ou4AofZOy5/toB6NKz2pFNibQIUg2+jc7mPMnGnvOW3MQEgSJ+gu/Q==} + engines: {node: '>= 12.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-body-length-node@3.208.0: + resolution: {integrity: sha512-3zj50e5g7t/MQf53SsuuSf0hEELzMtD8RX8C76f12OSRo2Bca4FLLYHe0TZbxcfQHom8/hOaeZEyTyMogMglqg==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-buffer-from@3.188.0: + resolution: {integrity: sha512-NX1WXZ8TH20IZb4jPFT2CnLKSqZWddGxtfiWxD9M47YOtq/SSQeR82fhqqVjJn4P8w2F5E28f+Du4ntg/sGcxA==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/is-array-buffer': 3.188.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-buffer-from@3.208.0: + resolution: {integrity: sha512-7L0XUixNEFcLUGPeBF35enCvB9Xl+K6SQsmbrPk1P3mlV9mguWSDQqbOBwY1Ir0OVbD6H/ZOQU7hI/9RtRI0Zw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/is-array-buffer': 3.201.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-config-provider@3.188.0: + resolution: {integrity: sha512-LBA7tLbi7v4uvbOJhSnjJrxbcRifKK/1ZVK94JTV2MNSCCyNkFotyEI5UWDl10YKriTIUyf7o5cakpiDZ3O4xg==} + engines: {node: '>= 12.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-config-provider@3.208.0: + resolution: {integrity: sha512-DSRqwrERUsT34ug+anlMBIFooBEGwM8GejC7q00Y/9IPrQy50KnG5PW2NiTjuLKNi7pdEOlwTSEocJE15eDZIg==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-defaults-mode-browser@3.193.0: + resolution: {integrity: sha512-9riQKFrSJcsNAMnPA/3ltpSxNykeO20klE/UKjxEoD7UWjxLwsPK22UJjFwMRaHoAFcZD0LU/SgPxbC0ktCYCg==} + engines: {node: '>= 10.0.0'} + dependencies: + '@aws-sdk/property-provider': 3.193.0 + '@aws-sdk/types': 3.193.0 + bowser: 2.11.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-defaults-mode-browser@3.224.0: + resolution: {integrity: sha512-umk+A/pmlbuyvDCgdndgJUa0xitcTUF7XoUt/3qDTpNbzR5Dzgdbz74BgXUAEBJ8kPP5pCo2VE1ZD7fxqYU/dQ==} + engines: {node: '>= 10.0.0'} + dependencies: + '@aws-sdk/property-provider': 3.224.0 + '@aws-sdk/types': 3.224.0 + bowser: 2.11.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-defaults-mode-browser@3.261.0: + resolution: {integrity: sha512-lX3X1NfzQVV6cakepGV24uRcqevlDnQ8VgaCV8dhnw1FVThueFigyoFaUA02+uRXbV9KIbNWkEvweNtm2wvyDw==} + engines: {node: '>= 10.0.0'} + dependencies: + '@aws-sdk/property-provider': 3.257.0 + '@aws-sdk/types': 3.257.0 + bowser: 2.11.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-defaults-mode-node@3.193.0: + resolution: {integrity: sha512-occQmckvPRiM4YQIZnulfKKKjykGKWloa5ByGC5gOEGlyeP9zJpfs4zc/M2kArTAt+d2r3wkBtsKe5yKSlVEhA==} + engines: {node: '>= 10.0.0'} + dependencies: + '@aws-sdk/config-resolver': 3.193.0 + '@aws-sdk/credential-provider-imds': 3.193.0 + '@aws-sdk/node-config-provider': 3.193.0 + '@aws-sdk/property-provider': 3.193.0 + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-defaults-mode-node@3.224.0: + resolution: {integrity: sha512-ZJQJ1McbQ5Rnf5foCFAKHT8Cbwg4IbM+bb6fCkHRJFH9AXEvwc+hPtSYf0KuI7TmoZFj9WG5JOE9Ns6g7lRHSA==} + engines: {node: '>= 10.0.0'} + dependencies: + '@aws-sdk/config-resolver': 3.224.0 + '@aws-sdk/credential-provider-imds': 3.224.0 + '@aws-sdk/node-config-provider': 3.224.0 + '@aws-sdk/property-provider': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-defaults-mode-node@3.261.0: + resolution: {integrity: sha512-4AK6yu4bOmHSocUdbGoEHbNXB09UA58ON2HBHY4NxMBuFBAd9XB2tYiyhce+Cm+o+lHbS8oQnw0VZw16WMzzew==} + engines: {node: '>= 10.0.0'} + dependencies: + '@aws-sdk/config-resolver': 3.259.0 + '@aws-sdk/credential-provider-imds': 3.259.0 + '@aws-sdk/node-config-provider': 3.259.0 + '@aws-sdk/property-provider': 3.257.0 + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-endpoints@3.196.0: + resolution: {integrity: sha512-X+DOpRUy/ij49a0GQtggk09oyIQGn0mhER6PbMT69IufZPIg3D5fC5FPEp8bfsPkb70fTEYQEsj/X/rgMQJKsA==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-endpoints@3.224.0: + resolution: {integrity: sha512-k5hHbk7AP/cajw5rF7wmKP39B0WQMFdxrn8dcVOHVK0FZeKbaGCEmOf3AYXrQhswR9Xo815Rqffoml9B1z3bCA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-endpoints@3.257.0: + resolution: {integrity: sha512-3bvmRn5XGYzPPWjLuvHBKdJOb+fijnb8Ungu9bfXnTYFsng/ndHUWeHC22O/p8w3OWoRYUIMaZHxdxe27BFozg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-hex-encoding@3.188.0: + resolution: {integrity: sha512-QyWovTtjQ2RYxqVM+STPh65owSqzuXURnfoof778spyX4iQ4z46wOge1YV2ZtwS8w5LWd9eeVvDrLu5POPYOnA==} + engines: {node: '>= 12.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-hex-encoding@3.201.0: + resolution: {integrity: sha512-7t1vR1pVxKx0motd3X9rI3m/xNp78p3sHtP5yo4NP4ARpxyJ0fokBomY8ScaH2D/B+U5o9ARxldJUdMqyBlJcA==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-locate-window@3.310.0: + resolution: {integrity: sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-middleware@3.193.0: + resolution: {integrity: sha512-+aC6pmkcGgpxaMWCH/FXTsGWl2W342oQGs1OYKGi+W8z9UguXrqamWjdkdMqgunvj9qOEG2KBMKz1FWFFZlUyA==} + engines: {node: '>= 12.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-middleware@3.224.0: + resolution: {integrity: sha512-yA20k9sJdFgs7buVilWExUSJ/Ecr5UJRNQlmgzIpBo9kh5x/N8WyB4kN5MQw5UAA1UZ+j3jmA9+YLFT/mbX3IQ==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-middleware@3.226.0: + resolution: {integrity: sha512-B96CQnwX4gRvQdaQkdUtqvDPkrptV5+va6FVeJOocU/DbSYMAScLxtR3peMS8cnlOT6nL1Eoa42OI9AfZz1VwQ==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-middleware@3.257.0: + resolution: {integrity: sha512-F9ieon8B8eGVs5tyZtAIG3DZEObDvujkspho0qRbUTHUosM0ylJLsMU800fmC/uRHLRrZvb/RSp59+kNDwSAMw==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-retry@3.257.0: + resolution: {integrity: sha512-l9TOsOAYtZxwW3q5fQKW4rsD9t2HVaBfQ4zBamHkNTfB4vBVvCnz4oxkvSvA2MlxCA6am+K1K/oj917Tpqk53g==} + engines: {node: '>= 14.0.0'} + dependencies: + '@aws-sdk/service-error-classification': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-stream-browser@3.224.0: + resolution: {integrity: sha512-JS+C8CyxVFMQ69P4QIDTrzkhseEFCVFy2YHZYlCx3M5P+L1/PQHebTETYFMmO9ThY8TRXmYZDJHv79guvV+saQ==} + dependencies: + '@aws-sdk/fetch-http-handler': 3.224.0 + '@aws-sdk/types': 3.224.0 + '@aws-sdk/util-base64': 3.208.0 + '@aws-sdk/util-hex-encoding': 3.201.0 + '@aws-sdk/util-utf8-browser': 3.188.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-stream-node@3.224.0: + resolution: {integrity: sha512-ztvZHJJg9/BwUrKnSz3jV6T8oOdxA1MRypK2zqTdjoPU9u/8CFQ2p0gszBApMjyxCnLWo1oM5oiMwzz1ufDrlA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/node-http-handler': 3.224.0 + '@aws-sdk/types': 3.224.0 + '@aws-sdk/util-buffer-from': 3.208.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-uri-escape@3.188.0: + resolution: {integrity: sha512-4Y6AYZMT483Tiuq8dxz5WHIiPNdSFPGrl6tRTo2Oi2FcwypwmFhqgEGcqxeXDUJktvaCBxeA08DLr/AemVhPCg==} + engines: {node: '>= 12.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-uri-escape@3.201.0: + resolution: {integrity: sha512-TeTWbGx4LU2c5rx0obHeDFeO9HvwYwQtMh1yniBz00pQb6Qt6YVOETVQikRZ+XRQwEyCg/dA375UplIpiy54mA==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-user-agent-browser@3.193.0: + resolution: {integrity: sha512-1EkGYsUtOMEyJG/UBIR4PtmO3lVjKNoUImoMpLtEucoGbWz5RG9zFSwLevjFyFs5roUBFlxkSpTMo8xQ3aRzQg==} + dependencies: + '@aws-sdk/types': 3.193.0 + bowser: 2.11.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-user-agent-browser@3.224.0: + resolution: {integrity: sha512-Dm/30cLUIM1Oam4V//m9sPrXyGOKFslUXP7Mz2AlR1HelUYoreWAIe7Rx44HR6PaXyZmjW5K0ItmcJ7tCgyMpw==} + dependencies: + '@aws-sdk/types': 3.224.0 + bowser: 2.11.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-user-agent-browser@3.257.0: + resolution: {integrity: sha512-YdavWK6/8Cw6mypEgysGGX/dT9p9qnzFbnN5PQsUY+JJk2Nx8fKFydjGiQ+6rWPeW17RAv9mmbboh9uPVWxVlw==} + dependencies: + '@aws-sdk/types': 3.257.0 + bowser: 2.11.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-user-agent-node@3.193.0: + resolution: {integrity: sha512-G/2/1cSgsxVtREAm8Eq8Duib5PXzXknFRHuDpAxJ5++lsJMXoYMReS278KgV54cojOkAVfcODDTqmY3Av0WHhQ==} + engines: {node: '>= 12.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + dependencies: + '@aws-sdk/node-config-provider': 3.193.0 + '@aws-sdk/types': 3.193.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-user-agent-node@3.224.0: + resolution: {integrity: sha512-BTj0vPorfT7AJzv6RxJHrnAKdIHwZmGjp5TFFaCYgFkHAPsyCPceSdZUjBRW+HbiwEwKfoHOXLGjnOBSqddZKg==} + engines: {node: '>=14.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + dependencies: + '@aws-sdk/node-config-provider': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-user-agent-node@3.259.0: + resolution: {integrity: sha512-R0VTmNs+ySDDebU98BUbsLyeIM5YmAEr9esPpy15XfSy3AWmAeru8nLlztdaLilHZzLIDzvM2t7NGk/FzZFCvA==} + engines: {node: '>=14.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + dependencies: + '@aws-sdk/node-config-provider': 3.259.0 + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-utf8-browser@3.188.0: + resolution: {integrity: sha512-jt627x0+jE+Ydr9NwkFstg3cUvgWh56qdaqAMDsqgRlKD21md/6G226z/Qxl7lb1VEW2LlmCx43ai/37Qwcj2Q==} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-utf8-browser@3.259.0: + resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-utf8-node@3.188.0: + resolution: {integrity: sha512-hCgP4+C0Lekjpjt2zFJ2R/iHes5sBGljXa5bScOFAEkRUc0Qw0VNgTv7LpEbIOAwGmqyxBoCwBW0YHPW1DfmYQ==} + engines: {node: '>= 12.0.0'} + dependencies: + '@aws-sdk/util-buffer-from': 3.188.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-utf8-node@3.208.0: + resolution: {integrity: sha512-jKY87Acv0yWBdFxx6bveagy5FYjz+dtV8IPT7ay1E2WPWH1czoIdMAkc8tSInK31T6CRnHWkLZ1qYwCbgRfERQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/util-buffer-from': 3.208.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-utf8@3.254.0: + resolution: {integrity: sha512-14Kso/eIt5/qfIBmhEL9L1IfyUqswjSTqO2mY7KOzUZ9SZbwn3rpxmtkhmATkRjD7XIlLKaxBkI7tU9Zjzj8Kw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/util-buffer-from': 3.208.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-waiter@3.224.0: + resolution: {integrity: sha512-+SNItYzUSPa8PV1iWwOi3637ztlczJNa2pZ/R1nWf2N8sAmk0BXzGJISv/GKvzPDyAz+uOpT549e8P6rRLZedA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/abort-controller': 3.224.0 + '@aws-sdk/types': 3.224.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-waiter@3.257.0: + resolution: {integrity: sha512-Fr6of3EDOcXVDs5534o7VsJMXdybB0uLy2LzeFAVSwGOY3geKhIquBAiUDqCVu9B+iTldrC0rQ9NIM7ZSpPG8w==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/abort-controller': 3.257.0 + '@aws-sdk/types': 3.257.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/xml-builder@3.201.0: + resolution: {integrity: sha512-brRdB1wwMgjWEnOQsv7zSUhIQuh7DEicrfslAqHop4S4FtSI3GQAShpQqgOpMTNFYcpaWKmE/Y1MJmNY7xLCnw==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@babel/code-frame@7.22.10: + resolution: {integrity: sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.22.10 + chalk: 2.4.2 + dev: true + + /@babel/compat-data@7.22.9: + resolution: {integrity: sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/core@7.18.6: + resolution: {integrity: sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@ampproject/remapping': 2.2.1 + '@babel/code-frame': 7.22.10 + '@babel/generator': 7.22.10 + '@babel/helper-compilation-targets': 7.22.10 + '@babel/helper-module-transforms': 7.22.9(@babel/core@7.18.6) + '@babel/helpers': 7.22.10 + '@babel/parser': 7.22.10 + '@babel/template': 7.22.5 + '@babel/traverse': 7.22.10 + '@babel/types': 7.22.10 + convert-source-map: 1.9.0 + debug: 4.3.4 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/generator@7.22.10: + resolution: {integrity: sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.10 + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.19 + jsesc: 2.5.2 + dev: true + + /@babel/helper-annotate-as-pure@7.22.5: + resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.10 + dev: true + + /@babel/helper-builder-binary-assignment-operator-visitor@7.22.10: + resolution: {integrity: sha512-Av0qubwDQxC56DoUReVDeLfMEjYYSN1nZrTUrWkXd7hpU73ymRANkbuDm3yni9npkn+RXy9nNbEJZEzXr7xrfQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.10 + dev: true + + /@babel/helper-compilation-targets@7.22.10: + resolution: {integrity: sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/compat-data': 7.22.9 + '@babel/helper-validator-option': 7.22.5 + browserslist: 4.21.10 + lru-cache: 5.1.1 + semver: 6.3.1 + dev: true + + /@babel/helper-create-class-features-plugin@7.22.10(@babel/core@7.18.6): + resolution: {integrity: sha512-5IBb77txKYQPpOEdUdIhBx8VrZyDCQ+H82H0+5dX1TmuscP5vJKEE3cKurjtIw/vFwzbVH48VweE78kVDBrqjA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-function-name': 7.22.5 + '@babel/helper-member-expression-to-functions': 7.22.5 + '@babel/helper-optimise-call-expression': 7.22.5 + '@babel/helper-replace-supers': 7.22.9(@babel/core@7.18.6) + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + semver: 6.3.1 + dev: true + + /@babel/helper-create-regexp-features-plugin@7.22.9(@babel/core@7.18.6): + resolution: {integrity: sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-annotate-as-pure': 7.22.5 + regexpu-core: 5.3.2 + semver: 6.3.1 + dev: true + + /@babel/helper-define-polyfill-provider@0.3.3(@babel/core@7.18.6): + resolution: {integrity: sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==} + peerDependencies: + '@babel/core': ^7.4.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-compilation-targets': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + debug: 4.3.4 + lodash.debounce: 4.0.8 + resolve: 1.22.4 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-environment-visitor@7.22.5: + resolution: {integrity: sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-function-name@7.22.5: + resolution: {integrity: sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.22.5 + '@babel/types': 7.22.10 + dev: true + + /@babel/helper-hoist-variables@7.22.5: + resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.10 + dev: true + + /@babel/helper-member-expression-to-functions@7.22.5: + resolution: {integrity: sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.10 + dev: true + + /@babel/helper-module-imports@7.22.5: + resolution: {integrity: sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.10 + dev: true + + /@babel/helper-module-transforms@7.22.9(@babel/core@7.18.6): + resolution: {integrity: sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-module-imports': 7.22.5 + '@babel/helper-simple-access': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/helper-validator-identifier': 7.22.5 + dev: true + + /@babel/helper-optimise-call-expression@7.22.5: + resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.10 + dev: true + + /@babel/helper-plugin-utils@7.22.5: + resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-remap-async-to-generator@7.22.9(@babel/core@7.18.6): + resolution: {integrity: sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-wrap-function': 7.22.10 + dev: true + + /@babel/helper-replace-supers@7.22.9(@babel/core@7.18.6): + resolution: {integrity: sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-member-expression-to-functions': 7.22.5 + '@babel/helper-optimise-call-expression': 7.22.5 + dev: true + + /@babel/helper-simple-access@7.22.5: + resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.10 + dev: true + + /@babel/helper-skip-transparent-expression-wrappers@7.22.5: + resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.10 + dev: true + + /@babel/helper-split-export-declaration@7.22.6: + resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.10 + dev: true + + /@babel/helper-string-parser@7.22.5: + resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-validator-identifier@7.22.5: + resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-validator-option@7.22.5: + resolution: {integrity: sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-wrap-function@7.22.10: + resolution: {integrity: sha512-OnMhjWjuGYtdoO3FmsEFWvBStBAe2QOgwOLsLNDjN+aaiMD8InJk1/O3HSD8lkqTjCgg5YI34Tz15KNNA3p+nQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-function-name': 7.22.5 + '@babel/template': 7.22.5 + '@babel/types': 7.22.10 + dev: true + + /@babel/helpers@7.22.10: + resolution: {integrity: sha512-a41J4NW8HyZa1I1vAndrraTlPZ/eZoga2ZgS7fEr0tZJGVU4xqdE80CEm0CcNjha5EZ8fTBYLKHF0kqDUuAwQw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.22.5 + '@babel/traverse': 7.22.10 + '@babel/types': 7.22.10 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/highlight@7.22.10: + resolution: {integrity: sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.22.5 + chalk: 2.4.2 + js-tokens: 4.0.0 + dev: true + + /@babel/parser@7.22.10: + resolution: {integrity: sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.22.10 + dev: true + + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/plugin-transform-optional-chaining': 7.22.10(@babel/core@7.18.6) + dev: true + + /@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.18.6): + resolution: {integrity: sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-remap-async-to-generator': 7.22.9(@babel/core@7.18.6) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.18.6) + dev: true + + /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.18.6): + resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-create-class-features-plugin': 7.22.10(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-proposal-class-static-block@7.21.0(@babel/core@7.18.6): + resolution: {integrity: sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-create-class-features-plugin': 7.22.10(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.18.6) + dev: true + + /@babel/plugin-proposal-dynamic-import@7.18.6(@babel/core@7.18.6): + resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.18.6) + dev: true + + /@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.18.6): + resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.18.6) + dev: true + + /@babel/plugin-proposal-json-strings@7.18.6(@babel/core@7.18.6): + resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.18.6) + dev: true + + /@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.18.6): + resolution: {integrity: sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.18.6) + dev: true + + /@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.18.6): + resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.18.6) + dev: true + + /@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.18.6): + resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.18.6) + dev: true + + /@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.18.6): + resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.22.9 + '@babel/core': 7.18.6 + '@babel/helper-compilation-targets': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-transform-parameters': 7.22.5(@babel/core@7.18.6) + dev: true + + /@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.18.6): + resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.18.6) + dev: true + + /@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.18.6): + resolution: {integrity: sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.18.6) + dev: true + + /@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.18.6): + resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-create-class-features-plugin': 7.22.10(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-proposal-private-property-in-object@7.21.11(@babel/core@7.18.6): + resolution: {integrity: sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-create-class-features-plugin': 7.22.10(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.18.6) + dev: true + + /@babel/plugin-proposal-unicode-property-regex@7.18.6(@babel/core@7.18.6): + resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==} + engines: {node: '>=4'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-create-regexp-features-plugin': 7.22.9(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.18.6): + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.18.6): + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.18.6): + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.18.6): + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.18.6): + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.18.6): + resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-import-assertions@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.18.6): + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.18.6): + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.18.6): + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.18.6): + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.18.6): + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.18.6): + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.18.6): + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.18.6): + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.18.6): + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.18.6): + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-typescript@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-arrow-functions@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-async-to-generator@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-module-imports': 7.22.5 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-remap-async-to-generator': 7.22.9(@babel/core@7.18.6) + dev: true + + /@babel/plugin-transform-block-scoped-functions@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-block-scoping@7.22.10(@babel/core@7.18.6): + resolution: {integrity: sha512-1+kVpGAOOI1Albt6Vse7c8pHzcZQdQKW+wJH+g8mCaszOdDVwRXa/slHPqIw+oJAJANTKDMuM2cBdV0Dg618Vg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-classes@7.22.6(@babel/core@7.18.6): + resolution: {integrity: sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-compilation-targets': 7.22.10 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-function-name': 7.22.5 + '@babel/helper-optimise-call-expression': 7.22.5 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-replace-supers': 7.22.9(@babel/core@7.18.6) + '@babel/helper-split-export-declaration': 7.22.6 + globals: 11.12.0 + dev: true + + /@babel/plugin-transform-computed-properties@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/template': 7.22.5 + dev: true + + /@babel/plugin-transform-destructuring@7.22.10(@babel/core@7.18.6): + resolution: {integrity: sha512-dPJrL0VOyxqLM9sritNbMSGx/teueHF/htMKrPT7DNxccXxRDPYqlgPFFdr8u+F+qUZOkZoXue/6rL5O5GduEw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-dotall-regex@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-create-regexp-features-plugin': 7.22.9(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-duplicate-keys@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-exponentiation-operator@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-for-of@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-function-name@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-compilation-targets': 7.22.10 + '@babel/helper-function-name': 7.22.5 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-literals@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-member-expression-literals@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-modules-amd@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-module-transforms': 7.22.9(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-modules-commonjs@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-module-transforms': 7.22.9(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-simple-access': 7.22.5 + dev: true + + /@babel/plugin-transform-modules-systemjs@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-module-transforms': 7.22.9(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-validator-identifier': 7.22.5 + dev: true + + /@babel/plugin-transform-modules-umd@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-module-transforms': 7.22.9(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-create-regexp-features-plugin': 7.22.9(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-new-target@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-object-super@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-replace-supers': 7.22.9(@babel/core@7.18.6) + dev: true + + /@babel/plugin-transform-optional-chaining@7.22.10(@babel/core@7.18.6): + resolution: {integrity: sha512-MMkQqZAZ+MGj+jGTG3OTuhKeBpNcO+0oCEbrGNEaOmiEn+1MzRyQlYsruGiU8RTK3zV6XwrVJTmwiDOyYK6J9g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.18.6) + dev: true + + /@babel/plugin-transform-parameters@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-property-literals@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-regenerator@7.22.10(@babel/core@7.18.6): + resolution: {integrity: sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + regenerator-transform: 0.15.2 + dev: true + + /@babel/plugin-transform-reserved-words@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-shorthand-properties@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-spread@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + dev: true + + /@babel/plugin-transform-sticky-regex@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-template-literals@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-typeof-symbol@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-unicode-escapes@7.22.10(@babel/core@7.18.6): + resolution: {integrity: sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-unicode-regex@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-create-regexp-features-plugin': 7.22.9(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/preset-env@7.18.6(@babel/core@7.18.6): + resolution: {integrity: sha512-WrthhuIIYKrEFAwttYzgRNQ5hULGmwTj+D6l7Zdfsv5M7IWV/OZbUfbeL++Qrzx1nVJwWROIFhCHRYQV4xbPNw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.22.9 + '@babel/core': 7.18.6 + '@babel/helper-compilation-targets': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-validator-option': 7.22.5 + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.18.6) + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.18.6) + '@babel/plugin-proposal-class-static-block': 7.21.0(@babel/core@7.18.6) + '@babel/plugin-proposal-dynamic-import': 7.18.6(@babel/core@7.18.6) + '@babel/plugin-proposal-export-namespace-from': 7.18.9(@babel/core@7.18.6) + '@babel/plugin-proposal-json-strings': 7.18.6(@babel/core@7.18.6) + '@babel/plugin-proposal-logical-assignment-operators': 7.20.7(@babel/core@7.18.6) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.18.6) + '@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.18.6) + '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.18.6) + '@babel/plugin-proposal-optional-catch-binding': 7.18.6(@babel/core@7.18.6) + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.18.6) + '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.18.6) + '@babel/plugin-proposal-private-property-in-object': 7.21.11(@babel/core@7.18.6) + '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.18.6) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.18.6) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.18.6) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.18.6) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-import-assertions': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.18.6) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.18.6) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.18.6) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.18.6) + '@babel/plugin-transform-arrow-functions': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-async-to-generator': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-block-scoped-functions': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-block-scoping': 7.22.10(@babel/core@7.18.6) + '@babel/plugin-transform-classes': 7.22.6(@babel/core@7.18.6) + '@babel/plugin-transform-computed-properties': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-destructuring': 7.22.10(@babel/core@7.18.6) + '@babel/plugin-transform-dotall-regex': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-duplicate-keys': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-exponentiation-operator': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-for-of': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-function-name': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-literals': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-member-expression-literals': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-modules-amd': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-modules-commonjs': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-modules-systemjs': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-modules-umd': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-new-target': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-object-super': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-parameters': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-property-literals': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-regenerator': 7.22.10(@babel/core@7.18.6) + '@babel/plugin-transform-reserved-words': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-shorthand-properties': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-spread': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-sticky-regex': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-template-literals': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-typeof-symbol': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-unicode-escapes': 7.22.10(@babel/core@7.18.6) + '@babel/plugin-transform-unicode-regex': 7.22.5(@babel/core@7.18.6) + '@babel/preset-modules': 0.1.6(@babel/core@7.18.6) + '@babel/types': 7.22.10 + babel-plugin-polyfill-corejs2: 0.3.3(@babel/core@7.18.6) + babel-plugin-polyfill-corejs3: 0.5.3(@babel/core@7.18.6) + babel-plugin-polyfill-regenerator: 0.3.1(@babel/core@7.18.6) + core-js-compat: 3.32.1 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/preset-modules@0.1.6(@babel/core@7.18.6): + resolution: {integrity: sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.18.6) + '@babel/plugin-transform-dotall-regex': 7.22.5(@babel/core@7.18.6) + '@babel/types': 7.22.10 + esutils: 2.0.3 + dev: true + + /@babel/regjsgen@0.8.0: + resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==} + dev: true + + /@babel/runtime@7.22.10: + resolution: {integrity: sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.14.0 + dev: true + + /@babel/template@7.22.5: + resolution: {integrity: sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.22.10 + '@babel/parser': 7.22.10 + '@babel/types': 7.22.10 + dev: true + + /@babel/traverse@7.22.10: + resolution: {integrity: sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.22.10 + '@babel/generator': 7.22.10 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-function-name': 7.22.5 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/parser': 7.22.10 + '@babel/types': 7.22.10 + debug: 4.3.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/types@7.22.10: + resolution: {integrity: sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.22.5 + '@babel/helper-validator-identifier': 7.22.5 + to-fast-properties: 2.0.0 + dev: true + + /@bcoe/v8-coverage@0.2.3: + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + dev: true + + /@cbor-extract/cbor-extract-darwin-arm64@2.1.1: + resolution: {integrity: sha512-blVBy5MXz6m36Vx0DfLd7PChOQKEs8lK2bD1WJn/vVgG4FXZiZmZb2GECHFvVPA5T7OnODd9xZiL3nMCv6QUhA==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@cbor-extract/cbor-extract-darwin-x64@2.1.1: + resolution: {integrity: sha512-h6KFOzqk8jXTvkOftyRIWGrd7sKQzQv2jVdTL9nKSf3D2drCvQB/LHUxAOpPXo3pv2clDtKs3xnHalpEh3rDsw==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@cbor-extract/cbor-extract-linux-arm64@2.1.1: + resolution: {integrity: sha512-SxAaRcYf8S0QHaMc7gvRSiTSr7nUYMqbUdErBEu+HYA4Q6UNydx1VwFE68hGcp1qvxcy9yT5U7gA+a5XikfwSQ==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@cbor-extract/cbor-extract-linux-arm@2.1.1: + resolution: {integrity: sha512-ds0uikdcIGUjPyraV4oJqyVE5gl/qYBpa/Wnh6l6xLE2lj/hwnjT2XcZCChdXwW/YFZ1LUHs6waoYN8PmK0nKQ==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@cbor-extract/cbor-extract-linux-x64@2.1.1: + resolution: {integrity: sha512-GVK+8fNIE9lJQHAlhOROYiI0Yd4bAZ4u++C2ZjlkS3YmO6hi+FUxe6Dqm+OKWTcMpL/l71N6CQAmaRcb4zyJuA==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@cbor-extract/cbor-extract-win32-x64@2.1.1: + resolution: {integrity: sha512-2Niq1C41dCRIDeD8LddiH+mxGlO7HJ612Ll3D/E73ZWBmycued+8ghTr/Ho3CMOWPUEr08XtyBMVXAjqF+TcKw==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@changesets/apply-release-plan@6.1.4: + resolution: {integrity: sha512-FMpKF1fRlJyCZVYHr3CbinpZZ+6MwvOtWUuO8uo+svcATEoc1zRDcj23pAurJ2TZ/uVz1wFHH6K3NlACy0PLew==} + dependencies: + '@babel/runtime': 7.22.10 + '@changesets/config': 2.3.1 + '@changesets/get-version-range-type': 0.3.2 + '@changesets/git': 2.0.0 + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.7.1 + resolve-from: 5.0.0 + semver: 7.5.4 + dev: true + + /@changesets/assemble-release-plan@5.2.4: + resolution: {integrity: sha512-xJkWX+1/CUaOUWTguXEbCDTyWJFECEhmdtbkjhn5GVBGxdP/JwaHBIU9sW3FR6gD07UwZ7ovpiPclQZs+j+mvg==} + dependencies: + '@babel/runtime': 7.22.10 + '@changesets/errors': 0.1.4 + '@changesets/get-dependents-graph': 1.3.6 + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + semver: 7.5.4 + dev: true + + /@changesets/changelog-git@0.1.14: + resolution: {integrity: sha512-+vRfnKtXVWsDDxGctOfzJsPhaCdXRYoe+KyWYoq5X/GqoISREiat0l3L8B0a453B2B4dfHGcZaGyowHbp9BSaA==} + dependencies: + '@changesets/types': 5.2.1 + dev: true + + /@changesets/changelog-github@0.4.8: + resolution: {integrity: sha512-jR1DHibkMAb5v/8ym77E4AMNWZKB5NPzw5a5Wtqm1JepAuIF+hrKp2u04NKM14oBZhHglkCfrla9uq8ORnK/dw==} + dependencies: + '@changesets/get-github-info': 0.5.2 + '@changesets/types': 5.2.1 + dotenv: 8.6.0 + transitivePeerDependencies: + - encoding + dev: true + + /@changesets/cli@2.26.2: + resolution: {integrity: sha512-dnWrJTmRR8bCHikJHl9b9HW3gXACCehz4OasrXpMp7sx97ECuBGGNjJhjPhdZNCvMy9mn4BWdplI323IbqsRig==} + hasBin: true + dependencies: + '@babel/runtime': 7.22.10 + '@changesets/apply-release-plan': 6.1.4 + '@changesets/assemble-release-plan': 5.2.4 + '@changesets/changelog-git': 0.1.14 + '@changesets/config': 2.3.1 + '@changesets/errors': 0.1.4 + '@changesets/get-dependents-graph': 1.3.6 + '@changesets/get-release-plan': 3.0.17 + '@changesets/git': 2.0.0 + '@changesets/logger': 0.0.5 + '@changesets/pre': 1.0.14 + '@changesets/read': 0.5.9 + '@changesets/types': 5.2.1 + '@changesets/write': 0.2.3 + '@manypkg/get-packages': 1.1.3 + '@types/is-ci': 3.0.0 + '@types/semver': 7.5.0 + ansi-colors: 4.1.3 + chalk: 2.4.2 + enquirer: 2.4.1 + external-editor: 3.1.0 + fs-extra: 7.0.1 + human-id: 1.0.2 + is-ci: 3.0.1 + meow: 6.1.1 + outdent: 0.5.0 + p-limit: 2.3.0 + preferred-pm: 3.0.3 + resolve-from: 5.0.0 + semver: 7.5.4 + spawndamnit: 2.0.0 + term-size: 2.2.1 + tty-table: 4.2.1 + dev: true + + /@changesets/config@2.3.1: + resolution: {integrity: sha512-PQXaJl82CfIXddUOppj4zWu+987GCw2M+eQcOepxN5s+kvnsZOwjEJO3DH9eVy+OP6Pg/KFEWdsECFEYTtbg6w==} + dependencies: + '@changesets/errors': 0.1.4 + '@changesets/get-dependents-graph': 1.3.6 + '@changesets/logger': 0.0.5 + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.5 + dev: true + + /@changesets/errors@0.1.4: + resolution: {integrity: sha512-HAcqPF7snsUJ/QzkWoKfRfXushHTu+K5KZLJWPb34s4eCZShIf8BFO3fwq6KU8+G7L5KdtN2BzQAXOSXEyiY9Q==} + dependencies: + extendable-error: 0.1.7 + dev: true + + /@changesets/get-dependents-graph@1.3.6: + resolution: {integrity: sha512-Q/sLgBANmkvUm09GgRsAvEtY3p1/5OCzgBE5vX3vgb5CvW0j7CEljocx5oPXeQSNph6FXulJlXV3Re/v3K3P3Q==} + dependencies: + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + chalk: 2.4.2 + fs-extra: 7.0.1 + semver: 7.5.4 + dev: true + + /@changesets/get-github-info@0.5.2: + resolution: {integrity: sha512-JppheLu7S114aEs157fOZDjFqUDpm7eHdq5E8SSR0gUBTEK0cNSHsrSR5a66xs0z3RWuo46QvA3vawp8BxDHvg==} + dependencies: + dataloader: 1.4.0 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + dev: true + + /@changesets/get-release-plan@3.0.17: + resolution: {integrity: sha512-6IwKTubNEgoOZwDontYc2x2cWXfr6IKxP3IhKeK+WjyD6y3M4Gl/jdQvBw+m/5zWILSOCAaGLu2ZF6Q+WiPniw==} + dependencies: + '@babel/runtime': 7.22.10 + '@changesets/assemble-release-plan': 5.2.4 + '@changesets/config': 2.3.1 + '@changesets/pre': 1.0.14 + '@changesets/read': 0.5.9 + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + dev: true + + /@changesets/get-version-range-type@0.3.2: + resolution: {integrity: sha512-SVqwYs5pULYjYT4op21F2pVbcrca4qA/bAA3FmFXKMN7Y+HcO8sbZUTx3TAy2VXulP2FACd1aC7f2nTuqSPbqg==} + dev: true + + /@changesets/git@2.0.0: + resolution: {integrity: sha512-enUVEWbiqUTxqSnmesyJGWfzd51PY4H7mH9yUw0hPVpZBJ6tQZFMU3F3mT/t9OJ/GjyiM4770i+sehAn6ymx6A==} + dependencies: + '@babel/runtime': 7.22.10 + '@changesets/errors': 0.1.4 + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.5 + spawndamnit: 2.0.0 + dev: true + + /@changesets/logger@0.0.5: + resolution: {integrity: sha512-gJyZHomu8nASHpaANzc6bkQMO9gU/ib20lqew1rVx753FOxffnCrJlGIeQVxNWCqM+o6OOleCo/ivL8UAO5iFw==} + dependencies: + chalk: 2.4.2 + dev: true + + /@changesets/parse@0.3.16: + resolution: {integrity: sha512-127JKNd167ayAuBjUggZBkmDS5fIKsthnr9jr6bdnuUljroiERW7FBTDNnNVyJ4l69PzR57pk6mXQdtJyBCJKg==} + dependencies: + '@changesets/types': 5.2.1 + js-yaml: 3.14.1 + dev: true + + /@changesets/pre@1.0.14: + resolution: {integrity: sha512-dTsHmxQWEQekHYHbg+M1mDVYFvegDh9j/kySNuDKdylwfMEevTeDouR7IfHNyVodxZXu17sXoJuf2D0vi55FHQ==} + dependencies: + '@babel/runtime': 7.22.10 + '@changesets/errors': 0.1.4 + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + dev: true + + /@changesets/read@0.5.9: + resolution: {integrity: sha512-T8BJ6JS6j1gfO1HFq50kU3qawYxa4NTbI/ASNVVCBTsKquy2HYwM9r7ZnzkiMe8IEObAJtUVGSrePCOxAK2haQ==} + dependencies: + '@babel/runtime': 7.22.10 + '@changesets/git': 2.0.0 + '@changesets/logger': 0.0.5 + '@changesets/parse': 0.3.16 + '@changesets/types': 5.2.1 + chalk: 2.4.2 + fs-extra: 7.0.1 + p-filter: 2.1.0 + dev: true + + /@changesets/types@4.1.0: + resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} + dev: true + + /@changesets/types@5.2.1: + resolution: {integrity: sha512-myLfHbVOqaq9UtUKqR/nZA/OY7xFjQMdfgfqeZIBK4d0hA6pgxArvdv8M+6NUzzBsjWLOtvApv8YHr4qM+Kpfg==} + dev: true + + /@changesets/write@0.2.3: + resolution: {integrity: sha512-Dbamr7AIMvslKnNYsLFafaVORx4H0pvCA2MHqgtNCySMe1blImEyAEOzDmcgKAkgz4+uwoLz7demIrX+JBr/Xw==} + dependencies: + '@babel/runtime': 7.22.10 + '@changesets/types': 5.2.1 + fs-extra: 7.0.1 + human-id: 1.0.2 + prettier: 2.7.1 + dev: true + + /@cspotcode/source-map-support@0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + dev: true + + /@datadog/native-appsec@2.0.0: + resolution: {integrity: sha512-XHARZ6MVgbnfOUO6/F3ZoZ7poXHJCNYFlgcyS2Xetuk9ITA5bfcooX2B2F7tReVB+RLJ+j8bsm0t55SyF04KDw==} + engines: {node: '>=12'} + requiresBuild: true + dependencies: + node-gyp-build: 3.9.0 + dev: false + + /@datadog/native-iast-rewriter@1.1.2: + resolution: {integrity: sha512-pigRfRtAjZjMjqIXyXb98S4aDnuHz/EmqpoxAajFZsNjBLM87YonwSY5zoBdCsOyA46ddKOJRoCQd5ZalpOFMQ==} + engines: {node: '>= 10'} + dependencies: + node-gyp-build: 4.6.1 + dev: false + + /@datadog/native-iast-taint-tracking@1.1.0: + resolution: {integrity: sha512-TOrngpt6Qh52zWFOz1CkFXw0g43rnuUziFBtIMUsOLGzSHr9wdnTnE6HAyuvKy3f3ecAoZESlMfilGRKP93hXQ==} + dependencies: + node-gyp-build: 3.9.0 + dev: false + + /@datadog/native-metrics@1.6.0: + resolution: {integrity: sha512-+8jBzd0nlLV+ay3Vb87DLwz8JHAS817hRhSRQ6zxhud9TyvvcNTNN+VA2sb2fe5UK4aMDvj/sGVJjEtgr4RHew==} + engines: {node: '>=12'} + requiresBuild: true + dependencies: + node-gyp-build: 3.9.0 + dev: false + + /@datadog/pprof@1.1.1: + resolution: {integrity: sha512-5lYXUpikQhrJwzODtJ7aFM0oKmPccISnTCecuWhjxIj4/7UJv0DamkLak634bgEW+kiChgkKFDapHSesuXRDXQ==} + engines: {node: '>=12'} + requiresBuild: true + dependencies: + delay: 5.0.0 + findit2: 2.2.3 + node-gyp-build: 3.9.0 + p-limit: 3.1.0 + pify: 5.0.0 + protobufjs: 7.2.5 + source-map: 0.7.4 + split: 1.0.1 + dev: false + + /@datadog/sketches-js@2.1.0: + resolution: {integrity: sha512-smLocSfrt3s53H/XSVP3/1kP42oqvrkjUPtyaFd1F79ux24oE31BKt+q0c6lsa6hOYrFzsIwyc5GXAI5JmfOew==} + dev: false + + /@did-plc/lib@0.0.1: + resolution: {integrity: sha512-RkY5w9DbYMco3SjeepqIiMveqz35exjlVDipCs2gz9AXF4/cp9hvmrp9zUWEw2vny+FjV8vGEN7QpaXWaO6nhg==} + dependencies: + '@atproto/common': 0.1.0 + '@atproto/crypto': 0.1.0 + '@ipld/dag-cbor': 7.0.3 + axios: 1.4.0 + multiformats: 9.9.0 + uint8arrays: 3.0.0 + zod: 3.21.4 + transitivePeerDependencies: + - debug + + /@did-plc/server@0.0.1: + resolution: {integrity: sha512-GtxxHcOrOQ6fNI1ufq3Zqjc2PtWqPZOdsuzlwtxiH9XibUGwDkb0GmaBHyU5GiOxOKZEW1GspZ8mreBA6XOlTQ==} + dependencies: + '@atproto/common': 0.1.0 + '@atproto/crypto': 0.1.0 + '@did-plc/lib': 0.0.1 + axios: 1.4.0 + cors: 2.8.5 + express: 4.18.2 + express-async-errors: 3.1.1(express@4.18.2) + http-terminator: 3.2.0 + kysely: 0.23.5 + multiformats: 9.9.0 + pg: 8.10.0 + pino: 8.15.0 + pino-http: 8.4.0 + transitivePeerDependencies: + - debug + - pg-native + - supports-color + + /@eslint/eslintrc@1.4.1: + resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.6.1 + globals: 13.21.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@fastify/deepmerge@1.3.0: + resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==} + + /@gar/promisify@1.1.3: + resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} + dev: true + + /@humanwhocodes/config-array@0.10.7: + resolution: {integrity: sha512-MDl6D6sBsaV452/QSdX+4CXIjZhIcI0PELsxUjk4U828yd58vk3bTIvk/6w5FY+4hIy9sLW0sfrV7K7Kc++j/w==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/gitignore-to-minimatch@1.0.2: + resolution: {integrity: sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==} + dev: true + + /@humanwhocodes/module-importer@1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true + + /@humanwhocodes/object-schema@1.2.1: + resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + dev: true + + /@ioredis/commands@1.2.0: + resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} + dev: false + + /@ipld/car@3.2.3: + resolution: {integrity: sha512-pXE5mFJlXzJVaBwqAJKGlKqMmxq8H2SLEWBJgkeBDPBIN8ZbscPc3I9itkSQSlS/s6Fgx35Ri3LDTDtodQjCCQ==} + dependencies: + '@ipld/dag-cbor': 7.0.3 + multiformats: 9.9.0 + varint: 6.0.0 + dev: false + + /@ipld/dag-cbor@7.0.3: + resolution: {integrity: sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA==} + dependencies: + cborg: 1.10.2 + multiformats: 9.9.0 + + /@isaacs/ttlcache@1.4.1: + resolution: {integrity: sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==} + engines: {node: '>=12'} + dev: false + + /@istanbuljs/load-nyc-config@1.1.0: + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + dev: true + + /@istanbuljs/schema@0.1.3: + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + dev: true + + /@jest/console@28.1.3: + resolution: {integrity: sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/types': 28.1.3 + '@types/node': 18.17.8 + chalk: 4.1.2 + jest-message-util: 28.1.3 + jest-util: 28.1.3 + slash: 3.0.0 + dev: true + + /@jest/core@28.1.3(ts-node@10.8.2): + resolution: {integrity: sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/console': 28.1.3 + '@jest/reporters': 28.1.3 + '@jest/test-result': 28.1.3 + '@jest/transform': 28.1.3 + '@jest/types': 28.1.3 + '@types/node': 18.17.8 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.8.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 28.1.3 + jest-config: 28.1.3(@types/node@18.17.8)(ts-node@10.8.2) + jest-haste-map: 28.1.3 + jest-message-util: 28.1.3 + jest-regex-util: 28.0.2 + jest-resolve: 28.1.3 + jest-resolve-dependencies: 28.1.3 + jest-runner: 28.1.3 + jest-runtime: 28.1.3 + jest-snapshot: 28.1.3 + jest-util: 28.1.3 + jest-validate: 28.1.3 + jest-watcher: 28.1.3 + micromatch: 4.0.5 + pretty-format: 28.1.3 + rimraf: 3.0.2 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - supports-color + - ts-node + dev: true + + /@jest/create-cache-key-function@27.5.1: + resolution: {integrity: sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/types': 27.5.1 + dev: true + + /@jest/environment@28.1.3: + resolution: {integrity: sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/fake-timers': 28.1.3 + '@jest/types': 28.1.3 + '@types/node': 18.17.8 + jest-mock: 28.1.3 + dev: true + + /@jest/expect-utils@28.1.3: + resolution: {integrity: sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + jest-get-type: 28.0.2 + dev: true + + /@jest/expect@28.1.3: + resolution: {integrity: sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + expect: 28.1.3 + jest-snapshot: 28.1.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/fake-timers@28.1.3: + resolution: {integrity: sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/types': 28.1.3 + '@sinonjs/fake-timers': 9.1.2 + '@types/node': 18.17.8 + jest-message-util: 28.1.3 + jest-mock: 28.1.3 + jest-util: 28.1.3 + dev: true + + /@jest/globals@28.1.3: + resolution: {integrity: sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/environment': 28.1.3 + '@jest/expect': 28.1.3 + '@jest/types': 28.1.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/reporters@28.1.3: + resolution: {integrity: sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 28.1.3 + '@jest/test-result': 28.1.3 + '@jest/transform': 28.1.3 + '@jest/types': 28.1.3 + '@jridgewell/trace-mapping': 0.3.19 + '@types/node': 18.17.8 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.0 + istanbul-lib-instrument: 5.2.1 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.6 + jest-message-util: 28.1.3 + jest-util: 28.1.3 + jest-worker: 28.1.3 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + terminal-link: 2.1.1 + v8-to-istanbul: 9.1.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/schemas@28.1.3: + resolution: {integrity: sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@sinclair/typebox': 0.24.51 + dev: true + + /@jest/source-map@28.1.2: + resolution: {integrity: sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jridgewell/trace-mapping': 0.3.19 + callsites: 3.1.0 + graceful-fs: 4.2.11 + dev: true + + /@jest/test-result@28.1.3: + resolution: {integrity: sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/console': 28.1.3 + '@jest/types': 28.1.3 + '@types/istanbul-lib-coverage': 2.0.4 + collect-v8-coverage: 1.0.2 + dev: true + + /@jest/test-sequencer@28.1.3: + resolution: {integrity: sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/test-result': 28.1.3 + graceful-fs: 4.2.11 + jest-haste-map: 28.1.3 + slash: 3.0.0 + dev: true + + /@jest/transform@28.1.3: + resolution: {integrity: sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@babel/core': 7.18.6 + '@jest/types': 28.1.3 + '@jridgewell/trace-mapping': 0.3.19 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 1.9.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 28.1.3 + jest-regex-util: 28.0.2 + jest-util: 28.1.3 + micromatch: 4.0.5 + pirates: 4.0.6 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/types@27.5.1: + resolution: {integrity: sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 3.0.1 + '@types/node': 18.17.8 + '@types/yargs': 16.0.5 + chalk: 4.1.2 + dev: true + + /@jest/types@28.1.3: + resolution: {integrity: sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/schemas': 28.1.3 + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 3.0.1 + '@types/node': 18.17.8 + '@types/yargs': 17.0.24 + chalk: 4.1.2 + dev: true + + /@jridgewell/gen-mapping@0.3.3: + resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.19 + dev: true + + /@jridgewell/resolve-uri@3.1.1: + resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/set-array@1.1.2: + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: true + + /@jridgewell/trace-mapping@0.3.19: + resolution: {integrity: sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==} + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /@jridgewell/trace-mapping@0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /@manypkg/find-root@1.1.0: + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + dependencies: + '@babel/runtime': 7.22.10 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 + dev: true + + /@manypkg/get-packages@1.1.3: + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + dependencies: + '@babel/runtime': 7.22.10 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 + dev: true + + /@noble/curves@1.1.0: + resolution: {integrity: sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==} + dependencies: + '@noble/hashes': 1.3.1 + dev: false + + /@noble/hashes@1.3.1: + resolution: {integrity: sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==} + engines: {node: '>= 16'} + dev: false + + /@noble/secp256k1@1.7.1: + resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + + /@npmcli/fs@2.1.2: + resolution: {integrity: sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + '@gar/promisify': 1.1.3 + semver: 7.5.4 + dev: true + + /@npmcli/move-file@2.0.1: + resolution: {integrity: sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This functionality has been moved to @npmcli/fs + dependencies: + mkdirp: 1.0.4 + rimraf: 3.0.2 + dev: true + + /@npmcli/package-json@3.0.0: + resolution: {integrity: sha512-NnuPuM97xfiCpbTEJYtEuKz6CFbpUHtaT0+5via5pQeI25omvQDFbp1GcGJ/c4zvL/WX0qbde6YiLgfZbWFgvg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + json-parse-even-better-errors: 3.0.0 + dev: true + + /@protobufjs/aspromise@1.1.2: + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + dev: false + + /@protobufjs/base64@1.1.2: + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + dev: false + + /@protobufjs/codegen@2.0.4: + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + dev: false + + /@protobufjs/eventemitter@1.1.0: + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + dev: false + + /@protobufjs/fetch@1.1.0: + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + dev: false + + /@protobufjs/float@1.0.2: + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + dev: false + + /@protobufjs/inquire@1.1.0: + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + dev: false + + /@protobufjs/path@1.1.2: + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + dev: false + + /@protobufjs/pool@1.1.0: + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + dev: false + + /@protobufjs/utf8@1.1.0: + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + dev: false + + /@sinclair/typebox@0.24.51: + resolution: {integrity: sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==} + dev: true + + /@sinonjs/commons@1.8.6: + resolution: {integrity: sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==} + dependencies: + type-detect: 4.0.8 + dev: true + + /@sinonjs/fake-timers@9.1.2: + resolution: {integrity: sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==} + dependencies: + '@sinonjs/commons': 1.8.6 + dev: true + + /@smithy/abort-controller@1.1.0: + resolution: {integrity: sha512-5imgGUlZL4dW4YWdMYAKLmal9ny/tlenM81QZY7xYyb76z9Z/QOg7oM5Ak9HQl8QfFTlGVWwcMXl+54jroRgEQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.2.0 + tslib: 2.6.2 + dev: false + + /@smithy/types@1.2.0: + resolution: {integrity: sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@swc/core-darwin-arm64@1.3.42: + resolution: {integrity: sha512-hM6RrZFyoCM9mX3cj/zM5oXwhAqjUdOCLXJx7KTQps7NIkv/Qjvobgvyf2gAb89j3ARNo9NdIoLjTjJ6oALtiA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@swc/core-darwin-x64@1.3.42: + resolution: {integrity: sha512-bjsWtHMb6wJK1+RGlBs2USvgZ0txlMk11y0qBLKo32gLKTqzUwRw0Fmfzuf6Ue2a/w//7eqMlPFEre4LvJajGw==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-arm-gnueabihf@1.3.42: + resolution: {integrity: sha512-Oe0ggMz3MyqXNfeVmY+bBTL0hFSNY3bx8dhcqsh4vXk/ZVGse94QoC4dd92LuPHmKT0x6nsUzB86x2jU9QHW5g==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-arm64-gnu@1.3.42: + resolution: {integrity: sha512-ZJsa8NIW1RLmmHGTJCbM7OPSbBZ9rOMrLqDtUOGrT0uoJXZnnQqolflamB5wviW0X6h3Z3/PSTNGNDCJ3u3Lqg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-arm64-musl@1.3.42: + resolution: {integrity: sha512-YpZwlFAfOp5vkm/uVUJX1O7N3yJDO1fDQRWqsOPPNyIJkI2ydlRQtgN6ZylC159Qv+TimfXnGTlNr7o3iBAqjg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-x64-gnu@1.3.42: + resolution: {integrity: sha512-0ccpKnsZbyHBzaQFdP8U9i29nvOfKitm6oJfdJzlqsY/jCqwvD8kv2CAKSK8WhJz//ExI2LqNrDI0yazx5j7+A==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-x64-musl@1.3.42: + resolution: {integrity: sha512-7eckRRuTZ6+3K21uyfXXgc2ZCg0mSWRRNwNT3wap2bYkKPeqTgb8pm8xYSZNEiMuDonHEat6XCCV36lFY6kOdQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-win32-arm64-msvc@1.3.42: + resolution: {integrity: sha512-t27dJkdw0GWANdN4TV0lY/V5vTYSx5SRjyzzZolep358ueCGuN1XFf1R0JcCbd1ojosnkQg2L7A7991UjXingg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core-win32-ia32-msvc@1.3.42: + resolution: {integrity: sha512-xfpc/Zt/aMILX4IX0e3loZaFyrae37u3MJCv1gJxgqrpeLi7efIQr3AmERkTK3mxTO6R5urSliWw2W3FyZ7D3Q==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core-win32-x64-msvc@1.3.42: + resolution: {integrity: sha512-ra2K4Tu++EJLPhzZ6L8hWUsk94TdK/2UKhL9dzCBhtzKUixsGCEqhtqH1zISXNvW8qaVLFIMUP37ULe80/IJaA==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core@1.3.42: + resolution: {integrity: sha512-nVFUd5+7tGniM2cT3LXaqnu3735Cu4az8A9gAKK+8sdpASI52SWuqfDBmjFCK9xG90MiVDVp2PTZr0BWqCIzpw==} + engines: {node: '>=10'} + requiresBuild: true + optionalDependencies: + '@swc/core-darwin-arm64': 1.3.42 + '@swc/core-darwin-x64': 1.3.42 + '@swc/core-linux-arm-gnueabihf': 1.3.42 + '@swc/core-linux-arm64-gnu': 1.3.42 + '@swc/core-linux-arm64-musl': 1.3.42 + '@swc/core-linux-x64-gnu': 1.3.42 + '@swc/core-linux-x64-musl': 1.3.42 + '@swc/core-win32-arm64-msvc': 1.3.42 + '@swc/core-win32-ia32-msvc': 1.3.42 + '@swc/core-win32-x64-msvc': 1.3.42 + dev: true + + /@swc/jest@0.2.24(@swc/core@1.3.42): + resolution: {integrity: sha512-fwgxQbM1wXzyKzl1+IW0aGrRvAA8k0Y3NxFhKigbPjOJ4mCKnWEcNX9HQS3gshflcxq8YKhadabGUVfdwjCr6Q==} + engines: {npm: '>= 7.0.0'} + peerDependencies: + '@swc/core': '*' + dependencies: + '@jest/create-cache-key-function': 27.5.1 + '@swc/core': 1.3.42 + jsonc-parser: 3.2.0 + dev: true + + /@tokenizer/token@0.3.0: + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + dev: false + + /@tootallnate/once@2.0.0: + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + dev: true + + /@ts-morph/common@0.17.0: + resolution: {integrity: sha512-RMSSvSfs9kb0VzkvQ2NWobwnj7TxCA9vI/IjR9bDHqgAyVbu2T0DN4wiKVqomyDWqO7dPr/tErSfq7urQ1Q37g==} + dependencies: + fast-glob: 3.3.1 + minimatch: 5.1.6 + mkdirp: 1.0.4 + path-browserify: 1.0.1 + dev: false + + /@tsconfig/node10@1.0.9: + resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + dev: true + + /@tsconfig/node12@1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true + + /@tsconfig/node14@1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true + + /@tsconfig/node16@1.0.4: + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + dev: true + + /@types/babel__core@7.20.1: + resolution: {integrity: sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==} + dependencies: + '@babel/parser': 7.22.10 + '@babel/types': 7.22.10 + '@types/babel__generator': 7.6.4 + '@types/babel__template': 7.4.1 + '@types/babel__traverse': 7.20.1 + dev: true + + /@types/babel__generator@7.6.4: + resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} + dependencies: + '@babel/types': 7.22.10 + dev: true + + /@types/babel__template@7.4.1: + resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} + dependencies: + '@babel/parser': 7.22.10 + '@babel/types': 7.22.10 + dev: true + + /@types/babel__traverse@7.20.1: + resolution: {integrity: sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==} + dependencies: + '@babel/types': 7.22.10 + dev: true + + /@types/bn.js@5.1.1: + resolution: {integrity: sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==} + dependencies: + '@types/node': 18.17.8 + dev: false + + /@types/body-parser@1.19.2: + resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} + dependencies: + '@types/connect': 3.4.35 + '@types/node': 18.17.8 + dev: true + + /@types/connect@3.4.35: + resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} + dependencies: + '@types/node': 18.17.8 + dev: true + + /@types/cors@2.8.12: + resolution: {integrity: sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==} + dev: true + + /@types/elliptic@6.4.14: + resolution: {integrity: sha512-z4OBcDAU0GVwDTuwJzQCiL6188QvZMkvoERgcVjq0/mPM8jCfdwZ3x5zQEVoL9WCAru3aG5wl3Z5Ww5wBWn7ZQ==} + dependencies: + '@types/bn.js': 5.1.1 + dev: false + + /@types/express-serve-static-core@4.17.36: + resolution: {integrity: sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==} + dependencies: + '@types/node': 18.17.8 + '@types/qs': 6.9.7 + '@types/range-parser': 1.2.4 + '@types/send': 0.17.1 + dev: true + + /@types/express@4.17.13: + resolution: {integrity: sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==} + dependencies: + '@types/body-parser': 1.19.2 + '@types/express-serve-static-core': 4.17.36 + '@types/qs': 6.9.7 + '@types/serve-static': 1.15.2 + dev: true + + /@types/graceful-fs@4.1.6: + resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==} + dependencies: + '@types/node': 18.17.8 + dev: true + + /@types/http-errors@2.0.1: + resolution: {integrity: sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==} + dev: true + + /@types/is-ci@3.0.0: + resolution: {integrity: sha512-Q0Op0hdWbYd1iahB+IFNQcWXFq4O0Q5MwQP7uN0souuQ4rPg1vEYcnIOfr1gY+M+6rc8FGoRaBO1mOOvL29sEQ==} + dependencies: + ci-info: 3.8.0 + dev: true + + /@types/istanbul-lib-coverage@2.0.4: + resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} + dev: true + + /@types/istanbul-lib-report@3.0.0: + resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==} + dependencies: + '@types/istanbul-lib-coverage': 2.0.4 + dev: true + + /@types/istanbul-reports@3.0.1: + resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==} + dependencies: + '@types/istanbul-lib-report': 3.0.0 + dev: true + + /@types/jest@28.1.4: + resolution: {integrity: sha512-telv6G5N7zRJiLcI3Rs3o+ipZ28EnE+7EvF0pSrt2pZOMnAVI/f+6/LucDxOvcBcTeTL3JMF744BbVQAVBUQRA==} + dependencies: + jest-matcher-utils: 28.1.3 + pretty-format: 28.1.3 + dev: true + + /@types/json-schema@7.0.12: + resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} + dev: true + + /@types/jsonwebtoken@8.5.9: + resolution: {integrity: sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg==} + dependencies: + '@types/node': 18.17.8 + dev: true + + /@types/mime@1.3.2: + resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} + dev: true + + /@types/mime@3.0.1: + resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==} + dev: true + + /@types/minimist@1.2.2: + resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} + dev: true + + /@types/node@12.20.55: + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + dev: true + + /@types/node@18.0.0: + resolution: {integrity: sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==} + dev: true + + /@types/node@18.17.8: + resolution: {integrity: sha512-Av/7MqX/iNKwT9Tr60V85NqMnsmh8ilfJoBlIVibkXfitk9Q22D9Y5mSpm+FvG5DET7EbVfB40bOiLzKgYFgPw==} + + /@types/nodemailer@6.4.6: + resolution: {integrity: sha512-pD6fL5GQtUKvD2WnPmg5bC2e8kWCAPDwMPmHe/ohQbW+Dy0EcHgZ2oCSuPlWNqk74LS5BVMig1SymQbFMPPK3w==} + dependencies: + '@types/node': 18.17.8 + dev: true + + /@types/normalize-package-data@2.4.1: + resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} + dev: true + + /@types/pg@8.6.6: + resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==} + dependencies: + '@types/node': 18.17.8 + pg-protocol: 1.6.0 + pg-types: 2.2.0 + dev: true + + /@types/prettier@2.7.3: + resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==} + dev: true + + /@types/qs@6.9.7: + resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} + dev: true + + /@types/range-parser@1.2.4: + resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} + dev: true + + /@types/semver@7.5.0: + resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} + dev: true + + /@types/send@0.17.1: + resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==} + dependencies: + '@types/mime': 1.3.2 + '@types/node': 18.17.8 + dev: true + + /@types/serve-static@1.15.2: + resolution: {integrity: sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==} + dependencies: + '@types/http-errors': 2.0.1 + '@types/mime': 3.0.1 + '@types/node': 18.17.8 + dev: true + + /@types/sharp@0.31.0: + resolution: {integrity: sha512-nwivOU101fYInCwdDcH/0/Ru6yIRXOpORx25ynEOc6/IakuCmjOAGpaO5VfUl4QkDtUC6hj+Z2eCQvgXOioknw==} + dependencies: + '@types/node': 18.17.8 + dev: true + + /@types/stack-utils@2.0.1: + resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} + dev: true + + /@types/ws@8.5.4: + resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} + dependencies: + '@types/node': 18.17.8 + dev: true + + /@types/yargs-parser@21.0.0: + resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} + dev: true + + /@types/yargs@16.0.5: + resolution: {integrity: sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==} + dependencies: + '@types/yargs-parser': 21.0.0 + dev: true + + /@types/yargs@17.0.24: + resolution: {integrity: sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==} + dependencies: + '@types/yargs-parser': 21.0.0 + dev: true + + /@typescript-eslint/eslint-plugin@5.38.1(@typescript-eslint/parser@5.38.1)(eslint@8.24.0)(typescript@4.8.4): + resolution: {integrity: sha512-ky7EFzPhqz3XlhS7vPOoMDaQnQMn+9o5ICR9CPr/6bw8HrFkzhMSxuA3gRfiJVvs7geYrSeawGJjZoZQKCOglQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/parser': 5.38.1(eslint@8.24.0)(typescript@4.8.4) + '@typescript-eslint/scope-manager': 5.38.1 + '@typescript-eslint/type-utils': 5.38.1(eslint@8.24.0)(typescript@4.8.4) + '@typescript-eslint/utils': 5.38.1(eslint@8.24.0)(typescript@4.8.4) + debug: 4.3.4 + eslint: 8.24.0 + ignore: 5.2.4 + regexpp: 3.2.0 + semver: 7.5.4 + tsutils: 3.21.0(typescript@4.8.4) + typescript: 4.8.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/parser@5.38.1(eslint@8.24.0)(typescript@4.8.4): + resolution: {integrity: sha512-LDqxZBVFFQnQRz9rUZJhLmox+Ep5kdUmLatLQnCRR6523YV+XhRjfYzStQ4MheFA8kMAfUlclHSbu+RKdRwQKw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.38.1 + '@typescript-eslint/types': 5.38.1 + '@typescript-eslint/typescript-estree': 5.38.1(typescript@4.8.4) + debug: 4.3.4 + eslint: 8.24.0 + typescript: 4.8.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/scope-manager@5.38.1: + resolution: {integrity: sha512-BfRDq5RidVU3RbqApKmS7RFMtkyWMM50qWnDAkKgQiezRtLKsoyRKIvz1Ok5ilRWeD9IuHvaidaLxvGx/2eqTQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.38.1 + '@typescript-eslint/visitor-keys': 5.38.1 + dev: true + + /@typescript-eslint/type-utils@5.38.1(eslint@8.24.0)(typescript@4.8.4): + resolution: {integrity: sha512-UU3j43TM66gYtzo15ivK2ZFoDFKKP0k03MItzLdq0zV92CeGCXRfXlfQX5ILdd4/DSpHkSjIgLLLh1NtkOJOAw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 5.38.1(typescript@4.8.4) + '@typescript-eslint/utils': 5.38.1(eslint@8.24.0)(typescript@4.8.4) + debug: 4.3.4 + eslint: 8.24.0 + tsutils: 3.21.0(typescript@4.8.4) + typescript: 4.8.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/types@5.38.1: + resolution: {integrity: sha512-QTW1iHq1Tffp9lNfbfPm4WJabbvpyaehQ0SrvVK2yfV79SytD9XDVxqiPvdrv2LK7DGSFo91TB2FgWanbJAZXg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@typescript-eslint/typescript-estree@5.38.1(typescript@4.8.4): + resolution: {integrity: sha512-99b5e/Enoe8fKMLdSuwrfH/C0EIbpUWmeEKHmQlGZb8msY33qn1KlkFww0z26o5Omx7EVjzVDCWEfrfCDHfE7g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.38.1 + '@typescript-eslint/visitor-keys': 5.38.1 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.5.4 + tsutils: 3.21.0(typescript@4.8.4) + typescript: 4.8.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/utils@5.38.1(eslint@8.24.0)(typescript@4.8.4): + resolution: {integrity: sha512-oIuUiVxPBsndrN81oP8tXnFa/+EcZ03qLqPDfSZ5xIJVm7A9V0rlkQwwBOAGtrdN70ZKDlKv+l1BeT4eSFxwXA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@types/json-schema': 7.0.12 + '@typescript-eslint/scope-manager': 5.38.1 + '@typescript-eslint/types': 5.38.1 + '@typescript-eslint/typescript-estree': 5.38.1(typescript@4.8.4) + eslint: 8.24.0 + eslint-scope: 5.1.1 + eslint-utils: 3.0.0(eslint@8.24.0) + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/visitor-keys@5.38.1: + resolution: {integrity: sha512-bSHr1rRxXt54+j2n4k54p4fj8AHJ49VDWtjpImOpzQj4qjAiOpPni+V1Tyajh19Api1i844F757cur8wH3YvOA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.38.1 + eslint-visitor-keys: 3.4.3 + dev: true + + /abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + dev: true + + /abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + dependencies: + event-target-shim: 5.0.1 + + /accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + /acorn-import-assertions@1.9.0(acorn@8.10.0): + resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} + peerDependencies: + acorn: ^8 + dependencies: + acorn: 8.10.0 + dev: false + + /acorn-jsx@5.3.2(acorn@8.10.0): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.10.0 + dev: true + + /acorn-walk@8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} + dev: true + + /acorn@8.10.0: + resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} + engines: {node: '>=0.4.0'} + hasBin: true + + /agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + + /agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} + dependencies: + humanize-ms: 1.2.1 + dev: true + + /aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + dev: true + + /ajv-formats@2.1.1(ajv@8.12.0): + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + dependencies: + ajv: 8.12.0 + + /ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: true + + /ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + /ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + dev: true + + /ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.21.3 + dev: true + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + dev: true + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /aproba@2.0.0: + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + dev: true + + /are-we-there-yet@3.0.1: + resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + dev: true + + /arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true + + /argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + dependencies: + sprintf-js: 1.0.3 + dev: true + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /array-buffer-byte-length@1.0.0: + resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + dependencies: + call-bind: 1.0.2 + is-array-buffer: 3.0.2 + dev: true + + /array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + /array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true + + /array.prototype.flat@1.3.1: + resolution: {integrity: sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + es-shim-unscopables: 1.0.0 + dev: true + + /arraybuffer.prototype.slice@1.0.1: + resolution: {integrity: sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.0 + call-bind: 1.0.2 + define-properties: 1.2.0 + get-intrinsic: 1.2.1 + is-array-buffer: 3.0.2 + is-shared-array-buffer: 1.0.2 + dev: true + + /arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + dev: true + + /asn1.js@5.4.1: + resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} + dependencies: + bn.js: 4.12.0 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + safer-buffer: 2.1.2 + dev: false + + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + /atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + /available-typed-arrays@1.0.5: + resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} + engines: {node: '>= 0.4'} + dev: true + + /axios@0.27.2: + resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} + dependencies: + follow-redirects: 1.15.2 + form-data: 4.0.0 + transitivePeerDependencies: + - debug + + /axios@1.4.0: + resolution: {integrity: sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==} + dependencies: + follow-redirects: 1.15.2 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + /babel-eslint@10.1.0(eslint@8.24.0): + resolution: {integrity: sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==} + engines: {node: '>=6'} + deprecated: babel-eslint is now @babel/eslint-parser. This package will no longer receive updates. + peerDependencies: + eslint: '>= 4.12.1' + dependencies: + '@babel/code-frame': 7.22.10 + '@babel/parser': 7.22.10 + '@babel/traverse': 7.22.10 + '@babel/types': 7.22.10 + eslint: 8.24.0 + eslint-visitor-keys: 1.3.0 + resolve: 1.22.4 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-jest@28.1.3(@babel/core@7.18.6): + resolution: {integrity: sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + dependencies: + '@babel/core': 7.18.6 + '@jest/transform': 28.1.3 + '@types/babel__core': 7.20.1 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 28.1.3(@babel/core@7.18.6) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + dependencies: + '@babel/helper-plugin-utils': 7.22.5 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-jest-hoist@28.1.3: + resolution: {integrity: sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@babel/template': 7.22.5 + '@babel/types': 7.22.10 + '@types/babel__core': 7.20.1 + '@types/babel__traverse': 7.20.1 + dev: true + + /babel-plugin-polyfill-corejs2@0.3.3(@babel/core@7.18.6): + resolution: {integrity: sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.22.9 + '@babel/core': 7.18.6 + '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.18.6) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-polyfill-corejs3@0.5.3(@babel/core@7.18.6): + resolution: {integrity: sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.18.6) + core-js-compat: 3.32.1 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-polyfill-regenerator@0.3.1(@babel/core@7.18.6): + resolution: {integrity: sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.18.6) + transitivePeerDependencies: + - supports-color + dev: true + + /babel-preset-current-node-syntax@1.0.1(@babel/core@7.18.6): + resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.18.6) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.18.6) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.18.6) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.18.6) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.18.6) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.18.6) + dev: true + + /babel-preset-jest@28.1.3(@babel/core@7.18.6): + resolution: {integrity: sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + babel-plugin-jest-hoist: 28.1.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.18.6) + dev: true + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + /better-path-resolve@1.0.0: + resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} + engines: {node: '>=4'} + dependencies: + is-windows: 1.0.2 + dev: true + + /better-sqlite3@7.6.2: + resolution: {integrity: sha512-S5zIU1Hink2AH4xPsN0W43T1/AJ5jrPh7Oy07ocuW/AKYYY02GWzz9NH0nbSMn/gw6fDZ5jZ1QsHt1BXAwJ6Lg==} + requiresBuild: true + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.1 + dev: false + + /big-integer@1.6.51: + resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} + engines: {node: '>=0.6'} + + /bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + dependencies: + file-uri-to-path: 1.0.0 + dev: false + + /bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: false + + /bn.js@4.12.0: + resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} + dev: false + + /body-parser@1.20.1: + resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.11.0 + raw-body: 2.5.1 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + /boolean@3.2.0: + resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + + /bowser@2.11.0: + resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + dev: false + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + + /breakword@1.0.6: + resolution: {integrity: sha512-yjxDAYyK/pBvws9H4xKYpLDpYKEH6CzrBPAuXq3x18I+c/2MkVtT3qAr7Oloi6Dss9qNhPVueAAVU1CSeNDIXw==} + dependencies: + wcwidth: 1.0.1 + dev: true + + /brorand@1.1.0: + resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} + dev: false + + /browserslist@4.21.10: + resolution: {integrity: sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001522 + electron-to-chromium: 1.4.499 + node-releases: 2.0.13 + update-browserslist-db: 1.0.11(browserslist@4.21.10) + dev: true + + /bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + dependencies: + node-int64: 0.4.0 + dev: true + + /buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + dev: false + + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true + + /buffer-writer@2.0.0: + resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==} + engines: {node: '>=4'} + + /buffer@5.6.0: + resolution: {integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: false + + /buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: false + + /buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + /bytes@3.0.0: + resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} + engines: {node: '>= 0.8'} + dev: false + + /bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + /cacache@16.1.3: + resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + '@npmcli/fs': 2.1.2 + '@npmcli/move-file': 2.0.1 + chownr: 2.0.0 + fs-minipass: 2.1.0 + glob: 8.1.0 + infer-owner: 1.0.4 + lru-cache: 7.18.3 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + mkdirp: 1.0.4 + p-map: 4.0.0 + promise-inflight: 1.0.1 + rimraf: 3.0.2 + ssri: 9.0.1 + tar: 6.1.15 + unique-filename: 2.0.1 + transitivePeerDependencies: + - bluebird + dev: true + + /call-bind@1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.2.1 + + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + + /camelcase-keys@6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} + dependencies: + camelcase: 5.3.1 + map-obj: 4.3.0 + quick-lru: 4.0.1 + dev: true + + /camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + dev: true + + /camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + dev: true + + /caniuse-lite@1.0.30001522: + resolution: {integrity: sha512-TKiyTVZxJGhsTszLuzb+6vUZSjVOAhClszBr2Ta2k9IwtNBT/4dzmL6aywt0HCgEZlmwJzXJd8yNiob6HgwTRg==} + dev: true + + /cbor-extract@2.1.1: + resolution: {integrity: sha512-1UX977+L+zOJHsp0mWFG13GLwO6ucKgSmSW6JTl8B9GUvACvHeIVpFqhU92299Z6PfD09aTXDell5p+lp1rUFA==} + hasBin: true + requiresBuild: true + dependencies: + node-gyp-build-optional-packages: 5.0.3 + optionalDependencies: + '@cbor-extract/cbor-extract-darwin-arm64': 2.1.1 + '@cbor-extract/cbor-extract-darwin-x64': 2.1.1 + '@cbor-extract/cbor-extract-linux-arm': 2.1.1 + '@cbor-extract/cbor-extract-linux-arm64': 2.1.1 + '@cbor-extract/cbor-extract-linux-x64': 2.1.1 + '@cbor-extract/cbor-extract-win32-x64': 2.1.1 + dev: false + optional: true + + /cbor-x@1.5.1: + resolution: {integrity: sha512-/vAkC4tiKCQCm5en4sA+mpKmjwY6Xxp1LO+BgZCNhp+Zow3pomyUHeBOK5EDp0mDaE36jw39l5eLHsoF3M1Lmg==} + optionalDependencies: + cbor-extract: 2.1.1 + dev: false + + /cborg@1.10.2: + resolution: {integrity: sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug==} + hasBin: true + + /chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + dev: true + + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /chalk@5.1.1: + resolution: {integrity: sha512-OItMegkSDU3P7OJRWBbNRsQsL8SzgwlIGXSZRVfHCLBYrDgzYDuozwDMwvEDpiZdjr50tdOTbTzuubirtEozsg==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: false + + /char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + dev: true + + /chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + dev: true + + /chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + dev: false + + /chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + dev: true + + /ci-info@3.8.0: + resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} + engines: {node: '>=8'} + dev: true + + /cjs-module-lexer@1.2.3: + resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} + + /clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + dev: true + + /cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + dev: true + + /cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: true + + /clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + dev: true + + /cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + dev: false + + /co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + dev: true + + /code-block-writer@11.0.3: + resolution: {integrity: sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==} + dev: false + + /collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + dev: true + + /color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + dev: true + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + + /color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: true + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + /color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + dev: false + + /color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + dev: true + + /color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + dev: false + + /colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + dev: true + + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + + /commander@9.4.0: + resolution: {integrity: sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==} + engines: {node: ^12.20.0 || >=14} + dev: false + + /common-tags@1.8.2: + resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} + engines: {node: '>=4.0.0'} + dev: true + + /compressible@2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + + /compression@1.7.4: + resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==} + engines: {node: '>= 0.8.0'} + dependencies: + accepts: 1.3.8 + bytes: 3.0.0 + compressible: 2.0.18 + debug: 2.6.9 + on-headers: 1.0.2 + safe-buffer: 5.1.2 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + dev: true + + /content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + dependencies: + safe-buffer: 5.2.1 + + /content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + /convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + dev: true + + /cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + + /cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + + /core-js-compat@3.32.1: + resolution: {integrity: sha512-GSvKDv4wE0bPnQtjklV101juQ85g6H3rm5PDP20mqlS5j0kXF3pP97YvAu5hl+uFHqMictp3b2VxOHljWMAtuA==} + dependencies: + browserslist: 4.21.10 + dev: true + + /cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + /create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true + + /cross-spawn@5.1.0: + resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} + dependencies: + lru-cache: 4.1.5 + shebang-command: 1.2.0 + which: 1.3.1 + dev: true + + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: true + + /crypto-randomuuid@1.0.0: + resolution: {integrity: sha512-/RC5F4l1SCqD/jazwUF6+t34Cd8zTSAGZ7rvvZu1whZUhD2a5MOGKjSGowoGcpj/cbVZk1ZODIooJEQQq3nNAA==} + dev: false + + /csv-generate@3.4.3: + resolution: {integrity: sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==} + dev: true + + /csv-parse@4.16.3: + resolution: {integrity: sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==} + dev: true + + /csv-stringify@5.6.5: + resolution: {integrity: sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A==} + dev: true + + /csv@5.5.3: + resolution: {integrity: sha512-QTaY0XjjhTQOdguARF0lGKm5/mEq9PD9/VhZZegHDIBq2tQwgNpHc3dneD4mGo2iJs+fTKv5Bp0fZ+BRuY3Z0g==} + engines: {node: '>= 0.1.90'} + dependencies: + csv-generate: 3.4.3 + csv-parse: 4.16.3 + csv-stringify: 5.6.5 + stream-transform: 2.1.3 + dev: true + + /dataloader@1.4.0: + resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} + dev: true + + /dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + dev: true + + /dd-trace@3.13.2: + resolution: {integrity: sha512-POO9nEcAufe5pgp2xV1X3PfWip6wh+6TpEcRSlSgZJCIIMvWVCkcIVL/J2a6KAZq6V3Yjbkl8Ktfe+MOzQf5kw==} + engines: {node: '>=14'} + requiresBuild: true + dependencies: + '@datadog/native-appsec': 2.0.0 + '@datadog/native-iast-rewriter': 1.1.2 + '@datadog/native-iast-taint-tracking': 1.1.0 + '@datadog/native-metrics': 1.6.0 + '@datadog/pprof': 1.1.1 + '@datadog/sketches-js': 2.1.0 + crypto-randomuuid: 1.0.0 + diagnostics_channel: 1.1.0 + ignore: 5.2.4 + import-in-the-middle: 1.4.2 + ipaddr.js: 2.1.0 + istanbul-lib-coverage: 3.2.0 + koalas: 1.0.2 + limiter: 1.1.5 + lodash.kebabcase: 4.1.1 + lodash.pick: 4.4.0 + lodash.sortby: 4.7.0 + lodash.uniq: 4.5.0 + lru-cache: 7.18.3 + methods: 1.1.2 + module-details-from-path: 1.0.3 + node-abort-controller: 3.1.1 + opentracing: 0.14.7 + path-to-regexp: 0.1.7 + protobufjs: 7.2.5 + retry: 0.10.1 + semver: 5.7.2 + dev: false + + /debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.0.0 + + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + + /decamelize-keys@1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + dev: true + + /decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + dev: true + + /decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + dependencies: + mimic-response: 3.1.0 + dev: false + + /dedent@0.7.0: + resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} + dev: true + + /deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + dev: false + + /deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true + + /deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + /defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + dependencies: + clone: 1.0.4 + dev: true + + /define-properties@1.2.0: + resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} + engines: {node: '>= 0.4'} + dependencies: + has-property-descriptors: 1.0.0 + object-keys: 1.1.1 + + /delay@5.0.0: + resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} + engines: {node: '>=10'} + + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + /delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + dev: true + + /denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + dev: false + + /depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + /destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + /detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + dev: true + + /detect-libc@2.0.2: + resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} + engines: {node: '>=8'} + dev: false + + /detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + dev: true + + /diagnostics_channel@1.1.0: + resolution: {integrity: sha512-OE1ngLDjSBPG6Tx0YATELzYzy3RKHC+7veQ8gLa8yS7AAgw65mFbVdcsu3501abqOZCEZqZyAIemB0zXlqDSuw==} + engines: {node: '>=4'} + dev: false + + /diff-sequences@28.1.1: + resolution: {integrity: sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dev: true + + /diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dev: true + + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true + + /doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dependencies: + esutils: 2.0.3 + dev: true + + /dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + dev: false + + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: false + + /domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: false + + /domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + dev: false + + /dotenv@16.0.3: + resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} + engines: {node: '>=12'} + + /dotenv@8.6.0: + resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} + engines: {node: '>=10'} + dev: true + + /ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + /electron-to-chromium@1.4.499: + resolution: {integrity: sha512-0NmjlYBLKVHva4GABWAaHuPJolnDuL0AhV3h1hES6rcLCWEIbRL6/8TghfsVwkx6TEroQVdliX7+aLysUpKvjw==} + dev: true + + /elliptic@6.5.4: + resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==} + dependencies: + bn.js: 4.12.0 + brorand: 1.1.0 + hash.js: 1.1.7 + hmac-drbg: 1.0.1 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + dev: false + + /emittery@0.10.2: + resolution: {integrity: sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==} + engines: {node: '>=12'} + dev: true + + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: true + + /encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + /encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + requiresBuild: true + dependencies: + iconv-lite: 0.6.3 + dev: true + optional: true + + /end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + dependencies: + once: 1.4.0 + + /enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + dev: true + + /entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + dev: false + + /env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + dev: true + + /err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + dev: true + + /error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + dependencies: + is-arrayish: 0.2.1 + dev: true + + /es-abstract@1.22.1: + resolution: {integrity: sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.0 + arraybuffer.prototype.slice: 1.0.1 + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + es-set-tostringtag: 2.0.1 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.6 + get-intrinsic: 1.2.1 + get-symbol-description: 1.0.0 + globalthis: 1.0.3 + gopd: 1.0.1 + has: 1.0.3 + has-property-descriptors: 1.0.0 + has-proto: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.5 + is-array-buffer: 3.0.2 + is-callable: 1.2.7 + is-negative-zero: 2.0.2 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-typed-array: 1.1.12 + is-weakref: 1.0.2 + object-inspect: 1.12.3 + object-keys: 1.1.1 + object.assign: 4.1.4 + regexp.prototype.flags: 1.5.0 + safe-array-concat: 1.0.1 + safe-regex-test: 1.0.0 + string.prototype.trim: 1.2.7 + string.prototype.trimend: 1.0.6 + string.prototype.trimstart: 1.0.7 + typed-array-buffer: 1.0.0 + typed-array-byte-length: 1.0.0 + typed-array-byte-offset: 1.0.0 + typed-array-length: 1.0.4 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.11 + dev: true + + /es-set-tostringtag@2.0.1: + resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.1 + has: 1.0.3 + has-tostringtag: 1.0.0 + dev: true + + /es-shim-unscopables@1.0.0: + resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} + dependencies: + has: 1.0.3 + dev: true + + /es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + dev: true + + /esbuild-android-64@0.14.48: + resolution: {integrity: sha512-3aMjboap/kqwCUpGWIjsk20TtxVoKck8/4Tu19rubh7t5Ra0Yrpg30Mt1QXXlipOazrEceGeWurXKeFJgkPOUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /esbuild-android-arm64@0.14.48: + resolution: {integrity: sha512-vptI3K0wGALiDq+EvRuZotZrJqkYkN5282iAfcffjI5lmGG9G1ta/CIVauhY42MBXwEgDJkweiDcDMRLzBZC4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /esbuild-darwin-64@0.14.48: + resolution: {integrity: sha512-gGQZa4+hab2Va/Zww94YbshLuWteyKGD3+EsVon8EWTWhnHFRm5N9NbALNbwi/7hQ/hM1Zm4FuHg+k6BLsl5UA==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /esbuild-darwin-arm64@0.14.48: + resolution: {integrity: sha512-bFjnNEXjhZT+IZ8RvRGNJthLWNHV5JkCtuOFOnjvo5pC0sk2/QVk0Qc06g2PV3J0TcU6kaPC3RN9yy9w2PSLEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /esbuild-freebsd-64@0.14.48: + resolution: {integrity: sha512-1NOlwRxmOsnPcWOGTB10JKAkYSb2nue0oM1AfHWunW/mv3wERfJmnYlGzL3UAOIUXZqW8GeA2mv+QGwq7DToqA==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-freebsd-arm64@0.14.48: + resolution: {integrity: sha512-gXqKdO8wabVcYtluAbikDH2jhXp+Klq5oCD5qbVyUG6tFiGhrC9oczKq3vIrrtwcxDQqK6+HDYK8Zrd4bCA9Gw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-32@0.14.48: + resolution: {integrity: sha512-ghGyDfS289z/LReZQUuuKq9KlTiTspxL8SITBFQFAFRA/IkIvDpnZnCAKTCjGXAmUqroMQfKJXMxyjJA69c/nQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-64@0.14.48: + resolution: {integrity: sha512-vni3p/gppLMVZLghI7oMqbOZdGmLbbKR23XFARKnszCIBpEMEDxOMNIKPmMItQrmH/iJrL1z8Jt2nynY0bE1ug==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-arm64@0.14.48: + resolution: {integrity: sha512-3CFsOlpoxlKPRevEHq8aAntgYGYkE1N9yRYAcPyng/p4Wyx0tPR5SBYsxLKcgPB9mR8chHEhtWYz6EZ+H199Zw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-arm@0.14.48: + resolution: {integrity: sha512-+VfSV7Akh1XUiDNXgqgY1cUP1i2vjI+BmlyXRfVz5AfV3jbpde8JTs5Q9sYgaoq5cWfuKfoZB/QkGOI+QcL1Tw==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-mips64le@0.14.48: + resolution: {integrity: sha512-cs0uOiRlPp6ymknDnjajCgvDMSsLw5mST2UXh+ZIrXTj2Ifyf2aAP3Iw4DiqgnyYLV2O/v/yWBJx+WfmKEpNLA==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-ppc64le@0.14.48: + resolution: {integrity: sha512-+2F0vJMkuI0Wie/wcSPDCqXvSFEELH7Jubxb7mpWrA/4NpT+/byjxDz0gG6R1WJoeDefcrMfpBx4GFNN1JQorQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-riscv64@0.14.48: + resolution: {integrity: sha512-BmaK/GfEE+5F2/QDrIXteFGKnVHGxlnK9MjdVKMTfvtmudjY3k2t8NtlY4qemKSizc+QwyombGWTBDc76rxePA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-s390x@0.14.48: + resolution: {integrity: sha512-tndw/0B9jiCL+KWKo0TSMaUm5UWBLsfCKVdbfMlb3d5LeV9WbijZ8Ordia8SAYv38VSJWOEt6eDCdOx8LqkC4g==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-netbsd-64@0.14.48: + resolution: {integrity: sha512-V9hgXfwf/T901Lr1wkOfoevtyNkrxmMcRHyticybBUHookznipMOHoF41Al68QBsqBxnITCEpjjd4yAos7z9Tw==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-node-externals@1.5.0(esbuild@0.14.48): + resolution: {integrity: sha512-9394Ne2t2Z243BWeNBRkXEYVMOVbQuzp7XSkASZTOQs0GSXDuno5aH5OmzEXc6GMuln5zJjpkZpgwUPW0uRKgw==} + peerDependencies: + esbuild: 0.12 - 0.15 + dependencies: + esbuild: 0.14.48 + find-up: 5.0.0 + tslib: 2.3.1 + dev: true + + /esbuild-openbsd-64@0.14.48: + resolution: {integrity: sha512-+IHf4JcbnnBl4T52egorXMatil/za0awqzg2Vy6FBgPcBpisDWT2sVz/tNdrK9kAqj+GZG/jZdrOkj7wsrNTKA==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-plugin-copy@1.6.0(esbuild@0.14.48): + resolution: {integrity: sha512-wN1paBCoE0yRBl9ZY3ZSD6SxGE4Yfr0Em7zh2yTbJv1JaHEIR3FYYN7HU6F+j/peSaGZJNSORSGxJ5QX1a1Sgg==} + peerDependencies: + esbuild: '>= 0.14.0' + dependencies: + chalk: 4.1.2 + esbuild: 0.14.48 + fs-extra: 10.1.0 + globby: 11.1.0 + dev: true + + /esbuild-sunos-64@0.14.48: + resolution: {integrity: sha512-77m8bsr5wOpOWbGi9KSqDphcq6dFeJyun8TA+12JW/GAjyfTwVtOnN8DOt6DSPUfEV+ltVMNqtXUeTeMAxl5KA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-32@0.14.48: + resolution: {integrity: sha512-EPgRuTPP8vK9maxpTGDe5lSoIBHGKO/AuxDncg5O3NkrPeLNdvvK8oywB0zGaAZXxYWfNNSHskvvDgmfVTguhg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-64@0.14.48: + resolution: {integrity: sha512-YmpXjdT1q0b8ictSdGwH3M8VCoqPpK1/UArze3X199w6u8hUx3V8BhAi1WjbsfDYRBanVVtduAhh2sirImtAvA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-arm64@0.14.48: + resolution: {integrity: sha512-HHaOMCsCXp0rz5BT2crTka6MPWVno121NKApsGs/OIW5QC0ggC69YMGs1aJct9/9FSUF4A1xNE/cLvgB5svR4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild@0.14.48: + resolution: {integrity: sha512-w6N1Yn5MtqK2U1/WZTX9ZqUVb8IOLZkZ5AdHkT6x3cHDMVsYWC7WPdiLmx19w3i4Rwzy5LqsEMtVihG3e4rFzA==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + esbuild-android-64: 0.14.48 + esbuild-android-arm64: 0.14.48 + esbuild-darwin-64: 0.14.48 + esbuild-darwin-arm64: 0.14.48 + esbuild-freebsd-64: 0.14.48 + esbuild-freebsd-arm64: 0.14.48 + esbuild-linux-32: 0.14.48 + esbuild-linux-64: 0.14.48 + esbuild-linux-arm: 0.14.48 + esbuild-linux-arm64: 0.14.48 + esbuild-linux-mips64le: 0.14.48 + esbuild-linux-ppc64le: 0.14.48 + esbuild-linux-riscv64: 0.14.48 + esbuild-linux-s390x: 0.14.48 + esbuild-netbsd-64: 0.14.48 + esbuild-openbsd-64: 0.14.48 + esbuild-sunos-64: 0.14.48 + esbuild-windows-32: 0.14.48 + esbuild-windows-64: 0.14.48 + esbuild-windows-arm64: 0.14.48 + dev: true + + /escalade@3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + dev: true + + /escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + /escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + dev: true + + /escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + dev: true + + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true + + /eslint-config-prettier@8.5.0(eslint@8.24.0): + resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + dependencies: + eslint: 8.24.0 + dev: true + + /eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.5.0)(eslint@8.24.0)(prettier@2.7.1): + resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + eslint: '>=7.28.0' + eslint-config-prettier: '*' + prettier: '>=2.0.0' + peerDependenciesMeta: + eslint-config-prettier: + optional: true + dependencies: + eslint: 8.24.0 + eslint-config-prettier: 8.5.0(eslint@8.24.0) + prettier: 2.7.1 + prettier-linter-helpers: 1.0.0 + dev: true + + /eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + dev: true + + /eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + + /eslint-utils@3.0.0(eslint@8.24.0): + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + dependencies: + eslint: 8.24.0 + eslint-visitor-keys: 2.1.0 + dev: true + + /eslint-visitor-keys@1.3.0: + resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} + engines: {node: '>=4'} + dev: true + + /eslint-visitor-keys@2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + dev: true + + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint@8.24.0: + resolution: {integrity: sha512-dWFaPhGhTAiPcCgm3f6LI2MBWbogMnTJzFBbhXVRQDJPkr9pGZvVjlVfXd+vyDcWPA2Ic9L2AXPIQM0+vk/cSQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint/eslintrc': 1.4.1 + '@humanwhocodes/config-array': 0.10.7 + '@humanwhocodes/gitignore-to-minimatch': 1.0.2 + '@humanwhocodes/module-importer': 1.0.1 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-utils: 3.0.0(eslint@8.24.0) + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.21.0 + globby: 11.1.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.4 + import-fresh: 3.3.0 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + js-sdsl: 4.4.2 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.3 + regexpp: 3.2.0 + strip-ansi: 6.0.1 + strip-json-comments: 3.1.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + + /espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.10.0 + acorn-jsx: 5.3.2(acorn@8.10.0) + eslint-visitor-keys: 3.4.3 + dev: true + + /esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + dev: true + + /esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 + dev: true + + /estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + dev: true + + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true + + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true + + /etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + /event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + /eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + dev: false + + /events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + /execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + dev: true + + /exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + dev: true + + /expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + dev: false + + /expect@28.1.3: + resolution: {integrity: sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/expect-utils': 28.1.3 + jest-get-type: 28.0.2 + jest-matcher-utils: 28.1.3 + jest-message-util: 28.1.3 + jest-util: 28.1.3 + dev: true + + /express-async-errors@3.1.1(express@4.18.2): + resolution: {integrity: sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==} + peerDependencies: + express: ^4.16.2 + dependencies: + express: 4.18.2 + + /express@4.18.2: + resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} + engines: {node: '>= 0.10.0'} + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.1 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.5.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.2.0 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.1 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: 2.0.7 + qs: 6.11.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.18.0 + serve-static: 1.15.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + /extendable-error@0.1.7: + resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + dev: true + + /external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + dev: true + + /fast-copy@2.1.7: + resolution: {integrity: sha512-ozrGwyuCTAy7YgFCua8rmqmytECYk/JYAMXcswOcm0qvGoE3tPb7ivBeIHTOK2DiapBhDZgacIhzhQIKU5TCfA==} + dev: true + + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + /fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + dev: true + + /fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true + + /fast-json-stringify@5.8.0: + resolution: {integrity: sha512-VVwK8CFMSALIvt14U8AvrSzQAwN/0vaVRiFFUVlpnXSnDGrSkOAO5MtzyN8oQNjLd5AqTW5OZRgyjoNuAuR3jQ==} + dependencies: + '@fastify/deepmerge': 1.3.0 + ajv: 8.12.0 + ajv-formats: 2.1.1(ajv@8.12.0) + fast-deep-equal: 3.1.3 + fast-uri: 2.2.0 + rfdc: 1.3.0 + + /fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true + + /fast-printf@1.6.9: + resolution: {integrity: sha512-FChq8hbz65WMj4rstcQsFB0O7Cy++nmbNfLYnD9cYv2cRn8EG6k/MGn9kO/tjO66t09DLDugj3yL+V2o6Qftrg==} + engines: {node: '>=10.0'} + dependencies: + boolean: 3.2.0 + + /fast-redact@3.3.0: + resolution: {integrity: sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==} + engines: {node: '>=6'} + + /fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + dev: true + + /fast-uri@2.2.0: + resolution: {integrity: sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==} + + /fast-url-parser@1.1.3: + resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==} + dependencies: + punycode: 1.4.1 + dev: false + + /fast-xml-parser@4.0.11: + resolution: {integrity: sha512-4aUg3aNRR/WjQAcpceODG1C3x3lFANXRo8+1biqfieHmg9pyMt7qB4lQV/Ta6sJCTbA5vfD8fnA8S54JATiFUA==} + hasBin: true + dependencies: + strnum: 1.0.5 + dev: false + + /fastq@1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + dependencies: + reusify: 1.0.4 + + /fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + dependencies: + bser: 2.1.1 + dev: true + + /file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: 3.0.4 + dev: true + + /file-type@16.5.4: + resolution: {integrity: sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==} + engines: {node: '>=10'} + dependencies: + readable-web-to-node-stream: 3.0.2 + strtok3: 6.3.0 + token-types: 4.2.1 + dev: false + + /file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + dev: false + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + + /finalhandler@1.2.0: + resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + engines: {node: '>= 0.8'} + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + /find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + dev: true + + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + dev: true + + /find-yarn-workspace-root2@1.2.16: + resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} + dependencies: + micromatch: 4.0.5 + pkg-dir: 4.2.0 + dev: true + + /findit2@2.2.3: + resolution: {integrity: sha512-lg/Moejf4qXovVutL0Lz4IsaPoNYMuxt4PA0nGqFxnJ1CTTGGlEO2wKgoDpwknhvZ8k4Q2F+eesgkLbG2Mxfog==} + engines: {node: '>=0.8.22'} + dev: false + + /flat-cache@3.0.4: + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: 3.2.7 + rimraf: 3.0.2 + dev: true + + /flatted@3.2.7: + resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} + dev: true + + /follow-redirects@1.15.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + /for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + dependencies: + is-callable: 1.2.7 + dev: true + + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + /forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + /fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + /fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + dev: false + + /fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.0 + dev: true + + /fs-extra@7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + dev: true + + /fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + dev: true + + /fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 + dev: true + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /function-bind@1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + + /function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + functions-have-names: 1.2.3 + dev: true + + /functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + dev: true + + /gauge@4.0.4: + resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + dev: true + + /gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + dev: true + + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + /get-intrinsic@1.2.1: + resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-proto: 1.0.1 + has-symbols: 1.0.3 + + /get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + dev: true + + /get-port@6.1.2: + resolution: {integrity: sha512-BrGGraKm2uPqurfGVj/z97/zv8dPleC6x9JBNRTrDNtCkkRF4rPwrQXFgL7+I+q8QSdU4ntLQX2D7KIxSy8nGw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + /get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + dev: true + + /get-symbol-description@1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + dev: true + + /github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + dev: false + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + dev: true + + /globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + dev: true + + /globals@13.21.0: + resolution: {integrity: sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: true + + /globalthis@1.0.3: + resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + engines: {node: '>= 0.4'} + dependencies: + define-properties: 1.2.0 + + /globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.1 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 3.0.0 + dev: true + + /gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + dependencies: + get-intrinsic: 1.2.1 + dev: true + + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + dev: true + + /grapheme-splitter@1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + dev: true + + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + dev: false + + /handlebars@4.7.7: + resolution: {integrity: sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==} + engines: {node: '>=0.4.7'} + hasBin: true + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.17.4 + dev: false + + /hard-rejection@2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + dev: true + + /has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + dev: true + + /has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + dev: true + + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: true + + /has-property-descriptors@1.0.0: + resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + dependencies: + get-intrinsic: 1.2.1 + + /has-proto@1.0.1: + resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + engines: {node: '>= 0.4'} + + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + /has-tostringtag@1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + dev: true + + /has@1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + + /hash.js@1.1.7: + resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + dev: false + + /he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + dev: false + + /help-me@4.2.0: + resolution: {integrity: sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==} + dependencies: + glob: 8.1.0 + readable-stream: 3.6.2 + dev: true + + /hmac-drbg@1.0.1: + resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + dependencies: + hash.js: 1.1.7 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + dev: false + + /hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + dev: true + + /html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + dev: true + + /html-to-text@7.1.1: + resolution: {integrity: sha512-c9QWysrfnRZevVpS8MlE7PyOdSuIOjg8Bt8ZE10jMU/BEngA6j3llj4GRfAmtQzcd1FjKE0sWu5IHXRUH9YxIQ==} + engines: {node: '>=10.23.2'} + hasBin: true + dependencies: + deepmerge: 4.3.1 + he: 1.2.0 + htmlparser2: 6.1.0 + minimist: 1.2.8 + dev: false + + /htmlparser2@6.1.0: + resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==} + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + domutils: 2.8.0 + entities: 2.2.0 + dev: false + + /http-cache-semantics@4.1.1: + resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + dev: true + + /http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + /http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + + /http-terminator@3.2.0: + resolution: {integrity: sha512-JLjck1EzPaWjsmIf8bziM3p9fgR1Y3JoUKAkyYEbZmFrIvJM6I8vVJfBGWlEtV9IWOvzNnaTtjuwZeBY2kwB4g==} + engines: {node: '>=14'} + dependencies: + delay: 5.0.0 + p-wait-for: 3.2.0 + roarr: 7.15.1 + type-fest: 2.19.0 + + /https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + + /human-id@1.0.2: + resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} + dev: true + + /human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + dev: true + + /humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + dependencies: + ms: 2.1.3 + dev: true + + /iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + + /iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + requiresBuild: true + dependencies: + safer-buffer: 2.1.2 + dev: true + optional: true + + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + /ignore@5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} + + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /import-in-the-middle@1.4.2: + resolution: {integrity: sha512-9WOz1Yh/cvO/p69sxRmhyQwrIGGSp7EIdcb+fFNVi7CzQGQB8U1/1XrKVSbEd/GNOAeM0peJtmi7+qphe7NvAw==} + dependencies: + acorn: 8.10.0 + acorn-import-assertions: 1.9.0(acorn@8.10.0) + cjs-module-lexer: 1.2.3 + module-details-from-path: 1.0.3 + dev: false + + /import-local@3.1.0: + resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} + engines: {node: '>=8'} + hasBin: true + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + dev: true + + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: true + + /indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + dev: true + + /infer-owner@1.0.4: + resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} + dev: true + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + /ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + dev: false + + /internal-slot@1.0.5: + resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.1 + has: 1.0.3 + side-channel: 1.0.4 + dev: true + + /ioredis@5.3.2: + resolution: {integrity: sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==} + engines: {node: '>=12.22.0'} + dependencies: + '@ioredis/commands': 1.2.0 + cluster-key-slot: 1.1.2 + debug: 4.3.4 + denque: 2.1.0 + lodash.defaults: 4.2.0 + lodash.isarguments: 3.1.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color + dev: false + + /ip@2.0.0: + resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==} + dev: true + + /ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + /ipaddr.js@2.1.0: + resolution: {integrity: sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==} + engines: {node: '>= 10'} + dev: false + + /is-array-buffer@3.0.2: + resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + is-typed-array: 1.1.12 + dev: true + + /is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + dev: true + + /is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + dev: false + + /is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 + dev: true + + /is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + dev: true + + /is-ci@3.0.1: + resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} + hasBin: true + dependencies: + ci-info: 3.8.0 + dev: true + + /is-core-module@2.13.0: + resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} + dependencies: + has: 1.0.3 + dev: true + + /is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: true + + /is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + + /is-lambda@1.0.1: + resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} + dev: true + + /is-negative-zero@2.0.2: + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} + dev: true + + /is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + /is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + dev: true + + /is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-shared-array-buffer@1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.2 + dev: true + + /is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: true + + /is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-subdir@1.2.0: + resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} + engines: {node: '>=4'} + dependencies: + better-path-resolve: 1.0.0 + dev: true + + /is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /is-typed-array@1.1.12: + resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} + engines: {node: '>= 0.4'} + dependencies: + which-typed-array: 1.1.11 + dev: true + + /is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + dependencies: + call-bind: 1.0.2 + dev: true + + /is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + dev: true + + /isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + dev: true + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + + /iso-datestring-validator@2.2.2: + resolution: {integrity: sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==} + dev: false + + /istanbul-lib-coverage@3.2.0: + resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} + engines: {node: '>=8'} + + /istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + dependencies: + '@babel/core': 7.18.6 + '@babel/parser': 7.22.10 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + dependencies: + istanbul-lib-coverage: 3.2.0 + make-dir: 4.0.0 + supports-color: 7.2.0 + dev: true + + /istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + dependencies: + debug: 4.3.4 + istanbul-lib-coverage: 3.2.0 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-reports@3.1.6: + resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==} + engines: {node: '>=8'} + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + dev: true + + /jest-changed-files@28.1.3: + resolution: {integrity: sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + execa: 5.1.1 + p-limit: 3.1.0 + dev: true + + /jest-circus@28.1.3: + resolution: {integrity: sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/environment': 28.1.3 + '@jest/expect': 28.1.3 + '@jest/test-result': 28.1.3 + '@jest/types': 28.1.3 + '@types/node': 18.17.8 + chalk: 4.1.2 + co: 4.6.0 + dedent: 0.7.0 + is-generator-fn: 2.1.0 + jest-each: 28.1.3 + jest-matcher-utils: 28.1.3 + jest-message-util: 28.1.3 + jest-runtime: 28.1.3 + jest-snapshot: 28.1.3 + jest-util: 28.1.3 + p-limit: 3.1.0 + pretty-format: 28.1.3 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-cli@28.1.3(@types/node@18.0.0)(ts-node@10.8.2): + resolution: {integrity: sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 28.1.3(ts-node@10.8.2) + '@jest/test-result': 28.1.3 + '@jest/types': 28.1.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + import-local: 3.1.0 + jest-config: 28.1.3(@types/node@18.0.0)(ts-node@10.8.2) + jest-util: 28.1.3 + jest-validate: 28.1.3 + prompts: 2.4.2 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - supports-color + - ts-node + dev: true + + /jest-config@28.1.3(@types/node@18.0.0)(ts-node@10.8.2): + resolution: {integrity: sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.18.6 + '@jest/test-sequencer': 28.1.3 + '@jest/types': 28.1.3 + '@types/node': 18.0.0 + babel-jest: 28.1.3(@babel/core@7.18.6) + chalk: 4.1.2 + ci-info: 3.8.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 28.1.3 + jest-environment-node: 28.1.3 + jest-get-type: 28.0.2 + jest-regex-util: 28.0.2 + jest-resolve: 28.1.3 + jest-runner: 28.1.3 + jest-util: 28.1.3 + jest-validate: 28.1.3 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 28.1.3 + slash: 3.0.0 + strip-json-comments: 3.1.1 + ts-node: 10.8.2(@swc/core@1.3.42)(@types/node@18.0.0)(typescript@4.8.4) + transitivePeerDependencies: + - supports-color + dev: true + + /jest-config@28.1.3(@types/node@18.17.8)(ts-node@10.8.2): + resolution: {integrity: sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.18.6 + '@jest/test-sequencer': 28.1.3 + '@jest/types': 28.1.3 + '@types/node': 18.17.8 + babel-jest: 28.1.3(@babel/core@7.18.6) + chalk: 4.1.2 + ci-info: 3.8.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 28.1.3 + jest-environment-node: 28.1.3 + jest-get-type: 28.0.2 + jest-regex-util: 28.0.2 + jest-resolve: 28.1.3 + jest-runner: 28.1.3 + jest-util: 28.1.3 + jest-validate: 28.1.3 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 28.1.3 + slash: 3.0.0 + strip-json-comments: 3.1.1 + ts-node: 10.8.2(@swc/core@1.3.42)(@types/node@18.0.0)(typescript@4.8.4) + transitivePeerDependencies: + - supports-color + dev: true + + /jest-diff@28.1.3: + resolution: {integrity: sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + chalk: 4.1.2 + diff-sequences: 28.1.1 + jest-get-type: 28.0.2 + pretty-format: 28.1.3 + dev: true + + /jest-docblock@28.1.1: + resolution: {integrity: sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + detect-newline: 3.1.0 + dev: true + + /jest-each@28.1.3: + resolution: {integrity: sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/types': 28.1.3 + chalk: 4.1.2 + jest-get-type: 28.0.2 + jest-util: 28.1.3 + pretty-format: 28.1.3 + dev: true + + /jest-environment-node@28.1.3: + resolution: {integrity: sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/environment': 28.1.3 + '@jest/fake-timers': 28.1.3 + '@jest/types': 28.1.3 + '@types/node': 18.17.8 + jest-mock: 28.1.3 + jest-util: 28.1.3 + dev: true + + /jest-get-type@28.0.2: + resolution: {integrity: sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dev: true + + /jest-haste-map@28.1.3: + resolution: {integrity: sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/types': 28.1.3 + '@types/graceful-fs': 4.1.6 + '@types/node': 18.17.8 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 28.0.2 + jest-util: 28.1.3 + jest-worker: 28.1.3 + micromatch: 4.0.5 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /jest-leak-detector@28.1.3: + resolution: {integrity: sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + jest-get-type: 28.0.2 + pretty-format: 28.1.3 + dev: true + + /jest-matcher-utils@28.1.3: + resolution: {integrity: sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + chalk: 4.1.2 + jest-diff: 28.1.3 + jest-get-type: 28.0.2 + pretty-format: 28.1.3 + dev: true + + /jest-message-util@28.1.3: + resolution: {integrity: sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@babel/code-frame': 7.22.10 + '@jest/types': 28.1.3 + '@types/stack-utils': 2.0.1 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.5 + pretty-format: 28.1.3 + slash: 3.0.0 + stack-utils: 2.0.6 + dev: true + + /jest-mock@28.1.3: + resolution: {integrity: sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/types': 28.1.3 + '@types/node': 18.17.8 + dev: true + + /jest-pnp-resolver@1.2.3(jest-resolve@28.1.3): + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + dependencies: + jest-resolve: 28.1.3 + dev: true + + /jest-regex-util@28.0.2: + resolution: {integrity: sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dev: true + + /jest-resolve-dependencies@28.1.3: + resolution: {integrity: sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + jest-regex-util: 28.0.2 + jest-snapshot: 28.1.3 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-resolve@28.1.3: + resolution: {integrity: sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 28.1.3 + jest-pnp-resolver: 1.2.3(jest-resolve@28.1.3) + jest-util: 28.1.3 + jest-validate: 28.1.3 + resolve: 1.22.4 + resolve.exports: 1.1.1 + slash: 3.0.0 + dev: true + + /jest-runner@28.1.3: + resolution: {integrity: sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/console': 28.1.3 + '@jest/environment': 28.1.3 + '@jest/test-result': 28.1.3 + '@jest/transform': 28.1.3 + '@jest/types': 28.1.3 + '@types/node': 18.17.8 + chalk: 4.1.2 + emittery: 0.10.2 + graceful-fs: 4.2.11 + jest-docblock: 28.1.1 + jest-environment-node: 28.1.3 + jest-haste-map: 28.1.3 + jest-leak-detector: 28.1.3 + jest-message-util: 28.1.3 + jest-resolve: 28.1.3 + jest-runtime: 28.1.3 + jest-util: 28.1.3 + jest-watcher: 28.1.3 + jest-worker: 28.1.3 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-runtime@28.1.3: + resolution: {integrity: sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/environment': 28.1.3 + '@jest/fake-timers': 28.1.3 + '@jest/globals': 28.1.3 + '@jest/source-map': 28.1.2 + '@jest/test-result': 28.1.3 + '@jest/transform': 28.1.3 + '@jest/types': 28.1.3 + chalk: 4.1.2 + cjs-module-lexer: 1.2.3 + collect-v8-coverage: 1.0.2 + execa: 5.1.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 28.1.3 + jest-message-util: 28.1.3 + jest-mock: 28.1.3 + jest-regex-util: 28.0.2 + jest-resolve: 28.1.3 + jest-snapshot: 28.1.3 + jest-util: 28.1.3 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-snapshot@28.1.3: + resolution: {integrity: sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@babel/core': 7.18.6 + '@babel/generator': 7.22.10 + '@babel/plugin-syntax-typescript': 7.22.5(@babel/core@7.18.6) + '@babel/traverse': 7.22.10 + '@babel/types': 7.22.10 + '@jest/expect-utils': 28.1.3 + '@jest/transform': 28.1.3 + '@jest/types': 28.1.3 + '@types/babel__traverse': 7.20.1 + '@types/prettier': 2.7.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.18.6) + chalk: 4.1.2 + expect: 28.1.3 + graceful-fs: 4.2.11 + jest-diff: 28.1.3 + jest-get-type: 28.0.2 + jest-haste-map: 28.1.3 + jest-matcher-utils: 28.1.3 + jest-message-util: 28.1.3 + jest-util: 28.1.3 + natural-compare: 1.4.0 + pretty-format: 28.1.3 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-util@28.1.3: + resolution: {integrity: sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/types': 28.1.3 + '@types/node': 18.17.8 + chalk: 4.1.2 + ci-info: 3.8.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + dev: true + + /jest-validate@28.1.3: + resolution: {integrity: sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/types': 28.1.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 28.0.2 + leven: 3.1.0 + pretty-format: 28.1.3 + dev: true + + /jest-watcher@28.1.3: + resolution: {integrity: sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/test-result': 28.1.3 + '@jest/types': 28.1.3 + '@types/node': 18.17.8 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.10.2 + jest-util: 28.1.3 + string-length: 4.0.2 + dev: true + + /jest-worker@28.1.3: + resolution: {integrity: sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@types/node': 18.17.8 + merge-stream: 2.0.0 + supports-color: 8.1.1 + dev: true + + /jest@28.1.2(@types/node@18.0.0)(ts-node@10.8.2): + resolution: {integrity: sha512-Tuf05DwLeCh2cfWCQbcz9UxldoDyiR1E9Igaei5khjonKncYdc6LDfynKCEWozK0oLE3GD+xKAo2u8x/0s6GOg==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 28.1.3(ts-node@10.8.2) + '@jest/types': 28.1.3 + import-local: 3.1.0 + jest-cli: 28.1.3(@types/node@18.0.0)(ts-node@10.8.2) + transitivePeerDependencies: + - '@types/node' + - supports-color + - ts-node + dev: true + + /joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + dev: true + + /js-sdsl@4.4.2: + resolution: {integrity: sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w==} + dev: true + + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: true + + /js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + dev: true + + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /jsesc@0.5.0: + resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} + hasBin: true + dev: true + + /jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + dev: true + + /json-parse-even-better-errors@3.0.0: + resolution: {integrity: sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: true + + /json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true + + /json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + /json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true + + /json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + dev: true + + /jsonc-parser@3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: true + + /jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + optionalDependencies: + graceful-fs: 4.2.11 + dev: true + + /jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + dependencies: + universalify: 2.0.0 + optionalDependencies: + graceful-fs: 4.2.11 + dev: true + + /jsonwebtoken@8.5.1: + resolution: {integrity: sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==} + engines: {node: '>=4', npm: '>=1.4.28'} + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 5.7.2 + dev: false + + /jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + dev: false + + /jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + dev: false + + /key-encoder@2.0.3: + resolution: {integrity: sha512-fgBtpAGIr/Fy5/+ZLQZIPPhsZEcbSlYu/Wu96tNDFNSjSACw5lEIOFeaVdQ/iwrb8oxjlWi6wmWdH76hV6GZjg==} + dependencies: + '@types/elliptic': 6.4.14 + asn1.js: 5.4.1 + bn.js: 4.12.0 + elliptic: 6.5.4 + dev: false + + /kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + dev: true + + /kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + dev: true + + /kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + dev: true + + /koalas@1.0.2: + resolution: {integrity: sha512-RYhBbYaTTTHId3l6fnMZc3eGQNW6FVCqMG6AMwA5I1Mafr6AflaXeoi6x3xQuATRotGYRLk6+1ELZH4dstFNOA==} + engines: {node: '>=0.10.0'} + dev: false + + /kysely@0.22.0: + resolution: {integrity: sha512-ZE3qWtnqLOalodzfK5QUEcm7AEulhxsPNuKaGFsC3XiqO92vMLm+mAHk/NnbSIOtC4RmGm0nsv700i8KDp1gfQ==} + engines: {node: '>=14.0.0'} + dev: false + + /kysely@0.23.5: + resolution: {integrity: sha512-TH+b56pVXQq0tsyooYLeNfV11j6ih7D50dyN8tkM0e7ndiUH28Nziojiog3qRFlmEj9XePYdZUrNJ2079Qjdow==} + engines: {node: '>=14.0.0'} + + /leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + dev: true + + /levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /limiter@1.1.5: + resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==} + dev: false + + /lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: true + + /load-yaml-file@0.2.0: + resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} + engines: {node: '>=6'} + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.14.1 + pify: 4.0.1 + strip-bom: 3.0.0 + dev: true + + /locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + dependencies: + p-locate: 4.1.0 + dev: true + + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: true + + /lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + dev: true + + /lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + dev: false + + /lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + dev: false + + /lodash.isarguments@3.1.0: + resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + dev: false + + /lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + dev: false + + /lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + dev: false + + /lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + dev: false + + /lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + dev: false + + /lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + dev: false + + /lodash.kebabcase@4.1.1: + resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} + dev: false + + /lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true + + /lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + dev: false + + /lodash.pick@4.4.0: + resolution: {integrity: sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==} + dev: false + + /lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + dev: false + + /lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + dev: true + + /lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + dev: false + + /long@5.2.3: + resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} + dev: false + + /lru-cache@10.0.1: + resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==} + engines: {node: 14 || >=16.14} + dev: false + + /lru-cache@4.1.5: + resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} + dependencies: + pseudomap: 1.0.2 + yallist: 2.1.2 + dev: true + + /lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + dependencies: + yallist: 3.1.1 + dev: true + + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + + /lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + + /make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + dependencies: + semver: 7.5.4 + dev: true + + /make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true + + /make-fetch-happen@10.2.1: + resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + agentkeepalive: 4.5.0 + cacache: 16.1.3 + http-cache-semantics: 4.1.1 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-lambda: 1.0.1 + lru-cache: 7.18.3 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-fetch: 2.1.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + negotiator: 0.6.3 + promise-retry: 2.0.1 + socks-proxy-agent: 7.0.0 + ssri: 9.0.1 + transitivePeerDependencies: + - bluebird + - supports-color + dev: true + + /makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + dependencies: + tmpl: 1.0.5 + dev: true + + /map-obj@1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + dev: true + + /map-obj@4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + dev: true + + /media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + /meow@6.1.1: + resolution: {integrity: sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==} + engines: {node: '>=8'} + dependencies: + '@types/minimist': 1.2.2 + camelcase-keys: 6.2.2 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 2.5.0 + read-pkg-up: 7.0.1 + redent: 3.0.0 + trim-newlines: 3.0.1 + type-fest: 0.13.1 + yargs-parser: 18.1.3 + dev: true + + /merge-descriptors@1.0.1: + resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true + + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + /methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + + /mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + /mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + dev: true + + /mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + dev: false + + /min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + dev: true + + /minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + dev: false + + /minimalistic-crypto-utils@1.0.1: + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + dev: false + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + + /minimist-options@4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + dev: true + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + /minipass-collect@1.0.2: + resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 + dev: true + + /minipass-fetch@2.1.2: + resolution: {integrity: sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + minipass: 3.3.6 + minipass-sized: 1.0.3 + minizlib: 2.1.2 + optionalDependencies: + encoding: 0.1.13 + dev: true + + /minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 + dev: true + + /minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + dependencies: + minipass: 3.3.6 + dev: true + + /minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} + dependencies: + minipass: 3.3.6 + dev: true + + /minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + dependencies: + yallist: 4.0.0 + dev: true + + /minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + dev: true + + /minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + dev: true + + /mixme@0.5.9: + resolution: {integrity: sha512-VC5fg6ySUscaWUpI4gxCBTQMH2RdUpNrk+MsbpCYtIvf9SBJdiUey4qE7BXviJsJR4nDQxCZ+3yaYNW3guz/Pw==} + engines: {node: '>= 8.0.0'} + dev: true + + /mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + dev: false + + /mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + /module-details-from-path@1.0.3: + resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} + dev: false + + /ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + /multiformats@9.9.0: + resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} + + /napi-build-utils@1.0.2: + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + dev: false + + /natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true + + /negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + /neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + dev: false + + /node-abi@3.47.0: + resolution: {integrity: sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A==} + engines: {node: '>=10'} + dependencies: + semver: 7.5.4 + dev: false + + /node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + dev: false + + /node-addon-api@5.1.0: + resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} + dev: false + + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: true + + /node-gyp-build-optional-packages@5.0.3: + resolution: {integrity: sha512-k75jcVzk5wnnc/FMxsf4udAoTEUv2jY3ycfdSd3yWu6Cnd1oee6/CfZJApyscA4FJOmdoixWwiwOyf16RzD5JA==} + hasBin: true + requiresBuild: true + dev: false + optional: true + + /node-gyp-build@3.9.0: + resolution: {integrity: sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A==} + hasBin: true + dev: false + + /node-gyp-build@4.6.1: + resolution: {integrity: sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==} + hasBin: true + dev: false + + /node-gyp@9.3.1: + resolution: {integrity: sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==} + engines: {node: ^12.13 || ^14.13 || >=16} + hasBin: true + dependencies: + env-paths: 2.2.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + make-fetch-happen: 10.2.1 + nopt: 6.0.0 + npmlog: 6.0.2 + rimraf: 3.0.2 + semver: 7.5.4 + tar: 6.1.15 + which: 2.0.2 + transitivePeerDependencies: + - bluebird + - supports-color + dev: true + + /node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + dev: true + + /node-releases@2.0.13: + resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} + dev: true + + /nodemailer-html-to-text@3.2.0: + resolution: {integrity: sha512-RJUC6640QV1PzTHHapOrc6IzrAJUZtk2BdVdINZ9VTLm+mcQNyBO9LYyhrnufkzqiD9l8hPLJ97rSyK4WanPNg==} + engines: {node: '>= 10.23.0'} + dependencies: + html-to-text: 7.1.1 + dev: false + + /nodemailer@6.8.0: + resolution: {integrity: sha512-EjYvSmHzekz6VNkNd12aUqAco+bOkRe3Of5jVhltqKhEsjw/y0PYPJfp83+s9Wzh1dspYAkUW/YNQ350NATbSQ==} + engines: {node: '>=6.0.0'} + dev: false + + /nopt@6.0.0: + resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + hasBin: true + dependencies: + abbrev: 1.1.1 + dev: true + + /normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.4 + semver: 5.7.2 + validate-npm-package-license: 3.0.4 + dev: true + + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + dependencies: + path-key: 3.1.1 + dev: true + + /npmlog@6.0.2: + resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + are-we-there-yet: 3.0.1 + console-control-strings: 1.1.0 + gauge: 4.0.4 + set-blocking: 2.0.0 + dev: true + + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + /object-inspect@1.12.3: + resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + + /object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + /object.assign@4.1.4: + resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + has-symbols: 1.0.3 + object-keys: 1.1.1 + dev: true + + /on-exit-leak-free@2.1.0: + resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==} + + /on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + dependencies: + ee-first: 1.1.1 + + /on-headers@1.0.2: + resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} + engines: {node: '>= 0.8'} + dev: false + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + + /one-webcrypto@1.0.3: + resolution: {integrity: sha512-fu9ywBVBPx0gS9K0etIROTiCkvI5S1TDjFsYFb3rC1ewFxeOqsbzq7aIMBHsYfrTHBcGXJaONXXjTl8B01cW1Q==} + + /onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + dependencies: + mimic-fn: 2.1.0 + dev: true + + /opentracing@0.14.7: + resolution: {integrity: sha512-vz9iS7MJ5+Bp1URw8Khvdyw1H/hGvzHWlKQ7eRrQojSCDL1/SrWfrY9QebLw97n2deyRtzHRC3MkQfVNUCo91Q==} + engines: {node: '>=0.10'} + dev: false + + /optionator@0.9.3: + resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} + engines: {node: '>= 0.8.0'} + dependencies: + '@aashutoshrathi/word-wrap': 1.2.6 + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + dev: true + + /outdent@0.5.0: + resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + dev: true + + /p-filter@2.1.0: + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} + engines: {node: '>=8'} + dependencies: + p-map: 2.1.0 + dev: true + + /p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + + /p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + dependencies: + p-try: 2.2.0 + dev: true + + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + + /p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + dependencies: + p-limit: 2.3.0 + dev: true + + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: true + + /p-map@2.1.0: + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} + dev: true + + /p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + dependencies: + aggregate-error: 3.1.0 + dev: true + + /p-queue@6.6.2: + resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} + engines: {node: '>=8'} + dependencies: + eventemitter3: 4.0.7 + p-timeout: 3.2.0 + dev: false + + /p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + dependencies: + p-finally: 1.0.0 + + /p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + dev: true + + /p-wait-for@3.2.0: + resolution: {integrity: sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA==} + engines: {node: '>=8'} + dependencies: + p-timeout: 3.2.0 + + /packet-reader@1.0.0: + resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==} + + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + dependencies: + '@babel/code-frame': 7.22.10 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + dev: true + + /parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + /path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + dev: false + + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true + + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true + + /path-to-regexp@0.1.7: + resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true + + /peek-readable@4.1.0: + resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==} + engines: {node: '>=8'} + dev: false + + /pg-connection-string@2.6.2: + resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==} + + /pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + /pg-pool@3.6.1(pg@8.10.0): + resolution: {integrity: sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==} + peerDependencies: + pg: '>=8.0' + dependencies: + pg: 8.10.0 + + /pg-protocol@1.6.0: + resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==} + + /pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + /pg@8.10.0: + resolution: {integrity: sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + dependencies: + buffer-writer: 2.0.0 + packet-reader: 1.0.0 + pg-connection-string: 2.6.2 + pg-pool: 3.6.1(pg@8.10.0) + pg-protocol: 1.6.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + + /pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + dependencies: + split2: 4.2.0 + + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: true + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + /pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + dev: true + + /pify@5.0.0: + resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} + engines: {node: '>=10'} + dev: false + + /pino-abstract-transport@1.0.0: + resolution: {integrity: sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==} + dependencies: + readable-stream: 4.4.2 + split2: 4.2.0 + + /pino-http@8.2.1: + resolution: {integrity: sha512-bdWAE4HYfFjDhKw2/N7BLNSIFAs+WDLZnetsGRpBdNEKq7/RoZUgblLS5OlMY257RPQml6J5QiiLkwxbstzWbA==} + dependencies: + fast-url-parser: 1.1.3 + get-caller-file: 2.0.5 + pino: 8.15.0 + pino-std-serializers: 6.2.2 + process-warning: 2.2.0 + dev: false + + /pino-http@8.4.0: + resolution: {integrity: sha512-9I1eRLxsujQJwLQTrHBU0wDlwnry2HzV2TlDwAsmZ9nT3Y2NQBLrz+DYp73L4i11vl/eudnFT8Eg0Kp62tMwEw==} + dependencies: + get-caller-file: 2.0.5 + pino: 8.15.0 + pino-std-serializers: 6.2.2 + process-warning: 2.2.0 + + /pino-pretty@9.1.0: + resolution: {integrity: sha512-IM6NY9LLo/dVgY7/prJhCh4rAJukafdt0ibxeNOWc2fxKMyTk90SOB9Ao2HfbtShT9QPeP0ePpJktksMhSQMYA==} + hasBin: true + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 2.1.7 + fast-safe-stringify: 2.1.1 + help-me: 4.2.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.0 + pino-abstract-transport: 1.0.0 + pump: 3.0.0 + readable-stream: 4.4.2 + secure-json-parse: 2.7.0 + sonic-boom: 3.3.0 + strip-json-comments: 3.1.1 + dev: true + + /pino-std-serializers@6.2.2: + resolution: {integrity: sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==} + + /pino@8.15.0: + resolution: {integrity: sha512-olUADJByk4twxccmAxb1RiGKOSvddHugCV3wkqjyv+3Sooa2KLrmXrKEWOKi0XPCLasRR5jBXxioE1jxUa4KzQ==} + hasBin: true + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.3.0 + on-exit-leak-free: 2.1.0 + pino-abstract-transport: 1.0.0 + pino-std-serializers: 6.2.2 + process-warning: 2.2.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.4.3 + sonic-boom: 3.3.0 + thread-stream: 2.4.0 + + /pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + dev: true + + /pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + dependencies: + find-up: 4.1.0 + dev: true + + /postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + /postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + + /postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + /postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + dependencies: + xtend: 4.0.2 + + /prebuild-install@7.1.1: + resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + detect-libc: 2.0.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 1.0.2 + node-abi: 3.47.0 + pump: 3.0.0 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.1 + tunnel-agent: 0.6.0 + dev: false + + /preferred-pm@3.0.3: + resolution: {integrity: sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==} + engines: {node: '>=10'} + dependencies: + find-up: 5.0.0 + find-yarn-workspace-root2: 1.2.16 + path-exists: 4.0.0 + which-pm: 2.0.0 + dev: true + + /prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true + + /prettier-config-standard@5.0.0(prettier@2.7.1): + resolution: {integrity: sha512-QK252QwCxlsak8Zx+rPKZU31UdbRcu9iUk9X1ONYtLSO221OgvV9TlKoTf6iPDZtvF3vE2mkgzFIEgSUcGELSQ==} + peerDependencies: + prettier: ^2.4.0 + dependencies: + prettier: 2.7.1 + dev: true + + /prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + dependencies: + fast-diff: 1.3.0 + dev: true + + /prettier@2.7.1: + resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + + /pretty-format@28.1.3: + resolution: {integrity: sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/schemas': 28.1.3 + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + + /process-warning@2.2.0: + resolution: {integrity: sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg==} + + /process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + /promise-inflight@1.0.1: + resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} + peerDependencies: + bluebird: '*' + peerDependenciesMeta: + bluebird: + optional: true + dev: true + + /promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + dev: true + + /prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + dev: true + + /protobufjs@7.2.5: + resolution: {integrity: sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==} + engines: {node: '>=12.0.0'} + requiresBuild: true + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 18.17.8 + long: 5.2.3 + dev: false + + /proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + /pseudomap@1.0.2: + resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} + dev: true + + /pump@3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + + /punycode@1.4.1: + resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} + dev: false + + /punycode@2.3.0: + resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} + engines: {node: '>=6'} + + /qs@6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + /quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + /quick-lru@4.0.1: + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} + engines: {node: '>=8'} + dev: true + + /range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + /rate-limiter-flexible@2.4.1: + resolution: {integrity: sha512-dgH4T44TzKVO9CLArNto62hJOwlWJMLUjVVr/ii0uUzZXEXthDNr7/yefW5z/1vvHAfycc1tnuiYyNJ8CTRB3g==} + dev: false + + /raw-body@2.5.1: + resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + /rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + dev: false + + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: true + + /read-pkg-up@7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 + dev: true + + /read-pkg@5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + dependencies: + '@types/normalize-package-data': 2.4.1 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + dev: true + + /read-yaml-file@1.1.0: + resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} + engines: {node: '>=6'} + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.14.1 + pify: 4.0.1 + strip-bom: 3.0.0 + dev: true + + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + /readable-stream@4.4.2: + resolution: {integrity: sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + /readable-web-to-node-stream@3.0.2: + resolution: {integrity: sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==} + engines: {node: '>=8'} + dependencies: + readable-stream: 3.6.2 + dev: false + + /real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + /redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + dev: true + + /redis-errors@1.2.0: + resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} + engines: {node: '>=4'} + dev: false + + /redis-parser@3.0.0: + resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} + engines: {node: '>=4'} + dependencies: + redis-errors: 1.2.0 + dev: false + + /regenerate-unicode-properties@10.1.0: + resolution: {integrity: sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==} + engines: {node: '>=4'} + dependencies: + regenerate: 1.4.2 + dev: true + + /regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + dev: true + + /regenerator-runtime@0.14.0: + resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} + dev: true + + /regenerator-transform@0.15.2: + resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} + dependencies: + '@babel/runtime': 7.22.10 + dev: true + + /regexp.prototype.flags@1.5.0: + resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + functions-have-names: 1.2.3 + dev: true + + /regexpp@3.2.0: + resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} + engines: {node: '>=8'} + dev: true + + /regexpu-core@5.3.2: + resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==} + engines: {node: '>=4'} + dependencies: + '@babel/regjsgen': 0.8.0 + regenerate: 1.4.2 + regenerate-unicode-properties: 10.1.0 + regjsparser: 0.9.1 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.1.0 + dev: true + + /regjsparser@0.9.1: + resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} + hasBin: true + dependencies: + jsesc: 0.5.0 + dev: true + + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: true + + /require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + /require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + dev: true + + /resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + dependencies: + resolve-from: 5.0.0 + dev: true + + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + dev: true + + /resolve.exports@1.1.1: + resolution: {integrity: sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==} + engines: {node: '>=10'} + dev: true + + /resolve@1.22.4: + resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} + hasBin: true + dependencies: + is-core-module: 2.13.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + + /retry@0.10.1: + resolution: {integrity: sha512-ZXUSQYTHdl3uS7IuCehYfMzKyIDBNoAuUblvy5oGO5UJSUTmStUUVPXbA9Qxd173Bgre53yCQczQuHgRWAdvJQ==} + dev: false + + /retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + dev: true + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + /rfdc@1.3.0: + resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} + + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /roarr@7.15.1: + resolution: {integrity: sha512-0ExL9rjOXeQPvQvQo8IcV8SR2GTXmDr1FQFlY2HiAV+gdVQjaVZNOx9d4FI2RqFFsd0sNsiw2TRS/8RU9g0ZfA==} + engines: {node: '>=12.0'} + dependencies: + boolean: 3.2.0 + fast-json-stringify: 5.8.0 + fast-printf: 1.6.9 + globalthis: 1.0.3 + safe-stable-stringify: 2.4.3 + semver-compare: 1.0.0 + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + + /rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + requiresBuild: true + dependencies: + tslib: 2.6.2 + dev: false + optional: true + + /safe-array-concat@1.0.1: + resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==} + engines: {node: '>=0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + has-symbols: 1.0.3 + isarray: 2.0.5 + dev: true + + /safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + dev: false + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + /safe-regex-test@1.0.0: + resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + is-regex: 1.1.4 + dev: true + + /safe-stable-stringify@2.4.3: + resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} + engines: {node: '>=10'} + + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + requiresBuild: true + + /secure-json-parse@2.7.0: + resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + dev: true + + /semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + + /semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + /semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + dev: true + + /semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + + /send@0.18.0: + resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + engines: {node: '>= 0.8.0'} + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + /serve-static@1.15.0: + resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} + engines: {node: '>= 0.8.0'} + dependencies: + encodeurl: 1.0.2 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.18.0 + transitivePeerDependencies: + - supports-color + + /set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + dev: true + + /setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + /sharp@0.31.2: + resolution: {integrity: sha512-DUdNVEXgS5A97cTagSLIIp8dUZ/lZtk78iNVZgHdHbx1qnQR7JAHY0BnXnwwH39Iw+VKhO08CTYhIg0p98vQ5Q==} + engines: {node: '>=14.15.0'} + requiresBuild: true + dependencies: + color: 4.2.3 + detect-libc: 2.0.2 + node-addon-api: 5.1.0 + prebuild-install: 7.1.1 + semver: 7.5.4 + simple-get: 4.0.1 + tar-fs: 2.1.1 + tunnel-agent: 0.6.0 + dev: false + + /shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + dependencies: + shebang-regex: 1.0.0 + dev: true + + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: true + + /shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + dev: true + + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: true + + /side-channel@1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + object-inspect: 1.12.3 + + /signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: true + + /simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + dev: false + + /simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + dev: false + + /simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + dependencies: + is-arrayish: 0.3.2 + dev: false + + /sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + dev: true + + /slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + dev: true + + /smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + dev: true + + /smartwrap@2.0.2: + resolution: {integrity: sha512-vCsKNQxb7PnCNd2wY1WClWifAc2lwqsG8OaswpJkVJsvMGcnEntdTCDajZCkk93Ay1U3t/9puJmb525Rg5MZBA==} + engines: {node: '>=6'} + hasBin: true + dependencies: + array.prototype.flat: 1.3.1 + breakword: 1.0.6 + grapheme-splitter: 1.0.4 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + yargs: 15.4.1 + dev: true + + /socks-proxy-agent@7.0.0: + resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==} + engines: {node: '>= 10'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + socks: 2.7.1 + transitivePeerDependencies: + - supports-color + dev: true + + /socks@2.7.1: + resolution: {integrity: sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==} + engines: {node: '>= 10.13.0', npm: '>= 3.0.0'} + dependencies: + ip: 2.0.0 + smart-buffer: 4.2.0 + dev: true + + /sonic-boom@3.3.0: + resolution: {integrity: sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g==} + dependencies: + atomic-sleep: 1.0.0 + + /source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + /source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + dev: false + + /spawndamnit@2.0.0: + resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} + dependencies: + cross-spawn: 5.1.0 + signal-exit: 3.0.7 + dev: true + + /spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.13 + dev: true + + /spdx-exceptions@2.3.0: + resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + dev: true + + /spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + dependencies: + spdx-exceptions: 2.3.0 + spdx-license-ids: 3.0.13 + dev: true + + /spdx-license-ids@3.0.13: + resolution: {integrity: sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==} + dev: true + + /split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + /split@1.0.1: + resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} + dependencies: + through: 2.3.8 + dev: false + + /sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + dev: true + + /ssri@9.0.1: + resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + minipass: 3.3.6 + dev: true + + /stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + dependencies: + escape-string-regexp: 2.0.0 + dev: true + + /standard-as-callback@2.1.0: + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + dev: false + + /statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + /stream-browserify@3.0.0: + resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: false + + /stream-transform@2.1.3: + resolution: {integrity: sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ==} + dependencies: + mixme: 0.5.9 + dev: true + + /string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + dev: true + + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: true + + /string.prototype.trim@1.2.7: + resolution: {integrity: sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + dev: true + + /string.prototype.trimend@1.0.6: + resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + dev: true + + /string.prototype.trimstart@1.0.7: + resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + dev: true + + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + + /strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + dev: true + + /strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + dev: true + + /strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + dev: true + + /strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + dependencies: + min-indent: 1.0.1 + dev: true + + /strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + dev: false + + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /strnum@1.0.5: + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + dev: false + + /strtok3@6.3.0: + resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==} + engines: {node: '>=10'} + dependencies: + '@tokenizer/token': 0.3.0 + peek-readable: 4.1.0 + dev: false + + /supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + dev: true + + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: true + + /supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + dependencies: + has-flag: 4.0.0 + dev: true + + /supports-hyperlinks@2.3.0: + resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + dev: true + + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true + + /tar-fs@2.1.1: + resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.0 + tar-stream: 2.2.0 + dev: false + + /tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: false + + /tar@6.1.15: + resolution: {integrity: sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==} + engines: {node: '>=10'} + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + dev: true + + /term-size@2.2.1: + resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} + engines: {node: '>=8'} + dev: true + + /terminal-link@2.1.1: + resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} + engines: {node: '>=8'} + dependencies: + ansi-escapes: 4.3.2 + supports-hyperlinks: 2.3.0 + dev: true + + /test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + dev: true + + /text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true + + /thread-stream@2.4.0: + resolution: {integrity: sha512-xZYtOtmnA63zj04Q+F9bdEay5r47bvpo1CaNqsKi7TpoJHcotUez8Fkfo2RJWpW91lnnaApdpRbVwCWsy+ifcw==} + dependencies: + real-require: 0.2.0 + + /through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + dev: false + + /tlds@1.234.0: + resolution: {integrity: sha512-TNDfeyDIC+oroH44bMbWC+Jn/2qNrfRvDK2EXt1icOXYG5NMqoRyUosADrukfb4D8lJ3S1waaBWSvQro0erdng==} + hasBin: true + dev: false + + /tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + dependencies: + os-tmpdir: 1.0.2 + dev: true + + /tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + dev: true + + /to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + dev: true + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + + /toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + /token-types@4.2.1: + resolution: {integrity: sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==} + engines: {node: '>=10'} + dependencies: + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + dev: false + + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: true + + /trim-newlines@3.0.1: + resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} + engines: {node: '>=8'} + dev: true + + /ts-morph@16.0.0: + resolution: {integrity: sha512-jGNF0GVpFj0orFw55LTsQxVYEUOCWBAbR5Ls7fTYE5pQsbW18ssTb/6UXx/GYAEjS+DQTp8VoTw0vqYMiaaQuw==} + dependencies: + '@ts-morph/common': 0.17.0 + code-block-writer: 11.0.3 + dev: false + + /ts-node@10.8.2(@swc/core@1.3.42)(@types/node@18.0.0)(typescript@4.8.4): + resolution: {integrity: sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@swc/core': 1.3.42 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 18.0.0 + acorn: 8.10.0 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.8.4 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + + /ts-node@10.8.2(@swc/core@1.3.42)(@types/node@18.17.8)(typescript@4.9.5): + resolution: {integrity: sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@swc/core': 1.3.42 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 18.17.8 + acorn: 8.10.0 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.9.5 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + + /tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + /tslib@2.3.1: + resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==} + dev: true + + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + dev: false + + /tsutils@3.21.0(typescript@4.8.4): + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 4.8.4 + dev: true + + /tty-table@4.2.1: + resolution: {integrity: sha512-xz0uKo+KakCQ+Dxj1D/tKn2FSyreSYWzdkL/BYhgN6oMW808g8QRMuh1atAV9fjTPbWBjfbkKQpI/5rEcnAc7g==} + engines: {node: '>=8.0.0'} + hasBin: true + dependencies: + chalk: 4.1.2 + csv: 5.5.3 + kleur: 4.1.5 + smartwrap: 2.0.2 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + yargs: 17.7.2 + dev: true + + /tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + dev: true + + /type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: true + + /type-fest@0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + dev: true + + /type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + dev: true + + /type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + dev: true + + /type-fest@0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + dev: true + + /type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + dev: true + + /type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + + /type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + /typed-array-buffer@1.0.0: + resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + is-typed-array: 1.1.12 + dev: true + + /typed-array-byte-length@1.0.0: + resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + for-each: 0.3.3 + has-proto: 1.0.1 + is-typed-array: 1.1.12 + dev: true + + /typed-array-byte-offset@1.0.0: + resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + has-proto: 1.0.1 + is-typed-array: 1.1.12 + dev: true + + /typed-array-length@1.0.4: + resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + dependencies: + call-bind: 1.0.2 + for-each: 0.3.3 + is-typed-array: 1.1.12 + dev: true + + /typed-emitter@2.1.0: + resolution: {integrity: sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==} + optionalDependencies: + rxjs: 7.8.1 + dev: false + + /typescript@4.8.4: + resolution: {integrity: sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + + /typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + + /uglify-js@3.17.4: + resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} + engines: {node: '>=0.8.0'} + hasBin: true + requiresBuild: true + dev: false + optional: true + + /uint8arrays@3.0.0: + resolution: {integrity: sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==} + dependencies: + multiformats: 9.9.0 + + /unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + dependencies: + call-bind: 1.0.2 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + dev: true + + /unicode-canonical-property-names-ecmascript@2.0.0: + resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} + engines: {node: '>=4'} + dev: true + + /unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.0 + unicode-property-aliases-ecmascript: 2.1.0 + dev: true + + /unicode-match-property-value-ecmascript@2.1.0: + resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==} + engines: {node: '>=4'} + dev: true + + /unicode-property-aliases-ecmascript@2.1.0: + resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} + engines: {node: '>=4'} + dev: true + + /unique-filename@2.0.1: + resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + unique-slug: 3.0.0 + dev: true + + /unique-slug@3.0.0: + resolution: {integrity: sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + imurmurhash: 0.1.4 + dev: true + + /universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + dev: true + + /universalify@2.0.0: + resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} + engines: {node: '>= 10.0.0'} + dev: true + + /unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + /update-browserslist-db@1.0.11(browserslist@4.21.10): + resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + dependencies: + browserslist: 4.21.10 + escalade: 3.1.1 + picocolors: 1.0.0 + dev: true + + /uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.3.0 + + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + /utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + /uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + dev: false + + /v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true + + /v8-to-istanbul@9.1.0: + resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==} + engines: {node: '>=10.12.0'} + dependencies: + '@jridgewell/trace-mapping': 0.3.19 + '@types/istanbul-lib-coverage': 2.0.4 + convert-source-map: 1.9.0 + dev: true + + /validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + dev: true + + /varint@6.0.0: + resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} + dev: false + + /vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + /walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + dependencies: + makeerror: 1.0.12 + dev: true + + /wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + dependencies: + defaults: 1.0.4 + dev: true + + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: true + + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: true + + /which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + dev: true + + /which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + dev: true + + /which-pm@2.0.0: + resolution: {integrity: sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==} + engines: {node: '>=8.15'} + dependencies: + load-yaml-file: 0.2.0 + path-exists: 4.0.0 + dev: true + + /which-typed-array@1.1.11: + resolution: {integrity: sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + dev: true + + /which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + dependencies: + string-width: 4.2.3 + dev: true + + /wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + dev: false + + /wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + /write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + dev: true + + /ws@8.12.0: + resolution: {integrity: sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: false + + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + /y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + dev: true + + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: true + + /yallist@2.1.2: + resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + dev: true + + /yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + dev: true + + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + /yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + dev: true + + /yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + dev: true + + /yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + dev: true + + /yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + dependencies: + cliui: 8.0.1 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + dev: true + + /yesno@0.4.0: + resolution: {integrity: sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA==} + dev: false + + /yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true + + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + /zod@3.21.4: + resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000000..ceb38b3a8f3 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - 'services/*' + - 'packages/*' diff --git a/packages/bsky/Dockerfile b/services/bsky/Dockerfile similarity index 65% rename from packages/bsky/Dockerfile rename to services/bsky/Dockerfile index 04feb758ac2..9da764ecc3d 100644 --- a/packages/bsky/Dockerfile +++ b/services/bsky/Dockerfile @@ -1,5 +1,7 @@ FROM node:18-alpine as build +RUN npm install -g pnpm + # Move files into the image and install WORKDIR /app COPY ./*.* ./ @@ -11,31 +13,35 @@ COPY ./packages/common ./packages/common COPY ./packages/common-web ./packages/common-web COPY ./packages/crypto ./packages/crypto COPY ./packages/identity ./packages/identity -COPY ./packages/identifier ./packages/identifier +COPY ./packages/syntax ./packages/syntax COPY ./packages/lexicon ./packages/lexicon -COPY ./packages/nsid ./packages/nsid COPY ./packages/repo ./packages/repo -COPY ./packages/uri ./packages/uri COPY ./packages/xrpc ./packages/xrpc COPY ./packages/xrpc-server ./packages/xrpc-server -RUN ATP_BUILD_SHALLOW=true yarn install --frozen-lockfile > /dev/null -RUN yarn workspaces run build --update-main-to-dist > /dev/null -# Remove non-prod deps -RUN yarn install --production --ignore-scripts --prefer-offline > /dev/null +COPY ./services/bsky ./services/bsky + +# install all deps +RUN pnpm install --frozen-lockfile > /dev/null +# build all packages with external node_modules +RUN ATP_BUILD_SHALLOW=true pnpm build > /dev/null +# update main with publishConfig +RUN pnpm update-main-to-dist > /dev/null +# clean up +RUN rm -rf node_modules +# install only prod deps, hoisted to root node_modules dir +RUN pnpm install --prod --shamefully-hoist --frozen-lockfile --prefer-offline > /dev/null -WORKDIR packages/bsky/service -RUN yarn install --frozen-lockfile > /dev/null +WORKDIR services/bsky # Uses assets from build stage to reduce build size FROM node:18-alpine -# RUN npm install -g yarn RUN apk add --update dumb-init # Avoid zombie processes, handle signal forwarding ENTRYPOINT ["dumb-init", "--"] -WORKDIR /app/packages/bsky/service +WORKDIR /app/services/bsky COPY --from=build /app /app EXPOSE 3000 @@ -44,7 +50,7 @@ ENV NODE_ENV=production # https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#non-root-user USER node -CMD ["node", "--enable-source-maps", "index.js"] +CMD ["node", "--enable-source-maps", "api.js"] LABEL org.opencontainers.image.source=https://github.com/bluesky-social/atproto LABEL org.opencontainers.image.description="Bsky App View" diff --git a/packages/bsky/service/index.js b/services/bsky/api.js similarity index 54% rename from packages/bsky/service/index.js rename to services/bsky/api.js index f7b4180a8a5..ec38c55ae55 100644 --- a/packages/bsky/service/index.js +++ b/services/bsky/api.js @@ -1,6 +1,7 @@ 'use strict' /* eslint-disable */ -require('dd-trace/init') // Only works with commonjs +require('dd-trace') // Only works with commonjs + .init({ logInjection: true }) .tracer.use('express', { hooks: { request: (span, req) => { @@ -11,37 +12,45 @@ require('dd-trace/init') // Only works with commonjs // Tracer code above must come before anything else const path = require('path') +const assert = require('assert') const { CloudfrontInvalidator } = require('@atproto/aws') const { - Database, + DatabaseCoordinator, + PrimaryDatabase, ServerConfig, BskyAppView, ViewMaintainer, makeAlgos, + PeriodicModerationActionReversal, } = require('@atproto/bsky') const main = async () => { const env = getEnv() - // Migrate using credentialed user - const migrateDb = Database.postgres({ - url: env.dbMigratePostgresUrl, + assert(env.dbPrimaryPostgresUrl, 'missing configuration for db') + const db = new DatabaseCoordinator({ schema: env.dbPostgresSchema, - poolSize: 2, - }) - await migrateDb.migrateToLatestOrThrow() - // Use lower-credentialed user to run the app - const db = Database.postgres({ - url: env.dbPostgresUrl, - schema: env.dbSchema, - poolSize: env.dbPoolSize, - poolMaxUses: env.dbPoolMaxUses, - poolIdleTimeoutMs: env.dbPoolIdleTimeoutMs, + primary: { + url: env.dbPrimaryPostgresUrl, + poolSize: env.dbPrimaryPoolSize || env.dbPoolSize, + poolMaxUses: env.dbPoolMaxUses, + poolIdleTimeoutMs: env.dbPoolIdleTimeoutMs, + }, + replicas: env.dbReplicaPostgresUrls?.map((url, i) => { + return { + url, + poolSize: env.dbPoolSize, + poolMaxUses: env.dbPoolMaxUses, + poolIdleTimeoutMs: env.dbPoolIdleTimeoutMs, + tags: getTagsForIdx(env.dbReplicaTags, i), + } + }), }) const cfg = ServerConfig.readEnv({ port: env.port, version: env.version, - repoProvider: env.repoProvider, - dbPostgresUrl: env.dbPostgresUrl, + dbPrimaryPostgresUrl: env.dbPrimaryPostgresUrl, + dbReplicaPostgresUrls: env.dbReplicaPostgresUrls, + dbReplicaTags: env.dbReplicaTags, dbPostgresSchema: env.dbPostgresSchema, publicUrl: env.publicUrl, didPlcUrl: env.didPlcUrl, @@ -63,11 +72,27 @@ const main = async () => { imgInvalidator: cfInvalidator, algos, }) + // separate db needed for more permissions + const migrateDb = new PrimaryDatabase({ + url: env.dbMigratePostgresUrl, + schema: env.dbPostgresSchema, + poolSize: 2, + }) const viewMaintainer = new ViewMaintainer(migrateDb) const viewMaintainerRunning = viewMaintainer.run() + + const periodicModerationActionReversal = new PeriodicModerationActionReversal( + bsky.ctx, + ) + const periodicModerationActionReversalRunning = + periodicModerationActionReversal.run() + await bsky.start() // Graceful shutdown (see also https://aws.amazon.com/blogs/containers/graceful-shutdowns-with-ecs/) process.on('SIGTERM', async () => { + // Gracefully shutdown periodic-moderation-action-reversal before destroying bsky instance + periodicModerationActionReversal.destroy() + await periodicModerationActionReversalRunning await bsky.destroy() viewMaintainer.destroy() await viewMaintainerRunning @@ -78,10 +103,20 @@ const main = async () => { const getEnv = () => ({ port: parseInt(process.env.PORT), version: process.env.BSKY_VERSION, - repoProvider: process.env.REPO_PROVIDER, - dbPostgresUrl: process.env.DB_POSTGRES_URL, dbMigratePostgresUrl: - process.env.DB_MIGRATE_POSTGRES_URL || process.env.DB_POSTGRES_URL, + process.env.DB_MIGRATE_POSTGRES_URL || process.env.DB_PRIMARY_POSTGRES_URL, + dbPrimaryPostgresUrl: process.env.DB_PRIMARY_POSTGRES_URL, + dbPrimaryPoolSize: maybeParseInt(process.env.DB_PRIMARY_POOL_SIZE), + dbReplicaPostgresUrls: process.env.DB_REPLICA_POSTGRES_URLS + ? process.env.DB_REPLICA_POSTGRES_URLS.split(',') + : undefined, + dbReplicaTags: { + '*': getTagIdxs(process.env.DB_REPLICA_TAGS_ANY), // e.g. DB_REPLICA_TAGS_ANY=0,1 + timeline: getTagIdxs(process.env.DB_REPLICA_TAGS_TIMELINE), + feed: getTagIdxs(process.env.DB_REPLICA_TAGS_FEED), + search: getTagIdxs(process.env.DB_REPLICA_TAGS_SEARCH), + thread: getTagIdxs(process.env.DB_REPLICA_TAGS_THREAD), + }, dbPostgresSchema: process.env.DB_POSTGRES_SCHEMA || undefined, dbPoolSize: maybeParseInt(process.env.DB_POOL_SIZE), dbPoolMaxUses: maybeParseInt(process.env.DB_POOL_MAX_USES), @@ -96,6 +131,27 @@ const getEnv = () => ({ feedPublisherDid: process.env.FEED_PUBLISHER_DID, }) +/** + * @param {Record} tags + * @param {number} idx + */ +const getTagsForIdx = (tagMap, idx) => { + const tags = [] + for (const [tag, indexes] of Object.entries(tagMap)) { + if (indexes.includes(idx)) { + tags.push(tag) + } + } + return tags +} + +/** + * @param {string} str + */ +const getTagIdxs = (str) => { + return str ? str.split(',').map((item) => parseInt(item, 10)) : [] +} + const maybeParseInt = (str) => { const parsed = parseInt(str) return isNaN(parsed) ? undefined : parsed diff --git a/services/bsky/indexer.js b/services/bsky/indexer.js new file mode 100644 index 00000000000..7ab287133df --- /dev/null +++ b/services/bsky/indexer.js @@ -0,0 +1,89 @@ +'use strict' /* eslint-disable */ + +require('dd-trace/init') // Only works with commonjs + +// Tracer code above must come before anything else +const { CloudfrontInvalidator } = require('@atproto/aws') +const { + IndexerConfig, + BskyIndexer, + Redis, + PrimaryDatabase, +} = require('@atproto/bsky') + +const main = async () => { + const env = getEnv() + const db = new PrimaryDatabase({ + url: env.dbPostgresUrl, + schema: env.dbPostgresSchema, + poolSize: env.dbPoolSize, + poolMaxUses: env.dbPoolMaxUses, + poolIdleTimeoutMs: env.dbPoolIdleTimeoutMs, + }) + const cfg = IndexerConfig.readEnv({ + version: env.version, + dbPostgresUrl: env.dbPostgresUrl, + dbPostgresSchema: env.dbPostgresSchema, + }) + const cfInvalidator = env.cfDistributionId + ? new CloudfrontInvalidator({ + distributionId: env.cfDistributionId, + pathPrefix: cfg.imgUriEndpoint && new URL(cfg.imgUriEndpoint).pathname, + }) + : undefined + const redis = new Redis( + cfg.redisSentinelName + ? { + sentinel: cfg.redisSentinelName, + hosts: cfg.redisSentinelHosts, + password: cfg.redisPassword, + } + : { + host: cfg.redisHost, + password: cfg.redisPassword, + }, + ) + const indexer = BskyIndexer.create({ + db, + redis, + cfg, + imgInvalidator: cfInvalidator, + }) + await indexer.start() + process.on('SIGTERM', async () => { + await indexer.destroy() + }) +} + +// Also accepts the following in readEnv(): +// - REDIS_HOST +// - REDIS_SENTINEL_NAME +// - REDIS_SENTINEL_HOSTS +// - REDIS_PASSWORD +// - DID_PLC_URL +// - DID_CACHE_STALE_TTL +// - DID_CACHE_MAX_TTL +// - LABELER_DID +// - HIVE_API_KEY +// - INDEXER_PARTITION_IDS +// - INDEXER_PARTITION_BATCH_SIZE +// - INDEXER_CONCURRENCY +// - INDEXER_SUB_LOCK_ID +const getEnv = () => ({ + version: process.env.BSKY_VERSION, + dbPostgresUrl: + process.env.DB_PRIMARY_POSTGRES_URL || process.env.DB_POSTGRES_URL, + dbPostgresSchema: process.env.DB_POSTGRES_SCHEMA || undefined, + dbPoolSize: maybeParseInt(process.env.DB_POOL_SIZE), + dbPoolMaxUses: maybeParseInt(process.env.DB_POOL_MAX_USES), + dbPoolIdleTimeoutMs: maybeParseInt(process.env.DB_POOL_IDLE_TIMEOUT_MS), + cfDistributionId: process.env.CF_DISTRIBUTION_ID, + imgUriEndpoint: process.env.IMG_URI_ENDPOINT, +}) + +const maybeParseInt = (str) => { + const parsed = parseInt(str) + return isNaN(parsed) ? undefined : parsed +} + +main() diff --git a/services/bsky/ingester.js b/services/bsky/ingester.js new file mode 100644 index 00000000000..19c33ea1067 --- /dev/null +++ b/services/bsky/ingester.js @@ -0,0 +1,75 @@ +'use strict' /* eslint-disable */ + +require('dd-trace/init') // Only works with commonjs + +// Tracer code above must come before anything else +const { + PrimaryDatabase, + IngesterConfig, + BskyIngester, + Redis, +} = require('@atproto/bsky') + +const main = async () => { + const env = getEnv() + // No migration: ingester only uses pg for a lock + const db = new PrimaryDatabase({ + url: env.dbPostgresUrl, + schema: env.dbPostgresSchema, + poolSize: env.dbPoolSize, + poolMaxUses: env.dbPoolMaxUses, + poolIdleTimeoutMs: env.dbPoolIdleTimeoutMs, + }) + const cfg = IngesterConfig.readEnv({ + version: env.version, + dbPostgresUrl: env.dbPostgresUrl, + dbPostgresSchema: env.dbPostgresSchema, + repoProvider: env.repoProvider, + ingesterSubLockId: env.subLockId, + }) + const redis = new Redis( + cfg.redisSentinelName + ? { + sentinel: cfg.redisSentinelName, + hosts: cfg.redisSentinelHosts, + password: cfg.redisPassword, + } + : { + host: cfg.redisHost, + password: cfg.redisPassword, + }, + ) + const ingester = BskyIngester.create({ db, redis, cfg }) + await ingester.start() + process.on('SIGTERM', async () => { + await ingester.destroy() + }) +} + +// Also accepts the following in readEnv(): +// - REDIS_HOST +// - REDIS_SENTINEL_NAME +// - REDIS_SENTINEL_HOSTS +// - REDIS_PASSWORD +// - REPO_PROVIDER +// - INGESTER_PARTITION_COUNT +// - INGESTER_MAX_ITEMS +// - INGESTER_CHECK_ITEMS_EVERY_N +// - INGESTER_INITIAL_CURSOR +// - INGESTER_SUB_LOCK_ID +const getEnv = () => ({ + version: process.env.BSKY_VERSION, + dbPostgresUrl: + process.env.DB_PRIMARY_POSTGRES_URL || process.env.DB_POSTGRES_URL, + dbPostgresSchema: process.env.DB_POSTGRES_SCHEMA || undefined, + dbPoolSize: maybeParseInt(process.env.DB_POOL_SIZE), + dbPoolMaxUses: maybeParseInt(process.env.DB_POOL_MAX_USES), + dbPoolIdleTimeoutMs: maybeParseInt(process.env.DB_POOL_IDLE_TIMEOUT_MS), +}) + +const maybeParseInt = (str) => { + const parsed = parseInt(str) + return isNaN(parsed) ? undefined : parsed +} + +main() diff --git a/services/bsky/package.json b/services/bsky/package.json new file mode 100644 index 00000000000..65de10674dc --- /dev/null +++ b/services/bsky/package.json @@ -0,0 +1,9 @@ +{ + "name": "bsky-app-view-service", + "private": true, + "dependencies": { + "@atproto/aws": "workspace:^", + "@atproto/bsky": "workspace:^", + "dd-trace": "3.13.2" + } +} diff --git a/packages/pds/Dockerfile b/services/pds/Dockerfile similarity index 70% rename from packages/pds/Dockerfile rename to services/pds/Dockerfile index e93b3e0e031..c108df56ddd 100644 --- a/packages/pds/Dockerfile +++ b/services/pds/Dockerfile @@ -1,5 +1,7 @@ FROM node:18-alpine as build +RUN npm install -g pnpm + # Move files into the image and install WORKDIR /app COPY ./*.* ./ @@ -10,34 +12,37 @@ COPY ./packages/aws ./packages/aws COPY ./packages/common ./packages/common COPY ./packages/common-web ./packages/common-web COPY ./packages/crypto ./packages/crypto -COPY ./packages/identifier ./packages/identifier +COPY ./packages/syntax ./packages/syntax COPY ./packages/identity ./packages/identity COPY ./packages/lex-cli ./packages/lex-cli COPY ./packages/lexicon ./packages/lexicon -COPY ./packages/nsid ./packages/nsid -COPY ./packages/pds ./packages/pds COPY ./packages/repo ./packages/repo -COPY ./packages/uri ./packages/uri COPY ./packages/xrpc ./packages/xrpc COPY ./packages/xrpc-server ./packages/xrpc-server -RUN ATP_BUILD_SHALLOW=true yarn install --frozen-lockfile > /dev/null -RUN yarn workspaces run build --update-main-to-dist > /dev/null -# Remove non-prod deps -RUN yarn install --production --ignore-scripts --prefer-offline > /dev/null +COPY ./services/pds ./services/pds + +# install all deps +RUN pnpm install --frozen-lockfile > /dev/null +# build all packages with external node_modules +RUN ATP_BUILD_SHALLOW=true pnpm build > /dev/null +# update main with publishConfig +RUN pnpm update-main-to-dist > /dev/null +# clean up +RUN rm -rf node_modules +# install only prod deps, hoisted to root node_modules dir +RUN pnpm install --prod --shamefully-hoist --frozen-lockfile --prefer-offline > /dev/null -WORKDIR packages/pds/service -RUN yarn install --frozen-lockfile > /dev/null +WORKDIR services/pds # Uses assets from build stage to reduce build size FROM node:18-alpine -# RUN npm install -g yarn RUN apk add --update dumb-init # Avoid zombie processes, handle signal forwarding ENTRYPOINT ["dumb-init", "--"] -WORKDIR /app/packages/pds/service +WORKDIR /app/services/pds COPY --from=build /app /app RUN mkdir /app/data && chown node /app/data diff --git a/packages/pds/service/index.js b/services/pds/index.js similarity index 69% rename from packages/pds/service/index.js rename to services/pds/index.js index 59a71492363..c2cabea4f73 100644 --- a/packages/pds/service/index.js +++ b/services/pds/index.js @@ -1,6 +1,7 @@ 'use strict' /* eslint-disable */ -require('dd-trace/init') // Only works with commonjs +require('dd-trace') // Only works with commonjs + .init({ logInjection: true }) .tracer.use('express', { hooks: { request: (span, req) => { @@ -18,6 +19,7 @@ const { envToSecrets, readEnv, httpLogger, + PeriodicModerationActionReversal, } = require('@atproto/pds') const pkg = require('@atproto/pds/package.json') @@ -38,12 +40,28 @@ const main = async () => { } else { await pds.ctx.db.migrateToLatestOrThrow() } + + // If the PDS is configured to proxy moderation, this will be running on appview instead of pds. + // Also don't run this on the sequencer leader, which may not be configured regarding moderation proxying at all. + const periodicModerationActionReversal = + pds.ctx.shouldProxyModeration() || pds.ctx.cfg.sequencerLeaderEnabled + ? null + : new PeriodicModerationActionReversal(pds.ctx) + const periodicModerationActionReversalRunning = + periodicModerationActionReversal?.run() + await pds.start() + httpLogger.info('pds is running') // Graceful shutdown (see also https://aws.amazon.com/blogs/containers/graceful-shutdowns-with-ecs/) process.on('SIGTERM', async () => { httpLogger.info('pds is stopping') + + periodicModerationActionReversal?.destroy() + await periodicModerationActionReversalRunning + await pds.destroy() + httpLogger.info('pds is stopped') }) } diff --git a/services/pds/package.json b/services/pds/package.json new file mode 100644 index 00000000000..96729d806e6 --- /dev/null +++ b/services/pds/package.json @@ -0,0 +1,10 @@ +{ + "name": "plc-service", + "private": true, + "dependencies": { + "@atproto/aws": "workspace:^", + "@atproto/crypto": "workspace:^", + "@atproto/pds": "workspace:^", + "dd-trace": "3.13.2" + } +} diff --git a/tsconfig.json b/tsconfig.json index efc5c955572..e9a88ea1164 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,8 +19,7 @@ "lib": ["dom", "dom.iterable", "esnext", "webworker"], "skipLibCheck": true, "forceConsistentCasingInFileNames": true, - "isolatedModules": true, - "composite": true + "isolatedModules": true }, "exclude": ["node_modules", "**/*/dist"], "references": [ @@ -34,6 +33,7 @@ { "path": "./packages/dev-env" }, { "path": "./packages/identity/tsconfig.build.json" }, { "path": "./packages/identifier/tsconfig.build.json" }, + { "path": "./packages/syntax/tsconfig.build.json" }, { "path": "./packages/lexicon/tsconfig.build.json" }, { "path": "./packages/lex-cli/tsconfig.build.json" }, { "path": "./packages/nsid/tsconfig.build.json" }, diff --git a/update-main-to-dist.js b/update-main-to-dist.js new file mode 100644 index 00000000000..fe625caa4d6 --- /dev/null +++ b/update-main-to-dist.js @@ -0,0 +1,9 @@ +const path = require('path') +const pkgJson = require('@npmcli/package-json') + +const [dir] = process.argv.slice(2) + +pkgJson + .load(path.resolve(__dirname, dir)) + .then((pkg) => pkg.update({ main: pkg.content.publishConfig.main })) + .then((pkg) => pkg.save()) diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index d8c71fc3c43..00000000000 --- a/yarn.lock +++ /dev/null @@ -1,12098 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ampproject/remapping@^2.1.0": - version "2.2.0" - resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz" - integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== - dependencies: - "@jridgewell/gen-mapping" "^0.1.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@atproto/common@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.1.0.tgz#4216a8fef5b985ab62ac21252a0f8ca0f4a0f210" - integrity sha512-OB5tWE2R19jwiMIs2IjQieH5KTUuMb98XGCn9h3xuu6NanwjlmbCYMv08fMYwIp3UQ6jcq//84cDT3Bu6fJD+A== - dependencies: - "@ipld/dag-cbor" "^7.0.3" - multiformats "^9.6.4" - pino "^8.6.1" - zod "^3.14.2" - -"@atproto/crypto@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@atproto/crypto/-/crypto-0.1.0.tgz#bc73a479f9dbe06fa025301c182d7f7ab01bc568" - integrity sha512-9xgFEPtsCiJEPt9o3HtJT30IdFTGw5cQRSJVIy5CFhqBA4vDLcdXiRDLCjkzHEVbtNCsHUW6CrlfOgbeLPcmcg== - dependencies: - "@noble/secp256k1" "^1.7.0" - big-integer "^1.6.51" - multiformats "^9.6.4" - one-webcrypto "^1.0.3" - uint8arrays "3.0.0" - -"@aws-crypto/crc32@2.0.0": - version "2.0.0" - resolved "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-2.0.0.tgz" - integrity sha512-TvE1r2CUueyXOuHdEigYjIZVesInd9KN+K/TFFNfkkxRThiNxO6i4ZqqAVMoEjAamZZ1AA8WXJkjCz7YShHPQA== - dependencies: - "@aws-crypto/util" "^2.0.0" - "@aws-sdk/types" "^3.1.0" - tslib "^1.11.1" - -"@aws-crypto/crc32c@2.0.0": - version "2.0.0" - resolved "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-2.0.0.tgz" - integrity sha512-vF0eMdMHx3O3MoOXUfBZry8Y4ZDtcuskjjKgJz8YfIDjLStxTZrYXk+kZqtl6A0uCmmiN/Eb/JbC/CndTV1MHg== - dependencies: - "@aws-crypto/util" "^2.0.0" - "@aws-sdk/types" "^3.1.0" - tslib "^1.11.1" - -"@aws-crypto/ie11-detection@^2.0.0": - version "2.0.2" - resolved "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.2.tgz" - integrity sha512-5XDMQY98gMAf/WRTic5G++jfmS/VLM0rwpiOpaainKi4L0nqWMSB1SzsrEG5rjFZGYN6ZAefO+/Yta2dFM0kMw== - dependencies: - tslib "^1.11.1" - -"@aws-crypto/ie11-detection@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz#640ae66b4ec3395cee6a8e94ebcd9f80c24cd688" - integrity sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q== - dependencies: - tslib "^1.11.1" - -"@aws-crypto/sha1-browser@2.0.0": - version "2.0.0" - resolved "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-2.0.0.tgz" - integrity sha512-3fIVRjPFY8EG5HWXR+ZJZMdWNRpwbxGzJ9IH9q93FpbgCH8u8GHRi46mZXp3cYD7gealmyqpm3ThZwLKJjWJhA== - dependencies: - "@aws-crypto/ie11-detection" "^2.0.0" - "@aws-crypto/supports-web-crypto" "^2.0.0" - "@aws-sdk/types" "^3.1.0" - "@aws-sdk/util-locate-window" "^3.0.0" - "@aws-sdk/util-utf8-browser" "^3.0.0" - tslib "^1.11.1" - -"@aws-crypto/sha256-browser@2.0.0": - version "2.0.0" - resolved "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-2.0.0.tgz" - integrity sha512-rYXOQ8BFOaqMEHJrLHul/25ckWH6GTJtdLSajhlqGMx0PmSueAuvboCuZCTqEKlxR8CQOwRarxYMZZSYlhRA1A== - dependencies: - "@aws-crypto/ie11-detection" "^2.0.0" - "@aws-crypto/sha256-js" "^2.0.0" - "@aws-crypto/supports-web-crypto" "^2.0.0" - "@aws-crypto/util" "^2.0.0" - "@aws-sdk/types" "^3.1.0" - "@aws-sdk/util-locate-window" "^3.0.0" - "@aws-sdk/util-utf8-browser" "^3.0.0" - tslib "^1.11.1" - -"@aws-crypto/sha256-browser@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz#05f160138ab893f1c6ba5be57cfd108f05827766" - integrity sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ== - dependencies: - "@aws-crypto/ie11-detection" "^3.0.0" - "@aws-crypto/sha256-js" "^3.0.0" - "@aws-crypto/supports-web-crypto" "^3.0.0" - "@aws-crypto/util" "^3.0.0" - "@aws-sdk/types" "^3.222.0" - "@aws-sdk/util-locate-window" "^3.0.0" - "@aws-sdk/util-utf8-browser" "^3.0.0" - tslib "^1.11.1" - -"@aws-crypto/sha256-js@2.0.0": - version "2.0.0" - resolved "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-2.0.0.tgz" - integrity sha512-VZY+mCY4Nmrs5WGfitmNqXzaE873fcIZDu54cbaDaaamsaTOP1DBImV9F4pICc3EHjQXujyE8jig+PFCaew9ig== - dependencies: - "@aws-crypto/util" "^2.0.0" - "@aws-sdk/types" "^3.1.0" - tslib "^1.11.1" - -"@aws-crypto/sha256-js@3.0.0", "@aws-crypto/sha256-js@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz#f06b84d550d25521e60d2a0e2a90139341e007c2" - integrity sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ== - dependencies: - "@aws-crypto/util" "^3.0.0" - "@aws-sdk/types" "^3.222.0" - tslib "^1.11.1" - -"@aws-crypto/sha256-js@^2.0.0": - version "2.0.2" - resolved "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-2.0.2.tgz" - integrity sha512-iXLdKH19qPmIC73fVCrHWCSYjN/sxaAvZ3jNNyw6FclmHyjLKg0f69WlC9KTnyElxCR5MO9SKaG00VwlJwyAkQ== - dependencies: - "@aws-crypto/util" "^2.0.2" - "@aws-sdk/types" "^3.110.0" - tslib "^1.11.1" - -"@aws-crypto/supports-web-crypto@^2.0.0": - version "2.0.2" - resolved "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-2.0.2.tgz" - integrity sha512-6mbSsLHwZ99CTOOswvCRP3C+VCWnzBf+1SnbWxzzJ9lR0mA0JnY2JEAhp8rqmTE0GPFy88rrM27ffgp62oErMQ== - dependencies: - tslib "^1.11.1" - -"@aws-crypto/supports-web-crypto@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz#5d1bf825afa8072af2717c3e455f35cda0103ec2" - integrity sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg== - dependencies: - tslib "^1.11.1" - -"@aws-crypto/util@^2.0.0", "@aws-crypto/util@^2.0.2": - version "2.0.2" - resolved "https://registry.npmjs.org/@aws-crypto/util/-/util-2.0.2.tgz" - integrity sha512-Lgu5v/0e/BcrZ5m/IWqzPUf3UYFTy/PpeED+uc9SWUR1iZQL8XXbGQg10UfllwwBryO3hFF5dizK+78aoXC1eA== - dependencies: - "@aws-sdk/types" "^3.110.0" - "@aws-sdk/util-utf8-browser" "^3.0.0" - tslib "^1.11.1" - -"@aws-crypto/util@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/util/-/util-3.0.0.tgz#1c7ca90c29293f0883468ad48117937f0fe5bfb0" - integrity sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w== - dependencies: - "@aws-sdk/types" "^3.222.0" - "@aws-sdk/util-utf8-browser" "^3.0.0" - tslib "^1.11.1" - -"@aws-sdk/abort-controller@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.193.0.tgz" - integrity sha512-MYPBm5PWyKP+Tq37mKs5wDbyAyVMocF5iYmx738LYXBSj8A1V4LTFrvfd4U16BRC/sM0DYB9fBFJUQ9ISFRVYw== - dependencies: - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/abort-controller@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.224.0.tgz" - integrity sha512-6DxaHnSDc2V5WiwtDaRwJJb2fkmDTyGr1svIM9H671aXIwe+q17mtpm5IooKL8bW5mLJoB1pT/5ntLkfxDQgSQ== - dependencies: - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/abort-controller@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/abort-controller/-/abort-controller-3.257.0.tgz#a9039bd9c409defbbeb7bafef3a1b206fbfedad1" - integrity sha512-ekWy391lOerS0ZECdhp/c+X7AToJIpfNrCPjuj3bKr+GMQYckGsYsdbm6AUD4sxBmfvuaQmVniSXWovaxwcFcQ== - dependencies: - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/chunked-blob-reader-native@3.208.0": - version "3.208.0" - resolved "https://registry.npmjs.org/@aws-sdk/chunked-blob-reader-native/-/chunked-blob-reader-native-3.208.0.tgz" - integrity sha512-JeOZ95PW+fJ6bbuqPySYqLqHk1n4+4ueEEraJsiUrPBV0S1ZtyvOGHcnGztKUjr2PYNaiexmpWuvUve9K12HRA== - dependencies: - "@aws-sdk/util-base64" "3.208.0" - tslib "^2.3.1" - -"@aws-sdk/chunked-blob-reader@3.188.0": - version "3.188.0" - resolved "https://registry.npmjs.org/@aws-sdk/chunked-blob-reader/-/chunked-blob-reader-3.188.0.tgz" - integrity sha512-zkPRFZZPL3eH+kH86LDYYXImiClA1/sW60zYOjse9Pgka+eDJlvBN6hcYxwDEKjcwATYiSRR1aVQHcfCinlGXg== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/client-cloudfront@^3.261.0": - version "3.261.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-cloudfront/-/client-cloudfront-3.261.0.tgz#83503e3f561d8795f5a0f3415c4b379d68fed082" - integrity sha512-7JOpLfgYdQ+CDA3McsAmzcCO+rZj3wVicNTF7Kpl0JaZ0NB0NShifMb4OAGuh2RNh+OYV6k3mtjsXh9ZIQ08PQ== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.261.0" - "@aws-sdk/config-resolver" "3.259.0" - "@aws-sdk/credential-provider-node" "3.261.0" - "@aws-sdk/fetch-http-handler" "3.257.0" - "@aws-sdk/hash-node" "3.257.0" - "@aws-sdk/invalid-dependency" "3.257.0" - "@aws-sdk/middleware-content-length" "3.257.0" - "@aws-sdk/middleware-endpoint" "3.257.0" - "@aws-sdk/middleware-host-header" "3.257.0" - "@aws-sdk/middleware-logger" "3.257.0" - "@aws-sdk/middleware-recursion-detection" "3.257.0" - "@aws-sdk/middleware-retry" "3.259.0" - "@aws-sdk/middleware-serde" "3.257.0" - "@aws-sdk/middleware-signing" "3.257.0" - "@aws-sdk/middleware-stack" "3.257.0" - "@aws-sdk/middleware-user-agent" "3.257.0" - "@aws-sdk/node-config-provider" "3.259.0" - "@aws-sdk/node-http-handler" "3.257.0" - "@aws-sdk/protocol-http" "3.257.0" - "@aws-sdk/smithy-client" "3.261.0" - "@aws-sdk/types" "3.257.0" - "@aws-sdk/url-parser" "3.257.0" - "@aws-sdk/util-base64" "3.208.0" - "@aws-sdk/util-body-length-browser" "3.188.0" - "@aws-sdk/util-body-length-node" "3.208.0" - "@aws-sdk/util-defaults-mode-browser" "3.261.0" - "@aws-sdk/util-defaults-mode-node" "3.261.0" - "@aws-sdk/util-endpoints" "3.257.0" - "@aws-sdk/util-retry" "3.257.0" - "@aws-sdk/util-user-agent-browser" "3.257.0" - "@aws-sdk/util-user-agent-node" "3.259.0" - "@aws-sdk/util-utf8" "3.254.0" - "@aws-sdk/util-waiter" "3.257.0" - "@aws-sdk/xml-builder" "3.201.0" - fast-xml-parser "4.0.11" - tslib "^2.3.1" - -"@aws-sdk/client-kms@^3.196.0": - version "3.196.0" - resolved "https://registry.npmjs.org/@aws-sdk/client-kms/-/client-kms-3.196.0.tgz" - integrity sha512-mR5jxfvHnv71FLd87PJ0KNgVXcZzNvKiI3i3JyLmukapnN5Kz2n0cG/jruo9d29zYQS60kfIPjdHddzOxNHH4A== - dependencies: - "@aws-crypto/sha256-browser" "2.0.0" - "@aws-crypto/sha256-js" "2.0.0" - "@aws-sdk/client-sts" "3.196.0" - "@aws-sdk/config-resolver" "3.193.0" - "@aws-sdk/credential-provider-node" "3.196.0" - "@aws-sdk/fetch-http-handler" "3.193.0" - "@aws-sdk/hash-node" "3.193.0" - "@aws-sdk/invalid-dependency" "3.193.0" - "@aws-sdk/middleware-content-length" "3.193.0" - "@aws-sdk/middleware-endpoint" "3.193.0" - "@aws-sdk/middleware-host-header" "3.193.0" - "@aws-sdk/middleware-logger" "3.193.0" - "@aws-sdk/middleware-recursion-detection" "3.193.0" - "@aws-sdk/middleware-retry" "3.193.0" - "@aws-sdk/middleware-serde" "3.193.0" - "@aws-sdk/middleware-signing" "3.193.0" - "@aws-sdk/middleware-stack" "3.193.0" - "@aws-sdk/middleware-user-agent" "3.193.0" - "@aws-sdk/node-config-provider" "3.193.0" - "@aws-sdk/node-http-handler" "3.193.0" - "@aws-sdk/protocol-http" "3.193.0" - "@aws-sdk/smithy-client" "3.193.0" - "@aws-sdk/types" "3.193.0" - "@aws-sdk/url-parser" "3.193.0" - "@aws-sdk/util-base64-browser" "3.188.0" - "@aws-sdk/util-base64-node" "3.188.0" - "@aws-sdk/util-body-length-browser" "3.188.0" - "@aws-sdk/util-body-length-node" "3.188.0" - "@aws-sdk/util-defaults-mode-browser" "3.193.0" - "@aws-sdk/util-defaults-mode-node" "3.193.0" - "@aws-sdk/util-endpoints" "3.196.0" - "@aws-sdk/util-user-agent-browser" "3.193.0" - "@aws-sdk/util-user-agent-node" "3.193.0" - "@aws-sdk/util-utf8-browser" "3.188.0" - "@aws-sdk/util-utf8-node" "3.188.0" - tslib "^2.3.1" - -"@aws-sdk/client-s3@^3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.224.0.tgz" - integrity sha512-CPU1sG4xr+fJ+OFpqz9Oum7cJwas0mA9YFvPLkgKLvNC2rhmmn0kbjwawtc6GUDu6xygeV8koBL2gz7OJHQ7fQ== - dependencies: - "@aws-crypto/sha1-browser" "2.0.0" - "@aws-crypto/sha256-browser" "2.0.0" - "@aws-crypto/sha256-js" "2.0.0" - "@aws-sdk/client-sts" "3.224.0" - "@aws-sdk/config-resolver" "3.224.0" - "@aws-sdk/credential-provider-node" "3.224.0" - "@aws-sdk/eventstream-serde-browser" "3.224.0" - "@aws-sdk/eventstream-serde-config-resolver" "3.224.0" - "@aws-sdk/eventstream-serde-node" "3.224.0" - "@aws-sdk/fetch-http-handler" "3.224.0" - "@aws-sdk/hash-blob-browser" "3.224.0" - "@aws-sdk/hash-node" "3.224.0" - "@aws-sdk/hash-stream-node" "3.224.0" - "@aws-sdk/invalid-dependency" "3.224.0" - "@aws-sdk/md5-js" "3.224.0" - "@aws-sdk/middleware-bucket-endpoint" "3.224.0" - "@aws-sdk/middleware-content-length" "3.224.0" - "@aws-sdk/middleware-endpoint" "3.224.0" - "@aws-sdk/middleware-expect-continue" "3.224.0" - "@aws-sdk/middleware-flexible-checksums" "3.224.0" - "@aws-sdk/middleware-host-header" "3.224.0" - "@aws-sdk/middleware-location-constraint" "3.224.0" - "@aws-sdk/middleware-logger" "3.224.0" - "@aws-sdk/middleware-recursion-detection" "3.224.0" - "@aws-sdk/middleware-retry" "3.224.0" - "@aws-sdk/middleware-sdk-s3" "3.224.0" - "@aws-sdk/middleware-serde" "3.224.0" - "@aws-sdk/middleware-signing" "3.224.0" - "@aws-sdk/middleware-ssec" "3.224.0" - "@aws-sdk/middleware-stack" "3.224.0" - "@aws-sdk/middleware-user-agent" "3.224.0" - "@aws-sdk/node-config-provider" "3.224.0" - "@aws-sdk/node-http-handler" "3.224.0" - "@aws-sdk/protocol-http" "3.224.0" - "@aws-sdk/signature-v4-multi-region" "3.224.0" - "@aws-sdk/smithy-client" "3.224.0" - "@aws-sdk/types" "3.224.0" - "@aws-sdk/url-parser" "3.224.0" - "@aws-sdk/util-base64" "3.208.0" - "@aws-sdk/util-body-length-browser" "3.188.0" - "@aws-sdk/util-body-length-node" "3.208.0" - "@aws-sdk/util-defaults-mode-browser" "3.224.0" - "@aws-sdk/util-defaults-mode-node" "3.224.0" - "@aws-sdk/util-endpoints" "3.224.0" - "@aws-sdk/util-stream-browser" "3.224.0" - "@aws-sdk/util-stream-node" "3.224.0" - "@aws-sdk/util-user-agent-browser" "3.224.0" - "@aws-sdk/util-user-agent-node" "3.224.0" - "@aws-sdk/util-utf8-browser" "3.188.0" - "@aws-sdk/util-utf8-node" "3.208.0" - "@aws-sdk/util-waiter" "3.224.0" - "@aws-sdk/xml-builder" "3.201.0" - fast-xml-parser "4.0.11" - tslib "^2.3.1" - -"@aws-sdk/client-sso-oidc@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.224.0.tgz" - integrity sha512-r7QAqinMvuZvGlfC4ltEBIq3gJ1AI4tTqEi8lG06+gDoiwnqTWii0+OrZJQiaeLc3PqDHwxmRpEmjFlr/f5TKg== - dependencies: - "@aws-crypto/sha256-browser" "2.0.0" - "@aws-crypto/sha256-js" "2.0.0" - "@aws-sdk/config-resolver" "3.224.0" - "@aws-sdk/fetch-http-handler" "3.224.0" - "@aws-sdk/hash-node" "3.224.0" - "@aws-sdk/invalid-dependency" "3.224.0" - "@aws-sdk/middleware-content-length" "3.224.0" - "@aws-sdk/middleware-endpoint" "3.224.0" - "@aws-sdk/middleware-host-header" "3.224.0" - "@aws-sdk/middleware-logger" "3.224.0" - "@aws-sdk/middleware-recursion-detection" "3.224.0" - "@aws-sdk/middleware-retry" "3.224.0" - "@aws-sdk/middleware-serde" "3.224.0" - "@aws-sdk/middleware-stack" "3.224.0" - "@aws-sdk/middleware-user-agent" "3.224.0" - "@aws-sdk/node-config-provider" "3.224.0" - "@aws-sdk/node-http-handler" "3.224.0" - "@aws-sdk/protocol-http" "3.224.0" - "@aws-sdk/smithy-client" "3.224.0" - "@aws-sdk/types" "3.224.0" - "@aws-sdk/url-parser" "3.224.0" - "@aws-sdk/util-base64" "3.208.0" - "@aws-sdk/util-body-length-browser" "3.188.0" - "@aws-sdk/util-body-length-node" "3.208.0" - "@aws-sdk/util-defaults-mode-browser" "3.224.0" - "@aws-sdk/util-defaults-mode-node" "3.224.0" - "@aws-sdk/util-endpoints" "3.224.0" - "@aws-sdk/util-user-agent-browser" "3.224.0" - "@aws-sdk/util-user-agent-node" "3.224.0" - "@aws-sdk/util-utf8-browser" "3.188.0" - "@aws-sdk/util-utf8-node" "3.208.0" - tslib "^2.3.1" - -"@aws-sdk/client-sso-oidc@3.261.0": - version "3.261.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.261.0.tgz#437e5b4ccc37bcc14e94afead8eae909887e8309" - integrity sha512-ItgRT/BThv2UxEeGJ5/GCF6JY1Rzk39IcDIPZAfBA8HbYcznXGDsBTRf45MErS+uollwNFX0T/WNlTbmjEDE7g== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/config-resolver" "3.259.0" - "@aws-sdk/fetch-http-handler" "3.257.0" - "@aws-sdk/hash-node" "3.257.0" - "@aws-sdk/invalid-dependency" "3.257.0" - "@aws-sdk/middleware-content-length" "3.257.0" - "@aws-sdk/middleware-endpoint" "3.257.0" - "@aws-sdk/middleware-host-header" "3.257.0" - "@aws-sdk/middleware-logger" "3.257.0" - "@aws-sdk/middleware-recursion-detection" "3.257.0" - "@aws-sdk/middleware-retry" "3.259.0" - "@aws-sdk/middleware-serde" "3.257.0" - "@aws-sdk/middleware-stack" "3.257.0" - "@aws-sdk/middleware-user-agent" "3.257.0" - "@aws-sdk/node-config-provider" "3.259.0" - "@aws-sdk/node-http-handler" "3.257.0" - "@aws-sdk/protocol-http" "3.257.0" - "@aws-sdk/smithy-client" "3.261.0" - "@aws-sdk/types" "3.257.0" - "@aws-sdk/url-parser" "3.257.0" - "@aws-sdk/util-base64" "3.208.0" - "@aws-sdk/util-body-length-browser" "3.188.0" - "@aws-sdk/util-body-length-node" "3.208.0" - "@aws-sdk/util-defaults-mode-browser" "3.261.0" - "@aws-sdk/util-defaults-mode-node" "3.261.0" - "@aws-sdk/util-endpoints" "3.257.0" - "@aws-sdk/util-retry" "3.257.0" - "@aws-sdk/util-user-agent-browser" "3.257.0" - "@aws-sdk/util-user-agent-node" "3.259.0" - "@aws-sdk/util-utf8" "3.254.0" - tslib "^2.3.1" - -"@aws-sdk/client-sso@3.196.0": - version "3.196.0" - resolved "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.196.0.tgz" - integrity sha512-u+UnxrVHLjLDdfCZft1AuyIhyv+77/inCHR4LcKsGASRA+jAg3z+OY+B7Q9hWHNcVt5ECMw7rxe4jA9BLf42sw== - dependencies: - "@aws-crypto/sha256-browser" "2.0.0" - "@aws-crypto/sha256-js" "2.0.0" - "@aws-sdk/config-resolver" "3.193.0" - "@aws-sdk/fetch-http-handler" "3.193.0" - "@aws-sdk/hash-node" "3.193.0" - "@aws-sdk/invalid-dependency" "3.193.0" - "@aws-sdk/middleware-content-length" "3.193.0" - "@aws-sdk/middleware-endpoint" "3.193.0" - "@aws-sdk/middleware-host-header" "3.193.0" - "@aws-sdk/middleware-logger" "3.193.0" - "@aws-sdk/middleware-recursion-detection" "3.193.0" - "@aws-sdk/middleware-retry" "3.193.0" - "@aws-sdk/middleware-serde" "3.193.0" - "@aws-sdk/middleware-stack" "3.193.0" - "@aws-sdk/middleware-user-agent" "3.193.0" - "@aws-sdk/node-config-provider" "3.193.0" - "@aws-sdk/node-http-handler" "3.193.0" - "@aws-sdk/protocol-http" "3.193.0" - "@aws-sdk/smithy-client" "3.193.0" - "@aws-sdk/types" "3.193.0" - "@aws-sdk/url-parser" "3.193.0" - "@aws-sdk/util-base64-browser" "3.188.0" - "@aws-sdk/util-base64-node" "3.188.0" - "@aws-sdk/util-body-length-browser" "3.188.0" - "@aws-sdk/util-body-length-node" "3.188.0" - "@aws-sdk/util-defaults-mode-browser" "3.193.0" - "@aws-sdk/util-defaults-mode-node" "3.193.0" - "@aws-sdk/util-endpoints" "3.196.0" - "@aws-sdk/util-user-agent-browser" "3.193.0" - "@aws-sdk/util-user-agent-node" "3.193.0" - "@aws-sdk/util-utf8-browser" "3.188.0" - "@aws-sdk/util-utf8-node" "3.188.0" - tslib "^2.3.1" - -"@aws-sdk/client-sso@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.224.0.tgz" - integrity sha512-ZfqjGGBhv+sKxYN9FHbepaL+ucFbAFndvNdalGj4mZsv5AqxgemkFoRofNJk4nu79JVf5cdrj7zL+BDW3KwEGg== - dependencies: - "@aws-crypto/sha256-browser" "2.0.0" - "@aws-crypto/sha256-js" "2.0.0" - "@aws-sdk/config-resolver" "3.224.0" - "@aws-sdk/fetch-http-handler" "3.224.0" - "@aws-sdk/hash-node" "3.224.0" - "@aws-sdk/invalid-dependency" "3.224.0" - "@aws-sdk/middleware-content-length" "3.224.0" - "@aws-sdk/middleware-endpoint" "3.224.0" - "@aws-sdk/middleware-host-header" "3.224.0" - "@aws-sdk/middleware-logger" "3.224.0" - "@aws-sdk/middleware-recursion-detection" "3.224.0" - "@aws-sdk/middleware-retry" "3.224.0" - "@aws-sdk/middleware-serde" "3.224.0" - "@aws-sdk/middleware-stack" "3.224.0" - "@aws-sdk/middleware-user-agent" "3.224.0" - "@aws-sdk/node-config-provider" "3.224.0" - "@aws-sdk/node-http-handler" "3.224.0" - "@aws-sdk/protocol-http" "3.224.0" - "@aws-sdk/smithy-client" "3.224.0" - "@aws-sdk/types" "3.224.0" - "@aws-sdk/url-parser" "3.224.0" - "@aws-sdk/util-base64" "3.208.0" - "@aws-sdk/util-body-length-browser" "3.188.0" - "@aws-sdk/util-body-length-node" "3.208.0" - "@aws-sdk/util-defaults-mode-browser" "3.224.0" - "@aws-sdk/util-defaults-mode-node" "3.224.0" - "@aws-sdk/util-endpoints" "3.224.0" - "@aws-sdk/util-user-agent-browser" "3.224.0" - "@aws-sdk/util-user-agent-node" "3.224.0" - "@aws-sdk/util-utf8-browser" "3.188.0" - "@aws-sdk/util-utf8-node" "3.208.0" - tslib "^2.3.1" - -"@aws-sdk/client-sso@3.261.0": - version "3.261.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.261.0.tgz#9ab7dfed385d9a18e68dc16e7dedbd9619db4f8e" - integrity sha512-tq5hu1WXa9BKsCH9zOBOykyiaoZQvaFHKdOamw5SZ69niyO3AG4xR1TkLqXj/9mDYMLgAIVObKZDGWtBLFTdiQ== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/config-resolver" "3.259.0" - "@aws-sdk/fetch-http-handler" "3.257.0" - "@aws-sdk/hash-node" "3.257.0" - "@aws-sdk/invalid-dependency" "3.257.0" - "@aws-sdk/middleware-content-length" "3.257.0" - "@aws-sdk/middleware-endpoint" "3.257.0" - "@aws-sdk/middleware-host-header" "3.257.0" - "@aws-sdk/middleware-logger" "3.257.0" - "@aws-sdk/middleware-recursion-detection" "3.257.0" - "@aws-sdk/middleware-retry" "3.259.0" - "@aws-sdk/middleware-serde" "3.257.0" - "@aws-sdk/middleware-stack" "3.257.0" - "@aws-sdk/middleware-user-agent" "3.257.0" - "@aws-sdk/node-config-provider" "3.259.0" - "@aws-sdk/node-http-handler" "3.257.0" - "@aws-sdk/protocol-http" "3.257.0" - "@aws-sdk/smithy-client" "3.261.0" - "@aws-sdk/types" "3.257.0" - "@aws-sdk/url-parser" "3.257.0" - "@aws-sdk/util-base64" "3.208.0" - "@aws-sdk/util-body-length-browser" "3.188.0" - "@aws-sdk/util-body-length-node" "3.208.0" - "@aws-sdk/util-defaults-mode-browser" "3.261.0" - "@aws-sdk/util-defaults-mode-node" "3.261.0" - "@aws-sdk/util-endpoints" "3.257.0" - "@aws-sdk/util-retry" "3.257.0" - "@aws-sdk/util-user-agent-browser" "3.257.0" - "@aws-sdk/util-user-agent-node" "3.259.0" - "@aws-sdk/util-utf8" "3.254.0" - tslib "^2.3.1" - -"@aws-sdk/client-sts@3.196.0": - version "3.196.0" - resolved "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.196.0.tgz" - integrity sha512-ChzK8606CugwnRLm7iwerXzeMqOsjGLe3j1j1HtQShzXZu4/ysQ3mUBBPAt2Lltx+1ep8MoI9vaQVyfw5h35ww== - dependencies: - "@aws-crypto/sha256-browser" "2.0.0" - "@aws-crypto/sha256-js" "2.0.0" - "@aws-sdk/config-resolver" "3.193.0" - "@aws-sdk/credential-provider-node" "3.196.0" - "@aws-sdk/fetch-http-handler" "3.193.0" - "@aws-sdk/hash-node" "3.193.0" - "@aws-sdk/invalid-dependency" "3.193.0" - "@aws-sdk/middleware-content-length" "3.193.0" - "@aws-sdk/middleware-endpoint" "3.193.0" - "@aws-sdk/middleware-host-header" "3.193.0" - "@aws-sdk/middleware-logger" "3.193.0" - "@aws-sdk/middleware-recursion-detection" "3.193.0" - "@aws-sdk/middleware-retry" "3.193.0" - "@aws-sdk/middleware-sdk-sts" "3.193.0" - "@aws-sdk/middleware-serde" "3.193.0" - "@aws-sdk/middleware-signing" "3.193.0" - "@aws-sdk/middleware-stack" "3.193.0" - "@aws-sdk/middleware-user-agent" "3.193.0" - "@aws-sdk/node-config-provider" "3.193.0" - "@aws-sdk/node-http-handler" "3.193.0" - "@aws-sdk/protocol-http" "3.193.0" - "@aws-sdk/smithy-client" "3.193.0" - "@aws-sdk/types" "3.193.0" - "@aws-sdk/url-parser" "3.193.0" - "@aws-sdk/util-base64-browser" "3.188.0" - "@aws-sdk/util-base64-node" "3.188.0" - "@aws-sdk/util-body-length-browser" "3.188.0" - "@aws-sdk/util-body-length-node" "3.188.0" - "@aws-sdk/util-defaults-mode-browser" "3.193.0" - "@aws-sdk/util-defaults-mode-node" "3.193.0" - "@aws-sdk/util-endpoints" "3.196.0" - "@aws-sdk/util-user-agent-browser" "3.193.0" - "@aws-sdk/util-user-agent-node" "3.193.0" - "@aws-sdk/util-utf8-browser" "3.188.0" - "@aws-sdk/util-utf8-node" "3.188.0" - fast-xml-parser "4.0.11" - tslib "^2.3.1" - -"@aws-sdk/client-sts@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.224.0.tgz" - integrity sha512-ao3jyjwk2fozk1d4PtrNf0BNsucPWAbALv8CCsPTC3r9g2Lg/TOi3pxmsfd69ddw89XSyP6zZATEHaWO+tk0CQ== - dependencies: - "@aws-crypto/sha256-browser" "2.0.0" - "@aws-crypto/sha256-js" "2.0.0" - "@aws-sdk/config-resolver" "3.224.0" - "@aws-sdk/credential-provider-node" "3.224.0" - "@aws-sdk/fetch-http-handler" "3.224.0" - "@aws-sdk/hash-node" "3.224.0" - "@aws-sdk/invalid-dependency" "3.224.0" - "@aws-sdk/middleware-content-length" "3.224.0" - "@aws-sdk/middleware-endpoint" "3.224.0" - "@aws-sdk/middleware-host-header" "3.224.0" - "@aws-sdk/middleware-logger" "3.224.0" - "@aws-sdk/middleware-recursion-detection" "3.224.0" - "@aws-sdk/middleware-retry" "3.224.0" - "@aws-sdk/middleware-sdk-sts" "3.224.0" - "@aws-sdk/middleware-serde" "3.224.0" - "@aws-sdk/middleware-signing" "3.224.0" - "@aws-sdk/middleware-stack" "3.224.0" - "@aws-sdk/middleware-user-agent" "3.224.0" - "@aws-sdk/node-config-provider" "3.224.0" - "@aws-sdk/node-http-handler" "3.224.0" - "@aws-sdk/protocol-http" "3.224.0" - "@aws-sdk/smithy-client" "3.224.0" - "@aws-sdk/types" "3.224.0" - "@aws-sdk/url-parser" "3.224.0" - "@aws-sdk/util-base64" "3.208.0" - "@aws-sdk/util-body-length-browser" "3.188.0" - "@aws-sdk/util-body-length-node" "3.208.0" - "@aws-sdk/util-defaults-mode-browser" "3.224.0" - "@aws-sdk/util-defaults-mode-node" "3.224.0" - "@aws-sdk/util-endpoints" "3.224.0" - "@aws-sdk/util-user-agent-browser" "3.224.0" - "@aws-sdk/util-user-agent-node" "3.224.0" - "@aws-sdk/util-utf8-browser" "3.188.0" - "@aws-sdk/util-utf8-node" "3.208.0" - fast-xml-parser "4.0.11" - tslib "^2.3.1" - -"@aws-sdk/client-sts@3.261.0": - version "3.261.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.261.0.tgz#0ff6709b1b4a4db42584f9eef1ea58c19e38765f" - integrity sha512-jnCKBjuHEMgwCmR9bXDVpl/WzpUQyU9DL3Mk65XYyZwRxgHSaw5D90zRouoZMUneNA2OnKZQnjk6oyL47mb7oA== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/config-resolver" "3.259.0" - "@aws-sdk/credential-provider-node" "3.261.0" - "@aws-sdk/fetch-http-handler" "3.257.0" - "@aws-sdk/hash-node" "3.257.0" - "@aws-sdk/invalid-dependency" "3.257.0" - "@aws-sdk/middleware-content-length" "3.257.0" - "@aws-sdk/middleware-endpoint" "3.257.0" - "@aws-sdk/middleware-host-header" "3.257.0" - "@aws-sdk/middleware-logger" "3.257.0" - "@aws-sdk/middleware-recursion-detection" "3.257.0" - "@aws-sdk/middleware-retry" "3.259.0" - "@aws-sdk/middleware-sdk-sts" "3.257.0" - "@aws-sdk/middleware-serde" "3.257.0" - "@aws-sdk/middleware-signing" "3.257.0" - "@aws-sdk/middleware-stack" "3.257.0" - "@aws-sdk/middleware-user-agent" "3.257.0" - "@aws-sdk/node-config-provider" "3.259.0" - "@aws-sdk/node-http-handler" "3.257.0" - "@aws-sdk/protocol-http" "3.257.0" - "@aws-sdk/smithy-client" "3.261.0" - "@aws-sdk/types" "3.257.0" - "@aws-sdk/url-parser" "3.257.0" - "@aws-sdk/util-base64" "3.208.0" - "@aws-sdk/util-body-length-browser" "3.188.0" - "@aws-sdk/util-body-length-node" "3.208.0" - "@aws-sdk/util-defaults-mode-browser" "3.261.0" - "@aws-sdk/util-defaults-mode-node" "3.261.0" - "@aws-sdk/util-endpoints" "3.257.0" - "@aws-sdk/util-retry" "3.257.0" - "@aws-sdk/util-user-agent-browser" "3.257.0" - "@aws-sdk/util-user-agent-node" "3.259.0" - "@aws-sdk/util-utf8" "3.254.0" - fast-xml-parser "4.0.11" - tslib "^2.3.1" - -"@aws-sdk/config-resolver@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.193.0.tgz" - integrity sha512-HIjuv2A1glgkXy9g/A8bfsiz3jTFaRbwGZheoHFZod6iEQQEbbeAsBe3u2AZyzOrVLgs8lOvBtgU8XKSJWjDkw== - dependencies: - "@aws-sdk/signature-v4" "3.193.0" - "@aws-sdk/types" "3.193.0" - "@aws-sdk/util-config-provider" "3.188.0" - "@aws-sdk/util-middleware" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/config-resolver@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.224.0.tgz" - integrity sha512-jS53QvF2jdv7d6cpPUH6N85i1WNHik1eGvxqSndsNbLf0keEGXYyN4pBLNB0xK1nk0ZG+8slRsXgWvWTCcFYKA== - dependencies: - "@aws-sdk/signature-v4" "3.224.0" - "@aws-sdk/types" "3.224.0" - "@aws-sdk/util-config-provider" "3.208.0" - "@aws-sdk/util-middleware" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/config-resolver@3.259.0": - version "3.259.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/config-resolver/-/config-resolver-3.259.0.tgz#b2c17b681f890dbe31bc1670da41ae653a734c84" - integrity sha512-gViMRsc4Ye6+nzJ0OYTZIT8m4glIAdtugN2Sr/t6P2iJW5X0bSL/EcbcHBgsve1lHjeGPeyzVkT7UnyGOZ5Z/A== - dependencies: - "@aws-sdk/signature-v4" "3.257.0" - "@aws-sdk/types" "3.257.0" - "@aws-sdk/util-config-provider" "3.208.0" - "@aws-sdk/util-middleware" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-env@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.193.0.tgz" - integrity sha512-pRqZoIaqCdWB4JJdR6DqDn3u+CwKJchwiCPnRtChwC8KXCMkT4njq9J1bWG3imYeTxP/G06O1PDONEuD4pPtNQ== - dependencies: - "@aws-sdk/property-provider" "3.193.0" - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-env@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.224.0.tgz" - integrity sha512-WUicVivCne9Ela2Nuufohy8+UV/W6GwanlpK9trJqrqHt2/zqdNYHqZbWL0zDNO8dvFN3+MC2a8boYPyR+cFRg== - dependencies: - "@aws-sdk/property-provider" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-env@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.257.0.tgz#131d06bafa738c7f2ce2e7ee12c227ff6a414ada" - integrity sha512-GsmBi5Di6hk1JAi1iB6/LCY8o+GmlCvJoB7wuoVmXI3VxRVwptUVjuj8EtJbIrVGrF9dSuIRPCzUoSuzEzYGlg== - dependencies: - "@aws-sdk/property-provider" "3.257.0" - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-imds@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.193.0.tgz" - integrity sha512-jC7uT7uVpO/iitz49toHMGFKXQ2igWQQG2SKirREqDRaz5HSXwEP1V3rcOlNNyGIBPMggDjZnxYgJHqBXSq9Ag== - dependencies: - "@aws-sdk/node-config-provider" "3.193.0" - "@aws-sdk/property-provider" "3.193.0" - "@aws-sdk/types" "3.193.0" - "@aws-sdk/url-parser" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-imds@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.224.0.tgz" - integrity sha512-n7uVR5Z9EUfVbg0gSNrJvu1g0cM/HqhRt+kaRJBGNf4q1tEbnCukKj+qUZbT1qdbDTyu9NTRphMvuIyN3RBDtQ== - dependencies: - "@aws-sdk/node-config-provider" "3.224.0" - "@aws-sdk/property-provider" "3.224.0" - "@aws-sdk/types" "3.224.0" - "@aws-sdk/url-parser" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-imds@3.259.0": - version "3.259.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.259.0.tgz#23bfa858dd4e97a6d530b9e3b0f4497ab0a0f8c7" - integrity sha512-yCxoYWZAaDrCUEWxRfrpB0Mp1cFgJEMYW8T6GIb/+DQ5QLpZmorgaVD/j90QXupqFrR5tlxwuskBIkdD2E9YNg== - dependencies: - "@aws-sdk/node-config-provider" "3.259.0" - "@aws-sdk/property-provider" "3.257.0" - "@aws-sdk/types" "3.257.0" - "@aws-sdk/url-parser" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-ini@3.196.0": - version "3.196.0" - resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.196.0.tgz" - integrity sha512-3lL+YLBQ9KwQxG4AdRm4u2cvBNZeBmS/i3BWnCPomg96lNGPMrTEloVaVEpnrzOff6sgFxRtjkbLkVxmdipIrw== - dependencies: - "@aws-sdk/credential-provider-env" "3.193.0" - "@aws-sdk/credential-provider-imds" "3.193.0" - "@aws-sdk/credential-provider-sso" "3.196.0" - "@aws-sdk/credential-provider-web-identity" "3.193.0" - "@aws-sdk/property-provider" "3.193.0" - "@aws-sdk/shared-ini-file-loader" "3.193.0" - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-ini@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.224.0.tgz" - integrity sha512-YaAHoHJVspqy5f8C6EXBifMfodKXl88IHuL6eBComigTPR3s1Ed1+3AJdjA1X7SjAHfrYna/WvZEH3e8NCSzFA== - dependencies: - "@aws-sdk/credential-provider-env" "3.224.0" - "@aws-sdk/credential-provider-imds" "3.224.0" - "@aws-sdk/credential-provider-sso" "3.224.0" - "@aws-sdk/credential-provider-web-identity" "3.224.0" - "@aws-sdk/property-provider" "3.224.0" - "@aws-sdk/shared-ini-file-loader" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-ini@3.261.0": - version "3.261.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.261.0.tgz#435525bd8d8ceb28ee69a628e22c8f0ee5af1dca" - integrity sha512-638jTnvFbGO0G0So+FijdC1vjn/dhw3l8nJwLq9PYOBJUKhjXDR/fpOhZkUJ+Zwfuqp9SlDDo/yfFa6j2L+F1g== - dependencies: - "@aws-sdk/credential-provider-env" "3.257.0" - "@aws-sdk/credential-provider-imds" "3.259.0" - "@aws-sdk/credential-provider-process" "3.257.0" - "@aws-sdk/credential-provider-sso" "3.261.0" - "@aws-sdk/credential-provider-web-identity" "3.257.0" - "@aws-sdk/property-provider" "3.257.0" - "@aws-sdk/shared-ini-file-loader" "3.257.0" - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-node@3.196.0": - version "3.196.0" - resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.196.0.tgz" - integrity sha512-PGY7pkmqgfEwTHsuUH6fGrXWri93jqKkMbhq/QJafMGtsVupfvXvE37Rl+qgjsZjRfROrEaeLw2DGrPPmVh2cg== - dependencies: - "@aws-sdk/credential-provider-env" "3.193.0" - "@aws-sdk/credential-provider-imds" "3.193.0" - "@aws-sdk/credential-provider-ini" "3.196.0" - "@aws-sdk/credential-provider-process" "3.193.0" - "@aws-sdk/credential-provider-sso" "3.196.0" - "@aws-sdk/credential-provider-web-identity" "3.193.0" - "@aws-sdk/property-provider" "3.193.0" - "@aws-sdk/shared-ini-file-loader" "3.193.0" - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-node@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.224.0.tgz" - integrity sha512-n/gijJAA3uVFl1b3+hp2E3lPaiajsPLHqH+mMxNxPkGo39HV1v9RAyOVW4Y3AH1QcT7sURevjGoF2Eemcro88g== - dependencies: - "@aws-sdk/credential-provider-env" "3.224.0" - "@aws-sdk/credential-provider-imds" "3.224.0" - "@aws-sdk/credential-provider-ini" "3.224.0" - "@aws-sdk/credential-provider-process" "3.224.0" - "@aws-sdk/credential-provider-sso" "3.224.0" - "@aws-sdk/credential-provider-web-identity" "3.224.0" - "@aws-sdk/property-provider" "3.224.0" - "@aws-sdk/shared-ini-file-loader" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-node@3.261.0": - version "3.261.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.261.0.tgz#af7587b7d284556626e718e6345f0f40c509237e" - integrity sha512-7T25a7jbHsXPe7XvIekzhR50b7PTlISKqHdE8LNVUSzFQbSjVXulFk3vyQVIhmt5HKNkSBcMPDr6hKrSl7OLBw== - dependencies: - "@aws-sdk/credential-provider-env" "3.257.0" - "@aws-sdk/credential-provider-imds" "3.259.0" - "@aws-sdk/credential-provider-ini" "3.261.0" - "@aws-sdk/credential-provider-process" "3.257.0" - "@aws-sdk/credential-provider-sso" "3.261.0" - "@aws-sdk/credential-provider-web-identity" "3.257.0" - "@aws-sdk/property-provider" "3.257.0" - "@aws-sdk/shared-ini-file-loader" "3.257.0" - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-process@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.193.0.tgz" - integrity sha512-zpXxtQzQqkaUuFqmHW9dSkh9p/1k+XNKlwEkG8FTwAJNUWmy2ZMJv+8NTVn4s4vaRu7xJ1er9chspYr7mvxHlA== - dependencies: - "@aws-sdk/property-provider" "3.193.0" - "@aws-sdk/shared-ini-file-loader" "3.193.0" - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-process@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.224.0.tgz" - integrity sha512-0nc8vGmv6vDfFlVyKREwAa4namfuGqKg3TTM0nW2vE10fpDXZM/DGVAs5HInX+27QQNLVVh3/OHHgti9wMkYkw== - dependencies: - "@aws-sdk/property-provider" "3.224.0" - "@aws-sdk/shared-ini-file-loader" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-process@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.257.0.tgz#7fd27f48606ad7c2af375b168c8e38dc938e3162" - integrity sha512-xK8uYeNXaclaBCGrLi4z2pxPRngqLf5BM5jg2fn57zqvlL9V5gJF972FehrVBL0bfp1/laG0ZJtD2K2sapyWAw== - dependencies: - "@aws-sdk/property-provider" "3.257.0" - "@aws-sdk/shared-ini-file-loader" "3.257.0" - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-sso@3.196.0": - version "3.196.0" - resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.196.0.tgz" - integrity sha512-hJV4LDVfvPfj5zC0ysHx3zkwwJOyF+BaMGaMzaScrHyijv5e3qZzdoBLbOQFmrqVnt7DjCU02NvRSS8amLpmSw== - dependencies: - "@aws-sdk/client-sso" "3.196.0" - "@aws-sdk/property-provider" "3.193.0" - "@aws-sdk/shared-ini-file-loader" "3.193.0" - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-sso@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.224.0.tgz" - integrity sha512-Qx5w8MCGAwT5cqimA3ZgtY1jSrC7QGPzZfNflY75PWQIaYgjUNNqdAW0jipr4M/dgVjvo1j/Ek+atNf/niTOsQ== - dependencies: - "@aws-sdk/client-sso" "3.224.0" - "@aws-sdk/property-provider" "3.224.0" - "@aws-sdk/shared-ini-file-loader" "3.224.0" - "@aws-sdk/token-providers" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-sso@3.261.0": - version "3.261.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.261.0.tgz#6265828dad45b1ef67c43f712ddbcfc80e2c6fab" - integrity sha512-Ofj7m85/RuxcZMtghhD+U2GGszrU5tB2kxXcnkcHCudOER6bcOOEXnSfmdZnIv4xG+vma3VFwiWk2JkQo5zB5w== - dependencies: - "@aws-sdk/client-sso" "3.261.0" - "@aws-sdk/property-provider" "3.257.0" - "@aws-sdk/shared-ini-file-loader" "3.257.0" - "@aws-sdk/token-providers" "3.261.0" - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-web-identity@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.193.0.tgz" - integrity sha512-MIQY9KwLCBnRyIt7an4EtMrFQZz2HC1E8vQDdKVzmeQBBePhW61fnX9XDP9bfc3Ypg1NggLG00KBPEC88twLFg== - dependencies: - "@aws-sdk/property-provider" "3.193.0" - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-web-identity@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.224.0.tgz" - integrity sha512-Z/xRFTm9pBVyuIAkYohisb3KPJowPVng7ZuZiblU0PaESoJBTkhAFOblpPv/ZWwb6fT85ANUKrvl4858zLpk/Q== - dependencies: - "@aws-sdk/property-provider" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/credential-provider-web-identity@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.257.0.tgz#928f3234818c6acbf67bf157e4a366f920285e62" - integrity sha512-Cm0uvRv4JuIbD0Kp3W0J/vwjADIyCx8HoZi5yg+QIi5nilocuTQ3ajvLeuPVSvFvdy+yaxSc5FxNXquWt7Mngw== - dependencies: - "@aws-sdk/property-provider" "3.257.0" - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/eventstream-codec@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/eventstream-codec/-/eventstream-codec-3.224.0.tgz" - integrity sha512-p8DePCwvgrrlYK7r3euI5aX/VVxrCl+DClHy0TV6/Eq8WCgWqYfZ5TSl5kbrxIc4U7pDlNIBkTiQMIl/ilEiQg== - dependencies: - "@aws-crypto/crc32" "2.0.0" - "@aws-sdk/types" "3.224.0" - "@aws-sdk/util-hex-encoding" "3.201.0" - tslib "^2.3.1" - -"@aws-sdk/eventstream-serde-browser@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/eventstream-serde-browser/-/eventstream-serde-browser-3.224.0.tgz" - integrity sha512-QeyGmKipZsbVkezI5OKe0Xad7u1JPkZWNm1m7uqjd9vTK3A+/fw7eNxOWYVdSKs/kHyAWr9PG+fASBtr3gesPA== - dependencies: - "@aws-sdk/eventstream-serde-universal" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/eventstream-serde-config-resolver@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.224.0.tgz" - integrity sha512-zl8YUa+JZV9Dj304pc2HovMuUsz3qzo8HHj+FjIHxVsNfFL4U/NX/eDhkiWNUwVMlQIWvjksoJZ75kIzDyWGKQ== - dependencies: - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/eventstream-serde-node@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/eventstream-serde-node/-/eventstream-serde-node-3.224.0.tgz" - integrity sha512-o0PXQwyyqeBk+kkn9wIPVIdzwp28EmRt2UqEH/UO6XzpmZTghuY4ZWkQTx9n+vMZ0e/EbqIlh2BPAhELwYzMug== - dependencies: - "@aws-sdk/eventstream-serde-universal" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/eventstream-serde-universal@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/eventstream-serde-universal/-/eventstream-serde-universal-3.224.0.tgz" - integrity sha512-sI9WKnaKfpVamLCESHDOg8SkMtkjjYX3awny5PJC3/Jx9zOFN9AnvGtnIJrOGFxs5kBmQNj7c4sKCAPiTCcITw== - dependencies: - "@aws-sdk/eventstream-codec" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/fetch-http-handler@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.193.0.tgz" - integrity sha512-UhIS2LtCK9hqBzYVon6BI8WebJW1KC0GGIL/Gse5bqzU9iAGgFLAe66qg9k+/h3Jjc5LNAYzqXNVizMwn7689Q== - dependencies: - "@aws-sdk/protocol-http" "3.193.0" - "@aws-sdk/querystring-builder" "3.193.0" - "@aws-sdk/types" "3.193.0" - "@aws-sdk/util-base64-browser" "3.188.0" - tslib "^2.3.1" - -"@aws-sdk/fetch-http-handler@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.224.0.tgz" - integrity sha512-IO1Je6ZM0fwT5YYPwQwwXcD4LlsYmP52pwit8AAI4ppz6AkSfs0747uDK0DYnqls7sevBQzUSqBSt6XjcMKjYQ== - dependencies: - "@aws-sdk/protocol-http" "3.224.0" - "@aws-sdk/querystring-builder" "3.224.0" - "@aws-sdk/types" "3.224.0" - "@aws-sdk/util-base64" "3.208.0" - tslib "^2.3.1" - -"@aws-sdk/fetch-http-handler@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.257.0.tgz#0b384ad33a57479f340ba558920a3eedded82131" - integrity sha512-zOF+RzQ+wfF7tq7tGUdPcqUTh3+k2f8KCVJE07A8kCopVq4nBu4NH6Eq29Tjpwdya3YlKvE+kFssuQRRRRex+Q== - dependencies: - "@aws-sdk/protocol-http" "3.257.0" - "@aws-sdk/querystring-builder" "3.257.0" - "@aws-sdk/types" "3.257.0" - "@aws-sdk/util-base64" "3.208.0" - tslib "^2.3.1" - -"@aws-sdk/hash-blob-browser@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/hash-blob-browser/-/hash-blob-browser-3.224.0.tgz" - integrity sha512-nUBRZzxbq6mU8FIK6OizC5jIeRkVn5tB2ZYxPd7P2IDhh1OVod6gXkq9EKTf3kecNvYgWUVHcOezZF1qLMDLHg== - dependencies: - "@aws-sdk/chunked-blob-reader" "3.188.0" - "@aws-sdk/chunked-blob-reader-native" "3.208.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/hash-node@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.193.0.tgz" - integrity sha512-O2SLPVBjrCUo+4ouAdRUoHBYsyurO9LcjNZNYD7YQOotBTbVFA3cx7kTZu+K4B6kX7FDaGbqbE1C/T1/eg/r+w== - dependencies: - "@aws-sdk/types" "3.193.0" - "@aws-sdk/util-buffer-from" "3.188.0" - tslib "^2.3.1" - -"@aws-sdk/hash-node@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.224.0.tgz" - integrity sha512-y7TXMDOSy5E2VZPvmsvRfyXkcQWcjTLFTd85yc70AAeFZiffff1nvZifQSzD78bW6ELJsWHXA2O8yxdBURyoBg== - dependencies: - "@aws-sdk/types" "3.224.0" - "@aws-sdk/util-buffer-from" "3.208.0" - tslib "^2.3.1" - -"@aws-sdk/hash-node@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/hash-node/-/hash-node-3.257.0.tgz#517e4c3c957586c0f35f916fd5c8c9841292f01f" - integrity sha512-W/USUuea5Ep3OJ2U7Ve8/5KN1YsDun2WzOFUxc1PyxXP5pW6OgC15/op0e+bmWPG851clvp5S8ZuroUr3aKi3Q== - dependencies: - "@aws-sdk/types" "3.257.0" - "@aws-sdk/util-buffer-from" "3.208.0" - "@aws-sdk/util-utf8" "3.254.0" - tslib "^2.3.1" - -"@aws-sdk/hash-stream-node@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/hash-stream-node/-/hash-stream-node-3.224.0.tgz" - integrity sha512-5RDwzB2C4Zjn4M2kZYntkc2LJdqe8CH9xmudu3ZYESkZToN5Rd3JyqobW9KPbm//R43VR4ml2qUhYHFzK6jvgg== - dependencies: - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/invalid-dependency@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.193.0.tgz" - integrity sha512-54DCknekLwJAI1os76XJ8XCzfAH7BGkBGtlWk5WCNkZTfj3rf5RUiXz4uoKUMWE1rZmyMDoDDS1PBo+yTVKW5w== - dependencies: - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/invalid-dependency@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.224.0.tgz" - integrity sha512-6huV8LBYQYx84uMhQ2SS7nqEkhTkAufwhKceXnysrcrLDuUmyth09Y7fcFblFIDTr4wTgSI0mf6DKVF4nqYCwQ== - dependencies: - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/invalid-dependency@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/invalid-dependency/-/invalid-dependency-3.257.0.tgz#e4cb2c7be40aa061dff32b0dc70db966da0938eb" - integrity sha512-T68SAPRNMEhpke0wlxURgogL7q0B8dfqZsSeS20BVR/lksJxLse9+pbmCDxiu1RrXoEIsEwl5rbLN+Hw8BFFYw== - dependencies: - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/is-array-buffer@3.188.0": - version "3.188.0" - resolved "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.188.0.tgz" - integrity sha512-n69N4zJZCNd87Rf4NzufPzhactUeM877Y0Tp/F3KiHqGeTnVjYUa4Lv1vLBjqtfjYb2HWT3NKlYn5yzrhaEwiQ== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/is-array-buffer@3.201.0": - version "3.201.0" - resolved "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.201.0.tgz" - integrity sha512-UPez5qLh3dNgt0DYnPD/q0mVJY84rA17QE26hVNOW3fAji8W2wrwrxdacWOxyXvlxWsVRcKmr+lay1MDqpAMfg== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/lib-storage@^3.226.0": - version "3.226.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/lib-storage/-/lib-storage-3.226.0.tgz#205b17e952136741ba58ed8a5cb43653e9984758" - integrity sha512-pTPQlZqYhonkaSpdD582fKKfUtQv+80vcyJdmAelUC4hZIyT98XT0wzZLp5N8etAFAgVj7Lxh59qxPB4Qz8MCw== - dependencies: - "@aws-sdk/middleware-endpoint" "3.226.0" - "@aws-sdk/smithy-client" "3.226.0" - buffer "5.6.0" - events "3.3.0" - stream-browserify "3.0.0" - tslib "^2.3.1" - -"@aws-sdk/md5-js@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/md5-js/-/md5-js-3.224.0.tgz" - integrity sha512-DT9hKzBYJUcPvGxTXwoug5Ac4zJ7q5pwOVF/PFCsN3TiXHHfDAIA0/GJjA6pZwPEi/qVy0iNhGKQK8/0i5JeWw== - dependencies: - "@aws-sdk/types" "3.224.0" - "@aws-sdk/util-utf8-browser" "3.188.0" - "@aws-sdk/util-utf8-node" "3.208.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-bucket-endpoint@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.224.0.tgz" - integrity sha512-cAmrSmVjBCENM9ojUBRhIsuQ2mPH4WxnqE5wxloHdP8BD7usNE/dMtGMhot3Dnf8WZEFpTMfhtrZrmSTCaANTQ== - dependencies: - "@aws-sdk/protocol-http" "3.224.0" - "@aws-sdk/types" "3.224.0" - "@aws-sdk/util-arn-parser" "3.208.0" - "@aws-sdk/util-config-provider" "3.208.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-content-length@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.193.0.tgz" - integrity sha512-em0Sqo7O7DFOcVXU460pbcYuIjblDTZqK2YE62nQ0T+5Nbj+MSjuoite+rRRdRww9VqBkUROGKON45bUNjogtQ== - dependencies: - "@aws-sdk/protocol-http" "3.193.0" - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-content-length@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.224.0.tgz" - integrity sha512-L9b84b7X/BH+sFZaXg5hQQv0TRqZIGuOIiWJ8CkYeju7OQV03DzbCoNCAgZdI28SSevfrrVK/hwjEQrv+A6x1Q== - dependencies: - "@aws-sdk/protocol-http" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-content-length@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-content-length/-/middleware-content-length-3.257.0.tgz#b84274ccdfca70068ce8526a197ab00359404a9a" - integrity sha512-yiawbV2azm6QnMY1L2ypG8PDRdjOcEIvFmT0T7y0F49rfbKJOu21j1ONAoCkLrINK6kMqcD5JSQLVCoURxiTxQ== - dependencies: - "@aws-sdk/protocol-http" "3.257.0" - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-endpoint@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.193.0.tgz" - integrity sha512-Inbpt7jcHGvzF7UOJOCxx9wih0+eAQYERikokidWJa7M405EJpVYq1mGbeOcQUPANU3uWF1AObmUUFhbkriHQw== - dependencies: - "@aws-sdk/middleware-serde" "3.193.0" - "@aws-sdk/protocol-http" "3.193.0" - "@aws-sdk/signature-v4" "3.193.0" - "@aws-sdk/types" "3.193.0" - "@aws-sdk/url-parser" "3.193.0" - "@aws-sdk/util-config-provider" "3.188.0" - "@aws-sdk/util-middleware" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-endpoint@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.224.0.tgz" - integrity sha512-Y+FkQmRyhQUX1E1tviodFwTrfAVjgteoALkFgIb7bxT7fmyQ/AQvdAytkDqIApTgkR61niNDSsAu7lHekDxQgg== - dependencies: - "@aws-sdk/middleware-serde" "3.224.0" - "@aws-sdk/protocol-http" "3.224.0" - "@aws-sdk/signature-v4" "3.224.0" - "@aws-sdk/types" "3.224.0" - "@aws-sdk/url-parser" "3.224.0" - "@aws-sdk/util-config-provider" "3.208.0" - "@aws-sdk/util-middleware" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-endpoint@3.226.0": - version "3.226.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.226.0.tgz#d776480be4b5a9534c2805b7425be05497f840b7" - integrity sha512-EvLFafjtUxTT0AC9p3aBQu1/fjhWdIeK58jIXaNFONfZ3F8QbEYUPuF/SqZvJM6cWfOO9qwYKkRDbCSTYhprIg== - dependencies: - "@aws-sdk/middleware-serde" "3.226.0" - "@aws-sdk/protocol-http" "3.226.0" - "@aws-sdk/signature-v4" "3.226.0" - "@aws-sdk/types" "3.226.0" - "@aws-sdk/url-parser" "3.226.0" - "@aws-sdk/util-config-provider" "3.208.0" - "@aws-sdk/util-middleware" "3.226.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-endpoint@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.257.0.tgz#425ee4ab43807b34957685d782c84fd418a2526f" - integrity sha512-RQNQe/jeVuWZtXXfcOm+e3qMFICY6ERsXUrbt0rjHgvajZCklcrRJgxJSCwrcS7Le3nl9azFPMAMj9L7uSK28g== - dependencies: - "@aws-sdk/middleware-serde" "3.257.0" - "@aws-sdk/protocol-http" "3.257.0" - "@aws-sdk/signature-v4" "3.257.0" - "@aws-sdk/types" "3.257.0" - "@aws-sdk/url-parser" "3.257.0" - "@aws-sdk/util-config-provider" "3.208.0" - "@aws-sdk/util-middleware" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-expect-continue@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.224.0.tgz" - integrity sha512-xgihNtu5dXzRqL0QrOuMLmSoji7BsKJ+rCXjW+X+Z1flYFV5UDY5PI0dgAlgWQDWZDyu17n4R5IIZUzb/aAI1g== - dependencies: - "@aws-sdk/protocol-http" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-flexible-checksums@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.224.0.tgz" - integrity sha512-8umP3a1YNg5+sowQgzKNiq//vSVC53iTBzg8/oszstwIMYE9aNf4RKd/X/H9biBF/G05xdTjqNAQrAh54UbKrQ== - dependencies: - "@aws-crypto/crc32" "2.0.0" - "@aws-crypto/crc32c" "2.0.0" - "@aws-sdk/is-array-buffer" "3.201.0" - "@aws-sdk/protocol-http" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-host-header@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.193.0.tgz" - integrity sha512-aegzj5oRWd//lmfmkzRmgG2b4l3140v8Ey4QkqCxcowvAEX5a7rh23yuKaGtmiePwv2RQalCKz+tN6JXCm8g6Q== - dependencies: - "@aws-sdk/protocol-http" "3.193.0" - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-host-header@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.224.0.tgz" - integrity sha512-4eL8EVhgxTjvdVs+P3SSEkoMXBte7hSQ/+kOZVNR5ze8QPnUiDpJMS2BQrMoA2INxX9tSqp6zTrDNMc3LNvKbQ== - dependencies: - "@aws-sdk/protocol-http" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-host-header@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.257.0.tgz#75d2ddb8073f901961665070d69c5ff3736fabdc" - integrity sha512-gEi9AJdJfRfU8Qr6HK1hfhxTzyV3Giq4B/h7um99hIFAT/GCg9xiPvAOKPo6UeuiKEv3b7RpSL4s6cBvnJMJBA== - dependencies: - "@aws-sdk/protocol-http" "3.257.0" - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-location-constraint@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.224.0.tgz" - integrity sha512-FpgKNGzImgmHTbz4Hjc41GEH4/dASxz6sTtn5T+kFDsT1j7o21tpWlS6psoazTz9Yi3ichBo2yzYUaY3QxOFew== - dependencies: - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-logger@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.193.0.tgz" - integrity sha512-D/h1pU5tAcyJpJ8ZeD1Sta0S9QZPcxERYRBiJdEl8VUrYwfy3Cl1WJedVOmd5nG73ZLRSyHeXHewb/ohge3yKQ== - dependencies: - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-logger@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.224.0.tgz" - integrity sha512-AmvuezI1vGgKZDsA2slHZJ6nQMqogUyzK27wM03458a2JgFqZvWCUPSY/P+OZ0FpnFEC34/kvvF4bI54T0C5jA== - dependencies: - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-logger@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.257.0.tgz#db35e776fe3561d0602fa39d6c69d68ee4ab36ca" - integrity sha512-8RDXW/VbMKBsXDfcCLmROZcWKyrekyiPa3J1aIaBy0tq9o4xpGoXw/lwwIrNVvISAFslb57rteup34bfn6ta6w== - dependencies: - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-recursion-detection@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.193.0.tgz" - integrity sha512-fMWP76Q1GOb/9OzS1arizm6Dbfo02DPZ6xp7OoAN3PS6ybH3Eb47s/gP3jzgBPAITQacFj4St/4a06YWYrN3NA== - dependencies: - "@aws-sdk/protocol-http" "3.193.0" - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-recursion-detection@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.224.0.tgz" - integrity sha512-ySTGlMvNaH5J77jYVVgwOF1ozz3Kp6f/wjTvivOcBR1zlRv0FXa1y033QMnrAAtKSNkzClXtNOycBM463QImJw== - dependencies: - "@aws-sdk/protocol-http" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-recursion-detection@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.257.0.tgz#83512e0228b41dfc37a337d2ad064cf6dc41f8df" - integrity sha512-rUCih6zHh8k9Edf5N5Er4s508FYbwLM0MWTD2axzlj9TjLqEQ9OKED3wHaLffXSDzodd3oTAfJCLPbWQyoZ3ZQ== - dependencies: - "@aws-sdk/protocol-http" "3.257.0" - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-retry@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.193.0.tgz" - integrity sha512-zTQkHLBQBJi6ns655WYcYLyLPc1tgbEYU080Oc8zlveLUqoDn1ogkcmNhG7XMeQuBvWZBYN7J3/wFaXlDzeCKg== - dependencies: - "@aws-sdk/protocol-http" "3.193.0" - "@aws-sdk/service-error-classification" "3.193.0" - "@aws-sdk/types" "3.193.0" - "@aws-sdk/util-middleware" "3.193.0" - tslib "^2.3.1" - uuid "^8.3.2" - -"@aws-sdk/middleware-retry@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.224.0.tgz" - integrity sha512-zwl8rZZb5OWLzOnEW58RRklbehDfcdtD98qtgm0NLM9ErBALEEb2Y4MM5zhRiMtVjzrDw71+Mhk5+4TAlwJyXA== - dependencies: - "@aws-sdk/protocol-http" "3.224.0" - "@aws-sdk/service-error-classification" "3.224.0" - "@aws-sdk/types" "3.224.0" - "@aws-sdk/util-middleware" "3.224.0" - tslib "^2.3.1" - uuid "^8.3.2" - -"@aws-sdk/middleware-retry@3.259.0": - version "3.259.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-retry/-/middleware-retry-3.259.0.tgz#18bbb2cd655fff1ea155dfcb9eaa2b583b67e42e" - integrity sha512-pVh1g8e84MAi7eVtWLiiiCtn82LzxOP7+LxTRHatmgIeN22yGQBZILliPDJypUPvDYlwxI1ekiK+oPTcte0Uww== - dependencies: - "@aws-sdk/protocol-http" "3.257.0" - "@aws-sdk/service-error-classification" "3.257.0" - "@aws-sdk/types" "3.257.0" - "@aws-sdk/util-middleware" "3.257.0" - "@aws-sdk/util-retry" "3.257.0" - tslib "^2.3.1" - uuid "^8.3.2" - -"@aws-sdk/middleware-sdk-s3@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.224.0.tgz" - integrity sha512-SDyFandByU9UBQOxqFk8TCE0e9FPA/nr0FRjANxkIm24/zxk2yZbk3OUx/Zr7ibo28b5BqcQV69IClBOukPiEw== - dependencies: - "@aws-sdk/middleware-bucket-endpoint" "3.224.0" - "@aws-sdk/protocol-http" "3.224.0" - "@aws-sdk/types" "3.224.0" - "@aws-sdk/util-arn-parser" "3.208.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-sdk-sts@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.193.0.tgz" - integrity sha512-TafiDkeflUsnbNa89TLkDnAiRRp1gAaZLDAjt75AzriRKZnhtFfYUXWb+qAuN50T+CkJ/gZI9LHDZL5ogz/HxQ== - dependencies: - "@aws-sdk/middleware-signing" "3.193.0" - "@aws-sdk/property-provider" "3.193.0" - "@aws-sdk/protocol-http" "3.193.0" - "@aws-sdk/signature-v4" "3.193.0" - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-sdk-sts@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.224.0.tgz" - integrity sha512-rUoPPejj4N8S+P39ap9Iqbprl9L7LBlkuMHwMCqgeRJBhdI+1YeDfUekegJxceJv/BDXaoI2aSE0tCUS8rK0Ug== - dependencies: - "@aws-sdk/middleware-signing" "3.224.0" - "@aws-sdk/property-provider" "3.224.0" - "@aws-sdk/protocol-http" "3.224.0" - "@aws-sdk/signature-v4" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-sdk-sts@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.257.0.tgz#9cfbe9e8846c9053a40e32bc695f4bd735afeae2" - integrity sha512-d6IJCLRi3O2tm4AFK60WNhIwmMmspj1WzKR1q1TaoPzoREPG2xg+Am18wZBRkCyYuRPPrbizmkvAmAJiUolMAw== - dependencies: - "@aws-sdk/middleware-signing" "3.257.0" - "@aws-sdk/property-provider" "3.257.0" - "@aws-sdk/protocol-http" "3.257.0" - "@aws-sdk/signature-v4" "3.257.0" - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-serde@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.193.0.tgz" - integrity sha512-dH93EJYVztY+ZDPzSMRi9LfAZfKO+luH62raNy49hlNa4jiyE1Tc/+qwlmOEpfGsrtcZ9TgsON1uFF9sgBXXaA== - dependencies: - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-serde@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.224.0.tgz" - integrity sha512-4wHJ4DyhvyqQ853zfIw6sRw909VB+hFEqatmXYvO5OYap03Eed92wslsR2Gtfw1B2/zjDscPpwPyHoCIk30sHA== - dependencies: - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-serde@3.226.0": - version "3.226.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-serde/-/middleware-serde-3.226.0.tgz#c837ef33b34bec2af19a1c177a0c02a1ae20da5e" - integrity sha512-nPuOOAkSfx9TxzdKFx0X2bDlinOxGrqD7iof926K/AEflxGD1DBdcaDdjlYlPDW2CVE8LV/rAgbYuLxh/E/1VA== - dependencies: - "@aws-sdk/types" "3.226.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-serde@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-serde/-/middleware-serde-3.257.0.tgz#13c529b942dafffcb198d9333f8f8dc2a662c187" - integrity sha512-/JasfXPWFq24mnCrx9fxW/ISBSp07RJwhsF14qzm8Qy3Z0z470C+QRM6otTwAkYuuVt1wuLjja5agq3Jtzq7dQ== - dependencies: - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-signing@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.193.0.tgz" - integrity sha512-obBoELGPf5ikvHYZwbzllLeuODiokdDfe92Ve2ufeOa/d8+xsmbqNzNdCTLNNTmr1tEIaEE7ngZVTOiHqAVhyw== - dependencies: - "@aws-sdk/property-provider" "3.193.0" - "@aws-sdk/protocol-http" "3.193.0" - "@aws-sdk/signature-v4" "3.193.0" - "@aws-sdk/types" "3.193.0" - "@aws-sdk/util-middleware" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-signing@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.224.0.tgz" - integrity sha512-6T+dybVn5EYsxkNc4eVKAeoj6x6FfRXkZWMRxkepDoOJufMUNTfpoDEl6PcgJU6Wq4odbqV737x/3j53VZc6dA== - dependencies: - "@aws-sdk/property-provider" "3.224.0" - "@aws-sdk/protocol-http" "3.224.0" - "@aws-sdk/signature-v4" "3.224.0" - "@aws-sdk/types" "3.224.0" - "@aws-sdk/util-middleware" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-signing@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.257.0.tgz#436c9e2fbbe1342c30572028e90ac62f7e90548f" - integrity sha512-hCH3D83LHmm6nqmtNrGTWZCVjsQXrGHIXbd17/qrw7aPFvcAhsiiCncGFP+XsUXEKa2ZqcSNMUyPrx69ofNRZQ== - dependencies: - "@aws-sdk/property-provider" "3.257.0" - "@aws-sdk/protocol-http" "3.257.0" - "@aws-sdk/signature-v4" "3.257.0" - "@aws-sdk/types" "3.257.0" - "@aws-sdk/util-middleware" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-ssec@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.224.0.tgz" - integrity sha512-S9a3fvF0Lv/NnXKbh0cbqhzfVcCOU1pPeGKuDB/p7AWCoql/KSG52MGBU6jKcevCtWVUKpSkgJfs+xkKmSiXIA== - dependencies: - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-stack@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.193.0.tgz" - integrity sha512-Ix5d7gE6bZwFNIVf0dGnjYuymz1gjitNoAZDPpv1nEZlUMek/jcno5lmzWFzUZXY/azpbIyaPwq/wm/c69au5A== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/middleware-stack@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.224.0.tgz" - integrity sha512-8mBrc3nj4h6FnDWnxbjfFXUPr/7UIAaGAG15D27Z/KNFnMjOqNTtpkbcoh3QQHRLX3PjTuvzT5WCqXmgD2/oiw== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/middleware-stack@3.226.0": - version "3.226.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-stack/-/middleware-stack-3.226.0.tgz#b0408370270188103987c457c758f9cf7651754f" - integrity sha512-85wF29LvPvpoed60fZGDYLwv1Zpd/cM0C22WSSFPw1SSJeqO4gtFYyCg2squfT3KI6kF43IIkOCJ+L7GtryPug== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/middleware-stack@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-stack/-/middleware-stack-3.257.0.tgz#c9fdc580c5337b703f87f6ae7df283540d6f16ac" - integrity sha512-awg2F0SvwACBaw4HIObK8pQGfSqAc4Vy+YFzWSfZNVC35oRO6RsRdKHVU99lRC0LrT2Ptmfghl2DMPSrRDbvlQ== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/middleware-user-agent@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.193.0.tgz" - integrity sha512-0vT6F9NwYQK7ARUUJeHTUIUPnupsO3IbmjHSi1+clkssFlJm2UfmSGeafiWe4AYH3anATTvZEtcxX5DZT/ExbA== - dependencies: - "@aws-sdk/protocol-http" "3.193.0" - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-user-agent@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.224.0.tgz" - integrity sha512-YXHC/n8k4qeIkqFVACPmF/QfJyKSOMD1HjM7iUZmJ9yGqDRFeGgn4o2Jktd0dor7sTv6pfUDkLqspxURAsokzA== - dependencies: - "@aws-sdk/protocol-http" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/middleware-user-agent@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.257.0.tgz#9ca650f5909bd9b55879835088760173a9d3d249" - integrity sha512-37rt75LZyD0UWpbcFuxEGqwF3DZKSixQPl7AsDe6q3KtrO5gGQB+diH5vbY0txNNYyv5IK9WMwvY73mVmoWRmw== - dependencies: - "@aws-sdk/protocol-http" "3.257.0" - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/node-config-provider@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.193.0.tgz" - integrity sha512-5RLdjQLH69ISRG8TX9klSLOpEySXxj+z9E9Em39HRvw0/rDcd8poCTADvjYIOqRVvMka0z/hm+elvUTIVn/DRw== - dependencies: - "@aws-sdk/property-provider" "3.193.0" - "@aws-sdk/shared-ini-file-loader" "3.193.0" - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/node-config-provider@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.224.0.tgz" - integrity sha512-ULv0Ao95vNEiwCreN9ZbZ5vntaGjdMLolCiyt3B2FDWbuOorZJR5QXFydPBpo4AQOh1y/S2MIUWLhz00DY364g== - dependencies: - "@aws-sdk/property-provider" "3.224.0" - "@aws-sdk/shared-ini-file-loader" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/node-config-provider@3.259.0": - version "3.259.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/node-config-provider/-/node-config-provider-3.259.0.tgz#0b522020c4a0e445b41f7150ce624b7b63e96e68" - integrity sha512-DUOqr71oonBvM6yKPdhDBmraqgXHCFrVWFw7hc5ZNxL2wS/EsbKfGPJp+C+SUgpn1upIWPNnh/bNoLAbBkcLsA== - dependencies: - "@aws-sdk/property-provider" "3.257.0" - "@aws-sdk/shared-ini-file-loader" "3.257.0" - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/node-http-handler@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.193.0.tgz" - integrity sha512-DP4BmFw64HOShgpAPEEMZedVnRmKKjHOwMEoXcnNlAkMXnYUFHiKvudYq87Q2AnSlT6OHkyMviB61gEvIk73dA== - dependencies: - "@aws-sdk/abort-controller" "3.193.0" - "@aws-sdk/protocol-http" "3.193.0" - "@aws-sdk/querystring-builder" "3.193.0" - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/node-http-handler@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.224.0.tgz" - integrity sha512-8h4jWsfVRUcJKkqZ9msSN4LhldBpXdNlMcA8ku8IVEBHf5waxqpIhupwR0uCMmV3FDINLqkf/8EwEYAODeRjrw== - dependencies: - "@aws-sdk/abort-controller" "3.224.0" - "@aws-sdk/protocol-http" "3.224.0" - "@aws-sdk/querystring-builder" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/node-http-handler@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/node-http-handler/-/node-http-handler-3.257.0.tgz#33e3ba0d8b0bf72a05be6c91e6b4cf90b8a7b786" - integrity sha512-8KnWHVVwaGKyTlkTU9BSOAiSovNDoagxemU2l10QqBbzUCVpljCUMUkABEGRJ1yoQCl6DJ7RtNkAyZ8Ne/E15A== - dependencies: - "@aws-sdk/abort-controller" "3.257.0" - "@aws-sdk/protocol-http" "3.257.0" - "@aws-sdk/querystring-builder" "3.257.0" - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/property-provider@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.193.0.tgz" - integrity sha512-IaDR/PdZjKlAeSq2E/6u6nkPsZF9wvhHZckwH7uumq4ocWsWXFzaT+hKpV4YZPHx9n+K2YV4Gn/bDedpz99W1Q== - dependencies: - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/property-provider@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.224.0.tgz" - integrity sha512-1F1Hepndlmj6wykNv0ynlS9YTaT3LRF/mqXhCRGLbCWSmCiaW9BUH/ddMdBZJiSw7kcPePKid5ueW84fAO/nKg== - dependencies: - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/property-provider@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/property-provider/-/property-provider-3.257.0.tgz#dd6872ace54f8fd691a15167490ab52e40306c58" - integrity sha512-3rUbRAcF0GZ5PhDiXhS4yREfZ5hOEtvYEa9S/19OdM5eoypOaLU5XnFcCKfnccSP8SkdgpJujzxOMRWNWadlAQ== - dependencies: - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/protocol-http@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.193.0.tgz" - integrity sha512-r0wbTwFJyXq0uiImI6giqG3g/RO1N/y4wwPA7qr7OC+KXJ0NkyVxIf6e7Vx8h06aM1ATtngbwJaMP59kVCp85A== - dependencies: - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/protocol-http@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.224.0.tgz" - integrity sha512-myp31UkADbktZtIZLc4cNfr5zSNVJjPReoH37NPpvgREKOGg7ZB6Lb3UyKbjzrmIv985brMOunlMgIBIJhuPIg== - dependencies: - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/protocol-http@3.226.0": - version "3.226.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/protocol-http/-/protocol-http-3.226.0.tgz#0af7bdc331508e556b722aad0cb78eefa93466e3" - integrity sha512-zWkVqiTA9RXL6y0hhfZc9bcU4DX2NI6Hw9IhQmSPeM59mdbPjJlY4bLlMr5YxywqO3yQ/ylNoAfrEzrDjlOSRg== - dependencies: - "@aws-sdk/types" "3.226.0" - tslib "^2.3.1" - -"@aws-sdk/protocol-http@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/protocol-http/-/protocol-http-3.257.0.tgz#1452ce4f6a51e24297cc39f73aa889570dddd348" - integrity sha512-xt7LGOgZIvbLS3418AYQLacOqx+mo5j4mPiIMz7f6AaUg+/fBUgESVsncKDqxbEJVwwCXSka8Ca0cntJmoeMSw== - dependencies: - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/querystring-builder@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.193.0.tgz" - integrity sha512-PRaK6649iw0UO45UjUoiUzFcOKXZb8pMjjFJpqALpEvdZT3twxqhlPXujT7GWPKrSwO4uPLNnyYEtPY82wx2vw== - dependencies: - "@aws-sdk/types" "3.193.0" - "@aws-sdk/util-uri-escape" "3.188.0" - tslib "^2.3.1" - -"@aws-sdk/querystring-builder@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.224.0.tgz" - integrity sha512-Fwzt42wWRhf04TetQPqDL03jX5W2cAkRFQewOkIRYVFV17b72z4BFhKID6bpLEtNb4YagyllCWosNg1xooDURQ== - dependencies: - "@aws-sdk/types" "3.224.0" - "@aws-sdk/util-uri-escape" "3.201.0" - tslib "^2.3.1" - -"@aws-sdk/querystring-builder@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-builder/-/querystring-builder-3.257.0.tgz#75e662fc451cf59763bdee52ba64b05e5cd2de0a" - integrity sha512-mZHWLP7XIkzx1GIXO5WfX/iJ+aY9TWs02RE9FkdL2+by0HEMR65L3brQTbU1mIBJ7BjaPwYH24dljUOSABX7yg== - dependencies: - "@aws-sdk/types" "3.257.0" - "@aws-sdk/util-uri-escape" "3.201.0" - tslib "^2.3.1" - -"@aws-sdk/querystring-parser@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.193.0.tgz" - integrity sha512-dGEPCe8SK4/td5dSpiaEI3SvT5eHXrbJWbLGyD4FL3n7WCGMy2xVWAB/yrgzD0GdLDjDa8L5vLVz6yT1P9i+hA== - dependencies: - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/querystring-parser@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.224.0.tgz" - integrity sha512-UIJZ76ClFtALXRIQS3Za4R76JTsjCYReSBEQ7ag7RF1jwVZLAggdfED9w3XDrN7jbaK6i+aI3Y+eFeq0sB2fcA== - dependencies: - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/querystring-parser@3.226.0": - version "3.226.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-parser/-/querystring-parser-3.226.0.tgz#ba6a26727c98d46c95180e6cdc463039c5e4740d" - integrity sha512-FzB+VrQ47KAFxiPt2YXrKZ8AOLZQqGTLCKHzx4bjxGmwgsjV8yIbtJiJhZLMcUQV4LtGeIY9ixIqQhGvnZHE4A== - dependencies: - "@aws-sdk/types" "3.226.0" - tslib "^2.3.1" - -"@aws-sdk/querystring-parser@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-parser/-/querystring-parser-3.257.0.tgz#c8614e424d7d840c01be919161f61ef85eca46af" - integrity sha512-UDrE1dEwWrWT8dG2VCrGYrPxCWOkZ1fPTPkjpkR4KZEdQDZBqU5gYZF2xPj8Nz7pjQVHFuW2wFm3XYEk56GEjg== - dependencies: - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/service-error-classification@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.193.0.tgz" - integrity sha512-bPnXVu8ErE1RfWVVQKc2TE7EuoImUi4dSPW9g80fGRzJdQNwXb636C+7OUuWvSDzmFwuBYqZza8GZjVd+rz2zQ== - -"@aws-sdk/service-error-classification@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.224.0.tgz" - integrity sha512-0bnbYtCe+vqtaGItL+1UzQPt+yZLbU8G/aIXPQUL7555jdnjnbAtczCbIcLAJUqlE/OLwRhQVGLKbau8QAdxgQ== - -"@aws-sdk/service-error-classification@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/service-error-classification/-/service-error-classification-3.257.0.tgz#a374e811ac587b9beb6e3fda77f2249570da7a8e" - integrity sha512-FAyR0XsueGkkqDtkP03cTJQk52NdQ9sZelLynmmlGPUP75LApRPvFe1riKrou6+LsDbwVNVffj6mbDfIcOhaOw== - -"@aws-sdk/shared-ini-file-loader@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.193.0.tgz" - integrity sha512-hnvZup8RSpFXfah7Rrn6+lQJnAOCO+OiDJ2R/iMgZQh475GRQpLbu3cPhCOkjB14vVLygJtW8trK/0+zKq93bQ== - dependencies: - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/shared-ini-file-loader@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.224.0.tgz" - integrity sha512-6a/XP3lRRcX5ic+bXzF2f644KERVqMx+s0JRrGsPAwTMaMiV0A7Ifl4HKggx6dnxh8j/MXUMsWMtuxt/kCu86A== - dependencies: - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/shared-ini-file-loader@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.257.0.tgz#513eee5c7ffa343bf5d91bdd73870fc5c47a4ad3" - integrity sha512-HNjC1+Wx3xHiJc+CP14GhIdVhfQGSjroAsWseRxAhONocA9Fl1ZX4hx7+sA5c9nOoMVOovi6ivJ/6lCRPTDRrQ== - dependencies: - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/signature-v4-multi-region@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.224.0.tgz" - integrity sha512-xOW8rtEH2Rcadr+CFfiISZwcbf4jPdc4OvL6OiPsv+arndOhxk+4ZaRT2xic1FrVdYQypmSToRITYlZc9N7PjQ== - dependencies: - "@aws-sdk/protocol-http" "3.224.0" - "@aws-sdk/signature-v4" "3.224.0" - "@aws-sdk/types" "3.224.0" - "@aws-sdk/util-arn-parser" "3.208.0" - tslib "^2.3.1" - -"@aws-sdk/signature-v4@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.193.0.tgz" - integrity sha512-JEqqOB8wQZz6g1ERNUOIBFDFt8OJtz5G5Uh1CdkS5W66gyWnJEz/dE1hA2VTqqQwHGGEsIEV/hlzruU1lXsvFA== - dependencies: - "@aws-sdk/is-array-buffer" "3.188.0" - "@aws-sdk/types" "3.193.0" - "@aws-sdk/util-hex-encoding" "3.188.0" - "@aws-sdk/util-middleware" "3.193.0" - "@aws-sdk/util-uri-escape" "3.188.0" - tslib "^2.3.1" - -"@aws-sdk/signature-v4@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.224.0.tgz" - integrity sha512-+oq1iylYQOvdXXO7r18SEhXIZpLd3GvJhmoReX+yjvVq8mGevDAmQiw6lwFZ6748sOmH4CREWD5H9Snrj+zLMg== - dependencies: - "@aws-sdk/is-array-buffer" "3.201.0" - "@aws-sdk/types" "3.224.0" - "@aws-sdk/util-hex-encoding" "3.201.0" - "@aws-sdk/util-middleware" "3.224.0" - "@aws-sdk/util-uri-escape" "3.201.0" - tslib "^2.3.1" - -"@aws-sdk/signature-v4@3.226.0": - version "3.226.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4/-/signature-v4-3.226.0.tgz#100390b5c5b55a9b0abd05b06fceb36cfa0ecf98" - integrity sha512-/R5q5agdPd7HJB68XMzpxrNPk158EHUvkFkuRu5Qf3kkkHebEzWEBlWoVpUe6ss4rP9Tqcue6xPuaftEmhjpYw== - dependencies: - "@aws-sdk/is-array-buffer" "3.201.0" - "@aws-sdk/types" "3.226.0" - "@aws-sdk/util-hex-encoding" "3.201.0" - "@aws-sdk/util-middleware" "3.226.0" - "@aws-sdk/util-uri-escape" "3.201.0" - tslib "^2.3.1" - -"@aws-sdk/signature-v4@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4/-/signature-v4-3.257.0.tgz#c2f0c998bfe1980ed91e0f92c311682a61de0f90" - integrity sha512-aLQQN59X/D0+ShzPD3Anj5ntdMA/RFeNLOUCDyDvremViGi6yxUS98usQ/8bG5Rq0sW2GGMdbFUFmrDvqdiqEQ== - dependencies: - "@aws-sdk/is-array-buffer" "3.201.0" - "@aws-sdk/types" "3.257.0" - "@aws-sdk/util-hex-encoding" "3.201.0" - "@aws-sdk/util-middleware" "3.257.0" - "@aws-sdk/util-uri-escape" "3.201.0" - "@aws-sdk/util-utf8" "3.254.0" - tslib "^2.3.1" - -"@aws-sdk/smithy-client@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.193.0.tgz" - integrity sha512-BY0jhfW76vyXr7ODMaKO3eyS98RSrZgOMl6DTQV9sk7eFP/MPVlG7p7nfX/CDIgPBIO1z0A0i2CVIzYur9uGgQ== - dependencies: - "@aws-sdk/middleware-stack" "3.193.0" - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/smithy-client@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.224.0.tgz" - integrity sha512-KXXzzrCBv8ewWdtm/aolZHr2f9NRZOcDutFaWXbfSptEsK50Zi9PNzB9ZVKUHyAXYjwJHb2Sl18WRrwIxH6H4g== - dependencies: - "@aws-sdk/middleware-stack" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/smithy-client@3.226.0": - version "3.226.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/smithy-client/-/smithy-client-3.226.0.tgz#d6869ca3627ca33024616c0ec3f707981e080d59" - integrity sha512-BWr1FhWSUhkSBp0TLzliD5AQBjA2Jmo9FlOOt+cBwd9BKkSGlGj+HgATYJ83Sjjg2+J6qvEZBxB78LKVHhorBw== - dependencies: - "@aws-sdk/middleware-stack" "3.226.0" - "@aws-sdk/types" "3.226.0" - tslib "^2.3.1" - -"@aws-sdk/smithy-client@3.261.0": - version "3.261.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/smithy-client/-/smithy-client-3.261.0.tgz#538096a39198cf41fa8002467536e5af1958c518" - integrity sha512-j8XQEa3caZUVFVZfhJjaskw80O/tB+IXu84HMN44N7UkXaCFHirUsNjTDztJhnVXf/gKXzIqUqprfRnOvwLtIg== - dependencies: - "@aws-sdk/middleware-stack" "3.257.0" - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/token-providers@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.224.0.tgz" - integrity sha512-cswWqA4n1v3JIALYRA8Tq/4uHcFpBg5cgi2khNHBCF/H09Hu3dynGup6Ji8cCzf3fTak4eBQipcWaWUGE0hTGw== - dependencies: - "@aws-sdk/client-sso-oidc" "3.224.0" - "@aws-sdk/property-provider" "3.224.0" - "@aws-sdk/shared-ini-file-loader" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/token-providers@3.261.0": - version "3.261.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.261.0.tgz#29144d2f3a6f15737cde69eb794e95d7ab76558f" - integrity sha512-Vi/GOnx8rPvQz5TdJJl5CwpTX6uRsSE3fzh94O4FEAIxIFtb4P5juqg92+2CJ81C7iNduB6eEeSHtwWUylypXQ== - dependencies: - "@aws-sdk/client-sso-oidc" "3.261.0" - "@aws-sdk/property-provider" "3.257.0" - "@aws-sdk/shared-ini-file-loader" "3.257.0" - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/types@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/types/-/types-3.193.0.tgz" - integrity sha512-LV/wcPolRZKORrcHwkH59QMCkiDR5sM+9ZtuTxvyUGG2QFW/kjoxs08fUF10OWNJMrotBI+czDc5QJRgN8BlAw== - -"@aws-sdk/types@3.224.0", "@aws-sdk/types@^3.1.0", "@aws-sdk/types@^3.110.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/types/-/types-3.224.0.tgz" - integrity sha512-7te9gRondKPjEebyiPYn59Kr5LZOL48HXC05TzFIN/JXwWPJbQpROBPeKd53V1aRdr3vSQhDY01a+vDOBBrEUQ== - -"@aws-sdk/types@3.226.0": - version "3.226.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.226.0.tgz#3dba2ba223fbb8ac1ebc84de0e036ce69a81d469" - integrity sha512-MmmNHrWeO4man7wpOwrAhXlevqtOV9ZLcH4RhnG5LmRce0RFOApx24HoKENfFCcOyCm5LQBlsXCqi0dZWDWU0A== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/types@3.257.0", "@aws-sdk/types@^3.222.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.257.0.tgz#4951ee3456cd9a46829516f5596c2b8a05ffe06a" - integrity sha512-LmqXuBQBGeaGi/3Rp7XiEX1B5IPO2UUfBVvu0wwGqVsmstT0SbOVDZGPmxygACbm64n+PRx3uTSDefRfoiWYZg== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/url-parser@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.193.0.tgz" - integrity sha512-hwD1koJlOu2a6GvaSbNbdo7I6a3tmrsNTZr8bCjAcbqpc5pDThcpnl/Uaz3zHmMPs92U8I6BvWoK6pH8By06qw== - dependencies: - "@aws-sdk/querystring-parser" "3.193.0" - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/url-parser@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.224.0.tgz" - integrity sha512-DGQoiOxRVq9eEbmcGF7oz/htcHxFtLlUTzKbaX1gFuh1kmhRQwJIzz6vkrMdxOgPjvUYMJuMEcYnsHolDNWbMg== - dependencies: - "@aws-sdk/querystring-parser" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/url-parser@3.226.0": - version "3.226.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/url-parser/-/url-parser-3.226.0.tgz#f53d1f868b27fe74aca091a799f2af56237b15a2" - integrity sha512-p5RLE0QWyP0OcTOLmFcLdVgUcUEzmEfmdrnOxyNzomcYb0p3vUagA5zfa1HVK2azsQJFBv28GfvMnba9bGhObg== - dependencies: - "@aws-sdk/querystring-parser" "3.226.0" - "@aws-sdk/types" "3.226.0" - tslib "^2.3.1" - -"@aws-sdk/url-parser@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/url-parser/-/url-parser-3.257.0.tgz#99b1abb302426f1b24c9777789fb0479d52d675d" - integrity sha512-Qe/AcFe/NFZHa6cN2afXEQn9ehXxh57dWGdRjfjd2lQqNV4WW1R2pl2Tm1ZJ1dwuCNLJi4NHLMk8lrD3QQ8rdg== - dependencies: - "@aws-sdk/querystring-parser" "3.257.0" - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/util-arn-parser@3.208.0": - version "3.208.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.208.0.tgz" - integrity sha512-QV4af+kscova9dv4VuHOgH8wEr/IIYHDGcnyVtkUEqahCejWr1Kuk+SBK0xMwnZY5LSycOtQ8aeqHOn9qOjZtA== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/util-base64-browser@3.188.0": - version "3.188.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-base64-browser/-/util-base64-browser-3.188.0.tgz" - integrity sha512-qlH+5NZBLiyKziL335BEPedYxX6j+p7KFRWXvDQox9S+s+gLCayednpK+fteOhBenCcR9fUZOVuAPScy1I8qCg== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/util-base64-node@3.188.0": - version "3.188.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-base64-node/-/util-base64-node-3.188.0.tgz" - integrity sha512-r1dccRsRjKq+OhVRUfqFiW3sGgZBjHbMeHLbrAs9jrOjU2PTQ8PSzAXLvX/9lmp7YjmX17Qvlsg0NCr1tbB9OA== - dependencies: - "@aws-sdk/util-buffer-from" "3.188.0" - tslib "^2.3.1" - -"@aws-sdk/util-base64@3.208.0": - version "3.208.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.208.0.tgz" - integrity sha512-PQniZph5A6N7uuEOQi+1hnMz/FSOK/8kMFyFO+4DgA1dZ5pcKcn5wiFwHkcTb/BsgVqQa3Jx0VHNnvhlS8JyTg== - dependencies: - "@aws-sdk/util-buffer-from" "3.208.0" - tslib "^2.3.1" - -"@aws-sdk/util-body-length-browser@3.188.0": - version "3.188.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.188.0.tgz" - integrity sha512-8VpnwFWXhnZ/iRSl9mTf+VKOX9wDE8QtN4bj9pBfxwf90H1X7E8T6NkiZD3k+HubYf2J94e7DbeHs7fuCPW5Qg== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/util-body-length-node@3.188.0": - version "3.188.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.188.0.tgz" - integrity sha512-XwqP3vxk60MKp4YDdvDeCD6BPOiG2e+/Ou4AofZOy5/toB6NKz2pFNibQIUg2+jc7mPMnGnvOW3MQEgSJ+gu/Q== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/util-body-length-node@3.208.0": - version "3.208.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.208.0.tgz" - integrity sha512-3zj50e5g7t/MQf53SsuuSf0hEELzMtD8RX8C76f12OSRo2Bca4FLLYHe0TZbxcfQHom8/hOaeZEyTyMogMglqg== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/util-buffer-from@3.188.0": - version "3.188.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.188.0.tgz" - integrity sha512-NX1WXZ8TH20IZb4jPFT2CnLKSqZWddGxtfiWxD9M47YOtq/SSQeR82fhqqVjJn4P8w2F5E28f+Du4ntg/sGcxA== - dependencies: - "@aws-sdk/is-array-buffer" "3.188.0" - tslib "^2.3.1" - -"@aws-sdk/util-buffer-from@3.208.0": - version "3.208.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.208.0.tgz" - integrity sha512-7L0XUixNEFcLUGPeBF35enCvB9Xl+K6SQsmbrPk1P3mlV9mguWSDQqbOBwY1Ir0OVbD6H/ZOQU7hI/9RtRI0Zw== - dependencies: - "@aws-sdk/is-array-buffer" "3.201.0" - tslib "^2.3.1" - -"@aws-sdk/util-config-provider@3.188.0": - version "3.188.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.188.0.tgz" - integrity sha512-LBA7tLbi7v4uvbOJhSnjJrxbcRifKK/1ZVK94JTV2MNSCCyNkFotyEI5UWDl10YKriTIUyf7o5cakpiDZ3O4xg== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/util-config-provider@3.208.0": - version "3.208.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.208.0.tgz" - integrity sha512-DSRqwrERUsT34ug+anlMBIFooBEGwM8GejC7q00Y/9IPrQy50KnG5PW2NiTjuLKNi7pdEOlwTSEocJE15eDZIg== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/util-defaults-mode-browser@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.193.0.tgz" - integrity sha512-9riQKFrSJcsNAMnPA/3ltpSxNykeO20klE/UKjxEoD7UWjxLwsPK22UJjFwMRaHoAFcZD0LU/SgPxbC0ktCYCg== - dependencies: - "@aws-sdk/property-provider" "3.193.0" - "@aws-sdk/types" "3.193.0" - bowser "^2.11.0" - tslib "^2.3.1" - -"@aws-sdk/util-defaults-mode-browser@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.224.0.tgz" - integrity sha512-umk+A/pmlbuyvDCgdndgJUa0xitcTUF7XoUt/3qDTpNbzR5Dzgdbz74BgXUAEBJ8kPP5pCo2VE1ZD7fxqYU/dQ== - dependencies: - "@aws-sdk/property-provider" "3.224.0" - "@aws-sdk/types" "3.224.0" - bowser "^2.11.0" - tslib "^2.3.1" - -"@aws-sdk/util-defaults-mode-browser@3.261.0": - version "3.261.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.261.0.tgz#ea9f43fa569887a11db289b2e77ec6e518c5f4ed" - integrity sha512-lX3X1NfzQVV6cakepGV24uRcqevlDnQ8VgaCV8dhnw1FVThueFigyoFaUA02+uRXbV9KIbNWkEvweNtm2wvyDw== - dependencies: - "@aws-sdk/property-provider" "3.257.0" - "@aws-sdk/types" "3.257.0" - bowser "^2.11.0" - tslib "^2.3.1" - -"@aws-sdk/util-defaults-mode-node@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.193.0.tgz" - integrity sha512-occQmckvPRiM4YQIZnulfKKKjykGKWloa5ByGC5gOEGlyeP9zJpfs4zc/M2kArTAt+d2r3wkBtsKe5yKSlVEhA== - dependencies: - "@aws-sdk/config-resolver" "3.193.0" - "@aws-sdk/credential-provider-imds" "3.193.0" - "@aws-sdk/node-config-provider" "3.193.0" - "@aws-sdk/property-provider" "3.193.0" - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/util-defaults-mode-node@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.224.0.tgz" - integrity sha512-ZJQJ1McbQ5Rnf5foCFAKHT8Cbwg4IbM+bb6fCkHRJFH9AXEvwc+hPtSYf0KuI7TmoZFj9WG5JOE9Ns6g7lRHSA== - dependencies: - "@aws-sdk/config-resolver" "3.224.0" - "@aws-sdk/credential-provider-imds" "3.224.0" - "@aws-sdk/node-config-provider" "3.224.0" - "@aws-sdk/property-provider" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/util-defaults-mode-node@3.261.0": - version "3.261.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.261.0.tgz#a7c09e3912a0f23e42b5c183d2a297b632014f9f" - integrity sha512-4AK6yu4bOmHSocUdbGoEHbNXB09UA58ON2HBHY4NxMBuFBAd9XB2tYiyhce+Cm+o+lHbS8oQnw0VZw16WMzzew== - dependencies: - "@aws-sdk/config-resolver" "3.259.0" - "@aws-sdk/credential-provider-imds" "3.259.0" - "@aws-sdk/node-config-provider" "3.259.0" - "@aws-sdk/property-provider" "3.257.0" - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/util-endpoints@3.196.0": - version "3.196.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.196.0.tgz" - integrity sha512-X+DOpRUy/ij49a0GQtggk09oyIQGn0mhER6PbMT69IufZPIg3D5fC5FPEp8bfsPkb70fTEYQEsj/X/rgMQJKsA== - dependencies: - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/util-endpoints@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.224.0.tgz" - integrity sha512-k5hHbk7AP/cajw5rF7wmKP39B0WQMFdxrn8dcVOHVK0FZeKbaGCEmOf3AYXrQhswR9Xo815Rqffoml9B1z3bCA== - dependencies: - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/util-endpoints@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.257.0.tgz#40cc8f67b996f8ea173f43d0e58e57ca8c244e67" - integrity sha512-3bvmRn5XGYzPPWjLuvHBKdJOb+fijnb8Ungu9bfXnTYFsng/ndHUWeHC22O/p8w3OWoRYUIMaZHxdxe27BFozg== - dependencies: - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/util-hex-encoding@3.188.0": - version "3.188.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.188.0.tgz" - integrity sha512-QyWovTtjQ2RYxqVM+STPh65owSqzuXURnfoof778spyX4iQ4z46wOge1YV2ZtwS8w5LWd9eeVvDrLu5POPYOnA== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/util-hex-encoding@3.201.0": - version "3.201.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.201.0.tgz" - integrity sha512-7t1vR1pVxKx0motd3X9rI3m/xNp78p3sHtP5yo4NP4ARpxyJ0fokBomY8ScaH2D/B+U5o9ARxldJUdMqyBlJcA== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/util-locate-window@^3.0.0": - version "3.188.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.188.0.tgz" - integrity sha512-SxobBVLZkkLSawTCfeQnhVX3Azm9O+C2dngZVe1+BqtF8+retUbVTs7OfYeWBlawVkULKF2e781lTzEHBBjCzw== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/util-middleware@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.193.0.tgz" - integrity sha512-+aC6pmkcGgpxaMWCH/FXTsGWl2W342oQGs1OYKGi+W8z9UguXrqamWjdkdMqgunvj9qOEG2KBMKz1FWFFZlUyA== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/util-middleware@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.224.0.tgz" - integrity sha512-yA20k9sJdFgs7buVilWExUSJ/Ecr5UJRNQlmgzIpBo9kh5x/N8WyB4kN5MQw5UAA1UZ+j3jmA9+YLFT/mbX3IQ== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/util-middleware@3.226.0": - version "3.226.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-middleware/-/util-middleware-3.226.0.tgz#7069ae96e2e00f6bb82c722e073922fb2b051ca2" - integrity sha512-B96CQnwX4gRvQdaQkdUtqvDPkrptV5+va6FVeJOocU/DbSYMAScLxtR3peMS8cnlOT6nL1Eoa42OI9AfZz1VwQ== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/util-middleware@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-middleware/-/util-middleware-3.257.0.tgz#b84ee6832eea9d439ff7e7a0453ea56af87b6b7a" - integrity sha512-F9ieon8B8eGVs5tyZtAIG3DZEObDvujkspho0qRbUTHUosM0ylJLsMU800fmC/uRHLRrZvb/RSp59+kNDwSAMw== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/util-retry@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-retry/-/util-retry-3.257.0.tgz#20454375267e120576c9f24316dad0ebc489dc4b" - integrity sha512-l9TOsOAYtZxwW3q5fQKW4rsD9t2HVaBfQ4zBamHkNTfB4vBVvCnz4oxkvSvA2MlxCA6am+K1K/oj917Tpqk53g== - dependencies: - "@aws-sdk/service-error-classification" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/util-stream-browser@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-stream-browser/-/util-stream-browser-3.224.0.tgz" - integrity sha512-JS+C8CyxVFMQ69P4QIDTrzkhseEFCVFy2YHZYlCx3M5P+L1/PQHebTETYFMmO9ThY8TRXmYZDJHv79guvV+saQ== - dependencies: - "@aws-sdk/fetch-http-handler" "3.224.0" - "@aws-sdk/types" "3.224.0" - "@aws-sdk/util-base64" "3.208.0" - "@aws-sdk/util-hex-encoding" "3.201.0" - "@aws-sdk/util-utf8-browser" "3.188.0" - tslib "^2.3.1" - -"@aws-sdk/util-stream-node@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-stream-node/-/util-stream-node-3.224.0.tgz" - integrity sha512-ztvZHJJg9/BwUrKnSz3jV6T8oOdxA1MRypK2zqTdjoPU9u/8CFQ2p0gszBApMjyxCnLWo1oM5oiMwzz1ufDrlA== - dependencies: - "@aws-sdk/node-http-handler" "3.224.0" - "@aws-sdk/types" "3.224.0" - "@aws-sdk/util-buffer-from" "3.208.0" - tslib "^2.3.1" - -"@aws-sdk/util-uri-escape@3.188.0": - version "3.188.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.188.0.tgz" - integrity sha512-4Y6AYZMT483Tiuq8dxz5WHIiPNdSFPGrl6tRTo2Oi2FcwypwmFhqgEGcqxeXDUJktvaCBxeA08DLr/AemVhPCg== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/util-uri-escape@3.201.0": - version "3.201.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.201.0.tgz" - integrity sha512-TeTWbGx4LU2c5rx0obHeDFeO9HvwYwQtMh1yniBz00pQb6Qt6YVOETVQikRZ+XRQwEyCg/dA375UplIpiy54mA== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/util-user-agent-browser@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.193.0.tgz" - integrity sha512-1EkGYsUtOMEyJG/UBIR4PtmO3lVjKNoUImoMpLtEucoGbWz5RG9zFSwLevjFyFs5roUBFlxkSpTMo8xQ3aRzQg== - dependencies: - "@aws-sdk/types" "3.193.0" - bowser "^2.11.0" - tslib "^2.3.1" - -"@aws-sdk/util-user-agent-browser@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.224.0.tgz" - integrity sha512-Dm/30cLUIM1Oam4V//m9sPrXyGOKFslUXP7Mz2AlR1HelUYoreWAIe7Rx44HR6PaXyZmjW5K0ItmcJ7tCgyMpw== - dependencies: - "@aws-sdk/types" "3.224.0" - bowser "^2.11.0" - tslib "^2.3.1" - -"@aws-sdk/util-user-agent-browser@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.257.0.tgz#6fa29ab2a15bfa82ce77d77b12891109b7673fb9" - integrity sha512-YdavWK6/8Cw6mypEgysGGX/dT9p9qnzFbnN5PQsUY+JJk2Nx8fKFydjGiQ+6rWPeW17RAv9mmbboh9uPVWxVlw== - dependencies: - "@aws-sdk/types" "3.257.0" - bowser "^2.11.0" - tslib "^2.3.1" - -"@aws-sdk/util-user-agent-node@3.193.0": - version "3.193.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.193.0.tgz" - integrity sha512-G/2/1cSgsxVtREAm8Eq8Duib5PXzXknFRHuDpAxJ5++lsJMXoYMReS278KgV54cojOkAVfcODDTqmY3Av0WHhQ== - dependencies: - "@aws-sdk/node-config-provider" "3.193.0" - "@aws-sdk/types" "3.193.0" - tslib "^2.3.1" - -"@aws-sdk/util-user-agent-node@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.224.0.tgz" - integrity sha512-BTj0vPorfT7AJzv6RxJHrnAKdIHwZmGjp5TFFaCYgFkHAPsyCPceSdZUjBRW+HbiwEwKfoHOXLGjnOBSqddZKg== - dependencies: - "@aws-sdk/node-config-provider" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/util-user-agent-node@3.259.0": - version "3.259.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.259.0.tgz#61141a0d64668ebcbbb1ac3dac1f497ca9f3707e" - integrity sha512-R0VTmNs+ySDDebU98BUbsLyeIM5YmAEr9esPpy15XfSy3AWmAeru8nLlztdaLilHZzLIDzvM2t7NGk/FzZFCvA== - dependencies: - "@aws-sdk/node-config-provider" "3.259.0" - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/util-utf8-browser@3.188.0", "@aws-sdk/util-utf8-browser@^3.0.0": - version "3.188.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.188.0.tgz" - integrity sha512-jt627x0+jE+Ydr9NwkFstg3cUvgWh56qdaqAMDsqgRlKD21md/6G226z/Qxl7lb1VEW2LlmCx43ai/37Qwcj2Q== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/util-utf8-node@3.188.0": - version "3.188.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-utf8-node/-/util-utf8-node-3.188.0.tgz" - integrity sha512-hCgP4+C0Lekjpjt2zFJ2R/iHes5sBGljXa5bScOFAEkRUc0Qw0VNgTv7LpEbIOAwGmqyxBoCwBW0YHPW1DfmYQ== - dependencies: - "@aws-sdk/util-buffer-from" "3.188.0" - tslib "^2.3.1" - -"@aws-sdk/util-utf8-node@3.208.0": - version "3.208.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-utf8-node/-/util-utf8-node-3.208.0.tgz" - integrity sha512-jKY87Acv0yWBdFxx6bveagy5FYjz+dtV8IPT7ay1E2WPWH1czoIdMAkc8tSInK31T6CRnHWkLZ1qYwCbgRfERQ== - dependencies: - "@aws-sdk/util-buffer-from" "3.208.0" - tslib "^2.3.1" - -"@aws-sdk/util-utf8@3.254.0": - version "3.254.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-utf8/-/util-utf8-3.254.0.tgz#909af9c6549833a9a9bf77004b7484bfc96b2c35" - integrity sha512-14Kso/eIt5/qfIBmhEL9L1IfyUqswjSTqO2mY7KOzUZ9SZbwn3rpxmtkhmATkRjD7XIlLKaxBkI7tU9Zjzj8Kw== - dependencies: - "@aws-sdk/util-buffer-from" "3.208.0" - tslib "^2.3.1" - -"@aws-sdk/util-waiter@3.224.0": - version "3.224.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-waiter/-/util-waiter-3.224.0.tgz" - integrity sha512-+SNItYzUSPa8PV1iWwOi3637ztlczJNa2pZ/R1nWf2N8sAmk0BXzGJISv/GKvzPDyAz+uOpT549e8P6rRLZedA== - dependencies: - "@aws-sdk/abort-controller" "3.224.0" - "@aws-sdk/types" "3.224.0" - tslib "^2.3.1" - -"@aws-sdk/util-waiter@3.257.0": - version "3.257.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-waiter/-/util-waiter-3.257.0.tgz#0e390f7d8be457c276b74bf8fafb78257856d187" - integrity sha512-Fr6of3EDOcXVDs5534o7VsJMXdybB0uLy2LzeFAVSwGOY3geKhIquBAiUDqCVu9B+iTldrC0rQ9NIM7ZSpPG8w== - dependencies: - "@aws-sdk/abort-controller" "3.257.0" - "@aws-sdk/types" "3.257.0" - tslib "^2.3.1" - -"@aws-sdk/xml-builder@3.201.0": - version "3.201.0" - resolved "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.201.0.tgz" - integrity sha512-brRdB1wwMgjWEnOQsv7zSUhIQuh7DEicrfslAqHop4S4FtSI3GQAShpQqgOpMTNFYcpaWKmE/Y1MJmNY7xLCnw== - dependencies: - tslib "^2.3.1" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz" - integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== - dependencies: - "@babel/highlight" "^7.18.6" - -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.1": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.1.tgz" - integrity sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg== - -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.18.6": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/core/-/core-7.19.1.tgz" - integrity sha512-1H8VgqXme4UXCRv7/Wa1bq7RVymKOzC7znjyFM8KiEzwFqcKUKYNoQef4GhdklgNvoBXyW4gYhuBNCM5o1zImw== - dependencies: - "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.0" - "@babel/helper-compilation-targets" "^7.19.1" - "@babel/helper-module-transforms" "^7.19.0" - "@babel/helpers" "^7.19.0" - "@babel/parser" "^7.19.1" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.1" - "@babel/types" "^7.19.0" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.1" - semver "^6.3.0" - -"@babel/generator@^7.19.0", "@babel/generator@^7.7.2": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz" - integrity sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg== - dependencies: - "@babel/types" "^7.19.0" - "@jridgewell/gen-mapping" "^0.3.2" - jsesc "^2.5.1" - -"@babel/helper-annotate-as-pure@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz" - integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz" - integrity sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw== - dependencies: - "@babel/helper-explode-assignable-expression" "^7.18.6" - "@babel/types" "^7.18.9" - -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0", "@babel/helper-compilation-targets@^7.19.1": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz" - integrity sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg== - dependencies: - "@babel/compat-data" "^7.19.1" - "@babel/helper-validator-option" "^7.18.6" - browserslist "^4.21.3" - semver "^6.3.0" - -"@babel/helper-create-class-features-plugin@^7.18.6": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz" - integrity sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-member-expression-to-functions" "^7.18.9" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.18.9" - "@babel/helper-split-export-declaration" "^7.18.6" - -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz" - integrity sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - regexpu-core "^5.1.0" - -"@babel/helper-define-polyfill-provider@^0.3.3": - version "0.3.3" - resolved "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz" - integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww== - dependencies: - "@babel/helper-compilation-targets" "^7.17.7" - "@babel/helper-plugin-utils" "^7.16.7" - debug "^4.1.1" - lodash.debounce "^4.0.8" - resolve "^1.14.2" - semver "^6.1.2" - -"@babel/helper-environment-visitor@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz" - integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== - -"@babel/helper-explode-assignable-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz" - integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz" - integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== - dependencies: - "@babel/template" "^7.18.10" - "@babel/types" "^7.19.0" - -"@babel/helper-hoist-variables@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz" - integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-member-expression-to-functions@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz" - integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== - dependencies: - "@babel/types" "^7.18.9" - -"@babel/helper-module-imports@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz" - integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz" - integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.18.6" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" - "@babel/types" "^7.19.0" - -"@babel/helper-optimise-call-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz" - integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz" - integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== - -"@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz" - integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-wrap-function" "^7.18.9" - "@babel/types" "^7.18.9" - -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz" - integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-member-expression-to-functions" "^7.18.9" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/traverse" "^7.19.1" - "@babel/types" "^7.19.0" - -"@babel/helper-simple-access@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz" - integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-skip-transparent-expression-wrappers@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz" - integrity sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw== - dependencies: - "@babel/types" "^7.18.9" - -"@babel/helper-split-export-declaration@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz" - integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-string-parser@^7.18.10": - version "7.18.10" - resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz" - integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== - -"@babel/helper-validator-identifier@^7.18.6": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz" - integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== - -"@babel/helper-validator-option@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz" - integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== - -"@babel/helper-wrap-function@^7.18.9": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz" - integrity sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg== - dependencies: - "@babel/helper-function-name" "^7.19.0" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" - "@babel/types" "^7.19.0" - -"@babel/helpers@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.0.tgz" - integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg== - dependencies: - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" - "@babel/types" "^7.19.0" - -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== - dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.1", "@babel/parser@^7.7.0": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.19.1.tgz" - integrity sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A== - -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz" - integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz" - integrity sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" - "@babel/plugin-proposal-optional-chaining" "^7.18.9" - -"@babel/plugin-proposal-async-generator-functions@^7.19.1": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.1.tgz" - integrity sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-remap-async-to-generator" "^7.18.9" - "@babel/plugin-syntax-async-generators" "^7.8.4" - -"@babel/plugin-proposal-class-properties@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz" - integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-proposal-class-static-block@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz" - integrity sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - -"@babel/plugin-proposal-dynamic-import@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz" - integrity sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - -"@babel/plugin-proposal-export-namespace-from@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz" - integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - -"@babel/plugin-proposal-json-strings@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz" - integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-json-strings" "^7.8.3" - -"@babel/plugin-proposal-logical-assignment-operators@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz" - integrity sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - -"@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz" - integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - -"@babel/plugin-proposal-numeric-separator@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz" - integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - -"@babel/plugin-proposal-object-rest-spread@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz" - integrity sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q== - dependencies: - "@babel/compat-data" "^7.18.8" - "@babel/helper-compilation-targets" "^7.18.9" - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.18.8" - -"@babel/plugin-proposal-optional-catch-binding@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz" - integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - -"@babel/plugin-proposal-optional-chaining@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz" - integrity sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - -"@babel/plugin-proposal-private-methods@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz" - integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-proposal-private-property-in-object@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz" - integrity sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - -"@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz" - integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.13", "@babel/plugin-syntax-class-properties@^7.8.3": - version "7.12.13" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz" - integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-export-namespace-from@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz" - integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-syntax-import-assertions@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz" - integrity sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-syntax-import-meta@^7.8.3": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-top-level-await@^7.14.5", "@babel/plugin-syntax-top-level-await@^7.8.3": - version "7.14.5" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.7.2": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz" - integrity sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-arrow-functions@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz" - integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-async-to-generator@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz" - integrity sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag== - dependencies: - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-remap-async-to-generator" "^7.18.6" - -"@babel/plugin-transform-block-scoped-functions@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz" - integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-block-scoping@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz" - integrity sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-classes@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz" - integrity sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-compilation-targets" "^7.19.0" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-replace-supers" "^7.18.9" - "@babel/helper-split-export-declaration" "^7.18.6" - globals "^11.1.0" - -"@babel/plugin-transform-computed-properties@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz" - integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-destructuring@^7.18.13": - version "7.18.13" - resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz" - integrity sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz" - integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-duplicate-keys@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz" - integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-exponentiation-operator@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz" - integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== - dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-for-of@^7.18.8": - version "7.18.8" - resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz" - integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-function-name@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz" - integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ== - dependencies: - "@babel/helper-compilation-targets" "^7.18.9" - "@babel/helper-function-name" "^7.18.9" - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-literals@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz" - integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-member-expression-literals@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz" - integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-modules-amd@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz" - integrity sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg== - dependencies: - "@babel/helper-module-transforms" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - babel-plugin-dynamic-import-node "^2.3.3" - -"@babel/plugin-transform-modules-commonjs@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz" - integrity sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q== - dependencies: - "@babel/helper-module-transforms" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-simple-access" "^7.18.6" - babel-plugin-dynamic-import-node "^2.3.3" - -"@babel/plugin-transform-modules-systemjs@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.0.tgz" - integrity sha512-x9aiR0WXAWmOWsqcsnrzGR+ieaTMVyGyffPVA7F8cXAGt/UxefYv6uSHZLkAFChN5M5Iy1+wjE+xJuPt22H39A== - dependencies: - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-module-transforms" "^7.19.0" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-validator-identifier" "^7.18.6" - babel-plugin-dynamic-import-node "^2.3.3" - -"@babel/plugin-transform-modules-umd@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz" - integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ== - dependencies: - "@babel/helper-module-transforms" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.19.1": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz" - integrity sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.19.0" - "@babel/helper-plugin-utils" "^7.19.0" - -"@babel/plugin-transform-new-target@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz" - integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-object-super@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz" - integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-replace-supers" "^7.18.6" - -"@babel/plugin-transform-parameters@^7.18.8": - version "7.18.8" - resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz" - integrity sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-property-literals@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz" - integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-regenerator@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz" - integrity sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - regenerator-transform "^0.15.0" - -"@babel/plugin-transform-reserved-words@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz" - integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-shorthand-properties@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz" - integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-spread@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz" - integrity sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w== - dependencies: - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" - -"@babel/plugin-transform-sticky-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz" - integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-template-literals@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz" - integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-typeof-symbol@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz" - integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-unicode-escapes@^7.18.10": - version "7.18.10" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz" - integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-unicode-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz" - integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/preset-env@^7.18.6": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.19.1.tgz" - integrity sha512-c8B2c6D16Lp+Nt6HcD+nHl0VbPKVnNPTpszahuxJJnurfMtKeZ80A+qUv48Y7wqvS+dTFuLuaM9oYxyNHbCLWA== - dependencies: - "@babel/compat-data" "^7.19.1" - "@babel/helper-compilation-targets" "^7.19.1" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-validator-option" "^7.18.6" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" - "@babel/plugin-proposal-async-generator-functions" "^7.19.1" - "@babel/plugin-proposal-class-properties" "^7.18.6" - "@babel/plugin-proposal-class-static-block" "^7.18.6" - "@babel/plugin-proposal-dynamic-import" "^7.18.6" - "@babel/plugin-proposal-export-namespace-from" "^7.18.9" - "@babel/plugin-proposal-json-strings" "^7.18.6" - "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" - "@babel/plugin-proposal-numeric-separator" "^7.18.6" - "@babel/plugin-proposal-object-rest-spread" "^7.18.9" - "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" - "@babel/plugin-proposal-optional-chaining" "^7.18.9" - "@babel/plugin-proposal-private-methods" "^7.18.6" - "@babel/plugin-proposal-private-property-in-object" "^7.18.6" - "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-import-assertions" "^7.18.6" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-transform-arrow-functions" "^7.18.6" - "@babel/plugin-transform-async-to-generator" "^7.18.6" - "@babel/plugin-transform-block-scoped-functions" "^7.18.6" - "@babel/plugin-transform-block-scoping" "^7.18.9" - "@babel/plugin-transform-classes" "^7.19.0" - "@babel/plugin-transform-computed-properties" "^7.18.9" - "@babel/plugin-transform-destructuring" "^7.18.13" - "@babel/plugin-transform-dotall-regex" "^7.18.6" - "@babel/plugin-transform-duplicate-keys" "^7.18.9" - "@babel/plugin-transform-exponentiation-operator" "^7.18.6" - "@babel/plugin-transform-for-of" "^7.18.8" - "@babel/plugin-transform-function-name" "^7.18.9" - "@babel/plugin-transform-literals" "^7.18.9" - "@babel/plugin-transform-member-expression-literals" "^7.18.6" - "@babel/plugin-transform-modules-amd" "^7.18.6" - "@babel/plugin-transform-modules-commonjs" "^7.18.6" - "@babel/plugin-transform-modules-systemjs" "^7.19.0" - "@babel/plugin-transform-modules-umd" "^7.18.6" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.1" - "@babel/plugin-transform-new-target" "^7.18.6" - "@babel/plugin-transform-object-super" "^7.18.6" - "@babel/plugin-transform-parameters" "^7.18.8" - "@babel/plugin-transform-property-literals" "^7.18.6" - "@babel/plugin-transform-regenerator" "^7.18.6" - "@babel/plugin-transform-reserved-words" "^7.18.6" - "@babel/plugin-transform-shorthand-properties" "^7.18.6" - "@babel/plugin-transform-spread" "^7.19.0" - "@babel/plugin-transform-sticky-regex" "^7.18.6" - "@babel/plugin-transform-template-literals" "^7.18.9" - "@babel/plugin-transform-typeof-symbol" "^7.18.9" - "@babel/plugin-transform-unicode-escapes" "^7.18.10" - "@babel/plugin-transform-unicode-regex" "^7.18.6" - "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.19.0" - babel-plugin-polyfill-corejs2 "^0.3.3" - babel-plugin-polyfill-corejs3 "^0.6.0" - babel-plugin-polyfill-regenerator "^0.4.1" - core-js-compat "^3.25.1" - semver "^6.3.0" - -"@babel/preset-modules@^0.1.5": - version "0.1.5" - resolved "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz" - integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" - "@babel/plugin-transform-dotall-regex" "^7.4.4" - "@babel/types" "^7.4.4" - esutils "^2.0.2" - -"@babel/runtime@^7.8.4": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz" - integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== - dependencies: - regenerator-runtime "^0.13.4" - -"@babel/template@^7.18.10", "@babel/template@^7.3.3": - version "7.18.10" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz" - integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.18.10" - "@babel/types" "^7.18.10" - -"@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.1.tgz" - integrity sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.0" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.19.1" - "@babel/types" "^7.19.0" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz" - integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== - dependencies: - "@babel/helper-string-parser" "^7.18.10" - "@babel/helper-validator-identifier" "^7.18.6" - to-fast-properties "^2.0.0" - -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== - -"@cbor-extract/cbor-extract-darwin-arm64@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.1.1.tgz#5721f6dd3feae0b96d23122853ce977e0671b7a6" - integrity sha512-blVBy5MXz6m36Vx0DfLd7PChOQKEs8lK2bD1WJn/vVgG4FXZiZmZb2GECHFvVPA5T7OnODd9xZiL3nMCv6QUhA== - -"@cbor-extract/cbor-extract-darwin-x64@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.1.1.tgz#c25e7d0133950d87d101d7b3afafea8d50d83f5f" - integrity sha512-h6KFOzqk8jXTvkOftyRIWGrd7sKQzQv2jVdTL9nKSf3D2drCvQB/LHUxAOpPXo3pv2clDtKs3xnHalpEh3rDsw== - -"@cbor-extract/cbor-extract-linux-arm64@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.1.1.tgz#48f78e7d8f0fcc84ed074b6bfa6d15dd83187c63" - integrity sha512-SxAaRcYf8S0QHaMc7gvRSiTSr7nUYMqbUdErBEu+HYA4Q6UNydx1VwFE68hGcp1qvxcy9yT5U7gA+a5XikfwSQ== - -"@cbor-extract/cbor-extract-linux-arm@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.1.1.tgz#7507d346389cb682e44fab8fae9534edd52e2e41" - integrity sha512-ds0uikdcIGUjPyraV4oJqyVE5gl/qYBpa/Wnh6l6xLE2lj/hwnjT2XcZCChdXwW/YFZ1LUHs6waoYN8PmK0nKQ== - -"@cbor-extract/cbor-extract-linux-x64@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.1.1.tgz#b7c1d2be61c58ec18d58afbad52411ded63cd4cd" - integrity sha512-GVK+8fNIE9lJQHAlhOROYiI0Yd4bAZ4u++C2ZjlkS3YmO6hi+FUxe6Dqm+OKWTcMpL/l71N6CQAmaRcb4zyJuA== - -"@cbor-extract/cbor-extract-win32-x64@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.1.1.tgz#21b11a1a3f18c3e7d62fd5f87438b7ed2c64c1f7" - integrity sha512-2Niq1C41dCRIDeD8LddiH+mxGlO7HJ612Ll3D/E73ZWBmycued+8ghTr/Ho3CMOWPUEr08XtyBMVXAjqF+TcKw== - -"@cspotcode/source-map-support@^0.8.0": - version "0.8.1" - resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" - integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== - dependencies: - "@jridgewell/trace-mapping" "0.3.9" - -"@did-plc/lib@*", "@did-plc/lib@^0.0.1": - version "0.0.1" - resolved "https://registry.yarnpkg.com/@did-plc/lib/-/lib-0.0.1.tgz#5fd78c71901168ac05c5650af3a376c76461991c" - integrity sha512-RkY5w9DbYMco3SjeepqIiMveqz35exjlVDipCs2gz9AXF4/cp9hvmrp9zUWEw2vny+FjV8vGEN7QpaXWaO6nhg== - dependencies: - "@atproto/common" "0.1.0" - "@atproto/crypto" "0.1.0" - "@ipld/dag-cbor" "^7.0.3" - axios "^1.3.4" - multiformats "^9.6.4" - uint8arrays "3.0.0" - zod "^3.14.2" - -"@did-plc/server@^0.0.1": - version "0.0.1" - resolved "https://registry.yarnpkg.com/@did-plc/server/-/server-0.0.1.tgz#8d1ba701f3b2b952b7c8fe03ef3118bb0cba077c" - integrity sha512-GtxxHcOrOQ6fNI1ufq3Zqjc2PtWqPZOdsuzlwtxiH9XibUGwDkb0GmaBHyU5GiOxOKZEW1GspZ8mreBA6XOlTQ== - dependencies: - "@atproto/common" "0.1.0" - "@atproto/crypto" "0.1.0" - "@did-plc/lib" "*" - axios "^1.3.4" - cors "^2.8.5" - express "^4.18.2" - express-async-errors "^3.1.1" - http-terminator "^3.2.0" - kysely "^0.23.4" - multiformats "^9.6.4" - pg "^8.9.0" - pino "^8.11.0" - pino-http "^8.3.3" - -"@esbuild/linux-loong64@0.14.54": - version "0.14.54" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028" - integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw== - -"@eslint/eslintrc@^1.3.2": - version "1.3.2" - resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.2.tgz" - integrity sha512-AXYd23w1S/bv3fTs3Lz0vjiYemS08jWkI3hYyS9I1ry+0f+Yjs1wm+sU0BS8qDOPrBIkp4qHYC16I8uVtpLajQ== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^9.4.0" - globals "^13.15.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@gar/promisify@^1.0.1", "@gar/promisify@^1.1.3": - version "1.1.3" - resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz" - integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== - -"@humanwhocodes/config-array@^0.10.5": - version "0.10.6" - resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.6.tgz" - integrity sha512-U/piU+VwXZsIgwnl+N+nRK12jCpHdc3s0UAc6zc1+HUgiESJxClpvYao/x9JwaN7onNeVb7kTlxlAvuEoaJ3ig== - dependencies: - "@humanwhocodes/object-schema" "^1.2.1" - debug "^4.1.1" - minimatch "^3.0.4" - -"@humanwhocodes/gitignore-to-minimatch@^1.0.2": - version "1.0.2" - resolved "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz" - integrity sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA== - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/object-schema@^1.2.1": - version "1.2.1" - resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== - -"@hutson/parse-repository-url@^3.0.0": - version "3.0.2" - resolved "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz" - integrity sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q== - -"@ipld/car@^3.2.3": - version "3.2.4" - resolved "https://registry.yarnpkg.com/@ipld/car/-/car-3.2.4.tgz#115951ba2255ec51d865773a074e422c169fb01c" - integrity sha512-rezKd+jk8AsTGOoJKqzfjLJ3WVft7NZNH95f0pfPbicROvzTyvHCNy567HzSUd6gRXZ9im29z5ZEv9Hw49jSYw== - dependencies: - "@ipld/dag-cbor" "^7.0.0" - multiformats "^9.5.4" - varint "^6.0.0" - -"@ipld/dag-cbor@^7.0.0", "@ipld/dag-cbor@^7.0.3": - version "7.0.3" - resolved "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-7.0.3.tgz" - integrity sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA== - dependencies: - cborg "^1.6.0" - multiformats "^9.5.4" - -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - dependencies: - camelcase "^5.3.1" - find-up "^4.1.0" - get-package-type "^0.1.0" - js-yaml "^3.13.1" - resolve-from "^5.0.0" - -"@istanbuljs/schema@^0.1.2": - version "0.1.3" - resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/console@^28.1.3": - version "28.1.3" - resolved "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz" - integrity sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw== - dependencies: - "@jest/types" "^28.1.3" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^28.1.3" - jest-util "^28.1.3" - slash "^3.0.0" - -"@jest/core@^28.1.3": - version "28.1.3" - resolved "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz" - integrity sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA== - dependencies: - "@jest/console" "^28.1.3" - "@jest/reporters" "^28.1.3" - "@jest/test-result" "^28.1.3" - "@jest/transform" "^28.1.3" - "@jest/types" "^28.1.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - ci-info "^3.2.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-changed-files "^28.1.3" - jest-config "^28.1.3" - jest-haste-map "^28.1.3" - jest-message-util "^28.1.3" - jest-regex-util "^28.0.2" - jest-resolve "^28.1.3" - jest-resolve-dependencies "^28.1.3" - jest-runner "^28.1.3" - jest-runtime "^28.1.3" - jest-snapshot "^28.1.3" - jest-util "^28.1.3" - jest-validate "^28.1.3" - jest-watcher "^28.1.3" - micromatch "^4.0.4" - pretty-format "^28.1.3" - rimraf "^3.0.0" - slash "^3.0.0" - strip-ansi "^6.0.0" - -"@jest/create-cache-key-function@^27.4.2": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-27.5.1.tgz#7448fae15602ea95c828f5eceed35c202a820b31" - integrity sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ== - dependencies: - "@jest/types" "^27.5.1" - -"@jest/environment@^28.1.3": - version "28.1.3" - resolved "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz" - integrity sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA== - dependencies: - "@jest/fake-timers" "^28.1.3" - "@jest/types" "^28.1.3" - "@types/node" "*" - jest-mock "^28.1.3" - -"@jest/expect-utils@^28.1.3": - version "28.1.3" - resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz" - integrity sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA== - dependencies: - jest-get-type "^28.0.2" - -"@jest/expect@^28.1.3": - version "28.1.3" - resolved "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz" - integrity sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw== - dependencies: - expect "^28.1.3" - jest-snapshot "^28.1.3" - -"@jest/fake-timers@^28.1.3": - version "28.1.3" - resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz" - integrity sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw== - dependencies: - "@jest/types" "^28.1.3" - "@sinonjs/fake-timers" "^9.1.2" - "@types/node" "*" - jest-message-util "^28.1.3" - jest-mock "^28.1.3" - jest-util "^28.1.3" - -"@jest/globals@^28.1.3": - version "28.1.3" - resolved "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz" - integrity sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA== - dependencies: - "@jest/environment" "^28.1.3" - "@jest/expect" "^28.1.3" - "@jest/types" "^28.1.3" - -"@jest/reporters@^28.1.3": - version "28.1.3" - resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz" - integrity sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^28.1.3" - "@jest/test-result" "^28.1.3" - "@jest/transform" "^28.1.3" - "@jest/types" "^28.1.3" - "@jridgewell/trace-mapping" "^0.3.13" - "@types/node" "*" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^5.1.0" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.1.3" - jest-message-util "^28.1.3" - jest-util "^28.1.3" - jest-worker "^28.1.3" - slash "^3.0.0" - string-length "^4.0.1" - strip-ansi "^6.0.0" - terminal-link "^2.0.0" - v8-to-istanbul "^9.0.1" - -"@jest/schemas@^28.1.3": - version "28.1.3" - resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz" - integrity sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg== - dependencies: - "@sinclair/typebox" "^0.24.1" - -"@jest/source-map@^28.1.2": - version "28.1.2" - resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz" - integrity sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww== - dependencies: - "@jridgewell/trace-mapping" "^0.3.13" - callsites "^3.0.0" - graceful-fs "^4.2.9" - -"@jest/test-result@^28.1.3": - version "28.1.3" - resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz" - integrity sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg== - dependencies: - "@jest/console" "^28.1.3" - "@jest/types" "^28.1.3" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^28.1.3": - version "28.1.3" - resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz" - integrity sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw== - dependencies: - "@jest/test-result" "^28.1.3" - graceful-fs "^4.2.9" - jest-haste-map "^28.1.3" - slash "^3.0.0" - -"@jest/transform@^28.1.3": - version "28.1.3" - resolved "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz" - integrity sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA== - dependencies: - "@babel/core" "^7.11.6" - "@jest/types" "^28.1.3" - "@jridgewell/trace-mapping" "^0.3.13" - babel-plugin-istanbul "^6.1.1" - chalk "^4.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^28.1.3" - jest-regex-util "^28.0.2" - jest-util "^28.1.3" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - write-file-atomic "^4.0.1" - -"@jest/types@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" - integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^16.0.0" - chalk "^4.0.0" - -"@jest/types@^28.1.3": - version "28.1.3" - resolved "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz" - integrity sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ== - dependencies: - "@jest/schemas" "^28.1.3" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@jridgewell/gen-mapping@^0.1.0": - version "0.1.1" - resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz" - integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== - dependencies: - "@jridgewell/set-array" "^1.0.0" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@jridgewell/gen-mapping@^0.3.2": - version "0.3.2" - resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/resolve-uri@^3.0.3": - version "3.1.0" - resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== - -"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.14" - resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/trace-mapping@0.3.9": - version "0.3.9" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" - integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.15" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz" - integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@lerna/add@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/add/-/add-4.0.0.tgz" - integrity sha512-cpmAH1iS3k8JBxNvnMqrGTTjbY/ZAiKa1ChJzFevMYY3eeqbvhsBKnBcxjRXtdrJ6bd3dCQM+ZtK+0i682Fhng== - dependencies: - "@lerna/bootstrap" "4.0.0" - "@lerna/command" "4.0.0" - "@lerna/filter-options" "4.0.0" - "@lerna/npm-conf" "4.0.0" - "@lerna/validation-error" "4.0.0" - dedent "^0.7.0" - npm-package-arg "^8.1.0" - p-map "^4.0.0" - pacote "^11.2.6" - semver "^7.3.4" - -"@lerna/bootstrap@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/bootstrap/-/bootstrap-4.0.0.tgz" - integrity sha512-RkS7UbeM2vu+kJnHzxNRCLvoOP9yGNgkzRdy4UV2hNalD7EP41bLvRVOwRYQ7fhc2QcbhnKNdOBihYRL0LcKtw== - dependencies: - "@lerna/command" "4.0.0" - "@lerna/filter-options" "4.0.0" - "@lerna/has-npm-version" "4.0.0" - "@lerna/npm-install" "4.0.0" - "@lerna/package-graph" "4.0.0" - "@lerna/pulse-till-done" "4.0.0" - "@lerna/rimraf-dir" "4.0.0" - "@lerna/run-lifecycle" "4.0.0" - "@lerna/run-topologically" "4.0.0" - "@lerna/symlink-binary" "4.0.0" - "@lerna/symlink-dependencies" "4.0.0" - "@lerna/validation-error" "4.0.0" - dedent "^0.7.0" - get-port "^5.1.1" - multimatch "^5.0.0" - npm-package-arg "^8.1.0" - npmlog "^4.1.2" - p-map "^4.0.0" - p-map-series "^2.1.0" - p-waterfall "^2.1.1" - read-package-tree "^5.3.1" - semver "^7.3.4" - -"@lerna/changed@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/changed/-/changed-4.0.0.tgz" - integrity sha512-cD+KuPRp6qiPOD+BO6S6SN5cARspIaWSOqGBpGnYzLb4uWT8Vk4JzKyYtc8ym1DIwyoFXHosXt8+GDAgR8QrgQ== - dependencies: - "@lerna/collect-updates" "4.0.0" - "@lerna/command" "4.0.0" - "@lerna/listable" "4.0.0" - "@lerna/output" "4.0.0" - -"@lerna/check-working-tree@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/check-working-tree/-/check-working-tree-4.0.0.tgz" - integrity sha512-/++bxM43jYJCshBiKP5cRlCTwSJdRSxVmcDAXM+1oUewlZJVSVlnks5eO0uLxokVFvLhHlC5kHMc7gbVFPHv6Q== - dependencies: - "@lerna/collect-uncommitted" "4.0.0" - "@lerna/describe-ref" "4.0.0" - "@lerna/validation-error" "4.0.0" - -"@lerna/child-process@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/child-process/-/child-process-4.0.0.tgz" - integrity sha512-XtCnmCT9eyVsUUHx6y/CTBYdV9g2Cr/VxyseTWBgfIur92/YKClfEtJTbOh94jRT62hlKLqSvux/UhxXVh613Q== - dependencies: - chalk "^4.1.0" - execa "^5.0.0" - strong-log-transformer "^2.1.0" - -"@lerna/clean@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/clean/-/clean-4.0.0.tgz" - integrity sha512-uugG2iN9k45ITx2jtd8nEOoAtca8hNlDCUM0N3lFgU/b1mEQYAPRkqr1qs4FLRl/Y50ZJ41wUz1eazS+d/0osA== - dependencies: - "@lerna/command" "4.0.0" - "@lerna/filter-options" "4.0.0" - "@lerna/prompt" "4.0.0" - "@lerna/pulse-till-done" "4.0.0" - "@lerna/rimraf-dir" "4.0.0" - p-map "^4.0.0" - p-map-series "^2.1.0" - p-waterfall "^2.1.1" - -"@lerna/cli@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/cli/-/cli-4.0.0.tgz" - integrity sha512-Neaw3GzFrwZiRZv2g7g6NwFjs3er1vhraIniEs0jjVLPMNC4eata0na3GfE5yibkM/9d3gZdmihhZdZ3EBdvYA== - dependencies: - "@lerna/global-options" "4.0.0" - dedent "^0.7.0" - npmlog "^4.1.2" - yargs "^16.2.0" - -"@lerna/collect-uncommitted@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/collect-uncommitted/-/collect-uncommitted-4.0.0.tgz" - integrity sha512-ufSTfHZzbx69YNj7KXQ3o66V4RC76ffOjwLX0q/ab//61bObJ41n03SiQEhSlmpP+gmFbTJ3/7pTe04AHX9m/g== - dependencies: - "@lerna/child-process" "4.0.0" - chalk "^4.1.0" - npmlog "^4.1.2" - -"@lerna/collect-updates@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/collect-updates/-/collect-updates-4.0.0.tgz" - integrity sha512-bnNGpaj4zuxsEkyaCZLka9s7nMs58uZoxrRIPJ+nrmrZYp1V5rrd+7/NYTuunOhY2ug1sTBvTAxj3NZQ+JKnOw== - dependencies: - "@lerna/child-process" "4.0.0" - "@lerna/describe-ref" "4.0.0" - minimatch "^3.0.4" - npmlog "^4.1.2" - slash "^3.0.0" - -"@lerna/command@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/command/-/command-4.0.0.tgz" - integrity sha512-LM9g3rt5FsPNFqIHUeRwWXLNHJ5NKzOwmVKZ8anSp4e1SPrv2HNc1V02/9QyDDZK/w+5POXH5lxZUI1CHaOK/A== - dependencies: - "@lerna/child-process" "4.0.0" - "@lerna/package-graph" "4.0.0" - "@lerna/project" "4.0.0" - "@lerna/validation-error" "4.0.0" - "@lerna/write-log-file" "4.0.0" - clone-deep "^4.0.1" - dedent "^0.7.0" - execa "^5.0.0" - is-ci "^2.0.0" - npmlog "^4.1.2" - -"@lerna/conventional-commits@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/conventional-commits/-/conventional-commits-4.0.0.tgz" - integrity sha512-CSUQRjJHFrH8eBn7+wegZLV3OrNc0Y1FehYfYGhjLE2SIfpCL4bmfu/ViYuHh9YjwHaA+4SX6d3hR+xkeseKmw== - dependencies: - "@lerna/validation-error" "4.0.0" - conventional-changelog-angular "^5.0.12" - conventional-changelog-core "^4.2.2" - conventional-recommended-bump "^6.1.0" - fs-extra "^9.1.0" - get-stream "^6.0.0" - lodash.template "^4.5.0" - npm-package-arg "^8.1.0" - npmlog "^4.1.2" - pify "^5.0.0" - semver "^7.3.4" - -"@lerna/create-symlink@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/create-symlink/-/create-symlink-4.0.0.tgz" - integrity sha512-I0phtKJJdafUiDwm7BBlEUOtogmu8+taxq6PtIrxZbllV9hWg59qkpuIsiFp+no7nfRVuaasNYHwNUhDAVQBig== - dependencies: - cmd-shim "^4.1.0" - fs-extra "^9.1.0" - npmlog "^4.1.2" - -"@lerna/create@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/create/-/create-4.0.0.tgz" - integrity sha512-mVOB1niKByEUfxlbKTM1UNECWAjwUdiioIbRQZEeEabtjCL69r9rscIsjlGyhGWCfsdAG5wfq4t47nlDXdLLag== - dependencies: - "@lerna/child-process" "4.0.0" - "@lerna/command" "4.0.0" - "@lerna/npm-conf" "4.0.0" - "@lerna/validation-error" "4.0.0" - dedent "^0.7.0" - fs-extra "^9.1.0" - globby "^11.0.2" - init-package-json "^2.0.2" - npm-package-arg "^8.1.0" - p-reduce "^2.1.0" - pacote "^11.2.6" - pify "^5.0.0" - semver "^7.3.4" - slash "^3.0.0" - validate-npm-package-license "^3.0.4" - validate-npm-package-name "^3.0.0" - whatwg-url "^8.4.0" - yargs-parser "20.2.4" - -"@lerna/describe-ref@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/describe-ref/-/describe-ref-4.0.0.tgz" - integrity sha512-eTU5+xC4C5Gcgz+Ey4Qiw9nV2B4JJbMulsYJMW8QjGcGh8zudib7Sduj6urgZXUYNyhYpRs+teci9M2J8u+UvQ== - dependencies: - "@lerna/child-process" "4.0.0" - npmlog "^4.1.2" - -"@lerna/diff@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/diff/-/diff-4.0.0.tgz" - integrity sha512-jYPKprQVg41+MUMxx6cwtqsNm0Yxx9GDEwdiPLwcUTFx+/qKCEwifKNJ1oGIPBxyEHX2PFCOjkK39lHoj2qiag== - dependencies: - "@lerna/child-process" "4.0.0" - "@lerna/command" "4.0.0" - "@lerna/validation-error" "4.0.0" - npmlog "^4.1.2" - -"@lerna/exec@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/exec/-/exec-4.0.0.tgz" - integrity sha512-VGXtL/b/JfY84NB98VWZpIExfhLOzy0ozm/0XaS4a2SmkAJc5CeUfrhvHxxkxiTBLkU+iVQUyYEoAT0ulQ8PCw== - dependencies: - "@lerna/child-process" "4.0.0" - "@lerna/command" "4.0.0" - "@lerna/filter-options" "4.0.0" - "@lerna/profiler" "4.0.0" - "@lerna/run-topologically" "4.0.0" - "@lerna/validation-error" "4.0.0" - p-map "^4.0.0" - -"@lerna/filter-options@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/filter-options/-/filter-options-4.0.0.tgz" - integrity sha512-vV2ANOeZhOqM0rzXnYcFFCJ/kBWy/3OA58irXih9AMTAlQLymWAK0akWybl++sUJ4HB9Hx12TOqaXbYS2NM5uw== - dependencies: - "@lerna/collect-updates" "4.0.0" - "@lerna/filter-packages" "4.0.0" - dedent "^0.7.0" - npmlog "^4.1.2" - -"@lerna/filter-packages@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/filter-packages/-/filter-packages-4.0.0.tgz" - integrity sha512-+4AJIkK7iIiOaqCiVTYJxh/I9qikk4XjNQLhE3kixaqgMuHl1NQ99qXRR0OZqAWB9mh8Z1HA9bM5K1HZLBTOqA== - dependencies: - "@lerna/validation-error" "4.0.0" - multimatch "^5.0.0" - npmlog "^4.1.2" - -"@lerna/get-npm-exec-opts@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-4.0.0.tgz" - integrity sha512-yvmkerU31CTWS2c7DvmAWmZVeclPBqI7gPVr5VATUKNWJ/zmVcU4PqbYoLu92I9Qc4gY1TuUplMNdNuZTSL7IQ== - dependencies: - npmlog "^4.1.2" - -"@lerna/get-packed@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/get-packed/-/get-packed-4.0.0.tgz" - integrity sha512-rfWONRsEIGyPJTxFzC8ECb3ZbsDXJbfqWYyeeQQDrJRPnEJErlltRLPLgC2QWbxFgFPsoDLeQmFHJnf0iDfd8w== - dependencies: - fs-extra "^9.1.0" - ssri "^8.0.1" - tar "^6.1.0" - -"@lerna/github-client@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/github-client/-/github-client-4.0.0.tgz" - integrity sha512-2jhsldZtTKXYUBnOm23Lb0Fx8G4qfSXF9y7UpyUgWUj+YZYd+cFxSuorwQIgk5P4XXrtVhsUesIsli+BYSThiw== - dependencies: - "@lerna/child-process" "4.0.0" - "@octokit/plugin-enterprise-rest" "^6.0.1" - "@octokit/rest" "^18.1.0" - git-url-parse "^11.4.4" - npmlog "^4.1.2" - -"@lerna/gitlab-client@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/gitlab-client/-/gitlab-client-4.0.0.tgz" - integrity sha512-OMUpGSkeDWFf7BxGHlkbb35T7YHqVFCwBPSIR6wRsszY8PAzCYahtH3IaJzEJyUg6vmZsNl0FSr3pdA2skhxqA== - dependencies: - node-fetch "^2.6.1" - npmlog "^4.1.2" - whatwg-url "^8.4.0" - -"@lerna/global-options@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/global-options/-/global-options-4.0.0.tgz" - integrity sha512-TRMR8afAHxuYBHK7F++Ogop2a82xQjoGna1dvPOY6ltj/pEx59pdgcJfYcynYqMkFIk8bhLJJN9/ndIfX29FTQ== - -"@lerna/has-npm-version@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/has-npm-version/-/has-npm-version-4.0.0.tgz" - integrity sha512-LQ3U6XFH8ZmLCsvsgq1zNDqka0Xzjq5ibVN+igAI5ccRWNaUsE/OcmsyMr50xAtNQMYMzmpw5GVLAivT2/YzCg== - dependencies: - "@lerna/child-process" "4.0.0" - semver "^7.3.4" - -"@lerna/import@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/import/-/import-4.0.0.tgz" - integrity sha512-FaIhd+4aiBousKNqC7TX1Uhe97eNKf5/SC7c5WZANVWtC7aBWdmswwDt3usrzCNpj6/Wwr9EtEbYROzxKH8ffg== - dependencies: - "@lerna/child-process" "4.0.0" - "@lerna/command" "4.0.0" - "@lerna/prompt" "4.0.0" - "@lerna/pulse-till-done" "4.0.0" - "@lerna/validation-error" "4.0.0" - dedent "^0.7.0" - fs-extra "^9.1.0" - p-map-series "^2.1.0" - -"@lerna/info@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/info/-/info-4.0.0.tgz" - integrity sha512-8Uboa12kaCSZEn4XRfPz5KU9XXoexSPS4oeYGj76s2UQb1O1GdnEyfjyNWoUl1KlJ2i/8nxUskpXIftoFYH0/Q== - dependencies: - "@lerna/command" "4.0.0" - "@lerna/output" "4.0.0" - envinfo "^7.7.4" - -"@lerna/init@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/init/-/init-4.0.0.tgz" - integrity sha512-wY6kygop0BCXupzWj5eLvTUqdR7vIAm0OgyV9WHpMYQGfs1V22jhztt8mtjCloD/O0nEe4tJhdG62XU5aYmPNQ== - dependencies: - "@lerna/child-process" "4.0.0" - "@lerna/command" "4.0.0" - fs-extra "^9.1.0" - p-map "^4.0.0" - write-json-file "^4.3.0" - -"@lerna/link@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/link/-/link-4.0.0.tgz" - integrity sha512-KlvPi7XTAcVOByfaLlOeYOfkkDcd+bejpHMCd1KcArcFTwijOwXOVi24DYomIeHvy6HsX/IUquJ4PPUJIeB4+w== - dependencies: - "@lerna/command" "4.0.0" - "@lerna/package-graph" "4.0.0" - "@lerna/symlink-dependencies" "4.0.0" - p-map "^4.0.0" - slash "^3.0.0" - -"@lerna/list@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/list/-/list-4.0.0.tgz" - integrity sha512-L2B5m3P+U4Bif5PultR4TI+KtW+SArwq1i75QZ78mRYxPc0U/piau1DbLOmwrdqr99wzM49t0Dlvl6twd7GHFg== - dependencies: - "@lerna/command" "4.0.0" - "@lerna/filter-options" "4.0.0" - "@lerna/listable" "4.0.0" - "@lerna/output" "4.0.0" - -"@lerna/listable@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/listable/-/listable-4.0.0.tgz" - integrity sha512-/rPOSDKsOHs5/PBLINZOkRIX1joOXUXEtyUs5DHLM8q6/RP668x/1lFhw6Dx7/U+L0+tbkpGtZ1Yt0LewCLgeQ== - dependencies: - "@lerna/query-graph" "4.0.0" - chalk "^4.1.0" - columnify "^1.5.4" - -"@lerna/log-packed@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/log-packed/-/log-packed-4.0.0.tgz" - integrity sha512-+dpCiWbdzgMAtpajLToy9PO713IHoE6GV/aizXycAyA07QlqnkpaBNZ8DW84gHdM1j79TWockGJo9PybVhrrZQ== - dependencies: - byte-size "^7.0.0" - columnify "^1.5.4" - has-unicode "^2.0.1" - npmlog "^4.1.2" - -"@lerna/npm-conf@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/npm-conf/-/npm-conf-4.0.0.tgz" - integrity sha512-uS7H02yQNq3oejgjxAxqq/jhwGEE0W0ntr8vM3EfpCW1F/wZruwQw+7bleJQ9vUBjmdXST//tk8mXzr5+JXCfw== - dependencies: - config-chain "^1.1.12" - pify "^5.0.0" - -"@lerna/npm-dist-tag@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/npm-dist-tag/-/npm-dist-tag-4.0.0.tgz" - integrity sha512-F20sg28FMYTgXqEQihgoqSfwmq+Id3zT23CnOwD+XQMPSy9IzyLf1fFVH319vXIw6NF6Pgs4JZN2Qty6/CQXGw== - dependencies: - "@lerna/otplease" "4.0.0" - npm-package-arg "^8.1.0" - npm-registry-fetch "^9.0.0" - npmlog "^4.1.2" - -"@lerna/npm-install@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/npm-install/-/npm-install-4.0.0.tgz" - integrity sha512-aKNxq2j3bCH3eXl3Fmu4D54s/YLL9WSwV8W7X2O25r98wzrO38AUN6AB9EtmAx+LV/SP15et7Yueg9vSaanRWg== - dependencies: - "@lerna/child-process" "4.0.0" - "@lerna/get-npm-exec-opts" "4.0.0" - fs-extra "^9.1.0" - npm-package-arg "^8.1.0" - npmlog "^4.1.2" - signal-exit "^3.0.3" - write-pkg "^4.0.0" - -"@lerna/npm-publish@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/npm-publish/-/npm-publish-4.0.0.tgz" - integrity sha512-vQb7yAPRo5G5r77DRjHITc9piR9gvEKWrmfCH7wkfBnGWEqu7n8/4bFQ7lhnkujvc8RXOsYpvbMQkNfkYibD/w== - dependencies: - "@lerna/otplease" "4.0.0" - "@lerna/run-lifecycle" "4.0.0" - fs-extra "^9.1.0" - libnpmpublish "^4.0.0" - npm-package-arg "^8.1.0" - npmlog "^4.1.2" - pify "^5.0.0" - read-package-json "^3.0.0" - -"@lerna/npm-run-script@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/npm-run-script/-/npm-run-script-4.0.0.tgz" - integrity sha512-Jmyh9/IwXJjOXqKfIgtxi0bxi1pUeKe5bD3S81tkcy+kyng/GNj9WSqD5ZggoNP2NP//s4CLDAtUYLdP7CU9rA== - dependencies: - "@lerna/child-process" "4.0.0" - "@lerna/get-npm-exec-opts" "4.0.0" - npmlog "^4.1.2" - -"@lerna/otplease@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/otplease/-/otplease-4.0.0.tgz" - integrity sha512-Sgzbqdk1GH4psNiT6hk+BhjOfIr/5KhGBk86CEfHNJTk9BK4aZYyJD4lpDbDdMjIV4g03G7pYoqHzH765T4fxw== - dependencies: - "@lerna/prompt" "4.0.0" - -"@lerna/output@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/output/-/output-4.0.0.tgz" - integrity sha512-Un1sHtO1AD7buDQrpnaYTi2EG6sLF+KOPEAMxeUYG5qG3khTs2Zgzq5WE3dt2N/bKh7naESt20JjIW6tBELP0w== - dependencies: - npmlog "^4.1.2" - -"@lerna/pack-directory@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/pack-directory/-/pack-directory-4.0.0.tgz" - integrity sha512-NJrmZNmBHS+5aM+T8N6FVbaKFScVqKlQFJNY2k7nsJ/uklNKsLLl6VhTQBPwMTbf6Tf7l6bcKzpy7aePuq9UiQ== - dependencies: - "@lerna/get-packed" "4.0.0" - "@lerna/package" "4.0.0" - "@lerna/run-lifecycle" "4.0.0" - npm-packlist "^2.1.4" - npmlog "^4.1.2" - tar "^6.1.0" - temp-write "^4.0.0" - -"@lerna/package-graph@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/package-graph/-/package-graph-4.0.0.tgz" - integrity sha512-QED2ZCTkfXMKFoTGoccwUzjHtZMSf3UKX14A4/kYyBms9xfFsesCZ6SLI5YeySEgcul8iuIWfQFZqRw+Qrjraw== - dependencies: - "@lerna/prerelease-id-from-version" "4.0.0" - "@lerna/validation-error" "4.0.0" - npm-package-arg "^8.1.0" - npmlog "^4.1.2" - semver "^7.3.4" - -"@lerna/package@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/package/-/package-4.0.0.tgz" - integrity sha512-l0M/izok6FlyyitxiQKr+gZLVFnvxRQdNhzmQ6nRnN9dvBJWn+IxxpM+cLqGACatTnyo9LDzNTOj2Db3+s0s8Q== - dependencies: - load-json-file "^6.2.0" - npm-package-arg "^8.1.0" - write-pkg "^4.0.0" - -"@lerna/prerelease-id-from-version@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/prerelease-id-from-version/-/prerelease-id-from-version-4.0.0.tgz" - integrity sha512-GQqguzETdsYRxOSmdFZ6zDBXDErIETWOqomLERRY54f4p+tk4aJjoVdd9xKwehC9TBfIFvlRbL1V9uQGHh1opg== - dependencies: - semver "^7.3.4" - -"@lerna/profiler@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/profiler/-/profiler-4.0.0.tgz" - integrity sha512-/BaEbqnVh1LgW/+qz8wCuI+obzi5/vRE8nlhjPzdEzdmWmZXuCKyWSEzAyHOJWw1ntwMiww5dZHhFQABuoFz9Q== - dependencies: - fs-extra "^9.1.0" - npmlog "^4.1.2" - upath "^2.0.1" - -"@lerna/project@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/project/-/project-4.0.0.tgz" - integrity sha512-o0MlVbDkD5qRPkFKlBZsXZjoNTWPyuL58564nSfZJ6JYNmgAptnWPB2dQlAc7HWRZkmnC2fCkEdoU+jioPavbg== - dependencies: - "@lerna/package" "4.0.0" - "@lerna/validation-error" "4.0.0" - cosmiconfig "^7.0.0" - dedent "^0.7.0" - dot-prop "^6.0.1" - glob-parent "^5.1.1" - globby "^11.0.2" - load-json-file "^6.2.0" - npmlog "^4.1.2" - p-map "^4.0.0" - resolve-from "^5.0.0" - write-json-file "^4.3.0" - -"@lerna/prompt@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/prompt/-/prompt-4.0.0.tgz" - integrity sha512-4Ig46oCH1TH5M7YyTt53fT6TuaKMgqUUaqdgxvp6HP6jtdak6+amcsqB8YGz2eQnw/sdxunx84DfI9XpoLj4bQ== - dependencies: - inquirer "^7.3.3" - npmlog "^4.1.2" - -"@lerna/publish@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/publish/-/publish-4.0.0.tgz" - integrity sha512-K8jpqjHrChH22qtkytA5GRKIVFEtqBF6JWj1I8dWZtHs4Jywn8yB1jQ3BAMLhqmDJjWJtRck0KXhQQKzDK2UPg== - dependencies: - "@lerna/check-working-tree" "4.0.0" - "@lerna/child-process" "4.0.0" - "@lerna/collect-updates" "4.0.0" - "@lerna/command" "4.0.0" - "@lerna/describe-ref" "4.0.0" - "@lerna/log-packed" "4.0.0" - "@lerna/npm-conf" "4.0.0" - "@lerna/npm-dist-tag" "4.0.0" - "@lerna/npm-publish" "4.0.0" - "@lerna/otplease" "4.0.0" - "@lerna/output" "4.0.0" - "@lerna/pack-directory" "4.0.0" - "@lerna/prerelease-id-from-version" "4.0.0" - "@lerna/prompt" "4.0.0" - "@lerna/pulse-till-done" "4.0.0" - "@lerna/run-lifecycle" "4.0.0" - "@lerna/run-topologically" "4.0.0" - "@lerna/validation-error" "4.0.0" - "@lerna/version" "4.0.0" - fs-extra "^9.1.0" - libnpmaccess "^4.0.1" - npm-package-arg "^8.1.0" - npm-registry-fetch "^9.0.0" - npmlog "^4.1.2" - p-map "^4.0.0" - p-pipe "^3.1.0" - pacote "^11.2.6" - semver "^7.3.4" - -"@lerna/pulse-till-done@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/pulse-till-done/-/pulse-till-done-4.0.0.tgz" - integrity sha512-Frb4F7QGckaybRhbF7aosLsJ5e9WuH7h0KUkjlzSByVycxY91UZgaEIVjS2oN9wQLrheLMHl6SiFY0/Pvo0Cxg== - dependencies: - npmlog "^4.1.2" - -"@lerna/query-graph@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/query-graph/-/query-graph-4.0.0.tgz" - integrity sha512-YlP6yI3tM4WbBmL9GCmNDoeQyzcyg1e4W96y/PKMZa5GbyUvkS2+Jc2kwPD+5KcXou3wQZxSPzR3Te5OenaDdg== - dependencies: - "@lerna/package-graph" "4.0.0" - -"@lerna/resolve-symlink@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/resolve-symlink/-/resolve-symlink-4.0.0.tgz" - integrity sha512-RtX8VEUzqT+uLSCohx8zgmjc6zjyRlh6i/helxtZTMmc4+6O4FS9q5LJas2uGO2wKvBlhcD6siibGt7dIC3xZA== - dependencies: - fs-extra "^9.1.0" - npmlog "^4.1.2" - read-cmd-shim "^2.0.0" - -"@lerna/rimraf-dir@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/rimraf-dir/-/rimraf-dir-4.0.0.tgz" - integrity sha512-QNH9ABWk9mcMJh2/muD9iYWBk1oQd40y6oH+f3wwmVGKYU5YJD//+zMiBI13jxZRtwBx0vmBZzkBkK1dR11cBg== - dependencies: - "@lerna/child-process" "4.0.0" - npmlog "^4.1.2" - path-exists "^4.0.0" - rimraf "^3.0.2" - -"@lerna/run-lifecycle@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/run-lifecycle/-/run-lifecycle-4.0.0.tgz" - integrity sha512-IwxxsajjCQQEJAeAaxF8QdEixfI7eLKNm4GHhXHrgBu185JcwScFZrj9Bs+PFKxwb+gNLR4iI5rpUdY8Y0UdGQ== - dependencies: - "@lerna/npm-conf" "4.0.0" - npm-lifecycle "^3.1.5" - npmlog "^4.1.2" - -"@lerna/run-topologically@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/run-topologically/-/run-topologically-4.0.0.tgz" - integrity sha512-EVZw9hGwo+5yp+VL94+NXRYisqgAlj0jWKWtAIynDCpghRxCE5GMO3xrQLmQgqkpUl9ZxQFpICgYv5DW4DksQA== - dependencies: - "@lerna/query-graph" "4.0.0" - p-queue "^6.6.2" - -"@lerna/run@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/run/-/run-4.0.0.tgz" - integrity sha512-9giulCOzlMPzcZS/6Eov6pxE9gNTyaXk0Man+iCIdGJNMrCnW7Dme0Z229WWP/UoxDKg71F2tMsVVGDiRd8fFQ== - dependencies: - "@lerna/command" "4.0.0" - "@lerna/filter-options" "4.0.0" - "@lerna/npm-run-script" "4.0.0" - "@lerna/output" "4.0.0" - "@lerna/profiler" "4.0.0" - "@lerna/run-topologically" "4.0.0" - "@lerna/timer" "4.0.0" - "@lerna/validation-error" "4.0.0" - p-map "^4.0.0" - -"@lerna/symlink-binary@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/symlink-binary/-/symlink-binary-4.0.0.tgz" - integrity sha512-zualodWC4q1QQc1pkz969hcFeWXOsVYZC5AWVtAPTDfLl+TwM7eG/O6oP+Rr3fFowspxo6b1TQ6sYfDV6HXNWA== - dependencies: - "@lerna/create-symlink" "4.0.0" - "@lerna/package" "4.0.0" - fs-extra "^9.1.0" - p-map "^4.0.0" - -"@lerna/symlink-dependencies@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/symlink-dependencies/-/symlink-dependencies-4.0.0.tgz" - integrity sha512-BABo0MjeUHNAe2FNGty1eantWp8u83BHSeIMPDxNq0MuW2K3CiQRaeWT3EGPAzXpGt0+hVzBrA6+OT0GPn7Yuw== - dependencies: - "@lerna/create-symlink" "4.0.0" - "@lerna/resolve-symlink" "4.0.0" - "@lerna/symlink-binary" "4.0.0" - fs-extra "^9.1.0" - p-map "^4.0.0" - p-map-series "^2.1.0" - -"@lerna/timer@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/timer/-/timer-4.0.0.tgz" - integrity sha512-WFsnlaE7SdOvjuyd05oKt8Leg3ENHICnvX3uYKKdByA+S3g+TCz38JsNs7OUZVt+ba63nC2nbXDlUnuT2Xbsfg== - -"@lerna/validation-error@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/validation-error/-/validation-error-4.0.0.tgz" - integrity sha512-1rBOM5/koiVWlRi3V6dB863E1YzJS8v41UtsHgMr6gB2ncJ2LsQtMKlJpi3voqcgh41H8UsPXR58RrrpPpufyw== - dependencies: - npmlog "^4.1.2" - -"@lerna/version@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/version/-/version-4.0.0.tgz" - integrity sha512-otUgiqs5W9zGWJZSCCMRV/2Zm2A9q9JwSDS7s/tlKq4mWCYriWo7+wsHEA/nPTMDyYyBO5oyZDj+3X50KDUzeA== - dependencies: - "@lerna/check-working-tree" "4.0.0" - "@lerna/child-process" "4.0.0" - "@lerna/collect-updates" "4.0.0" - "@lerna/command" "4.0.0" - "@lerna/conventional-commits" "4.0.0" - "@lerna/github-client" "4.0.0" - "@lerna/gitlab-client" "4.0.0" - "@lerna/output" "4.0.0" - "@lerna/prerelease-id-from-version" "4.0.0" - "@lerna/prompt" "4.0.0" - "@lerna/run-lifecycle" "4.0.0" - "@lerna/run-topologically" "4.0.0" - "@lerna/validation-error" "4.0.0" - chalk "^4.1.0" - dedent "^0.7.0" - load-json-file "^6.2.0" - minimatch "^3.0.4" - npmlog "^4.1.2" - p-map "^4.0.0" - p-pipe "^3.1.0" - p-reduce "^2.1.0" - p-waterfall "^2.1.1" - semver "^7.3.4" - slash "^3.0.0" - temp-write "^4.0.0" - write-json-file "^4.3.0" - -"@lerna/write-log-file@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@lerna/write-log-file/-/write-log-file-4.0.0.tgz" - integrity sha512-XRG5BloiArpXRakcnPHmEHJp+4AtnhRtpDIHSghmXD5EichI1uD73J7FgPp30mm2pDRq3FdqB0NbwSEsJ9xFQg== - dependencies: - npmlog "^4.1.2" - write-file-atomic "^3.0.3" - -"@noble/curves@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" - integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA== - dependencies: - "@noble/hashes" "1.3.1" - -"@noble/hashes@1.3.1", "@noble/hashes@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" - integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== - -"@noble/secp256k1@^1.7.0": - version "1.7.0" - resolved "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.0.tgz" - integrity sha512-kbacwGSsH/CTout0ZnZWxnW1B+jH/7r/WAAKLBtrRJ/+CUH7lgmQzl3GTrQua3SGKWNSDsS6lmjnDpIJ5Dxyaw== - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@npmcli/ci-detect@^1.0.0": - version "1.4.0" - resolved "https://registry.npmjs.org/@npmcli/ci-detect/-/ci-detect-1.4.0.tgz" - integrity sha512-3BGrt6FLjqM6br5AhWRKTr3u5GIVkjRYeAFrMp3HjnfICrg4xOrVRwFavKT6tsp++bq5dluL5t8ME/Nha/6c1Q== - -"@npmcli/fs@^1.0.0": - version "1.1.1" - resolved "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz" - integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== - dependencies: - "@gar/promisify" "^1.0.1" - semver "^7.3.5" - -"@npmcli/fs@^2.1.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865" - integrity sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ== - dependencies: - "@gar/promisify" "^1.1.3" - semver "^7.3.5" - -"@npmcli/git@^2.1.0": - version "2.1.0" - resolved "https://registry.npmjs.org/@npmcli/git/-/git-2.1.0.tgz" - integrity sha512-/hBFX/QG1b+N7PZBFs0bi+evgRZcK9nWBxQKZkGoXUT5hJSwl5c4d7y8/hm+NQZRPhQ67RzFaj5UM9YeyKoryw== - dependencies: - "@npmcli/promise-spawn" "^1.3.2" - lru-cache "^6.0.0" - mkdirp "^1.0.4" - npm-pick-manifest "^6.1.1" - promise-inflight "^1.0.1" - promise-retry "^2.0.1" - semver "^7.3.5" - which "^2.0.2" - -"@npmcli/installed-package-contents@^1.0.6": - version "1.0.7" - resolved "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz" - integrity sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw== - dependencies: - npm-bundled "^1.1.1" - npm-normalize-package-bin "^1.0.1" - -"@npmcli/move-file@^1.0.1": - version "1.1.2" - resolved "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz" - integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== - dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" - -"@npmcli/move-file@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4" - integrity sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ== - dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" - -"@npmcli/node-gyp@^1.0.2": - version "1.0.3" - resolved "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-1.0.3.tgz" - integrity sha512-fnkhw+fmX65kiLqk6E3BFLXNC26rUhK90zVwe2yncPliVT/Qos3xjhTLE59Df8KnPlcwIERXKVlU1bXoUQ+liA== - -"@npmcli/package-json@^3.0.0": - version "3.0.0" - resolved "https://registry.npmjs.org/@npmcli/package-json/-/package-json-3.0.0.tgz" - integrity sha512-NnuPuM97xfiCpbTEJYtEuKz6CFbpUHtaT0+5via5pQeI25omvQDFbp1GcGJ/c4zvL/WX0qbde6YiLgfZbWFgvg== - dependencies: - json-parse-even-better-errors "^3.0.0" - -"@npmcli/promise-spawn@^1.2.0", "@npmcli/promise-spawn@^1.3.2": - version "1.3.2" - resolved "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz" - integrity sha512-QyAGYo/Fbj4MXeGdJcFzZ+FkDkomfRBrPM+9QYJSg+PxgAUL+LU3FneQk37rKR2/zjqkCV1BLHccX98wRXG3Sg== - dependencies: - infer-owner "^1.0.4" - -"@npmcli/run-script@^1.8.2": - version "1.8.6" - resolved "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.8.6.tgz" - integrity sha512-e42bVZnC6VluBZBAFEr3YrdqSspG3bgilyg4nSLBJ7TRGNCzxHa92XAHxQBLYg0BmgwO4b2mf3h/l5EkEWRn3g== - dependencies: - "@npmcli/node-gyp" "^1.0.2" - "@npmcli/promise-spawn" "^1.3.2" - node-gyp "^7.1.0" - read-package-json-fast "^2.0.1" - -"@octokit/auth-token@^2.4.4": - version "2.5.0" - resolved "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz" - integrity sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g== - dependencies: - "@octokit/types" "^6.0.3" - -"@octokit/core@^3.5.1": - version "3.6.0" - resolved "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz" - integrity sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q== - dependencies: - "@octokit/auth-token" "^2.4.4" - "@octokit/graphql" "^4.5.8" - "@octokit/request" "^5.6.3" - "@octokit/request-error" "^2.0.5" - "@octokit/types" "^6.0.3" - before-after-hook "^2.2.0" - universal-user-agent "^6.0.0" - -"@octokit/endpoint@^6.0.1": - version "6.0.12" - resolved "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz" - integrity sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA== - dependencies: - "@octokit/types" "^6.0.3" - is-plain-object "^5.0.0" - universal-user-agent "^6.0.0" - -"@octokit/graphql@^4.5.8": - version "4.8.0" - resolved "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz" - integrity sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg== - dependencies: - "@octokit/request" "^5.6.0" - "@octokit/types" "^6.0.3" - universal-user-agent "^6.0.0" - -"@octokit/openapi-types@^12.11.0": - version "12.11.0" - resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz" - integrity sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ== - -"@octokit/plugin-enterprise-rest@^6.0.1": - version "6.0.1" - resolved "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz" - integrity sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw== - -"@octokit/plugin-paginate-rest@^2.16.8": - version "2.21.3" - resolved "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz" - integrity sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw== - dependencies: - "@octokit/types" "^6.40.0" - -"@octokit/plugin-request-log@^1.0.4": - version "1.0.4" - resolved "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz" - integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== - -"@octokit/plugin-rest-endpoint-methods@^5.12.0": - version "5.16.2" - resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz" - integrity sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw== - dependencies: - "@octokit/types" "^6.39.0" - deprecation "^2.3.1" - -"@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": - version "2.1.0" - resolved "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz" - integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg== - dependencies: - "@octokit/types" "^6.0.3" - deprecation "^2.0.0" - once "^1.4.0" - -"@octokit/request@^5.6.0", "@octokit/request@^5.6.3": - version "5.6.3" - resolved "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz" - integrity sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A== - dependencies: - "@octokit/endpoint" "^6.0.1" - "@octokit/request-error" "^2.1.0" - "@octokit/types" "^6.16.1" - is-plain-object "^5.0.0" - node-fetch "^2.6.7" - universal-user-agent "^6.0.0" - -"@octokit/rest@^18.1.0": - version "18.12.0" - resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz" - integrity sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q== - dependencies: - "@octokit/core" "^3.5.1" - "@octokit/plugin-paginate-rest" "^2.16.8" - "@octokit/plugin-request-log" "^1.0.4" - "@octokit/plugin-rest-endpoint-methods" "^5.12.0" - -"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.39.0", "@octokit/types@^6.40.0": - version "6.41.0" - resolved "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz" - integrity sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg== - dependencies: - "@octokit/openapi-types" "^12.11.0" - -"@sinclair/typebox@^0.24.1": - version "0.24.42" - resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.42.tgz" - integrity sha512-d+2AtrHGyWek2u2ITF0lHRIv6Tt7X0dEHW+0rP+5aDCEjC3fiN2RBjrLD0yU0at52BcZbRGxLbAtXiR0hFCjYw== - -"@sinonjs/commons@^1.7.0": - version "1.8.3" - resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz" - integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== - dependencies: - type-detect "4.0.8" - -"@sinonjs/fake-timers@^9.1.2": - version "9.1.2" - resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz" - integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== - dependencies: - "@sinonjs/commons" "^1.7.0" - -"@swc/core-darwin-arm64@1.3.42": - version "1.3.42" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.42.tgz#fabb645b288199b730d846e3eda370b77f5ebe9f" - integrity sha512-hM6RrZFyoCM9mX3cj/zM5oXwhAqjUdOCLXJx7KTQps7NIkv/Qjvobgvyf2gAb89j3ARNo9NdIoLjTjJ6oALtiA== - -"@swc/core-darwin-x64@1.3.42": - version "1.3.42" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.42.tgz#dcd434ec8dda6f2178a10da0def036a071a6e008" - integrity sha512-bjsWtHMb6wJK1+RGlBs2USvgZ0txlMk11y0qBLKo32gLKTqzUwRw0Fmfzuf6Ue2a/w//7eqMlPFEre4LvJajGw== - -"@swc/core-linux-arm-gnueabihf@1.3.42": - version "1.3.42" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.42.tgz#59c57b15113d316e8a4a6d690a6c09429483d201" - integrity sha512-Oe0ggMz3MyqXNfeVmY+bBTL0hFSNY3bx8dhcqsh4vXk/ZVGse94QoC4dd92LuPHmKT0x6nsUzB86x2jU9QHW5g== - -"@swc/core-linux-arm64-gnu@1.3.42": - version "1.3.42" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.42.tgz#50d026b9f4d7a5f25deacc8c8dd45fc12be70a95" - integrity sha512-ZJsa8NIW1RLmmHGTJCbM7OPSbBZ9rOMrLqDtUOGrT0uoJXZnnQqolflamB5wviW0X6h3Z3/PSTNGNDCJ3u3Lqg== - -"@swc/core-linux-arm64-musl@1.3.42": - version "1.3.42" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.42.tgz#3c0e51b0709dcf06289949803c9a36a46a97827c" - integrity sha512-YpZwlFAfOp5vkm/uVUJX1O7N3yJDO1fDQRWqsOPPNyIJkI2ydlRQtgN6ZylC159Qv+TimfXnGTlNr7o3iBAqjg== - -"@swc/core-linux-x64-gnu@1.3.42": - version "1.3.42" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.42.tgz#059ac0acddebd0360851871929a14dbacf74f865" - integrity sha512-0ccpKnsZbyHBzaQFdP8U9i29nvOfKitm6oJfdJzlqsY/jCqwvD8kv2CAKSK8WhJz//ExI2LqNrDI0yazx5j7+A== - -"@swc/core-linux-x64-musl@1.3.42": - version "1.3.42" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.42.tgz#7a61093d93a3abc2f893b7d31fd6c22c4cab2212" - integrity sha512-7eckRRuTZ6+3K21uyfXXgc2ZCg0mSWRRNwNT3wap2bYkKPeqTgb8pm8xYSZNEiMuDonHEat6XCCV36lFY6kOdQ== - -"@swc/core-win32-arm64-msvc@1.3.42": - version "1.3.42" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.42.tgz#12f92c960ea801aa26ffa5b91d369ac24c2a3cca" - integrity sha512-t27dJkdw0GWANdN4TV0lY/V5vTYSx5SRjyzzZolep358ueCGuN1XFf1R0JcCbd1ojosnkQg2L7A7991UjXingg== - -"@swc/core-win32-ia32-msvc@1.3.42": - version "1.3.42" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.42.tgz#be022aff03838515fa5506be300f0ea15f3fb476" - integrity sha512-xfpc/Zt/aMILX4IX0e3loZaFyrae37u3MJCv1gJxgqrpeLi7efIQr3AmERkTK3mxTO6R5urSliWw2W3FyZ7D3Q== - -"@swc/core-win32-x64-msvc@1.3.42": - version "1.3.42" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.42.tgz#fccac26974f03234e502276389f4330e2696887f" - integrity sha512-ra2K4Tu++EJLPhzZ6L8hWUsk94TdK/2UKhL9dzCBhtzKUixsGCEqhtqH1zISXNvW8qaVLFIMUP37ULe80/IJaA== - -"@swc/core@^1.3.42": - version "1.3.42" - resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.3.42.tgz#7067c4fd9a02536f9ca7b54ed8ebc45e2df810cf" - integrity sha512-nVFUd5+7tGniM2cT3LXaqnu3735Cu4az8A9gAKK+8sdpASI52SWuqfDBmjFCK9xG90MiVDVp2PTZr0BWqCIzpw== - optionalDependencies: - "@swc/core-darwin-arm64" "1.3.42" - "@swc/core-darwin-x64" "1.3.42" - "@swc/core-linux-arm-gnueabihf" "1.3.42" - "@swc/core-linux-arm64-gnu" "1.3.42" - "@swc/core-linux-arm64-musl" "1.3.42" - "@swc/core-linux-x64-gnu" "1.3.42" - "@swc/core-linux-x64-musl" "1.3.42" - "@swc/core-win32-arm64-msvc" "1.3.42" - "@swc/core-win32-ia32-msvc" "1.3.42" - "@swc/core-win32-x64-msvc" "1.3.42" - -"@swc/jest@^0.2.24": - version "0.2.24" - resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.24.tgz#35d9377ede049613cd5fdd6c24af2b8dcf622875" - integrity sha512-fwgxQbM1wXzyKzl1+IW0aGrRvAA8k0Y3NxFhKigbPjOJ4mCKnWEcNX9HQS3gshflcxq8YKhadabGUVfdwjCr6Q== - dependencies: - "@jest/create-cache-key-function" "^27.4.2" - jsonc-parser "^3.2.0" - -"@tokenizer/token@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" - integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== - -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== - -"@tootallnate/once@2": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" - integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== - -"@ts-morph/common@~0.17.0": - version "0.17.0" - resolved "https://registry.npmjs.org/@ts-morph/common/-/common-0.17.0.tgz" - integrity sha512-RMSSvSfs9kb0VzkvQ2NWobwnj7TxCA9vI/IjR9bDHqgAyVbu2T0DN4wiKVqomyDWqO7dPr/tErSfq7urQ1Q37g== - dependencies: - fast-glob "^3.2.11" - minimatch "^5.1.0" - mkdirp "^1.0.4" - path-browserify "^1.0.1" - -"@tsconfig/node10@^1.0.7": - version "1.0.9" - resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz" - integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== - -"@tsconfig/node12@^1.0.7": - version "1.0.11" - resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz" - integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== - -"@tsconfig/node14@^1.0.0": - version "1.0.3" - resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz" - integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== - -"@tsconfig/node16@^1.0.2": - version "1.0.3" - resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz" - integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== - -"@types/babel__core@^7.1.14": - version "7.1.19" - resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz" - integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.6.4" - resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz" - integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.1" - resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz" - integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.18.1" - resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.1.tgz" - integrity sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA== - dependencies: - "@babel/types" "^7.3.0" - -"@types/bn.js@*": - version "5.1.1" - resolved "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz" - integrity sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g== - dependencies: - "@types/node" "*" - -"@types/body-parser@*": - version "1.19.2" - resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz" - integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.35" - resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== - dependencies: - "@types/node" "*" - -"@types/cors@^2.8.12": - version "2.8.12" - resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz" - integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== - -"@types/elliptic@^6.4.9": - version "6.4.14" - resolved "https://registry.npmjs.org/@types/elliptic/-/elliptic-6.4.14.tgz" - integrity sha512-z4OBcDAU0GVwDTuwJzQCiL6188QvZMkvoERgcVjq0/mPM8jCfdwZ3x5zQEVoL9WCAru3aG5wl3Z5Ww5wBWn7ZQ== - dependencies: - "@types/bn.js" "*" - -"@types/express-serve-static-core@^4.17.18": - version "4.17.31" - resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz" - integrity sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - -"@types/express@^4.17.13": - version "4.17.14" - resolved "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz" - integrity sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.18" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/graceful-fs@^4.1.3": - version "4.1.5" - resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz" - integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== - dependencies: - "@types/node" "*" - -"@types/http-errors@^2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz" - integrity sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ== - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.4" - resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" - integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== - -"@types/istanbul-lib-report@*": - version "3.0.0" - resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" - integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.1" - resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz" - integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/jest@^28.1.4": - version "28.1.8" - resolved "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz" - integrity sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw== - dependencies: - expect "^28.0.0" - pretty-format "^28.0.0" - -"@types/json-schema@^7.0.9": - version "7.0.11" - resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz" - integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== - -"@types/jsonwebtoken@^8.5.9": - version "8.5.9" - resolved "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.9.tgz" - integrity sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg== - dependencies: - "@types/node" "*" - -"@types/mime@*": - version "3.0.1" - resolved "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz" - integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== - -"@types/minimatch@^3.0.3": - version "3.0.5" - resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz" - integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== - -"@types/minimist@^1.2.0": - version "1.2.2" - resolved "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz" - integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== - -"@types/node@*", "@types/node@^18.0.0": - version "18.11.11" - resolved "https://registry.npmjs.org/@types/node/-/node-18.11.11.tgz" - integrity sha512-KJ021B1nlQUBLopzZmPBVuGU9un7WJd/W4ya7Ih02B4Uwky5Nja0yGYav2EfYIk0RR2Q9oVhf60S2XR1BCWJ2g== - -"@types/nodemailer@^6.4.6": - version "6.4.6" - resolved "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.6.tgz" - integrity sha512-pD6fL5GQtUKvD2WnPmg5bC2e8kWCAPDwMPmHe/ohQbW+Dy0EcHgZ2oCSuPlWNqk74LS5BVMig1SymQbFMPPK3w== - dependencies: - "@types/node" "*" - -"@types/normalize-package-data@^2.4.0": - version "2.4.1" - resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz" - integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== - -"@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== - -"@types/pg@^8.6.6": - version "8.6.6" - resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.6.6.tgz#21cdf873a3e345a6e78f394677e3b3b1b543cb80" - integrity sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw== - dependencies: - "@types/node" "*" - pg-protocol "*" - pg-types "^2.2.0" - -"@types/prettier@^2.1.5": - version "2.7.0" - resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz" - integrity sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A== - -"@types/qs@*": - version "6.9.7" - resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== - -"@types/range-parser@*": - version "1.2.4" - resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== - -"@types/serve-static@*": - version "1.15.0" - resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz" - integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== - dependencies: - "@types/mime" "*" - "@types/node" "*" - -"@types/sharp@^0.31.0": - version "0.31.0" - resolved "https://registry.npmjs.org/@types/sharp/-/sharp-0.31.0.tgz" - integrity sha512-nwivOU101fYInCwdDcH/0/Ru6yIRXOpORx25ynEOc6/IakuCmjOAGpaO5VfUl4QkDtUC6hj+Z2eCQvgXOioknw== - dependencies: - "@types/node" "*" - -"@types/stack-utils@^2.0.0": - version "2.0.1" - resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" - integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== - -"@types/ws@^8.5.4": - version "8.5.4" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" - integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== - dependencies: - "@types/node" "*" - -"@types/yargs-parser@*": - version "21.0.0" - resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz" - integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== - -"@types/yargs@^16.0.0": - version "16.0.5" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.5.tgz#12cc86393985735a283e387936398c2f9e5f88e3" - integrity sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ== - dependencies: - "@types/yargs-parser" "*" - -"@types/yargs@^17.0.8": - version "17.0.12" - resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz" - integrity sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ== - dependencies: - "@types/yargs-parser" "*" - -"@typescript-eslint/eslint-plugin@^5.38.1": - version "5.38.1" - resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.1.tgz" - integrity sha512-ky7EFzPhqz3XlhS7vPOoMDaQnQMn+9o5ICR9CPr/6bw8HrFkzhMSxuA3gRfiJVvs7geYrSeawGJjZoZQKCOglQ== - dependencies: - "@typescript-eslint/scope-manager" "5.38.1" - "@typescript-eslint/type-utils" "5.38.1" - "@typescript-eslint/utils" "5.38.1" - debug "^4.3.4" - ignore "^5.2.0" - regexpp "^3.2.0" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/parser@^5.38.1": - version "5.38.1" - resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.38.1.tgz" - integrity sha512-LDqxZBVFFQnQRz9rUZJhLmox+Ep5kdUmLatLQnCRR6523YV+XhRjfYzStQ4MheFA8kMAfUlclHSbu+RKdRwQKw== - dependencies: - "@typescript-eslint/scope-manager" "5.38.1" - "@typescript-eslint/types" "5.38.1" - "@typescript-eslint/typescript-estree" "5.38.1" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@5.38.1": - version "5.38.1" - resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.38.1.tgz" - integrity sha512-BfRDq5RidVU3RbqApKmS7RFMtkyWMM50qWnDAkKgQiezRtLKsoyRKIvz1Ok5ilRWeD9IuHvaidaLxvGx/2eqTQ== - dependencies: - "@typescript-eslint/types" "5.38.1" - "@typescript-eslint/visitor-keys" "5.38.1" - -"@typescript-eslint/type-utils@5.38.1": - version "5.38.1" - resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.38.1.tgz" - integrity sha512-UU3j43TM66gYtzo15ivK2ZFoDFKKP0k03MItzLdq0zV92CeGCXRfXlfQX5ILdd4/DSpHkSjIgLLLh1NtkOJOAw== - dependencies: - "@typescript-eslint/typescript-estree" "5.38.1" - "@typescript-eslint/utils" "5.38.1" - debug "^4.3.4" - tsutils "^3.21.0" - -"@typescript-eslint/types@5.38.1": - version "5.38.1" - resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.38.1.tgz" - integrity sha512-QTW1iHq1Tffp9lNfbfPm4WJabbvpyaehQ0SrvVK2yfV79SytD9XDVxqiPvdrv2LK7DGSFo91TB2FgWanbJAZXg== - -"@typescript-eslint/typescript-estree@5.38.1": - version "5.38.1" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.38.1.tgz" - integrity sha512-99b5e/Enoe8fKMLdSuwrfH/C0EIbpUWmeEKHmQlGZb8msY33qn1KlkFww0z26o5Omx7EVjzVDCWEfrfCDHfE7g== - dependencies: - "@typescript-eslint/types" "5.38.1" - "@typescript-eslint/visitor-keys" "5.38.1" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/utils@5.38.1": - version "5.38.1" - resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.38.1.tgz" - integrity sha512-oIuUiVxPBsndrN81oP8tXnFa/+EcZ03qLqPDfSZ5xIJVm7A9V0rlkQwwBOAGtrdN70ZKDlKv+l1BeT4eSFxwXA== - dependencies: - "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.38.1" - "@typescript-eslint/types" "5.38.1" - "@typescript-eslint/typescript-estree" "5.38.1" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" - -"@typescript-eslint/visitor-keys@5.38.1": - version "5.38.1" - resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.38.1.tgz" - integrity sha512-bSHr1rRxXt54+j2n4k54p4fj8AHJ49VDWtjpImOpzQj4qjAiOpPni+V1Tyajh19Api1i844F757cur8wH3YvOA== - dependencies: - "@typescript-eslint/types" "5.38.1" - eslint-visitor-keys "^3.3.0" - -JSONStream@^1.0.4: - version "1.3.5" - resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" - integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - -abbrev@1, abbrev@^1.0.0: - version "1.1.1" - resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - -accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn-walk@^8.1.1: - version "8.2.0" - resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== - -acorn@^8.4.1, acorn@^8.8.0: - version "8.8.0" - resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz" - integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== - -add-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz" - integrity sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ== - -agent-base@6, agent-base@^6.0.2: - version "6.0.2" - resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== - dependencies: - debug "4" - -agentkeepalive@^4.1.3: - version "4.2.1" - resolved "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz" - integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA== - dependencies: - debug "^4.1.0" - depd "^1.1.2" - humanize-ms "^1.2.1" - -agentkeepalive@^4.2.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.3.0.tgz#bb999ff07412653c1803b3ced35e50729830a255" - integrity sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg== - dependencies: - debug "^4.1.0" - depd "^2.0.0" - humanize-ms "^1.2.1" - -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ajv@^6.10.0, ajv@^6.11.0, ajv@^6.12.3, ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ansi-escapes@^4.2.1: - version "4.3.2" - resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" - integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^5.0.0: - version "5.2.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" - integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== - -anymatch@^3.0.3: - version "3.1.2" - resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - -"aproba@^1.0.3 || ^2.0.0", aproba@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz" - integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== - -are-we-there-yet@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" - integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== - dependencies: - delegates "^1.0.0" - readable-stream "^3.6.0" - -are-we-there-yet@~1.1.2: - version "1.1.7" - resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz" - integrity sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-differ@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz" - integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - -array-ify@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz" - integrity sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -array.prototype.reduce@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz" - integrity sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.2" - es-array-method-boxes-properly "^1.0.0" - is-string "^1.0.7" - -arrify@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz" - integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== - -arrify@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz" - integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== - -asap@^2.0.0: - version "2.0.6" - resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz" - integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== - -asn1.js@^5.0.1: - version "5.4.1" - resolved "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz" - integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - safer-buffer "^2.1.0" - -asn1@~0.2.3: - version "0.2.6" - resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz" - integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== - -atomic-sleep@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz" - integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz" - integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== - -aws4@^1.8.0: - version "1.11.0" - resolved "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz" - integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== - -axios@^0.27.2: - version "0.27.2" - resolved "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz" - integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== - dependencies: - follow-redirects "^1.14.9" - form-data "^4.0.0" - -axios@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.4.tgz#f5760cefd9cfb51fd2481acf88c05f67c4523024" - integrity sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ== - dependencies: - follow-redirects "^1.15.0" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - -babel-eslint@^10.1.0: - version "10.1.0" - resolved "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz" - integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.7.0" - "@babel/traverse" "^7.7.0" - "@babel/types" "^7.7.0" - eslint-visitor-keys "^1.0.0" - resolve "^1.12.0" - -babel-jest@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz" - integrity sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q== - dependencies: - "@jest/transform" "^28.1.3" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^28.1.3" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - -babel-plugin-dynamic-import-node@^2.3.3: - version "2.3.3" - resolved "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz" - integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== - dependencies: - object.assign "^4.1.0" - -babel-plugin-istanbul@^6.1.1: - version "6.1.1" - resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^5.0.4" - test-exclude "^6.0.0" - -babel-plugin-jest-hoist@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz" - integrity sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.1.14" - "@types/babel__traverse" "^7.0.6" - -babel-plugin-polyfill-corejs2@^0.3.3: - version "0.3.3" - resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz" - integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q== - dependencies: - "@babel/compat-data" "^7.17.7" - "@babel/helper-define-polyfill-provider" "^0.3.3" - semver "^6.1.1" - -babel-plugin-polyfill-corejs3@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz" - integrity sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.3" - core-js-compat "^3.25.1" - -babel-plugin-polyfill-regenerator@^0.4.1: - version "0.4.1" - resolved "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz" - integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.3" - -babel-preset-current-node-syntax@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz" - integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.8.3" - "@babel/plugin-syntax-import-meta" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-top-level-await" "^7.8.3" - -babel-preset-jest@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz" - integrity sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A== - dependencies: - babel-plugin-jest-hoist "^28.1.3" - babel-preset-current-node-syntax "^1.0.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.0.2, base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz" - integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== - dependencies: - tweetnacl "^0.14.3" - -before-after-hook@^2.2.0: - version "2.2.2" - resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz" - integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== - -better-sqlite3@^7.6.2: - version "7.6.2" - resolved "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-7.6.2.tgz" - integrity sha512-S5zIU1Hink2AH4xPsN0W43T1/AJ5jrPh7Oy07ocuW/AKYYY02GWzz9NH0nbSMn/gw6fDZ5jZ1QsHt1BXAwJ6Lg== - dependencies: - bindings "^1.5.0" - prebuild-install "^7.1.0" - -big-integer@^1.6.51: - version "1.6.51" - resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" - integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== - -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - -bl@^4.0.3: - version "4.1.0" - resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -bn.js@^4.0.0, bn.js@^4.11.8, bn.js@^4.11.9: - version "4.12.0" - resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz" - integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== - -body-parser@1.20.0: - version "1.20.0" - resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz" - integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.10.3" - raw-body "2.5.1" - type-is "~1.6.18" - unpipe "1.0.0" - -body-parser@1.20.1: - version "1.20.1" - resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz" - integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.1" - type-is "~1.6.18" - unpipe "1.0.0" - -boolean@^3.1.4: - version "3.2.0" - resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b" - integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== - -bowser@^2.11.0: - version "2.11.0" - resolved "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz" - integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -brorand@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz" - integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== - -browserslist@^4.21.3, browserslist@^4.21.4: - version "4.21.4" - resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz" - integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== - dependencies: - caniuse-lite "^1.0.30001400" - electron-to-chromium "^1.4.251" - node-releases "^2.0.6" - update-browserslist-db "^1.0.9" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" - -buffer-equal-constant-time@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz" - integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer-writer@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz" - integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== - -buffer@5.6.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" - integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - -buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -buffer@^6.0.3: - version "6.0.3" - resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - -builtins@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz" - integrity sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ== - -byline@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz" - integrity sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q== - -byte-size@^7.0.0: - version "7.0.1" - resolved "https://registry.npmjs.org/byte-size/-/byte-size-7.0.1.tgz" - integrity sha512-crQdqyCwhokxwV1UyDzLZanhkugAgft7vt0qbbdt60C6Zf3CAiGmtUCylbtYwrU6loOUw3euGrNtW1J651ot1A== - -bytes@3.1.2, bytes@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -cacache@^15.0.5, cacache@^15.2.0: - version "15.3.0" - resolved "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz" - integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== - dependencies: - "@npmcli/fs" "^1.0.0" - "@npmcli/move-file" "^1.0.1" - chownr "^2.0.0" - fs-minipass "^2.0.0" - glob "^7.1.4" - infer-owner "^1.0.4" - lru-cache "^6.0.0" - minipass "^3.1.1" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.2" - mkdirp "^1.0.3" - p-map "^4.0.0" - promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^8.0.1" - tar "^6.0.2" - unique-filename "^1.1.1" - -cacache@^16.1.0: - version "16.1.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-16.1.3.tgz#a02b9f34ecfaf9a78c9f4bc16fceb94d5d67a38e" - integrity sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ== - dependencies: - "@npmcli/fs" "^2.1.0" - "@npmcli/move-file" "^2.0.0" - chownr "^2.0.0" - fs-minipass "^2.1.0" - glob "^8.0.1" - infer-owner "^1.0.4" - lru-cache "^7.7.1" - minipass "^3.1.6" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - mkdirp "^1.0.4" - p-map "^4.0.0" - promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^9.0.0" - tar "^6.1.11" - unique-filename "^2.0.0" - -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase-keys@^6.2.2: - version "6.2.2" - resolved "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz" - integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== - dependencies: - camelcase "^5.3.1" - map-obj "^4.0.0" - quick-lru "^4.0.1" - -camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -caniuse-lite@^1.0.30001400: - version "1.0.30001409" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001409.tgz" - integrity sha512-V0mnJ5dwarmhYv8/MzhJ//aW68UpvnQBXv8lJ2QUsvn2pHcmAuNtu8hQEDz37XnA1iE+lRR9CIfGWWpgJ5QedQ== - -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz" - integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== - -cbor-extract@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/cbor-extract/-/cbor-extract-2.1.1.tgz#f154b31529fdb6b7c70fb3ca448f44eda96a1b42" - integrity sha512-1UX977+L+zOJHsp0mWFG13GLwO6ucKgSmSW6JTl8B9GUvACvHeIVpFqhU92299Z6PfD09aTXDell5p+lp1rUFA== - dependencies: - node-gyp-build-optional-packages "5.0.3" - optionalDependencies: - "@cbor-extract/cbor-extract-darwin-arm64" "2.1.1" - "@cbor-extract/cbor-extract-darwin-x64" "2.1.1" - "@cbor-extract/cbor-extract-linux-arm" "2.1.1" - "@cbor-extract/cbor-extract-linux-arm64" "2.1.1" - "@cbor-extract/cbor-extract-linux-x64" "2.1.1" - "@cbor-extract/cbor-extract-win32-x64" "2.1.1" - -cbor-x@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/cbor-x/-/cbor-x-1.5.1.tgz#d2b0915c556c8ca294bebb4eac7d602218fd63c0" - integrity sha512-/vAkC4tiKCQCm5en4sA+mpKmjwY6Xxp1LO+BgZCNhp+Zow3pomyUHeBOK5EDp0mDaE36jw39l5eLHsoF3M1Lmg== - optionalDependencies: - cbor-extract "^2.1.1" - -cborg@^1.6.0: - version "1.9.5" - resolved "https://registry.npmjs.org/cborg/-/cborg-1.9.5.tgz" - integrity sha512-fLBv8wmqtlXqy1Yu+pHzevAIkW6k2K0ZtMujNzWphLsA34vzzg9BHn+5GmZqOJkSA9V7EMKsWrf6K976c1QMjQ== - -chalk@^2.0.0, chalk@^2.4.1: - version "2.4.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz" - integrity sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w== - -chalk@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/chalk/-/chalk-5.1.1.tgz" - integrity sha512-OItMegkSDU3P7OJRWBbNRsQsL8SzgwlIGXSZRVfHCLBYrDgzYDuozwDMwvEDpiZdjr50tdOTbTzuubirtEozsg== - -char-regex@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" - integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== - -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== - -chownr@^1.1.1, chownr@^1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz" - integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== - -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== - -ci-info@^3.2.0: - version "3.4.0" - resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz" - integrity sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug== - -cjs-module-lexer@^1.0.0: - version "1.2.2" - resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz" - integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== - -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-width@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz" - integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - -clone@^1.0.2: - version "1.0.4" - resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz" - integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== - -cmd-shim@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/cmd-shim/-/cmd-shim-4.1.0.tgz" - integrity sha512-lb9L7EM4I/ZRVuljLPEtUJOP+xiQVknZ4ZMpMgEp4JzNldPb27HU03hi6K1/6CoIuit/Zm/LQXySErFeXxDprw== - dependencies: - mkdirp-infer-owner "^2.0.0" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" - integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== - -code-block-writer@^11.0.3: - version "11.0.3" - resolved "https://registry.npmjs.org/code-block-writer/-/code-block-writer-11.0.3.tgz" - integrity sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw== - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz" - integrity sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA== - -collect-v8-coverage@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz" - integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@^1.0.0, color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -color-string@^1.9.0: - version "1.9.1" - resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color-support@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" - integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== - -color@^4.2.3: - version "4.2.3" - resolved "https://registry.npmjs.org/color/-/color-4.2.3.tgz" - integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== - dependencies: - color-convert "^2.0.1" - color-string "^1.9.0" - -colorette@^2.0.7: - version "2.0.19" - resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz" - integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== - -columnify@^1.5.4: - version "1.6.0" - resolved "https://registry.npmjs.org/columnify/-/columnify-1.6.0.tgz" - integrity sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q== - dependencies: - strip-ansi "^6.0.1" - wcwidth "^1.0.0" - -combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: - version "1.0.8" - resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@^9.4.0: - version "9.4.0" - resolved "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz" - integrity sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw== - -compare-func@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz" - integrity sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA== - dependencies: - array-ify "^1.0.0" - dot-prop "^5.1.0" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -concat-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz" - integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.0.2" - typedarray "^0.0.6" - -config-chain@^1.1.12: - version "1.1.13" - resolved "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz" - integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== - dependencies: - ini "^1.3.4" - proto-list "~1.2.1" - -console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" - integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - -conventional-changelog-angular@^5.0.12: - version "5.0.13" - resolved "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz" - integrity sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA== - dependencies: - compare-func "^2.0.0" - q "^1.5.1" - -conventional-changelog-core@^4.2.2: - version "4.2.4" - resolved "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz" - integrity sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg== - dependencies: - add-stream "^1.0.0" - conventional-changelog-writer "^5.0.0" - conventional-commits-parser "^3.2.0" - dateformat "^3.0.0" - get-pkg-repo "^4.0.0" - git-raw-commits "^2.0.8" - git-remote-origin-url "^2.0.0" - git-semver-tags "^4.1.1" - lodash "^4.17.15" - normalize-package-data "^3.0.0" - q "^1.5.1" - read-pkg "^3.0.0" - read-pkg-up "^3.0.0" - through2 "^4.0.0" - -conventional-changelog-preset-loader@^2.3.4: - version "2.3.4" - resolved "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz" - integrity sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g== - -conventional-changelog-writer@^5.0.0: - version "5.0.1" - resolved "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz" - integrity sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ== - dependencies: - conventional-commits-filter "^2.0.7" - dateformat "^3.0.0" - handlebars "^4.7.7" - json-stringify-safe "^5.0.1" - lodash "^4.17.15" - meow "^8.0.0" - semver "^6.0.0" - split "^1.0.0" - through2 "^4.0.0" - -conventional-commits-filter@^2.0.7: - version "2.0.7" - resolved "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz" - integrity sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA== - dependencies: - lodash.ismatch "^4.4.0" - modify-values "^1.0.0" - -conventional-commits-parser@^3.2.0: - version "3.2.4" - resolved "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz" - integrity sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q== - dependencies: - JSONStream "^1.0.4" - is-text-path "^1.0.1" - lodash "^4.17.15" - meow "^8.0.0" - split2 "^3.0.0" - through2 "^4.0.0" - -conventional-recommended-bump@^6.1.0: - version "6.1.0" - resolved "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz" - integrity sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw== - dependencies: - concat-stream "^2.0.0" - conventional-changelog-preset-loader "^2.3.4" - conventional-commits-filter "^2.0.7" - conventional-commits-parser "^3.2.0" - git-raw-commits "^2.0.8" - git-semver-tags "^4.1.1" - meow "^8.0.0" - q "^1.5.1" - -convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: - version "1.8.0" - resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz" - integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== - dependencies: - safe-buffer "~5.1.1" - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.5.0: - version "0.5.0" - resolved "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== - -core-js-compat@^3.25.1: - version "3.25.2" - resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.25.2.tgz" - integrity sha512-TxfyECD4smdn3/CjWxczVtJqVLEEC2up7/82t7vC0AzNogr+4nQ8vyF7abxAuTXWvjTClSbvGhU0RgqA4ToQaQ== - dependencies: - browserslist "^4.21.4" - -core-util-is@1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" - integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cors@^2.8.5: - version "2.8.5" - resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - -cosmiconfig@^7.0.0: - version "7.0.1" - resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz" - integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.2.1" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.10.0" - -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - -cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - -cross-spawn@^7.0.2, cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -dargs@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz" - integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz" - integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== - dependencies: - assert-plus "^1.0.0" - -dateformat@^3.0.0: - version "3.0.3" - resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz" - integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== - -dateformat@^4.6.3: - version "4.6.3" - resolved "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz" - integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -debuglog@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz" - integrity sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw== - -decamelize-keys@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz" - integrity sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg== - dependencies: - decamelize "^1.1.0" - map-obj "^1.0.0" - -decamelize@^1.1.0: - version "1.2.0" - resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" - integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz" - integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og== - -decompress-response@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz" - integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== - dependencies: - mimic-response "^3.1.0" - -dedent@^0.7.0: - version "0.7.0" - resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz" - integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -deepmerge@^4.2.2: - version "4.2.2" - resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz" - integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== - -defaults@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz" - integrity sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA== - dependencies: - clone "^1.0.2" - -define-properties@^1.1.3, define-properties@^1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz" - integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== - dependencies: - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -delay@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" - integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" - integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== - -depd@2.0.0, depd@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -depd@^1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" - integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== - -deprecation@^2.0.0, deprecation@^2.3.1: - version "2.3.1" - resolved "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz" - integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detect-indent@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz" - integrity sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g== - -detect-indent@^6.0.0: - version "6.1.0" - resolved "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz" - integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== - -detect-libc@^2.0.0, detect-libc@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz" - integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== - -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - -dezalgo@^1.0.0: - version "1.0.4" - resolved "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz" - integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== - dependencies: - asap "^2.0.0" - wrappy "1" - -diff-sequences@^28.1.1: - version "28.1.1" - resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz" - integrity sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -dom-serializer@^1.0.1: - version "1.4.1" - resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz" - integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.2.0" - entities "^2.0.0" - -domelementtype@^2.0.1, domelementtype@^2.2.0: - version "2.3.0" - resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz" - integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== - -domhandler@^4.0.0, domhandler@^4.2.0: - version "4.3.1" - resolved "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz" - integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== - dependencies: - domelementtype "^2.2.0" - -domutils@^2.5.2: - version "2.8.0" - resolved "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz" - integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== - dependencies: - dom-serializer "^1.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" - -dot-prop@^5.1.0: - version "5.3.0" - resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz" - integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== - dependencies: - is-obj "^2.0.0" - -dot-prop@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz" - integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA== - dependencies: - is-obj "^2.0.0" - -dotenv@^16.0.0, dotenv@^16.0.1, dotenv@^16.0.3: - version "16.0.3" - resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz" - integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== - -duplexer@^0.1.1: - version "0.1.2" - resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz" - integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== - -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz" - integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - -ecdsa-sig-formatter@1.0.11: - version "1.0.11" - resolved "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz" - integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== - dependencies: - safe-buffer "^5.0.1" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -electron-to-chromium@^1.4.251: - version "1.4.257" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.257.tgz" - integrity sha512-C65sIwHqNnPC2ADMfse/jWTtmhZMII+x6ADI9gENzrOiI7BpxmfKFE84WkIEl5wEg+7+SfIkwChDlsd1Erju2A== - -elliptic@^6.4.1: - version "6.5.4" - resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz" - integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== - dependencies: - bn.js "^4.11.9" - brorand "^1.1.0" - hash.js "^1.0.0" - hmac-drbg "^1.0.1" - inherits "^2.0.4" - minimalistic-assert "^1.0.1" - minimalistic-crypto-utils "^1.0.1" - -emittery@^0.10.2: - version "0.10.2" - resolved "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz" - integrity sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -encoding@^0.1.12, encoding@^0.1.13: - version "0.1.13" - resolved "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz" - integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== - dependencies: - iconv-lite "^0.6.2" - -end-of-stream@^1.1.0, end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -entities@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz" - integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== - -env-paths@^2.2.0: - version "2.2.1" - resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" - integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== - -envinfo@^7.7.4: - version "7.8.1" - resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz" - integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== - -err-code@^2.0.2: - version "2.0.3" - resolved "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz" - integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-abstract@^1.19.0, es-abstract@^1.19.2, es-abstract@^1.19.5, es-abstract@^1.20.1: - version "1.20.2" - resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.2.tgz" - integrity sha512-XxXQuVNrySBNlEkTYJoDNFe5+s2yIOpzq80sUHEdPdQr0S5nTLz4ZPPPswNIpKseDDUS5yghX1gfLIHQZ1iNuQ== - dependencies: - call-bind "^1.0.2" - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - function.prototype.name "^1.1.5" - get-intrinsic "^1.1.2" - get-symbol-description "^1.0.0" - has "^1.0.3" - has-property-descriptors "^1.0.0" - has-symbols "^1.0.3" - internal-slot "^1.0.3" - is-callable "^1.2.4" - is-negative-zero "^2.0.2" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" - is-string "^1.0.7" - is-weakref "^1.0.2" - object-inspect "^1.12.2" - object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.4.3" - string.prototype.trimend "^1.0.5" - string.prototype.trimstart "^1.0.5" - unbox-primitive "^1.0.2" - -es-abstract@^1.20.4: - version "1.20.5" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.5.tgz#e6dc99177be37cacda5988e692c3fa8b218e95d2" - integrity sha512-7h8MM2EQhsCA7pU/Nv78qOXFpD8Rhqd12gYiSJVkrH9+e8VuA8JlPJK/hQjjlLv6pJvx/z1iRFKzYb0XT/RuAQ== - dependencies: - call-bind "^1.0.2" - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - function.prototype.name "^1.1.5" - get-intrinsic "^1.1.3" - get-symbol-description "^1.0.0" - gopd "^1.0.1" - has "^1.0.3" - has-property-descriptors "^1.0.0" - has-symbols "^1.0.3" - internal-slot "^1.0.3" - is-callable "^1.2.7" - is-negative-zero "^2.0.2" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" - is-string "^1.0.7" - is-weakref "^1.0.2" - object-inspect "^1.12.2" - object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.4.3" - safe-regex-test "^1.0.0" - string.prototype.trimend "^1.0.6" - string.prototype.trimstart "^1.0.6" - unbox-primitive "^1.0.2" - -es-array-method-boxes-properly@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz" - integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -esbuild-android-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be" - integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ== - -esbuild-android-arm64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771" - integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg== - -esbuild-darwin-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz#24ba67b9a8cb890a3c08d9018f887cc221cdda25" - integrity sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug== - -esbuild-darwin-arm64@0.14.54: - version "0.14.54" - resolved "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz" - integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw== - -esbuild-freebsd-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d" - integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg== - -esbuild-freebsd-arm64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48" - integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q== - -esbuild-linux-32@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5" - integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw== - -esbuild-linux-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz#de5fdba1c95666cf72369f52b40b03be71226652" - integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg== - -esbuild-linux-arm64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b" - integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig== - -esbuild-linux-arm@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59" - integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw== - -esbuild-linux-mips64le@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34" - integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw== - -esbuild-linux-ppc64le@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e" - integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ== - -esbuild-linux-riscv64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8" - integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg== - -esbuild-linux-s390x@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6" - integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA== - -esbuild-netbsd-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81" - integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w== - -esbuild-node-externals@^1.5.0: - version "1.5.0" - resolved "https://registry.npmjs.org/esbuild-node-externals/-/esbuild-node-externals-1.5.0.tgz" - integrity sha512-9394Ne2t2Z243BWeNBRkXEYVMOVbQuzp7XSkASZTOQs0GSXDuno5aH5OmzEXc6GMuln5zJjpkZpgwUPW0uRKgw== - dependencies: - find-up "5.0.0" - tslib "2.3.1" - -esbuild-openbsd-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b" - integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw== - -esbuild-plugin-copy@^1.6.0: - version "1.6.0" - resolved "https://registry.npmjs.org/esbuild-plugin-copy/-/esbuild-plugin-copy-1.6.0.tgz" - integrity sha512-wN1paBCoE0yRBl9ZY3ZSD6SxGE4Yfr0Em7zh2yTbJv1JaHEIR3FYYN7HU6F+j/peSaGZJNSORSGxJ5QX1a1Sgg== - dependencies: - chalk "^4.1.2" - fs-extra "^10.0.1" - globby "^11.0.3" - -esbuild-sunos-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da" - integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw== - -esbuild-windows-32@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31" - integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w== - -esbuild-windows-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4" - integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ== - -esbuild-windows-arm64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982" - integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg== - -esbuild@^0.14.48: - version "0.14.54" - resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.14.54.tgz" - integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA== - optionalDependencies: - "@esbuild/linux-loong64" "0.14.54" - esbuild-android-64 "0.14.54" - esbuild-android-arm64 "0.14.54" - esbuild-darwin-64 "0.14.54" - esbuild-darwin-arm64 "0.14.54" - esbuild-freebsd-64 "0.14.54" - esbuild-freebsd-arm64 "0.14.54" - esbuild-linux-32 "0.14.54" - esbuild-linux-64 "0.14.54" - esbuild-linux-arm "0.14.54" - esbuild-linux-arm64 "0.14.54" - esbuild-linux-mips64le "0.14.54" - esbuild-linux-ppc64le "0.14.54" - esbuild-linux-riscv64 "0.14.54" - esbuild-linux-s390x "0.14.54" - esbuild-netbsd-64 "0.14.54" - esbuild-openbsd-64 "0.14.54" - esbuild-sunos-64 "0.14.54" - esbuild-windows-32 "0.14.54" - esbuild-windows-64 "0.14.54" - esbuild-windows-arm64 "0.14.54" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-config-prettier@^8.5.0: - version "8.5.0" - resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz" - integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== - -eslint-plugin-prettier@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" - integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== - dependencies: - prettier-linter-helpers "^1.0.0" - -eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -eslint-scope@^7.1.1: - version "7.1.1" - resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz" - integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz" - integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== - dependencies: - eslint-visitor-keys "^2.0.0" - -eslint-visitor-keys@^1.0.0: - version "1.3.0" - resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== - -eslint-visitor-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== - -eslint-visitor-keys@^3.3.0: - version "3.3.0" - resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz" - integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== - -eslint@^8.24.0: - version "8.24.0" - resolved "https://registry.npmjs.org/eslint/-/eslint-8.24.0.tgz" - integrity sha512-dWFaPhGhTAiPcCgm3f6LI2MBWbogMnTJzFBbhXVRQDJPkr9pGZvVjlVfXd+vyDcWPA2Ic9L2AXPIQM0+vk/cSQ== - dependencies: - "@eslint/eslintrc" "^1.3.2" - "@humanwhocodes/config-array" "^0.10.5" - "@humanwhocodes/gitignore-to-minimatch" "^1.0.2" - "@humanwhocodes/module-importer" "^1.0.1" - ajv "^6.10.0" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.3.2" - doctrine "^3.0.0" - escape-string-regexp "^4.0.0" - eslint-scope "^7.1.1" - eslint-utils "^3.0.0" - eslint-visitor-keys "^3.3.0" - espree "^9.4.0" - esquery "^1.4.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - find-up "^5.0.0" - glob-parent "^6.0.1" - globals "^13.15.0" - globby "^11.1.0" - grapheme-splitter "^1.0.4" - ignore "^5.2.0" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - js-sdsl "^4.1.4" - js-yaml "^4.1.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.1" - regexpp "^3.2.0" - strip-ansi "^6.0.1" - strip-json-comments "^3.1.0" - text-table "^0.2.0" - -espree@^9.4.0: - version "9.4.0" - resolved "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz" - integrity sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw== - dependencies: - acorn "^8.8.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.3.0" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - -eventemitter3@^4.0.4: - version "4.0.7" - resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -events@3.3.0, events@^3.3.0: - version "3.3.0" - resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" - integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== - -expand-template@^2.0.3: - 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== - -expect@^28.0.0, expect@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz" - integrity sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g== - dependencies: - "@jest/expect-utils" "^28.1.3" - jest-get-type "^28.0.2" - jest-matcher-utils "^28.1.3" - jest-message-util "^28.1.3" - jest-util "^28.1.3" - -express-async-errors@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/express-async-errors/-/express-async-errors-3.1.1.tgz" - integrity sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng== - -express@^4.17.2: - version "4.18.1" - resolved "https://registry.npmjs.org/express/-/express-4.18.1.tgz" - integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.0" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.5.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.10.3" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -express@^4.18.2: - version "4.18.2" - resolved "https://registry.npmjs.org/express/-/express-4.18.2.tgz" - integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.1" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.5.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.11.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" - integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== - -extsprintf@^1.2.0: - version "1.4.1" - resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz" - integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== - -fast-copy@^2.1.1: - version "2.1.7" - resolved "https://registry.npmjs.org/fast-copy/-/fast-copy-2.1.7.tgz" - integrity sha512-ozrGwyuCTAy7YgFCua8rmqmytECYk/JYAMXcswOcm0qvGoE3tPb7ivBeIHTOK2DiapBhDZgacIhzhQIKU5TCfA== - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-diff@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" - integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== - -fast-glob@^3.2.11, fast-glob@^3.2.9: - version "3.2.12" - resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz" - integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-json-stringify@^2.7.10: - version "2.7.13" - resolved "https://registry.yarnpkg.com/fast-json-stringify/-/fast-json-stringify-2.7.13.tgz#277aa86c2acba4d9851bd6108ed657aa327ed8c0" - integrity sha512-ar+hQ4+OIurUGjSJD1anvYSDcUflywhKjfxnsW4TBTD7+u0tJufv6DKRWoQk3vI6YBOWMoz0TQtfbe7dxbQmvA== - dependencies: - ajv "^6.11.0" - deepmerge "^4.2.2" - rfdc "^1.2.0" - string-similarity "^4.0.1" - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - -fast-printf@^1.6.9: - version "1.6.9" - resolved "https://registry.yarnpkg.com/fast-printf/-/fast-printf-1.6.9.tgz#212f56570d2dc8ccdd057ee93d50dd414d07d676" - integrity sha512-FChq8hbz65WMj4rstcQsFB0O7Cy++nmbNfLYnD9cYv2cRn8EG6k/MGn9kO/tjO66t09DLDugj3yL+V2o6Qftrg== - dependencies: - boolean "^3.1.4" - -fast-redact@^3.1.1: - version "3.1.2" - resolved "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.2.tgz" - integrity sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw== - -fast-safe-stringify@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz" - integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== - -fast-url-parser@^1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz" - integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ== - dependencies: - punycode "^1.3.2" - -fast-xml-parser@4.0.11: - version "4.0.11" - resolved "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz" - integrity sha512-4aUg3aNRR/WjQAcpceODG1C3x3lFANXRo8+1biqfieHmg9pyMt7qB4lQV/Ta6sJCTbA5vfD8fnA8S54JATiFUA== - dependencies: - strnum "^1.0.5" - -fastq@^1.6.0: - version "1.13.0" - resolved "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz" - integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== - dependencies: - reusify "^1.0.4" - -fb-watchman@^2.0.0: - version "2.0.2" - resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz" - integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== - dependencies: - bser "2.1.1" - -figures@^3.0.0: - version "3.2.0" - resolved "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" - -file-type@^16.5.4: - version "16.5.4" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.4.tgz#474fb4f704bee427681f98dd390058a172a6c2fd" - integrity sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw== - dependencies: - readable-web-to-node-stream "^3.0.0" - strtok3 "^6.2.4" - token-types "^4.1.1" - -file-uri-to-path@1.0.0: - 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== - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -filter-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz" - integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== - -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - -find-up@5.0.0, find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -find-up@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz" - integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ== - dependencies: - locate-path "^2.0.0" - -find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== - dependencies: - flatted "^3.1.0" - rimraf "^3.0.2" - -flatted@^3.1.0: - version "3.2.7" - resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz" - integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== - -follow-redirects@^1.14.9, follow-redirects@^1.15.0: - version "1.15.2" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== - -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" - integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== - -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -fs-constants@^1.0.0: - 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== - -fs-extra@^10.0.1: - version "10.1.0" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz" - integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-extra@^9.1.0: - version "9.1.0" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-minipass@^1.2.7: - version "1.2.7" - resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz" - integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== - dependencies: - minipass "^2.6.0" - -fs-minipass@^2.0.0, fs-minipass@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@^2.3.2: - version "2.3.2" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -function.prototype.name@^1.1.5: - version "1.1.5" - resolved "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz" - integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" - functions-have-names "^1.2.2" - -functions-have-names@^1.2.2: - version "1.2.3" - resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz" - integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== - -gauge@^4.0.3: - version "4.0.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" - integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== - dependencies: - aproba "^1.0.3 || ^2.0.0" - color-support "^1.1.3" - console-control-strings "^1.1.0" - has-unicode "^2.0.1" - signal-exit "^3.0.7" - string-width "^4.2.3" - strip-ansi "^6.0.1" - wide-align "^1.1.5" - -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz" - integrity sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg== - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.2, get-intrinsic@^1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz" - integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.3" - -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - -get-pkg-repo@^4.0.0: - version "4.2.1" - resolved "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz" - integrity sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA== - dependencies: - "@hutson/parse-repository-url" "^3.0.0" - hosted-git-info "^4.0.0" - through2 "^2.0.0" - yargs "^16.2.0" - -get-port@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz" - integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== - -get-port@^6.1.2: - version "6.1.2" - resolved "https://registry.yarnpkg.com/get-port/-/get-port-6.1.2.tgz#c1228abb67ba0e17fb346da33b15187833b9c08a" - integrity sha512-BrGGraKm2uPqurfGVj/z97/zv8dPleC6x9JBNRTrDNtCkkRF4rPwrQXFgL7+I+q8QSdU4ntLQX2D7KIxSy8nGw== - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -get-symbol-description@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz" - integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz" - integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== - dependencies: - assert-plus "^1.0.0" - -git-raw-commits@^2.0.8: - version "2.0.11" - resolved "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz" - integrity sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A== - dependencies: - dargs "^7.0.0" - lodash "^4.17.15" - meow "^8.0.0" - split2 "^3.0.0" - through2 "^4.0.0" - -git-remote-origin-url@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz" - integrity sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw== - dependencies: - gitconfiglocal "^1.0.0" - pify "^2.3.0" - -git-semver-tags@^4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-4.1.1.tgz" - integrity sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA== - dependencies: - meow "^8.0.0" - semver "^6.0.0" - -git-up@^4.0.0: - version "4.0.5" - resolved "https://registry.npmjs.org/git-up/-/git-up-4.0.5.tgz" - integrity sha512-YUvVDg/vX3d0syBsk/CKUTib0srcQME0JyHkL5BaYdwLsiCslPWmDSi8PUMo9pXYjrryMcmsCoCgsTpSCJEQaA== - dependencies: - is-ssh "^1.3.0" - parse-url "^6.0.0" - -git-url-parse@^11.4.4: - version "11.6.0" - resolved "https://registry.npmjs.org/git-url-parse/-/git-url-parse-11.6.0.tgz" - integrity sha512-WWUxvJs5HsyHL6L08wOusa/IXYtMuCAhrMmnTjQPpBU0TTHyDhnOATNH3xNQz7YOQUsqIIPTGr4xiVti1Hsk5g== - dependencies: - git-up "^4.0.0" - -gitconfiglocal@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz" - integrity sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ== - dependencies: - ini "^1.3.2" - -github-from-package@0.0.0: - 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== - -glob-parent@^5.1.1, glob-parent@^5.1.2: - version "5.1.2" - resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.1: - version "6.0.2" - resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: - version "7.2.3" - resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^8.0.0: - version "8.0.3" - resolved "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz" - integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -glob@^8.0.1: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globals@^13.15.0: - version "13.17.0" - resolved "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz" - integrity sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw== - dependencies: - type-fest "^0.20.2" - -globalthis@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" - integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== - dependencies: - define-properties "^1.1.3" - -globby@^11.0.2, globby@^11.0.3, globby@^11.1.0: - version "11.1.0" - resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" - -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.3, graceful-fs@^4.2.9: - version "4.2.10" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== - -graceful-fs@^4.2.6: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -grapheme-splitter@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz" - integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== - -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - -handlebars@^4.7.7: - version "4.7.7" - resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz" - integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== - dependencies: - minimist "^1.2.5" - neo-async "^2.6.0" - source-map "^0.6.1" - wordwrap "^1.0.0" - optionalDependencies: - uglify-js "^3.1.4" - -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz" - integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== - -har-validator@~5.1.3: - version "5.1.5" - resolved "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz" - integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== - dependencies: - ajv "^6.12.3" - har-schema "^2.0.0" - -hard-rejection@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz" - integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== - -has-bigints@^1.0.1, has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz" - integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== - dependencies: - get-intrinsic "^1.1.1" - -has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-tostringtag@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz" - integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== - dependencies: - has-symbols "^1.0.2" - -has-unicode@^2.0.0, has-unicode@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" - integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.7" - resolved "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - -he@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -help-me@^4.0.1: - version "4.1.0" - resolved "https://registry.npmjs.org/help-me/-/help-me-4.1.0.tgz" - integrity sha512-5HMrkOks2j8Fpu2j5nTLhrBhT7VwHwELpqnSnx802ckofys5MO2SkLpgSz3dgNFHV7IYFX2igm5CM75SmuYidw== - dependencies: - glob "^8.0.0" - readable-stream "^3.6.0" - -hmac-drbg@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz" - integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - -hosted-git-info@^4.0.0, hosted-git-info@^4.0.1: - version "4.1.0" - resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz" - integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== - dependencies: - lru-cache "^6.0.0" - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -html-to-text@7.1.1: - version "7.1.1" - resolved "https://registry.npmjs.org/html-to-text/-/html-to-text-7.1.1.tgz" - integrity sha512-c9QWysrfnRZevVpS8MlE7PyOdSuIOjg8Bt8ZE10jMU/BEngA6j3llj4GRfAmtQzcd1FjKE0sWu5IHXRUH9YxIQ== - dependencies: - deepmerge "^4.2.2" - he "^1.2.0" - htmlparser2 "^6.1.0" - minimist "^1.2.5" - -htmlparser2@^6.1.0: - version "6.1.0" - resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz" - integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.0.0" - domutils "^2.5.2" - entities "^2.0.0" - -http-cache-semantics@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz" - integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== - -http-errors@2.0.0, http-errors@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -http-proxy-agent@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== - dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" - -http-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" - integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== - dependencies: - "@tootallnate/once" "2" - agent-base "6" - debug "4" - -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz" - integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -http-terminator@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/http-terminator/-/http-terminator-3.2.0.tgz#bc158d2694b733ca4fbf22a35065a81a609fb3e9" - integrity sha512-JLjck1EzPaWjsmIf8bziM3p9fgR1Y3JoUKAkyYEbZmFrIvJM6I8vVJfBGWlEtV9IWOvzNnaTtjuwZeBY2kwB4g== - dependencies: - delay "^5.0.0" - p-wait-for "^3.2.0" - roarr "^7.0.4" - type-fest "^2.3.3" - -https-proxy-agent@^5.0.0: - version "5.0.1" - resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" - integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== - dependencies: - agent-base "6" - debug "4" - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz" - integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== - dependencies: - ms "^2.0.0" - -iconv-lite@0.4.24, iconv-lite@^0.4.24: - version "0.4.24" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -iconv-lite@^0.6.2: - version "0.6.3" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -ignore-walk@^3.0.3: - version "3.0.4" - resolved "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz" - integrity sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ== - dependencies: - minimatch "^3.0.4" - -ignore@^5.2.0: - version "5.2.0" - resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz" - integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== - -import-fresh@^3.0.0, import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-local@^3.0.2: - version "3.1.0" - resolved "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz" - integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -infer-owner@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz" - integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4: - version "2.0.4" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: - version "1.3.8" - resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -init-package-json@^2.0.2: - version "2.0.5" - resolved "https://registry.npmjs.org/init-package-json/-/init-package-json-2.0.5.tgz" - integrity sha512-u1uGAtEFu3VA6HNl/yUWw57jmKEMx8SKOxHhxjGnOFUiIlFnohKDFg4ZrPpv9wWqk44nDxGJAtqjdQFm+9XXQA== - dependencies: - npm-package-arg "^8.1.5" - promzard "^0.3.0" - read "~1.0.1" - read-package-json "^4.1.1" - semver "^7.3.5" - validate-npm-package-license "^3.0.4" - validate-npm-package-name "^3.0.0" - -inquirer@^7.3.3: - version "7.3.3" - resolved "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz" - integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== - dependencies: - ansi-escapes "^4.2.1" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-width "^3.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.19" - mute-stream "0.0.8" - run-async "^2.4.0" - rxjs "^6.6.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - -internal-slot@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz" - integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== - dependencies: - get-intrinsic "^1.1.0" - has "^1.0.3" - side-channel "^1.0.4" - -ip@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz" - integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== - dependencies: - has-bigints "^1.0.1" - -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-callable@^1.1.4, is-callable@^1.2.4: - version "1.2.6" - resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.6.tgz" - integrity sha512-krO72EO2NptOGAX2KYyqbP9vYMlNAXdB53rq6f8LXY6RY7JdSR/3BD6wLUlPHSAesmY9vstNrjvqGaCiRK/91Q== - -is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== - dependencies: - ci-info "^2.0.0" - -is-core-module@^2.5.0, is-core-module@^2.9.0: - version "2.10.0" - resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz" - integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== - dependencies: - has "^1.0.3" - -is-date-object@^1.0.1: - version "1.0.5" - resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== - dependencies: - has-tostringtag "^1.0.0" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz" - integrity sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw== - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: - version "4.0.3" - resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-lambda@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz" - integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== - -is-negative-zero@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz" - integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== - -is-number-object@^1.0.4: - version "1.0.7" - resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz" - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== - dependencies: - has-tostringtag "^1.0.0" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz" - integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== - -is-plain-obj@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== - -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-plain-object@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz" - integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== - -is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-shared-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz" - integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== - dependencies: - call-bind "^1.0.2" - -is-ssh@^1.3.0: - version "1.4.0" - resolved "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz" - integrity sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ== - dependencies: - protocols "^2.0.1" - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - -is-text-path@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz" - integrity sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w== - dependencies: - text-extensions "^1.0.0" - -is-typedarray@^1.0.0, is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" - integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== - -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== - dependencies: - call-bind "^1.0.2" - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -iso-datestring-validator@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz#2daa80d2900b7a954f9f731d42f96ee0c19a6895" - integrity sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA== - -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" - integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== - -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz" - integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== - -istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: - version "5.2.0" - resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz" - integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^6.3.0" - -istanbul-lib-report@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" - integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^3.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^4.0.0: - version "4.0.1" - resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz" - integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" - -istanbul-reports@^3.1.3: - version "3.1.5" - resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz" - integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== - dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" - -jest-changed-files@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz" - integrity sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA== - dependencies: - execa "^5.0.0" - p-limit "^3.1.0" - -jest-circus@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz" - integrity sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow== - dependencies: - "@jest/environment" "^28.1.3" - "@jest/expect" "^28.1.3" - "@jest/test-result" "^28.1.3" - "@jest/types" "^28.1.3" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - dedent "^0.7.0" - is-generator-fn "^2.0.0" - jest-each "^28.1.3" - jest-matcher-utils "^28.1.3" - jest-message-util "^28.1.3" - jest-runtime "^28.1.3" - jest-snapshot "^28.1.3" - jest-util "^28.1.3" - p-limit "^3.1.0" - pretty-format "^28.1.3" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-cli@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz" - integrity sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ== - dependencies: - "@jest/core" "^28.1.3" - "@jest/test-result" "^28.1.3" - "@jest/types" "^28.1.3" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - import-local "^3.0.2" - jest-config "^28.1.3" - jest-util "^28.1.3" - jest-validate "^28.1.3" - prompts "^2.0.1" - yargs "^17.3.1" - -jest-config@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz" - integrity sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ== - dependencies: - "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^28.1.3" - "@jest/types" "^28.1.3" - babel-jest "^28.1.3" - chalk "^4.0.0" - ci-info "^3.2.0" - deepmerge "^4.2.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-circus "^28.1.3" - jest-environment-node "^28.1.3" - jest-get-type "^28.0.2" - jest-regex-util "^28.0.2" - jest-resolve "^28.1.3" - jest-runner "^28.1.3" - jest-util "^28.1.3" - jest-validate "^28.1.3" - micromatch "^4.0.4" - parse-json "^5.2.0" - pretty-format "^28.1.3" - slash "^3.0.0" - strip-json-comments "^3.1.1" - -jest-diff@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz" - integrity sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw== - dependencies: - chalk "^4.0.0" - diff-sequences "^28.1.1" - jest-get-type "^28.0.2" - pretty-format "^28.1.3" - -jest-docblock@^28.1.1: - version "28.1.1" - resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz" - integrity sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA== - dependencies: - detect-newline "^3.0.0" - -jest-each@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz" - integrity sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g== - dependencies: - "@jest/types" "^28.1.3" - chalk "^4.0.0" - jest-get-type "^28.0.2" - jest-util "^28.1.3" - pretty-format "^28.1.3" - -jest-environment-node@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz" - integrity sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A== - dependencies: - "@jest/environment" "^28.1.3" - "@jest/fake-timers" "^28.1.3" - "@jest/types" "^28.1.3" - "@types/node" "*" - jest-mock "^28.1.3" - jest-util "^28.1.3" - -jest-get-type@^28.0.2: - version "28.0.2" - resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz" - integrity sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA== - -jest-haste-map@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz" - integrity sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA== - dependencies: - "@jest/types" "^28.1.3" - "@types/graceful-fs" "^4.1.3" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.9" - jest-regex-util "^28.0.2" - jest-util "^28.1.3" - jest-worker "^28.1.3" - micromatch "^4.0.4" - walker "^1.0.8" - optionalDependencies: - fsevents "^2.3.2" - -jest-leak-detector@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz" - integrity sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA== - dependencies: - jest-get-type "^28.0.2" - pretty-format "^28.1.3" - -jest-matcher-utils@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz" - integrity sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw== - dependencies: - chalk "^4.0.0" - jest-diff "^28.1.3" - jest-get-type "^28.0.2" - pretty-format "^28.1.3" - -jest-message-util@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz" - integrity sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^28.1.3" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^28.1.3" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-mock@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz" - integrity sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA== - dependencies: - "@jest/types" "^28.1.3" - "@types/node" "*" - -jest-pnp-resolver@^1.2.2: - version "1.2.2" - resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz" - integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== - -jest-regex-util@^28.0.2: - version "28.0.2" - resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz" - integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== - -jest-resolve-dependencies@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz" - integrity sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA== - dependencies: - jest-regex-util "^28.0.2" - jest-snapshot "^28.1.3" - -jest-resolve@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz" - integrity sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ== - dependencies: - chalk "^4.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^28.1.3" - jest-pnp-resolver "^1.2.2" - jest-util "^28.1.3" - jest-validate "^28.1.3" - resolve "^1.20.0" - resolve.exports "^1.1.0" - slash "^3.0.0" - -jest-runner@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz" - integrity sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA== - dependencies: - "@jest/console" "^28.1.3" - "@jest/environment" "^28.1.3" - "@jest/test-result" "^28.1.3" - "@jest/transform" "^28.1.3" - "@jest/types" "^28.1.3" - "@types/node" "*" - chalk "^4.0.0" - emittery "^0.10.2" - graceful-fs "^4.2.9" - jest-docblock "^28.1.1" - jest-environment-node "^28.1.3" - jest-haste-map "^28.1.3" - jest-leak-detector "^28.1.3" - jest-message-util "^28.1.3" - jest-resolve "^28.1.3" - jest-runtime "^28.1.3" - jest-util "^28.1.3" - jest-watcher "^28.1.3" - jest-worker "^28.1.3" - p-limit "^3.1.0" - source-map-support "0.5.13" - -jest-runtime@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz" - integrity sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw== - dependencies: - "@jest/environment" "^28.1.3" - "@jest/fake-timers" "^28.1.3" - "@jest/globals" "^28.1.3" - "@jest/source-map" "^28.1.2" - "@jest/test-result" "^28.1.3" - "@jest/transform" "^28.1.3" - "@jest/types" "^28.1.3" - chalk "^4.0.0" - cjs-module-lexer "^1.0.0" - collect-v8-coverage "^1.0.0" - execa "^5.0.0" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-haste-map "^28.1.3" - jest-message-util "^28.1.3" - jest-mock "^28.1.3" - jest-regex-util "^28.0.2" - jest-resolve "^28.1.3" - jest-snapshot "^28.1.3" - jest-util "^28.1.3" - slash "^3.0.0" - strip-bom "^4.0.0" - -jest-snapshot@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz" - integrity sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg== - dependencies: - "@babel/core" "^7.11.6" - "@babel/generator" "^7.7.2" - "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/traverse" "^7.7.2" - "@babel/types" "^7.3.3" - "@jest/expect-utils" "^28.1.3" - "@jest/transform" "^28.1.3" - "@jest/types" "^28.1.3" - "@types/babel__traverse" "^7.0.6" - "@types/prettier" "^2.1.5" - babel-preset-current-node-syntax "^1.0.0" - chalk "^4.0.0" - expect "^28.1.3" - graceful-fs "^4.2.9" - jest-diff "^28.1.3" - jest-get-type "^28.0.2" - jest-haste-map "^28.1.3" - jest-matcher-utils "^28.1.3" - jest-message-util "^28.1.3" - jest-util "^28.1.3" - natural-compare "^1.4.0" - pretty-format "^28.1.3" - semver "^7.3.5" - -jest-util@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz" - integrity sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ== - dependencies: - "@jest/types" "^28.1.3" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-validate@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz" - integrity sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA== - dependencies: - "@jest/types" "^28.1.3" - camelcase "^6.2.0" - chalk "^4.0.0" - jest-get-type "^28.0.2" - leven "^3.1.0" - pretty-format "^28.1.3" - -jest-watcher@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz" - integrity sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g== - dependencies: - "@jest/test-result" "^28.1.3" - "@jest/types" "^28.1.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - emittery "^0.10.2" - jest-util "^28.1.3" - string-length "^4.0.1" - -jest-worker@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz" - integrity sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest@^28.1.2: - version "28.1.3" - resolved "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz" - integrity sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA== - dependencies: - "@jest/core" "^28.1.3" - "@jest/types" "^28.1.3" - import-local "^3.0.2" - jest-cli "^28.1.3" - -joycon@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz" - integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== - -js-sdsl@^4.1.4: - version "4.1.4" - resolved "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz" - integrity sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw== - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" - integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" - integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== - -json-parse-better-errors@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-parse-even-better-errors@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz" - integrity sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema@0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz" - integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - -json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" - integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== - -json5@^2.2.1: - version "2.2.1" - resolved "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== - -jsonc-parser@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" - integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== - -jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - -jsonparse@^1.2.0, jsonparse@^1.3.1: - version "1.3.1" - resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz" - integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== - -jsonwebtoken@^8.5.1: - version "8.5.1" - resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz" - integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== - dependencies: - jws "^3.2.2" - lodash.includes "^4.3.0" - lodash.isboolean "^3.0.3" - lodash.isinteger "^4.0.4" - lodash.isnumber "^3.0.3" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.once "^4.0.0" - ms "^2.1.1" - semver "^5.6.0" - -jsprim@^1.2.2: - version "1.4.2" - resolved "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz" - integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.4.0" - verror "1.10.0" - -jwa@^1.4.1: - version "1.4.1" - resolved "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz" - integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jws@^3.2.2: - version "3.2.2" - resolved "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== - dependencies: - jwa "^1.4.1" - safe-buffer "^5.0.1" - -key-encoder@^2.0.3: - version "2.0.3" - resolved "https://registry.npmjs.org/key-encoder/-/key-encoder-2.0.3.tgz" - integrity sha512-fgBtpAGIr/Fy5/+ZLQZIPPhsZEcbSlYu/Wu96tNDFNSjSACw5lEIOFeaVdQ/iwrb8oxjlWi6wmWdH76hV6GZjg== - dependencies: - "@types/elliptic" "^6.4.9" - asn1.js "^5.0.1" - bn.js "^4.11.8" - elliptic "^6.4.1" - -kind-of@^6.0.2, kind-of@^6.0.3: - version "6.0.3" - resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -kysely@^0.22.0: - version "0.22.0" - resolved "https://registry.npmjs.org/kysely/-/kysely-0.22.0.tgz" - integrity sha512-ZE3qWtnqLOalodzfK5QUEcm7AEulhxsPNuKaGFsC3XiqO92vMLm+mAHk/NnbSIOtC4RmGm0nsv700i8KDp1gfQ== - -kysely@^0.23.4: - version "0.23.4" - resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.23.4.tgz#1975bfc37fb5074d60a415e8db73d5698528199a" - integrity sha512-3icLnj1fahUtZsP9zzOvF4DcdhekGsLX4ZaoBaIz0ZeHegyRDdbwpJD7zezAJ+KwQZNDeKchel6MikFNLsSZIA== - -lerna@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/lerna/-/lerna-4.0.0.tgz" - integrity sha512-DD/i1znurfOmNJb0OBw66NmNqiM8kF6uIrzrJ0wGE3VNdzeOhz9ziWLYiRaZDGGwgbcjOo6eIfcx9O5Qynz+kg== - dependencies: - "@lerna/add" "4.0.0" - "@lerna/bootstrap" "4.0.0" - "@lerna/changed" "4.0.0" - "@lerna/clean" "4.0.0" - "@lerna/cli" "4.0.0" - "@lerna/create" "4.0.0" - "@lerna/diff" "4.0.0" - "@lerna/exec" "4.0.0" - "@lerna/import" "4.0.0" - "@lerna/info" "4.0.0" - "@lerna/init" "4.0.0" - "@lerna/link" "4.0.0" - "@lerna/list" "4.0.0" - "@lerna/publish" "4.0.0" - "@lerna/run" "4.0.0" - "@lerna/version" "4.0.0" - import-local "^3.0.2" - npmlog "^4.1.2" - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -libnpmaccess@^4.0.1: - version "4.0.3" - resolved "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-4.0.3.tgz" - integrity sha512-sPeTSNImksm8O2b6/pf3ikv4N567ERYEpeKRPSmqlNt1dTZbvgpJIzg5vAhXHpw2ISBsELFRelk0jEahj1c6nQ== - dependencies: - aproba "^2.0.0" - minipass "^3.1.1" - npm-package-arg "^8.1.2" - npm-registry-fetch "^11.0.0" - -libnpmpublish@^4.0.0: - version "4.0.2" - resolved "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-4.0.2.tgz" - integrity sha512-+AD7A2zbVeGRCFI2aO//oUmapCwy7GHqPXFJh3qpToSRNU+tXKJ2YFUgjt04LPPAf2dlEH95s6EhIHM1J7bmOw== - dependencies: - normalize-package-data "^3.0.2" - npm-package-arg "^8.1.2" - npm-registry-fetch "^11.0.0" - semver "^7.1.3" - ssri "^8.0.1" - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -load-json-file@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz" - integrity sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw== - dependencies: - graceful-fs "^4.1.2" - parse-json "^4.0.0" - pify "^3.0.0" - strip-bom "^3.0.0" - -load-json-file@^6.2.0: - version "6.2.0" - resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-6.2.0.tgz" - integrity sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ== - dependencies: - graceful-fs "^4.1.15" - parse-json "^5.0.0" - strip-bom "^4.0.0" - type-fest "^0.6.0" - -locate-path@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz" - integrity sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA== - dependencies: - p-locate "^2.0.0" - path-exists "^3.0.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz" - integrity sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA== - -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" - integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== - -lodash.includes@^4.3.0: - version "4.3.0" - resolved "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz" - integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== - -lodash.isboolean@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz" - integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== - -lodash.isinteger@^4.0.4: - version "4.0.4" - resolved "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz" - integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== - -lodash.ismatch@^4.4.0: - version "4.4.0" - resolved "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz" - integrity sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g== - -lodash.isnumber@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz" - integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== - -lodash.isplainobject@^4.0.6: - version "4.0.6" - resolved "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz" - integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== - -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz" - integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lodash.once@^4.0.0: - version "4.1.1" - resolved "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz" - integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== - -lodash.template@^4.5.0: - version "4.5.0" - resolved "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz" - integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.templatesettings "^4.0.0" - -lodash.templatesettings@^4.0.0: - version "4.2.0" - resolved "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz" - integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== - dependencies: - lodash._reinterpolate "^3.0.0" - -lodash@^4.17.15, lodash@^4.17.19, lodash@^4.7.0: - version "4.17.21" - resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -lru-cache@^7.7.1: - version "7.18.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" - integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== - -make-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz" - integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== - dependencies: - pify "^4.0.1" - semver "^5.6.0" - -make-dir@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -make-error@^1.1.1: - version "1.3.6" - resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -make-fetch-happen@^10.0.3: - version "10.2.1" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz#f5e3835c5e9817b617f2770870d9492d28678164" - integrity sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w== - dependencies: - agentkeepalive "^4.2.1" - cacache "^16.1.0" - http-cache-semantics "^4.1.0" - http-proxy-agent "^5.0.0" - https-proxy-agent "^5.0.0" - is-lambda "^1.0.1" - lru-cache "^7.7.1" - minipass "^3.1.6" - minipass-collect "^1.0.2" - minipass-fetch "^2.0.3" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - negotiator "^0.6.3" - promise-retry "^2.0.1" - socks-proxy-agent "^7.0.0" - ssri "^9.0.0" - -make-fetch-happen@^8.0.9: - version "8.0.14" - resolved "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-8.0.14.tgz" - integrity sha512-EsS89h6l4vbfJEtBZnENTOFk8mCRpY5ru36Xe5bcX1KYIli2mkSHqoFsp5O1wMDvTJJzxe/4THpCTtygjeeGWQ== - dependencies: - agentkeepalive "^4.1.3" - cacache "^15.0.5" - http-cache-semantics "^4.1.0" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - is-lambda "^1.0.1" - lru-cache "^6.0.0" - minipass "^3.1.3" - minipass-collect "^1.0.2" - minipass-fetch "^1.3.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - promise-retry "^2.0.1" - socks-proxy-agent "^5.0.0" - ssri "^8.0.0" - -make-fetch-happen@^9.0.1: - version "9.1.0" - resolved "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz" - integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== - dependencies: - agentkeepalive "^4.1.3" - cacache "^15.2.0" - http-cache-semantics "^4.1.0" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - is-lambda "^1.0.1" - lru-cache "^6.0.0" - minipass "^3.1.3" - minipass-collect "^1.0.2" - minipass-fetch "^1.3.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - negotiator "^0.6.2" - promise-retry "^2.0.1" - socks-proxy-agent "^6.0.0" - ssri "^8.0.0" - -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - -map-obj@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz" - integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg== - -map-obj@^4.0.0: - version "4.3.0" - resolved "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz" - integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -memorystream@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" - integrity sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw== - -meow@^8.0.0: - version "8.1.2" - resolved "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz" - integrity sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q== - dependencies: - "@types/minimist" "^1.2.0" - camelcase-keys "^6.2.2" - decamelize-keys "^1.1.0" - hard-rejection "^2.1.0" - minimist-options "4.1.0" - normalize-package-data "^3.0.0" - read-pkg-up "^7.0.1" - redent "^3.0.0" - trim-newlines "^3.0.0" - type-fest "^0.18.0" - yargs-parser "^20.2.3" - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.3.0, merge2@^1.4.1: - version "1.4.1" - resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12, mime-types@^2.1.35, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mimic-response@^3.1.0: - 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== - -min-indent@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz" - integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== - -minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz" - integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== - -minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^5.0.1, minimatch@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz" - integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== - dependencies: - brace-expansion "^2.0.1" - -minimist-options@4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz" - integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== - dependencies: - arrify "^1.0.1" - is-plain-obj "^1.1.0" - kind-of "^6.0.3" - -minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: - version "1.2.6" - resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== - -minipass-collect@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz" - integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== - dependencies: - minipass "^3.0.0" - -minipass-fetch@^1.3.0, minipass-fetch@^1.3.2: - version "1.4.1" - resolved "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz" - integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== - dependencies: - minipass "^3.1.0" - minipass-sized "^1.0.3" - minizlib "^2.0.0" - optionalDependencies: - encoding "^0.1.12" - -minipass-fetch@^2.0.3: - version "2.1.2" - resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-2.1.2.tgz#95560b50c472d81a3bc76f20ede80eaed76d8add" - integrity sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA== - dependencies: - minipass "^3.1.6" - minipass-sized "^1.0.3" - minizlib "^2.1.2" - optionalDependencies: - encoding "^0.1.13" - -minipass-flush@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz" - integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== - dependencies: - minipass "^3.0.0" - -minipass-json-stream@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz" - integrity sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg== - dependencies: - jsonparse "^1.3.1" - minipass "^3.0.0" - -minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: - version "1.2.4" - resolved "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz" - integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== - dependencies: - minipass "^3.0.0" - -minipass-sized@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz" - integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== - dependencies: - minipass "^3.0.0" - -minipass@^2.6.0, minipass@^2.9.0: - version "2.9.0" - resolved "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz" - integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: - version "3.3.4" - resolved "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz" - integrity sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw== - dependencies: - yallist "^4.0.0" - -minipass@^3.1.6: - version "3.3.6" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" - integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== - dependencies: - yallist "^4.0.0" - -minipass@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" - integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== - -minizlib@^1.3.3: - version "1.3.3" - resolved "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz" - integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== - dependencies: - minipass "^2.9.0" - -minizlib@^2.0.0, minizlib@^2.1.1, minizlib@^2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz" - integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" - -mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: - version "0.5.3" - resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz" - integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== - -mkdirp-infer-owner@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz" - integrity sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw== - dependencies: - chownr "^2.0.0" - infer-owner "^1.0.4" - mkdirp "^1.0.3" - -mkdirp@^0.5.1, mkdirp@^0.5.5: - version "0.5.6" - resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz" - integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== - dependencies: - minimist "^1.2.6" - -mkdirp@^1.0.3, mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -modify-values@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz" - integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3, ms@^2.0.0, ms@^2.1.1: - version "2.1.3" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -multiformats@^9.4.2, multiformats@^9.5.4, multiformats@^9.6.4: - version "9.9.0" - resolved "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz" - integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== - -multimatch@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz" - integrity sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA== - dependencies: - "@types/minimatch" "^3.0.3" - array-differ "^3.0.0" - array-union "^2.1.0" - arrify "^2.0.1" - minimatch "^3.0.4" - -mute-stream@0.0.8, mute-stream@~0.0.4: - version "0.0.8" - resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== - -napi-build-utils@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz" - integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -negotiator@0.6.3, negotiator@^0.6.2, negotiator@^0.6.3: - version "0.6.3" - resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -neo-async@^2.6.0: - version "2.6.2" - resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - -node-abi@^3.3.0: - version "3.26.0" - resolved "https://registry.npmjs.org/node-abi/-/node-abi-3.26.0.tgz" - integrity sha512-jRVtMFTChbi2i/jqo/i2iP9634KMe+7K1v35mIdj3Mn59i5q27ZYhn+sW6npISM/PQg7HrP2kwtRBMmh5Uvzdg== - dependencies: - semver "^7.3.5" - -node-addon-api@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz" - integrity sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA== - -node-fetch@^2.6.1, node-fetch@^2.6.7: - version "2.6.7" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - -node-gyp-build-optional-packages@5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.3.tgz#92a89d400352c44ad3975010368072b41ad66c17" - integrity sha512-k75jcVzk5wnnc/FMxsf4udAoTEUv2jY3ycfdSd3yWu6Cnd1oee6/CfZJApyscA4FJOmdoixWwiwOyf16RzD5JA== - -node-gyp@^5.0.2: - version "5.1.1" - resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.1.tgz" - integrity sha512-WH0WKGi+a4i4DUt2mHnvocex/xPLp9pYt5R6M2JdFB7pJ7Z34hveZ4nDTGTiLXCkitA9T8HFZjhinBCiVHYcWw== - dependencies: - env-paths "^2.2.0" - glob "^7.1.4" - graceful-fs "^4.2.2" - mkdirp "^0.5.1" - nopt "^4.0.1" - npmlog "^4.1.2" - request "^2.88.0" - rimraf "^2.6.3" - semver "^5.7.1" - tar "^4.4.12" - which "^1.3.1" - -node-gyp@^7.1.0: - version "7.1.2" - resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-7.1.2.tgz" - integrity sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ== - dependencies: - env-paths "^2.2.0" - glob "^7.1.4" - graceful-fs "^4.2.3" - nopt "^5.0.0" - npmlog "^4.1.2" - request "^2.88.2" - rimraf "^3.0.2" - semver "^7.3.2" - tar "^6.0.2" - which "^2.0.2" - -node-gyp@^9.3.1: - version "9.3.1" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.3.1.tgz#1e19f5f290afcc9c46973d68700cbd21a96192e4" - integrity sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg== - dependencies: - env-paths "^2.2.0" - glob "^7.1.4" - graceful-fs "^4.2.6" - make-fetch-happen "^10.0.3" - nopt "^6.0.0" - npmlog "^6.0.0" - rimraf "^3.0.2" - semver "^7.3.5" - tar "^6.1.2" - which "^2.0.2" - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" - integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== - -node-releases@^2.0.6: - version "2.0.6" - resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz" - integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== - -nodemailer-html-to-text@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/nodemailer-html-to-text/-/nodemailer-html-to-text-3.2.0.tgz" - integrity sha512-RJUC6640QV1PzTHHapOrc6IzrAJUZtk2BdVdINZ9VTLm+mcQNyBO9LYyhrnufkzqiD9l8hPLJ97rSyK4WanPNg== - dependencies: - html-to-text "7.1.1" - -nodemailer@^6.8.0: - version "6.8.0" - resolved "https://registry.npmjs.org/nodemailer/-/nodemailer-6.8.0.tgz" - integrity sha512-EjYvSmHzekz6VNkNd12aUqAco+bOkRe3Of5jVhltqKhEsjw/y0PYPJfp83+s9Wzh1dspYAkUW/YNQ350NATbSQ== - -nopt@^4.0.1: - version "4.0.3" - resolved "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz" - integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== - dependencies: - abbrev "1" - osenv "^0.1.4" - -nopt@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz" - integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== - dependencies: - abbrev "1" - -nopt@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-6.0.0.tgz#245801d8ebf409c6df22ab9d95b65e1309cdb16d" - integrity sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g== - dependencies: - abbrev "^1.0.0" - -normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: - version "2.5.0" - resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-package-data@^3.0.0, normalize-package-data@^3.0.2: - version "3.0.3" - resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz" - integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== - dependencies: - hosted-git-info "^4.0.1" - is-core-module "^2.5.0" - semver "^7.3.4" - validate-npm-package-license "^3.0.1" - -normalize-path@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -normalize-url@^6.1.0: - version "6.1.0" - resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== - -npm-bundled@^1.1.1: - version "1.1.2" - resolved "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz" - integrity sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ== - dependencies: - npm-normalize-package-bin "^1.0.1" - -npm-install-checks@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-4.0.0.tgz" - integrity sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w== - dependencies: - semver "^7.1.1" - -npm-lifecycle@^3.1.5: - version "3.1.5" - resolved "https://registry.npmjs.org/npm-lifecycle/-/npm-lifecycle-3.1.5.tgz" - integrity sha512-lDLVkjfZmvmfvpvBzA4vzee9cn+Me4orq0QF8glbswJVEbIcSNWib7qGOffolysc3teCqbbPZZkzbr3GQZTL1g== - dependencies: - byline "^5.0.0" - graceful-fs "^4.1.15" - node-gyp "^5.0.2" - resolve-from "^4.0.0" - slide "^1.1.6" - uid-number "0.0.6" - umask "^1.1.0" - which "^1.3.1" - -npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz" - integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== - -npm-package-arg@^8.0.0, npm-package-arg@^8.0.1, npm-package-arg@^8.1.0, npm-package-arg@^8.1.2, npm-package-arg@^8.1.5: - version "8.1.5" - resolved "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.5.tgz" - integrity sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q== - dependencies: - hosted-git-info "^4.0.1" - semver "^7.3.4" - validate-npm-package-name "^3.0.0" - -npm-packlist@^2.1.4: - version "2.2.2" - resolved "https://registry.npmjs.org/npm-packlist/-/npm-packlist-2.2.2.tgz" - integrity sha512-Jt01acDvJRhJGthnUJVF/w6gumWOZxO7IkpY/lsX9//zqQgnF7OJaxgQXcerd4uQOLu7W5bkb4mChL9mdfm+Zg== - dependencies: - glob "^7.1.6" - ignore-walk "^3.0.3" - npm-bundled "^1.1.1" - npm-normalize-package-bin "^1.0.1" - -npm-pick-manifest@^6.0.0, npm-pick-manifest@^6.1.1: - version "6.1.1" - resolved "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz" - integrity sha512-dBsdBtORT84S8V8UTad1WlUyKIY9iMsAmqxHbLdeEeBNMLQDlDWWra3wYUx9EBEIiG/YwAy0XyNHDd2goAsfuA== - dependencies: - npm-install-checks "^4.0.0" - npm-normalize-package-bin "^1.0.1" - npm-package-arg "^8.1.2" - semver "^7.3.4" - -npm-registry-fetch@^11.0.0: - version "11.0.0" - resolved "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-11.0.0.tgz" - integrity sha512-jmlgSxoDNuhAtxUIG6pVwwtz840i994dL14FoNVZisrmZW5kWd63IUTNv1m/hyRSGSqWjCUp/YZlS1BJyNp9XA== - dependencies: - make-fetch-happen "^9.0.1" - minipass "^3.1.3" - minipass-fetch "^1.3.0" - minipass-json-stream "^1.0.1" - minizlib "^2.0.0" - npm-package-arg "^8.0.0" - -npm-registry-fetch@^9.0.0: - version "9.0.0" - resolved "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-9.0.0.tgz" - integrity sha512-PuFYYtnQ8IyVl6ib9d3PepeehcUeHN9IO5N/iCRhyg9tStQcqGQBRVHmfmMWPDERU3KwZoHFvbJ4FPXPspvzbA== - dependencies: - "@npmcli/ci-detect" "^1.0.0" - lru-cache "^6.0.0" - make-fetch-happen "^8.0.9" - minipass "^3.1.3" - minipass-fetch "^1.3.0" - minipass-json-stream "^1.0.1" - minizlib "^2.0.0" - npm-package-arg "^8.0.0" - -npm-run-all@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba" - integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ== - dependencies: - ansi-styles "^3.2.1" - chalk "^2.4.1" - cross-spawn "^6.0.5" - memorystream "^0.3.1" - minimatch "^3.0.4" - pidtree "^0.3.0" - read-pkg "^3.0.0" - shell-quote "^1.6.1" - string.prototype.padend "^3.0.0" - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -npmlog@^4.1.2: - version "4.1.2" - resolved "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - -npmlog@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" - integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== - dependencies: - are-we-there-yet "^3.0.0" - console-control-strings "^1.1.0" - gauge "^4.0.3" - set-blocking "^2.0.0" - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz" - integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ== - -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - -object-assign@^4, object-assign@^4.1.0: - version "4.1.1" - resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.12.2, object-inspect@^1.9.0: - version "1.12.2" - resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz" - integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.0, object.assign@^4.1.4: - version "4.1.4" - resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz" - integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - has-symbols "^1.0.3" - object-keys "^1.1.1" - -object.getownpropertydescriptors@^2.0.3: - version "2.1.4" - resolved "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz" - integrity sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ== - dependencies: - array.prototype.reduce "^1.0.4" - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.1" - -on-exit-leak-free@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz" - integrity sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w== - -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -one-webcrypto@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/one-webcrypto/-/one-webcrypto-1.0.3.tgz" - integrity sha512-fu9ywBVBPx0gS9K0etIROTiCkvI5S1TDjFsYFb3rC1ewFxeOqsbzq7aIMBHsYfrTHBcGXJaONXXjTl8B01cW1Q== - -onetime@^5.1.0, onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.3" - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz" - integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ== - -os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" - integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== - -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - -p-limit@^1.1.0: - version "1.3.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz" - integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== - dependencies: - p-try "^1.0.0" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2, p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz" - integrity sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg== - dependencies: - p-limit "^1.1.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-map-series@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/p-map-series/-/p-map-series-2.1.0.tgz" - integrity sha512-RpYIIK1zXSNEOdwxcfe7FdvGcs7+y5n8rifMhMNWvaxRNMPINJHF5GDeuVxWqnfrcHPSCnp7Oo5yNXHId9Av2Q== - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - -p-pipe@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/p-pipe/-/p-pipe-3.1.0.tgz" - integrity sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw== - -p-queue@^6.6.2: - version "6.6.2" - resolved "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz" - integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== - dependencies: - eventemitter3 "^4.0.4" - p-timeout "^3.2.0" - -p-reduce@^2.0.0, p-reduce@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz" - integrity sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw== - -p-timeout@^3.0.0, p-timeout@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz" - integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== - dependencies: - p-finally "^1.0.0" - -p-try@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz" - integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww== - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -p-wait-for@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/p-wait-for/-/p-wait-for-3.2.0.tgz#640429bcabf3b0dd9f492c31539c5718cb6a3f1f" - integrity sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA== - dependencies: - p-timeout "^3.0.0" - -p-waterfall@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/p-waterfall/-/p-waterfall-2.1.1.tgz" - integrity sha512-RRTnDb2TBG/epPRI2yYXsimO0v3BXC8Yd3ogr1545IaqKK17VGhbWVeGGN+XfCm/08OK8635nH31c8bATkHuSw== - dependencies: - p-reduce "^2.0.0" - -packet-reader@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz" - integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== - -pacote@^11.2.6: - version "11.3.5" - resolved "https://registry.npmjs.org/pacote/-/pacote-11.3.5.tgz" - integrity sha512-fT375Yczn4zi+6Hkk2TBe1x1sP8FgFsEIZ2/iWaXY2r/NkhDJfxbcn5paz1+RTFCyNf+dPnaoBDJoAxXSU8Bkg== - dependencies: - "@npmcli/git" "^2.1.0" - "@npmcli/installed-package-contents" "^1.0.6" - "@npmcli/promise-spawn" "^1.2.0" - "@npmcli/run-script" "^1.8.2" - cacache "^15.0.5" - chownr "^2.0.0" - fs-minipass "^2.1.0" - infer-owner "^1.0.4" - minipass "^3.1.3" - mkdirp "^1.0.3" - npm-package-arg "^8.0.1" - npm-packlist "^2.1.4" - npm-pick-manifest "^6.0.0" - npm-registry-fetch "^11.0.0" - promise-retry "^2.0.1" - read-package-json-fast "^2.0.1" - rimraf "^3.0.2" - ssri "^8.0.1" - tar "^6.1.0" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz" - integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== - dependencies: - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - -parse-json@^5.0.0, parse-json@^5.2.0: - version "5.2.0" - resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -parse-path@^4.0.0: - version "4.0.4" - resolved "https://registry.npmjs.org/parse-path/-/parse-path-4.0.4.tgz" - integrity sha512-Z2lWUis7jlmXC1jeOG9giRO2+FsuyNipeQ43HAjqAZjwSe3SEf+q/84FGPHoso3kyntbxa4c4i77t3m6fGf8cw== - dependencies: - is-ssh "^1.3.0" - protocols "^1.4.0" - qs "^6.9.4" - query-string "^6.13.8" - -parse-url@^6.0.0: - version "6.0.5" - resolved "https://registry.npmjs.org/parse-url/-/parse-url-6.0.5.tgz" - integrity sha512-e35AeLTSIlkw/5GFq70IN7po8fmDUjpDPY1rIK+VubRfsUvBonjQ+PBZG+vWMACnQSmNlvl524IucoDmcioMxA== - dependencies: - is-ssh "^1.3.0" - normalize-url "^6.1.0" - parse-path "^4.0.0" - protocols "^1.4.0" - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-browserify@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz" - integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz" - integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== - -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz" - integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== - dependencies: - pify "^3.0.0" - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -peek-readable@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.1.0.tgz#4ece1111bf5c2ad8867c314c81356847e8a62e72" - integrity sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg== - -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" - integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== - -pg-connection-string@^2.5.0: - version "2.5.0" - resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz" - integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== - -pg-int8@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz" - integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== - -pg-pool@^3.5.2: - version "3.5.2" - resolved "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz" - integrity sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w== - -pg-pool@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.0.tgz#3190df3e4747a0d23e5e9e8045bcd99bda0a712e" - integrity sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ== - -pg-protocol@*, pg-protocol@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.0.tgz#4c91613c0315349363af2084608db843502f8833" - integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q== - -pg-types@^2.1.0, pg-types@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz" - integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== - dependencies: - pg-int8 "1.0.1" - postgres-array "~2.0.0" - postgres-bytea "~1.0.0" - postgres-date "~1.0.4" - postgres-interval "^1.1.0" - -pg@^8.10.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/pg/-/pg-8.10.0.tgz#5b8379c9b4a36451d110fc8cd98fc325fe62ad24" - integrity sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ== - dependencies: - buffer-writer "2.0.0" - packet-reader "1.0.0" - pg-connection-string "^2.5.0" - pg-pool "^3.6.0" - pg-protocol "^1.6.0" - pg-types "^2.1.0" - pgpass "1.x" - -pg@^8.9.0: - version "8.9.0" - resolved "https://registry.yarnpkg.com/pg/-/pg-8.9.0.tgz#73c5d77a854d36b0e185450dacb8b90c669e040b" - integrity sha512-ZJM+qkEbtOHRuXjmvBtOgNOXOtLSbxiMiUVMgE4rV6Zwocy03RicCVvDXgx8l4Biwo8/qORUnEqn2fdQzV7KCg== - dependencies: - buffer-writer "2.0.0" - packet-reader "1.0.0" - pg-connection-string "^2.5.0" - pg-pool "^3.5.2" - pg-protocol "^1.6.0" - pg-types "^2.1.0" - pgpass "1.x" - -pgpass@1.x: - version "1.0.5" - resolved "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz" - integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== - dependencies: - split2 "^4.1.0" - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pidtree@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a" - integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA== - -pify@^2.3.0: - version "2.3.0" - resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" - integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== - -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz" - integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== - -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - -pify@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz" - integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== - -pino-abstract-transport@^1.0.0, pino-abstract-transport@v1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz" - integrity sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA== - dependencies: - readable-stream "^4.0.0" - split2 "^4.0.0" - -pino-http@^8.2.1: - version "8.2.1" - resolved "https://registry.npmjs.org/pino-http/-/pino-http-8.2.1.tgz" - integrity sha512-bdWAE4HYfFjDhKw2/N7BLNSIFAs+WDLZnetsGRpBdNEKq7/RoZUgblLS5OlMY257RPQml6J5QiiLkwxbstzWbA== - dependencies: - fast-url-parser "^1.1.3" - get-caller-file "^2.0.5" - pino "^8.0.0" - pino-std-serializers "^6.0.0" - process-warning "^2.0.0" - -pino-http@^8.3.3: - version "8.3.3" - resolved "https://registry.yarnpkg.com/pino-http/-/pino-http-8.3.3.tgz#2b140e734bfc6babe0df272a43bb8f36f2b525c0" - integrity sha512-p4umsNIXXVu95HD2C8wie/vXH7db5iGRpc+yj1/ZQ3sRtTQLXNjoS6Be5+eI+rQbqCRxen/7k/KSN+qiZubGDw== - dependencies: - get-caller-file "^2.0.5" - pino "^8.0.0" - pino-std-serializers "^6.0.0" - process-warning "^2.0.0" - -pino-pretty@^9.1.0: - version "9.1.0" - resolved "https://registry.npmjs.org/pino-pretty/-/pino-pretty-9.1.0.tgz" - integrity sha512-IM6NY9LLo/dVgY7/prJhCh4rAJukafdt0ibxeNOWc2fxKMyTk90SOB9Ao2HfbtShT9QPeP0ePpJktksMhSQMYA== - dependencies: - colorette "^2.0.7" - dateformat "^4.6.3" - fast-copy "^2.1.1" - fast-safe-stringify "^2.1.1" - help-me "^4.0.1" - joycon "^3.1.1" - minimist "^1.2.6" - on-exit-leak-free "^2.1.0" - pino-abstract-transport "^1.0.0" - pump "^3.0.0" - readable-stream "^4.0.0" - secure-json-parse "^2.4.0" - sonic-boom "^3.0.0" - strip-json-comments "^3.1.1" - -pino-std-serializers@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.0.0.tgz" - integrity sha512-mMMOwSKrmyl+Y12Ri2xhH1lbzQxwwpuru9VjyJpgFIH4asSj88F2csdMwN6+M5g1Ll4rmsYghHLQJw81tgZ7LQ== - -pino@^8.0.0, pino@^8.6.1: - version "8.6.1" - resolved "https://registry.npmjs.org/pino/-/pino-8.6.1.tgz" - integrity sha512-fi+V2K98eMZjQ/uEHHSiMALNrz7HaFdKNYuyA3ZUrbH0f1e8sPFDmeRGzg7ZH2q4QDxGnJPOswmqlEaTAZeDPA== - dependencies: - atomic-sleep "^1.0.0" - fast-redact "^3.1.1" - on-exit-leak-free "^2.1.0" - pino-abstract-transport v1.0.0 - pino-std-serializers "^6.0.0" - process-warning "^2.0.0" - quick-format-unescaped "^4.0.3" - real-require "^0.2.0" - safe-stable-stringify "^2.3.1" - sonic-boom "^3.1.0" - thread-stream "^2.0.0" - -pino@^8.11.0: - version "8.11.0" - resolved "https://registry.yarnpkg.com/pino/-/pino-8.11.0.tgz#2a91f454106b13e708a66c74ebc1c2ab7ab38498" - integrity sha512-Z2eKSvlrl2rH8p5eveNUnTdd4AjJk8tAsLkHYZQKGHP4WTh2Gi1cOSOs3eWPqaj+niS3gj4UkoreoaWgF3ZWYg== - dependencies: - atomic-sleep "^1.0.0" - fast-redact "^3.1.1" - on-exit-leak-free "^2.1.0" - pino-abstract-transport v1.0.0 - pino-std-serializers "^6.0.0" - process-warning "^2.0.0" - quick-format-unescaped "^4.0.3" - real-require "^0.2.0" - safe-stable-stringify "^2.3.1" - sonic-boom "^3.1.0" - thread-stream "^2.0.0" - -pirates@^4.0.4: - version "4.0.5" - resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz" - integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -postgres-array@~2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz" - integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== - -postgres-bytea@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz" - integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== - -postgres-date@~1.0.4: - version "1.0.7" - resolved "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz" - integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== - -postgres-interval@^1.1.0: - version "1.2.0" - resolved "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz" - integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== - dependencies: - xtend "^4.0.0" - -prebuild-install@^7.1.0, prebuild-install@^7.1.1: - version "7.1.1" - resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz" - integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== - 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 "^1.0.1" - 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" - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -prettier-config-standard@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/prettier-config-standard/-/prettier-config-standard-5.0.0.tgz" - integrity sha512-QK252QwCxlsak8Zx+rPKZU31UdbRcu9iUk9X1ONYtLSO221OgvV9TlKoTf6iPDZtvF3vE2mkgzFIEgSUcGELSQ== - -prettier-linter-helpers@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" - integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== - dependencies: - fast-diff "^1.1.2" - -prettier@^2.7.1: - version "2.7.1" - resolved "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz" - integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== - -pretty-format@^28.0.0, pretty-format@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz" - integrity sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q== - dependencies: - "@jest/schemas" "^28.1.3" - ansi-regex "^5.0.1" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -process-warning@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/process-warning/-/process-warning-2.0.0.tgz" - integrity sha512-+MmoAXoUX+VTHAlwns0h+kFUWFs/3FZy+ZuchkgjyOu3oioLAo2LB5aCfKPh2+P9O18i3m43tUEv3YqttSy0Ww== - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz" - integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== - -promise-inflight@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz" - integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== - -promise-retry@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz" - integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== - dependencies: - err-code "^2.0.2" - retry "^0.12.0" - -prompts@^2.0.1: - version "2.4.2" - resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -promzard@^0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz" - integrity sha512-JZeYqd7UAcHCwI+sTOeUDYkvEU+1bQ7iE0UT1MgB/tERkAPkesW46MrpIySzODi+owTjZtiF8Ay5j9m60KmMBw== - dependencies: - read "1" - -proto-list@~1.2.1: - version "1.2.4" - resolved "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz" - integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== - -protocols@^1.4.0: - version "1.4.8" - resolved "https://registry.npmjs.org/protocols/-/protocols-1.4.8.tgz" - integrity sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg== - -protocols@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz" - integrity sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q== - -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - -psl@^1.1.28: - version "1.9.0" - resolved "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz" - integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -punycode@^1.3.2: - version "1.4.1" - resolved "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" - integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== - -punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -q@^1.5.1: - version "1.5.1" - resolved "https://registry.npmjs.org/q/-/q-1.5.1.tgz" - integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== - -qs@6.10.3: - version "6.10.3" - resolved "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz" - integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== - dependencies: - side-channel "^1.0.4" - -qs@6.11.0, qs@^6.9.4: - version "6.11.0" - resolved "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== - dependencies: - side-channel "^1.0.4" - -qs@~6.5.2: - version "6.5.3" - resolved "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz" - integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== - -query-string@^6.13.8: - version "6.14.1" - resolved "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz" - integrity sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw== - dependencies: - decode-uri-component "^0.2.0" - filter-obj "^1.1.0" - split-on-first "^1.0.0" - strict-uri-encode "^2.0.0" - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -quick-format-unescaped@^4.0.3: - version "4.0.4" - resolved "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz" - integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== - -quick-lru@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz" - integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -react-is@^18.0.0: - version "18.2.0" - resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" - integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== - -read-cmd-shim@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-2.0.0.tgz" - integrity sha512-HJpV9bQpkl6KwjxlJcBoqu9Ba0PQg8TqSNIOrulGt54a0uup0HtevreFHzYzkm0lpnleRdNBzXznKrgxglEHQw== - -read-package-json-fast@^2.0.1: - version "2.0.3" - resolved "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz" - integrity sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ== - dependencies: - json-parse-even-better-errors "^2.3.0" - npm-normalize-package-bin "^1.0.1" - -read-package-json@^2.0.0: - version "2.1.2" - resolved "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.2.tgz" - integrity sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA== - dependencies: - glob "^7.1.1" - json-parse-even-better-errors "^2.3.0" - normalize-package-data "^2.0.0" - npm-normalize-package-bin "^1.0.0" - -read-package-json@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/read-package-json/-/read-package-json-3.0.1.tgz" - integrity sha512-aLcPqxovhJTVJcsnROuuzQvv6oziQx4zd3JvG0vGCL5MjTONUc4uJ90zCBC6R7W7oUKBNoR/F8pkyfVwlbxqng== - dependencies: - glob "^7.1.1" - json-parse-even-better-errors "^2.3.0" - normalize-package-data "^3.0.0" - npm-normalize-package-bin "^1.0.0" - -read-package-json@^4.1.1: - version "4.1.2" - resolved "https://registry.npmjs.org/read-package-json/-/read-package-json-4.1.2.tgz" - integrity sha512-Dqer4pqzamDE2O4M55xp1qZMuLPqi4ldk2ya648FOMHRjwMzFhuxVrG04wd0c38IsvkVdr3vgHI6z+QTPdAjrQ== - dependencies: - glob "^7.1.1" - json-parse-even-better-errors "^2.3.0" - normalize-package-data "^3.0.0" - npm-normalize-package-bin "^1.0.0" - -read-package-tree@^5.3.1: - version "5.3.1" - resolved "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.3.1.tgz" - integrity sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw== - dependencies: - read-package-json "^2.0.0" - readdir-scoped-modules "^1.0.0" - util-promisify "^2.1.0" - -read-pkg-up@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz" - integrity sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw== - dependencies: - find-up "^2.0.0" - read-pkg "^3.0.0" - -read-pkg-up@^7.0.1: - version "7.0.1" - resolved "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz" - integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== - dependencies: - find-up "^4.1.0" - read-pkg "^5.2.0" - type-fest "^0.8.1" - -read-pkg@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz" - integrity sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA== - dependencies: - load-json-file "^4.0.0" - normalize-package-data "^2.3.2" - path-type "^3.0.0" - -read-pkg@^5.2.0: - version "5.2.0" - resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz" - integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== - dependencies: - "@types/normalize-package-data" "^2.4.0" - normalize-package-data "^2.5.0" - parse-json "^5.0.0" - type-fest "^0.6.0" - -read@1, read@~1.0.1: - version "1.0.7" - resolved "https://registry.npmjs.org/read/-/read-1.0.7.tgz" - integrity sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ== - dependencies: - mute-stream "~0.0.4" - -readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readable-stream@^2.0.6, readable-stream@~2.3.6: - version "2.3.7" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^4.0.0: - version "4.2.0" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-4.2.0.tgz" - integrity sha512-gJrBHsaI3lgBoGMW/jHZsQ/o/TIWiu5ENCJG1BB7fuCKzpFM8GaS2UoBVt9NO+oI+3FcrBNbUkl3ilDe09aY4A== - dependencies: - abort-controller "^3.0.0" - buffer "^6.0.3" - events "^3.3.0" - process "^0.11.10" - -readable-web-to-node-stream@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb" - integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw== - dependencies: - readable-stream "^3.6.0" - -readdir-scoped-modules@^1.0.0: - version "1.1.0" - resolved "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz" - integrity sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw== - dependencies: - debuglog "^1.0.1" - dezalgo "^1.0.0" - graceful-fs "^4.1.2" - once "^1.3.0" - -real-require@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz" - integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== - -redent@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz" - integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== - dependencies: - indent-string "^4.0.0" - strip-indent "^3.0.0" - -regenerate-unicode-properties@^10.1.0: - version "10.1.0" - resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz" - integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== - dependencies: - regenerate "^1.4.2" - -regenerate@^1.4.2: - version "1.4.2" - resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz" - integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== - -regenerator-runtime@^0.13.4: - version "0.13.9" - resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz" - integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== - -regenerator-transform@^0.15.0: - version "0.15.0" - resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz" - integrity sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg== - dependencies: - "@babel/runtime" "^7.8.4" - -regexp.prototype.flags@^1.4.3: - version "1.4.3" - resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz" - integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - functions-have-names "^1.2.2" - -regexpp@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== - -regexpu-core@^5.1.0: - version "5.2.1" - resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.1.tgz" - integrity sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ== - dependencies: - regenerate "^1.4.2" - regenerate-unicode-properties "^10.1.0" - regjsgen "^0.7.1" - regjsparser "^0.9.1" - unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.0.0" - -regjsgen@^0.7.1: - version "0.7.1" - resolved "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz" - integrity sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA== - -regjsparser@^0.9.1: - version "0.9.1" - resolved "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz" - integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== - dependencies: - jsesc "~0.5.0" - -request@^2.88.0, request@^2.88.2: - version "2.88.2" - resolved "https://registry.npmjs.org/request/-/request-2.88.2.tgz" - integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.5.0" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve.exports@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz" - integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== - -resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.20.0: - version "1.22.1" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== - dependencies: - is-core-module "^2.9.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz" - integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rfdc@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" - integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== - -rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -rimraf@^3.0.0, rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -roarr@^7.0.4: - version "7.14.0" - resolved "https://registry.yarnpkg.com/roarr/-/roarr-7.14.0.tgz#4de049c94aa930160a9e52ef3b0ebf166c5935b6" - integrity sha512-Np9LwC48JHvqQaS1lXvSi8aC+eaZSJItC3b0rNIan8jTwl8oTUMpqrcCvSO2bC6jDtqPI8zkQSVQWEpyl0L48Q== - dependencies: - boolean "^3.1.4" - fast-json-stringify "^2.7.10" - fast-printf "^1.6.9" - fast-safe-stringify "^2.1.1" - globalthis "^1.0.2" - semver-compare "^1.0.0" - -run-async@^2.4.0: - version "2.4.1" - resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz" - integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -rxjs@^6.6.0: - version "6.6.7" - resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz" - integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== - dependencies: - tslib "^1.9.0" - -rxjs@^7.5.2: - version "7.6.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.6.0.tgz#361da5362b6ddaa691a2de0b4f2d32028f1eb5a2" - integrity sha512-DDa7d8TFNUalGC9VqXvQ1euWNN7sc63TrUCuM9J998+ViviahMIjKSOU7rfcgFOF+FCD71BhDRv4hrFz+ImDLQ== - dependencies: - tslib "^2.1.0" - -safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-regex-test@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" - integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.3" - is-regex "^1.1.4" - -safe-stable-stringify@^2.3.1: - version "2.4.0" - resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.0.tgz" - integrity sha512-eehKHKpab6E741ud7ZIMcXhKcP6TSIezPkNZhy5U8xC6+VvrRdUA2tMgxGxaGl4cz7c2Ew5+mg5+wNB16KQqrA== - -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -secure-json-parse@^2.4.0: - version "2.5.0" - resolved "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.5.0.tgz" - integrity sha512-ZQruFgZnIWH+WyO9t5rWt4ZEGqCKPwhiw+YbzTwpmT9elgLrLcfuyUiSnwwjUiVy9r4VM3urtbNF1xmEh9IL2w== - -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" - integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== - -"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0, semver@^5.7.1: - version "5.7.1" - resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^7.1.1, semver@^7.1.3, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7: - version "7.3.7" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz" - integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== - dependencies: - lru-cache "^6.0.0" - -semver@^7.3.8: - version "7.3.8" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz" - integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== - dependencies: - lru-cache "^6.0.0" - -send@0.18.0: - version "0.18.0" - resolved "https://registry.npmjs.org/send/-/send-0.18.0.tgz" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.18.0" - -set-blocking@^2.0.0, set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" - integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - -sharp@^0.31.2: - version "0.31.2" - resolved "https://registry.npmjs.org/sharp/-/sharp-0.31.2.tgz" - integrity sha512-DUdNVEXgS5A97cTagSLIIp8dUZ/lZtk78iNVZgHdHbx1qnQR7JAHY0BnXnwwH39Iw+VKhO08CTYhIg0p98vQ5Q== - dependencies: - color "^4.2.3" - detect-libc "^2.0.1" - node-addon-api "^5.0.0" - prebuild-install "^7.1.1" - semver "^7.3.8" - simple-get "^4.0.1" - tar-fs "^2.1.1" - tunnel-agent "^0.6.0" - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== - dependencies: - shebang-regex "^1.0.0" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -shell-quote@^1.6.1: - version "1.7.4" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.4.tgz#33fe15dee71ab2a81fcbd3a52106c5cfb9fb75d8" - integrity sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw== - -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -simple-concat@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz" - integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== - -simple-get@^4.0.0, simple-get@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz" - integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== - dependencies: - decompress-response "^6.0.0" - once "^1.3.1" - simple-concat "^1.0.0" - -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz" - integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== - dependencies: - is-arrayish "^0.3.1" - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -slide@^1.1.6: - version "1.1.6" - resolved "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz" - integrity sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw== - -smart-buffer@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz" - integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== - -socks-proxy-agent@^5.0.0: - version "5.0.1" - resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz" - integrity sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ== - dependencies: - agent-base "^6.0.2" - debug "4" - socks "^2.3.3" - -socks-proxy-agent@^6.0.0: - version "6.2.1" - resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz" - integrity sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ== - dependencies: - agent-base "^6.0.2" - debug "^4.3.3" - socks "^2.6.2" - -socks-proxy-agent@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz#dc069ecf34436621acb41e3efa66ca1b5fed15b6" - integrity sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww== - dependencies: - agent-base "^6.0.2" - debug "^4.3.3" - socks "^2.6.2" - -socks@^2.3.3, socks@^2.6.2: - version "2.7.0" - resolved "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz" - integrity sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA== - dependencies: - ip "^2.0.0" - smart-buffer "^4.2.0" - -sonic-boom@^3.0.0, sonic-boom@^3.1.0: - version "3.2.0" - resolved "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.2.0.tgz" - integrity sha512-SbbZ+Kqj/XIunvIAgUZRlqd6CGQYq71tRRbXR92Za8J/R3Yh4Av+TWENiSiEgnlwckYLyP0YZQWVfyNC0dzLaA== - dependencies: - atomic-sleep "^1.0.0" - -sort-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz" - integrity sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg== - dependencies: - is-plain-obj "^1.0.0" - -sort-keys@^4.0.0: - version "4.2.0" - resolved "https://registry.npmjs.org/sort-keys/-/sort-keys-4.2.0.tgz" - integrity sha512-aUYIEU/UviqPgc8mHR6IW1EGxkAXpeRETYcrzg8cLAvUPZcpAlleSXHV2mY7G12GphSH6Gzv+4MMVSSkbdteHg== - dependencies: - is-plain-obj "^2.0.0" - -source-map-support@0.5.13: - version "0.5.13" - resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz" - integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.12" - resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz" - integrity sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA== - -split-on-first@^1.0.0: - version "1.1.0" - resolved "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz" - integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== - -split2@^3.0.0: - version "3.2.2" - resolved "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz" - integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== - dependencies: - readable-stream "^3.0.0" - -split2@^4.0.0, split2@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz" - integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ== - -split@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/split/-/split-1.0.1.tgz" - integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== - dependencies: - through "2" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -sshpk@^1.7.0: - version "1.17.0" - resolved "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz" - integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - -ssri@^8.0.0, ssri@^8.0.1: - version "8.0.1" - resolved "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz" - integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== - dependencies: - minipass "^3.1.1" - -ssri@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057" - integrity sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q== - dependencies: - minipass "^3.1.1" - -stack-utils@^2.0.3: - version "2.0.5" - resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz" - integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== - dependencies: - escape-string-regexp "^2.0.0" - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -stream-browserify@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" - integrity sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA== - dependencies: - inherits "~2.0.4" - readable-stream "^3.5.0" - -strict-uri-encode@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz" - integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== - -string-length@^4.0.1: - version "4.0.2" - resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" - integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== - dependencies: - char-regex "^1.0.2" - strip-ansi "^6.0.0" - -string-similarity@^4.0.1: - version "4.0.4" - resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.4.tgz#42d01ab0b34660ea8a018da8f56a3309bb8b2a5b" - integrity sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ== - -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz" - integrity sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw== - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string.prototype.padend@^3.0.0: - version "3.1.4" - resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.4.tgz#2c43bb3a89eb54b6750de5942c123d6c98dd65b6" - integrity sha512-67otBXoksdjsnXXRUq+KMVTdlVRZ2af422Y0aTyTjVaoQkGr3mxl2Bc5emi7dOQ3OGVVQQskmLEWwFXwommpNw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - -string.prototype.trimend@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz" - integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.19.5" - -string.prototype.trimend@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" - integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - -string.prototype.trimstart@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz" - integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.19.5" - -string.prototype.trimstart@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" - integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - -string_decoder@^1.1.1: - 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== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" - integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" - integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== - -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-indent@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz" - integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== - dependencies: - min-indent "^1.0.0" - -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" - integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== - -strnum@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz" - integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== - -strong-log-transformer@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz" - integrity sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA== - dependencies: - duplexer "^0.1.1" - minimist "^1.2.0" - through "^2.3.4" - -strtok3@^6.2.4: - version "6.3.0" - resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.3.0.tgz#358b80ffe6d5d5620e19a073aa78ce947a90f9a0" - integrity sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw== - dependencies: - "@tokenizer/token" "^0.3.0" - peek-readable "^4.1.0" - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.0.0, supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-hyperlinks@^2.0.0: - version "2.3.0" - resolved "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz" - integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== - dependencies: - has-flag "^4.0.0" - supports-color "^7.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -tar-fs@^2.0.0, tar-fs@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz" - integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== - dependencies: - chownr "^1.1.1" - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^2.1.4" - -tar-stream@^2.1.4: - version "2.2.0" - resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz" - integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== - 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" - -tar@^4.4.12: - version "4.4.19" - resolved "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz" - integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== - dependencies: - chownr "^1.1.4" - fs-minipass "^1.2.7" - minipass "^2.9.0" - minizlib "^1.3.3" - mkdirp "^0.5.5" - safe-buffer "^5.2.1" - yallist "^3.1.1" - -tar@^6.0.2, tar@^6.1.0: - version "6.1.11" - resolved "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz" - integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^3.0.0" - minizlib "^2.1.1" - mkdirp "^1.0.3" - yallist "^4.0.0" - -tar@^6.1.11, tar@^6.1.2: - version "6.1.14" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.14.tgz#e87926bec1cfe7c9e783a77a79f3e81c1cfa3b66" - integrity sha512-piERznXu0U7/pW7cdSn7hjqySIVTYT6F76icmFk7ptU7dDYlXTm5r9A6K04R2vU3olYgoKeo1Cg3eeu5nhftAw== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^5.0.0" - minizlib "^2.1.1" - mkdirp "^1.0.3" - yallist "^4.0.0" - -temp-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz" - integrity sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ== - -temp-write@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/temp-write/-/temp-write-4.0.0.tgz" - integrity sha512-HIeWmj77uOOHb0QX7siN3OtwV3CTntquin6TNVg6SHOqCP3hYKmox90eeFOGaY1MqJ9WYDDjkyZrW6qS5AWpbw== - dependencies: - graceful-fs "^4.1.15" - is-stream "^2.0.0" - make-dir "^3.0.0" - temp-dir "^1.0.0" - uuid "^3.3.2" - -terminal-link@^2.0.0: - version "2.1.1" - resolved "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz" - integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== - dependencies: - ansi-escapes "^4.2.1" - supports-hyperlinks "^2.0.0" - -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - -text-extensions@^1.0.0: - version "1.9.0" - resolved "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz" - integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== - -thread-stream@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/thread-stream/-/thread-stream-2.2.0.tgz" - integrity sha512-rUkv4/fnb4rqy/gGy7VuqK6wE1+1DOCOWy4RMeaV69ZHMP11tQKZvZSip1yTgrKCMZzEMcCL/bKfHvSfDHx+iQ== - dependencies: - real-require "^0.2.0" - -through2@^2.0.0: - version "2.0.5" - resolved "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -through2@^4.0.0: - version "4.0.2" - resolved "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz" - integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw== - dependencies: - readable-stream "3" - -through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6: - version "2.3.8" - resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - -tlds@^1.234.0: - version "1.238.0" - resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.238.0.tgz#ffe7c19c8940c35b497cda187a6927f9450325a4" - integrity sha512-lFPF9pZFhLrPodaJ0wt9QIN0l8jOxqmUezGZnm7BfkDSVd9q667oVIJukLVzhF+4oW7uDlrLlfJrL5yu9RWwew== - -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - -tmpl@1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -token-types@^4.1.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/token-types/-/token-types-4.2.1.tgz#0f897f03665846982806e138977dbe72d44df753" - integrity sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ== - dependencies: - "@tokenizer/token" "^0.3.0" - ieee754 "^1.2.1" - -tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - -tr46@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz" - integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== - dependencies: - punycode "^2.1.1" - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -trim-newlines@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz" - integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== - -ts-morph@^16.0.0: - version "16.0.0" - resolved "https://registry.npmjs.org/ts-morph/-/ts-morph-16.0.0.tgz" - integrity sha512-jGNF0GVpFj0orFw55LTsQxVYEUOCWBAbR5Ls7fTYE5pQsbW18ssTb/6UXx/GYAEjS+DQTp8VoTw0vqYMiaaQuw== - dependencies: - "@ts-morph/common" "~0.17.0" - code-block-writer "^11.0.3" - -ts-node@^10.8.1, ts-node@^10.8.2: - version "10.9.1" - resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz" - integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== - dependencies: - "@cspotcode/source-map-support" "^0.8.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - v8-compile-cache-lib "^3.0.1" - yn "3.1.1" - -tslib@2.3.1: - version "2.3.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz" - integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== - -tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.0: - version "1.14.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tslib@^2.1.0: - version "2.4.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz" - integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== - -tslib@^2.3.1: - version "2.4.0" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz" - integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== - -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz" - integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== - dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" - integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-detect@4.0.8: - version "4.0.8" - resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - -type-fest@^0.18.0: - version "0.18.1" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz" - integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -type-fest@^0.4.1: - version "0.4.1" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.4.1.tgz" - integrity sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw== - -type-fest@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz" - integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== - -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - -type-fest@^2.3.3: - version "2.19.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" - integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typed-emitter@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/typed-emitter/-/typed-emitter-2.1.0.tgz#ca78e3d8ef1476f228f548d62e04e3d4d3fd77fb" - integrity sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA== - optionalDependencies: - rxjs "^7.5.2" - -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" - integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== - -typescript@^4.8.4: - version "4.8.4" - resolved "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz" - integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== - -uglify-js@^3.1.4: - version "3.17.1" - resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.1.tgz" - integrity sha512-+juFBsLLw7AqMaqJ0GFvlsGZwdQfI2ooKQB39PSBgMnMakcFosi9O8jCwE+2/2nMNcc0z63r9mwjoDG8zr+q0Q== - -uid-number@0.0.6: - version "0.0.6" - resolved "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz" - integrity sha512-c461FXIljswCuscZn67xq9PpszkPT6RjheWFQTgCyabJrTUozElanb0YEqv2UGgk247YpcJkFBuSGNvBlpXM9w== - -uint8arrays@3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz" - integrity sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA== - dependencies: - multiformats "^9.4.2" - -umask@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz" - integrity sha512-lE/rxOhmiScJu9L6RTNVgB/zZbF+vGC0/p6D3xnkAePI2o0sMyFG966iR5Ki50OI/0mNi2yaRnxfLsPmEZF/JA== - -unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== - dependencies: - call-bind "^1.0.2" - has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" - -unicode-canonical-property-names-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" - integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== - -unicode-match-property-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz" - integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== - dependencies: - unicode-canonical-property-names-ecmascript "^2.0.0" - unicode-property-aliases-ecmascript "^2.0.0" - -unicode-match-property-value-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz" - integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== - -unicode-property-aliases-ecmascript@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz" - integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== - -unique-filename@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz" - integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== - dependencies: - unique-slug "^2.0.0" - -unique-filename@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2" - integrity sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A== - dependencies: - unique-slug "^3.0.0" - -unique-slug@^2.0.0: - version "2.0.2" - resolved "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz" - integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== - dependencies: - imurmurhash "^0.1.4" - -unique-slug@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-3.0.0.tgz#6d347cf57c8a7a7a6044aabd0e2d74e4d76dc7c9" - integrity sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w== - dependencies: - imurmurhash "^0.1.4" - -universal-user-agent@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz" - integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== - -universalify@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz" - integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -upath@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz" - integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== - -update-browserslist-db@^1.0.9: - version "1.0.9" - resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz" - integrity sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -util-promisify@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/util-promisify/-/util-promisify-2.1.0.tgz" - integrity sha512-K+5eQPYs14b3+E+hmE2J6gCZ4JmMl9DbYS6BeP2CHq6WMuNxErxf5B/n0fz85L8zUuoO6rIzNNmIQDu/j+1OcA== - dependencies: - object.getownpropertydescriptors "^2.0.3" - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -v8-compile-cache-lib@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" - integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== - -v8-to-istanbul@^9.0.1: - version "9.0.1" - resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz" - integrity sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w== - dependencies: - "@jridgewell/trace-mapping" "^0.3.12" - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^1.6.0" - -validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: - version "3.0.4" - resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -validate-npm-package-name@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz" - integrity sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw== - dependencies: - builtins "^1.0.3" - -varint@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/varint/-/varint-6.0.0.tgz#9881eb0ce8feaea6512439d19ddf84bf551661d0" - integrity sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg== - -vary@^1, vary@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz" - integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -walker@^1.0.8: - version "1.0.8" - resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== - dependencies: - makeerror "1.0.12" - -wcwidth@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz" - integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== - dependencies: - defaults "^1.0.3" - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -webidl-conversions@^6.1.0: - version "6.1.0" - resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz" - integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -whatwg-url@^8.4.0: - version "8.7.0" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz" - integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== - dependencies: - lodash "^4.7.0" - tr46 "^2.1.0" - webidl-conversions "^6.1.0" - -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - -which@^1.2.9, which@^1.3.1: - version "1.3.1" - resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -which@^2.0.1, which@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wide-align@^1.1.0, wide-align@^1.1.5: - version "1.1.5" - resolved "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz" - integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== - dependencies: - string-width "^1.0.2 || 2 || 3 || 4" - -word-wrap@^1.2.3: - version "1.2.3" - resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== - -wordwrap@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" - integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -write-file-atomic@^2.4.2: - version "2.4.3" - resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz" - integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ== - dependencies: - graceful-fs "^4.1.11" - imurmurhash "^0.1.4" - signal-exit "^3.0.2" - -write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -write-file-atomic@^4.0.1: - version "4.0.2" - resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz" - integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^3.0.7" - -write-json-file@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/write-json-file/-/write-json-file-3.2.0.tgz" - integrity sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ== - dependencies: - detect-indent "^5.0.0" - graceful-fs "^4.1.15" - make-dir "^2.1.0" - pify "^4.0.1" - sort-keys "^2.0.0" - write-file-atomic "^2.4.2" - -write-json-file@^4.3.0: - version "4.3.0" - resolved "https://registry.npmjs.org/write-json-file/-/write-json-file-4.3.0.tgz" - integrity sha512-PxiShnxf0IlnQuMYOPPhPkhExoCQuTUNPOa/2JWCYTmBquU9njyyDuwRKN26IZBlp4yn1nt+Agh2HOOBl+55HQ== - dependencies: - detect-indent "^6.0.0" - graceful-fs "^4.1.15" - is-plain-obj "^2.0.0" - make-dir "^3.0.0" - sort-keys "^4.0.0" - write-file-atomic "^3.0.0" - -write-pkg@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/write-pkg/-/write-pkg-4.0.0.tgz" - integrity sha512-v2UQ+50TNf2rNHJ8NyWttfm/EJUBWMJcx6ZTYZr6Qp52uuegWw/lBkCtCbnYZEmPRNL61m+u67dAmGxo+HTULA== - dependencies: - sort-keys "^2.0.0" - type-fest "^0.4.1" - write-json-file "^3.2.0" - -ws@^8.12.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.0.tgz#485074cc392689da78e1828a9ff23585e06cddd8" - integrity sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig== - -xtend@^4.0.0, xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^3.0.0, yallist@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yaml@^1.10.0: - version "1.10.2" - resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -yargs-parser@20.2.4: - version "20.2.4" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - -yargs-parser@^20.2.2, yargs-parser@^20.2.3: - version "20.2.9" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs-parser@^21.0.0: - version "21.1.1" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^16.2.0: - version "16.2.0" - resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yargs@^17.3.1: - version "17.5.1" - resolved "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz" - integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.0.0" - -yesno@^0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/yesno/-/yesno-0.4.0.tgz" - integrity sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA== - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zod@^3.14.2: - version "3.19.1" - resolved "https://registry.npmjs.org/zod/-/zod-3.19.1.tgz" - integrity sha512-LYjZsEDhCdYET9ikFu6dVPGp2YH9DegXjdJToSzD9rO6fy4qiRYFoyEYwps88OseJlPyl2NOe2iJuhEhL7IpEA== - -zod@^3.21.4: - version "3.21.4" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db" - integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==