Shared lexicon schemas for long-form publishing on AT Protocol. Uses typescript to json via prototypey.
44
fork

Configure Feed

Select the types of activity you want to include in your feed.

update prototypey, remove linter, and update readme

Brooke e3db4a18 040df64a

+31 -150
+15 -5
README.md
··· 26 26 / 27 27 ├── src/ 28 28 │ └── lexicons/ # TypeScript lexicon definitions (source) 29 + │ ├── site.standard.authFull.ts 30 + │ ├── site.standard.authSocial.ts 29 31 │ ├── site.standard.document.ts 32 + │ ├── site.standard.graph.recommend.ts 30 33 │ ├── site.standard.graph.subscription.ts 31 34 │ ├── site.standard.publication.ts 32 35 │ ├── site.standard.theme.basic.ts 33 36 │ └── site.standard.theme.color.ts 34 37 └── out/ # Generated JSON schemas 35 - ├── site.standard.document.json 36 - ├── site.standard.graph.subscription.json 37 - ├── site.standard.publication.json 38 - ├── site.standard.theme.basic.json 39 - └── site.standard.theme.color.json 38 + └── site/ 39 + └── standard/ 40 + ├── authFull.json 41 + ├── authSocial.json 42 + ├── document.json 43 + ├── publication.json 44 + ├── graph/ 45 + │ ├── recommend.json 46 + │ └── subscription.json 47 + └── theme/ 48 + ├── basic.json 49 + └── color.json 40 50 ``` 41 51 42 52 ## Resources
+15 -7
bun.lock
··· 5 5 "": { 6 6 "name": "lexicons", 7 7 "dependencies": { 8 - "@atproto/api": "^0.15.5", 9 - "prototypey": "^0.3.7", 8 + "@atproto/api": "^0.15.27", 9 + "prototypey": "^0.7.0", 10 10 }, 11 11 "devDependencies": { 12 12 "@types/bun": "latest", 13 13 }, 14 14 "peerDependencies": { 15 - "typescript": "^5", 15 + "typescript": "^5.9.3", 16 16 }, 17 17 }, 18 18 }, ··· 47 47 48 48 "multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 49 49 50 - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], 50 + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], 51 51 52 - "prototypey": ["prototypey@0.3.8", "", { "dependencies": { "@atproto/lexicon": "^0.5.2", "sade": "^1.8.1", "tinyglobby": "^0.2.15" }, "bin": { "prototypey": "lib/cli/main.js" } }, "sha512-xSSOWfVVr1boe+O5R19bFy9Gacvwj1PiyAk/3UUzvbCvPWM6JUycNH6BigXicOorZYUugT/frP+TX2Wj1stI7g=="], 52 + "prototypey": ["prototypey@0.7.0", "", { "dependencies": { "@atproto/lexicon": "^0.6.2", "sade": "^1.8.1", "tinyglobby": "^0.2.16" }, "bin": { "prototypey": "lib/cli/main.js" } }, "sha512-/Gbzq3kFM0jP97ehBf3/tlqCeykkMf5eCvbgPIoEtefLxQhaIYFwmce7zdAHiUlBpZU5RAnUTx+t6QJNy04lWA=="], 53 53 54 54 "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], 55 55 56 - "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], 56 + "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], 57 57 58 58 "tlds": ["tlds@1.261.0", "", { "bin": { "tlds": "bin.js" } }, "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA=="], 59 59 ··· 71 71 72 72 "@atproto/xrpc/@atproto/lexicon": ["@atproto/lexicon@0.6.0", "", { "dependencies": { "@atproto/common-web": "^0.4.7", "@atproto/syntax": "^0.4.2", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-5veb8aD+J5M0qszLJ+73KSFsFrJBgAY/nM1TSAJvGY7fNc9ZAT+PSUlmIyrdye9YznAZ07yktalls/TwNV7cHQ=="], 73 73 74 - "prototypey/@atproto/lexicon": ["@atproto/lexicon@0.5.2", "", { "dependencies": { "@atproto/common-web": "^0.4.4", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-lRmJgMA8f5j7VB5Iu5cp188ald5FuI4FlmZ7nn6EBrk1dgOstWVrI5Ft6K3z2vjyLZRG6nzknlsw+tDP63p7bQ=="], 74 + "prototypey/@atproto/lexicon": ["@atproto/lexicon@0.6.2", "", { "dependencies": { "@atproto/common-web": "^0.4.18", "@atproto/syntax": "^0.5.0", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-p3Ly6hinVZW0ETuAXZMeUGwuMm3g8HvQMQ41yyEE6AL0hAkfeKFaZKos6BdBrr6CjkpbrDZqE8M+5+QOceysMw=="], 75 + 76 + "prototypey/@atproto/lexicon/@atproto/common-web": ["@atproto/common-web@0.4.21", "", { "dependencies": { "@atproto/lex-data": "^0.0.15", "@atproto/lex-json": "^0.0.16", "@atproto/syntax": "^0.5.4", "zod": "^3.23.8" } }, "sha512-Odq+wdk3YNasGCjjlpl3bCIPvqYHige5DLfMkIffNv/2PI/iIj5ZvAvMvJlJ59OhReKSxtpI0invx5UQPc3+fw=="], 77 + 78 + "prototypey/@atproto/lexicon/@atproto/syntax": ["@atproto/syntax@0.5.4", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-9XJOpMAgsGFxMEIp8nJ8AIWv+krrY1xQMj+wULbbXhQztQV+9aZ0TbG9Jtn3Op2or8Kr6OqyWR4ga9Z189kKDw=="], 79 + 80 + "prototypey/@atproto/lexicon/@atproto/common-web/@atproto/lex-data": ["@atproto/lex-data@0.0.15", "", { "dependencies": { "multiformats": "^9.9.0", "tslib": "^2.8.1", "uint8arrays": "3.0.0", "unicode-segmenter": "^0.14.0" } }, "sha512-ZsbGiaM5S3CnGrcTMbDGON3bLZzCi/Mx9UvcMREKSRujnF68eHgMiXxJqvykP7+QpOX6tYCK93axZkuJVhtSEw=="], 81 + 82 + "prototypey/@atproto/lexicon/@atproto/common-web/@atproto/lex-json": ["@atproto/lex-json@0.0.16", "", { "dependencies": { "@atproto/lex-data": "^0.0.15", "tslib": "^2.8.1" } }, "sha512-IgLgQ0krshVlrIYZ+heTBDbCnM3LmAgWvsaYn5MxvKA3LcBot3PG3ptdO8VOweVZ+WgCLuo39cz9EbUmIbqdtg=="], 75 83 } 76 84 }
+1 -2
package.json
··· 4 4 "type": "module", 5 5 "private": true, 6 6 "scripts": { 7 - "lexicon:emit": "bunx prototypey gen-emit ./out ./src/lexicons/**/*.ts && bun run scripts/lint.ts", 7 + "lexicon:emit": "bunx prototypey gen-emit ./out ./src/lexicons/**/*.ts", 8 8 "lexicon:import": "bunx prototypey gen-from-json ./src/lexicons ./out/**/*.json", 9 - "lexicon:lint": "bun run scripts/lint.ts", 10 9 "lexicon:publish": "bun run scripts/publish.ts" 11 10 }, 12 11 "devDependencies": {
-136
scripts/lint.ts
··· 1 - import * as fs from 'fs' 2 - import * as path from 'path' 3 - import { glob } from 'tinyglobby' 4 - 5 - export type LexiconPatches = Record<string, Record<string, unknown>> 6 - 7 - /** 8 - * Get a nested value from an object using a dot-separated path. 9 - */ 10 - function getPath(obj: Record<string, unknown>, pathStr: string): unknown { 11 - return pathStr.split('.').reduce((acc: unknown, key) => { 12 - if (acc && typeof acc === 'object') { 13 - return (acc as Record<string, unknown>)[key] 14 - } 15 - return undefined 16 - }, obj) 17 - } 18 - 19 - /** 20 - * Load patches from lexicon source files. 21 - */ 22 - async function loadPatches(): Promise<Record<string, LexiconPatches>> { 23 - const srcDir = path.join(process.cwd(), 'src/lexicons') 24 - const files = await glob('**/*.ts', { cwd: srcDir, absolute: true }) 25 - 26 - const allPatches: Record<string, LexiconPatches> = {} 27 - 28 - for (const file of files) { 29 - try { 30 - const module = await import(file) 31 - if (!module.patches) continue 32 - 33 - const lexiconId = Object.values(module) 34 - .find((v): v is { json: { id: string } } => 35 - v !== null && typeof v === 'object' && 'json' in v && typeof (v as { 36 - json?: { id?: string } 37 - }).json?.id === 'string' 38 - )?.json.id 39 - 40 - if (!lexiconId) continue 41 - 42 - allPatches[lexiconId] = module.patches 43 - } catch { 44 - // Skip files that can't be imported 45 - } 46 - } 47 - 48 - return allPatches 49 - } 50 - 51 - /** 52 - * Apply patches to a lexicon object. 53 - */ 54 - function applyPatches(lexicon: Record<string, unknown>, patches: Record<string, LexiconPatches>): boolean { 55 - const id = lexicon.id as string 56 - const lexiconPatches = patches[id] 57 - if (!lexiconPatches) return false 58 - 59 - let applied = false 60 - for (const [pathStr, fields] of Object.entries(lexiconPatches)) { 61 - const target = getPath(lexicon, pathStr) as Record<string, unknown> | undefined 62 - if (!target || typeof target !== 'object') continue 63 - 64 - for (const [field, value] of Object.entries(fields)) { 65 - if (target[field] === value) continue 66 - 67 - target[field] = value 68 - applied = true 69 - } 70 - } 71 - return applied 72 - } 73 - 74 - /** 75 - * Recursively removes `"required": true` (boolean) from an object, 76 - * while preserving `"required": [...]` (arrays). 77 - */ 78 - function removeRequiredBooleans(obj: unknown): unknown { 79 - if (Array.isArray(obj)) { 80 - return obj.map(removeRequiredBooleans) 81 - } 82 - 83 - if (obj !== null && typeof obj === 'object') { 84 - const result: Record<string, unknown> = {} 85 - 86 - for (const [key, value] of Object.entries(obj)) { 87 - // Skip "required" if it's a boolean 88 - if (key === 'required' && typeof value === 'boolean') { 89 - continue 90 - } 91 - result[key] = removeRequiredBooleans(value) 92 - } 93 - 94 - return result 95 - } 96 - 97 - return obj 98 - } 99 - 100 - /** 101 - * Lint all JSON files in the out directory. 102 - */ 103 - async function lintLexicons() { 104 - const outDir = path.join(process.cwd(), 'out') 105 - 106 - const files = fs.readdirSync(outDir).filter((f) => f.endsWith('.json')) 107 - const patches = await loadPatches() 108 - 109 - let totalFixed = 0 110 - 111 - for (const file of files) { 112 - const filePath = path.join(outDir, file) 113 - const content = fs.readFileSync(filePath, 'utf8') 114 - const original = JSON.parse(content) 115 - const cleaned = removeRequiredBooleans(original) as Record<string, unknown> 116 - 117 - const originalStr = JSON.stringify(original) 118 - 119 - // Apply patches for features prototypey doesn't support 120 - applyPatches(cleaned, patches) 121 - 122 - const cleanedStr = JSON.stringify(cleaned, null, '\t') 123 - 124 - if (originalStr !== JSON.stringify(cleaned)) { 125 - fs.writeFileSync(filePath, cleanedStr + '\n') 126 - console.log(`Fixed: ${file}`) 127 - totalFixed++ 128 - } else { 129 - console.log(`OK: ${file}`) 130 - } 131 - } 132 - 133 - console.log(`\nLinted ${files.length} files, fixed ${totalFixed}`) 134 - } 135 - 136 - lintLexicons()