Scratch space for learning atproto app development

lexgen an example lexicon

dholms 321f3435 a90cbaf4

Changed files
+258 -10
lexicons
src
lexicon
types
example
lexicon
+18
lexicons/status.json
···
+
{
+
"lexicon": 1,
+
"id": "example.lexicon.status",
+
"defs": {
+
"main": {
+
"type": "record",
+
"key": "literal:self",
+
"record": {
+
"type": "object",
+
"required": ["status", "updatedAt"],
+
"properties": {
+
"status": { "type": "string" },
+
"updatedAt": { "type": "string", "format": "datetime" }
+
}
+
}
+
}
+
}
+
}
+10 -2
package.json
···
"dev": "tsx watch --clear-screen=false src/index.ts | pino-pretty",
"build": "tsup",
"start": "node dist/index.js",
+
"lexgen": "lex gen-server ./src/lexicon ./lexicons/*",
"clean": "rimraf dist coverage",
"lint": "biome check src/",
"lint:fix": "biome check src/ --fix",
···
"pino-http": "^10.0.0"
},
"devDependencies": {
+
"@atproto/lex-cli": "^0.4.1",
"@biomejs/biome": "1.8.3",
"@types/better-sqlite3": "^7.6.11",
"@types/cors": "^2.8.17",
···
"vitest": "^2.0.0"
},
"lint-staged": {
-
"*.{js,ts,cjs,mjs,d.cts,d.mts,json,jsonc}": ["biome check --apply --no-errors-on-unmatched"]
+
"*.{js,ts,cjs,mjs,d.cts,d.mts,json,jsonc}": [
+
"biome check --apply --no-errors-on-unmatched"
+
]
},
"tsup": {
-
"entry": ["src", "!src/**/__tests__/**", "!src/**/*.test.*"],
+
"entry": [
+
"src",
+
"!src/**/__tests__/**",
+
"!src/**/*.test.*"
+
],
"splitting": false,
"sourcemap": true,
"clean": true
+89 -8
pnpm-lock.yaml
···
version: 10.2.0
devDependencies:
+
'@atproto/lex-cli':
+
specifier: ^0.4.1
+
version: 0.4.1
'@biomejs/biome':
specifier: 1.8.3
version: 1.8.3
···
multiformats: 9.9.0
uint8arrays: 3.0.0
zod: 3.23.8
-
dev: false
/@atproto/common@0.4.1:
resolution: {integrity: sha512-uL7kQIcBTbvkBDNfxMXL6lBH4fO2DQpHd2BryJxMtbw/4iEPKe9xBYApwECHhEIk9+zhhpTRZ15FJ3gxTXN82Q==}
···
uint8arrays: 3.0.0
dev: false
+
/@atproto/lex-cli@0.4.1:
+
resolution: {integrity: sha512-QP9mE8MYzXR2ydhCBb/mtGqKZjqpffqcpZCr7JM4mFOZPvXV8k7OqVP1h+T94JB/tGcGPhB750S6tqUH9VRLVg==}
+
hasBin: true
+
dependencies:
+
'@atproto/lexicon': 0.4.0
+
'@atproto/syntax': 0.3.0
+
chalk: 4.1.2
+
commander: 9.5.0
+
prettier: 3.3.3
+
ts-morph: 16.0.0
+
yesno: 0.4.0
+
zod: 3.23.8
+
dev: true
+
/@atproto/lexicon@0.4.0:
resolution: {integrity: sha512-RvCBKdSI4M8qWm5uTNz1z3R2yIvIhmOsMuleOj8YR6BwRD+QbtUBy3l+xQ7iXf4M5fdfJFxaUNa6Ty0iRwdKqQ==}
dependencies:
···
iso-datestring-validator: 2.2.2
multiformats: 9.9.0
zod: 3.23.8
-
dev: false
/@atproto/repo@0.4.1:
resolution: {integrity: sha512-DXv/cBwRcAM0KFb4SwafcQBONd0g31QUNLfjTri1bg5adCbX3bxxE4fCPpQM9Qc3+5lcCkTL/EniHW1j3UQjVA==}
···
/@atproto/syntax@0.3.0:
resolution: {integrity: sha512-Weq0ZBxffGHDXHl9U7BQc2BFJi/e23AL+k+i5+D9hUq/bzT4yjGsrCejkjq0xt82xXDjmhhvQSZ0LqxyZ5woxA==}
-
dev: false
/@atproto/xrpc-server@0.5.3:
resolution: {integrity: sha512-Gxe5dPDp7mj7E1JaK0yEwGuWot78/HjszHYakqleKp+IXlM+iZxH0N20O+x7b3g7itImuQ2LzH3Zk1jLB0yZjQ==}
···
dev: true
optional: true
+
/@ts-morph/common@0.17.0:
+
resolution: {integrity: sha512-RMSSvSfs9kb0VzkvQ2NWobwnj7TxCA9vI/IjR9bDHqgAyVbu2T0DN4wiKVqomyDWqO7dPr/tErSfq7urQ1Q37g==}
+
dependencies:
+
fast-glob: 3.3.2
+
minimatch: 5.1.6
+
mkdirp: 1.0.4
+
path-browserify: 1.0.1
+
dev: true
+
/@types/better-sqlite3@7.6.11:
resolution: {integrity: sha512-i8KcD3PgGtGBLl3+mMYA8PdKkButvPyARxA7IQAd6qeslht13qxb1zzO8dRCtE7U3IoJS782zDBAeoKiM695kg==}
dependencies:
···
pathval: 2.0.0
dev: true
+
/chalk@4.1.2:
+
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+
engines: {node: '>=10'}
+
dependencies:
+
ansi-styles: 4.3.0
+
supports-color: 7.2.0
+
dev: true
+
/chalk@5.3.0:
resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
···
string-width: 7.2.0
dev: true
+
/code-block-writer@11.0.3:
+
resolution: {integrity: sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==}
+
dev: true
+
/color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
···
/commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
+
dev: true
+
+
/commander@9.5.0:
+
resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
+
engines: {node: ^12.20.0 || >=14}
dev: true
/component-emitter@1.3.1:
···
/graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
-
dev: false
+
+
/has-flag@4.0.0:
+
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+
engines: {node: '>=8'}
+
dev: true
/has-property-descriptors@1.0.2:
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
···
/iso-datestring-validator@2.2.2:
resolution: {integrity: sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==}
-
dev: false
/jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
···
engines: {node: '>=10'}
dev: false
+
/minimatch@5.1.6:
+
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
+
engines: {node: '>=10'}
+
dependencies:
+
brace-expansion: 2.0.1
+
dev: true
+
/minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
···
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
dev: false
+
/mkdirp@1.0.4:
+
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
+
engines: {node: '>=10'}
+
hasBin: true
+
dev: true
+
/ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
dev: false
···
/multiformats@9.9.0:
resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==}
-
dev: false
/mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
···
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
dev: false
+
+
/path-browserify@1.0.1:
+
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
+
dev: true
/path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
···
tunnel-agent: 0.6.0
dev: false
+
/prettier@3.3.3:
+
resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==}
+
engines: {node: '>=14'}
+
hasBin: true
+
dev: true
+
/process-warning@3.0.0:
resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==}
dev: false
···
- supports-color
dev: true
+
/supports-color@7.2.0:
+
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+
engines: {node: '>=8'}
+
dependencies:
+
has-flag: 4.0.0
+
dev: true
+
/tar-fs@2.1.1:
resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==}
dependencies:
···
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
dev: true
+
/ts-morph@16.0.0:
+
resolution: {integrity: sha512-jGNF0GVpFj0orFw55LTsQxVYEUOCWBAbR5Ls7fTYE5pQsbW18ssTb/6UXx/GYAEjS+DQTp8VoTw0vqYMiaaQuw==}
+
dependencies:
+
'@ts-morph/common': 0.17.0
+
code-block-writer: 11.0.3
+
dev: true
+
/tsconfck@3.1.1(typescript@5.5.4):
resolution: {integrity: sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==}
engines: {node: ^18 || >=20}
···
resolution: {integrity: sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==}
dependencies:
multiformats: 9.9.0
-
dev: false
/undici-types@6.13.0:
resolution: {integrity: sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==}
···
hasBin: true
dev: true
+
/yesno@0.4.0:
+
resolution: {integrity: sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA==}
+
dev: true
+
/zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
-
dev: false
+69
src/lexicon/index.ts
···
+
/**
+
* GENERATED CODE - DO NOT MODIFY
+
*/
+
import {
+
createServer as createXrpcServer,
+
Server as XrpcServer,
+
Options as XrpcOptions,
+
AuthVerifier,
+
StreamAuthVerifier,
+
} from '@atproto/xrpc-server'
+
import { schemas } from './lexicons'
+
+
export function createServer(options?: XrpcOptions): Server {
+
return new Server(options)
+
}
+
+
export class Server {
+
xrpc: XrpcServer
+
example: ExampleNS
+
+
constructor(options?: XrpcOptions) {
+
this.xrpc = createXrpcServer(schemas, options)
+
this.example = new ExampleNS(this)
+
}
+
}
+
+
export class ExampleNS {
+
_server: Server
+
lexicon: ExampleLexiconNS
+
+
constructor(server: Server) {
+
this._server = server
+
this.lexicon = new ExampleLexiconNS(server)
+
}
+
}
+
+
export class ExampleLexiconNS {
+
_server: Server
+
+
constructor(server: Server) {
+
this._server = server
+
}
+
}
+
+
type SharedRateLimitOpts<T> = {
+
name: string
+
calcKey?: (ctx: T) => string
+
calcPoints?: (ctx: T) => number
+
}
+
type RouteRateLimitOpts<T> = {
+
durationMs: number
+
points: number
+
calcKey?: (ctx: T) => string
+
calcPoints?: (ctx: T) => number
+
}
+
type HandlerOpts = { blobLimit?: number }
+
type HandlerRateLimitOpts<T> = SharedRateLimitOpts<T> | RouteRateLimitOpts<T>
+
type ConfigOf<Auth, Handler, ReqCtx> =
+
| Handler
+
| {
+
auth?: Auth
+
opts?: HandlerOpts
+
rateLimit?: HandlerRateLimitOpts<ReqCtx> | HandlerRateLimitOpts<ReqCtx>[]
+
handler: Handler
+
}
+
type ExtractAuth<AV extends AuthVerifier | StreamAuthVerifier> = Extract<
+
Awaited<ReturnType<AV>>,
+
{ credentials: unknown }
+
>
+33
src/lexicon/lexicons.ts
···
+
/**
+
* GENERATED CODE - DO NOT MODIFY
+
*/
+
import { LexiconDoc, Lexicons } from '@atproto/lexicon'
+
+
export const schemaDict = {
+
ExampleLexiconStatus: {
+
lexicon: 1,
+
id: 'example.lexicon.status',
+
defs: {
+
main: {
+
type: 'record',
+
key: 'literal:self',
+
record: {
+
type: 'object',
+
required: ['status', 'updatedAt'],
+
properties: {
+
status: {
+
type: 'string',
+
},
+
updatedAt: {
+
type: 'string',
+
format: 'datetime',
+
},
+
},
+
},
+
},
+
},
+
},
+
}
+
export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[]
+
export const lexicons: Lexicons = new Lexicons(schemas)
+
export const ids = { ExampleLexiconStatus: 'example.lexicon.status' }
+26
src/lexicon/types/example/lexicon/status.ts
···
+
/**
+
* GENERATED CODE - DO NOT MODIFY
+
*/
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
+
import { lexicons } from '../../../lexicons'
+
import { isObj, hasProp } from '../../../util'
+
import { CID } from 'multiformats/cid'
+
+
export interface Record {
+
status: string
+
updatedAt: string
+
[k: string]: unknown
+
}
+
+
export function isRecord(v: unknown): v is Record {
+
return (
+
isObj(v) &&
+
hasProp(v, '$type') &&
+
(v.$type === 'example.lexicon.status#main' ||
+
v.$type === 'example.lexicon.status')
+
)
+
}
+
+
export function validateRecord(v: unknown): ValidationResult {
+
return lexicons.validate('example.lexicon.status#main', v)
+
}
+13
src/lexicon/util.ts
···
+
/**
+
* GENERATED CODE - DO NOT MODIFY
+
*/
+
export function isObj(v: unknown): v is Record<string, unknown> {
+
return typeof v === 'object' && v !== null
+
}
+
+
export function hasProp<K extends PropertyKey>(
+
data: object,
+
prop: K,
+
): data is Record<K, unknown> {
+
return prop in data
+
}