* onSignInFailed oauth hook
* plumb Account through to onSignInFailed hook
* plumb client ids through to oauth hooks
* pass sub to InvalidCredentialsError, not full Account
* defensively downgrade InvalidCredentialsError to InvalidRequestError after delivering onSignInFailed hook
* changeset
* support InvalidCredentialsError in PDS oauth store, test for enumeration attacks
* changeset
* fix test
* slight simplification
* Refactor feature gates client to be less handler-specific
* Improve passing around of feature gates
* Ensure merging of contexts results in a correct output
* Sync with main
* Docs
* Changeset
* Remove image gate
* Comments
* Format
* Update new usage
* Remove deprecated handling from getSuggestedFollowsByActor
* Remove very old test that is not testing existing functionality
* Import sort
* Softer off-ramp if suggestionsAgent
The motivation for this is to for read-only apps to display lists and
feeds which have been pinned/followed by the user. Presumably the prefs
could also be used by read-only apps to apply labeling configuration.
Change "chat.bsky.convo.exportAccountData" to
"chat.bsky.actor.exportAccountData" in an lxm of
chat.bsky.authFullChatClient#main's permission-set, as the former does
not exist, but seems to be a typo of the latter.
* Deprecate recId in favor of recIdStr
* Add changeset
* Revert "Add changeset"
This reverts commit 3241aec7202c1bcb2a8e28f7064def5024dcae73.
* Recreate changeset as patch version
* Lex SDK error handling improvements
* Add support for method defined through `@atproto/lex` in addition to `@atproto/lexicon` "codegen"
* review comments
* tidy
* docs: Updating app password based session example
Updating atproto app password based session example in README
* refactor: prettier lint + changeset
* Delete any pre-existing OAuth session when a new one is created (for a given `sub`)
* OAuth client improvements
* tidy
* tidy
* tdy
* remove event-target-polyfill dep
* Update .changeset/neat-humans-fix.md
Co-authored-by: devin ivy <devinivy@gmail.com>
* tidy
---------
Co-authored-by: devin ivy <devinivy@gmail.com>
* move feature gates to analytics folder
* implement scoped feature gate evals
* Add separate config for metrics trackingHost
* Add source param
* Pare down metadata sent to event proxy
* Just build metrics within feature gate client
* Refactor a bit to streamline usage
* Further pare down API to be super clear
* Add readme
* Cleanup
* Use new env vars
* Stop metrics if we stop feature-gates
* Add metrics client test
* Adjust imports for lint
* Try catch for safety, revert logger change
* Structure log metadata to match proxy needs
* Nah use stableId
* Revert "Nah use stableId"
This reverts commit 5c6d39a2420f32efa43955b85d0cf19f20bf2c16.
* Align on deviceId
---------
Co-authored-by: Eric Bailey <git@esb.lol>
* db: create index for moderation_event.createdBy
* tests: add queryEvents by creator test case
* refactor: add additional columns to createdBy index to match runtime query
* refactor: remove id since including both createdAt and id provide little incremental efficiency
* refactor: adding id back since it could matter in some cases
* Adds GrowthBook feature flag for suggested users from Discover
* Moves GrowthBook URL to envvar
* Reverts search feature
* Some minor fixes
* Adds GrowthBook feature flag for suggested users from Discover
* Moves GrowthBook URL to envvar
* Some minor fixes
* Keep search filtering exploration around but disable until migration
* Properly initialize GrowthBook client
* Cleans up
* Migrate flags
* Tweak context creation and types
* Add refetching and more error handling
* Rename config to be a little more clear
* Remove check validation, not needed
* Yeah ok we can do this
* Changeset
---------
Co-authored-by: Eric Bailey <git@esb.lol>
* Improve code coverage of `@atproto/lex-data` tests
* Improve typing of `@atproto/syntax` type assertion utilities
* Improve performance of `@atproto/lex-schema ` string format checking
* Remove `assertX` string format assertion utilities
* tidy
* tidy
* Rename `isLanguageString` to `validateLanguage`
* add string format utils
* tidy
* Refactor uri validation to use `@atproto/syntax`
* More language validation to @atproto/syntax
* lexicons: add germ chat declaration record, and ref from profile view
* codegen germ record for bsky pkg
* make lex-client build happy by including germ ref
* add germ chat declaration methods to dataplane .proto
* dataplane codegen
* start wiring up germ chat records
* germ lex in api package
* api codegen
* initial attempt at germ record test
* update germ lexicon
* make codegen
* updates for germ declaration NSID rename
* implement new germ hydration
* more progress on germ integration
* fix germ profile test
The main issue here was forgetting the '.associated.' bit in the data
path.
* remove unnecessary germ imports/refs
* hydrate germ metadata in basic profile view (as per schema)
* type fixes for germ appview test
* prettier
* synchronize bsky.proto with dataplane; run buf:gen
* add changeset for Germ declaration records
* Replace `tap`'s event validation from "zod" to "@atproto/lex"
* Expose `record` data as parsed atproto data (including CIDs and Uint8Arrays)
* Minor change to validation of integers in lex data
* tidy
* tidy
* test using vitest
* add missing `tap` from root `tsconfig.json`
* Fix inability to assign (object containing) open union results to `LexMap` type
* properly support and type default values
* strong cohesion
* changeset
* tidy
* Add validation error as cause when handling invalid records
* tidy
* Update `cborg` dependency, fixing encoding of strings with invalid surrogate pairs, and ignoring `undefined` object properties
* tidy
* Simplify encoding logic of `number`
* Improvements around usage of `noUndefinedVals`
* Memoize array schemas (without options)
* Memoize empty params schemas
* tidy
* tidy
* Fix oauth response when using `prompt=select_account` and no session are available
Fixes#4566
* allow the account to be pre-selected when `prompt=login`
* pds: stop probing image dimensions
* remove width and height from BlobMetadata
* remove image dimension checks from tests
* comment out width and height in Blob schema
Implements detection and facet generation for cashtags (stock tickers) like $AAPL
and $BTC. Cashtags are identified by a dollar sign followed by 1-5 alphanumeric
characters, with the first character being a letter. Detected cashtags are
normalized to uppercase and included as tag facets in the rich text output.
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
* add recId field to getSuggestedUsersSkeleton
* Revert "add recId field to getSuggestedUsersSkeleton"
This reverts commit 6b936265cd7efd1fc5ec0e05404122feec97e397.
* Adds recId to SuggestedUsersSkeleton
* Do some codegen
* What if more codegen?
* Adds `recId` in `getSuggestUsers` presentation fn
---------
Co-authored-by: Eduardo Cuducos <4732915+cuducos@users.noreply.github.com>
* Avoid escaping export identifier when it is a known global
* Use a record type instead of a record schema type as the generic parameter for `ListRecord`
* Export everything from `@atproto/lex-data` and `@atproto/lex-json`
* Add lex-json and lex-data to lex readme
* lock
* Apply defaults when running `schema.$build()` on objects and records.
* changeset
* simplify $Typed and $TypedMaybe
* tidy
* tidy
* Add `enumBlobRefs` utility function
* Add an `indexFile` option that allows generating an "index.ts" file that re-exports every tld namespaces.
* readme
* Add `base64ToUtf8` and `utf8ToBase64` utilities
* Add service auth authentication method
* Add prompt_values_supported to Authorization Server Metadata
* Expose prompt_values_supported in Authorization Server Metadata
* Support selecting view in oauth-provider-ui based on prompt parameter
* Support initiating user registration via prompt=create
* Add support to OAuth Client Browser Example for prompt=create
* Add test coverage for prompt=create
* early prototype perm sets for chat.bsky
* iterate on chat permission-set
* add initial app.bsky.* permission sets
* fix NSID for authManageLabelerService
* add just-merged contact verification endpoints to authFullApp
* fix more NSIDs
* Revert "add just-merged contact verification endpoints to authFullApp"
Will check with app team about which of these actually need client app
perms.
* rename authManageNotifications
* remove authDeletePosts (probably needs broader scope)
* add sitemap protos
* cd packages/bsky && npm run buf:gen
* implement user sitemap handlers
* add a test
* lint:fix
* use /external
* use Readable pipe for slightly more effecient data transfer
* rename functions to include the word user
* improve not found case and add test
* Increase string format typing strictness
* fix tests
* tidy
* Use string formats from `@atproto/syntax`
* tidy
* `key` field in `record` definitions is non optional and now properly validated
* add missing /*@__NO_SIDE_EFFECTS__*/
* Add `l.nullable` schema builder
* Use unique symbol to describe Validator type metadata
* fixup! Add `l.nullable` schema builder
* Rework object validation logic to work without `options` argument
* Do not use symbol for type inference
* Use `Issue` classes to represent validation issues
* Properly apply default value with `const` and `enum` schemas
* style
* Require `l.discriminatedUnion` discriminator field to be a literal or enum schema
* Add `l.refined` schema
* Add more lexicons document validation tests
* wip
* use "assert" fn
* rework refine system
* use assert instead of check fn
* tidy
* Rename schema methods `validate`, `check` and `maybe` to `safeParse`, `matches` and `ifMatches` respectively.
* docs
* changeset
* Remove ability to define `blob` permission in permission sets
* Disallow `rpc` permissions with specific `aud` in permission-sets
* Add `toScopes()` utility on `IncludeScope`
* tidy
* Deterministically order lexicon manifest items (Fixes#4378)
* Use `literal` value as default
* Code style
* Remove `--build`/`--no-build` argument from the `install` command
Fixes#4377
* Add ability to configure file extenstion and import file extension in `lex build`
Fixes#4376
* Fix description of `--allowLegacyBlobs` argument
* create new websocket lib
* switch out ozone impl
* keepalive test
* remove websocket keepalive code from xrpc-server
* add the package in all the spots
* websocket -> ws-client
* missed ref
* fix import in ozone test
* Add debug field to lex and proto, codegen
* Include debug field for DIDs configured via env var
* Gen bufs
* Update types and view
* Add debug field to profile views
* Safely populate debug field
* Format
* Use util
* Simplify existence check
* Values may be undefined
* Clarify type
* Update descriptions
* Include default value for dev-env
* Add test harness
(cherry picked from commit 7e49b4cf4cf3cbb989114540081e74f7a34acb6f)
* Use unknown field type instead of serializing
* KISS keep it set stupid
* Fix comments
* Differentiate between source of debug
* Add matching profile view test
* Specifying types of the merged objects isn't really helpful
* Changeset
* Add comment
* Put includeDebugField on HydrateCtx and use in views
* Mutate TestNetwork innards for tests
* Format
* Use separate debug schema for profile test
* Little more terse
* Oops, remove last usage
* Remove vestigial props
* Include other actor metadata
* Simply views, remove debugField
Previously the notification for the label creation was dispatched before the label was actually inserted into the database. In all other cases where we use postgresql's notify, we do it after the insertInto, such that the listener is guaranteed to receive the newly created record when querying
* ✨ Add delayed takedown feature to ozone
* ✨ Apply tag auth check and add takedown event push
* ✅ Add tests for takedown restrictions
* 🐛 Remove only test block
* 🐛 Add service forwarding
* ✨ Copy event properties into scheduled takedown event
* 🧹 Cleanup
* 🐛 Make statuses required in listScheduledActions
* 🐛 Define required field in lexicon
* ✨ Add tag when scheduling and cancelling scheduled takedown
* Integrate new reporting reasons
* Update bnn to BNR, prefix all with reason* to match previous
* Remap deprecations
* Update naming, add notes about Bluesky-only reasons
* Update reason
* Move new defs to tools.ozone namespace
* Add ozone lexicons to app view
* Copy known values to merge defs
* Update comments
* Add reasonAppeal to new ozone namespace defs
* Changeset
* ❇️ Support new reporting categories in ozone (#3974)
* ✨ Validate report reason using labeler service profile
* ✅ Rename test
* :rotating_lights: Fix lint issue
* ✨ Use both appeal reason type for materialized views
* ✨ Add old to new reason mapping for fallback
* ✅ Update test snapshot
* :rotating_lights: Fix lint issue
* 🧹 Cleanup
* :rotating_lights: Fix lint issue
* ✨ Adjust report reason tagging
* 📝 Additional comment for new migration
---------
Co-authored-by: Foysal Ahamed <foysal@blueskyweb.xyz>
* Allow unexpected error to go through when fetching permission sets
* Log `cid` as string after succesful lexicon resolution
* Log `cid` and `uri` as string on successful lexicon resolution
* Export constants and type assertion utilities
* Add permission set support to oauth provider
* improve permission set parsing
* Rename `PermissionSet` to `ScopePermissions`
* Improve performance of NSID validation
* Add support for `permission-set` in lexicon document
* Validate NSID syntax using `@atproto/syntax`
* Export all types used in public interfaces (from `lexicon-resolver`)
* Small performance improvement
* Rework scope parsing utilities to work with Lexicon defined permissions
* file rename
* fixup! Rework scope parsing utilities to work with Lexicon defined permissions
* removed outdated comment
* removed outdated comment
* fix comment typo
* Improve `SimpleStore` api
* permission-set NSID auth scopes
* Remove dev dependency on dev-env
* fix build script
* pnpm-lock
* Improve fetch-node unicast protection
* Explicitly set the `redirect: "follow"` `fetch()` option
* Add delay when building oauth-provider-ui in watch mode
* Remove external dependencies from auth-scopes
* Add customizable lexicon authority to pds (for dev purposes)
* fix pds migration
* update permission-set icon
* Add support for `include:` syntax in scopes
* tidy
* Renaming of "resource" concept to better reflect the fact that not all oauth scope values are about resources
* changeset
* ui improvmeents
* i18n
* ui imporvements
* add `AtprotoAudience` type
* Enforce proper formatting of audience (atproto supported did + fragment part)
* tidy
* tidy
* tidy
* fix ci ?
* ci fix ?
* tidy ?
* Apply consistent outline around focusable items
* Use `inheritAud: true` to control `aud` inheritance
* Update packages/oauth/oauth-provider/src/lexicon/lexicon-manager.ts
Co-authored-by: devin ivy <devinivy@gmail.com>
* Review comments
* Add `nsid` property to `LexiconResolutionError`
* improve nsid validation
* i18n
* Improve oauth scope parsing
* Simplify lex scope parsing
* tidy
* docs
* tidy
* ci
* Code simplification
* tidy
* improve type safety
* improve deps graph
* naming
* Improve tests and package structure
* Improve error when resolving a non permission-set
* improve nsid parsing perfs
* benchmark
* Refactor ozone and lexicon into using a common service profile mechanism
* improve perfs
* ci fix (?)
* tidy
* Allow storage of valid lexicons in lexicon store
* Improve handling of lexicon resolution failures
* review comment
* Test both regexp and non regexp based nsid validation
* properly detect presence of port number in https did:web
* Re-enable logging of `safeFetch` requests
* tidy
---------
Co-authored-by: devin ivy <devinivy@gmail.com>
* Support multiple redirect URIs for @atproto/oauth-client-browser
* For redirect_uri callback parameter type
* fix-type-error
* Do not fail if the client can't figure out which redirect uri was used (and only one is available)
---------
Co-authored-by: Emelia Smith <ThisIsMissEm@users.noreply.github.com>
* @atproto/lexicon: relax validation of lexicon documents to allow unknown fields
* changeset
* remove field from lexiconDoc validation, no longer necessary
* Remore requirement for JWK to define either `use` or `key_ops`
* Prevent inconsistent use of `use` and `key_ops` in JWK
* docs
* review comments
* comment
* Validate user status before initiating
* Format
* Add test, not working yet
* Fix test
* Import order
---------
Co-authored-by: rafael <rafael@blueskyweb.xyz>
* 🚧 WIP
* ✨ Make age assurance state queryable
* ✨ Split age assurance events into 2
* ✨ Implement admin and user state overrides
* ✨ Add blocked as a known value for age assurance state
* ✅ Update test snapshot
* ✅ Update test snapshot
* ✨ Cleanup
* xrpc-server: skip body parsing when input encoding is */*, fix json and text uploads
* changeset
* pds: add tests for text and json uploads
* tidy
* xrpc-server: only create body parser when it will be used
* ✨ Add userAgent tracking for events
* :rotating_lights: Fix lint issue
* ♻️ Refactor userAgent to modTool
* ✨ Rename extra to meta
* 📝 Add changeset
* ✨ Support modTools param in createReport
* ✨ Add support for mod tool in createReport
* Fix authorization error type name
* Refactor authorization error handling: replace AccessDeniedError with AuthorizationError and improve error reporting
* Improve OAuth Example app
* Improve style
* bsync: Accept NSID with fragment in operation ns (#3954)
* Add `match: MuteWordMatch` to `muted-word` mod decision `cause` (#2934)
* Return MuteWordMatch instead of simple boolean
* Return full mute word with match
* Add MuteWordMatch to decision cause, update a few tests
* Backwards compat
* Tighter types
* Return all mute word matches
* Clean up types
* Rename
* More cleanup of naming
* Remove unneeded changes
* Format
* Add predicate value to matches
* Better migration path
* Changeset
* Import sort
* Tighten up addMuteWord API
Co-authored-by: Matthieu Sieben <matthieusieben@users.noreply.github.com>
* Mute words: handle `Andor` and `and/or` case (#3948)
* Handle Andor case
* Remove useless escape
* Changeset
---------
Co-authored-by: Matthieu Sieben <matthieusieben@users.noreply.github.com>
* Version packages (#3947)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* Update README.md to add some missing details in examples (#3254)
Update README.md
Improve code examples (some OAuth implementation details are missing in these examples)
* Increase oauth session & refresh token lifetimes (#3883)
* Allow HTTPS `redirect_uris` from any origin (#3811)
* bump MST key length from 256 to 1024 chars (#3956)
* bump MST key length from 256 to 1024 chars
* update MST key test
* add a changeset
* Version packages (#3959)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* Rename `filter` -> `include` (#3966)
* rename filter -> include
* changeset
* fix tests
* Minor Fixes: Typo Correction and Comment Update (#3961)
* Update blob-resolver.ts
* Update index.ts
* Appview: sync up protos for notification prefs (#3970)
appview: sync up protos for notification prefs
* Version packages (#3969)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* Fix invalid use of `invalid_client` (#3967)
* Replace slice() with subarray() in car file parsing (#3971)
* Replace slice() with subarray() in car file parsing
* changeset
---------
Co-authored-by: Devin Ivy <devinivy@gmail.com>
* Re-export all types & utilities needed to instantiate an OAuth client (#3976)
* Re-export all types & utilities needed to instantiate an OAuth client
* Add `jwkPrivateSchema` to ensure a key is private
* Return object instead of array as result of `findPrivateKey`
* Allow override of default `handleResolver` and `runtimeImplementation` options for NodeOAuthClient
* changeset
* Allow `OAuthClient` to be instantiated with custom `didResolver` instance
* Version packages (#3975)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* Perform a bi-directional check when resolving identity from did (#3977)
* Perform a bi-directional check when resolving identity from did
* tidy
* Reject did documents containing invalid `alsoKnownAs` ATProto handles
* Use error classes
* tidy
* Improve identity resolution
* tidy
* Allow non-normalized handles in did document
* pnpm-lock
* Version packages (#3979)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* repo: MST should allow tilde in keys (#3981)
* repo: MST should allow tilde in keys
* add changeset
* fic ci
* tidy
* tidy
---------
Co-authored-by: rafael <rafael@blueskyweb.xyz>
Co-authored-by: Eric Bailey <git@esb.lol>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: James Futhey <kidGodzilla@users.noreply.github.com>
Co-authored-by: bnewbold <bnewbold@robocracy.org>
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Co-authored-by: leopardracer <136604165+leopardracer@users.noreply.github.com>
Co-authored-by: devin ivy <devinivy@gmail.com>
Co-authored-by: Paul Frazee <pfrazee@gmail.com>
* Re-export all types & utilities needed to instantiate an OAuth client
* Add `jwkPrivateSchema` to ensure a key is private
* Return object instead of array as result of `findPrivateKey`
* Allow override of default `handleResolver` and `runtimeImplementation` options for NodeOAuthClient
* changeset
* Allow `OAuthClient` to be instantiated with custom `didResolver` instance
* Return MuteWordMatch instead of simple boolean
* Return full mute word with match
* Add MuteWordMatch to decision cause, update a few tests
* Backwards compat
* Tighter types
* Return all mute word matches
* Clean up types
* Rename
* More cleanup of naming
* Remove unneeded changes
* Format
* Add predicate value to matches
* Better migration path
* Changeset
* Import sort
* Tighten up addMuteWord API
Co-authored-by: Matthieu Sieben <matthieusieben@users.noreply.github.com>
* Mute words: handle `Andor` and `and/or` case (#3948)
* Handle Andor case
* Remove useless escape
* Changeset
---------
Co-authored-by: Matthieu Sieben <matthieusieben@users.noreply.github.com>
* Ensure that the credentials used during a refresh correspond to those used to create the OAuth tokens.
* tidy
* Bind the OAuth session to the kid that was used to authenticate the client (private_key_jwt)
* Store the whole authentication method in the client session store rather than the kid only
* tidy
* Improve error reporting in case an invalid `token_endpoint_auth_method` is used in the client metadata document.
* tidy
* tidy
* Improve JAR checks
* tidy
* changeset
* tidy
* Remove schema's `.optional()` modifier when a `.default()` is defined
* tidy
* verify client auth during code exchange
* tidy
* Minor naming improvement
* tidy
* Update .changeset/quiet-pans-fix.md
Co-authored-by: devin ivy <devinivy@gmail.com>
* Update packages/oauth/oauth-client/src/oauth-client-auth.ts
* Use `private_key_jwt` instead of incorrect `client_secret_jwt` as authentication method for confidential clients
* style
* code split
* dead code removal
* Represent missing client auth with a `null` instead of "none" when storing request data.
* Allow storing `null` in authorization_request's `clientAuth` json column
* document
* tidy
* Remove non-standard behavior that allowed client to authenticate through JAR
* Improved error messages
* Parse JSON encoded Authorization Request Parameters
* Use `application/x-www-form-urlencoded` content instead of JSON for OAuth requests
Fixes: #3723
* tidy
* tidy
* tidy
* tidy
* code style
* remove un-necessary checks
* tidy
* Pre-process number too
* improved type checking
* add missing exports
* fix merge conflict
* tidy
* Remove invalid default for `code_challenge_method` authorization request parameter
* tidy
* Delete inaccurate changeset
* PR comment
* tidy
* Update OAuth client credentials factory to return headers and payload separately.
* tidy
* Renamed `clientAuthCheck` to `validateClientAuth`
* Validate presence of DPoP proofs sooner when processing token requests.
Fixes: #3859
* Protect against concurrent use of request code
* tidy
* tidy
* Update packages/oauth/oauth-provider/src/client/client.ts
Co-authored-by: devin ivy <devinivy@gmail.com>
* Review comments
* Add missing `exp` claim in client attestation JWT
* fixup! Review comments
* Review comments
* Refactor: explicit optionality of unsigned JAR issuer & audience
* Use client attestation's `exp` claim to determine the life time of JWT's `jti` nonce.
* Fix PDS: consumeRequestCode should delete request data
* tidy
* tidy
* Unused code removal
* Restore "Native clients must authenticate using "none" method" check
* tidy
* tidy
* cleanup
* comment
* Allow missing DPoP header during PAR request if `dpop_jkt` is provided
* tidy
---------
Co-authored-by: devin ivy <devinivy@gmail.com>
* Refactor route rate limiter builder
* Refactor RouteRateLimiter handle method to improve bypass logic and return type
* Use `redis` as rate limit db when available
* Return atproto handle in identity resolution result
* Use resolved handle or did instead of raw input as "login_hint"
* Normalize and validate `login_hint` in oauth request properties
* Properly validate JWK `htu` claim by enforcing URL without query or fragment
* type fix
* Return DPoP validation result from `authenticateRequest`
* Log clients using invalid "htu" claim in DPoP proof
* review comments
* fix lint
* tidy
* rename dpop result to dpop proof
Currently, the `lingui extract` command is being run as part of the `build` and `dev` commands. This causes very large diffs in PRs, even when no change are made to `.po` files.
With this change, only running `pnpm i18n` (from the root folder), or `pnpm i18n:extract` (from ui libs that support it) will cause the PO files to be re-computed.
* 🐛 Fetch record from pds if appview fails to find it for ozone
* ✨ Resolve and etch from pds without auth
* ♻️ Refactor and cleanup
* ✅ Fix tests
* ✅ Fix tests
* 🚨 Fix linter issue
* 🧹 Cleanup
* Allow proxying of dpop bound requests by using service auth instead, for the `getSession` endpoint.
* Show `getSession` data in example app
* Add scope
* strings
* cleanup
* tidy
* tidy
* Add transition:email scope to example app
* strings
* changeset
* pr comments
* Handle resolution improvements
On PDS, only perform resolution if appview is not configured.
On appview, never perform resolution, only rely on dataplane.
* changeset
* appview: add lookup_unidirectional to GetDidsByHandles
* appview: plumb lookup_unidirectional for handle lookup
* note
---------
Co-authored-by: Devin Ivy <devinivy@gmail.com>
* First vouch implementation
* Remove unneeded endpoints
* wip
* ✨ wip
* ✨ Process jetstream events through p-queue and add tests
* ✅ Add test for cursor update
* 🐛 Use utc time to update updatedAt
* 🧹 Cleanup
* 🔨 Fix pnpm versioning issues
* ✨ Replace jetstream lib with manual implementation
* ✨ Remove unnecessary 3p dep
* ✨ Add e2e test for jetstream
* 🚨 Fix import
* 🧹 Remove unnecessary property
* ✨ Fix dev-env and add profile to verification view in ozone
* ✅ Add profile type
* ✨ Add backpressure handling to jetstream listener
* ✨ Use WebSocketKeepAlive from xrpc-server and replace partysocket
* ✨ Add a new verifier role to ozone team meber roles
* 📝 Run codegen
* 🐛 Fix auth check
* 🐛 Fix test failure check
* 🚨 Fix json formatting
* 🐛 Fix team role check
* 🚧 Checking failing test
* ✅ Fix tests
* ✨ Address review comments
* ✨ Add xrpc-server to version
* 🚨 Fix linter issue
* 🚨 Fix linter issue
* ✨ Resolve race condition in cursor update
* ✅ Add verification check on profile
* 🐛 Fix missing cid in test and firehose cursor
* ✨ Fix test
* ✨ Add record validation for verification and separate xrpc-server version
* ✨ Return error object for failed revocations
* ✨ Add re-login on expired session case
* 📝 Fix typo
---------
Co-authored-by: rafael <rafael@blueskyweb.xyz>
* Allow `:` chars in url path parts
* Allow customizing contrast and hue colors
* Allow customizing contrast and hue colors
* Use white as primary contrast color
* Fix buttons alignment and labels in "My Devices" section
* Add a `<title>` tag to all pages
* Properly display the "lastSeenAt" date
* Improve display of clients & devices
* tidy
* code split
* Add definition for `ConventionalOAuthClientId`
* Remove hard coded `client_name` from loopback client metadata
* Code factorization
* Fix `<title>` of branding page
* new host status com.atproto.sync lexicons
* add description
* add specific error response from request crawl if host is banned
* lint
* break out hostStatus to com.atproto.sync.defs
* add a changeset
* codegen
* ✨ Add getSubjects endpoint to ozone for full view of subjects
* 📝 Better lexicon description
* 📝 Add changeset
* ✨ Add subject to subject view
* ✅ Update snapshot
* :rotating_lights: Fix lint issue
* ✨ Make subject required in subjectView
* 🐛 Add subject check before attempting to run queries
* ✨ Keed the order of subjects from the request in the responding array
* ✨ Use a union for profile field
* 🐛 Remove minLength and address profile value
* 📝 Bring back minLength
* Adds "password reset" during OAuth flows
* Adds "Sign up" during OAuth flows
* Adds support for multiple languages in the OAuth flow
* Adds "fr" translation for the OAuth flow
Co-authored-by: devin ivy <devinivy@gmail.com>
Co-authored-by: Eric Bailey <git@esb.lol>
* deprecate blobs & tooBig
* add sync event, deprecate handle & tombstone
* fix up tests
* small tidy
* add test for sync account on account activation
* use new sync event in another place
* remove deprecated events from lexicons
* formatting
* pr cleanup
* changeset
* Mirror new fields on labelerViewDetailed
(cherry picked from commit 33fdceec26c189995936129355135fdb1480a738)
(cherry picked from commit fe5626639e2d9d03448d8a7e829ad4d8b5e54ec3)
* Mirror new labeler service record properties on labelerViewDetailed lexicons
(cherry picked from commit 6a490f83232c258ac4cc2cd46379048195c4710f)
* Include new labeler service record properties on the labelerViewDetailed reponse from the app view.
(cherry picked from commit 279ed6472d370a61bf669656fda2fddaf2f88621)
* Format
(cherry picked from commit 620346d1e15d130e11fea45cf05d12d246b8e605)
* Fix up bad conflict resolution in test suite
(cherry picked from commit efa04cf592088be0bb8c7c96eb2d45ecbc1a2ea4)
* Import order
(cherry picked from commit 82567caaf53daa09951f71704827ff8d48f1bcc3)
* serialize sequencing by moving sequenceCommit into actor store transaction
* do not throw on updateRepoRoot failure
* build branch
* changset
* dont build branch
* add x-forward-for to pds proxied requests to entryway
* pds: pull entryway auth/passthru headers into own methods, adjust forwarding
* tidy
* changeset
* pds: switch back to forward just first untrusted ips, trust local ips
* updates to com.atproto.moderation.* for report routing
* updates to labeler service declaration; and new reason types
* Apply suggestions from code review
Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
* remove dupe reasonAppeal
* iterate on labeler declaration lexicons
* Makefile: fast helper for formatting just lex json
* external, not link
* change default when subjectCollections is not defined
* remove 'example reason' disclaimers
* simplify lex report proposal: just labeler declartions
* missed knownValues list
* Codegen, changeset
---------
Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
Co-authored-by: Eric Bailey <git@esb.lol>
* OAuthProvider: Update "trustProxy" options to allow function
* DeviceManager options can now be passed as argument to the OAuthProvider constructor
* Only trust one level of proxying when computing IP during OAuthFlows
* Prevent invalid use of trustProxy config
* tidy
* make the code compliant with legacy behavior
* mark 'blobs' as deprecated (can parse from records blocks)
* add 'prev' CID to record ops
* add inductive changes and #resync event
* updates from recent discussion
* add sync v1.1 account hosting status states (broken, throttled)
* prettier
* updates from discussion
* remove 'data' from #sync message
* add note on 'repo' field about inconsistent name
* codegen
* changeset
* schema
* reset rate limit codegen
* codegen
* send prev cids on firehose
* fix test
* fix some test compiler errors & add experimental note
* fix linting
* build branch
* add prevData to commit event
* fix cbor undefined err
* add sibling proofs to relevant blocks
* bump depth of obj in test
* fix bug on right sibling proof & add some tests
* another test
* refactor proof construction
* more tests
* factor into fixtures
* fix styles in json
* lint: import ordering
* pr feedback
* add invertible op test
* remove prev from outgoing events
* return to original proof construction
* dont build branch
* changeset
* lex: use 'tid' and 'record-key' formats in lexicons
* more TID formats; and remove redundant maxLength
* add maxLength back, with a comment/description
* revert accidential change to applyWrites
* codegen
* changeset
* update test for invalid record-key
* com.atproto.sync.listReposByCollection, for collections directory
* bump listReposByCollection limit
* update max+default to align more with listRepos
* codegen
* changeset for listReposByCollection
* ✨ Add reporter_stats materialized view and endpoint to fetch reporter stats
* 🚨 Fix linter issues
* ✨ Change reporter stats query from materialized view to on demand select
* Add "createdAt" as part of the index
---------
Co-authored-by: Matthieu Sieben <matthieu.sieben@gmail.com>
Co-authored-by: Matthieu Sieben <matthieusieben@users.noreply.github.com>
* Make codegen types stricter
* Add .js file extension to import statements in generated code
* Fixes a bug that would clear interests prefs when updating hidden posts prefs.
* Add setPostInteractionSettings and related prefs
* Fix test
* Align types and implementation
* Tighten up, clarifying descriptions
* Fix test
* Format
* Add linting rule to sort imports
* remove spacing between import groups
* changeset
* changeset
* prettier config fine tuning
* forbid use of deprecated imports
* tidy
* Similification of the `pds` package by:
- Removing `DetailedAccountStore` class (moving its logic inside `AccountManager`)
- Factorizes image URL building into its own class (for easy re-use from `AccountManager`)
- Adds an `AppView` class that exposes an `agent: AtpAgent` and url builder function (used by the `ImageUrlBuilder`).
- Reworks the `ActorStore` to avoid circular dependency between `AccountManager` and `LocalViewerCreator` (needed because of first item)
* tidy
* move classes in their own file
* appview: begin rewiring logic for applying modlist, based on owner status.
* appview: unify logic for checking list-block/mutes
* appview: apply actor takedowns for 3p list-blocks
* appview: apply actor takedowns for 1p list-blocks, fix dataplane method
* appview: test takedown on modlist author, application of list
* Add account and record level statistics when querring `tools.ozone.moderation.queryStatuses`.
* Update `tools.ozone.moderation.queryStatuses` lexicon
* jwk: Improve type safety and compatibility with Bun
* improve type safety of jwk keys
* improve typing of verifyAccessToken
* update @types/http-errors
* Better report invalid content-encoding errors
* Mark jwk key fields as readonly
Migrate backend apis from `@atproto/api`, `@atproto/lexicon`, `@atproto/xrpc`,
and `@atproto/lex-cli` to `@atproto/lex`. Use this skill whenever the user
wants to adopt `@atproto/lex` in a package that currently relies on
`@atproto/lex-cli` to perform code generation, replace AtpAgent with Client,
migrate generated lexicon code to the new `lex build` output, replace
`ids.XxxYyy` with namespace accessors, adopt branded string types, switch from
`jsonStringToLex` to `lexParse`, or any refactoring that moves code from the
old generated lexicon system to `@atproto/lex`. Also trigger when the user
mentions "lexification", "lex SDK", "lex migration", or asks about replacing
`@atproto/api` imports in service code.
---
# Lexification: Migrating to `@atproto/lex`
This skill describes how to refactor an AT Protocol service package to replace:
-`@atproto/api` (the old high-level client)
-`@atproto/lexicon` (the old runtime lexicon library)
-`@atproto/xrpc` (the old XRPC client types)
-`@atproto/lex-cli` codegen (the old code generator that produced `src/lexicon/` directories)
...with `@atproto/lex`, which provides generated TypeScript schemas with type-safe validation, type guards, builders, and an XRPC client.
For the full `@atproto/lex` API reference, read `packages/lex/lex/README.md`.
## Guiding Principles
- **Minimize runtime changes.** Prefer type-level changes over runtime changes. If the old code works correctly at runtime, keep its logic and just improve the types around it.
- **Tests still use `@atproto/api` exclusively.** Do not migrate test files — they will be migrated in a separate phase. Tests that imported from the old `src/lexicon/` should switch to importing from `@atproto/api` instead.
The generated schemas live in `src/lexicons/` (note the plural). They expose a namespace-based API (e.g., `app.bsky.feed.post`) with `$type`, `$build`, `$check`, `$matches`, `$isTypeOf`, `$parse`, `$validate`, `$safeParse`, `$lxm`, and other utilities.
## Overview of Changes
The refactor touches these areas (in recommended order):
2.**Project Code Setup** - Replace server creation, endpoint registration, imports from old generated code, app context initialization (`AtpAgent` → `Client`), and type aliases with new patterns from `@atproto/lex`
3.**Endpoint Handlers Registration** - New `server.add()` pattern, handler return types, LXM references, and parameter/output/record type replacements
4.**XRPC Client Calls** - Replace `AtpAgent` API calls with `Client.xrpc()`, `Client.call()`, or `xrpcSafe()`
6.**Data Utilities** - Replace old data manipulation functions (`jsonStringToLex` → `lexParse`, datetime helpers, `BlobRef`, etc.)
7.**Schema Validation and Type Guards** - Replace old `isX()` functions with `$matches()` / `$isTypeOf` / `$build()`
## 1. Project Configuration
### Dependencies
Add `@atproto/lex` as a dependency.
Remove from dependencies (if present):
-`multiformats` (CID handling is now in `@atproto/lex-data`)
Remove from devDependencies:
-`@atproto/api` (unless still needed for tests in this phase)
-`@atproto/lexicon`
-`@atproto/lex-cli`
-`@atproto/xrpc`
### Lexicon installation
Remove the old `src/lexicon/` directory (singular) and any codegen scripts that referenced `lex gen-server`. We will also remove the (manually maintained) `./lexicons/` directory in order to manage installed lexicons with the new `lex install` commands:
```sh
rm -rf ./src/lexicon
rm -rf ./lexicons
```
Install every lexicon NSID that the code uses. Run `lex install` once per NSID (or pass multiple NSIDs at once):
The `--indexFile` flag generates an index file that re-exports all root-level namespaces, and `--clear` ensures a clean output directory on each build.
The `lex install --ci` command will ensure that the `manifest.json` is up to date with the installed lexicons. Using the `postinstall` hook ensures that the command runs after `npm install` or `yarn install`, which ensures lexicon integrity in CI environments.
The `./src/lexicons/` directory (generated output) should be gitignored since it is regenerated on every build:
```sh
echo'/src/lexicons/' >> .gitignore
```
## 2. Project Code Setup
After running `lex build`, the new generated code lives in `src/lexicons/` (plural). Import the namespace objects from the index file:
```typescript
import{app,com,chat}from'../lexicons/index.js'
```
Each namespace provides access to schemas through dot notation:
-`app.bsky.feed.post` - a record schema
-`app.bsky.feed.defs.postView` - an object definition
-`com.atproto.admin.defs.repoRef` - another object definition
-`app.bsky.feed.getAuthorFeed` - a query/procedure schema
The old codegen used `ids` for NSID string constants (e.g., `ids.AppBskyFeedPost`). These are replaced with `$type` or `$lxm` properties on the schema objects.
> [!NOTE]
>
> If the app's build process & bundler supports it, consider using path aliases to simplify imports from `src/lexicons/index.js` (e.g., `import { app } from '#lexicons'`).
### Server Creation
The `createServer` function now comes from `@atproto/xrpc-server` directly, not from generated code:
```diff
- import { createServer } from './lexicon'
+ import { createServer } from '@atproto/xrpc-server'
```
The call signature changes slightly:
```diff
- let server = createServer({
+ const server = createServer([], {
validateResponse: config.debugMode,
payload: { ... },
})
```
Note the empty array `[]` as first argument.
The `Server` type used in handler files also changes:
```diff
- import { Server } from '../../../../lexicon'
+ import { Server } from '@atproto/xrpc-server'
```
The server's express router is accessed differently:
```diff
- app.use(server.xrpc.router)
+ app.use(server.router)
```
### App Context / Initialization
Replace `AtpAgent` with `Client` from `@atproto/lex`:
Headers are passed directly in the `Client` constructor options rather than being set imperatively after construction.
### Type Aliases File (if applicable)
The recommended pattern is to import from the generated code directly where needed, using import aliases. However, if the project contains a centralized types file that re-exports types from generated schemas, update it to import from the new generated code.
Otherwise, for plain data objects that don't require a `$type`, just use the namespace types for type annotations without changing the construction logic:
- Response headers use the standard `Headers` API (`.get()`)
- Parameters go in a `params` sub-object
- Procedure input goes in an `input` sub-object (not used in this example)
### Using `Client.call()`
Calls that only need the response body, and are happy letting any error propagate, can use the simpler `call()` method:
```typescript
constbody=awaitctx.suggestionsClient.call(
app.bsky.unspecced.getSuggestionsSkeleton,
// "params" for Queries, "input" for Procedures:
{viewer: params.hydrateCtx.viewer,relativeToDid},
// Optional additional options (see API reference):
{
headers,
signal,
},
)
```
### Using `xrpcSafe()` for Error Handling
For calls where you want to handle errors without exceptions, use `xrpcSafe()`:
```diff
- import { AtpAgent } from '@atproto/api'
- import { ResponseType, XRPCError } from '@atproto/xrpc'
+ import { xrpcSafe } from '@atproto/lex'
- const agent = new AtpAgent({ service: fgEndpoint })
- try {
- const result = await agent.api.app.bsky.feed.getFeedSkeleton(
- { feed, limit, cursor },
- { headers },
- )
- skeleton = result.data
- } catch (err) {
- if (err instanceof AppBskyFeedGetFeedSkeleton.UnknownFeedError) { ... }
- if (err instanceof XRPCError) {
- if (err.status === ResponseType.Unknown) { ... }
- if (err.status === ResponseType.InvalidResponse) { ... }
- }
- throw err
- }
+ const result = await xrpcSafe(fgEndpoint, app.bsky.feed.getFeedSkeleton, {
+ headers,
+ params: { feed, limit, cursor },
+ })
+ if (!result.success) {
+ if (result.matchesSchemaErrors()) {
+ throw new InvalidRequestError(result.message, result.error)
+ }
+ if (result.error === 'InternalServerError') { ... }
+ if (result.error === 'UpstreamFailure') { ... }
+ throw result.reason
+ }
+ // result.body is the typed response
```
### XrpcError
Replace `XRPCError` from `@atproto/xrpc` with `XrpcError` from `@atproto/lex`:
```diff
- import { XRPCError } from '@atproto/xrpc'
+ import { XrpcError } from '@atproto/lex'
```
## 5. Type Strictness (Branded Types)
`@atproto/lex` exports branded string types that improve type safety. Apply these at type boundaries — function signatures, interface fields, database schema types — while keeping runtime code unchanged where possible.
### `DidString`
```diff
- did: string
+ did: DidString
```
```diff
- iss: string
+ iss: DidString | `${DidString}#${string}`
```
Use the type guard instead of string prefix checks:
```diff
- if (typeof iss !== 'string' || !iss.startsWith('did:')) {
+ if (typeof iss !== 'string' || !isDidString(iss)) {
The old `BlobRef` class from `@atproto/lexicon` is replaced by a simple interface from `@atproto/lex-data`. It is no longer a class, so `instanceof` checks are not possible anymore. Instead, use type guards to check if an object is a `BlobRef`:
Legacy blob references (`{ cid: string, mimeType: string }`) are automatically handled based on the **strict mode** setting. When `strict: false`, both standard and legacy blob formats are accepted. When `strict: true` (the default), only standard `TypedBlobRef` format is accepted.
```typescript
import{
TypedBlobRef,
LegacyBlobRef,
isTypedBlobRef,
isLegacyBlobRef,
}from'@atproto/lex-data'
// Check for standard BlobRef
if(isTypedBlobRef(value)){
console.log(value.ref.toString())
}
// Check for legacy format
if(isLegacyBlobRef(value)){
console.log(value.cid)
}
```
New utility functions are available for working with both formats:
constsize=getBlobSize(blobRef)// Returns number | undefined (legacy refs don't have size)
```
### Strict Mode in Validation
All schema validation methods (`$parse`, `$safeParse`, `$validate`, `$safeValidate`) accept an optional `{ strict }` option that controls validation behavior uniformly across both parse and validate modes:
**Strict mode (`strict: true`, the default):**
- Datetime strings must have proper timezone information
- Blob MIME types and size constraints are enforced
- Only raw CIDs are allowed in blob references
- Legacy blob references are rejected
**Non-strict mode (`strict: false`):**
- Datetime strings without timezones are accepted
- Blob MIME type and size constraints are not enforced
- Any valid CID is allowed in blob references
- Legacy blob references are accepted
```typescript
// Default strict validation
constresult1=schema.$safeParse(data)// strict: true by default
The `Client` class has a `strictResponseProcessing` option that controls the default strict mode for all XRPC calls:
```typescript
constclient=newClient(session,{
strictResponseProcessing: false,// Use non-strict mode for all calls
})
```
When `strictResponseProcessing: false`, response validation will use `strict: false`, which means legacy blobs and other lenient data formats are automatically accepted. Individual calls can override this with per-call options.
### Lex Stringify
```diff
- import { stringifyLex } from '@atproto/lexicon'
+ import { lexStringify } from '@atproto/lex'
```
## 7. Schema Validation and Type Guards
### `$matches()` — Validates and Narrows Unknown Data
Use `$matches()` when the data has not been pre-validated (e.g., it comes from an external source, or you need full runtime validation):
```diff
- import { isRepoRef } from '../../../../lexicon/types/com/atproto/admin/defs'
- if (isRepoRef(subject)) { ... }
+ if (com.atproto.admin.defs.repoRef.$matches(subject)) { ... }
### `$isTypeOf` — Discriminates Pre-Validated Data by `$type`
Use `$isTypeOf` when the data is already validated and you only need to discriminate based on the `$type` property. This is faster than `$matches()` because it skips validation. Common in `.find()` and `.filter()` callbacks on arrays of already-parsed preference objects or union members:
`$build()` automatically sets the `$type` field and returns a properly typed object.
## Tests
Tests still rely exclusively on `@atproto/api`. When tests previously imported from the old `src/lexicon/` directory, redirect those imports to `@atproto/api`:
```diff
- import { ids } from '../../src/lexicon/lexicons'
- import { RepoRef, isRepoRef } from '../../src/lexicon/types/com/atproto/admin/defs'
- import { $Typed } from '../../src/lexicon/util'
+ import { $Typed, AtpAgent, ComAtprotoAdminDefs, ids } from '@atproto/api'
```
Do not change how tests make XRPC calls — they continue to use `AtpAgent` from `@atproto/api`. This allows to ensure that the refactor does not break existing functionality at runtime. Tests will be migrated to `@atproto/lex` in a separate phase after all service code has been lexified.
## Common Pitfalls
1.**`$type` vs `$lxm`**: Use `$type` for record/object type strings (e.g., `app.bsky.feed.post.$type` = `'app.bsky.feed.post'`). Use `$lxm` for XRPC method identifiers used in auth checks and proxy routing. `$nsid` is also available for the raw NSID string if needed, this is especially useful for lexicon defs that don't have a `main` type but still need to reference their NSID.
2.**`$matches` vs `$isTypeOf`**: Use `$matches()` when data needs validation (unknown input). Use `$isTypeOf` when data is already validated and you just need to check the `$type` tag (e.g., union discrimination, filtering an array of pre-parsed objects).
3.**Branded type casts at boundaries**: Data from protobuf/data plane/Kysely returns plain strings. Cast to branded types (`as DidString`, `as AtUriString`) at the point where data enters the typed domain. Avoid `assert()` — use `as` casts at known-safe boundaries instead.
4.**`'application/json' as const`**: Handler return values need `as const` on the encoding string literal to satisfy the return type. Without it, TypeScript widens the type.
5.**Response header changes**: Old `AtpAgent` returned headers as a plain object with string indexing. New `Client`/`xrpc` returns standard `Headers` objects requiring `.get()`.
6.**`@atproto/lex-data` sub-export**: `Cid`, `parseCid`, and `BlobRef` are available from `@atproto/lex-data` for files that only need data types without the full `@atproto/lex` package. Both import paths work.
7.**Prefer `@atproto/lex` imports** over `@atproto/syntax` when both export the same symbol (e.g., `DidString`, `AtUriString`).
8.**Avoid `assert()` calls.** Use type guards (`isDidString()`, `isHandleString()`) with conditional logic rather than assertions.
description: Patterns and conventions for writing vitest tests in this project. This skill should be used when writing new tests, adding test cases to existing test files, or reviewing test code for correctness. Trigger whenever the user asks to write tests, add test coverage, create test files, or mentions vitest/testing.
---
# Vitest Test Patterns
Conventions and patterns for writing vitest tests in this codebase.
## Imports
Always import test utilities from `vitest`. Use named imports:
```ts
import{assert,describe,expect,it,vi}from'vitest'
```
Only import what you use. Add `vi` only when using mock functions. Add `assert` only when narrowing types.
## Test Structure
### Describe labels
Pass the function/class under test directly as the `describe` label when possible. Use string labels for conceptual groupings:
```ts
// Function reference as label (preferred when testing a single export)
describe(parseCid,()=>{...})
describe(isLexValue,()=>{...})
// String label for conceptual groups or when testing multiple related behaviors
Use `assert()` from vitest for type narrowing and boolean checks. **Always prefer `assert(result.success)` over `expect(result.success).toBe(true)`** — the `assert` provides type narrowing in TypeScript, which allows the rest of the test to access narrowed properties without additional type guards.
```ts
// Narrow to a specific type before further assertions
assert(errinstanceofXrpcFetchError)
expect(err.cause).toBeInstanceOf(TypeError)
// Discriminated union checks - PREFERRED
assert(result.success)
expect(result.body).toEqual({value:'hello'})
// TypeScript now knows result.body exists
assert(!result.success)
expect(result).toBeInstanceOf(XrpcResponseError)
// TypeScript now knows result has error properties
// DON'T do this - it doesn't narrow types
expect(result.success).toBe(true)// ❌ No type narrowing
if(result.success){
expect(result.body).toEqual({value:'hello'})// Still need type guard
}
```
### Error assertions with `rejects.toSatisfy`
For thrown errors, prefer `rejects.toSatisfy()` over `rejects.toThrow()` when you need multiple detailed assertions:
```ts
awaitexpect(
someAsyncFn(),
).rejects.toSatisfy((err)=>{
assert(errinstanceofSomeError)
expect(err.cause).toBeInstanceOf(TypeError)
expect(err.message).toContain('failed')
returntrue// must return true
})
```
Always `return true` at the end of `toSatisfy` callbacks.
For simple "it throws" checks, `toThrow()` is fine:
Copyright (c) 2022-2024 Bluesky PBC, and Contributors
Copyright (c) 2022-2026 Bluesky Social PBC, and Contributors
Except as otherwise noted in individual files, this software is licensed under the MIT license (<http://opensource.org/licenses/MIT>), or the Apache License, Version 2.0 (<http://www.apache.org/licenses/LICENSE-2.0>).
The Authenticated Transfer Protocol ("ATP" or "atproto") is a decentralized social media protocol, developed by [Bluesky PBC](https://bsky.social). Learn more at:
The Authenticated Transfer Protocol ("ATP" or "atproto") is a decentralized social media protocol, developed by [Bluesky Social PBC](https://bsky.social). Learn more at:
- [Overview and Guides](https://atproto.com/guides/overview) 👈 Best starting point
- [Github Discussions](https://github.com/bluesky-social/atproto/discussions) 👈 Great place to ask questions
@@ -113,3 +113,5 @@ This project is dual-licensed under MIT and Apache 2.0 terms:
- Apache License, Version 2.0, ([LICENSE-APACHE.txt](https://github.com/bluesky-social/atproto/blob/main/LICENSE-APACHE.txt) or http://www.apache.org/licenses/LICENSE-2.0)
Downstream projects and end users may chose either license individually, or both together, at their discretion. The motivation for this dual-licensing is the additional software patent assurance provided by Apache 2.0.
Bluesky Social PBC has committed to a software patent non-aggression pledge. For details see [the original announcement](https://bsky.social/about/blog/10-01-2025-patent-pledge).
"description":"The user who issued this verification.",
"format":"did"
},
"uri":{
"type":"string",
"description":"The AT-URI of the verification record.",
"format":"at-uri"
},
"isValid":{
"type":"boolean",
"description":"True if the verification passes validation, otherwise false."
},
"createdAt":{
"type":"string",
"description":"Timestamp when the verification was created.",
"format":"datetime"
}
}
},
"preferences":{
"type":"array",
"items":{
@@ -168,13 +295,17 @@
"#savedFeedsPref",
"#savedFeedsPrefV2",
"#personalDetailsPref",
"#declaredAgePref",
"#feedViewPref",
"#threadViewPref",
"#interestsPref",
"#mutedWordsPref",
"#hiddenPostsPref",
"#bskyAppStatePref",
"#labelersPref"
"#labelersPref",
"#postInteractionSettingsPref",
"#verificationPrefs",
"#liveEventPreferences"
]
}
},
@@ -266,6 +397,24 @@
}
}
},
"declaredAgePref":{
"type":"object",
"description":"Read-only preference containing value(s) inferred from the user's declared birthdate. Absence of this preference object in the response indicates that the user has not made a declaration.",
"properties":{
"isOverAge13":{
"type":"boolean",
"description":"Indicates if the user has declared that they are over 13 years of age."
},
"isOverAge16":{
"type":"boolean",
"description":"Indicates if the user has declared that they are over 16 years of age."
},
"isOverAge18":{
"type":"boolean",
"description":"Indicates if the user has declared that they are over 18 years of age."
"description":"Show followed users at the top of all replies."
}
}
},
@@ -468,6 +613,100 @@
"description":"The date and time at which the NUX will expire and should be considered completed."
}
}
},
"verificationPrefs":{
"type":"object",
"description":"Preferences for how verified accounts appear in the app.",
"required":[],
"properties":{
"hideBadges":{
"description":"Hide the blue check badges for verified accounts and trusted verifiers.",
"type":"boolean",
"default":false
}
}
},
"liveEventPreferences":{
"type":"object",
"description":"Preferences for live events.",
"properties":{
"hiddenFeedIds":{
"description":"A list of feed IDs that the user has hidden from live events.",
"type":"array",
"items":{"type":"string"}
},
"hideAllFeeds":{
"description":"Whether to hide all feeds from live events.",
"type":"boolean",
"default":false
}
}
},
"postInteractionSettingsPref":{
"type":"object",
"description":"Default post interaction settings for the account. These values should be applied as default values when creating new posts. These refs should mirror the threadgate and postgate records exactly.",
"required":[],
"properties":{
"threadgateAllowRules":{
"description":"Matches threadgate record. List of rules defining who can reply to this users posts. If value is an empty array, no one can reply. If value is undefined, anyone can reply.",
"type":"array",
"maxLength":5,
"items":{
"type":"union",
"refs":[
"app.bsky.feed.threadgate#mentionRule",
"app.bsky.feed.threadgate#followerRule",
"app.bsky.feed.threadgate#followingRule",
"app.bsky.feed.threadgate#listRule"
]
}
},
"postgateEmbeddingRules":{
"description":"Matches postgate record. List of rules defining who can embed this users posts. If value is an empty array or is undefined, no particular rules apply and anyone can embed.",
"type":"array",
"maxLength":5,
"items":{
"type":"union",
"refs":["app.bsky.feed.postgate#disableRule"]
}
}
}
},
"statusView":{
"type":"object",
"required":["status","record"],
"properties":{
"uri":{"type":"string","format":"at-uri"},
"cid":{"type":"string","format":"cid"},
"status":{
"type":"string",
"description":"The status for the account.",
"knownValues":["app.bsky.actor.status#live"]
},
"record":{"type":"unknown"},
"embed":{
"type":"union",
"description":"An optional embed associated with the status.",
"description":"The ISO 3166-1 alpha-2 country code this configuration applies to."
},
"regionCode":{
"type":"string",
"description":"The ISO 3166-2 region code this configuration applies to. If omitted, the configuration applies to the entire country."
},
"minAccessAge":{
"type":"integer",
"description":"The minimum age (as a whole integer) required to use Bluesky in this region."
},
"rules":{
"type":"array",
"description":"The ordered list of Age Assurance rules that apply to this region. Rules should be applied in order, and the first matching rule determines the access level granted. The rules array should always include a default rule as the last item.",
"items":{
"type":"union",
"refs":[
"#configRegionRuleDefault",
"#configRegionRuleIfDeclaredOverAge",
"#configRegionRuleIfDeclaredUnderAge",
"#configRegionRuleIfAssuredOverAge",
"#configRegionRuleIfAssuredUnderAge",
"#configRegionRuleIfAccountNewerThan",
"#configRegionRuleIfAccountOlderThan"
]
}
}
}
},
"configRegionRuleDefault":{
"type":"object",
"description":"Age Assurance rule that applies by default.",
"required":["access"],
"properties":{
"access":{
"type":"ref",
"ref":"app.bsky.ageassurance.defs#access"
}
}
},
"configRegionRuleIfDeclaredOverAge":{
"type":"object",
"description":"Age Assurance rule that applies if the user has declared themselves equal-to or over a certain age.",
"required":["age","access"],
"properties":{
"age":{
"type":"integer",
"description":"The age threshold as a whole integer."
},
"access":{
"type":"ref",
"ref":"app.bsky.ageassurance.defs#access"
}
}
},
"configRegionRuleIfDeclaredUnderAge":{
"type":"object",
"description":"Age Assurance rule that applies if the user has declared themselves under a certain age.",
"required":["age","access"],
"properties":{
"age":{
"type":"integer",
"description":"The age threshold as a whole integer."
},
"access":{
"type":"ref",
"ref":"app.bsky.ageassurance.defs#access"
}
}
},
"configRegionRuleIfAssuredOverAge":{
"type":"object",
"description":"Age Assurance rule that applies if the user has been assured to be equal-to or over a certain age.",
"required":["age","access"],
"properties":{
"age":{
"type":"integer",
"description":"The age threshold as a whole integer."
},
"access":{
"type":"ref",
"ref":"app.bsky.ageassurance.defs#access"
}
}
},
"configRegionRuleIfAssuredUnderAge":{
"type":"object",
"description":"Age Assurance rule that applies if the user has been assured to be under a certain age.",
"required":["age","access"],
"properties":{
"age":{
"type":"integer",
"description":"The age threshold as a whole integer."
},
"access":{
"type":"ref",
"ref":"app.bsky.ageassurance.defs#access"
}
}
},
"configRegionRuleIfAccountNewerThan":{
"type":"object",
"description":"Age Assurance rule that applies if the account is equal-to or newer than a certain date.",
"required":["date","access"],
"properties":{
"date":{
"type":"string",
"format":"datetime",
"description":"The date threshold as a datetime string."
},
"access":{
"type":"ref",
"ref":"app.bsky.ageassurance.defs#access"
}
}
},
"configRegionRuleIfAccountOlderThan":{
"type":"object",
"description":"Age Assurance rule that applies if the account is older than a certain date.",
"required":["date","access"],
"properties":{
"date":{
"type":"string",
"format":"datetime",
"description":"The date threshold as a datetime string."
},
"access":{
"type":"ref",
"ref":"app.bsky.ageassurance.defs#access"
}
}
},
"event":{
"type":"object",
"description":"Object used to store Age Assurance data in stash.",
"description":"Returns server-computed Age Assurance state, if available, and any additional metadata needed to compute Age Assurance state client-side.",
"description":"Creates a private bookmark for the specified record. Currently, only `app.bsky.feed.post` records are supported. Requires authentication.",
"input":{
"encoding":"application/json",
"schema":{
"type":"object",
"required":["uri","cid"],
"properties":{
"uri":{"type":"string","format":"at-uri"},
"cid":{"type":"string","format":"cid"}
}
}
},
"errors":[
{
"name":"UnsupportedCollection",
"description":"The URI to be bookmarked is for an unsupported collection."
"description":"Deletes a private bookmark for the specified record. Currently, only `app.bsky.feed.post` records are supported. Requires authentication.",
"input":{
"encoding":"application/json",
"schema":{
"type":"object",
"required":["uri"],
"properties":{
"uri":{"type":"string","format":"at-uri"}
}
}
},
"errors":[
{
"name":"UnsupportedCollection",
"description":"The URI to be bookmarked is for an unsupported collection."
"description":"Associates a profile with the positional index of the contact import input in the call to `app.bsky.contact.importContacts`, so clients can know which phone caused a particular match.",
"type":"object",
"required":["match","contactIndex"],
"properties":{
"match":{
"description":"Profile of the matched user.",
"type":"ref",
"ref":"app.bsky.actor.defs#profileView"
},
"contactIndex":{
"description":"The index of this match in the import contact input.",
"type":"integer",
"minimum":0,
"maximum":999
}
}
},
"syncStatus":{
"type":"object",
"required":["syncedAt","matchesCount"],
"properties":{
"syncedAt":{
"description":"Last date when contacts where imported.",
"type":"string",
"format":"datetime"
},
"matchesCount":{
"description":"Number of existing contact matches resulting of the user imports and of their imported contacts having imported the user. Matches stop being counted when the user either follows the matched contact or dismisses the match.",
"type":"integer",
"minimum":0
}
}
},
"notification":{
"description":"A stash object to be sent via bsync representing a notification to be created.",
"type":"object",
"required":["from","to"],
"properties":{
"from":{
"description":"The DID of who this notification comes from.",
"type":"string",
"format":"did"
},
"to":{
"description":"The DID of who this notification should go to.",
"description":"Removes a match that was found via contact import. It shouldn't appear again if the same contact is re-imported. Requires authentication.",
"input":{
"encoding":"application/json",
"schema":{
"type":"object",
"required":["subject"],
"properties":{
"subject":{
"description":"The subject's DID to dismiss the match with.",
"description":"Gets the user's current contact import status. Requires authentication.",
"parameters":{
"type":"params",
"properties":{}
},
"output":{
"encoding":"application/json",
"schema":{
"type":"object",
"properties":{
"syncStatus":{
"description":"If present, indicates the user has imported their contacts. If not present, indicates the user never used the feature or called `app.bsky.contact.removeData` and didn't import again since.",
"description":"Import contacts for securely matching with other users. This follows the protocol explained in https://docs.bsky.app/blog/contact-import-rfc. Requires authentication.",
"input":{
"encoding":"application/json",
"schema":{
"type":"object",
"required":["token","contacts"],
"properties":{
"token":{
"description":"JWT to authenticate the call. Use the JWT received as a response to the call to `app.bsky.contact.verifyPhone`.",
"type":"string"
},
"contacts":{
"description":"List of phone numbers in global E.164 format (e.g., '+12125550123'). Phone numbers that cannot be normalized into a valid phone number will be discarded. Should not repeat the 'phone' input used in `app.bsky.contact.verifyPhone`.",
"type":"array",
"items":{
"type":"string"
},
"minLength":1,
"maxLength":1000
}
}
}
},
"output":{
"encoding":"application/json",
"schema":{
"type":"object",
"required":["matchesAndContactIndexes"],
"properties":{
"matchesAndContactIndexes":{
"description":"The users that matched during import and their indexes on the input contacts, so the client can correlate with its local list.",
"description":"Starts a phone verification flow. The phone passed will receive a code via SMS that should be passed to `app.bsky.contact.verifyPhone`. Requires authentication.",
"input":{
"encoding":"application/json",
"schema":{
"type":"object",
"required":["phone"],
"properties":{
"phone":{
"description":"The phone number to receive the code via SMS.",
"description":"A draft with an identifier, used to store drafts in private storage (stash).",
"type":"object",
"required":["id","draft"],
"properties":{
"id":{
"description":"A TID to be used as a draft identifier.",
"type":"string",
"format":"tid"
},
"draft":{
"type":"ref",
"ref":"#draft"
}
}
},
"draft":{
"description":"A draft containing an array of draft posts.",
"type":"object",
"required":["posts"],
"properties":{
"deviceId":{
"type":"string",
"description":"UUIDv4 identifier of the device that created this draft.",
"maxLength":100
},
"deviceName":{
"type":"string",
"description":"The device and/or platform on which the draft was created.",
"maxLength":100
},
"posts":{
"description":"Array of draft posts that compose this draft.",
"type":"array",
"minLength":1,
"maxLength":100,
"items":{
"type":"ref",
"ref":"#draftPost"
}
},
"langs":{
"type":"array",
"description":"Indicates human language of posts primary text content.",
"maxLength":3,
"items":{"type":"string","format":"language"}
},
"postgateEmbeddingRules":{
"description":"Embedding rules for the postgates to be created when this draft is published.",
"type":"array",
"maxLength":5,
"items":{
"type":"union",
"refs":["app.bsky.feed.postgate#disableRule"]
}
},
"threadgateAllow":{
"description":"Allow-rules for the threadgate to be created when this draft is published.",
"type":"array",
"maxLength":5,
"items":{
"type":"union",
"refs":[
"app.bsky.feed.threadgate#mentionRule",
"app.bsky.feed.threadgate#followerRule",
"app.bsky.feed.threadgate#followingRule",
"app.bsky.feed.threadgate#listRule"
]
}
}
}
},
"draftPost":{
"description":"One of the posts that compose a draft.",
"type":"object",
"required":["text"],
"properties":{
"text":{
"type":"string",
"maxLength":10000,
"maxGraphemes":1000,
"description":"The primary post content. It has a higher limit than post contents to allow storing a larger text that can later be refined into smaller posts."
},
"labels":{
"type":"union",
"description":"Self-label values for this post. Effectively content warnings.",
"description":"Updates a draft using private storage (stash). If the draft ID points to a non-existing ID, the update will be silently ignored. This is done because updates don't enforce draft limit, so it accepts all writes, but will ignore invalid ones. Requires authentication.",
"description":"List of AT-URIs embedding this post that the author has detached from."
},
"embeddingRules":{
"description":"List of rules defining who can embed this post. If value is an empty array or is undefined, no particular rules apply and anyone can embed.",
"description":"Find posts matching search criteria, returning views of those posts.",
"description":"Find posts matching search criteria, returning views of those posts. Note that this API endpoint may require authentication (eg, not public) for some service providers and implementations.",
"description":"Reference (AT-URI) to the post record."
},
"allow":{
"description":"List of rules defining who can reply to this post. If value is an empty array, no one can reply. If value is undefined, anyone can reply.",
"description":"Enumerates the lists created by the session user, and includes membership information about `actor` in those lists. Only supports curation and moderation lists (no reference lists, used in starter packs). Requires auth.",
"parameters":{
"type":"params",
"required":["actor"],
"properties":{
"actor":{
"type":"string",
"format":"at-identifier",
"description":"The account (actor) to check for membership."
},
"limit":{
"type":"integer",
"minimum":1,
"maximum":100,
"default":50
},
"cursor":{"type":"string"},
"purposes":{
"type":"array",
"description":"Optional filter by list purpose. If not specified, all supported types are returned.",
"description":"Enumerates the starter packs created by the session user, and includes membership information about `actor` in those starter packs. Requires auth.",
"parameters":{
"type":"params",
"required":["actor"],
"properties":{
"actor":{
"type":"string",
"format":"at-identifier",
"description":"The account (actor) to check for membership."
"description":"Record declaring a verification relationship between two accounts. Verifications are only considered valid by an app if issued by an account the app considers trusted.",
"description":"DID of the subject the verification applies to.",
"type":"string",
"format":"did"
},
"handle":{
"description":"Handle of the subject the verification applies to at the moment of verifying, which might not be the same at the time of viewing. The verification is only valid if the current handle matches the one at the time of verifying.",
"type":"string",
"format":"handle"
},
"displayName":{
"description":"Display name of the subject the verification applies to at the moment of verifying, which might not be the same at the time of viewing. The verification is only valid if the current displayName matches the one at the time of verifying.",
"type":"string"
},
"createdAt":{
"description":"Date of when the verification was created.",
"description":"The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed.",
"type":"array",
"items":{
"type":"ref",
"ref":"com.atproto.moderation.defs#reasonType"
}
},
"subjectTypes":{
"description":"The set of subject types (account, record, etc) this service accepts reports on.",
"type":"array",
"items":{
"type":"ref",
"ref":"com.atproto.moderation.defs#subjectType"
}
},
"subjectCollections":{
"type":"array",
"description":"Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type.",
"description":"The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed.",
"type":"array",
"items":{
"type":"ref",
"ref":"com.atproto.moderation.defs#reasonType"
}
},
"subjectTypes":{
"description":"The set of subject types (account, record, etc) this service accepts reports on.",
"type":"array",
"items":{
"type":"ref",
"ref":"com.atproto.moderation.defs#subjectType"
}
},
"subjectCollections":{
"type":"array",
"description":"Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type.",
"description":"The inverse of registerPush - inform a specified service that push notifications should no longer be sent to the given token for the requesting account. Requires auth.",
"description":"This post has more parents that were not present in the response. This is just a boolean, without the number of parents."
},
"moreReplies":{
"type":"integer",
"description":"This post has more replies that were not present in the response. This is a numeric value, which is best-effort and might not be accurate."
},
"opThread":{
"type":"boolean",
"description":"This post is part of a contiguous thread by the OP from the thread root. Many different OP threads can happen in the same thread."
},
"hiddenByThreadgate":{
"type":"boolean",
"description":"The threadgate created by the author indicates this post as a reply to be hidden for everyone consuming the thread."
},
"mutedByViewer":{
"type":"boolean",
"description":"This is by an account muted by the viewer requesting it."
"description":"Returns the current state of the age assurance process for an account. This is used to check if the user has completed age assurance or if further action is required.",
"output":{
"encoding":"application/json",
"schema":{
"type":"ref",
"ref":"app.bsky.unspecced.defs#ageAssuranceState"
}
}
}
}
}
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.