diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..28cf99a --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,131 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances of + any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, + without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at hi@tylur.dev. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][mozilla coc]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][faq]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[mozilla coc]: https://github.com/mozilla/diversity +[faq]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..c508849 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,97 @@ +# Contributing + +Thanks for your interest in contributing to `prototypey`! 💖 + +> After this page, see [DEVELOPMENT.md](./DEVELOPMENT.md) for local development instructions. + +## Code of Conduct + +This project contains a [Contributor Covenant code of conduct](./CODE_OF_CONDUCT.md) all contributors are expected to follow. + +## Reporting Issues + +Please do [report an issue on the issue tracker](https://github.com/tylersayshi/prototypey/issues/new/choose) if there's any bugfix, documentation improvement, or general enhancement you'd like to see in the repository! Please fully fill out all required fields in the most appropriate issue form. + +## Sending Contributions + +Sending your own changes as contribution is always appreciated! +There are two steps involved: + +1. [Finding an Issue](#finding-an-issue) +2. [Sending a Pull Request](#sending-a-pull-request) + +### Finding an Issue + +With the exception of very small typos, all changes to this repository generally need to correspond to an [unassigned open issue marked as `status: accepting prs` on the issue tracker](https://github.com/tylersayshi/prototypey/issues?q=is%3Aissue+is%3Aopen+label%3A%22status%3A+accepting+prs%22+no%3Aassignee+). +If this is your first time contributing, consider searching for [unassigned issues that also have the `good first issue` label](https://github.com/tylersayshi/prototypey/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22+label%3A%22status%3A+accepting+prs%22+no%3Aassignee+). +If the issue you'd like to fix isn't found on the issue, see [Reporting Issues](#reporting-issues) for filing your own (please do!). + +#### Issue Claiming + +We don't use any kind of issue claiming system. +We've found in the past that they result in accidental ["licked cookie"](https://devblogs.microsoft.com/oldnewthing/20091201-00/?p=15843) situations where contributors claim an issue but run out of time or energy trying before sending a PR. + +If an unassigned issue has been marked as `status: accepting prs` and an open PR does not exist, feel free to send a PR. +Please don't post comments asking for permission or stating you will work on an issue. + +### Sending a Pull Request + +Once you've identified an open issue accepting PRs that doesn't yet have a PR sent, you're free to send a pull request. +Be sure to fill out the pull request template's requested information -- otherwise your PR will likely be closed. + +PRs are also expected to have a title that adheres to [conventional commits](https://www.conventionalcommits.org/en/v1.0.0). +Only PR titles need to be in that format, not individual commits. +Don't worry if you get this wrong: you can always change the PR title after sending it. +Check [previously merged PRs](https://github.com/tylersayshi/prototypey/pulls?q=is%3Apr+is%3Amerged+-label%3Adependencies+) for reference. + +#### Draft PRs + +If you don't think your PR is ready for review, [set it as a draft](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-stage-of-a-pull-request#converting-a-pull-request-to-a-draft). +Draft PRs won't be reviewed. + +#### Granular PRs + +Please keep pull requests single-purpose: in other words, don't attempt to solve multiple unrelated problems in one pull request. +Send one PR per area of concern. +Multi-purpose pull requests are harder and slower to review, block all changes from being merged until the whole pull request is reviewed, and are difficult to name well with semantic PR titles. + +#### Pull Request Reviews + +When a PR is not in draft, it's considered ready for review. +Please don't manually `@` tag anybody to request review. +A maintainer will look at it when they're next able to. + +PRs should have passing [GitHub status checks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/about-status-checks) before review is requested (unless there are explicit questions asked in the PR about any failures). + +#### Asking Questions + +If you need help and/or have a question, posting a comment in the PR is a great way to do so. +There's no need to tag anybody individually. +One of us will drop by and help when we can. + +Please post comments as [line comments](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/commenting-on-a-pull-request#adding-line-comments-to-a-pull-request) when possible, so that they can be threaded. +You can [resolve conversations](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/commenting-on-a-pull-request#resolving-conversations) on your own when you feel they're resolved - no need to comment explicitly and/or wait for a maintainer. + +#### Requested Changes + +After a maintainer reviews your PR, they may request changes on it. +Once you've made those changes, [re-request review on GitHub](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/about-pull-request-reviews#re-requesting-a-review). + +Please try not to force-push commits to PRs that have already been reviewed. +Doing so makes it harder to review the changes. +We squash merge all commits so there's no need to try to preserve Git history within a PR branch. + +Once you've addressed all our feedback by making code changes and/or started a followup discussion, [re-request review](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/about-pull-request-reviews#re-requesting-a-review) from each maintainer whose feedback you addressed. + +Once all feedback is addressed and the PR is approved, we'll ensure the branch is up to date with `main` and merge it for you. + +#### Post-Merge Recognition + +Once your PR is merged, if you haven't yet been added to the [_Contributors_ table in the README.md](../README.md#contributors) for its [type of contribution](https://allcontributors.org/docs/en/emoji-key "Allcontributors emoji key"), you should be soon. +Please do ping the maintainer who merged your PR if that doesn't happen within 24 hours - it was likely an oversight on our end! + +## Emojis & Appreciation + +If you made it all the way to the end, bravo dear user, we love you. +Please include an emoji in the bottom of your issues and PRs to signal to us that you did in fact read this file and are trying to conform to it as best as possible. +💖 is a good starter if you're not sure which to use. diff --git a/.github/DEVELOPMENT.md b/.github/DEVELOPMENT.md new file mode 100644 index 0000000..4e638ed --- /dev/null +++ b/.github/DEVELOPMENT.md @@ -0,0 +1,67 @@ +# Development + +After [forking the repo from GitHub](https://help.github.com/articles/fork-a-repo) and [installing pnpm](https://pnpm.io/installation): + +```shell +git clone https://github.com/(your-name-here)/prototypey +cd prototypey +pnpm install +``` + +## Building + +Run [**tsdown**](https://tsdown.dev) locally to build source files from `src/` into output files in `lib/`: + +```shell +pnpm build +``` + +Add `--watch` to run the builder in a watch mode that continuously cleans and recreates `lib/` as you save files: + +```shell +pnpm build --watch +``` + +## Formatting + +[Prettier](https://prettier.io) is used to format code. +It should be applied automatically when you save files in VS Code or make a Git commit. + +To manually reformat all files, you can run: + +```shell +pnpm format --write +``` + +## Linting + +This package includes several forms of linting to enforce consistent code quality and styling. +Each should be shown in VS Code, and can be run manually on the command-line: + +- `pnpm lint` ([ESLint](https://eslint.org) with [typescript-eslint](https://typescript-eslint.io)): Lints JavaScript and TypeScript source files + +Read the individual documentation for each linter to understand how it can be configured and used best. + +For example, ESLint can be run with `--fix` to auto-fix some lint rule complaints: + +```shell +pnpm run lint --fix +``` + +Note that you'll need to run `pnpm build` before `pnpm lint` so that lint rules which check the file system can pick up on any built files. + +## Type Checking + +You should be able to see suggestions from [TypeScript](https://typescriptlang.org) in your editor for all open files. + +However, it can be useful to run the TypeScript command-line (`tsc`) to type check all files in `src/`: + +```shell +pnpm tsc +``` + +Add `--watch` to keep the type checker running in a watch mode that updates the display as you save files: + +```shell +pnpm tsc --watch +``` diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..6542cd9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,9 @@ + + + + + + +## Overview + +... diff --git a/.github/ISSUE_TEMPLATE/01-bug.yml b/.github/ISSUE_TEMPLATE/01-bug.yml new file mode 100644 index 0000000..59f76f9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/01-bug.yml @@ -0,0 +1,37 @@ +body: + - attributes: + description: If any of these required steps are not taken, we may not be able to review your issue. Help us to help you! + label: Bug Report Checklist + options: + - label: I have tried restarting my IDE and the issue persists. + required: true + - label: I have pulled the latest `main` branch of the repository. + required: true + - label: I have [searched for related issues](https://github.com/tylersayshi/prototypey/issues?q=is%3Aissue) and found none that matched my issue. + required: true + type: checkboxes + - attributes: + description: What did you expect to happen? + label: Expected + type: textarea + validations: + required: true + - attributes: + description: What happened instead? + label: Actual + type: textarea + validations: + required: true + - attributes: + description: Any additional info you'd like to provide. + label: Additional Info + type: textarea + +description: Report a bug trying to run the code + +labels: + - "type: bug" + +name: 🐛 Bug + +title: "🐛 Bug: " diff --git a/.github/ISSUE_TEMPLATE/02-documentation.yml b/.github/ISSUE_TEMPLATE/02-documentation.yml new file mode 100644 index 0000000..0c632ad --- /dev/null +++ b/.github/ISSUE_TEMPLATE/02-documentation.yml @@ -0,0 +1,29 @@ +body: + - attributes: + description: If any of these required steps are not taken, we may not be able to review your issue. Help us to help you! + label: Documentation Report Checklist + options: + - label: I have pulled the latest `main` branch of the repository. + required: true + - label: I have [searched for related issues](https://github.com/tylersayshi/prototypey/issues?q=is%3Aissue) and found none that matched my issue. + required: true + type: checkboxes + - attributes: + description: What would you like to report? + label: Overview + type: textarea + validations: + required: true + - attributes: + description: Any additional info you'd like to provide. + label: Additional Info + type: textarea + +description: Report a typo or missing area of documentation + +labels: + - "area: documentation" + +name: 📝 Documentation + +title: "📝 Documentation: " diff --git a/.github/ISSUE_TEMPLATE/03-feature.yml b/.github/ISSUE_TEMPLATE/03-feature.yml new file mode 100644 index 0000000..38b02e2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/03-feature.yml @@ -0,0 +1,29 @@ +body: + - attributes: + description: If any of these required steps are not taken, we may not be able to review your issue. Help us to help you! + label: Feature Request Checklist + options: + - label: I have pulled the latest `main` branch of the repository. + required: true + - label: I have [searched for related issues](https://github.com/tylersayshi/prototypey/issues?q=is%3Aissue) and found none that matched my issue. + required: true + type: checkboxes + - attributes: + description: What did you expect to be able to do? + label: Overview + type: textarea + validations: + required: true + - attributes: + description: Any additional info you'd like to provide. + label: Additional Info + type: textarea + +description: Request that a new feature be added or an existing feature improved + +labels: + - "type: feature" + +name: 🚀 Feature + +title: "🚀 Feature: " diff --git a/.github/ISSUE_TEMPLATE/04-tooling.yml b/.github/ISSUE_TEMPLATE/04-tooling.yml new file mode 100644 index 0000000..c915d50 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/04-tooling.yml @@ -0,0 +1,31 @@ +body: + - attributes: + description: If any of these required steps are not taken, we may not be able to review your issue. Help us to help you! + label: Tooling Report Checklist + options: + - label: I have tried restarting my IDE and the issue persists. + required: true + - label: I have pulled the latest `main` branch of the repository. + required: true + - label: I have [searched for related issues](https://github.com/tylersayshi/prototypey/issues?q=is%3Aissue) and found none that matched my issue. + required: true + type: checkboxes + - attributes: + description: What did you expect to be able to do? + label: Overview + type: textarea + validations: + required: true + - attributes: + description: Any additional info you'd like to provide. + label: Additional Info + type: textarea + +description: Report a bug or request an enhancement in repository tooling + +labels: + - "area: tooling" + +name: 🛠 Tooling + +title: "🛠 Tooling: " diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..f237fc2 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,13 @@ + + +## PR Checklist + +- [ ] Addresses an existing open issue: fixes #000 +- [ ] That issue was marked as [`status: accepting prs`](https://github.com/tylersayshi/prototypey/issues?q=is%3Aopen+is%3Aissue+label%3A%22status%3A+accepting+prs%22) +- [ ] Steps in [CONTRIBUTING.md](https://github.com/tylersayshi/prototypey/blob/main/.github/CONTRIBUTING.md) were taken + +## Overview + + diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..ae58685 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,9 @@ +# Security Policy + +We take all security vulnerabilities seriously. +If you have a vulnerability or other security issues to disclose: + +- Thank you very much, please do! +- Please send them to us by emailing `hi@tylur.dev` + +We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions. diff --git a/.github/actions/prepare/action.yml b/.github/actions/prepare/action.yml new file mode 100644 index 0000000..ea7f2a6 --- /dev/null +++ b/.github/actions/prepare/action.yml @@ -0,0 +1,14 @@ +description: Prepares the repo for a typical CI job + +name: Prepare + +runs: + steps: + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + cache: pnpm + node-version: 24.3.0 + - run: pnpm install --frozen-lockfile + shell: bash + using: composite diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a2ee5bd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm build + - run: node lib/index.js + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm lint + prettier: + name: Prettier + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm format --list-different + type_check: + name: Type Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm tsc + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm test + +name: CI + +on: + pull_request: ~ + push: + branches: + - main diff --git a/.github/workflows/deno.yml b/.github/workflows/deno.yml deleted file mode 100644 index a959ad3..0000000 --- a/.github/workflows/deno.yml +++ /dev/null @@ -1,43 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -# This workflow will install Deno then run `deno lint` and `deno test`. -# For more information see: https://github.com/denoland/setup-deno - -name: Deno - -on: - push: - branches: ["main"] - pull_request: - branches: ["main"] - -permissions: - contents: read - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - name: Setup repo - uses: actions/checkout@v4 - - - name: Setup Deno - uses: denoland/setup-deno@v1 - with: - deno-version: v2.x - - - name: Verify formatting - run: deno fmt --check - - - name: Run linter - run: deno lint - - - name: Run type checker - run: deno check - - - name: Run tests - run: deno test -A diff --git a/.github/workflows/pr-review-requested.yml b/.github/workflows/pr-review-requested.yml new file mode 100644 index 0000000..e2e518c --- /dev/null +++ b/.github/workflows/pr-review-requested.yml @@ -0,0 +1,21 @@ +jobs: + pr_review_requested: + runs-on: ubuntu-latest + steps: + - uses: actions-ecosystem/action-remove-labels@v1 + with: + labels: "status: waiting for author" + - if: failure() + run: | + echo "Don't worry if the previous step failed." + echo "See https://github.com/actions-ecosystem/action-remove-labels/issues/221." + +name: PR Review Requested + +on: + pull_request_target: + types: + - review_requested + +permissions: + pull-requests: write diff --git a/.gitignore b/.gitignore index e69de29..626c4f3 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,2 @@ +/lib +/node_modules diff --git a/.husky/.gitignore b/.husky/.gitignore new file mode 100644 index 0000000..31354ec --- /dev/null +++ b/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..2312dc5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..f8eb0b3 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +/.husky +/lib +/pnpm-lock.yaml diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..41ada8d --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1 @@ +{ "$schema": "http://json.schemastore.org/prettierrc", "useTabs": true } diff --git a/.vscode/settings.json b/.vscode/settings.json index eea3bca..ff3414b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,3 @@ { - "editor.defaultFormatter": "denoland.vscode-deno", - "prettier.enable": false + "editor.defaultFormatter": "esbenp.prettier-vscode" } diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..08520a1 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,20 @@ +# MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index c7b6bda..3f042c7 100644 --- a/README.md +++ b/README.md @@ -30,13 +30,13 @@ those as the authoring and validation tools. ```typescript const profileNamespace = lx.namespace("app.bsky.actor.profile", { - main: lx.record({ - key: "self", - record: lx.object({ - displayName: lx.string({ maxLength: 64, maxGraphemes: 64 }), - description: lx.string({ maxLength: 256, maxGraphemes: 256 }), - }), - }), + main: lx.record({ + key: "self", + record: lx.object({ + displayName: lx.string({ maxLength: 64, maxGraphemes: 64 }), + description: lx.string({ maxLength: 256, maxGraphemes: 256 }), + }), + }), }); ``` @@ -44,28 +44,51 @@ const profileNamespace = lx.namespace("app.bsky.actor.profile", { ```json { - "lexicon": 1, - "id": "app.bsky.actor.profile", - "defs": { - "main": { - "type": "record", - "key": "self", - "record": { - "type": "object", - "properties": { - "displayName": { - "type": "string", - "maxLength": 64, - "maxGraphemes": 64 - }, - "description": { - "type": "string", - "maxLength": 256, - "maxGraphemes": 256 - } - } - } - } - } + "lexicon": 1, + "id": "app.bsky.actor.profile", + "defs": { + "main": { + "type": "record", + "key": "self", + "record": { + "type": "object", + "properties": { + "displayName": { + "type": "string", + "maxLength": 64, + "maxGraphemes": 64 + }, + "description": { + "type": "string", + "maxLength": 256, + "maxGraphemes": 256 + } + } + } + } + } } ``` + +--- + +

+ 🤝 Code of Conduct: Kept + 📝 License: MIT + 💪 TypeScript: Strict +

