Skip to main content

lexification-server

Migrate a service package off the legacy server stack — `lex gen-server` codegen, a committed `src/lexicon/` tree, `server.<ns>.<method>()` route chains, `@atproto/api` inside service code — onto `@atproto/lex` and `@atproto/xrpc-server`. Use when asked to lexify, modernize, or migrate a server package; when a route file imports `Server` or `createServer` from `../lexicon/index.js`; when replacing `ids.*` constants, generated `isX()` guards, `server.xrpc.router`, or `QueryParams`/`OutputSchema` type imports; and when a package still runs `lex gen-server` in its `codegen:lex` script. For the calls a service makes *out* to other services, use lexification-client. For writing new routes on an already-migrated package, use xrpc-server.

Informações da origem

Repositório
bluesky-social/atproto
Última atividade na origem
2 de setembro de 2026 às 09:57
Idioma detectado do SKILL.md
inglês
Estrelas
9.680
Forks
932

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Explorador de arquivos
4 arquivos

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
lexification-server
description
Migrate a service package off the legacy server stack — `lex gen-server` codegen, a committed `src/lexicon/` tree, `server.<ns>.<method>()` route chains, `@atproto/api` inside service code — onto `@atproto/lex` and `@atproto/xrpc-server`. Use when asked to lexify, modernize, or migrate a server package; when a route file imports `Server` or `createServer` from `../lexicon/index.js`; when replacing `ids.*` constants, generated `isX()` guards, `server.xrpc.router`, or `QueryParams`/`OutputSchema` type imports; and when a package still runs `lex gen-server` in its `codegen:lex` script. For the calls a service makes *out* to other services, use lexification-client. For writing new routes on an already-migrated package, use xrpc-server.
disable-model-invocation
false
# Lexification: migrating a service package Moves a package from `@atproto/lex-cli`'s `lex gen-server` output to `@atproto/lex` schemas plus `@atproto/xrpc-server` routing. Every service in this repo — `packages/pds`, `packages/bsky`, `packages/ozone` — is already migrated, so this skill now applies to packages outside the repo, or to a stray route chain left behind. **Read the migrated services before inventing a pattern.** ## Which skill applies | Situation | Skill | | ------------------------------------------------------------ | ------------------------------------------------------ | | Package has `src/lexicon/` (singular) and route chains | **this skill** | | Adding a route to a package already on `server.add()` | [xrpc-server](../xrpc-server/SKILL.md) | | The same package's outbound `AtpAgent` / `agent.api.…` calls | [lexification-client](../lexification-client/SKILL.md) | A service migration almost always needs this skill _and_ lexification-client: one package both serves routes and calls other services. Do the server half first — it forces `src/lexicons/` into existence, which the client half then imports from. ## Guiding principles - **Type-level changes over runtime changes.** Working runtime logic stays; what changes is where types and constants come from. A migration diff that alters behavior is a bug in the migration. - **Migrate one namespace of route files at a time**, building in between. A route file is self-contained, so a broken one doesn't block the rest. - **Leave test call sites on `AtpAgent`.** Tests double as the regression check that the migration didn't change behavior — swapping the client they use destroys that signal. Tests that import _types or constants_ from the deleted `src/lexicon/` do have to move (details below). ## Order of work 1. **Project config** — swap codegen, deps, gitignore, delete `src/lexicon/`. → [references/project-config.md](references/project-config.md) 2. **Server bootstrap** — `createServer`, `Server` type, Express mount. 3. **Route files** — `server.<ns>.<method>(…)` → `server.add(schema, …)`. → [references/routes.md](references/routes.md) 4. **Constants and guards** — `ids.*`, `isX()`, token exports, `$build`. → [references/types-and-guards.md](references/types-and-guards.md) 5. **Outbound calls** — [lexification-client](../lexification-client/SKILL.md). 6. **Branded types** at function/interface/DB boundaries. → [references/types-and-guards.md](references/types-and-guards.md) Steps 1–2 are a single non-splittable commit (the package won't compile in between). Steps 3–6 are incremental. ## Server bootstrap The only structural change in the whole migration. `createServer` and `Server` stop coming from generated code: ```diff - import { createServer } from './lexicon/index.js' + import { createServer } from '@atproto/xrpc-server' - let server = createServer({ + const server = createServer([], { validateResponse: config.debugMode, payload: { /* … */ }, }) - app.use(server.xrpc.router) + app.use(server.router) ``` The new first argument is an array of legacy `LexiconDoc`s for routes not backed by a generated schema; schema-based setups pass `[]`. Option names (`validateResponse`, `payload`, `catchall`, `rateLimits`, `errorParser`) are unchanged — see [packages/pds/src/index.ts](../../../packages/pds/src/index.ts). In route files, `Server` comes from the same package: ```diff - import { Server } from '../../../../lexicon/index.js' + import type { Server } from '@atproto/xrpc-server' ``` ## Import mapping | Before | After | | ------------------------------------------------------ | -------------------------------------------------------------------- | | `../lexicon` (`Server`, `createServer`) | `@atproto/xrpc-server` | | `../lexicon/lexicons` (`ids`) | `../lexicons/index.js` → `.$type` / `.$lxm` | | `../lexicon/types/…` (record & def types) | `../lexicons/index.js` → `.Main`, `.ViewRecord`, … | | `../lexicon/types/…` (`QueryParams`, `OutputSchema`) | `../lexicons/index.js` → `.$Params`, `.$OutputBody` | | `../lexicon/types/…` (`isX`, `validateX`) | `../lexicons/index.js` → `.$isTypeOf`, `.$matches`, `.$safeValidate` | | `../lexicon/util` (`$Typed`, `Un$Typed`) | `@atproto/lex` | | `@atproto/api` (`AtpAgent`) | `@atproto/lex` (`Client`) | | `@atproto/lexicon` (`jsonStringToLex`, `stringifyLex`) | `@atproto/lex` (`lexParse`, `lexStringify`) | | `@atproto/lexicon` (`BlobRef`) | `@atproto/lex` (`BlobRef`) | | `@atproto/xrpc` (`HeadersMap`) | `@atproto/xrpc-server` (`Headers`, usually aliased) | | `@atproto/xrpc` (`XRPCError`, thrown by a _caller_) | `@atproto/lex` (`XrpcError`) | | `multiformats/cid` (`CID`) | `@atproto/lex` (`Cid`, `parseCid`) | `@atproto/xrpc-server` keeps its own `XRPCError` for errors handlers **throw**. The casing distinguishes them: `XRPCError` server-side (concrete, thrown), `XrpcError` client-side (abstract, caught). Both can appear in one file. `@atproto/lex` re-exports `lex-client`, `lex-schema`, `lex-data`, and `lex-json`, so `Cid`, `BlobRef`, `lexParse`, and `$Typed` all resolve from it. `@atproto/lex-cbor` is **not** re-exported. Importing from either the umbrella or the sub-package is fine; match whatever the file already does. ## Pitfalls - **`$type` vs `$lxm` vs `$nsid`.** `$type` is the record/object type string (`app.bsky.feed.post.$type` → `'app.bsky.feed.post'`), used for collections and union tags. `$lxm` is the method id, used in auth and proxy checks. `$nsid` is the raw NSID, needed for `defs` documents that have no `main`. They can hold identical strings, so a wrong one type-checks and only fails at runtime. - **`$matches` vs `$isTypeOf`.** `$isTypeOf` only reads the `$type` tag; `$matches` validates the whole value. Substituting `$isTypeOf` where the data is unvalidated silently accepts malformed records. See [references/types-and-guards.md](references/types-and-guards.md). - **`encoding` widening** affects only the bare-handler form of `server.add`, not the object form. Don't sprinkle `as const` everywhere — see [references/routes.md](references/routes.md). - **Branded strings arriving from outside TypeScript's view** — protobuf, Kysely rows, `JSON.parse` — are plain `string`. Cast once at the boundary (`as DidString`), never deep in the call graph. - **Generated `src/lexicons/` is gitignored.** It won't exist until `pnpm run codegen` (or a build) runs. "Cannot find module '../lexicons/index.js'" means codegen hasn't run, not that the import is wrong. ## Tests Test _call sites_ keep using `AtpAgent`, deliberately (see principles above). `dev-env` exposes both `getAgent(): AtpAgent` and `getClient(): Client`, so a new-style client is available when a test genuinely needs one. What must move is anything a test imported from the now-deleted `src/lexicon/`. Two valid destinations, both already used in migrated packages: ```diff - import { ids } from '../../src/lexicon/lexicons' - import { isRepoRef } from '../../src/lexicon/types/com/atproto/admin/defs' + import { com } from '../../src/lexicons/index.js' // generated schemas + import { $Typed, ComAtprotoAdminDefs, ids } from '@atproto/api' // legacy equivalents ``` Prefer `../../src/lexicons/index.js` for new code; `@atproto/api` is the lower-churn option when the surrounding test is otherwise untouched. Do not add `@atproto/api` back to a package's dependencies just for this — it is a devDependency in `pds` and only a runtime dependency in `bsky` because of `getAgeAssuranceRegionConfig`, unrelated to tests. Before writing or restructuring tests, use the [testing skill](../testing/SKILL.md) — `pds` and `ozone` are jest, `bsky` is vitest. ## Related skills [xrpc-server](../xrpc-server/SKILL.md) documents the target route API in full and is the better reference once a package is migrated. [lex-setup](../lex-setup/SKILL.md) owns codegen wiring, [lex-schema](../lex-schema/SKILL.md) the `$`-accessors, and [lex-data](../lex-data/SKILL.md) branded strings, CIDs, and blobs.
Ver no GitHub