+ +## Usage + +tbd + +## Development + +See [`.github/CONTRIBUTING.md`](./.github/CONTRIBUTING.md), then +[`.github/DEVELOPMENT.md`](./.github/DEVELOPMENT.md). Thanks! 💖 + + + +> 💝 This package was templated with +> [`create-typescript-app`](https://github.com/JoshuaKGoldberg/create-typescript-app) +> using the [Bingo framework](https://create.bingo). diff --git a/deno.json b/deno.json deleted file mode 100644 index 1c76dcd..0000000 --- a/deno.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tasks": { - "dev": "deno run --watch main.ts" - }, - "imports": { - "@std/assert": "jsr:@std/assert@1" - }, - "compilerOptions": { - "exactOptionalPropertyTypes": true - } -} diff --git a/deno.lock b/deno.lock deleted file mode 100644 index 4fa95d0..0000000 --- a/deno.lock +++ /dev/null @@ -1,23 +0,0 @@ -{ - "version": "5", - "specifiers": { - "jsr:@std/assert@1": "1.0.14", - "jsr:@std/internal@^1.0.10": "1.0.10" - }, - "jsr": { - "@std/assert@1.0.14": { - "integrity": "68d0d4a43b365abc927f45a9b85c639ea18a9fab96ad92281e493e4ed84abaa4", - "dependencies": [ - "jsr:@std/internal" - ] - }, - "@std/internal@1.0.10": { - "integrity": "e3be62ce42cab0e177c27698e5d9800122f67b766a0bea6ca4867886cbde8cf7" - } - }, - "workspace": { - "dependencies": [ - "jsr:@std/assert@1" - ] - } -} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..ab8db4b --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,26 @@ +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + { ignores: ["lib", "node_modules", "pnpm-lock.yaml"] }, + { linterOptions: { reportUnusedDisableDirectives: "error" } }, + eslint.configs.recommended, + { + extends: [ + tseslint.configs.strictTypeChecked, + tseslint.configs.stylisticTypeChecked, + ], + files: ["**/*.{js,ts}"], + languageOptions: { + parserOptions: { + projectService: { allowDefaultProject: ["*.config.*s"] }, + }, + }, + }, + { + files: ["**/*.test.ts"], + rules: { + "@typescript-eslint/no-floating-promises": "off", + }, + }, +); diff --git a/package.json b/package.json new file mode 100644 index 0000000..dba209c --- /dev/null +++ b/package.json @@ -0,0 +1,48 @@ +{ + "name": "prototypey", + "version": "0.0.0", + "description": "A very lovely package. Hooray!", + "repository": { + "type": "git", + "url": "git+https://github.com/tylersayshi/prototypey.git" + }, + "license": "MIT", + "author": { + "name": "tylersayshi", + "email": "hi@tylur.dev" + }, + "type": "module", + "main": "lib/index.js", + "files": [ + "LICENSE.md", + "README.md", + "lib/", + "package.json" + ], + "scripts": { + "build": "tsdown", + "format": "prettier .", + "lint": "eslint . --max-warnings 0", + "test": "node --test", + "prepare": "husky", + "tsc": "tsc" + }, + "lint-staged": { + "*": "prettier --ignore-unknown --write" + }, + "devDependencies": { + "@eslint/js": "9.29.0", + "@types/node": "24.0.4", + "eslint": "9.29.0", + "husky": "9.1.7", + "lint-staged": "16.1.2", + "prettier": "3.6.1", + "tsdown": "0.12.7", + "typescript": "5.8.3", + "typescript-eslint": "8.35.0" + }, + "packageManager": "pnpm@10.4.0", + "engines": { + "node": ">=20.19.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..3d23795 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1807 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@eslint/js': + specifier: 9.29.0 + version: 9.29.0 + '@types/node': + specifier: 24.0.4 + version: 24.0.4 + eslint: + specifier: 9.29.0 + version: 9.29.0(jiti@2.6.1) + husky: + specifier: 9.1.7 + version: 9.1.7 + lint-staged: + specifier: 16.1.2 + version: 16.1.2 + prettier: + specifier: 3.6.1 + version: 3.6.1 + tsdown: + specifier: 0.12.7 + version: 0.12.7(typescript@5.8.3) + typescript: + specifier: 5.8.3 + version: 5.8.3 + typescript-eslint: + specifier: 8.35.0 + version: 8.35.0(eslint@9.29.0(jiti@2.6.1))(typescript@5.8.3) + +packages: + + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.4': + resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.28.4': + resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} + engines: {node: '>=6.9.0'} + + '@emnapi/core@1.5.0': + resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==} + + '@emnapi/runtime@1.5.0': + resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} + + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.20.1': + resolution: {integrity: sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.2.3': + resolution: {integrity: sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.14.0': + resolution: {integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.15.2': + resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.29.0': + resolution: {integrity: sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.3.5': + resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@0.2.12': + resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@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'} + + '@oxc-project/runtime@0.72.2': + resolution: {integrity: sha512-J2lsPDen2mFs3cOA1gIBd0wsHEhum2vTnuKIRwmj3HJJcIz/XgeNdzvgSOioIXOJgURIpcDaK05jwaDG1rhDwg==} + engines: {node: '>=6.9.0'} + + '@oxc-project/types@0.72.2': + resolution: {integrity: sha512-il5RF8AP85XC0CMjHF4cnVT9nT/v/ocm6qlZQpSiAR9qBbQMGkFKloBZwm7PcnOdiUX97yHgsKM7uDCCWCu3tg==} + + '@quansync/fs@0.1.5': + resolution: {integrity: sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==} + + '@rolldown/binding-darwin-arm64@1.0.0-beta.11-commit.f051675': + resolution: {integrity: sha512-Hlt/h+lOJ+ksC2wED2M9Hku/9CA2Hr17ENK82gNMmi3OqwcZLdZFqJDpASTli65wIOeT4p9rIUMdkfshCoJpYA==} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-beta.11-commit.f051675': + resolution: {integrity: sha512-Bnst+HBwhW2YrNybEiNf9TJkI1myDgXmiPBVIOS0apzrLCmByzei6PilTClOpTpNFYB+UviL3Ox2gKUmcgUjGw==} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-beta.11-commit.f051675': + resolution: {integrity: sha512-3jAxVmYDPc8vMZZOfZI1aokGB9cP6VNeU9XNCx0UJ6ShlSPK3qkAa0sWgueMhaQkgBVf8MOfGpjo47ohGd7QrA==} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.11-commit.f051675': + resolution: {integrity: sha512-TpUltUdvcsAf2WvXXD8AVc3BozvhgazJ2gJLXp4DVV2V82m26QelI373Bzx8d/4hB167EEIg4wWW/7GXB/ltoQ==} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.11-commit.f051675': + resolution: {integrity: sha512-eGvHnYQSdbdhsTdjdp/+83LrN81/7X9HD6y3jg7mEmdsicxEMEIt6CsP7tvYS/jn4489jgO/6mLxW/7Vg+B8pw==} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.11-commit.f051675': + resolution: {integrity: sha512-0NJZWXJls83FpBRzkTbGBsXXstaQLsfodnyeOghxbnNdsjn+B4dcNPpMK5V3QDsjC0pNjDLaDdzB2jWKlZbP/Q==} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.11-commit.f051675': + resolution: {integrity: sha512-9vXnu27r4zgS/BHP6RCLBOrJoV2xxtLYHT68IVpSOdCkBHGpf1oOJt6blv1y5NRRJBEfAFCvj5NmwSMhETF96w==} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.11-commit.f051675': + resolution: {integrity: sha512-e6tvsZbtHt4kzl82oCajOUxwIN8uMfjhuQ0qxIVRzPekRRjKEzyH9agYPW6toN0cnHpkhPsu51tyZKJOdUl7jg==} + cpu: [x64] + os: [linux] + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.11-commit.f051675': + resolution: {integrity: sha512-nBQVizPoUQiViANhWrOyihXNf2booP2iq3S396bI1tmHftdgUXWKa6yAoleJBgP0oF0idXpTPU82ciaROUcjpg==} + engines: {node: '>=14.21.3'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.11-commit.f051675': + resolution: {integrity: sha512-Rey/ECXKI/UEykrKfJX3oVAPXDH2k1p2BKzYGza0z3S2X5I3sTDOeBn2I0IQgyyf7U3+DCBhYjkDFnmSePrU/A==} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.11-commit.f051675': + resolution: {integrity: sha512-LtuMKJe6iFH4iV55dy+gDwZ9v23Tfxx5cd7ZAxvhYFGoVNSvarxAgl844BvFGReERCnLTGRvo85FUR6fDHQX+A==} + cpu: [ia32] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.11-commit.f051675': + resolution: {integrity: sha512-YY8UYfBm4dbWa4psgEPPD9T9X0nAvlYu0BOsQC5vDfCwzzU7IHT4jAfetvlQq+4+M6qWHSTr6v+/WX5EmlM1WA==} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-beta.11-commit.f051675': + resolution: {integrity: sha512-TAqMYehvpauLKz7v4TZOTUQNjxa5bUQWw2+51/+Zk3ItclBxgoSWhnZ31sXjdoX6le6OXdK2vZfV3KoyW/O/GA==} + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@24.0.4': + resolution: {integrity: sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==} + + '@typescript-eslint/eslint-plugin@8.35.0': + resolution: {integrity: sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.35.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/parser@8.35.0': + resolution: {integrity: sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/project-service@8.35.0': + resolution: {integrity: sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/scope-manager@8.35.0': + resolution: {integrity: sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.35.0': + resolution: {integrity: sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/type-utils@8.35.0': + resolution: {integrity: sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/types@8.35.0': + resolution: {integrity: sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.35.0': + resolution: {integrity: sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/utils@8.35.0': + resolution: {integrity: sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/visitor-keys@8.35.0': + resolution: {integrity: sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-escapes@7.1.1: + resolution: {integrity: sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==} + engines: {node: '>=18'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + ast-kit@2.1.3: + resolution: {integrity: sha512-TH+b3Lv6pUjy/Nu0m6A2JULtdzLpmqF9x1Dhj00ZoEiML8qvVA9j1flkzTKNYgdEhWrjDwtWNpyyCUbfQe514g==} + engines: {node: '>=20.19.0'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + birpc@2.6.1: + resolution: {integrity: sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-truncate@4.0.0: + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} + engines: {node: '>=18'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + commander@14.0.1: + resolution: {integrity: sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==} + engines: {node: '>=20'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} + + dts-resolver@2.1.2: + resolution: {integrity: sha512-xeXHBQkn2ISSXxbJWD828PFjtyg+/UrMDo7W4Ffcs7+YWCquxU8YjV1KoxuiL+eJ5pg3ll+bC6flVv61L3LKZg==} + engines: {node: '>=20.18.0'} + peerDependencies: + oxc-resolver: '>=11.0.0' + peerDependenciesMeta: + oxc-resolver: + optional: true + + emoji-regex@10.5.0: + resolution: {integrity: sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==} + + empathic@1.1.0: + resolution: {integrity: sha512-rsPft6CK3eHtrlp9Y5ALBb+hfK+DWnA4WFebbazxjWyx8vSm3rZeoM3z9irsjcqO3PYRzlfv27XIB4tz2DV7RA==} + engines: {node: '>=14'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + 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} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.29.0: + resolution: {integrity: sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + get-east-asian-width@1.4.0: + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + engines: {node: '>=18'} + + get-tsconfig@4.12.0: + resolution: {integrity: sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lint-staged@16.1.2: + resolution: {integrity: sha512-sQKw2Si2g9KUZNY3XNvRuDq4UJqpHwF0/FQzZR2M7I5MvtpWvibikCjUVJzZdGE0ByurEl3KQNvsGetd1ty1/Q==} + engines: {node: '>=20.17'} + hasBin: true + + listr2@8.3.3: + resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==} + engines: {node: '>=18.0.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nano-spawn@1.0.3: + resolution: {integrity: sha512-jtpsQDetTnvS2Ts1fiRdci5rx0VYws5jGyC+4IYOTnIQ/wwdf6JdomlHBwqC3bJYOvaKu0C2GSZ1A60anrYpaA==} + engines: {node: '>=20.17'} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.6.1: + resolution: {integrity: sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==} + engines: {node: '>=14'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rolldown-plugin-dts@0.13.14: + resolution: {integrity: sha512-wjNhHZz9dlN6PTIXyizB6u/mAg1wEFMW9yw7imEVe3CxHSRnNHVyycIX0yDEOVJfDNISLPbkCIPEpFpizy5+PQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + '@typescript/native-preview': '>=7.0.0-dev.20250601.1' + rolldown: ^1.0.0-beta.9 + typescript: ^5.0.0 + vue-tsc: ^2.2.0 || ^3.0.0 + peerDependenciesMeta: + '@typescript/native-preview': + optional: true + typescript: + optional: true + vue-tsc: + optional: true + + rolldown@1.0.0-beta.11-commit.f051675: + resolution: {integrity: sha512-g8MCVkvg2GnrrG+j+WplOTx1nAmjSwYOMSOQI0qfxf8D4NmYZqJuG3f85yWK64XXQv6pKcXZsfMkOPs9B6B52A==} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + tinyexec@1.0.1: + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tsdown@0.12.7: + resolution: {integrity: sha512-VJjVaqJfIQuQwtOoeuEJMOJUf3MPDrfX0X7OUNx3nq5pQeuIl3h58tmdbM1IZcu8Dn2j8NQjLh+5TXa0yPb9zg==} + engines: {node: '>=18.0.0'} + hasBin: true + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + publint: ^0.3.0 + typescript: ^5.0.0 + unplugin-lightningcss: ^0.4.0 + unplugin-unused: ^0.5.0 + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + publint: + optional: true + typescript: + optional: true + unplugin-lightningcss: + optional: true + unplugin-unused: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.35.0: + resolution: {integrity: sha512-uEnz70b7kBz6eg/j0Czy6K5NivaYopgxRjsnAJ2Fx5oTLo3wefTHIbL7AkQr1+7tJCRVpTs/wiM8JR/11Loq9A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + unconfig@7.3.3: + resolution: {integrity: sha512-QCkQoOnJF8L107gxfHL0uavn7WD9b3dpBcFX6HtfQYmjw2YzWxGuFQ0N0J6tE9oguCBJn9KOvfqYDCMPHIZrBA==} + + undici-types@7.8.0: + resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + yaml@2.8.1: + resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + engines: {node: '>= 14.6'} + hasBin: true + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@babel/generator@7.28.3': + dependencies: + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/parser@7.28.4': + dependencies: + '@babel/types': 7.28.4 + + '@babel/types@7.28.4': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@emnapi/core@1.5.0': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.5.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@eslint-community/eslint-utils@4.9.0(eslint@9.29.0(jiti@2.6.1))': + dependencies: + eslint: 9.29.0(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.20.1': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.2.3': {} + + '@eslint/core@0.14.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/core@0.15.2': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.29.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.3.5': + dependencies: + '@eslint/core': 0.15.2 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@0.2.12': + dependencies: + '@emnapi/core': 1.5.0 + '@emnapi/runtime': 1.5.0 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@oxc-project/runtime@0.72.2': {} + + '@oxc-project/types@0.72.2': {} + + '@quansync/fs@0.1.5': + dependencies: + quansync: 0.2.11 + + '@rolldown/binding-darwin-arm64@1.0.0-beta.11-commit.f051675': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-beta.11-commit.f051675': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-beta.11-commit.f051675': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.11-commit.f051675': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.11-commit.f051675': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.11-commit.f051675': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.11-commit.f051675': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.11-commit.f051675': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.11-commit.f051675': + dependencies: + '@napi-rs/wasm-runtime': 0.2.12 + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.11-commit.f051675': + optional: true + + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.11-commit.f051675': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.11-commit.f051675': + optional: true + + '@rolldown/pluginutils@1.0.0-beta.11-commit.f051675': {} + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@24.0.4': + dependencies: + undici-types: 7.8.0 + + '@typescript-eslint/eslint-plugin@8.35.0(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.29.0(jiti@2.6.1))(typescript@5.8.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.35.0(eslint@9.29.0(jiti@2.6.1))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.35.0 + '@typescript-eslint/type-utils': 8.35.0(eslint@9.29.0(jiti@2.6.1))(typescript@5.8.3) + '@typescript-eslint/utils': 8.35.0(eslint@9.29.0(jiti@2.6.1))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.35.0 + eslint: 9.29.0(jiti@2.6.1) + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.6.1))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.35.0 + '@typescript-eslint/types': 8.35.0 + '@typescript-eslint/typescript-estree': 8.35.0(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.35.0 + debug: 4.4.3 + eslint: 9.29.0(jiti@2.6.1) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.35.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.35.0(typescript@5.8.3) + '@typescript-eslint/types': 8.35.0 + debug: 4.4.3 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.35.0': + dependencies: + '@typescript-eslint/types': 8.35.0 + '@typescript-eslint/visitor-keys': 8.35.0 + + '@typescript-eslint/tsconfig-utils@8.35.0(typescript@5.8.3)': + dependencies: + typescript: 5.8.3 + + '@typescript-eslint/type-utils@8.35.0(eslint@9.29.0(jiti@2.6.1))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.35.0(typescript@5.8.3) + '@typescript-eslint/utils': 8.35.0(eslint@9.29.0(jiti@2.6.1))(typescript@5.8.3) + debug: 4.4.3 + eslint: 9.29.0(jiti@2.6.1) + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.35.0': {} + + '@typescript-eslint/typescript-estree@8.35.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/project-service': 8.35.0(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.35.0(typescript@5.8.3) + '@typescript-eslint/types': 8.35.0 + '@typescript-eslint/visitor-keys': 8.35.0 + debug: 4.4.3 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.3 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.35.0(eslint@9.29.0(jiti@2.6.1))(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.29.0(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.35.0 + '@typescript-eslint/types': 8.35.0 + '@typescript-eslint/typescript-estree': 8.35.0(typescript@5.8.3) + eslint: 9.29.0(jiti@2.6.1) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.35.0': + dependencies: + '@typescript-eslint/types': 8.35.0 + eslint-visitor-keys: 4.2.1 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + 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 + + ansi-escapes@7.1.1: + dependencies: + environment: 1.1.0 + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + ansis@4.2.0: {} + + argparse@2.0.1: {} + + ast-kit@2.1.3: + dependencies: + '@babel/parser': 7.28.4 + pathe: 2.0.3 + + balanced-match@1.0.2: {} + + birpc@2.6.1: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + cac@6.7.14: {} + + callsites@3.1.0: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-truncate@4.0.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 7.2.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colorette@2.0.20: {} + + commander@14.0.1: {} + + concat-map@0.0.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + defu@6.1.4: {} + + diff@8.0.2: {} + + dts-resolver@2.1.2: {} + + emoji-regex@10.5.0: {} + + empathic@1.1.0: {} + + environment@1.1.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.29.0(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.29.0(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.20.1 + '@eslint/config-helpers': 0.2.3 + '@eslint/core': 0.14.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.29.0 + '@eslint/plugin-kit': 0.3.5 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + eventemitter3@5.0.1: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + 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.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + get-east-asian-width@1.4.0: {} + + get-tsconfig@4.12.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + hookable@5.5.3: {} + + husky@9.1.7: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@4.0.0: {} + + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.4.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + isexe@2.0.0: {} + + jiti@2.6.1: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lint-staged@16.1.2: + dependencies: + chalk: 5.6.2 + commander: 14.0.1 + debug: 4.4.3 + lilconfig: 3.1.3 + listr2: 8.3.3 + micromatch: 4.0.8 + nano-spawn: 1.0.3 + pidtree: 0.6.0 + string-argv: 0.3.2 + yaml: 2.8.1 + transitivePeerDependencies: + - supports-color + + listr2@8.3.3: + dependencies: + cli-truncate: 4.0.0 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.2 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.1.1 + cli-cursor: 5.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.1.2 + wrap-ansi: 9.0.2 + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mimic-function@5.0.1: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + ms@2.1.3: {} + + nano-spawn@1.0.3: {} + + natural-compare@1.4.0: {} + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + pathe@2.0.3: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pidtree@0.6.0: {} + + prelude-ls@1.2.1: {} + + prettier@3.6.1: {} + + punycode@2.3.1: {} + + quansync@0.2.11: {} + + queue-microtask@1.2.3: {} + + readdirp@4.1.2: {} + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + + rolldown-plugin-dts@0.13.14(rolldown@1.0.0-beta.11-commit.f051675)(typescript@5.8.3): + dependencies: + '@babel/generator': 7.28.3 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + ast-kit: 2.1.3 + birpc: 2.6.1 + debug: 4.4.3 + dts-resolver: 2.1.2 + get-tsconfig: 4.12.0 + rolldown: 1.0.0-beta.11-commit.f051675 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - oxc-resolver + - supports-color + + rolldown@1.0.0-beta.11-commit.f051675: + dependencies: + '@oxc-project/runtime': 0.72.2 + '@oxc-project/types': 0.72.2 + '@rolldown/pluginutils': 1.0.0-beta.11-commit.f051675 + ansis: 4.2.0 + optionalDependencies: + '@rolldown/binding-darwin-arm64': 1.0.0-beta.11-commit.f051675 + '@rolldown/binding-darwin-x64': 1.0.0-beta.11-commit.f051675 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.11-commit.f051675 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.11-commit.f051675 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.11-commit.f051675 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.11-commit.f051675 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.11-commit.f051675 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.11-commit.f051675 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.11-commit.f051675 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.11-commit.f051675 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.11-commit.f051675 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.11-commit.f051675 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + semver@7.7.3: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + + slice-ansi@5.0.0: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 4.0.0 + + slice-ansi@7.1.2: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + string-argv@0.3.2: {} + + string-width@7.2.0: + dependencies: + emoji-regex: 10.5.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + tinyexec@1.0.1: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-api-utils@2.1.0(typescript@5.8.3): + dependencies: + typescript: 5.8.3 + + tsdown@0.12.7(typescript@5.8.3): + dependencies: + ansis: 4.2.0 + cac: 6.7.14 + chokidar: 4.0.3 + debug: 4.4.3 + diff: 8.0.2 + empathic: 1.1.0 + hookable: 5.5.3 + rolldown: 1.0.0-beta.11-commit.f051675 + rolldown-plugin-dts: 0.13.14(rolldown@1.0.0-beta.11-commit.f051675)(typescript@5.8.3) + semver: 7.7.3 + tinyexec: 1.0.1 + tinyglobby: 0.2.15 + unconfig: 7.3.3 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - '@typescript/native-preview' + - oxc-resolver + - supports-color + - vue-tsc + + tslib@2.8.1: + optional: true + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.35.0(eslint@9.29.0(jiti@2.6.1))(typescript@5.8.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.35.0(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.29.0(jiti@2.6.1))(typescript@5.8.3) + '@typescript-eslint/parser': 8.35.0(eslint@9.29.0(jiti@2.6.1))(typescript@5.8.3) + '@typescript-eslint/utils': 8.35.0(eslint@9.29.0(jiti@2.6.1))(typescript@5.8.3) + eslint: 9.29.0(jiti@2.6.1) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + typescript@5.8.3: {} + + unconfig@7.3.3: + dependencies: + '@quansync/fs': 0.1.5 + defu: 6.1.4 + jiti: 2.6.1 + quansync: 0.2.11 + + undici-types@7.8.0: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.1.2 + + yaml@2.8.1: {} + + yocto-queue@0.1.0: {} diff --git a/samples/actor-namespace.json b/samples/actor-namespace.json index e8bc28b..1a36bc4 100644 --- a/samples/actor-namespace.json +++ b/samples/actor-namespace.json @@ -1,103 +1,100 @@ { - "lexicon": 1, - "id": "app.bsky.actor.defs", - "defs": { - "profileViewBasic": { - "type": "object", - "properties": { - "did": { - "type": "string", - "required": true, - "format": "did" - }, - "handle": { - "type": "string", - "required": true, - "format": "handle" - }, - "displayName": { - "type": "string", - "maxGraphemes": 64, - "maxLength": 640 - }, - "pronouns": { - "type": "string" - }, - "avatar": { - "type": "string", - "format": "uri" - }, - "associated": { - "type": "ref", - "ref": "#profileAssociated" - }, - "viewer": { - "type": "ref", - "ref": "#viewerState" - }, - "labels": { - "type": "array", - "items": { - "type": "ref", - "ref": "com.atproto.label.defs#label" - } - }, - "createdAt": { - "type": "string", - "format": "datetime" - }, - "verification": { - "type": "ref", - "ref": "#verificationState" - }, - "status": { - "type": "ref", - "ref": "#statusView" - } - }, - "required": [ - "did", - "handle" - ] - }, - "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" - }, - "blockingByList": { - "type": "ref", - "ref": "app.bsky.graph.defs#listViewBasic" - }, - "following": { - "type": "string", - "format": "at-uri" - }, - "followedBy": { - "type": "string", - "format": "at-uri" - }, - "knownFollowers": { - "type": "ref", - "ref": "#knownFollowers" - }, - "activitySubscription": { - "type": "ref", - "ref": "app.bsky.notification.defs#activitySubscription" - } - } - } - } + "lexicon": 1, + "id": "app.bsky.actor.defs", + "defs": { + "profileViewBasic": { + "type": "object", + "properties": { + "did": { + "type": "string", + "required": true, + "format": "did" + }, + "handle": { + "type": "string", + "required": true, + "format": "handle" + }, + "displayName": { + "type": "string", + "maxGraphemes": 64, + "maxLength": 640 + }, + "pronouns": { + "type": "string" + }, + "avatar": { + "type": "string", + "format": "uri" + }, + "associated": { + "type": "ref", + "ref": "#profileAssociated" + }, + "viewer": { + "type": "ref", + "ref": "#viewerState" + }, + "labels": { + "type": "array", + "items": { + "type": "ref", + "ref": "com.atproto.label.defs#label" + } + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "verification": { + "type": "ref", + "ref": "#verificationState" + }, + "status": { + "type": "ref", + "ref": "#statusView" + } + }, + "required": ["did", "handle"] + }, + "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" + }, + "blockingByList": { + "type": "ref", + "ref": "app.bsky.graph.defs#listViewBasic" + }, + "following": { + "type": "string", + "format": "at-uri" + }, + "followedBy": { + "type": "string", + "format": "at-uri" + }, + "knownFollowers": { + "type": "ref", + "ref": "#knownFollowers" + }, + "activitySubscription": { + "type": "ref", + "ref": "app.bsky.notification.defs#activitySubscription" + } + } + } + } } diff --git a/samples/demo.json b/samples/demo.json new file mode 100644 index 0000000..da301dd --- /dev/null +++ b/samples/demo.json @@ -0,0 +1,25 @@ +{ + "lexicon": 1, + "id": "app.bsky.actor.profile", + "defs": { + "main": { + "type": "record", + "key": "self", + "record": { + "type": "object", + "properties": { + "displayName": { + "type": "string", + "maxLength": 64, + "maxGraphemes": 64 + }, + "description": { + "type": "string", + "maxLength": 256, + "maxGraphemes": 256 + } + } + } + } + } +} diff --git a/samples/generator-view.json b/samples/generator-view.json index baa4f5e..44e8e3a 100644 --- a/samples/generator-view.json +++ b/samples/generator-view.json @@ -1,83 +1,76 @@ { - "type": "object", - "properties": { - "uri": { - "type": "string", - "required": true, - "format": "at-uri" - }, - "cid": { - "type": "string", - "required": true, - "format": "cid" - }, - "did": { - "type": "string", - "required": true, - "format": "did" - }, - "creator": { - "type": "ref", - "ref": "app.bsky.actor.defs#profileView", - "required": true - }, - "displayName": { - "type": "string", - "required": true - }, - "description": { - "type": "string", - "maxGraphemes": 300, - "maxLength": 3000 - }, - "descriptionFacets": { - "type": "array", - "items": { - "type": "ref", - "ref": "app.bsky.richtext.facet" - } - }, - "avatar": { - "type": "string", - "format": "uri" - }, - "likeCount": { - "type": "integer", - "minimum": 0 - }, - "acceptsInteractions": { - "type": "boolean" - }, - "labels": { - "type": "array", - "items": { - "type": "ref", - "ref": "com.atproto.label.defs#label" - } - }, - "viewer": { - "type": "ref", - "ref": "#generatorViewerState" - }, - "contentMode": { - "type": "string", - "knownValues": [ - "app.bsky.feed.defs#contentModeUnspecified", - "app.bsky.feed.defs#contentModeVideo" - ] - }, - "indexedAt": { - "type": "string", - "required": true, - "format": "datetime" - } - }, - "required": [ - "uri", - "cid", - "did", - "creator", - "displayName", - "indexedAt" - ] + "type": "object", + "properties": { + "uri": { + "type": "string", + "required": true, + "format": "at-uri" + }, + "cid": { + "type": "string", + "required": true, + "format": "cid" + }, + "did": { + "type": "string", + "required": true, + "format": "did" + }, + "creator": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileView", + "required": true + }, + "displayName": { + "type": "string", + "required": true + }, + "description": { + "type": "string", + "maxGraphemes": 300, + "maxLength": 3000 + }, + "descriptionFacets": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.richtext.facet" + } + }, + "avatar": { + "type": "string", + "format": "uri" + }, + "likeCount": { + "type": "integer", + "minimum": 0 + }, + "acceptsInteractions": { + "type": "boolean" + }, + "labels": { + "type": "array", + "items": { + "type": "ref", + "ref": "com.atproto.label.defs#label" + } + }, + "viewer": { + "type": "ref", + "ref": "#generatorViewerState" + }, + "contentMode": { + "type": "string", + "knownValues": [ + "app.bsky.feed.defs#contentModeUnspecified", + "app.bsky.feed.defs#contentModeVideo" + ] + }, + "indexedAt": { + "type": "string", + "required": true, + "format": "datetime" + } + }, + "required": ["uri", "cid", "did", "creator", "displayName", "indexedAt"] } diff --git a/samples/post-view.json b/samples/post-view.json index f428f23..b036ca9 100644 --- a/samples/post-view.json +++ b/samples/post-view.json @@ -1,76 +1,70 @@ { - "type": "object", - "properties": { - "uri": { - "type": "string", - "required": true, - "format": "at-uri" - }, - "cid": { - "type": "string", - "required": true, - "format": "cid" - }, - "author": { - "type": "ref", - "ref": "app.bsky.actor.defs#profileViewBasic", - "required": true - }, - "record": { - "type": "unknown", - "required": true - }, - "embed": { - "type": "union", - "refs": [ - "app.bsky.embed.images#view", - "app.bsky.embed.video#view", - "app.bsky.embed.external#view", - "app.bsky.embed.record#view", - "app.bsky.embed.recordWithMedia#view" - ] - }, - "bookmarkCount": { - "type": "integer" - }, - "replyCount": { - "type": "integer" - }, - "repostCount": { - "type": "integer" - }, - "likeCount": { - "type": "integer" - }, - "quoteCount": { - "type": "integer" - }, - "indexedAt": { - "type": "string", - "required": true, - "format": "datetime" - }, - "viewer": { - "type": "ref", - "ref": "#viewerState" - }, - "labels": { - "type": "array", - "items": { - "type": "ref", - "ref": "com.atproto.label.defs#label" - } - }, - "threadgate": { - "type": "ref", - "ref": "#threadgateView" - } - }, - "required": [ - "uri", - "cid", - "author", - "record", - "indexedAt" - ] + "type": "object", + "properties": { + "uri": { + "type": "string", + "required": true, + "format": "at-uri" + }, + "cid": { + "type": "string", + "required": true, + "format": "cid" + }, + "author": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileViewBasic", + "required": true + }, + "record": { + "type": "unknown", + "required": true + }, + "embed": { + "type": "union", + "refs": [ + "app.bsky.embed.images#view", + "app.bsky.embed.video#view", + "app.bsky.embed.external#view", + "app.bsky.embed.record#view", + "app.bsky.embed.recordWithMedia#view" + ] + }, + "bookmarkCount": { + "type": "integer" + }, + "replyCount": { + "type": "integer" + }, + "repostCount": { + "type": "integer" + }, + "likeCount": { + "type": "integer" + }, + "quoteCount": { + "type": "integer" + }, + "indexedAt": { + "type": "string", + "required": true, + "format": "datetime" + }, + "viewer": { + "type": "ref", + "ref": "#viewerState" + }, + "labels": { + "type": "array", + "items": { + "type": "ref", + "ref": "com.atproto.label.defs#label" + } + }, + "threadgate": { + "type": "ref", + "ref": "#threadgateView" + } + }, + "required": ["uri", "cid", "author", "record", "indexedAt"] } diff --git a/samples/primitives-array.json b/samples/primitives-array.json index 103c58b..c2bf335 100644 --- a/samples/primitives-array.json +++ b/samples/primitives-array.json @@ -1,9 +1,9 @@ { - "type": "array", - "items": { - "type": "string" - }, - "minLength": 1, - "maxLength": 10, - "required": true + "type": "array", + "items": { + "type": "string" + }, + "minLength": 1, + "maxLength": 10, + "required": true } diff --git a/samples/primitives-blob.json b/samples/primitives-blob.json index 17dbbd2..48730e9 100644 --- a/samples/primitives-blob.json +++ b/samples/primitives-blob.json @@ -1,8 +1,5 @@ { - "type": "blob", - "accept": [ - "image/png", - "image/jpeg" - ], - "maxSize": 5000000 + "type": "blob", + "accept": ["image/png", "image/jpeg"], + "maxSize": 5000000 } diff --git a/samples/primitives-token.json b/samples/primitives-token.json index fa92cbb..6d2ece7 100644 --- a/samples/primitives-token.json +++ b/samples/primitives-token.json @@ -1,4 +1,4 @@ { - "type": "token", - "description": "Request that less content like the given feed item be shown in the feed" + "type": "token", + "description": "Request that less content like the given feed item be shown in the feed" } diff --git a/samples/procedure-create-post.json b/samples/procedure-create-post.json index b1d2221..0e781be 100644 --- a/samples/procedure-create-post.json +++ b/samples/procedure-create-post.json @@ -1,61 +1,54 @@ { - "type": "procedure", - "description": "Create a post", - "input": { - "encoding": "application/json", - "schema": { - "type": "object", - "properties": { - "repo": { - "type": "string", - "required": true - }, - "collection": { - "type": "string", - "required": true - }, - "record": { - "type": "unknown", - "required": true - }, - "validate": { - "type": "boolean", - "default": true - } - }, - "required": [ - "repo", - "collection", - "record" - ] - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "properties": { - "uri": { - "type": "string", - "required": true - }, - "cid": { - "type": "string", - "required": true - } - }, - "required": [ - "uri", - "cid" - ] - } - }, - "errors": [ - { - "name": "InvalidSwap" - }, - { - "name": "InvalidRecord" - } - ] + "type": "procedure", + "description": "Create a post", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": { + "repo": { + "type": "string", + "required": true + }, + "collection": { + "type": "string", + "required": true + }, + "record": { + "type": "unknown", + "required": true + }, + "validate": { + "type": "boolean", + "default": true + } + }, + "required": ["repo", "collection", "record"] + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": { + "uri": { + "type": "string", + "required": true + }, + "cid": { + "type": "string", + "required": true + } + }, + "required": ["uri", "cid"] + } + }, + "errors": [ + { + "name": "InvalidSwap" + }, + { + "name": "InvalidRecord" + } + ] } diff --git a/samples/profile-namespace.json b/samples/profile-namespace.json index 035c658..da301dd 100644 --- a/samples/profile-namespace.json +++ b/samples/profile-namespace.json @@ -1,25 +1,25 @@ { - "lexicon": 1, - "id": "app.bsky.actor.profile", - "defs": { - "main": { - "type": "record", - "key": "self", - "record": { - "type": "object", - "properties": { - "displayName": { - "type": "string", - "maxLength": 64, - "maxGraphemes": 64 - }, - "description": { - "type": "string", - "maxLength": 256, - "maxGraphemes": 256 - } - } - } - } - } + "lexicon": 1, + "id": "app.bsky.actor.profile", + "defs": { + "main": { + "type": "record", + "key": "self", + "record": { + "type": "object", + "properties": { + "displayName": { + "type": "string", + "maxLength": 64, + "maxGraphemes": 64 + }, + "description": { + "type": "string", + "maxLength": 256, + "maxGraphemes": 256 + } + } + } + } + } } diff --git a/samples/profile-view-basic.json b/samples/profile-view-basic.json index d7f56a9..3aad2e8 100644 --- a/samples/profile-view-basic.json +++ b/samples/profile-view-basic.json @@ -1,58 +1,55 @@ { - "type": "object", - "properties": { - "did": { - "type": "string", - "required": true, - "format": "did" - }, - "handle": { - "type": "string", - "required": true, - "format": "handle" - }, - "displayName": { - "type": "string", - "maxGraphemes": 64, - "maxLength": 640 - }, - "pronouns": { - "type": "string" - }, - "avatar": { - "type": "string", - "format": "uri" - }, - "associated": { - "type": "ref", - "ref": "#profileAssociated" - }, - "viewer": { - "type": "ref", - "ref": "#viewerState" - }, - "labels": { - "type": "array", - "items": { - "type": "ref", - "ref": "com.atproto.label.defs#label" - } - }, - "createdAt": { - "type": "string", - "format": "datetime" - }, - "verification": { - "type": "ref", - "ref": "#verificationState" - }, - "status": { - "type": "ref", - "ref": "#statusView" - } - }, - "required": [ - "did", - "handle" - ] + "type": "object", + "properties": { + "did": { + "type": "string", + "required": true, + "format": "did" + }, + "handle": { + "type": "string", + "required": true, + "format": "handle" + }, + "displayName": { + "type": "string", + "maxGraphemes": 64, + "maxLength": 640 + }, + "pronouns": { + "type": "string" + }, + "avatar": { + "type": "string", + "format": "uri" + }, + "associated": { + "type": "ref", + "ref": "#profileAssociated" + }, + "viewer": { + "type": "ref", + "ref": "#viewerState" + }, + "labels": { + "type": "array", + "items": { + "type": "ref", + "ref": "com.atproto.label.defs#label" + } + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "verification": { + "type": "ref", + "ref": "#verificationState" + }, + "status": { + "type": "ref", + "ref": "#statusView" + } + }, + "required": ["did", "handle"] } diff --git a/samples/query-search-posts.json b/samples/query-search-posts.json index 12e2512..5b86e84 100644 --- a/samples/query-search-posts.json +++ b/samples/query-search-posts.json @@ -1,63 +1,56 @@ { - "type": "query", - "description": "Find posts matching search criteria", - "parameters": { - "type": "params", - "properties": { - "q": { - "type": "string", - "required": true - }, - "sort": { - "type": "string", - "enum": [ - "top", - "latest" - ], - "default": "latest" - }, - "limit": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 25 - }, - "cursor": { - "type": "string" - } - }, - "required": [ - "q" - ] - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "properties": { - "cursor": { - "type": "string" - }, - "hitsTotal": { - "type": "integer" - }, - "posts": { - "type": "array", - "items": { - "type": "ref", - "ref": "app.bsky.feed.defs#postView" - }, - "required": true - } - }, - "required": [ - "posts" - ] - } - }, - "errors": [ - { - "name": "BadQueryString" - } - ] + "type": "query", + "description": "Find posts matching search criteria", + "parameters": { + "type": "params", + "properties": { + "q": { + "type": "string", + "required": true + }, + "sort": { + "type": "string", + "enum": ["top", "latest"], + "default": "latest" + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + }, + "cursor": { + "type": "string" + } + }, + "required": ["q"] + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": { + "cursor": { + "type": "string" + }, + "hitsTotal": { + "type": "integer" + }, + "posts": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.feed.defs#postView" + }, + "required": true + } + }, + "required": ["posts"] + } + }, + "errors": [ + { + "name": "BadQueryString" + } + ] } diff --git a/samples/subscription-repos.json b/samples/subscription-repos.json index 52c581f..151c8f4 100644 --- a/samples/subscription-repos.json +++ b/samples/subscription-repos.json @@ -1,35 +1,35 @@ { - "type": "subscription", - "description": "Repository event stream, aka Firehose endpoint", - "parameters": { - "type": "params", - "properties": { - "cursor": { - "type": "integer" - } - } - }, - "message": { - "description": "Represents an update of repository state", - "schema": { - "type": "union", - "refs": [ - "#commit", - "#identity", - "#account", - "#handle", - "#migrate", - "#tombstone", - "#info" - ] - } - }, - "errors": [ - { - "name": "FutureCursor" - }, - { - "name": "ConsumerTooSlow" - } - ] + "type": "subscription", + "description": "Repository event stream, aka Firehose endpoint", + "parameters": { + "type": "params", + "properties": { + "cursor": { + "type": "integer" + } + } + }, + "message": { + "description": "Represents an update of repository state", + "schema": { + "type": "union", + "refs": [ + "#commit", + "#identity", + "#account", + "#handle", + "#migrate", + "#tombstone", + "#info" + ] + } + }, + "errors": [ + { + "name": "FutureCursor" + }, + { + "name": "ConsumerTooSlow" + } + ] } diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..5fcd5f7 --- /dev/null +++ b/src/index.ts @@ -0,0 +1 @@ +export * from "./lib.ts"; diff --git a/src/infer.ts b/src/infer.ts index 3a4982e..3e1fa5d 100644 --- a/src/infer.ts +++ b/src/infer.ts @@ -1,20 +1,35 @@ -// deno-lint-ignore-file ban-types -type InferType = T extends { type: "record" } ? InferRecord - : T extends { type: "object" } ? InferObject - : T extends { type: "array" } ? InferArray - : T extends { type: "params" } ? InferParams - : T extends { type: "union" } ? InferUnion - : T extends { type: "token" } ? InferToken - : T extends { type: "ref" } ? InferRef - : T extends { type: "unknown" } ? unknown - : T extends { type: "null" } ? null - : T extends { type: "boolean" } ? boolean - : T extends { type: "integer" } ? number - : T extends { type: "string" } ? string - : T extends { type: "bytes" } ? Uint8Array - : T extends { type: "cid-link" } ? string - : T extends { type: "blob" } ? Blob - : never; +/* eslint-disable @typescript-eslint/no-empty-object-type */ +type InferType = T extends { type: "record" } + ? InferRecord + : T extends { type: "object" } + ? InferObject + : T extends { type: "array" } + ? InferArray + : T extends { type: "params" } + ? InferParams + : T extends { type: "union" } + ? InferUnion + : T extends { type: "token" } + ? InferToken + : T extends { type: "ref" } + ? InferRef + : T extends { type: "unknown" } + ? unknown + : T extends { type: "null" } + ? null + : T extends { type: "boolean" } + ? boolean + : T extends { type: "integer" } + ? number + : T extends { type: "string" } + ? string + : T extends { type: "bytes" } + ? Uint8Array + : T extends { type: "cid-link" } + ? string + : T extends { type: "blob" } + ? Blob + : never; type InferToken = T extends { enum: readonly (infer U)[] } ? U : string; @@ -22,110 +37,66 @@ type GetRequired = T extends { required: readonly (infer R)[] } ? R : never; type GetNullable = T extends { nullable: readonly (infer N)[] } ? N : never; type InferObject< - T, - Nullable extends string = GetNullable & string, - Required extends string = GetRequired & string, - NullableAndRequired extends string = Required & Nullable & string, - Normal extends string = "properties" extends keyof T - ? Exclude & string - : never, + T, + Nullable extends string = GetNullable & string, + Required extends string = GetRequired & string, + NullableAndRequired extends string = Required & Nullable & string, + Normal extends string = "properties" extends keyof T + ? Exclude & string + : never, > = Prettify< - T extends { properties: infer P } ? - & { - -readonly [ - K in Normal - ]?: InferType< - P[K & keyof P] - >; - } - & ({ - -readonly [K in Exclude]-?: InferType< - P[K & keyof P] - >; - }) - & ({ - -readonly [K in Exclude]?: - | InferType< - P[K & keyof P] - > - | null; - }) - & ({ - -readonly [K in NullableAndRequired]: - | InferType< - P[K & keyof P] - > - | null; - }) - : {} + T extends { properties: infer P } + ? { + -readonly [K in Normal]?: InferType; + } & { + -readonly [K in Exclude]-?: InferType< + P[K & keyof P] + >; + } & { + -readonly [K in Exclude]?: InferType< + P[K & keyof P] + > | null; + } & { + -readonly [K in NullableAndRequired]: InferType | null; + } + : {} >; -type InferArray = T extends { items: infer Items } ? InferType[] - : never[]; +type InferArray = T extends { items: infer Items } + ? InferType[] + : never[]; type InferUnion = T extends { refs: readonly (infer R)[] } - ? R extends string ? { $type: R; [key: string]: unknown } : never - : never; + ? R extends string + ? { $type: R; [key: string]: unknown } + : never + : never; type InferRef = T extends { ref: infer R } - ? R extends string ? { $type: R; [key: string]: unknown } : unknown - : unknown; + ? R extends string + ? { $type: R; [key: string]: unknown } + : unknown + : unknown; -type InferParams = T extends { properties: infer P } ? InferObject - : never; +type InferParams = T extends { properties: infer P } + ? InferObject

+ : never; type InferRecord = T extends { record: infer R } - ? R extends { type: "object" } ? InferObject - : R extends { type: "union" } ? InferUnion - : unknown - : unknown; + ? R extends { type: "object" } + ? InferObject + : R extends { type: "union" } + ? InferUnion + : unknown + : unknown; -type Prettify = - & { - [K in keyof T]: T[K]; - } - & {}; +type Prettify = { + [K in keyof T]: T[K]; +} & {}; -export type InferDefs> = Prettify< - { - -readonly [K in keyof T]: InferType; - } ->; - -type InferNS }> = - InferDefs; - -const schema = { - "lexicon": 1, - "id": "app.bsky.actor.profile", - "defs": { - "main": { - "type": "record", - "key": "self", - "record": { - "type": "object", - "properties": { - "displayName": { - "type": "string", - "maxLength": 64, - "maxGraphemes": 64, - }, - "description": { - "type": "string", - "maxLength": 256, - "maxGraphemes": 256, - }, - }, - }, - }, - }, -} as const; - -type Schema = InferNS; - -const main: Schema["main"] = { - displayName: "blob", - description: "I am a blob dude", -}; +export type InferDefs> = Prettify<{ + -readonly [K in keyof T]: InferType; +}>; -console.log(`hi ${main.displayName}`); +export type InferNS }> = + InferDefs; diff --git a/src/lib.ts b/src/lib.ts index 88b067b..e22798c 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -1,38 +1,38 @@ // deno-lint-ignore-file ban-types /** @see https://atproto.com/specs/lexicon#overview-of-types */ type LexiconType = - // Concrete types - | "null" - | "boolean" - | "integer" - | "string" - | "bytes" - | "cid-link" - | "blob" - // Container types - | "array" - | "object" - | "params" - // Meta types - | "token" - | "ref" - | "union" - | "unknown" - // Primary types - | "record" - | "query" - | "procedure" - | "subscription"; + // Concrete types + | "null" + | "boolean" + | "integer" + | "string" + | "bytes" + | "cid-link" + | "blob" + // Container types + | "array" + | "object" + | "params" + // Meta types + | "token" + | "ref" + | "union" + | "unknown" + // Primary types + | "record" + | "query" + | "procedure" + | "subscription"; /** * Common options available for lexicon items. * @see https://atproto.com/specs/lexicon#string-formats */ interface LexiconItemCommonOptions { - /** Indicates this field must be provided */ - required?: boolean; - /** Indicates this field can be explicitly set to null */ - nullable?: boolean; + /** Indicates this field must be provided */ + required?: boolean; + /** Indicates this field can be explicitly set to null */ + nullable?: boolean; } /** @@ -40,28 +40,26 @@ interface LexiconItemCommonOptions { * @see https://atproto.com/specs/lexicon#overview-of-types */ interface LexiconItem extends LexiconItemCommonOptions { - type: LexiconType; + type: LexiconType; } /** * Definition in a lexicon namespace. * @see https://atproto.com/specs/lexicon#lexicon-document */ -type Def = { - type: LexiconType; -}; +interface Def { + type: LexiconType; +} /** * Lexicon namespace document structure. * @see https://atproto.com/specs/lexicon#lexicon-document */ interface LexiconNamespace { - /** Namespaced identifier (NSID) for this lexicon */ - id: string; - /** Named definitions within this namespace */ - defs: { - [name: string]: Def; - }; + /** Namespaced identifier (NSID) for this lexicon */ + id: string; + /** Named definitions within this namespace */ + defs: Record; } /** @@ -69,38 +67,38 @@ interface LexiconNamespace { * @see https://atproto.com/specs/lexicon#string */ interface StringOptions extends LexiconItemCommonOptions { - /** - * Semantic string format constraint. - * @see https://atproto.com/specs/lexicon#string-formats - */ - format?: - | "at-identifier" // Handle or DID - | "at-uri" // AT Protocol URI - | "cid" // Content Identifier - | "datetime" // Timestamp (UTC, ISO 8601) - | "did" // Decentralized Identifier - | "handle" // User handle identifier - | "nsid" // Namespaced Identifier - | "tid" // Timestamp Identifier - | "record-key" // Repository record key - | "uri" // Generic URI - | "language"; // IETF BCP 47 language tag - /** Maximum string length in bytes */ - maxLength?: number; - /** Minimum string length in bytes */ - minLength?: number; - /** Maximum string length in Unicode graphemes */ - maxGraphemes?: number; - /** Minimum string length in Unicode graphemes */ - minGraphemes?: number; - /** Hints at expected values, not enforced */ - knownValues?: string[]; - /** Restricts to an exact set of string values */ - enum?: string[]; - /** Default value if not provided */ - default?: string; - /** Fixed, unchangeable value */ - const?: string; + /** + * Semantic string format constraint. + * @see https://atproto.com/specs/lexicon#string-formats + */ + format?: + | "at-identifier" // Handle or DID + | "at-uri" // AT Protocol URI + | "cid" // Content Identifier + | "datetime" // Timestamp (UTC, ISO 8601) + | "did" // Decentralized Identifier + | "handle" // User handle identifier + | "nsid" // Namespaced Identifier + | "tid" // Timestamp Identifier + | "record-key" // Repository record key + | "uri" // Generic URI + | "language"; // IETF BCP 47 language tag + /** Maximum string length in bytes */ + maxLength?: number; + /** Minimum string length in bytes */ + minLength?: number; + /** Maximum string length in Unicode graphemes */ + maxGraphemes?: number; + /** Minimum string length in Unicode graphemes */ + minGraphemes?: number; + /** Hints at expected values, not enforced */ + knownValues?: string[]; + /** Restricts to an exact set of string values */ + enum?: string[]; + /** Default value if not provided */ + default?: string; + /** Fixed, unchangeable value */ + const?: string; } /** @@ -108,10 +106,10 @@ interface StringOptions extends LexiconItemCommonOptions { * @see https://atproto.com/specs/lexicon#boolean */ interface BooleanOptions extends LexiconItemCommonOptions { - /** Default value if not provided */ - default?: boolean; - /** Fixed, unchangeable value */ - const?: boolean; + /** Default value if not provided */ + default?: boolean; + /** Fixed, unchangeable value */ + const?: boolean; } /** @@ -119,16 +117,16 @@ interface BooleanOptions extends LexiconItemCommonOptions { * @see https://atproto.com/specs/lexicon#integer */ interface IntegerOptions extends LexiconItemCommonOptions { - /** Minimum allowed value (inclusive) */ - minimum?: number; - /** Maximum allowed value (inclusive) */ - maximum?: number; - /** Restricts to an exact set of integer values */ - enum?: number[]; - /** Default value if not provided */ - default?: number; - /** Fixed, unchangeable value */ - const?: number; + /** Minimum allowed value (inclusive) */ + minimum?: number; + /** Maximum allowed value (inclusive) */ + maximum?: number; + /** Restricts to an exact set of integer values */ + enum?: number[]; + /** Default value if not provided */ + default?: number; + /** Fixed, unchangeable value */ + const?: number; } /** @@ -136,10 +134,10 @@ interface IntegerOptions extends LexiconItemCommonOptions { * @see https://atproto.com/specs/lexicon#bytes */ interface BytesOptions extends LexiconItemCommonOptions { - /** Minimum byte array length */ - minLength?: number; - /** Maximum byte array length */ - maxLength?: number; + /** Minimum byte array length */ + minLength?: number; + /** Maximum byte array length */ + maxLength?: number; } /** @@ -147,10 +145,10 @@ interface BytesOptions extends LexiconItemCommonOptions { * @see https://atproto.com/specs/lexicon#blob */ interface BlobOptions extends LexiconItemCommonOptions { - /** Allowed MIME types (e.g., ["image/png", "image/jpeg"]) */ - accept?: string[]; - /** Maximum blob size in bytes */ - maxSize?: number; + /** Allowed MIME types (e.g., ["image/png", "image/jpeg"]) */ + accept?: string[]; + /** Maximum blob size in bytes */ + maxSize?: number; } /** @@ -158,10 +156,10 @@ interface BlobOptions extends LexiconItemCommonOptions { * @see https://atproto.com/specs/lexicon#array */ interface ArrayOptions extends LexiconItemCommonOptions { - /** Minimum array length */ - minLength?: number; - /** Maximum array length */ - maxLength?: number; + /** Minimum array length */ + minLength?: number; + /** Maximum array length */ + maxLength?: number; } /** @@ -169,12 +167,12 @@ interface ArrayOptions extends LexiconItemCommonOptions { * @see https://atproto.com/specs/lexicon#record */ interface RecordOptions { - /** Record key strategy: "self" for self-describing or "tid" for timestamp IDs */ - key: "self" | "tid"; - /** Object schema defining the record structure */ - record: { type: "object" }; - /** Human-readable description */ - description?: string; + /** Record key strategy: "self" for self-describing or "tid" for timestamp IDs */ + key: "self" | "tid"; + /** Object schema defining the record structure */ + record: { type: "object" }; + /** Human-readable description */ + description?: string; } /** @@ -182,50 +180,46 @@ interface RecordOptions { * @see https://atproto.com/specs/lexicon#union */ interface UnionOptions extends LexiconItemCommonOptions { - /** If true, only listed refs are allowed; if false, additional types may be added */ - closed?: boolean; + /** If true, only listed refs are allowed; if false, additional types may be added */ + closed?: boolean; } /** * Map of property names to their lexicon item definitions. * @see https://atproto.com/specs/lexicon#object */ -interface ObjectProperties { - [key: string]: LexiconItem; -} +type ObjectProperties = Record; /** * Resulting object schema with required and nullable fields extracted. * @see https://atproto.com/specs/lexicon#object */ interface ObjectResult { - type: "object"; - /** Property definitions */ - properties: T; - /** List of required property names */ - required?: string[]; - /** List of nullable property names */ - nullable?: string[]; + type: "object"; + /** Property definitions */ + properties: T; + /** List of required property names */ + required?: string[]; + /** List of nullable property names */ + nullable?: string[]; } /** * Map of parameter names to their lexicon item definitions. * @see https://atproto.com/specs/lexicon#params */ -interface ParamsProperties { - [key: string]: LexiconItem; -} +type ParamsProperties = Record; /** * Resulting params schema with required fields extracted. * @see https://atproto.com/specs/lexicon#params */ interface ParamsResult { - type: "params"; - /** Parameter definitions */ - properties: T; - /** List of required parameter names */ - required?: string[]; + type: "params"; + /** Parameter definitions */ + properties: T; + /** List of required parameter names */ + required?: string[]; } /** @@ -233,12 +227,12 @@ interface ParamsResult { * @see https://atproto.com/specs/lexicon#http-endpoints */ interface BodySchema { - /** MIME type encoding (typically "application/json") */ - encoding: "application/json" | (string & {}); - /** Human-readable description */ - description?: string; - /** Object schema defining the body structure */ - schema?: ObjectResult; + /** MIME type encoding (typically "application/json") */ + encoding: "application/json" | (string & {}); + /** Human-readable description */ + description?: string; + /** Object schema defining the body structure */ + schema?: ObjectResult; } /** @@ -246,10 +240,10 @@ interface BodySchema { * @see https://atproto.com/specs/lexicon#http-endpoints */ interface ErrorDef { - /** Error name/code */ - name: string; - /** Human-readable error description */ - description?: string; + /** Error name/code */ + name: string; + /** Human-readable error description */ + description?: string; } /** @@ -257,14 +251,14 @@ interface ErrorDef { * @see https://atproto.com/specs/lexicon#query */ interface QueryOptions { - /** Human-readable description */ - description?: string; - /** Query string parameters */ - parameters?: ParamsResult; - /** Response body schema */ - output?: BodySchema; - /** Possible error responses */ - errors?: ErrorDef[]; + /** Human-readable description */ + description?: string; + /** Query string parameters */ + parameters?: ParamsResult; + /** Response body schema */ + output?: BodySchema; + /** Possible error responses */ + errors?: ErrorDef[]; } /** @@ -272,16 +266,16 @@ interface QueryOptions { * @see https://atproto.com/specs/lexicon#procedure */ interface ProcedureOptions { - /** Human-readable description */ - description?: string; - /** Query string parameters */ - parameters?: ParamsResult; - /** Request body schema */ - input?: BodySchema; - /** Response body schema */ - output?: BodySchema; - /** Possible error responses */ - errors?: ErrorDef[]; + /** Human-readable description */ + description?: string; + /** Query string parameters */ + parameters?: ParamsResult; + /** Request body schema */ + input?: BodySchema; + /** Response body schema */ + output?: BodySchema; + /** Possible error responses */ + errors?: ErrorDef[]; } /** @@ -289,10 +283,10 @@ interface ProcedureOptions { * @see https://atproto.com/specs/lexicon#subscription */ interface MessageSchema { - /** Human-readable description */ - description?: string; - /** Union of possible message types */ - schema: { type: "union"; refs: readonly string[] }; + /** Human-readable description */ + description?: string; + /** Union of possible message types */ + schema: { type: "union"; refs: readonly string[] }; } /** @@ -300,14 +294,14 @@ interface MessageSchema { * @see https://atproto.com/specs/lexicon#subscription */ interface SubscriptionOptions { - /** Human-readable description */ - description?: string; - /** Query string parameters */ - parameters?: ParamsResult; - /** Message schema for events */ - message?: MessageSchema; - /** Possible error responses */ - errors?: ErrorDef[]; + /** Human-readable description */ + description?: string; + /** Query string parameters */ + parameters?: ParamsResult; + /** Message schema for events */ + message?: MessageSchema; + /** Possible error responses */ + errors?: ErrorDef[]; } /** @@ -315,243 +309,243 @@ interface SubscriptionOptions { * @see https://atproto.com/specs/lexicon */ export const lx = { - /** - * Creates a null type. - * @see https://atproto.com/specs/lexicon#null - */ - null( - options?: LexiconItemCommonOptions, - ): { type: "null" } & LexiconItemCommonOptions { - return { - type: "null", - ...options, - }; - }, - /** - * Creates a boolean type with optional constraints. - * @see https://atproto.com/specs/lexicon#boolean - */ - boolean(options?: T): T & { type: "boolean" } { - return { - type: "boolean", - ...options, - } as T & { type: "boolean" }; - }, - /** - * Creates an integer type with optional min/max and enum constraints. - * @see https://atproto.com/specs/lexicon#integer - */ - integer(options?: T): T & { type: "integer" } { - return { - type: "integer", - ...options, - } as T & { type: "integer" }; - }, - /** - * Creates a string type with optional format, length, and value constraints. - * @see https://atproto.com/specs/lexicon#string - */ - string(options?: T): T & { type: "string" } { - return { - type: "string", - ...options, - } as T & { type: "string" }; - }, - /** - * Creates an unknown type for flexible, unvalidated objects. - * @see https://atproto.com/specs/lexicon#unknown - */ - unknown( - options?: LexiconItemCommonOptions, - ): { type: "unknown" } & LexiconItemCommonOptions { - return { - type: "unknown", - ...options, - }; - }, - /** - * Creates a bytes type for arbitrary byte arrays. - * @see https://atproto.com/specs/lexicon#bytes - */ - bytes(options?: T): T & { type: "bytes" } { - return { - type: "bytes", - ...options, - } as T & { type: "bytes" }; - }, - /** - * Creates a CID link reference to content-addressed data. - * @see https://atproto.com/specs/lexicon#cid-link - */ - cidLink(link: Link): { type: "cid-link"; $link: Link } { - return { - type: "cid-link", - $link: link, - }; - }, - /** - * Creates a blob type for binary data with MIME type constraints. - * @see https://atproto.com/specs/lexicon#blob - */ - blob(options?: T): T & { type: "blob" } { - return { - type: "blob", - ...options, - } as T & { type: "blob" }; - }, - /** - * Creates an array type with item schema and length constraints. - * @see https://atproto.com/specs/lexicon#array - */ - array( - items: Items, - options?: Options, - ): Options & { type: "array"; items: Items } { - return { - type: "array", - items, - ...options, - } as Options & { type: "array"; items: Items }; - }, - /** - * Creates a token type for symbolic values in unions. - * @see https://atproto.com/specs/lexicon#token - */ - token( - description: Description, - ): { type: "token"; description: Description } { - return { type: "token", description }; - }, - /** - * Creates a reference to another schema definition. - * @see https://atproto.com/specs/lexicon#ref - */ - ref( - ref: Ref, - options?: LexiconItemCommonOptions, - ): LexiconItemCommonOptions & { type: "ref"; ref: Ref } { - return { - type: "ref", - ref, - ...options, - } as LexiconItemCommonOptions & { type: "ref"; ref: Ref }; - }, - /** - * Creates a union type for multiple possible type variants. - * @see https://atproto.com/specs/lexicon#union - */ - union( - refs: Refs, - options?: Options, - ): Options & { type: "union"; refs: Refs } { - return { - type: "union", - refs, - ...options, - } as Options & { type: "union"; refs: Refs }; - }, - /** - * Creates a record type for repository records. - * @see https://atproto.com/specs/lexicon#record - */ - record(options: T): T & { type: "record" } { - return { - type: "record", - ...options, - }; - }, - /** - * Creates an object type with defined properties. - * @see https://atproto.com/specs/lexicon#object - */ - object(options: T): ObjectResult { - const required = Object.keys(options).filter( - (key) => options[key].required, - ); - const nullable = Object.keys(options).filter( - (key) => options[key].nullable, - ); - const result: ObjectResult = { - type: "object", - properties: options, - }; - if (required.length > 0) { - result.required = required; - } - if (nullable.length > 0) { - result.nullable = nullable; - } - return result; - }, - /** - * Creates a params type for query string parameters. - * @see https://atproto.com/specs/lexicon#params - */ - params( - properties: Properties, - ): ParamsResult { - const required = Object.keys(properties).filter( - (key) => properties[key].required, - ); - const result: { - type: "params"; - properties: Properties; - required?: string[]; - } = { - type: "params", - properties, - }; - if (required.length > 0) { - result.required = required; - } - return result; - }, - /** - * Creates a query endpoint definition (HTTP GET). - * @see https://atproto.com/specs/lexicon#query - */ - query(options?: T): T & { type: "query" } { - return { - type: "query", - ...options, - } as T & { type: "query" }; - }, - /** - * Creates a procedure endpoint definition (HTTP POST). - * @see https://atproto.com/specs/lexicon#procedure - */ - procedure( - options?: T, - ): T & { type: "procedure" } { - return { - type: "procedure", - ...options, - } as T & { type: "procedure" }; - }, - /** - * Creates a subscription endpoint definition (WebSocket). - * @see https://atproto.com/specs/lexicon#subscription - */ - subscription( - options?: T, - ): T & { type: "subscription" } { - return { - type: "subscription", - ...options, - } as T & { type: "subscription" }; - }, - /** - * Creates a lexicon namespace document. - * @see https://atproto.com/specs/lexicon#lexicon-document - */ - namespace( - id: ID, - defs: D, - ): { lexicon: 1; id: ID; defs: D } { - return { - lexicon: 1, - id, - defs, - }; - }, + /** + * Creates a null type. + * @see https://atproto.com/specs/lexicon#null + */ + null( + options?: LexiconItemCommonOptions, + ): { type: "null" } & LexiconItemCommonOptions { + return { + type: "null", + ...options, + }; + }, + /** + * Creates a boolean type with optional constraints. + * @see https://atproto.com/specs/lexicon#boolean + */ + boolean(options?: T): T & { type: "boolean" } { + return { + type: "boolean", + ...options, + } as T & { type: "boolean" }; + }, + /** + * Creates an integer type with optional min/max and enum constraints. + * @see https://atproto.com/specs/lexicon#integer + */ + integer(options?: T): T & { type: "integer" } { + return { + type: "integer", + ...options, + } as T & { type: "integer" }; + }, + /** + * Creates a string type with optional format, length, and value constraints. + * @see https://atproto.com/specs/lexicon#string + */ + string(options?: T): T & { type: "string" } { + return { + type: "string", + ...options, + } as T & { type: "string" }; + }, + /** + * Creates an unknown type for flexible, unvalidated objects. + * @see https://atproto.com/specs/lexicon#unknown + */ + unknown( + options?: LexiconItemCommonOptions, + ): { type: "unknown" } & LexiconItemCommonOptions { + return { + type: "unknown", + ...options, + }; + }, + /** + * Creates a bytes type for arbitrary byte arrays. + * @see https://atproto.com/specs/lexicon#bytes + */ + bytes(options?: T): T & { type: "bytes" } { + return { + type: "bytes", + ...options, + } as T & { type: "bytes" }; + }, + /** + * Creates a CID link reference to content-addressed data. + * @see https://atproto.com/specs/lexicon#cid-link + */ + cidLink(link: Link): { type: "cid-link"; $link: Link } { + return { + type: "cid-link", + $link: link, + }; + }, + /** + * Creates a blob type for binary data with MIME type constraints. + * @see https://atproto.com/specs/lexicon#blob + */ + blob(options?: T): T & { type: "blob" } { + return { + type: "blob", + ...options, + } as T & { type: "blob" }; + }, + /** + * Creates an array type with item schema and length constraints. + * @see https://atproto.com/specs/lexicon#array + */ + array( + items: Items, + options?: Options, + ): Options & { type: "array"; items: Items } { + return { + type: "array", + items, + ...options, + } as Options & { type: "array"; items: Items }; + }, + /** + * Creates a token type for symbolic values in unions. + * @see https://atproto.com/specs/lexicon#token + */ + token( + description: Description, + ): { type: "token"; description: Description } { + return { type: "token", description }; + }, + /** + * Creates a reference to another schema definition. + * @see https://atproto.com/specs/lexicon#ref + */ + ref( + ref: Ref, + options?: LexiconItemCommonOptions, + ): LexiconItemCommonOptions & { type: "ref"; ref: Ref } { + return { + type: "ref", + ref, + ...options, + } as LexiconItemCommonOptions & { type: "ref"; ref: Ref }; + }, + /** + * Creates a union type for multiple possible type variants. + * @see https://atproto.com/specs/lexicon#union + */ + union( + refs: Refs, + options?: Options, + ): Options & { type: "union"; refs: Refs } { + return { + type: "union", + refs, + ...options, + } as Options & { type: "union"; refs: Refs }; + }, + /** + * Creates a record type for repository records. + * @see https://atproto.com/specs/lexicon#record + */ + record(options: T): T & { type: "record" } { + return { + type: "record", + ...options, + }; + }, + /** + * Creates an object type with defined properties. + * @see https://atproto.com/specs/lexicon#object + */ + object(options: T): ObjectResult { + const required = Object.keys(options).filter( + (key) => options[key].required, + ); + const nullable = Object.keys(options).filter( + (key) => options[key].nullable, + ); + const result: ObjectResult = { + type: "object", + properties: options, + }; + if (required.length > 0) { + result.required = required; + } + if (nullable.length > 0) { + result.nullable = nullable; + } + return result; + }, + /** + * Creates a params type for query string parameters. + * @see https://atproto.com/specs/lexicon#params + */ + params( + properties: Properties, + ): ParamsResult { + const required = Object.keys(properties).filter( + (key) => properties[key].required, + ); + const result: { + type: "params"; + properties: Properties; + required?: string[]; + } = { + type: "params", + properties, + }; + if (required.length > 0) { + result.required = required; + } + return result; + }, + /** + * Creates a query endpoint definition (HTTP GET). + * @see https://atproto.com/specs/lexicon#query + */ + query(options?: T): T & { type: "query" } { + return { + type: "query", + ...options, + } as T & { type: "query" }; + }, + /** + * Creates a procedure endpoint definition (HTTP POST). + * @see https://atproto.com/specs/lexicon#procedure + */ + procedure( + options?: T, + ): T & { type: "procedure" } { + return { + type: "procedure", + ...options, + } as T & { type: "procedure" }; + }, + /** + * Creates a subscription endpoint definition (WebSocket). + * @see https://atproto.com/specs/lexicon#subscription + */ + subscription( + options?: T, + ): T & { type: "subscription" } { + return { + type: "subscription", + ...options, + } as T & { type: "subscription" }; + }, + /** + * Creates a lexicon namespace document. + * @see https://atproto.com/specs/lexicon#lexicon-document + */ + namespace( + id: ID, + defs: D, + ): { lexicon: 1; id: ID; defs: D } { + return { + lexicon: 1, + id, + defs, + }; + }, }; diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 0000000..7ec1f3e --- /dev/null +++ b/src/test.ts @@ -0,0 +1,3 @@ +import type schema from "../samples/demo.json"; +import type { InferNS } from "./infer.ts"; +export type Schema = InferNS; diff --git a/src/tests/base-case.test.ts b/src/tests/base-case.test.ts new file mode 100644 index 0000000..952bdbe --- /dev/null +++ b/src/tests/base-case.test.ts @@ -0,0 +1,41 @@ +import { strict as assert } from "node:assert"; +import { test } from "node:test"; +import { lx } from "../lib.ts"; + +test("app.bsky.actor.profile", () => { + const profileNamespace = lx.namespace("app.bsky.actor.profile", { + main: lx.record({ + key: "self", + record: lx.object({ + displayName: lx.string({ maxLength: 64, maxGraphemes: 64 }), + description: lx.string({ maxLength: 256, maxGraphemes: 256 }), + }), + }), + }); + + assert.deepEqual(profileNamespace, { + lexicon: 1, + id: "app.bsky.actor.profile", + defs: { + main: { + type: "record", + key: "self", + record: { + type: "object", + properties: { + displayName: { + type: "string", + maxLength: 64, + maxGraphemes: 64, + }, + description: { + type: "string", + maxLength: 256, + maxGraphemes: 256, + }, + }, + }, + }, + }, + }); +}); diff --git a/src/tests/bsky-actor.test.ts b/src/tests/bsky-actor.test.ts new file mode 100644 index 0000000..6013809 --- /dev/null +++ b/src/tests/bsky-actor.test.ts @@ -0,0 +1,868 @@ +import { strict as assert } from "node:assert"; +import { test } from "node:test"; +import { lx } from "../lib.ts"; + +test("app.bsky.actor.defs - profileViewBasic", () => { + const profileViewBasic = lx.object({ + did: lx.string({ required: true, format: "did" }), + handle: lx.string({ required: true, format: "handle" }), + displayName: lx.string({ maxGraphemes: 64, maxLength: 640 }), + pronouns: lx.string(), + avatar: lx.string({ format: "uri" }), + associated: lx.ref("#profileAssociated"), + viewer: lx.ref("#viewerState"), + labels: lx.array(lx.ref("com.atproto.label.defs#label")), + createdAt: lx.string({ format: "datetime" }), + verification: lx.ref("#verificationState"), + status: lx.ref("#statusView"), + }); + + assert.deepEqual(profileViewBasic, { + type: "object", + properties: { + did: { type: "string", required: true, format: "did" }, + handle: { type: "string", required: true, format: "handle" }, + displayName: { type: "string", maxGraphemes: 64, maxLength: 640 }, + pronouns: { type: "string" }, + avatar: { type: "string", format: "uri" }, + associated: { type: "ref", ref: "#profileAssociated" }, + viewer: { type: "ref", ref: "#viewerState" }, + labels: { + type: "array", + items: { type: "ref", ref: "com.atproto.label.defs#label" }, + }, + createdAt: { type: "string", format: "datetime" }, + verification: { type: "ref", ref: "#verificationState" }, + status: { type: "ref", ref: "#statusView" }, + }, + required: ["did", "handle"], + }); +}); + +test("app.bsky.actor.defs - profileView", () => { + const profileView = lx.object({ + did: lx.string({ required: true, format: "did" }), + handle: lx.string({ required: true, format: "handle" }), + displayName: lx.string({ maxGraphemes: 64, maxLength: 640 }), + pronouns: lx.string(), + description: lx.string({ maxGraphemes: 256, maxLength: 2560 }), + avatar: lx.string({ format: "uri" }), + associated: lx.ref("#profileAssociated"), + indexedAt: lx.string({ format: "datetime" }), + createdAt: lx.string({ format: "datetime" }), + viewer: lx.ref("#viewerState"), + labels: lx.array(lx.ref("com.atproto.label.defs#label")), + verification: lx.ref("#verificationState"), + status: lx.ref("#statusView"), + }); + + assert.deepEqual(profileView, { + type: "object", + properties: { + did: { type: "string", required: true, format: "did" }, + handle: { type: "string", required: true, format: "handle" }, + displayName: { type: "string", maxGraphemes: 64, maxLength: 640 }, + pronouns: { type: "string" }, + description: { type: "string", maxGraphemes: 256, maxLength: 2560 }, + avatar: { type: "string", format: "uri" }, + associated: { type: "ref", ref: "#profileAssociated" }, + indexedAt: { type: "string", format: "datetime" }, + createdAt: { type: "string", format: "datetime" }, + viewer: { type: "ref", ref: "#viewerState" }, + labels: { + type: "array", + items: { type: "ref", ref: "com.atproto.label.defs#label" }, + }, + verification: { type: "ref", ref: "#verificationState" }, + status: { type: "ref", ref: "#statusView" }, + }, + required: ["did", "handle"], + }); +}); + +test("app.bsky.actor.defs - profileViewDetailed", () => { + const profileViewDetailed = lx.object({ + did: lx.string({ required: true, format: "did" }), + handle: lx.string({ required: true, format: "handle" }), + displayName: lx.string({ maxGraphemes: 64, maxLength: 640 }), + description: lx.string({ maxGraphemes: 256, maxLength: 2560 }), + pronouns: lx.string(), + website: lx.string({ format: "uri" }), + avatar: lx.string({ format: "uri" }), + banner: lx.string({ format: "uri" }), + followersCount: lx.integer(), + followsCount: lx.integer(), + postsCount: lx.integer(), + associated: lx.ref("#profileAssociated"), + joinedViaStarterPack: lx.ref("app.bsky.graph.defs#starterPackViewBasic"), + indexedAt: lx.string({ format: "datetime" }), + createdAt: lx.string({ format: "datetime" }), + viewer: lx.ref("#viewerState"), + labels: lx.array(lx.ref("com.atproto.label.defs#label")), + pinnedPost: lx.ref("com.atproto.repo.strongRef"), + verification: lx.ref("#verificationState"), + status: lx.ref("#statusView"), + }); + + assert.deepEqual(profileViewDetailed, { + type: "object", + properties: { + did: { type: "string", required: true, format: "did" }, + handle: { type: "string", required: true, format: "handle" }, + displayName: { type: "string", maxGraphemes: 64, maxLength: 640 }, + description: { type: "string", maxGraphemes: 256, maxLength: 2560 }, + pronouns: { type: "string" }, + website: { type: "string", format: "uri" }, + avatar: { type: "string", format: "uri" }, + banner: { type: "string", format: "uri" }, + followersCount: { type: "integer" }, + followsCount: { type: "integer" }, + postsCount: { type: "integer" }, + associated: { type: "ref", ref: "#profileAssociated" }, + joinedViaStarterPack: { + type: "ref", + ref: "app.bsky.graph.defs#starterPackViewBasic", + }, + indexedAt: { type: "string", format: "datetime" }, + createdAt: { type: "string", format: "datetime" }, + viewer: { type: "ref", ref: "#viewerState" }, + labels: { + type: "array", + items: { type: "ref", ref: "com.atproto.label.defs#label" }, + }, + pinnedPost: { type: "ref", ref: "com.atproto.repo.strongRef" }, + verification: { type: "ref", ref: "#verificationState" }, + status: { type: "ref", ref: "#statusView" }, + }, + required: ["did", "handle"], + }); +}); + +test("app.bsky.actor.defs - profileAssociated", () => { + const profileAssociated = lx.object({ + lists: lx.integer(), + feedgens: lx.integer(), + starterPacks: lx.integer(), + labeler: lx.boolean(), + chat: lx.ref("#profileAssociatedChat"), + activitySubscription: lx.ref("#profileAssociatedActivitySubscription"), + }); + + assert.deepEqual(profileAssociated, { + type: "object", + properties: { + lists: { type: "integer" }, + feedgens: { type: "integer" }, + starterPacks: { type: "integer" }, + labeler: { type: "boolean" }, + chat: { type: "ref", ref: "#profileAssociatedChat" }, + activitySubscription: { + type: "ref", + ref: "#profileAssociatedActivitySubscription", + }, + }, + }); +}); + +test("app.bsky.actor.defs - profileAssociatedChat", () => { + const profileAssociatedChat = lx.object({ + allowIncoming: lx.string({ + required: true, + knownValues: ["all", "none", "following"], + }), + }); + + assert.deepEqual(profileAssociatedChat, { + type: "object", + properties: { + allowIncoming: { + type: "string", + required: true, + knownValues: ["all", "none", "following"], + }, + }, + required: ["allowIncoming"], + }); +}); + +test("app.bsky.actor.defs - profileAssociatedActivitySubscription", () => { + const profileAssociatedActivitySubscription = lx.object({ + allowSubscriptions: lx.string({ + required: true, + knownValues: ["followers", "mutuals", "none"], + }), + }); + + assert.deepEqual(profileAssociatedActivitySubscription, { + type: "object", + properties: { + allowSubscriptions: { + type: "string", + required: true, + knownValues: ["followers", "mutuals", "none"], + }, + }, + required: ["allowSubscriptions"], + }); +}); + +test("app.bsky.actor.defs - viewerState", () => { + const viewerState = lx.object({ + muted: lx.boolean(), + mutedByList: lx.ref("app.bsky.graph.defs#listViewBasic"), + blockedBy: lx.boolean(), + blocking: lx.string({ format: "at-uri" }), + blockingByList: lx.ref("app.bsky.graph.defs#listViewBasic"), + following: lx.string({ format: "at-uri" }), + followedBy: lx.string({ format: "at-uri" }), + knownFollowers: lx.ref("#knownFollowers"), + activitySubscription: lx.ref( + "app.bsky.notification.defs#activitySubscription", + ), + }); + + assert.deepEqual(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" }, + blockingByList: { type: "ref", ref: "app.bsky.graph.defs#listViewBasic" }, + following: { type: "string", format: "at-uri" }, + followedBy: { type: "string", format: "at-uri" }, + knownFollowers: { type: "ref", ref: "#knownFollowers" }, + activitySubscription: { + type: "ref", + ref: "app.bsky.notification.defs#activitySubscription", + }, + }, + }); +}); + +test("app.bsky.actor.defs - knownFollowers", () => { + const knownFollowers = lx.object({ + count: lx.integer({ required: true }), + followers: lx.array(lx.ref("#profileViewBasic"), { + required: true, + minLength: 0, + maxLength: 5, + }), + }); + + assert.deepEqual(knownFollowers, { + type: "object", + properties: { + count: { type: "integer", required: true }, + followers: { + type: "array", + items: { type: "ref", ref: "#profileViewBasic" }, + required: true, + minLength: 0, + maxLength: 5, + }, + }, + required: ["count", "followers"], + }); +}); + +test("app.bsky.actor.defs - verificationState", () => { + const verificationState = lx.object({ + verifications: lx.array(lx.ref("#verificationView"), { required: true }), + verifiedStatus: lx.string({ + required: true, + knownValues: ["valid", "invalid", "none"], + }), + trustedVerifierStatus: lx.string({ + required: true, + knownValues: ["valid", "invalid", "none"], + }), + }); + + assert.deepEqual(verificationState, { + type: "object", + properties: { + verifications: { + type: "array", + items: { type: "ref", ref: "#verificationView" }, + required: true, + }, + verifiedStatus: { + type: "string", + required: true, + knownValues: ["valid", "invalid", "none"], + }, + trustedVerifierStatus: { + type: "string", + required: true, + knownValues: ["valid", "invalid", "none"], + }, + }, + required: ["verifications", "verifiedStatus", "trustedVerifierStatus"], + }); +}); + +test("app.bsky.actor.defs - verificationView", () => { + const verificationView = lx.object({ + issuer: lx.string({ required: true, format: "did" }), + uri: lx.string({ required: true, format: "at-uri" }), + isValid: lx.boolean({ required: true }), + createdAt: lx.string({ required: true, format: "datetime" }), + }); + + assert.deepEqual(verificationView, { + type: "object", + properties: { + issuer: { type: "string", required: true, format: "did" }, + uri: { type: "string", required: true, format: "at-uri" }, + isValid: { type: "boolean", required: true }, + createdAt: { type: "string", required: true, format: "datetime" }, + }, + required: ["issuer", "uri", "isValid", "createdAt"], + }); +}); + +test("app.bsky.actor.defs - preferences", () => { + const preferences = lx.array( + lx.union([ + "#adultContentPref", + "#contentLabelPref", + "#savedFeedsPref", + "#savedFeedsPrefV2", + "#personalDetailsPref", + "#feedViewPref", + "#threadViewPref", + "#interestsPref", + "#mutedWordsPref", + "#hiddenPostsPref", + "#bskyAppStatePref", + "#labelersPref", + "#postInteractionSettingsPref", + "#verificationPrefs", + ]), + ); + + assert.deepEqual(preferences, { + type: "array", + items: { + type: "union", + refs: [ + "#adultContentPref", + "#contentLabelPref", + "#savedFeedsPref", + "#savedFeedsPrefV2", + "#personalDetailsPref", + "#feedViewPref", + "#threadViewPref", + "#interestsPref", + "#mutedWordsPref", + "#hiddenPostsPref", + "#bskyAppStatePref", + "#labelersPref", + "#postInteractionSettingsPref", + "#verificationPrefs", + ], + }, + }); +}); + +test("app.bsky.actor.defs - adultContentPref", () => { + const adultContentPref = lx.object({ + enabled: lx.boolean({ required: true, default: false }), + }); + + assert.deepEqual(adultContentPref, { + type: "object", + properties: { + enabled: { type: "boolean", required: true, default: false }, + }, + required: ["enabled"], + }); +}); + +test("app.bsky.actor.defs - contentLabelPref", () => { + const contentLabelPref = lx.object({ + labelerDid: lx.string({ format: "did" }), + label: lx.string({ required: true }), + visibility: lx.string({ + required: true, + knownValues: ["ignore", "show", "warn", "hide"], + }), + }); + + assert.deepEqual(contentLabelPref, { + type: "object", + properties: { + labelerDid: { type: "string", format: "did" }, + label: { type: "string", required: true }, + visibility: { + type: "string", + required: true, + knownValues: ["ignore", "show", "warn", "hide"], + }, + }, + required: ["label", "visibility"], + }); +}); + +test("app.bsky.actor.defs - savedFeed", () => { + const savedFeed = lx.object({ + id: lx.string({ required: true }), + type: lx.string({ + required: true, + knownValues: ["feed", "list", "timeline"], + }), + value: lx.string({ required: true }), + pinned: lx.boolean({ required: true }), + }); + + assert.deepEqual(savedFeed, { + type: "object", + properties: { + id: { type: "string", required: true }, + type: { + type: "string", + required: true, + knownValues: ["feed", "list", "timeline"], + }, + value: { type: "string", required: true }, + pinned: { type: "boolean", required: true }, + }, + required: ["id", "type", "value", "pinned"], + }); +}); + +test("app.bsky.actor.defs - savedFeedsPrefV2", () => { + const savedFeedsPrefV2 = lx.object({ + items: lx.array(lx.ref("app.bsky.actor.defs#savedFeed"), { + required: true, + }), + }); + + assert.deepEqual(savedFeedsPrefV2, { + type: "object", + properties: { + items: { + type: "array", + items: { type: "ref", ref: "app.bsky.actor.defs#savedFeed" }, + required: true, + }, + }, + required: ["items"], + }); +}); + +test("app.bsky.actor.defs - savedFeedsPref", () => { + const savedFeedsPref = lx.object({ + pinned: lx.array(lx.string({ format: "at-uri" }), { required: true }), + saved: lx.array(lx.string({ format: "at-uri" }), { required: true }), + timelineIndex: lx.integer(), + }); + + assert.deepEqual(savedFeedsPref, { + type: "object", + properties: { + pinned: { + type: "array", + items: { type: "string", format: "at-uri" }, + required: true, + }, + saved: { + type: "array", + items: { type: "string", format: "at-uri" }, + required: true, + }, + timelineIndex: { type: "integer" }, + }, + required: ["pinned", "saved"], + }); +}); + +test("app.bsky.actor.defs - personalDetailsPref", () => { + const personalDetailsPref = lx.object({ + birthDate: lx.string({ format: "datetime" }), + }); + + assert.deepEqual(personalDetailsPref, { + type: "object", + properties: { + birthDate: { type: "string", format: "datetime" }, + }, + }); +}); + +test("app.bsky.actor.defs - feedViewPref", () => { + const feedViewPref = lx.object({ + feed: lx.string({ required: true }), + hideReplies: lx.boolean(), + hideRepliesByUnfollowed: lx.boolean({ default: true }), + hideRepliesByLikeCount: lx.integer(), + hideReposts: lx.boolean(), + hideQuotePosts: lx.boolean(), + }); + + assert.deepEqual(feedViewPref, { + type: "object", + properties: { + feed: { type: "string", required: true }, + hideReplies: { type: "boolean" }, + hideRepliesByUnfollowed: { type: "boolean", default: true }, + hideRepliesByLikeCount: { type: "integer" }, + hideReposts: { type: "boolean" }, + hideQuotePosts: { type: "boolean" }, + }, + required: ["feed"], + }); +}); + +test("app.bsky.actor.defs - threadViewPref", () => { + const threadViewPref = lx.object({ + sort: lx.string({ + knownValues: ["oldest", "newest", "most-likes", "random", "hotness"], + }), + prioritizeFollowedUsers: lx.boolean(), + }); + + assert.deepEqual(threadViewPref, { + type: "object", + properties: { + sort: { + type: "string", + knownValues: ["oldest", "newest", "most-likes", "random", "hotness"], + }, + prioritizeFollowedUsers: { type: "boolean" }, + }, + }); +}); + +test("app.bsky.actor.defs - interestsPref", () => { + const interestsPref = lx.object({ + tags: lx.array(lx.string({ maxLength: 640, maxGraphemes: 64 }), { + required: true, + maxLength: 100, + }), + }); + + assert.deepEqual(interestsPref, { + type: "object", + properties: { + tags: { + type: "array", + items: { type: "string", maxLength: 640, maxGraphemes: 64 }, + required: true, + maxLength: 100, + }, + }, + required: ["tags"], + }); +}); + +test("app.bsky.actor.defs - mutedWordTarget", () => { + const mutedWordTarget = lx.string({ + knownValues: ["content", "tag"], + maxLength: 640, + maxGraphemes: 64, + }); + + assert.deepEqual(mutedWordTarget, { + type: "string", + knownValues: ["content", "tag"], + maxLength: 640, + maxGraphemes: 64, + }); +}); + +test("app.bsky.actor.defs - mutedWord", () => { + const mutedWord = lx.object({ + id: lx.string(), + value: lx.string({ required: true, maxLength: 10000, maxGraphemes: 1000 }), + targets: lx.array(lx.ref("app.bsky.actor.defs#mutedWordTarget"), { + required: true, + }), + actorTarget: lx.string({ + knownValues: ["all", "exclude-following"], + default: "all", + }), + expiresAt: lx.string({ format: "datetime" }), + }); + + assert.deepEqual(mutedWord, { + type: "object", + properties: { + id: { type: "string" }, + value: { + type: "string", + required: true, + maxLength: 10000, + maxGraphemes: 1000, + }, + targets: { + type: "array", + items: { type: "ref", ref: "app.bsky.actor.defs#mutedWordTarget" }, + required: true, + }, + actorTarget: { + type: "string", + knownValues: ["all", "exclude-following"], + default: "all", + }, + expiresAt: { type: "string", format: "datetime" }, + }, + required: ["value", "targets"], + }); +}); + +test("app.bsky.actor.defs - mutedWordsPref", () => { + const mutedWordsPref = lx.object({ + items: lx.array(lx.ref("app.bsky.actor.defs#mutedWord"), { + required: true, + }), + }); + + assert.deepEqual(mutedWordsPref, { + type: "object", + properties: { + items: { + type: "array", + items: { type: "ref", ref: "app.bsky.actor.defs#mutedWord" }, + required: true, + }, + }, + required: ["items"], + }); +}); + +test("app.bsky.actor.defs - hiddenPostsPref", () => { + const hiddenPostsPref = lx.object({ + items: lx.array(lx.string({ format: "at-uri" }), { required: true }), + }); + + assert.deepEqual(hiddenPostsPref, { + type: "object", + properties: { + items: { + type: "array", + items: { type: "string", format: "at-uri" }, + required: true, + }, + }, + required: ["items"], + }); +}); + +test("app.bsky.actor.defs - labelersPref", () => { + const labelersPref = lx.object({ + labelers: lx.array(lx.ref("#labelerPrefItem"), { required: true }), + }); + + assert.deepEqual(labelersPref, { + type: "object", + properties: { + labelers: { + type: "array", + items: { type: "ref", ref: "#labelerPrefItem" }, + required: true, + }, + }, + required: ["labelers"], + }); +}); + +test("app.bsky.actor.defs - labelerPrefItem", () => { + const labelerPrefItem = lx.object({ + did: lx.string({ required: true, format: "did" }), + }); + + assert.deepEqual(labelerPrefItem, { + type: "object", + properties: { + did: { type: "string", required: true, format: "did" }, + }, + required: ["did"], + }); +}); + +test("app.bsky.actor.defs - bskyAppStatePref", () => { + const bskyAppStatePref = lx.object({ + activeProgressGuide: lx.ref("#bskyAppProgressGuide"), + queuedNudges: lx.array(lx.string({ maxLength: 100 }), { maxLength: 1000 }), + nuxs: lx.array(lx.ref("app.bsky.actor.defs#nux"), { maxLength: 100 }), + }); + + assert.deepEqual(bskyAppStatePref, { + type: "object", + properties: { + activeProgressGuide: { type: "ref", ref: "#bskyAppProgressGuide" }, + queuedNudges: { + type: "array", + items: { type: "string", maxLength: 100 }, + maxLength: 1000, + }, + nuxs: { + type: "array", + items: { type: "ref", ref: "app.bsky.actor.defs#nux" }, + maxLength: 100, + }, + }, + }); +}); + +test("app.bsky.actor.defs - bskyAppProgressGuide", () => { + const bskyAppProgressGuide = lx.object({ + guide: lx.string({ required: true, maxLength: 100 }), + }); + + assert.deepEqual(bskyAppProgressGuide, { + type: "object", + properties: { + guide: { type: "string", required: true, maxLength: 100 }, + }, + required: ["guide"], + }); +}); + +test("app.bsky.actor.defs - nux", () => { + const nux = lx.object({ + id: lx.string({ required: true, maxLength: 100 }), + completed: lx.boolean({ required: true, default: false }), + data: lx.string({ maxLength: 3000, maxGraphemes: 300 }), + expiresAt: lx.string({ format: "datetime" }), + }); + + assert.deepEqual(nux, { + type: "object", + properties: { + id: { type: "string", required: true, maxLength: 100 }, + completed: { type: "boolean", required: true, default: false }, + data: { type: "string", maxLength: 3000, maxGraphemes: 300 }, + expiresAt: { type: "string", format: "datetime" }, + }, + required: ["id", "completed"], + }); +}); + +test("app.bsky.actor.defs - verificationPrefs", () => { + const verificationPrefs = lx.object({ + hideBadges: lx.boolean({ default: false }), + }); + + assert.deepEqual(verificationPrefs, { + type: "object", + properties: { + hideBadges: { type: "boolean", default: false }, + }, + }); +}); + +test("app.bsky.actor.defs - postInteractionSettingsPref", () => { + const postInteractionSettingsPref = lx.object({ + threadgateAllowRules: lx.array( + lx.union([ + "app.bsky.feed.threadgate#mentionRule", + "app.bsky.feed.threadgate#followerRule", + "app.bsky.feed.threadgate#followingRule", + "app.bsky.feed.threadgate#listRule", + ]), + { maxLength: 5 }, + ), + postgateEmbeddingRules: lx.array( + lx.union(["app.bsky.feed.postgate#disableRule"]), + { maxLength: 5 }, + ), + }); + + assert.deepEqual(postInteractionSettingsPref, { + type: "object", + properties: { + threadgateAllowRules: { + type: "array", + items: { + type: "union", + refs: [ + "app.bsky.feed.threadgate#mentionRule", + "app.bsky.feed.threadgate#followerRule", + "app.bsky.feed.threadgate#followingRule", + "app.bsky.feed.threadgate#listRule", + ], + }, + maxLength: 5, + }, + postgateEmbeddingRules: { + type: "array", + items: { + type: "union", + refs: ["app.bsky.feed.postgate#disableRule"], + }, + maxLength: 5, + }, + }, + }); +}); + +test("app.bsky.actor.defs - statusView", () => { + const statusView = lx.object({ + status: lx.string({ + required: true, + knownValues: ["app.bsky.actor.status#live"], + }), + record: lx.unknown({ required: true }), + embed: lx.union(["app.bsky.embed.external#view"]), + expiresAt: lx.string({ format: "datetime" }), + isActive: lx.boolean(), + }); + + assert.deepEqual(statusView, { + type: "object", + properties: { + status: { + type: "string", + required: true, + knownValues: ["app.bsky.actor.status#live"], + }, + record: { type: "unknown", required: true }, + embed: { + type: "union", + refs: ["app.bsky.embed.external#view"], + }, + expiresAt: { type: "string", format: "datetime" }, + isActive: { type: "boolean" }, + }, + required: ["status", "record"], + }); +}); + +test("app.bsky.actor.defs - full namespace", () => { + const actorDefs = lx.namespace("app.bsky.actor.defs", { + profileViewBasic: lx.object({ + did: lx.string({ required: true, format: "did" }), + handle: lx.string({ required: true, format: "handle" }), + displayName: lx.string({ maxGraphemes: 64, maxLength: 640 }), + pronouns: lx.string(), + avatar: lx.string({ format: "uri" }), + associated: lx.ref("#profileAssociated"), + viewer: lx.ref("#viewerState"), + labels: lx.array(lx.ref("com.atproto.label.defs#label")), + createdAt: lx.string({ format: "datetime" }), + verification: lx.ref("#verificationState"), + status: lx.ref("#statusView"), + }), + viewerState: lx.object({ + muted: lx.boolean(), + mutedByList: lx.ref("app.bsky.graph.defs#listViewBasic"), + blockedBy: lx.boolean(), + blocking: lx.string({ format: "at-uri" }), + blockingByList: lx.ref("app.bsky.graph.defs#listViewBasic"), + following: lx.string({ format: "at-uri" }), + followedBy: lx.string({ format: "at-uri" }), + knownFollowers: lx.ref("#knownFollowers"), + activitySubscription: lx.ref( + "app.bsky.notification.defs#activitySubscription", + ), + }), + }); + + assert.deepEqual(actorDefs.lexicon, 1); + assert.deepEqual(actorDefs.id, "app.bsky.actor.defs"); + assert.deepEqual(actorDefs.defs.profileViewBasic.type, "object"); + assert.deepEqual(actorDefs.defs.viewerState.type, "object"); +}); diff --git a/src/tests/bsky-feed.test.ts b/src/tests/bsky-feed.test.ts new file mode 100644 index 0000000..0ce6609 --- /dev/null +++ b/src/tests/bsky-feed.test.ts @@ -0,0 +1,682 @@ +import { strict as assert } from "node:assert"; +import { test } from "node:test"; +import { lx } from "../lib.ts"; + +test("app.bsky.feed.defs - postView", () => { + const postView = lx.object({ + uri: lx.string({ required: true, format: "at-uri" }), + cid: lx.string({ required: true, format: "cid" }), + author: lx.ref("app.bsky.actor.defs#profileViewBasic", { required: true }), + record: lx.unknown({ required: true }), + embed: lx.union([ + "app.bsky.embed.images#view", + "app.bsky.embed.video#view", + "app.bsky.embed.external#view", + "app.bsky.embed.record#view", + "app.bsky.embed.recordWithMedia#view", + ]), + bookmarkCount: lx.integer(), + replyCount: lx.integer(), + repostCount: lx.integer(), + likeCount: lx.integer(), + quoteCount: lx.integer(), + indexedAt: lx.string({ required: true, format: "datetime" }), + viewer: lx.ref("#viewerState"), + labels: lx.array(lx.ref("com.atproto.label.defs#label")), + threadgate: lx.ref("#threadgateView"), + }); + + assert.deepEqual(postView, { + type: "object", + properties: { + uri: { type: "string", required: true, format: "at-uri" }, + cid: { type: "string", required: true, format: "cid" }, + author: { + type: "ref", + ref: "app.bsky.actor.defs#profileViewBasic", + required: true, + }, + record: { type: "unknown", required: true }, + embed: { + type: "union", + refs: [ + "app.bsky.embed.images#view", + "app.bsky.embed.video#view", + "app.bsky.embed.external#view", + "app.bsky.embed.record#view", + "app.bsky.embed.recordWithMedia#view", + ], + }, + bookmarkCount: { type: "integer" }, + replyCount: { type: "integer" }, + repostCount: { type: "integer" }, + likeCount: { type: "integer" }, + quoteCount: { type: "integer" }, + indexedAt: { type: "string", required: true, format: "datetime" }, + viewer: { type: "ref", ref: "#viewerState" }, + labels: { + type: "array", + items: { type: "ref", ref: "com.atproto.label.defs#label" }, + }, + threadgate: { type: "ref", ref: "#threadgateView" }, + }, + required: ["uri", "cid", "author", "record", "indexedAt"], + }); +}); + +test("app.bsky.feed.defs - viewerState", () => { + const viewerState = lx.object({ + repost: lx.string({ format: "at-uri" }), + like: lx.string({ format: "at-uri" }), + bookmarked: lx.boolean(), + threadMuted: lx.boolean(), + replyDisabled: lx.boolean(), + embeddingDisabled: lx.boolean(), + pinned: lx.boolean(), + }); + + assert.deepEqual(viewerState, { + type: "object", + properties: { + repost: { type: "string", format: "at-uri" }, + like: { type: "string", format: "at-uri" }, + bookmarked: { type: "boolean" }, + threadMuted: { type: "boolean" }, + replyDisabled: { type: "boolean" }, + embeddingDisabled: { type: "boolean" }, + pinned: { type: "boolean" }, + }, + }); +}); + +test("app.bsky.feed.defs - threadContext", () => { + const threadContext = lx.object({ + rootAuthorLike: lx.string({ format: "at-uri" }), + }); + + assert.deepEqual(threadContext, { + type: "object", + properties: { + rootAuthorLike: { type: "string", format: "at-uri" }, + }, + }); +}); + +test("app.bsky.feed.defs - feedViewPost", () => { + const feedViewPost = lx.object({ + post: lx.ref("#postView", { required: true }), + reply: lx.ref("#replyRef"), + reason: lx.union(["#reasonRepost", "#reasonPin"]), + feedContext: lx.string({ maxLength: 2000 }), + reqId: lx.string({ maxLength: 100 }), + }); + + assert.deepEqual(feedViewPost, { + type: "object", + properties: { + post: { type: "ref", ref: "#postView", required: true }, + reply: { type: "ref", ref: "#replyRef" }, + reason: { + type: "union", + refs: ["#reasonRepost", "#reasonPin"], + }, + feedContext: { type: "string", maxLength: 2000 }, + reqId: { type: "string", maxLength: 100 }, + }, + required: ["post"], + }); +}); + +test("app.bsky.feed.defs - replyRef", () => { + const replyRef = lx.object({ + root: lx.union(["#postView", "#notFoundPost", "#blockedPost"], { + required: true, + }), + parent: lx.union(["#postView", "#notFoundPost", "#blockedPost"], { + required: true, + }), + grandparentAuthor: lx.ref("app.bsky.actor.defs#profileViewBasic"), + }); + + assert.deepEqual(replyRef, { + type: "object", + properties: { + root: { + type: "union", + refs: ["#postView", "#notFoundPost", "#blockedPost"], + required: true, + }, + parent: { + type: "union", + refs: ["#postView", "#notFoundPost", "#blockedPost"], + required: true, + }, + grandparentAuthor: { + type: "ref", + ref: "app.bsky.actor.defs#profileViewBasic", + }, + }, + required: ["root", "parent"], + }); +}); + +test("app.bsky.feed.defs - reasonRepost", () => { + const reasonRepost = lx.object({ + by: lx.ref("app.bsky.actor.defs#profileViewBasic", { required: true }), + uri: lx.string({ format: "at-uri" }), + cid: lx.string({ format: "cid" }), + indexedAt: lx.string({ required: true, format: "datetime" }), + }); + + assert.deepEqual(reasonRepost, { + type: "object", + properties: { + by: { + type: "ref", + ref: "app.bsky.actor.defs#profileViewBasic", + required: true, + }, + uri: { type: "string", format: "at-uri" }, + cid: { type: "string", format: "cid" }, + indexedAt: { type: "string", required: true, format: "datetime" }, + }, + required: ["by", "indexedAt"], + }); +}); + +test("app.bsky.feed.defs - reasonPin", () => { + const reasonPin = lx.object({}); + + assert.deepEqual(reasonPin, { + type: "object", + properties: {}, + }); +}); + +test("app.bsky.feed.defs - threadViewPost", () => { + const threadViewPost = lx.object({ + post: lx.ref("#postView", { required: true }), + parent: lx.union(["#threadViewPost", "#notFoundPost", "#blockedPost"]), + replies: lx.array( + lx.union(["#threadViewPost", "#notFoundPost", "#blockedPost"]), + ), + threadContext: lx.ref("#threadContext"), + }); + + assert.deepEqual(threadViewPost, { + type: "object", + properties: { + post: { type: "ref", ref: "#postView", required: true }, + parent: { + type: "union", + refs: ["#threadViewPost", "#notFoundPost", "#blockedPost"], + }, + replies: { + type: "array", + items: { + type: "union", + refs: ["#threadViewPost", "#notFoundPost", "#blockedPost"], + }, + }, + threadContext: { type: "ref", ref: "#threadContext" }, + }, + required: ["post"], + }); +}); + +test("app.bsky.feed.defs - notFoundPost", () => { + const notFoundPost = lx.object({ + uri: lx.string({ required: true, format: "at-uri" }), + notFound: lx.boolean({ required: true, const: true }), + }); + + assert.deepEqual(notFoundPost, { + type: "object", + properties: { + uri: { type: "string", required: true, format: "at-uri" }, + notFound: { type: "boolean", required: true, const: true }, + }, + required: ["uri", "notFound"], + }); +}); + +test("app.bsky.feed.defs - blockedPost", () => { + const blockedPost = lx.object({ + uri: lx.string({ required: true, format: "at-uri" }), + blocked: lx.boolean({ required: true, const: true }), + author: lx.ref("#blockedAuthor", { required: true }), + }); + + assert.deepEqual(blockedPost, { + type: "object", + properties: { + uri: { type: "string", required: true, format: "at-uri" }, + blocked: { type: "boolean", required: true, const: true }, + author: { type: "ref", ref: "#blockedAuthor", required: true }, + }, + required: ["uri", "blocked", "author"], + }); +}); + +test("app.bsky.feed.defs - blockedAuthor", () => { + const blockedAuthor = lx.object({ + did: lx.string({ required: true, format: "did" }), + viewer: lx.ref("app.bsky.actor.defs#viewerState"), + }); + + assert.deepEqual(blockedAuthor, { + type: "object", + properties: { + did: { type: "string", required: true, format: "did" }, + viewer: { type: "ref", ref: "app.bsky.actor.defs#viewerState" }, + }, + required: ["did"], + }); +}); + +test("app.bsky.feed.defs - generatorView", () => { + const generatorView = lx.object({ + uri: lx.string({ required: true, format: "at-uri" }), + cid: lx.string({ required: true, format: "cid" }), + did: lx.string({ required: true, format: "did" }), + creator: lx.ref("app.bsky.actor.defs#profileView", { required: true }), + displayName: lx.string({ required: true }), + description: lx.string({ maxGraphemes: 300, maxLength: 3000 }), + descriptionFacets: lx.array(lx.ref("app.bsky.richtext.facet")), + avatar: lx.string({ format: "uri" }), + likeCount: lx.integer({ minimum: 0 }), + acceptsInteractions: lx.boolean(), + labels: lx.array(lx.ref("com.atproto.label.defs#label")), + viewer: lx.ref("#generatorViewerState"), + contentMode: lx.string({ + knownValues: [ + "app.bsky.feed.defs#contentModeUnspecified", + "app.bsky.feed.defs#contentModeVideo", + ], + }), + indexedAt: lx.string({ required: true, format: "datetime" }), + }); + + assert.deepEqual(generatorView, { + type: "object", + properties: { + uri: { type: "string", required: true, format: "at-uri" }, + cid: { type: "string", required: true, format: "cid" }, + did: { type: "string", required: true, format: "did" }, + creator: { + type: "ref", + ref: "app.bsky.actor.defs#profileView", + required: true, + }, + displayName: { type: "string", required: true }, + description: { type: "string", maxGraphemes: 300, maxLength: 3000 }, + descriptionFacets: { + type: "array", + items: { type: "ref", ref: "app.bsky.richtext.facet" }, + }, + avatar: { type: "string", format: "uri" }, + likeCount: { type: "integer", minimum: 0 }, + acceptsInteractions: { type: "boolean" }, + labels: { + type: "array", + items: { type: "ref", ref: "com.atproto.label.defs#label" }, + }, + viewer: { type: "ref", ref: "#generatorViewerState" }, + contentMode: { + type: "string", + knownValues: [ + "app.bsky.feed.defs#contentModeUnspecified", + "app.bsky.feed.defs#contentModeVideo", + ], + }, + indexedAt: { type: "string", required: true, format: "datetime" }, + }, + required: ["uri", "cid", "did", "creator", "displayName", "indexedAt"], + }); +}); + +test("app.bsky.feed.defs - generatorViewerState", () => { + const generatorViewerState = lx.object({ + like: lx.string({ format: "at-uri" }), + }); + + assert.deepEqual(generatorViewerState, { + type: "object", + properties: { + like: { type: "string", format: "at-uri" }, + }, + }); +}); + +test("app.bsky.feed.defs - skeletonFeedPost", () => { + const skeletonFeedPost = lx.object({ + post: lx.string({ required: true, format: "at-uri" }), + reason: lx.union(["#skeletonReasonRepost", "#skeletonReasonPin"]), + feedContext: lx.string({ maxLength: 2000 }), + }); + + assert.deepEqual(skeletonFeedPost, { + type: "object", + properties: { + post: { type: "string", required: true, format: "at-uri" }, + reason: { + type: "union", + refs: ["#skeletonReasonRepost", "#skeletonReasonPin"], + }, + feedContext: { type: "string", maxLength: 2000 }, + }, + required: ["post"], + }); +}); + +test("app.bsky.feed.defs - skeletonReasonRepost", () => { + const skeletonReasonRepost = lx.object({ + repost: lx.string({ required: true, format: "at-uri" }), + }); + + assert.deepEqual(skeletonReasonRepost, { + type: "object", + properties: { + repost: { type: "string", required: true, format: "at-uri" }, + }, + required: ["repost"], + }); +}); + +test("app.bsky.feed.defs - skeletonReasonPin", () => { + const skeletonReasonPin = lx.object({}); + + assert.deepEqual(skeletonReasonPin, { + type: "object", + properties: {}, + }); +}); + +test("app.bsky.feed.defs - threadgateView", () => { + const threadgateView = lx.object({ + uri: lx.string({ format: "at-uri" }), + cid: lx.string({ format: "cid" }), + record: lx.unknown(), + lists: lx.array(lx.ref("app.bsky.graph.defs#listViewBasic")), + }); + + assert.deepEqual(threadgateView, { + type: "object", + properties: { + uri: { type: "string", format: "at-uri" }, + cid: { type: "string", format: "cid" }, + record: { type: "unknown" }, + lists: { + type: "array", + items: { type: "ref", ref: "app.bsky.graph.defs#listViewBasic" }, + }, + }, + }); +}); + +test("app.bsky.feed.defs - interaction", () => { + const interaction = lx.object({ + item: lx.string({ format: "at-uri" }), + event: lx.string({ + knownValues: [ + "app.bsky.feed.defs#requestLess", + "app.bsky.feed.defs#requestMore", + "app.bsky.feed.defs#clickthroughItem", + "app.bsky.feed.defs#clickthroughAuthor", + "app.bsky.feed.defs#clickthroughReposter", + "app.bsky.feed.defs#clickthroughEmbed", + "app.bsky.feed.defs#interactionSeen", + "app.bsky.feed.defs#interactionLike", + "app.bsky.feed.defs#interactionRepost", + "app.bsky.feed.defs#interactionReply", + "app.bsky.feed.defs#interactionQuote", + "app.bsky.feed.defs#interactionShare", + ], + }), + feedContext: lx.string({ maxLength: 2000 }), + reqId: lx.string({ maxLength: 100 }), + }); + + assert.deepEqual(interaction, { + type: "object", + properties: { + item: { type: "string", format: "at-uri" }, + event: { + type: "string", + knownValues: [ + "app.bsky.feed.defs#requestLess", + "app.bsky.feed.defs#requestMore", + "app.bsky.feed.defs#clickthroughItem", + "app.bsky.feed.defs#clickthroughAuthor", + "app.bsky.feed.defs#clickthroughReposter", + "app.bsky.feed.defs#clickthroughEmbed", + "app.bsky.feed.defs#interactionSeen", + "app.bsky.feed.defs#interactionLike", + "app.bsky.feed.defs#interactionRepost", + "app.bsky.feed.defs#interactionReply", + "app.bsky.feed.defs#interactionQuote", + "app.bsky.feed.defs#interactionShare", + ], + }, + feedContext: { type: "string", maxLength: 2000 }, + reqId: { type: "string", maxLength: 100 }, + }, + }); +}); + +test("app.bsky.feed.defs - requestLess token", () => { + const requestLess = lx.token( + "Request that less content like the given feed item be shown in the feed", + ); + + assert.deepEqual(requestLess, { + type: "token", + description: + "Request that less content like the given feed item be shown in the feed", + }); +}); + +test("app.bsky.feed.defs - requestMore token", () => { + const requestMore = lx.token( + "Request that more content like the given feed item be shown in the feed", + ); + + assert.deepEqual(requestMore, { + type: "token", + description: + "Request that more content like the given feed item be shown in the feed", + }); +}); + +test("app.bsky.feed.defs - clickthroughItem token", () => { + const clickthroughItem = lx.token("User clicked through to the feed item"); + + assert.deepEqual(clickthroughItem, { + type: "token", + description: "User clicked through to the feed item", + }); +}); + +test("app.bsky.feed.defs - clickthroughAuthor token", () => { + const clickthroughAuthor = lx.token( + "User clicked through to the author of the feed item", + ); + + assert.deepEqual(clickthroughAuthor, { + type: "token", + description: "User clicked through to the author of the feed item", + }); +}); + +test("app.bsky.feed.defs - clickthroughReposter token", () => { + const clickthroughReposter = lx.token( + "User clicked through to the reposter of the feed item", + ); + + assert.deepEqual(clickthroughReposter, { + type: "token", + description: "User clicked through to the reposter of the feed item", + }); +}); + +test("app.bsky.feed.defs - clickthroughEmbed token", () => { + const clickthroughEmbed = lx.token( + "User clicked through to the embedded content of the feed item", + ); + + assert.deepEqual(clickthroughEmbed, { + type: "token", + description: + "User clicked through to the embedded content of the feed item", + }); +}); + +test("app.bsky.feed.defs - contentModeUnspecified token", () => { + const contentModeUnspecified = lx.token( + "Declares the feed generator returns any types of posts.", + ); + + assert.deepEqual(contentModeUnspecified, { + type: "token", + description: "Declares the feed generator returns any types of posts.", + }); +}); + +test("app.bsky.feed.defs - contentModeVideo token", () => { + const contentModeVideo = lx.token( + "Declares the feed generator returns posts containing app.bsky.embed.video embeds.", + ); + + assert.deepEqual(contentModeVideo, { + type: "token", + description: + "Declares the feed generator returns posts containing app.bsky.embed.video embeds.", + }); +}); + +test("app.bsky.feed.defs - interactionSeen token", () => { + const interactionSeen = lx.token("Feed item was seen by user"); + + assert.deepEqual(interactionSeen, { + type: "token", + description: "Feed item was seen by user", + }); +}); + +test("app.bsky.feed.defs - interactionLike token", () => { + const interactionLike = lx.token("User liked the feed item"); + + assert.deepEqual(interactionLike, { + type: "token", + description: "User liked the feed item", + }); +}); + +test("app.bsky.feed.defs - interactionRepost token", () => { + const interactionRepost = lx.token("User reposted the feed item"); + + assert.deepEqual(interactionRepost, { + type: "token", + description: "User reposted the feed item", + }); +}); + +test("app.bsky.feed.defs - interactionReply token", () => { + const interactionReply = lx.token("User replied to the feed item"); + + assert.deepEqual(interactionReply, { + type: "token", + description: "User replied to the feed item", + }); +}); + +test("app.bsky.feed.defs - interactionQuote token", () => { + const interactionQuote = lx.token("User quoted the feed item"); + + assert.deepEqual(interactionQuote, { + type: "token", + description: "User quoted the feed item", + }); +}); + +test("app.bsky.feed.defs - interactionShare token", () => { + const interactionShare = lx.token("User shared the feed item"); + + assert.deepEqual(interactionShare, { + type: "token", + description: "User shared the feed item", + }); +}); + +test("app.bsky.feed.defs - full namespace", () => { + const feedDefs = lx.namespace("app.bsky.feed.defs", { + postView: lx.object({ + uri: lx.string({ required: true, format: "at-uri" }), + cid: lx.string({ required: true, format: "cid" }), + author: lx.ref("app.bsky.actor.defs#profileViewBasic", { + required: true, + }), + record: lx.unknown({ required: true }), + embed: lx.union([ + "app.bsky.embed.images#view", + "app.bsky.embed.video#view", + "app.bsky.embed.external#view", + "app.bsky.embed.record#view", + "app.bsky.embed.recordWithMedia#view", + ]), + bookmarkCount: lx.integer(), + replyCount: lx.integer(), + repostCount: lx.integer(), + likeCount: lx.integer(), + quoteCount: lx.integer(), + indexedAt: lx.string({ required: true, format: "datetime" }), + viewer: lx.ref("#viewerState"), + labels: lx.array(lx.ref("com.atproto.label.defs#label")), + threadgate: lx.ref("#threadgateView"), + }), + viewerState: lx.object({ + repost: lx.string({ format: "at-uri" }), + like: lx.string({ format: "at-uri" }), + bookmarked: lx.boolean(), + threadMuted: lx.boolean(), + replyDisabled: lx.boolean(), + embeddingDisabled: lx.boolean(), + pinned: lx.boolean(), + }), + requestLess: lx.token( + "Request that less content like the given feed item be shown in the feed", + ), + requestMore: lx.token( + "Request that more content like the given feed item be shown in the feed", + ), + clickthroughItem: lx.token("User clicked through to the feed item"), + clickthroughAuthor: lx.token( + "User clicked through to the author of the feed item", + ), + clickthroughReposter: lx.token( + "User clicked through to the reposter of the feed item", + ), + clickthroughEmbed: lx.token( + "User clicked through to the embedded content of the feed item", + ), + contentModeUnspecified: lx.token( + "Declares the feed generator returns any types of posts.", + ), + contentModeVideo: lx.token( + "Declares the feed generator returns posts containing app.bsky.embed.video embeds.", + ), + interactionSeen: lx.token("Feed item was seen by user"), + interactionLike: lx.token("User liked the feed item"), + interactionRepost: lx.token("User reposted the feed item"), + interactionReply: lx.token("User replied to the feed item"), + interactionQuote: lx.token("User quoted the feed item"), + interactionShare: lx.token("User shared the feed item"), + }); + + assert.deepEqual(feedDefs.lexicon, 1); + assert.deepEqual(feedDefs.id, "app.bsky.feed.defs"); + assert.deepEqual(feedDefs.defs.postView.type, "object"); + assert.deepEqual(feedDefs.defs.viewerState.type, "object"); + assert.deepEqual(feedDefs.defs.requestLess.type, "token"); + assert.deepEqual(feedDefs.defs.contentModeVideo.type, "token"); +}); diff --git a/src/tests/primitives.test.ts b/src/tests/primitives.test.ts new file mode 100644 index 0000000..6f13691 --- /dev/null +++ b/src/tests/primitives.test.ts @@ -0,0 +1,790 @@ +import { strict as assert } from "node:assert"; +import { lx } from "../lib.ts"; +import { test } from "node:test"; + +test("lx.null()", () => { + const result = lx.null(); + assert.deepEqual(result, { type: "null" }); +}); + +test("lx.boolean()", () => { + const result = lx.boolean(); + assert.deepEqual(result, { type: "boolean" }); +}); + +test("lx.boolean() with default", () => { + const result = lx.boolean({ default: true }); + assert.deepEqual(result, { type: "boolean", default: true }); +}); + +test("lx.boolean() with const", () => { + const result = lx.boolean({ const: false }); + assert.deepEqual(result, { type: "boolean", const: false }); +}); + +test("lx.integer()", () => { + const result = lx.integer(); + assert.deepEqual(result, { type: "integer" }); +}); + +test("lx.integer() with minimum", () => { + const result = lx.integer({ minimum: 0 }); + assert.deepEqual(result, { type: "integer", minimum: 0 }); +}); + +test("lx.integer() with maximum", () => { + const result = lx.integer({ maximum: 100 }); + assert.deepEqual(result, { type: "integer", maximum: 100 }); +}); + +test("lx.integer() with minimum and maximum", () => { + const result = lx.integer({ minimum: 0, maximum: 100 }); + assert.deepEqual(result, { type: "integer", minimum: 0, maximum: 100 }); +}); + +test("lx.integer() with enum", () => { + const result = lx.integer({ enum: [1, 2, 3, 5, 8, 13] }); + assert.deepEqual(result, { type: "integer", enum: [1, 2, 3, 5, 8, 13] }); +}); + +test("lx.integer() with default", () => { + const result = lx.integer({ default: 42 }); + assert.deepEqual(result, { type: "integer", default: 42 }); +}); + +test("lx.integer() with const", () => { + const result = lx.integer({ const: 7 }); + assert.deepEqual(result, { type: "integer", const: 7 }); +}); + +test("lx.string()", () => { + const result = lx.string(); + assert.deepEqual(result, { type: "string" }); +}); + +test("lx.string() with maxLength", () => { + const result = lx.string({ maxLength: 64 }); + assert.deepEqual(result, { type: "string", maxLength: 64 }); +}); + +test("lx.string() with enum", () => { + const result = lx.string({ enum: ["light", "dark", "auto"] }); + assert.deepEqual(result, { type: "string", enum: ["light", "dark", "auto"] }); +}); + +test("lx.unknown()", () => { + const result = lx.unknown(); + assert.deepEqual(result, { type: "unknown" }); +}); + +test("lx.bytes()", () => { + const result = lx.bytes(); + assert.deepEqual(result, { type: "bytes" }); +}); + +test("lx.bytes() with minLength", () => { + const result = lx.bytes({ minLength: 1 }); + assert.deepEqual(result, { type: "bytes", minLength: 1 }); +}); + +test("lx.bytes() with maxLength", () => { + const result = lx.bytes({ maxLength: 1024 }); + assert.deepEqual(result, { type: "bytes", maxLength: 1024 }); +}); + +test("lx.bytes() with minLength and maxLength", () => { + const result = lx.bytes({ minLength: 1, maxLength: 1024 }); + assert.deepEqual(result, { type: "bytes", minLength: 1, maxLength: 1024 }); +}); + +test("lx.cidLink()", () => { + const result = lx.cidLink( + "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a", + ); + assert.deepEqual(result, { + type: "cid-link", + $link: "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a", + }); +}); + +test("lx.blob()", () => { + const result = lx.blob(); + assert.deepEqual(result, { type: "blob" }); +}); + +test("lx.blob() with accept", () => { + const result = lx.blob({ accept: ["image/png", "image/jpeg"] }); + assert.deepEqual(result, { + type: "blob", + accept: ["image/png", "image/jpeg"], + }); +}); + +test("lx.blob() with maxSize", () => { + const result = lx.blob({ maxSize: 1000000 }); + assert.deepEqual(result, { type: "blob", maxSize: 1000000 }); +}); + +test("lx.blob() with accept and maxSize", () => { + const result = lx.blob({ + accept: ["image/png", "image/jpeg"], + maxSize: 5000000, + }); + assert.deepEqual(result, { + type: "blob", + accept: ["image/png", "image/jpeg"], + maxSize: 5000000, + }); +}); + +test("lx.array() with string items", () => { + const result = lx.array(lx.string()); + assert.deepEqual(result, { type: "array", items: { type: "string" } }); +}); + +test("lx.array() with integer items", () => { + const result = lx.array(lx.integer()); + assert.deepEqual(result, { type: "array", items: { type: "integer" } }); +}); + +test("lx.array() with minLength", () => { + const result = lx.array(lx.string(), { minLength: 1 }); + assert.deepEqual(result, { + type: "array", + items: { type: "string" }, + minLength: 1, + }); +}); + +test("lx.array() with maxLength", () => { + const result = lx.array(lx.string(), { maxLength: 10 }); + assert.deepEqual(result, { + type: "array", + items: { type: "string" }, + maxLength: 10, + }); +}); + +test("lx.array() with minLength and maxLength", () => { + const result = lx.array(lx.string(), { minLength: 1, maxLength: 10 }); + assert.deepEqual(result, { + type: "array", + items: { type: "string" }, + minLength: 1, + maxLength: 10, + }); +}); + +test("lx.array() with required", () => { + const result = lx.array(lx.string(), { required: true }); + assert.deepEqual(result, { + type: "array", + items: { type: "string" }, + required: true, + }); +}); + +test("lx.token() with interaction event", () => { + const result = lx.token( + "Request that less content like the given feed item be shown in the feed", + ); + assert.deepEqual(result, { + type: "token", + description: + "Request that less content like the given feed item be shown in the feed", + }); +}); + +test("lx.token() with content mode", () => { + const result = lx.token( + "Declares the feed generator returns posts containing app.bsky.embed.video embeds", + ); + assert.deepEqual(result, { + type: "token", + description: + "Declares the feed generator returns posts containing app.bsky.embed.video embeds", + }); +}); + +test("lx.ref() with local definition", () => { + const result = lx.ref("#profileAssociated"); + assert.deepEqual(result, { + type: "ref", + ref: "#profileAssociated", + }); +}); + +test("lx.ref() with external schema", () => { + const result = lx.ref("com.atproto.label.defs#label"); + assert.deepEqual(result, { + type: "ref", + ref: "com.atproto.label.defs#label", + }); +}); + +test("lx.ref() with required option", () => { + const result = lx.ref("#profileView", { required: true }); + assert.deepEqual(result, { + type: "ref", + ref: "#profileView", + required: true, + }); +}); + +test("lx.ref() with nullable option", () => { + const result = lx.ref("#profileView", { nullable: true }); + assert.deepEqual(result, { + type: "ref", + ref: "#profileView", + nullable: true, + }); +}); + +test("lx.ref() with both required and nullable", () => { + const result = lx.ref("app.bsky.actor.defs#profileView", { + required: true, + nullable: true, + }); + assert.deepEqual(result, { + type: "ref", + ref: "app.bsky.actor.defs#profileView", + required: true, + nullable: true, + }); +}); + +test("lx.union() with local refs", () => { + const result = lx.union(["#reasonRepost", "#reasonPin"]); + assert.deepEqual(result, { + type: "union", + refs: ["#reasonRepost", "#reasonPin"], + }); +}); + +test("lx.union() with external refs", () => { + const result = lx.union([ + "app.bsky.embed.images#view", + "app.bsky.embed.video#view", + "app.bsky.embed.external#view", + "app.bsky.embed.record#view", + "app.bsky.embed.recordWithMedia#view", + ]); + assert.deepEqual(result, { + type: "union", + refs: [ + "app.bsky.embed.images#view", + "app.bsky.embed.video#view", + "app.bsky.embed.external#view", + "app.bsky.embed.record#view", + "app.bsky.embed.recordWithMedia#view", + ], + }); +}); + +test("lx.union() with closed option", () => { + const result = lx.union(["#postView", "#notFoundPost", "#blockedPost"], { + closed: true, + }); + assert.deepEqual(result, { + type: "union", + refs: ["#postView", "#notFoundPost", "#blockedPost"], + closed: true, + }); +}); + +test("lx.union() with closed: false (open union)", () => { + const result = lx.union(["#threadViewPost", "#notFoundPost"], { + closed: false, + }); + assert.deepEqual(result, { + type: "union", + refs: ["#threadViewPost", "#notFoundPost"], + closed: false, + }); +}); + +test("lx.params() with basic properties", () => { + const result = lx.params({ + q: lx.string(), + limit: lx.integer(), + }); + assert.deepEqual(result, { + type: "params", + properties: { + q: { type: "string" }, + limit: { type: "integer" }, + }, + }); +}); + +test("lx.params() with required properties", () => { + const result = lx.params({ + q: lx.string({ required: true }), + limit: lx.integer(), + }); + assert.deepEqual(result, { + type: "params", + properties: { + q: { type: "string", required: true }, + limit: { type: "integer" }, + }, + required: ["q"], + }); +}); + +test("lx.params() with property options", () => { + const result = lx.params({ + q: lx.string(), + limit: lx.integer({ minimum: 1, maximum: 100, default: 25 }), + cursor: lx.string(), + }); + assert.deepEqual(result, { + type: "params", + properties: { + q: { type: "string" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 25 }, + cursor: { type: "string" }, + }, + }); +}); + +test("lx.params() with array properties", () => { + const result = lx.params({ + tags: lx.array(lx.string()), + ids: lx.array(lx.integer()), + }); + assert.deepEqual(result, { + type: "params", + properties: { + tags: { type: "array", items: { type: "string" } }, + ids: { type: "array", items: { type: "integer" } }, + }, + }); +}); + +test("lx.params() real-world example from searchActors", () => { + const result = lx.params({ + q: lx.string({ required: true }), + limit: lx.integer({ minimum: 1, maximum: 100, default: 25 }), + cursor: lx.string(), + }); + assert.deepEqual(result, { + type: "params", + properties: { + q: { type: "string", required: true }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 25 }, + cursor: { type: "string" }, + }, + required: ["q"], + }); +}); + +test("lx.query() basic", () => { + const result = lx.query(); + assert.deepEqual(result, { type: "query" }); +}); + +test("lx.query() with description", () => { + const result = lx.query({ description: "Search for actors" }); + assert.deepEqual(result, { type: "query", description: "Search for actors" }); +}); + +test("lx.query() with parameters", () => { + const result = lx.query({ + parameters: lx.params({ + q: lx.string({ required: true }), + limit: lx.integer({ minimum: 1, maximum: 100, default: 25 }), + }), + }); + assert.deepEqual(result, { + type: "query", + parameters: { + type: "params", + properties: { + q: { type: "string", required: true }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 25 }, + }, + required: ["q"], + }, + }); +}); + +test("lx.query() with output", () => { + const result = lx.query({ + output: { + encoding: "application/json", + schema: lx.object({ + posts: lx.array(lx.ref("app.bsky.feed.defs#postView"), { + required: true, + }), + cursor: lx.string(), + }), + }, + }); + assert.deepEqual(result, { + type: "query", + output: { + encoding: "application/json", + schema: { + type: "object", + properties: { + posts: { + type: "array", + items: { type: "ref", ref: "app.bsky.feed.defs#postView" }, + required: true, + }, + cursor: { type: "string" }, + }, + required: ["posts"], + }, + }, + }); +}); + +test("lx.query() with errors", () => { + const result = lx.query({ + errors: [{ name: "BadQueryString" }], + }); + assert.deepEqual(result, { + type: "query", + errors: [{ name: "BadQueryString" }], + }); +}); + +test("lx.query() real-world example: searchPosts", () => { + const result = lx.query({ + description: "Find posts matching search criteria", + parameters: lx.params({ + q: lx.string({ required: true }), + sort: lx.string({ enum: ["top", "latest"], default: "latest" }), + limit: lx.integer({ minimum: 1, maximum: 100, default: 25 }), + cursor: lx.string(), + }), + output: { + encoding: "application/json", + schema: lx.object({ + cursor: lx.string(), + hitsTotal: lx.integer(), + posts: lx.array(lx.ref("app.bsky.feed.defs#postView"), { + required: true, + }), + }), + }, + errors: [{ name: "BadQueryString" }], + }); + assert.deepEqual(result, { + type: "query", + description: "Find posts matching search criteria", + parameters: { + type: "params", + properties: { + q: { type: "string", required: true }, + sort: { type: "string", enum: ["top", "latest"], default: "latest" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 25 }, + cursor: { type: "string" }, + }, + required: ["q"], + }, + output: { + encoding: "application/json", + schema: { + type: "object", + properties: { + cursor: { type: "string" }, + hitsTotal: { type: "integer" }, + posts: { + type: "array", + items: { type: "ref", ref: "app.bsky.feed.defs#postView" }, + required: true, + }, + }, + required: ["posts"], + }, + }, + errors: [{ name: "BadQueryString" }], + }); +}); + +test("lx.procedure() basic", () => { + const result = lx.procedure(); + assert.deepEqual(result, { type: "procedure" }); +}); + +test("lx.procedure() with description", () => { + const result = lx.procedure({ description: "Create a new post" }); + assert.deepEqual(result, { + type: "procedure", + description: "Create a new post", + }); +}); + +test("lx.procedure() with parameters", () => { + const result = lx.procedure({ + parameters: lx.params({ + validate: lx.boolean({ default: true }), + }), + }); + assert.deepEqual(result, { + type: "procedure", + parameters: { + type: "params", + properties: { + validate: { type: "boolean", default: true }, + }, + }, + }); +}); + +test("lx.procedure() with input", () => { + const result = lx.procedure({ + input: { + encoding: "application/json", + schema: lx.object({ + text: lx.string({ required: true, maxGraphemes: 300 }), + createdAt: lx.string({ format: "datetime" }), + }), + }, + }); + assert.deepEqual(result, { + type: "procedure", + input: { + encoding: "application/json", + schema: { + type: "object", + properties: { + text: { type: "string", required: true, maxGraphemes: 300 }, + createdAt: { type: "string", format: "datetime" }, + }, + required: ["text"], + }, + }, + }); +}); + +test("lx.procedure() with output", () => { + const result = lx.procedure({ + output: { + encoding: "application/json", + schema: lx.object({ + uri: lx.string({ required: true }), + cid: lx.string({ required: true }), + }), + }, + }); + assert.deepEqual(result, { + type: "procedure", + output: { + encoding: "application/json", + schema: { + type: "object", + properties: { + uri: { type: "string", required: true }, + cid: { type: "string", required: true }, + }, + required: ["uri", "cid"], + }, + }, + }); +}); + +test("lx.procedure() with errors", () => { + const result = lx.procedure({ + errors: [ + { name: "InvalidRequest" }, + { name: "RateLimitExceeded", description: "Too many requests" }, + ], + }); + assert.deepEqual(result, { + type: "procedure", + errors: [ + { name: "InvalidRequest" }, + { name: "RateLimitExceeded", description: "Too many requests" }, + ], + }); +}); + +test("lx.procedure() real-world example: createPost", () => { + const result = lx.procedure({ + description: "Create a post", + input: { + encoding: "application/json", + schema: lx.object({ + repo: lx.string({ required: true }), + collection: lx.string({ required: true }), + record: lx.unknown({ required: true }), + validate: lx.boolean({ default: true }), + }), + }, + output: { + encoding: "application/json", + schema: lx.object({ + uri: lx.string({ required: true }), + cid: lx.string({ required: true }), + }), + }, + errors: [{ name: "InvalidSwap" }, { name: "InvalidRecord" }], + }); + assert.deepEqual(result, { + type: "procedure", + description: "Create a post", + input: { + encoding: "application/json", + schema: { + type: "object", + properties: { + repo: { type: "string", required: true }, + collection: { type: "string", required: true }, + record: { type: "unknown", required: true }, + validate: { type: "boolean", default: true }, + }, + required: ["repo", "collection", "record"], + }, + }, + output: { + encoding: "application/json", + schema: { + type: "object", + properties: { + uri: { type: "string", required: true }, + cid: { type: "string", required: true }, + }, + required: ["uri", "cid"], + }, + }, + errors: [{ name: "InvalidSwap" }, { name: "InvalidRecord" }], + }); +}); + +test("lx.subscription() basic", () => { + const result = lx.subscription(); + assert.deepEqual(result, { type: "subscription" }); +}); + +test("lx.subscription() with description", () => { + const result = lx.subscription({ + description: "Repository event stream", + }); + assert.deepEqual(result, { + type: "subscription", + description: "Repository event stream", + }); +}); + +test("lx.subscription() with parameters", () => { + const result = lx.subscription({ + parameters: lx.params({ + cursor: lx.integer(), + }), + }); + assert.deepEqual(result, { + type: "subscription", + parameters: { + type: "params", + properties: { + cursor: { type: "integer" }, + }, + }, + }); +}); + +test("lx.subscription() with message", () => { + const result = lx.subscription({ + message: { + schema: lx.union(["#commit", "#identity", "#account"]), + }, + }); + assert.deepEqual(result, { + type: "subscription", + message: { + schema: { + type: "union", + refs: ["#commit", "#identity", "#account"], + }, + }, + }); +}); + +test("lx.subscription() with message description", () => { + const result = lx.subscription({ + message: { + description: "Event message types", + schema: lx.union(["#commit", "#handle", "#migrate"]), + }, + }); + assert.deepEqual(result, { + type: "subscription", + message: { + description: "Event message types", + schema: { + type: "union", + refs: ["#commit", "#handle", "#migrate"], + }, + }, + }); +}); + +test("lx.subscription() with errors", () => { + const result = lx.subscription({ + errors: [ + { name: "FutureCursor" }, + { name: "ConsumerTooSlow", description: "Consumer is too slow" }, + ], + }); + assert.deepEqual(result, { + type: "subscription", + errors: [ + { name: "FutureCursor" }, + { name: "ConsumerTooSlow", description: "Consumer is too slow" }, + ], + }); +}); + +test("lx.subscription() real-world example: subscribeRepos", () => { + const result = lx.subscription({ + description: "Repository event stream, aka Firehose endpoint", + parameters: lx.params({ + cursor: lx.integer(), + }), + message: { + description: "Represents an update of repository state", + schema: lx.union([ + "#commit", + "#identity", + "#account", + "#handle", + "#migrate", + "#tombstone", + "#info", + ]), + }, + errors: [{ name: "FutureCursor" }, { name: "ConsumerTooSlow" }], + }); + assert.deepEqual(result, { + type: "subscription", + description: "Repository event stream, aka Firehose endpoint", + parameters: { + type: "params", + properties: { + cursor: { + type: "integer", + }, + }, + }, + message: { + description: "Represents an update of repository state", + schema: { + type: "union", + refs: [ + "#commit", + "#identity", + "#account", + "#handle", + "#migrate", + "#tombstone", + "#info", + ], + }, + }, + errors: [{ name: "FutureCursor" }, { name: "ConsumerTooSlow" }], + }); +}); diff --git a/tests/base-case.test.ts b/tests/base-case.test.ts deleted file mode 100644 index 5744b40..0000000 --- a/tests/base-case.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { assertEquals } from "@std/assert"; -import { lx } from "../src/lib.ts"; - -Deno.test("app.bsky.actor.profile", () => { - const profileNamespace = lx.namespace("app.bsky.actor.profile", { - main: lx.record({ - key: "self", - record: lx.object({ - displayName: lx.string({ maxLength: 64, maxGraphemes: 64 }), - description: lx.string({ maxLength: 256, maxGraphemes: 256 }), - }), - }), - }); - - assertEquals(profileNamespace, { - lexicon: 1, - id: "app.bsky.actor.profile", - defs: { - main: { - type: "record", - key: "self", - record: { - type: "object", - properties: { - displayName: { - type: "string", - maxLength: 64, - maxGraphemes: 64, - }, - description: { - type: "string", - maxLength: 256, - maxGraphemes: 256, - }, - }, - }, - }, - }, - }); -}); diff --git a/tests/bsky-actor.test.ts b/tests/bsky-actor.test.ts deleted file mode 100644 index 66be973..0000000 --- a/tests/bsky-actor.test.ts +++ /dev/null @@ -1,867 +0,0 @@ -import { assertEquals } from "@std/assert"; -import { lx } from "../src/lib.ts"; - -Deno.test("app.bsky.actor.defs - profileViewBasic", () => { - const profileViewBasic = lx.object({ - did: lx.string({ required: true, format: "did" }), - handle: lx.string({ required: true, format: "handle" }), - displayName: lx.string({ maxGraphemes: 64, maxLength: 640 }), - pronouns: lx.string(), - avatar: lx.string({ format: "uri" }), - associated: lx.ref("#profileAssociated"), - viewer: lx.ref("#viewerState"), - labels: lx.array(lx.ref("com.atproto.label.defs#label")), - createdAt: lx.string({ format: "datetime" }), - verification: lx.ref("#verificationState"), - status: lx.ref("#statusView"), - }); - - assertEquals(profileViewBasic, { - type: "object", - properties: { - did: { type: "string", required: true, format: "did" }, - handle: { type: "string", required: true, format: "handle" }, - displayName: { type: "string", maxGraphemes: 64, maxLength: 640 }, - pronouns: { type: "string" }, - avatar: { type: "string", format: "uri" }, - associated: { type: "ref", ref: "#profileAssociated" }, - viewer: { type: "ref", ref: "#viewerState" }, - labels: { - type: "array", - items: { type: "ref", ref: "com.atproto.label.defs#label" }, - }, - createdAt: { type: "string", format: "datetime" }, - verification: { type: "ref", ref: "#verificationState" }, - status: { type: "ref", ref: "#statusView" }, - }, - required: ["did", "handle"], - }); -}); - -Deno.test("app.bsky.actor.defs - profileView", () => { - const profileView = lx.object({ - did: lx.string({ required: true, format: "did" }), - handle: lx.string({ required: true, format: "handle" }), - displayName: lx.string({ maxGraphemes: 64, maxLength: 640 }), - pronouns: lx.string(), - description: lx.string({ maxGraphemes: 256, maxLength: 2560 }), - avatar: lx.string({ format: "uri" }), - associated: lx.ref("#profileAssociated"), - indexedAt: lx.string({ format: "datetime" }), - createdAt: lx.string({ format: "datetime" }), - viewer: lx.ref("#viewerState"), - labels: lx.array(lx.ref("com.atproto.label.defs#label")), - verification: lx.ref("#verificationState"), - status: lx.ref("#statusView"), - }); - - assertEquals(profileView, { - type: "object", - properties: { - did: { type: "string", required: true, format: "did" }, - handle: { type: "string", required: true, format: "handle" }, - displayName: { type: "string", maxGraphemes: 64, maxLength: 640 }, - pronouns: { type: "string" }, - description: { type: "string", maxGraphemes: 256, maxLength: 2560 }, - avatar: { type: "string", format: "uri" }, - associated: { type: "ref", ref: "#profileAssociated" }, - indexedAt: { type: "string", format: "datetime" }, - createdAt: { type: "string", format: "datetime" }, - viewer: { type: "ref", ref: "#viewerState" }, - labels: { - type: "array", - items: { type: "ref", ref: "com.atproto.label.defs#label" }, - }, - verification: { type: "ref", ref: "#verificationState" }, - status: { type: "ref", ref: "#statusView" }, - }, - required: ["did", "handle"], - }); -}); - -Deno.test("app.bsky.actor.defs - profileViewDetailed", () => { - const profileViewDetailed = lx.object({ - did: lx.string({ required: true, format: "did" }), - handle: lx.string({ required: true, format: "handle" }), - displayName: lx.string({ maxGraphemes: 64, maxLength: 640 }), - description: lx.string({ maxGraphemes: 256, maxLength: 2560 }), - pronouns: lx.string(), - website: lx.string({ format: "uri" }), - avatar: lx.string({ format: "uri" }), - banner: lx.string({ format: "uri" }), - followersCount: lx.integer(), - followsCount: lx.integer(), - postsCount: lx.integer(), - associated: lx.ref("#profileAssociated"), - joinedViaStarterPack: lx.ref("app.bsky.graph.defs#starterPackViewBasic"), - indexedAt: lx.string({ format: "datetime" }), - createdAt: lx.string({ format: "datetime" }), - viewer: lx.ref("#viewerState"), - labels: lx.array(lx.ref("com.atproto.label.defs#label")), - pinnedPost: lx.ref("com.atproto.repo.strongRef"), - verification: lx.ref("#verificationState"), - status: lx.ref("#statusView"), - }); - - assertEquals(profileViewDetailed, { - type: "object", - properties: { - did: { type: "string", required: true, format: "did" }, - handle: { type: "string", required: true, format: "handle" }, - displayName: { type: "string", maxGraphemes: 64, maxLength: 640 }, - description: { type: "string", maxGraphemes: 256, maxLength: 2560 }, - pronouns: { type: "string" }, - website: { type: "string", format: "uri" }, - avatar: { type: "string", format: "uri" }, - banner: { type: "string", format: "uri" }, - followersCount: { type: "integer" }, - followsCount: { type: "integer" }, - postsCount: { type: "integer" }, - associated: { type: "ref", ref: "#profileAssociated" }, - joinedViaStarterPack: { - type: "ref", - ref: "app.bsky.graph.defs#starterPackViewBasic", - }, - indexedAt: { type: "string", format: "datetime" }, - createdAt: { type: "string", format: "datetime" }, - viewer: { type: "ref", ref: "#viewerState" }, - labels: { - type: "array", - items: { type: "ref", ref: "com.atproto.label.defs#label" }, - }, - pinnedPost: { type: "ref", ref: "com.atproto.repo.strongRef" }, - verification: { type: "ref", ref: "#verificationState" }, - status: { type: "ref", ref: "#statusView" }, - }, - required: ["did", "handle"], - }); -}); - -Deno.test("app.bsky.actor.defs - profileAssociated", () => { - const profileAssociated = lx.object({ - lists: lx.integer(), - feedgens: lx.integer(), - starterPacks: lx.integer(), - labeler: lx.boolean(), - chat: lx.ref("#profileAssociatedChat"), - activitySubscription: lx.ref("#profileAssociatedActivitySubscription"), - }); - - assertEquals(profileAssociated, { - type: "object", - properties: { - lists: { type: "integer" }, - feedgens: { type: "integer" }, - starterPacks: { type: "integer" }, - labeler: { type: "boolean" }, - chat: { type: "ref", ref: "#profileAssociatedChat" }, - activitySubscription: { - type: "ref", - ref: "#profileAssociatedActivitySubscription", - }, - }, - }); -}); - -Deno.test("app.bsky.actor.defs - profileAssociatedChat", () => { - const profileAssociatedChat = lx.object({ - allowIncoming: lx.string({ - required: true, - knownValues: ["all", "none", "following"], - }), - }); - - assertEquals(profileAssociatedChat, { - type: "object", - properties: { - allowIncoming: { - type: "string", - required: true, - knownValues: ["all", "none", "following"], - }, - }, - required: ["allowIncoming"], - }); -}); - -Deno.test("app.bsky.actor.defs - profileAssociatedActivitySubscription", () => { - const profileAssociatedActivitySubscription = lx.object({ - allowSubscriptions: lx.string({ - required: true, - knownValues: ["followers", "mutuals", "none"], - }), - }); - - assertEquals(profileAssociatedActivitySubscription, { - type: "object", - properties: { - allowSubscriptions: { - type: "string", - required: true, - knownValues: ["followers", "mutuals", "none"], - }, - }, - required: ["allowSubscriptions"], - }); -}); - -Deno.test("app.bsky.actor.defs - viewerState", () => { - const viewerState = lx.object({ - muted: lx.boolean(), - mutedByList: lx.ref("app.bsky.graph.defs#listViewBasic"), - blockedBy: lx.boolean(), - blocking: lx.string({ format: "at-uri" }), - blockingByList: lx.ref("app.bsky.graph.defs#listViewBasic"), - following: lx.string({ format: "at-uri" }), - followedBy: lx.string({ format: "at-uri" }), - knownFollowers: lx.ref("#knownFollowers"), - activitySubscription: lx.ref( - "app.bsky.notification.defs#activitySubscription", - ), - }); - - assertEquals(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" }, - blockingByList: { type: "ref", ref: "app.bsky.graph.defs#listViewBasic" }, - following: { type: "string", format: "at-uri" }, - followedBy: { type: "string", format: "at-uri" }, - knownFollowers: { type: "ref", ref: "#knownFollowers" }, - activitySubscription: { - type: "ref", - ref: "app.bsky.notification.defs#activitySubscription", - }, - }, - }); -}); - -Deno.test("app.bsky.actor.defs - knownFollowers", () => { - const knownFollowers = lx.object({ - count: lx.integer({ required: true }), - followers: lx.array(lx.ref("#profileViewBasic"), { - required: true, - minLength: 0, - maxLength: 5, - }), - }); - - assertEquals(knownFollowers, { - type: "object", - properties: { - count: { type: "integer", required: true }, - followers: { - type: "array", - items: { type: "ref", ref: "#profileViewBasic" }, - required: true, - minLength: 0, - maxLength: 5, - }, - }, - required: ["count", "followers"], - }); -}); - -Deno.test("app.bsky.actor.defs - verificationState", () => { - const verificationState = lx.object({ - verifications: lx.array(lx.ref("#verificationView"), { required: true }), - verifiedStatus: lx.string({ - required: true, - knownValues: ["valid", "invalid", "none"], - }), - trustedVerifierStatus: lx.string({ - required: true, - knownValues: ["valid", "invalid", "none"], - }), - }); - - assertEquals(verificationState, { - type: "object", - properties: { - verifications: { - type: "array", - items: { type: "ref", ref: "#verificationView" }, - required: true, - }, - verifiedStatus: { - type: "string", - required: true, - knownValues: ["valid", "invalid", "none"], - }, - trustedVerifierStatus: { - type: "string", - required: true, - knownValues: ["valid", "invalid", "none"], - }, - }, - required: ["verifications", "verifiedStatus", "trustedVerifierStatus"], - }); -}); - -Deno.test("app.bsky.actor.defs - verificationView", () => { - const verificationView = lx.object({ - issuer: lx.string({ required: true, format: "did" }), - uri: lx.string({ required: true, format: "at-uri" }), - isValid: lx.boolean({ required: true }), - createdAt: lx.string({ required: true, format: "datetime" }), - }); - - assertEquals(verificationView, { - type: "object", - properties: { - issuer: { type: "string", required: true, format: "did" }, - uri: { type: "string", required: true, format: "at-uri" }, - isValid: { type: "boolean", required: true }, - createdAt: { type: "string", required: true, format: "datetime" }, - }, - required: ["issuer", "uri", "isValid", "createdAt"], - }); -}); - -Deno.test("app.bsky.actor.defs - preferences", () => { - const preferences = lx.array( - lx.union([ - "#adultContentPref", - "#contentLabelPref", - "#savedFeedsPref", - "#savedFeedsPrefV2", - "#personalDetailsPref", - "#feedViewPref", - "#threadViewPref", - "#interestsPref", - "#mutedWordsPref", - "#hiddenPostsPref", - "#bskyAppStatePref", - "#labelersPref", - "#postInteractionSettingsPref", - "#verificationPrefs", - ]), - ); - - assertEquals(preferences, { - type: "array", - items: { - type: "union", - refs: [ - "#adultContentPref", - "#contentLabelPref", - "#savedFeedsPref", - "#savedFeedsPrefV2", - "#personalDetailsPref", - "#feedViewPref", - "#threadViewPref", - "#interestsPref", - "#mutedWordsPref", - "#hiddenPostsPref", - "#bskyAppStatePref", - "#labelersPref", - "#postInteractionSettingsPref", - "#verificationPrefs", - ], - }, - }); -}); - -Deno.test("app.bsky.actor.defs - adultContentPref", () => { - const adultContentPref = lx.object({ - enabled: lx.boolean({ required: true, default: false }), - }); - - assertEquals(adultContentPref, { - type: "object", - properties: { - enabled: { type: "boolean", required: true, default: false }, - }, - required: ["enabled"], - }); -}); - -Deno.test("app.bsky.actor.defs - contentLabelPref", () => { - const contentLabelPref = lx.object({ - labelerDid: lx.string({ format: "did" }), - label: lx.string({ required: true }), - visibility: lx.string({ - required: true, - knownValues: ["ignore", "show", "warn", "hide"], - }), - }); - - assertEquals(contentLabelPref, { - type: "object", - properties: { - labelerDid: { type: "string", format: "did" }, - label: { type: "string", required: true }, - visibility: { - type: "string", - required: true, - knownValues: ["ignore", "show", "warn", "hide"], - }, - }, - required: ["label", "visibility"], - }); -}); - -Deno.test("app.bsky.actor.defs - savedFeed", () => { - const savedFeed = lx.object({ - id: lx.string({ required: true }), - type: lx.string({ - required: true, - knownValues: ["feed", "list", "timeline"], - }), - value: lx.string({ required: true }), - pinned: lx.boolean({ required: true }), - }); - - assertEquals(savedFeed, { - type: "object", - properties: { - id: { type: "string", required: true }, - type: { - type: "string", - required: true, - knownValues: ["feed", "list", "timeline"], - }, - value: { type: "string", required: true }, - pinned: { type: "boolean", required: true }, - }, - required: ["id", "type", "value", "pinned"], - }); -}); - -Deno.test("app.bsky.actor.defs - savedFeedsPrefV2", () => { - const savedFeedsPrefV2 = lx.object({ - items: lx.array(lx.ref("app.bsky.actor.defs#savedFeed"), { - required: true, - }), - }); - - assertEquals(savedFeedsPrefV2, { - type: "object", - properties: { - items: { - type: "array", - items: { type: "ref", ref: "app.bsky.actor.defs#savedFeed" }, - required: true, - }, - }, - required: ["items"], - }); -}); - -Deno.test("app.bsky.actor.defs - savedFeedsPref", () => { - const savedFeedsPref = lx.object({ - pinned: lx.array(lx.string({ format: "at-uri" }), { required: true }), - saved: lx.array(lx.string({ format: "at-uri" }), { required: true }), - timelineIndex: lx.integer(), - }); - - assertEquals(savedFeedsPref, { - type: "object", - properties: { - pinned: { - type: "array", - items: { type: "string", format: "at-uri" }, - required: true, - }, - saved: { - type: "array", - items: { type: "string", format: "at-uri" }, - required: true, - }, - timelineIndex: { type: "integer" }, - }, - required: ["pinned", "saved"], - }); -}); - -Deno.test("app.bsky.actor.defs - personalDetailsPref", () => { - const personalDetailsPref = lx.object({ - birthDate: lx.string({ format: "datetime" }), - }); - - assertEquals(personalDetailsPref, { - type: "object", - properties: { - birthDate: { type: "string", format: "datetime" }, - }, - }); -}); - -Deno.test("app.bsky.actor.defs - feedViewPref", () => { - const feedViewPref = lx.object({ - feed: lx.string({ required: true }), - hideReplies: lx.boolean(), - hideRepliesByUnfollowed: lx.boolean({ default: true }), - hideRepliesByLikeCount: lx.integer(), - hideReposts: lx.boolean(), - hideQuotePosts: lx.boolean(), - }); - - assertEquals(feedViewPref, { - type: "object", - properties: { - feed: { type: "string", required: true }, - hideReplies: { type: "boolean" }, - hideRepliesByUnfollowed: { type: "boolean", default: true }, - hideRepliesByLikeCount: { type: "integer" }, - hideReposts: { type: "boolean" }, - hideQuotePosts: { type: "boolean" }, - }, - required: ["feed"], - }); -}); - -Deno.test("app.bsky.actor.defs - threadViewPref", () => { - const threadViewPref = lx.object({ - sort: lx.string({ - knownValues: ["oldest", "newest", "most-likes", "random", "hotness"], - }), - prioritizeFollowedUsers: lx.boolean(), - }); - - assertEquals(threadViewPref, { - type: "object", - properties: { - sort: { - type: "string", - knownValues: ["oldest", "newest", "most-likes", "random", "hotness"], - }, - prioritizeFollowedUsers: { type: "boolean" }, - }, - }); -}); - -Deno.test("app.bsky.actor.defs - interestsPref", () => { - const interestsPref = lx.object({ - tags: lx.array(lx.string({ maxLength: 640, maxGraphemes: 64 }), { - required: true, - maxLength: 100, - }), - }); - - assertEquals(interestsPref, { - type: "object", - properties: { - tags: { - type: "array", - items: { type: "string", maxLength: 640, maxGraphemes: 64 }, - required: true, - maxLength: 100, - }, - }, - required: ["tags"], - }); -}); - -Deno.test("app.bsky.actor.defs - mutedWordTarget", () => { - const mutedWordTarget = lx.string({ - knownValues: ["content", "tag"], - maxLength: 640, - maxGraphemes: 64, - }); - - assertEquals(mutedWordTarget, { - type: "string", - knownValues: ["content", "tag"], - maxLength: 640, - maxGraphemes: 64, - }); -}); - -Deno.test("app.bsky.actor.defs - mutedWord", () => { - const mutedWord = lx.object({ - id: lx.string(), - value: lx.string({ required: true, maxLength: 10000, maxGraphemes: 1000 }), - targets: lx.array(lx.ref("app.bsky.actor.defs#mutedWordTarget"), { - required: true, - }), - actorTarget: lx.string({ - knownValues: ["all", "exclude-following"], - default: "all", - }), - expiresAt: lx.string({ format: "datetime" }), - }); - - assertEquals(mutedWord, { - type: "object", - properties: { - id: { type: "string" }, - value: { - type: "string", - required: true, - maxLength: 10000, - maxGraphemes: 1000, - }, - targets: { - type: "array", - items: { type: "ref", ref: "app.bsky.actor.defs#mutedWordTarget" }, - required: true, - }, - actorTarget: { - type: "string", - knownValues: ["all", "exclude-following"], - default: "all", - }, - expiresAt: { type: "string", format: "datetime" }, - }, - required: ["value", "targets"], - }); -}); - -Deno.test("app.bsky.actor.defs - mutedWordsPref", () => { - const mutedWordsPref = lx.object({ - items: lx.array(lx.ref("app.bsky.actor.defs#mutedWord"), { - required: true, - }), - }); - - assertEquals(mutedWordsPref, { - type: "object", - properties: { - items: { - type: "array", - items: { type: "ref", ref: "app.bsky.actor.defs#mutedWord" }, - required: true, - }, - }, - required: ["items"], - }); -}); - -Deno.test("app.bsky.actor.defs - hiddenPostsPref", () => { - const hiddenPostsPref = lx.object({ - items: lx.array(lx.string({ format: "at-uri" }), { required: true }), - }); - - assertEquals(hiddenPostsPref, { - type: "object", - properties: { - items: { - type: "array", - items: { type: "string", format: "at-uri" }, - required: true, - }, - }, - required: ["items"], - }); -}); - -Deno.test("app.bsky.actor.defs - labelersPref", () => { - const labelersPref = lx.object({ - labelers: lx.array(lx.ref("#labelerPrefItem"), { required: true }), - }); - - assertEquals(labelersPref, { - type: "object", - properties: { - labelers: { - type: "array", - items: { type: "ref", ref: "#labelerPrefItem" }, - required: true, - }, - }, - required: ["labelers"], - }); -}); - -Deno.test("app.bsky.actor.defs - labelerPrefItem", () => { - const labelerPrefItem = lx.object({ - did: lx.string({ required: true, format: "did" }), - }); - - assertEquals(labelerPrefItem, { - type: "object", - properties: { - did: { type: "string", required: true, format: "did" }, - }, - required: ["did"], - }); -}); - -Deno.test("app.bsky.actor.defs - bskyAppStatePref", () => { - const bskyAppStatePref = lx.object({ - activeProgressGuide: lx.ref("#bskyAppProgressGuide"), - queuedNudges: lx.array(lx.string({ maxLength: 100 }), { maxLength: 1000 }), - nuxs: lx.array(lx.ref("app.bsky.actor.defs#nux"), { maxLength: 100 }), - }); - - assertEquals(bskyAppStatePref, { - type: "object", - properties: { - activeProgressGuide: { type: "ref", ref: "#bskyAppProgressGuide" }, - queuedNudges: { - type: "array", - items: { type: "string", maxLength: 100 }, - maxLength: 1000, - }, - nuxs: { - type: "array", - items: { type: "ref", ref: "app.bsky.actor.defs#nux" }, - maxLength: 100, - }, - }, - }); -}); - -Deno.test("app.bsky.actor.defs - bskyAppProgressGuide", () => { - const bskyAppProgressGuide = lx.object({ - guide: lx.string({ required: true, maxLength: 100 }), - }); - - assertEquals(bskyAppProgressGuide, { - type: "object", - properties: { - guide: { type: "string", required: true, maxLength: 100 }, - }, - required: ["guide"], - }); -}); - -Deno.test("app.bsky.actor.defs - nux", () => { - const nux = lx.object({ - id: lx.string({ required: true, maxLength: 100 }), - completed: lx.boolean({ required: true, default: false }), - data: lx.string({ maxLength: 3000, maxGraphemes: 300 }), - expiresAt: lx.string({ format: "datetime" }), - }); - - assertEquals(nux, { - type: "object", - properties: { - id: { type: "string", required: true, maxLength: 100 }, - completed: { type: "boolean", required: true, default: false }, - data: { type: "string", maxLength: 3000, maxGraphemes: 300 }, - expiresAt: { type: "string", format: "datetime" }, - }, - required: ["id", "completed"], - }); -}); - -Deno.test("app.bsky.actor.defs - verificationPrefs", () => { - const verificationPrefs = lx.object({ - hideBadges: lx.boolean({ default: false }), - }); - - assertEquals(verificationPrefs, { - type: "object", - properties: { - hideBadges: { type: "boolean", default: false }, - }, - }); -}); - -Deno.test("app.bsky.actor.defs - postInteractionSettingsPref", () => { - const postInteractionSettingsPref = lx.object({ - threadgateAllowRules: lx.array( - lx.union([ - "app.bsky.feed.threadgate#mentionRule", - "app.bsky.feed.threadgate#followerRule", - "app.bsky.feed.threadgate#followingRule", - "app.bsky.feed.threadgate#listRule", - ]), - { maxLength: 5 }, - ), - postgateEmbeddingRules: lx.array( - lx.union(["app.bsky.feed.postgate#disableRule"]), - { maxLength: 5 }, - ), - }); - - assertEquals(postInteractionSettingsPref, { - type: "object", - properties: { - threadgateAllowRules: { - type: "array", - items: { - type: "union", - refs: [ - "app.bsky.feed.threadgate#mentionRule", - "app.bsky.feed.threadgate#followerRule", - "app.bsky.feed.threadgate#followingRule", - "app.bsky.feed.threadgate#listRule", - ], - }, - maxLength: 5, - }, - postgateEmbeddingRules: { - type: "array", - items: { - type: "union", - refs: ["app.bsky.feed.postgate#disableRule"], - }, - maxLength: 5, - }, - }, - }); -}); - -Deno.test("app.bsky.actor.defs - statusView", () => { - const statusView = lx.object({ - status: lx.string({ - required: true, - knownValues: ["app.bsky.actor.status#live"], - }), - record: lx.unknown({ required: true }), - embed: lx.union(["app.bsky.embed.external#view"]), - expiresAt: lx.string({ format: "datetime" }), - isActive: lx.boolean(), - }); - - assertEquals(statusView, { - type: "object", - properties: { - status: { - type: "string", - required: true, - knownValues: ["app.bsky.actor.status#live"], - }, - record: { type: "unknown", required: true }, - embed: { - type: "union", - refs: ["app.bsky.embed.external#view"], - }, - expiresAt: { type: "string", format: "datetime" }, - isActive: { type: "boolean" }, - }, - required: ["status", "record"], - }); -}); - -Deno.test("app.bsky.actor.defs - full namespace", () => { - const actorDefs = lx.namespace("app.bsky.actor.defs", { - profileViewBasic: lx.object({ - did: lx.string({ required: true, format: "did" }), - handle: lx.string({ required: true, format: "handle" }), - displayName: lx.string({ maxGraphemes: 64, maxLength: 640 }), - pronouns: lx.string(), - avatar: lx.string({ format: "uri" }), - associated: lx.ref("#profileAssociated"), - viewer: lx.ref("#viewerState"), - labels: lx.array(lx.ref("com.atproto.label.defs#label")), - createdAt: lx.string({ format: "datetime" }), - verification: lx.ref("#verificationState"), - status: lx.ref("#statusView"), - }), - viewerState: lx.object({ - muted: lx.boolean(), - mutedByList: lx.ref("app.bsky.graph.defs#listViewBasic"), - blockedBy: lx.boolean(), - blocking: lx.string({ format: "at-uri" }), - blockingByList: lx.ref("app.bsky.graph.defs#listViewBasic"), - following: lx.string({ format: "at-uri" }), - followedBy: lx.string({ format: "at-uri" }), - knownFollowers: lx.ref("#knownFollowers"), - activitySubscription: lx.ref( - "app.bsky.notification.defs#activitySubscription", - ), - }), - }); - - assertEquals(actorDefs.lexicon, 1); - assertEquals(actorDefs.id, "app.bsky.actor.defs"); - assertEquals(actorDefs.defs.profileViewBasic.type, "object"); - assertEquals(actorDefs.defs.viewerState.type, "object"); -}); diff --git a/tests/bsky-feed.test.ts b/tests/bsky-feed.test.ts deleted file mode 100644 index cb25fe7..0000000 --- a/tests/bsky-feed.test.ts +++ /dev/null @@ -1,681 +0,0 @@ -import { assertEquals } from "@std/assert"; -import { lx } from "../src/lib.ts"; - -Deno.test("app.bsky.feed.defs - postView", () => { - const postView = lx.object({ - uri: lx.string({ required: true, format: "at-uri" }), - cid: lx.string({ required: true, format: "cid" }), - author: lx.ref("app.bsky.actor.defs#profileViewBasic", { required: true }), - record: lx.unknown({ required: true }), - embed: lx.union([ - "app.bsky.embed.images#view", - "app.bsky.embed.video#view", - "app.bsky.embed.external#view", - "app.bsky.embed.record#view", - "app.bsky.embed.recordWithMedia#view", - ]), - bookmarkCount: lx.integer(), - replyCount: lx.integer(), - repostCount: lx.integer(), - likeCount: lx.integer(), - quoteCount: lx.integer(), - indexedAt: lx.string({ required: true, format: "datetime" }), - viewer: lx.ref("#viewerState"), - labels: lx.array(lx.ref("com.atproto.label.defs#label")), - threadgate: lx.ref("#threadgateView"), - }); - - assertEquals(postView, { - type: "object", - properties: { - uri: { type: "string", required: true, format: "at-uri" }, - cid: { type: "string", required: true, format: "cid" }, - author: { - type: "ref", - ref: "app.bsky.actor.defs#profileViewBasic", - required: true, - }, - record: { type: "unknown", required: true }, - embed: { - type: "union", - refs: [ - "app.bsky.embed.images#view", - "app.bsky.embed.video#view", - "app.bsky.embed.external#view", - "app.bsky.embed.record#view", - "app.bsky.embed.recordWithMedia#view", - ], - }, - bookmarkCount: { type: "integer" }, - replyCount: { type: "integer" }, - repostCount: { type: "integer" }, - likeCount: { type: "integer" }, - quoteCount: { type: "integer" }, - indexedAt: { type: "string", required: true, format: "datetime" }, - viewer: { type: "ref", ref: "#viewerState" }, - labels: { - type: "array", - items: { type: "ref", ref: "com.atproto.label.defs#label" }, - }, - threadgate: { type: "ref", ref: "#threadgateView" }, - }, - required: ["uri", "cid", "author", "record", "indexedAt"], - }); -}); - -Deno.test("app.bsky.feed.defs - viewerState", () => { - const viewerState = lx.object({ - repost: lx.string({ format: "at-uri" }), - like: lx.string({ format: "at-uri" }), - bookmarked: lx.boolean(), - threadMuted: lx.boolean(), - replyDisabled: lx.boolean(), - embeddingDisabled: lx.boolean(), - pinned: lx.boolean(), - }); - - assertEquals(viewerState, { - type: "object", - properties: { - repost: { type: "string", format: "at-uri" }, - like: { type: "string", format: "at-uri" }, - bookmarked: { type: "boolean" }, - threadMuted: { type: "boolean" }, - replyDisabled: { type: "boolean" }, - embeddingDisabled: { type: "boolean" }, - pinned: { type: "boolean" }, - }, - }); -}); - -Deno.test("app.bsky.feed.defs - threadContext", () => { - const threadContext = lx.object({ - rootAuthorLike: lx.string({ format: "at-uri" }), - }); - - assertEquals(threadContext, { - type: "object", - properties: { - rootAuthorLike: { type: "string", format: "at-uri" }, - }, - }); -}); - -Deno.test("app.bsky.feed.defs - feedViewPost", () => { - const feedViewPost = lx.object({ - post: lx.ref("#postView", { required: true }), - reply: lx.ref("#replyRef"), - reason: lx.union(["#reasonRepost", "#reasonPin"]), - feedContext: lx.string({ maxLength: 2000 }), - reqId: lx.string({ maxLength: 100 }), - }); - - assertEquals(feedViewPost, { - type: "object", - properties: { - post: { type: "ref", ref: "#postView", required: true }, - reply: { type: "ref", ref: "#replyRef" }, - reason: { - type: "union", - refs: ["#reasonRepost", "#reasonPin"], - }, - feedContext: { type: "string", maxLength: 2000 }, - reqId: { type: "string", maxLength: 100 }, - }, - required: ["post"], - }); -}); - -Deno.test("app.bsky.feed.defs - replyRef", () => { - const replyRef = lx.object({ - root: lx.union(["#postView", "#notFoundPost", "#blockedPost"], { - required: true, - }), - parent: lx.union(["#postView", "#notFoundPost", "#blockedPost"], { - required: true, - }), - grandparentAuthor: lx.ref("app.bsky.actor.defs#profileViewBasic"), - }); - - assertEquals(replyRef, { - type: "object", - properties: { - root: { - type: "union", - refs: ["#postView", "#notFoundPost", "#blockedPost"], - required: true, - }, - parent: { - type: "union", - refs: ["#postView", "#notFoundPost", "#blockedPost"], - required: true, - }, - grandparentAuthor: { - type: "ref", - ref: "app.bsky.actor.defs#profileViewBasic", - }, - }, - required: ["root", "parent"], - }); -}); - -Deno.test("app.bsky.feed.defs - reasonRepost", () => { - const reasonRepost = lx.object({ - by: lx.ref("app.bsky.actor.defs#profileViewBasic", { required: true }), - uri: lx.string({ format: "at-uri" }), - cid: lx.string({ format: "cid" }), - indexedAt: lx.string({ required: true, format: "datetime" }), - }); - - assertEquals(reasonRepost, { - type: "object", - properties: { - by: { - type: "ref", - ref: "app.bsky.actor.defs#profileViewBasic", - required: true, - }, - uri: { type: "string", format: "at-uri" }, - cid: { type: "string", format: "cid" }, - indexedAt: { type: "string", required: true, format: "datetime" }, - }, - required: ["by", "indexedAt"], - }); -}); - -Deno.test("app.bsky.feed.defs - reasonPin", () => { - const reasonPin = lx.object({}); - - assertEquals(reasonPin, { - type: "object", - properties: {}, - }); -}); - -Deno.test("app.bsky.feed.defs - threadViewPost", () => { - const threadViewPost = lx.object({ - post: lx.ref("#postView", { required: true }), - parent: lx.union(["#threadViewPost", "#notFoundPost", "#blockedPost"]), - replies: lx.array( - lx.union(["#threadViewPost", "#notFoundPost", "#blockedPost"]), - ), - threadContext: lx.ref("#threadContext"), - }); - - assertEquals(threadViewPost, { - type: "object", - properties: { - post: { type: "ref", ref: "#postView", required: true }, - parent: { - type: "union", - refs: ["#threadViewPost", "#notFoundPost", "#blockedPost"], - }, - replies: { - type: "array", - items: { - type: "union", - refs: ["#threadViewPost", "#notFoundPost", "#blockedPost"], - }, - }, - threadContext: { type: "ref", ref: "#threadContext" }, - }, - required: ["post"], - }); -}); - -Deno.test("app.bsky.feed.defs - notFoundPost", () => { - const notFoundPost = lx.object({ - uri: lx.string({ required: true, format: "at-uri" }), - notFound: lx.boolean({ required: true, const: true }), - }); - - assertEquals(notFoundPost, { - type: "object", - properties: { - uri: { type: "string", required: true, format: "at-uri" }, - notFound: { type: "boolean", required: true, const: true }, - }, - required: ["uri", "notFound"], - }); -}); - -Deno.test("app.bsky.feed.defs - blockedPost", () => { - const blockedPost = lx.object({ - uri: lx.string({ required: true, format: "at-uri" }), - blocked: lx.boolean({ required: true, const: true }), - author: lx.ref("#blockedAuthor", { required: true }), - }); - - assertEquals(blockedPost, { - type: "object", - properties: { - uri: { type: "string", required: true, format: "at-uri" }, - blocked: { type: "boolean", required: true, const: true }, - author: { type: "ref", ref: "#blockedAuthor", required: true }, - }, - required: ["uri", "blocked", "author"], - }); -}); - -Deno.test("app.bsky.feed.defs - blockedAuthor", () => { - const blockedAuthor = lx.object({ - did: lx.string({ required: true, format: "did" }), - viewer: lx.ref("app.bsky.actor.defs#viewerState"), - }); - - assertEquals(blockedAuthor, { - type: "object", - properties: { - did: { type: "string", required: true, format: "did" }, - viewer: { type: "ref", ref: "app.bsky.actor.defs#viewerState" }, - }, - required: ["did"], - }); -}); - -Deno.test("app.bsky.feed.defs - generatorView", () => { - const generatorView = lx.object({ - uri: lx.string({ required: true, format: "at-uri" }), - cid: lx.string({ required: true, format: "cid" }), - did: lx.string({ required: true, format: "did" }), - creator: lx.ref("app.bsky.actor.defs#profileView", { required: true }), - displayName: lx.string({ required: true }), - description: lx.string({ maxGraphemes: 300, maxLength: 3000 }), - descriptionFacets: lx.array(lx.ref("app.bsky.richtext.facet")), - avatar: lx.string({ format: "uri" }), - likeCount: lx.integer({ minimum: 0 }), - acceptsInteractions: lx.boolean(), - labels: lx.array(lx.ref("com.atproto.label.defs#label")), - viewer: lx.ref("#generatorViewerState"), - contentMode: lx.string({ - knownValues: [ - "app.bsky.feed.defs#contentModeUnspecified", - "app.bsky.feed.defs#contentModeVideo", - ], - }), - indexedAt: lx.string({ required: true, format: "datetime" }), - }); - - assertEquals(generatorView, { - type: "object", - properties: { - uri: { type: "string", required: true, format: "at-uri" }, - cid: { type: "string", required: true, format: "cid" }, - did: { type: "string", required: true, format: "did" }, - creator: { - type: "ref", - ref: "app.bsky.actor.defs#profileView", - required: true, - }, - displayName: { type: "string", required: true }, - description: { type: "string", maxGraphemes: 300, maxLength: 3000 }, - descriptionFacets: { - type: "array", - items: { type: "ref", ref: "app.bsky.richtext.facet" }, - }, - avatar: { type: "string", format: "uri" }, - likeCount: { type: "integer", minimum: 0 }, - acceptsInteractions: { type: "boolean" }, - labels: { - type: "array", - items: { type: "ref", ref: "com.atproto.label.defs#label" }, - }, - viewer: { type: "ref", ref: "#generatorViewerState" }, - contentMode: { - type: "string", - knownValues: [ - "app.bsky.feed.defs#contentModeUnspecified", - "app.bsky.feed.defs#contentModeVideo", - ], - }, - indexedAt: { type: "string", required: true, format: "datetime" }, - }, - required: ["uri", "cid", "did", "creator", "displayName", "indexedAt"], - }); -}); - -Deno.test("app.bsky.feed.defs - generatorViewerState", () => { - const generatorViewerState = lx.object({ - like: lx.string({ format: "at-uri" }), - }); - - assertEquals(generatorViewerState, { - type: "object", - properties: { - like: { type: "string", format: "at-uri" }, - }, - }); -}); - -Deno.test("app.bsky.feed.defs - skeletonFeedPost", () => { - const skeletonFeedPost = lx.object({ - post: lx.string({ required: true, format: "at-uri" }), - reason: lx.union(["#skeletonReasonRepost", "#skeletonReasonPin"]), - feedContext: lx.string({ maxLength: 2000 }), - }); - - assertEquals(skeletonFeedPost, { - type: "object", - properties: { - post: { type: "string", required: true, format: "at-uri" }, - reason: { - type: "union", - refs: ["#skeletonReasonRepost", "#skeletonReasonPin"], - }, - feedContext: { type: "string", maxLength: 2000 }, - }, - required: ["post"], - }); -}); - -Deno.test("app.bsky.feed.defs - skeletonReasonRepost", () => { - const skeletonReasonRepost = lx.object({ - repost: lx.string({ required: true, format: "at-uri" }), - }); - - assertEquals(skeletonReasonRepost, { - type: "object", - properties: { - repost: { type: "string", required: true, format: "at-uri" }, - }, - required: ["repost"], - }); -}); - -Deno.test("app.bsky.feed.defs - skeletonReasonPin", () => { - const skeletonReasonPin = lx.object({}); - - assertEquals(skeletonReasonPin, { - type: "object", - properties: {}, - }); -}); - -Deno.test("app.bsky.feed.defs - threadgateView", () => { - const threadgateView = lx.object({ - uri: lx.string({ format: "at-uri" }), - cid: lx.string({ format: "cid" }), - record: lx.unknown(), - lists: lx.array(lx.ref("app.bsky.graph.defs#listViewBasic")), - }); - - assertEquals(threadgateView, { - type: "object", - properties: { - uri: { type: "string", format: "at-uri" }, - cid: { type: "string", format: "cid" }, - record: { type: "unknown" }, - lists: { - type: "array", - items: { type: "ref", ref: "app.bsky.graph.defs#listViewBasic" }, - }, - }, - }); -}); - -Deno.test("app.bsky.feed.defs - interaction", () => { - const interaction = lx.object({ - item: lx.string({ format: "at-uri" }), - event: lx.string({ - knownValues: [ - "app.bsky.feed.defs#requestLess", - "app.bsky.feed.defs#requestMore", - "app.bsky.feed.defs#clickthroughItem", - "app.bsky.feed.defs#clickthroughAuthor", - "app.bsky.feed.defs#clickthroughReposter", - "app.bsky.feed.defs#clickthroughEmbed", - "app.bsky.feed.defs#interactionSeen", - "app.bsky.feed.defs#interactionLike", - "app.bsky.feed.defs#interactionRepost", - "app.bsky.feed.defs#interactionReply", - "app.bsky.feed.defs#interactionQuote", - "app.bsky.feed.defs#interactionShare", - ], - }), - feedContext: lx.string({ maxLength: 2000 }), - reqId: lx.string({ maxLength: 100 }), - }); - - assertEquals(interaction, { - type: "object", - properties: { - item: { type: "string", format: "at-uri" }, - event: { - type: "string", - knownValues: [ - "app.bsky.feed.defs#requestLess", - "app.bsky.feed.defs#requestMore", - "app.bsky.feed.defs#clickthroughItem", - "app.bsky.feed.defs#clickthroughAuthor", - "app.bsky.feed.defs#clickthroughReposter", - "app.bsky.feed.defs#clickthroughEmbed", - "app.bsky.feed.defs#interactionSeen", - "app.bsky.feed.defs#interactionLike", - "app.bsky.feed.defs#interactionRepost", - "app.bsky.feed.defs#interactionReply", - "app.bsky.feed.defs#interactionQuote", - "app.bsky.feed.defs#interactionShare", - ], - }, - feedContext: { type: "string", maxLength: 2000 }, - reqId: { type: "string", maxLength: 100 }, - }, - }); -}); - -Deno.test("app.bsky.feed.defs - requestLess token", () => { - const requestLess = lx.token( - "Request that less content like the given feed item be shown in the feed", - ); - - assertEquals(requestLess, { - type: "token", - description: - "Request that less content like the given feed item be shown in the feed", - }); -}); - -Deno.test("app.bsky.feed.defs - requestMore token", () => { - const requestMore = lx.token( - "Request that more content like the given feed item be shown in the feed", - ); - - assertEquals(requestMore, { - type: "token", - description: - "Request that more content like the given feed item be shown in the feed", - }); -}); - -Deno.test("app.bsky.feed.defs - clickthroughItem token", () => { - const clickthroughItem = lx.token("User clicked through to the feed item"); - - assertEquals(clickthroughItem, { - type: "token", - description: "User clicked through to the feed item", - }); -}); - -Deno.test("app.bsky.feed.defs - clickthroughAuthor token", () => { - const clickthroughAuthor = lx.token( - "User clicked through to the author of the feed item", - ); - - assertEquals(clickthroughAuthor, { - type: "token", - description: "User clicked through to the author of the feed item", - }); -}); - -Deno.test("app.bsky.feed.defs - clickthroughReposter token", () => { - const clickthroughReposter = lx.token( - "User clicked through to the reposter of the feed item", - ); - - assertEquals(clickthroughReposter, { - type: "token", - description: "User clicked through to the reposter of the feed item", - }); -}); - -Deno.test("app.bsky.feed.defs - clickthroughEmbed token", () => { - const clickthroughEmbed = lx.token( - "User clicked through to the embedded content of the feed item", - ); - - assertEquals(clickthroughEmbed, { - type: "token", - description: - "User clicked through to the embedded content of the feed item", - }); -}); - -Deno.test("app.bsky.feed.defs - contentModeUnspecified token", () => { - const contentModeUnspecified = lx.token( - "Declares the feed generator returns any types of posts.", - ); - - assertEquals(contentModeUnspecified, { - type: "token", - description: "Declares the feed generator returns any types of posts.", - }); -}); - -Deno.test("app.bsky.feed.defs - contentModeVideo token", () => { - const contentModeVideo = lx.token( - "Declares the feed generator returns posts containing app.bsky.embed.video embeds.", - ); - - assertEquals(contentModeVideo, { - type: "token", - description: - "Declares the feed generator returns posts containing app.bsky.embed.video embeds.", - }); -}); - -Deno.test("app.bsky.feed.defs - interactionSeen token", () => { - const interactionSeen = lx.token("Feed item was seen by user"); - - assertEquals(interactionSeen, { - type: "token", - description: "Feed item was seen by user", - }); -}); - -Deno.test("app.bsky.feed.defs - interactionLike token", () => { - const interactionLike = lx.token("User liked the feed item"); - - assertEquals(interactionLike, { - type: "token", - description: "User liked the feed item", - }); -}); - -Deno.test("app.bsky.feed.defs - interactionRepost token", () => { - const interactionRepost = lx.token("User reposted the feed item"); - - assertEquals(interactionRepost, { - type: "token", - description: "User reposted the feed item", - }); -}); - -Deno.test("app.bsky.feed.defs - interactionReply token", () => { - const interactionReply = lx.token("User replied to the feed item"); - - assertEquals(interactionReply, { - type: "token", - description: "User replied to the feed item", - }); -}); - -Deno.test("app.bsky.feed.defs - interactionQuote token", () => { - const interactionQuote = lx.token("User quoted the feed item"); - - assertEquals(interactionQuote, { - type: "token", - description: "User quoted the feed item", - }); -}); - -Deno.test("app.bsky.feed.defs - interactionShare token", () => { - const interactionShare = lx.token("User shared the feed item"); - - assertEquals(interactionShare, { - type: "token", - description: "User shared the feed item", - }); -}); - -Deno.test("app.bsky.feed.defs - full namespace", () => { - const feedDefs = lx.namespace("app.bsky.feed.defs", { - postView: lx.object({ - uri: lx.string({ required: true, format: "at-uri" }), - cid: lx.string({ required: true, format: "cid" }), - author: lx.ref("app.bsky.actor.defs#profileViewBasic", { - required: true, - }), - record: lx.unknown({ required: true }), - embed: lx.union([ - "app.bsky.embed.images#view", - "app.bsky.embed.video#view", - "app.bsky.embed.external#view", - "app.bsky.embed.record#view", - "app.bsky.embed.recordWithMedia#view", - ]), - bookmarkCount: lx.integer(), - replyCount: lx.integer(), - repostCount: lx.integer(), - likeCount: lx.integer(), - quoteCount: lx.integer(), - indexedAt: lx.string({ required: true, format: "datetime" }), - viewer: lx.ref("#viewerState"), - labels: lx.array(lx.ref("com.atproto.label.defs#label")), - threadgate: lx.ref("#threadgateView"), - }), - viewerState: lx.object({ - repost: lx.string({ format: "at-uri" }), - like: lx.string({ format: "at-uri" }), - bookmarked: lx.boolean(), - threadMuted: lx.boolean(), - replyDisabled: lx.boolean(), - embeddingDisabled: lx.boolean(), - pinned: lx.boolean(), - }), - requestLess: lx.token( - "Request that less content like the given feed item be shown in the feed", - ), - requestMore: lx.token( - "Request that more content like the given feed item be shown in the feed", - ), - clickthroughItem: lx.token("User clicked through to the feed item"), - clickthroughAuthor: lx.token( - "User clicked through to the author of the feed item", - ), - clickthroughReposter: lx.token( - "User clicked through to the reposter of the feed item", - ), - clickthroughEmbed: lx.token( - "User clicked through to the embedded content of the feed item", - ), - contentModeUnspecified: lx.token( - "Declares the feed generator returns any types of posts.", - ), - contentModeVideo: lx.token( - "Declares the feed generator returns posts containing app.bsky.embed.video embeds.", - ), - interactionSeen: lx.token("Feed item was seen by user"), - interactionLike: lx.token("User liked the feed item"), - interactionRepost: lx.token("User reposted the feed item"), - interactionReply: lx.token("User replied to the feed item"), - interactionQuote: lx.token("User quoted the feed item"), - interactionShare: lx.token("User shared the feed item"), - }); - - assertEquals(feedDefs.lexicon, 1); - assertEquals(feedDefs.id, "app.bsky.feed.defs"); - assertEquals(feedDefs.defs.postView.type, "object"); - assertEquals(feedDefs.defs.viewerState.type, "object"); - assertEquals(feedDefs.defs.requestLess.type, "token"); - assertEquals(feedDefs.defs.contentModeVideo.type, "token"); -}); diff --git a/tests/primitives.test.ts b/tests/primitives.test.ts deleted file mode 100644 index 146013b..0000000 --- a/tests/primitives.test.ts +++ /dev/null @@ -1,783 +0,0 @@ -import { assertEquals } from "@std/assert"; -import { lx } from "../src/lib.ts"; - -Deno.test("lx.null()", () => { - const result = lx.null(); - assertEquals(result, { type: "null" }); -}); - -Deno.test("lx.boolean()", () => { - const result = lx.boolean(); - assertEquals(result, { type: "boolean" }); -}); - -Deno.test("lx.boolean() with default", () => { - const result = lx.boolean({ default: true }); - assertEquals(result, { type: "boolean", default: true }); -}); - -Deno.test("lx.boolean() with const", () => { - const result = lx.boolean({ const: false }); - assertEquals(result, { type: "boolean", const: false }); -}); - -Deno.test("lx.integer()", () => { - const result = lx.integer(); - assertEquals(result, { type: "integer" }); -}); - -Deno.test("lx.integer() with minimum", () => { - const result = lx.integer({ minimum: 0 }); - assertEquals(result, { type: "integer", minimum: 0 }); -}); - -Deno.test("lx.integer() with maximum", () => { - const result = lx.integer({ maximum: 100 }); - assertEquals(result, { type: "integer", maximum: 100 }); -}); - -Deno.test("lx.integer() with minimum and maximum", () => { - const result = lx.integer({ minimum: 0, maximum: 100 }); - assertEquals(result, { type: "integer", minimum: 0, maximum: 100 }); -}); - -Deno.test("lx.integer() with enum", () => { - const result = lx.integer({ enum: [1, 2, 3, 5, 8, 13] }); - assertEquals(result, { type: "integer", enum: [1, 2, 3, 5, 8, 13] }); -}); - -Deno.test("lx.integer() with default", () => { - const result = lx.integer({ default: 42 }); - assertEquals(result, { type: "integer", default: 42 }); -}); - -Deno.test("lx.integer() with const", () => { - const result = lx.integer({ const: 7 }); - assertEquals(result, { type: "integer", const: 7 }); -}); - -Deno.test("lx.string()", () => { - const result = lx.string(); - assertEquals(result, { type: "string" }); -}); - -Deno.test("lx.string() with maxLength", () => { - const result = lx.string({ maxLength: 64 }); - assertEquals(result, { type: "string", maxLength: 64 }); -}); - -Deno.test("lx.string() with enum", () => { - const result = lx.string({ enum: ["light", "dark", "auto"] }); - assertEquals(result, { type: "string", enum: ["light", "dark", "auto"] }); -}); - -Deno.test("lx.unknown()", () => { - const result = lx.unknown(); - assertEquals(result, { type: "unknown" }); -}); - -Deno.test("lx.bytes()", () => { - const result = lx.bytes(); - assertEquals(result, { type: "bytes" }); -}); - -Deno.test("lx.bytes() with minLength", () => { - const result = lx.bytes({ minLength: 1 }); - assertEquals(result, { type: "bytes", minLength: 1 }); -}); - -Deno.test("lx.bytes() with maxLength", () => { - const result = lx.bytes({ maxLength: 1024 }); - assertEquals(result, { type: "bytes", maxLength: 1024 }); -}); - -Deno.test("lx.bytes() with minLength and maxLength", () => { - const result = lx.bytes({ minLength: 1, maxLength: 1024 }); - assertEquals(result, { type: "bytes", minLength: 1, maxLength: 1024 }); -}); - -Deno.test("lx.cidLink()", () => { - const result = lx.cidLink( - "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a", - ); - assertEquals(result, { - type: "cid-link", - $link: "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a", - }); -}); - -Deno.test("lx.blob()", () => { - const result = lx.blob(); - assertEquals(result, { type: "blob" }); -}); - -Deno.test("lx.blob() with accept", () => { - const result = lx.blob({ accept: ["image/png", "image/jpeg"] }); - assertEquals(result, { type: "blob", accept: ["image/png", "image/jpeg"] }); -}); - -Deno.test("lx.blob() with maxSize", () => { - const result = lx.blob({ maxSize: 1000000 }); - assertEquals(result, { type: "blob", maxSize: 1000000 }); -}); - -Deno.test("lx.blob() with accept and maxSize", () => { - const result = lx.blob({ - accept: ["image/png", "image/jpeg"], - maxSize: 5000000, - }); - assertEquals(result, { - type: "blob", - accept: ["image/png", "image/jpeg"], - maxSize: 5000000, - }); -}); - -Deno.test("lx.array() with string items", () => { - const result = lx.array(lx.string()); - assertEquals(result, { type: "array", items: { type: "string" } }); -}); - -Deno.test("lx.array() with integer items", () => { - const result = lx.array(lx.integer()); - assertEquals(result, { type: "array", items: { type: "integer" } }); -}); - -Deno.test("lx.array() with minLength", () => { - const result = lx.array(lx.string(), { minLength: 1 }); - assertEquals(result, { - type: "array", - items: { type: "string" }, - minLength: 1, - }); -}); - -Deno.test("lx.array() with maxLength", () => { - const result = lx.array(lx.string(), { maxLength: 10 }); - assertEquals(result, { - type: "array", - items: { type: "string" }, - maxLength: 10, - }); -}); - -Deno.test("lx.array() with minLength and maxLength", () => { - const result = lx.array(lx.string(), { minLength: 1, maxLength: 10 }); - assertEquals(result, { - type: "array", - items: { type: "string" }, - minLength: 1, - maxLength: 10, - }); -}); - -Deno.test("lx.array() with required", () => { - const result = lx.array(lx.string(), { required: true }); - assertEquals(result, { - type: "array", - items: { type: "string" }, - required: true, - }); -}); - -Deno.test("lx.token() with interaction event", () => { - const result = lx.token( - "Request that less content like the given feed item be shown in the feed", - ); - assertEquals(result, { - type: "token", - description: - "Request that less content like the given feed item be shown in the feed", - }); -}); - -Deno.test("lx.token() with content mode", () => { - const result = lx.token( - "Declares the feed generator returns posts containing app.bsky.embed.video embeds", - ); - assertEquals(result, { - type: "token", - description: - "Declares the feed generator returns posts containing app.bsky.embed.video embeds", - }); -}); - -Deno.test("lx.ref() with local definition", () => { - const result = lx.ref("#profileAssociated"); - assertEquals(result, { - type: "ref", - ref: "#profileAssociated", - }); -}); - -Deno.test("lx.ref() with external schema", () => { - const result = lx.ref("com.atproto.label.defs#label"); - assertEquals(result, { - type: "ref", - ref: "com.atproto.label.defs#label", - }); -}); - -Deno.test("lx.ref() with required option", () => { - const result = lx.ref("#profileView", { required: true }); - assertEquals(result, { - type: "ref", - ref: "#profileView", - required: true, - }); -}); - -Deno.test("lx.ref() with nullable option", () => { - const result = lx.ref("#profileView", { nullable: true }); - assertEquals(result, { - type: "ref", - ref: "#profileView", - nullable: true, - }); -}); - -Deno.test("lx.ref() with both required and nullable", () => { - const result = lx.ref("app.bsky.actor.defs#profileView", { - required: true, - nullable: true, - }); - assertEquals(result, { - type: "ref", - ref: "app.bsky.actor.defs#profileView", - required: true, - nullable: true, - }); -}); - -Deno.test("lx.union() with local refs", () => { - const result = lx.union(["#reasonRepost", "#reasonPin"]); - assertEquals(result, { - type: "union", - refs: ["#reasonRepost", "#reasonPin"], - }); -}); - -Deno.test("lx.union() with external refs", () => { - const result = lx.union([ - "app.bsky.embed.images#view", - "app.bsky.embed.video#view", - "app.bsky.embed.external#view", - "app.bsky.embed.record#view", - "app.bsky.embed.recordWithMedia#view", - ]); - assertEquals(result, { - type: "union", - refs: [ - "app.bsky.embed.images#view", - "app.bsky.embed.video#view", - "app.bsky.embed.external#view", - "app.bsky.embed.record#view", - "app.bsky.embed.recordWithMedia#view", - ], - }); -}); - -Deno.test("lx.union() with closed option", () => { - const result = lx.union(["#postView", "#notFoundPost", "#blockedPost"], { - closed: true, - }); - assertEquals(result, { - type: "union", - refs: ["#postView", "#notFoundPost", "#blockedPost"], - closed: true, - }); -}); - -Deno.test("lx.union() with closed: false (open union)", () => { - const result = lx.union(["#threadViewPost", "#notFoundPost"], { - closed: false, - }); - assertEquals(result, { - type: "union", - refs: ["#threadViewPost", "#notFoundPost"], - closed: false, - }); -}); - -Deno.test("lx.params() with basic properties", () => { - const result = lx.params({ - q: lx.string(), - limit: lx.integer(), - }); - assertEquals(result, { - type: "params", - properties: { - q: { type: "string" }, - limit: { type: "integer" }, - }, - }); -}); - -Deno.test("lx.params() with required properties", () => { - const result = lx.params({ - q: lx.string({ required: true }), - limit: lx.integer(), - }); - assertEquals(result, { - type: "params", - properties: { - q: { type: "string", required: true }, - limit: { type: "integer" }, - }, - required: ["q"], - }); -}); - -Deno.test("lx.params() with property options", () => { - const result = lx.params({ - q: lx.string(), - limit: lx.integer({ minimum: 1, maximum: 100, default: 25 }), - cursor: lx.string(), - }); - assertEquals(result, { - type: "params", - properties: { - q: { type: "string" }, - limit: { type: "integer", minimum: 1, maximum: 100, default: 25 }, - cursor: { type: "string" }, - }, - }); -}); - -Deno.test("lx.params() with array properties", () => { - const result = lx.params({ - tags: lx.array(lx.string()), - ids: lx.array(lx.integer()), - }); - assertEquals(result, { - type: "params", - properties: { - tags: { type: "array", items: { type: "string" } }, - ids: { type: "array", items: { type: "integer" } }, - }, - }); -}); - -Deno.test("lx.params() real-world example from searchActors", () => { - const result = lx.params({ - q: lx.string({ required: true }), - limit: lx.integer({ minimum: 1, maximum: 100, default: 25 }), - cursor: lx.string(), - }); - assertEquals(result, { - type: "params", - properties: { - q: { type: "string", required: true }, - limit: { type: "integer", minimum: 1, maximum: 100, default: 25 }, - cursor: { type: "string" }, - }, - required: ["q"], - }); -}); - -Deno.test("lx.query() basic", () => { - const result = lx.query(); - assertEquals(result, { type: "query" }); -}); - -Deno.test("lx.query() with description", () => { - const result = lx.query({ description: "Search for actors" }); - assertEquals(result, { type: "query", description: "Search for actors" }); -}); - -Deno.test("lx.query() with parameters", () => { - const result = lx.query({ - parameters: lx.params({ - q: lx.string({ required: true }), - limit: lx.integer({ minimum: 1, maximum: 100, default: 25 }), - }), - }); - assertEquals(result, { - type: "query", - parameters: { - type: "params", - properties: { - q: { type: "string", required: true }, - limit: { type: "integer", minimum: 1, maximum: 100, default: 25 }, - }, - required: ["q"], - }, - }); -}); - -Deno.test("lx.query() with output", () => { - const result = lx.query({ - output: { - encoding: "application/json", - schema: lx.object({ - posts: lx.array(lx.ref("app.bsky.feed.defs#postView"), { - required: true, - }), - cursor: lx.string(), - }), - }, - }); - assertEquals(result, { - type: "query", - output: { - encoding: "application/json", - schema: { - type: "object", - properties: { - posts: { - type: "array", - items: { type: "ref", ref: "app.bsky.feed.defs#postView" }, - required: true, - }, - cursor: { type: "string" }, - }, - required: ["posts"], - }, - }, - }); -}); - -Deno.test("lx.query() with errors", () => { - const result = lx.query({ - errors: [{ name: "BadQueryString" }], - }); - assertEquals(result, { - type: "query", - errors: [{ name: "BadQueryString" }], - }); -}); - -Deno.test("lx.query() real-world example: searchPosts", () => { - const result = lx.query({ - description: "Find posts matching search criteria", - parameters: lx.params({ - q: lx.string({ required: true }), - sort: lx.string({ enum: ["top", "latest"], default: "latest" }), - limit: lx.integer({ minimum: 1, maximum: 100, default: 25 }), - cursor: lx.string(), - }), - output: { - encoding: "application/json", - schema: lx.object({ - cursor: lx.string(), - hitsTotal: lx.integer(), - posts: lx.array(lx.ref("app.bsky.feed.defs#postView"), { - required: true, - }), - }), - }, - errors: [{ name: "BadQueryString" }], - }); - assertEquals(result, { - type: "query", - description: "Find posts matching search criteria", - parameters: { - type: "params", - properties: { - q: { type: "string", required: true }, - sort: { type: "string", enum: ["top", "latest"], default: "latest" }, - limit: { type: "integer", minimum: 1, maximum: 100, default: 25 }, - cursor: { type: "string" }, - }, - required: ["q"], - }, - output: { - encoding: "application/json", - schema: { - type: "object", - properties: { - cursor: { type: "string" }, - hitsTotal: { type: "integer" }, - posts: { - type: "array", - items: { type: "ref", ref: "app.bsky.feed.defs#postView" }, - required: true, - }, - }, - required: ["posts"], - }, - }, - errors: [{ name: "BadQueryString" }], - }); -}); - -Deno.test("lx.procedure() basic", () => { - const result = lx.procedure(); - assertEquals(result, { type: "procedure" }); -}); - -Deno.test("lx.procedure() with description", () => { - const result = lx.procedure({ description: "Create a new post" }); - assertEquals(result, { type: "procedure", description: "Create a new post" }); -}); - -Deno.test("lx.procedure() with parameters", () => { - const result = lx.procedure({ - parameters: lx.params({ - validate: lx.boolean({ default: true }), - }), - }); - assertEquals(result, { - type: "procedure", - parameters: { - type: "params", - properties: { - validate: { type: "boolean", default: true }, - }, - }, - }); -}); - -Deno.test("lx.procedure() with input", () => { - const result = lx.procedure({ - input: { - encoding: "application/json", - schema: lx.object({ - text: lx.string({ required: true, maxGraphemes: 300 }), - createdAt: lx.string({ format: "datetime" }), - }), - }, - }); - assertEquals(result, { - type: "procedure", - input: { - encoding: "application/json", - schema: { - type: "object", - properties: { - text: { type: "string", required: true, maxGraphemes: 300 }, - createdAt: { type: "string", format: "datetime" }, - }, - required: ["text"], - }, - }, - }); -}); - -Deno.test("lx.procedure() with output", () => { - const result = lx.procedure({ - output: { - encoding: "application/json", - schema: lx.object({ - uri: lx.string({ required: true }), - cid: lx.string({ required: true }), - }), - }, - }); - assertEquals(result, { - type: "procedure", - output: { - encoding: "application/json", - schema: { - type: "object", - properties: { - uri: { type: "string", required: true }, - cid: { type: "string", required: true }, - }, - required: ["uri", "cid"], - }, - }, - }); -}); - -Deno.test("lx.procedure() with errors", () => { - const result = lx.procedure({ - errors: [ - { name: "InvalidRequest" }, - { name: "RateLimitExceeded", description: "Too many requests" }, - ], - }); - assertEquals(result, { - type: "procedure", - errors: [ - { name: "InvalidRequest" }, - { name: "RateLimitExceeded", description: "Too many requests" }, - ], - }); -}); - -Deno.test("lx.procedure() real-world example: createPost", () => { - const result = lx.procedure({ - description: "Create a post", - input: { - encoding: "application/json", - schema: lx.object({ - repo: lx.string({ required: true }), - collection: lx.string({ required: true }), - record: lx.unknown({ required: true }), - validate: lx.boolean({ default: true }), - }), - }, - output: { - encoding: "application/json", - schema: lx.object({ - uri: lx.string({ required: true }), - cid: lx.string({ required: true }), - }), - }, - errors: [{ name: "InvalidSwap" }, { name: "InvalidRecord" }], - }); - assertEquals(result, { - type: "procedure", - description: "Create a post", - input: { - encoding: "application/json", - schema: { - type: "object", - properties: { - repo: { type: "string", required: true }, - collection: { type: "string", required: true }, - record: { type: "unknown", required: true }, - validate: { type: "boolean", default: true }, - }, - required: ["repo", "collection", "record"], - }, - }, - output: { - encoding: "application/json", - schema: { - type: "object", - properties: { - uri: { type: "string", required: true }, - cid: { type: "string", required: true }, - }, - required: ["uri", "cid"], - }, - }, - errors: [{ name: "InvalidSwap" }, { name: "InvalidRecord" }], - }); -}); - -Deno.test("lx.subscription() basic", () => { - const result = lx.subscription(); - assertEquals(result, { type: "subscription" }); -}); - -Deno.test("lx.subscription() with description", () => { - const result = lx.subscription({ - description: "Repository event stream", - }); - assertEquals(result, { - type: "subscription", - description: "Repository event stream", - }); -}); - -Deno.test("lx.subscription() with parameters", () => { - const result = lx.subscription({ - parameters: lx.params({ - cursor: lx.integer(), - }), - }); - assertEquals(result, { - type: "subscription", - parameters: { - type: "params", - properties: { - cursor: { type: "integer" }, - }, - }, - }); -}); - -Deno.test("lx.subscription() with message", () => { - const result = lx.subscription({ - message: { - schema: lx.union(["#commit", "#identity", "#account"]), - }, - }); - assertEquals(result, { - type: "subscription", - message: { - schema: { - type: "union", - refs: ["#commit", "#identity", "#account"], - }, - }, - }); -}); - -Deno.test("lx.subscription() with message description", () => { - const result = lx.subscription({ - message: { - description: "Event message types", - schema: lx.union(["#commit", "#handle", "#migrate"]), - }, - }); - assertEquals(result, { - type: "subscription", - message: { - description: "Event message types", - schema: { - type: "union", - refs: ["#commit", "#handle", "#migrate"], - }, - }, - }); -}); - -Deno.test("lx.subscription() with errors", () => { - const result = lx.subscription({ - errors: [ - { name: "FutureCursor" }, - { name: "ConsumerTooSlow", description: "Consumer is too slow" }, - ], - }); - assertEquals(result, { - type: "subscription", - errors: [ - { name: "FutureCursor" }, - { name: "ConsumerTooSlow", description: "Consumer is too slow" }, - ], - }); -}); - -Deno.test("lx.subscription() real-world example: subscribeRepos", () => { - const result = lx.subscription({ - description: "Repository event stream, aka Firehose endpoint", - parameters: lx.params({ - cursor: lx.integer(), - }), - message: { - description: "Represents an update of repository state", - schema: lx.union([ - "#commit", - "#identity", - "#account", - "#handle", - "#migrate", - "#tombstone", - "#info", - ]), - }, - errors: [{ name: "FutureCursor" }, { name: "ConsumerTooSlow" }], - }); - assertEquals(result, { - type: "subscription", - description: "Repository event stream, aka Firehose endpoint", - parameters: { - type: "params", - properties: { - cursor: { - type: "integer", - }, - }, - }, - message: { - description: "Represents an update of repository state", - schema: { - type: "union", - refs: [ - "#commit", - "#identity", - "#account", - "#handle", - "#migrate", - "#tombstone", - "#info", - ], - }, - }, - errors: [{ name: "FutureCursor" }, { name: "ConsumerTooSlow" }], - }); -}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..42962a6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2022", + "allowImportingTsExtensions": true + }, + "include": ["src"] +} diff --git a/tsdown.config.ts b/tsdown.config.ts new file mode 100644 index 0000000..62d8a89 --- /dev/null +++ b/tsdown.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "tsdown"; + +export default defineConfig({ + dts: true, + entry: ["src/**/*.ts"], + outDir: "lib", + unbundle: true, +});