The ATProto lexicons for bragsheet.social.
0
fork

Configure Feed

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

Initial commit

Vince Hodges aabae24e

+623
+12
.envrc
··· 1 + #!/usr/bin/env bash 2 + 3 + export DIRENV_WARN_TIMEOUT=20s 4 + 5 + eval "$(devenv direnvrc)" 6 + 7 + # `use devenv` supports the same options as the `devenv shell` command. 8 + # 9 + # To silence all output, use `--quiet`. 10 + # 11 + # Example usage: use devenv --quiet --impure --option services.postgres.enable:bool true 12 + use devenv
+45
.gitignore
··· 1 + # 2 + node_modules 3 + 4 + # output 5 + out 6 + dist 7 + *.tgz 8 + 9 + # code coverage 10 + coverage 11 + *.lcov 12 + 13 + # logs 14 + logs 15 + _.log 16 + report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 17 + 18 + # dotenv environment variable files 19 + .env 20 + .env.development.local 21 + .env.test.local 22 + .env.production.local 23 + .env.local 24 + 25 + # Devenv 26 + .devenv* 27 + devenv.local.nix 28 + devenv.local.yaml 29 + 30 + # direnv 31 + .direnv 32 + 33 + # pre-commit 34 + .pre-commit-config.yaml 35 + 36 + # caches 37 + .eslintcache 38 + .cache 39 + *.tsbuildinfo 40 + 41 + # IntelliJ based IDEs 42 + .idea 43 + 44 + # Finder (MacOS) folder config 45 + .DS_Store
+21
LICENSE
··· 1 + MIT License 2 + 3 + Copyright (c) 2025 Vince Hodges 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
+47
README.md
··· 1 + # Bragsheet.social 2 + Shared lexicon schemas for Bragsheet.social 3 + 4 + ## Overview 5 + 6 + This repository contains the official lexicon definitions for [bragsheet.social](https://bragsheet.social/). These schemas enable content discovery, indexing, and portability across the decentralized AT Protocol network. 7 + 8 + One schema. Every platform. 9 + 10 + ## Installation 11 + 12 + ```bash 13 + bun install 14 + ``` 15 + 16 + ## Usage 17 + 18 + ```bash 19 + bun run lexicon:emit # Generate JSON schemas from TypeScript 20 + bun run lexicon:import # Generate TypeScript from JSON schemas 21 + ``` 22 + 23 + ## Project Structure 24 + 25 + ``` 26 + / 27 + ├── src/ 28 + │ └── lexicons/ # TypeScript lexicon definitions (source) 29 + │ ├── social.bragsheet.entry.ts 30 + └── out/ # Generated JSON schemas 31 + ├── social.bragsheet.entry.json 32 + ``` 33 + 34 + ## Resources 35 + 36 + - [Bragsheet.social](https://bragsheet.social/) - The app 37 + - [AT Protocol](https://atproto.com/) - The underlying protocol 38 + - [Lexicon Documentation](https://atproto.com/specs/lexicon) - AT Protocol lexicon spec 39 + - [Prototypey](https://github.com/tylersayshi/prototypey) - AT Protocol lexicon typescript toolkit 40 + 41 + ## License 42 + 43 + This project is open-source software licensed under the [MIT license](LICENSE). 44 + 45 + --- 46 + 47 + **Designed for the ATmosphere**
+76
bun.lock
··· 1 + { 2 + "lockfileVersion": 1, 3 + "configVersion": 1, 4 + "workspaces": { 5 + "": { 6 + "name": "lexicons", 7 + "dependencies": { 8 + "@atproto/api": "^0.15.27", 9 + "prototypey": "^0.3.8", 10 + }, 11 + "devDependencies": { 12 + "@types/bun": "latest", 13 + }, 14 + "peerDependencies": { 15 + "typescript": "^5.9.3", 16 + }, 17 + }, 18 + }, 19 + "packages": { 20 + "@atproto/api": ["@atproto/api@0.15.27", "", { "dependencies": { "@atproto/common-web": "^0.4.2", "@atproto/lexicon": "^0.4.12", "@atproto/syntax": "^0.4.0", "@atproto/xrpc": "^0.7.1", "await-lock": "^2.2.2", "multiformats": "^9.9.0", "tlds": "^1.234.0", "zod": "^3.23.8" } }, "sha512-ok/WGafh1nz4t8pEQGtAF/32x2E2VDWU4af6BajkO5Gky2jp2q6cv6aB2A5yuvNNcc3XkYMYipsqVHVwLPMF9g=="], 21 + 22 + "@atproto/common-web": ["@atproto/common-web@0.4.8", "", { "dependencies": { "@atproto/lex-data": "0.0.4", "@atproto/lex-json": "0.0.4", "zod": "^3.23.8" } }, "sha512-2YDVTYAXmd8UStebscDglisrxT5q7qt+0Fbf2zpkOITeNEEXCeTcoE0X369/ssdPtiw4CMq2rGHDH003SO7bdQ=="], 23 + 24 + "@atproto/lex-data": ["@atproto/lex-data@0.0.4", "", { "dependencies": { "@atproto/syntax": "0.4.2", "multiformats": "^9.9.0", "tslib": "^2.8.1", "uint8arrays": "3.0.0", "unicode-segmenter": "^0.14.0" } }, "sha512-ziWY8R4wJ0NGDSlt+gzPxMsIh1DXFeLt+lsBoVc6wPaJamCxngwWAxONuQ3p9oRE6zR/gXsCOdtZAH5yjWW5ag=="], 25 + 26 + "@atproto/lex-json": ["@atproto/lex-json@0.0.4", "", { "dependencies": { "@atproto/lex-data": "0.0.4", "tslib": "^2.8.1" } }, "sha512-BTBnRZUW7XFCbJnuSMvUZSLXYP6RK/RdTg68sySoK+Hg0A5k43uniA7xtFhJFZCfZ96brl3k/ykdVh76LizQ8Q=="], 27 + 28 + "@atproto/lexicon": ["@atproto/lexicon@0.4.14", "", { "dependencies": { "@atproto/common-web": "^0.4.2", "@atproto/syntax": "^0.4.0", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-jiKpmH1QER3Gvc7JVY5brwrfo+etFoe57tKPQX/SmPwjvUsFnJAow5xLIryuBaJgFAhnTZViXKs41t//pahGHQ=="], 29 + 30 + "@atproto/syntax": ["@atproto/syntax@0.4.2", "", {}, "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA=="], 31 + 32 + "@atproto/xrpc": ["@atproto/xrpc@0.7.7", "", { "dependencies": { "@atproto/lexicon": "^0.6.0", "zod": "^3.23.8" } }, "sha512-K1ZyO/BU8JNtXX5dmPp7b5UrkLMMqpsIa/Lrj5D3Su+j1Xwq1m6QJ2XJ1AgjEjkI1v4Muzm7klianLE6XGxtmA=="], 33 + 34 + "@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="], 35 + 36 + "@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="], 37 + 38 + "await-lock": ["await-lock@2.2.2", "", {}, "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw=="], 39 + 40 + "bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="], 41 + 42 + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], 43 + 44 + "iso-datestring-validator": ["iso-datestring-validator@2.2.2", "", {}, "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA=="], 45 + 46 + "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], 47 + 48 + "multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 49 + 50 + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], 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=="], 53 + 54 + "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], 55 + 56 + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], 57 + 58 + "tlds": ["tlds@1.261.0", "", { "bin": { "tlds": "bin.js" } }, "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA=="], 59 + 60 + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 61 + 62 + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 63 + 64 + "uint8arrays": ["uint8arrays@3.0.0", "", { "dependencies": { "multiformats": "^9.4.2" } }, "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA=="], 65 + 66 + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], 67 + 68 + "unicode-segmenter": ["unicode-segmenter@0.14.5", "", {}, "sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g=="], 69 + 70 + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 71 + 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 + 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=="], 75 + } 76 + }
+123
devenv.lock
··· 1 + { 2 + "nodes": { 3 + "devenv": { 4 + "locked": { 5 + "dir": "src/modules", 6 + "lastModified": 1775201809, 7 + "owner": "cachix", 8 + "repo": "devenv", 9 + "rev": "42a5505d4700e791732e48a38b4cca05a755f94b", 10 + "type": "github" 11 + }, 12 + "original": { 13 + "dir": "src/modules", 14 + "owner": "cachix", 15 + "repo": "devenv", 16 + "type": "github" 17 + } 18 + }, 19 + "flake-compat": { 20 + "flake": false, 21 + "locked": { 22 + "lastModified": 1767039857, 23 + "owner": "NixOS", 24 + "repo": "flake-compat", 25 + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", 26 + "type": "github" 27 + }, 28 + "original": { 29 + "owner": "NixOS", 30 + "repo": "flake-compat", 31 + "type": "github" 32 + } 33 + }, 34 + "git-hooks": { 35 + "inputs": { 36 + "flake-compat": "flake-compat", 37 + "gitignore": "gitignore", 38 + "nixpkgs": [ 39 + "nixpkgs" 40 + ] 41 + }, 42 + "locked": { 43 + "lastModified": 1775036584, 44 + "owner": "cachix", 45 + "repo": "git-hooks.nix", 46 + "rev": "4e0eb042b67d863b1b34b3f64d52ceb9cd926735", 47 + "type": "github" 48 + }, 49 + "original": { 50 + "owner": "cachix", 51 + "repo": "git-hooks.nix", 52 + "type": "github" 53 + } 54 + }, 55 + "gitignore": { 56 + "inputs": { 57 + "nixpkgs": [ 58 + "git-hooks", 59 + "nixpkgs" 60 + ] 61 + }, 62 + "locked": { 63 + "lastModified": 1762808025, 64 + "owner": "hercules-ci", 65 + "repo": "gitignore.nix", 66 + "rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c", 67 + "type": "github" 68 + }, 69 + "original": { 70 + "owner": "hercules-ci", 71 + "repo": "gitignore.nix", 72 + "type": "github" 73 + } 74 + }, 75 + "nixpkgs": { 76 + "inputs": { 77 + "nixpkgs-src": "nixpkgs-src" 78 + }, 79 + "locked": { 80 + "lastModified": 1774287239, 81 + "owner": "cachix", 82 + "repo": "devenv-nixpkgs", 83 + "rev": "fa7125ea7f1ae5430010a6e071f68375a39bd24c", 84 + "type": "github" 85 + }, 86 + "original": { 87 + "owner": "cachix", 88 + "ref": "rolling", 89 + "repo": "devenv-nixpkgs", 90 + "type": "github" 91 + } 92 + }, 93 + "nixpkgs-src": { 94 + "flake": false, 95 + "locked": { 96 + "lastModified": 1769922788, 97 + "narHash": "sha256-H3AfG4ObMDTkTJYkd8cz1/RbY9LatN5Mk4UF48VuSXc=", 98 + "owner": "NixOS", 99 + "repo": "nixpkgs", 100 + "rev": "207d15f1a6603226e1e223dc79ac29c7846da32e", 101 + "type": "github" 102 + }, 103 + "original": { 104 + "owner": "NixOS", 105 + "ref": "nixpkgs-unstable", 106 + "repo": "nixpkgs", 107 + "type": "github" 108 + } 109 + }, 110 + "root": { 111 + "inputs": { 112 + "devenv": "devenv", 113 + "git-hooks": "git-hooks", 114 + "nixpkgs": "nixpkgs", 115 + "pre-commit-hooks": [ 116 + "git-hooks" 117 + ] 118 + } 119 + } 120 + }, 121 + "root": "root", 122 + "version": 7 123 + }
+52
devenv.nix
··· 1 + { pkgs, lib, config, inputs, ... }: 2 + 3 + { 4 + # https://devenv.sh/basics/ 5 + env.GREET = "devenv"; 6 + 7 + # https://devenv.sh/packages/ 8 + packages = [ 9 + pkgs.git 10 + pkgs.ngrok 11 + ]; 12 + 13 + # https://devenv.sh/languages/ 14 + languages.go.enable = true; 15 + languages.typescript.enable = true; 16 + languages.javascript.enable = true; 17 + languages.javascript.bun.enable = true; 18 + 19 + # https://devenv.sh/processes/ 20 + # processes.dev.exec = "${lib.getExe pkgs.watchexec} -n -- ls -la"; 21 + 22 + # https://devenv.sh/services/ 23 + # services.postgres.enable = true; 24 + 25 + # https://devenv.sh/scripts/ 26 + scripts.hello.exec = '' 27 + echo hello from $GREET 28 + ''; 29 + 30 + # https://devenv.sh/basics/ 31 + enterShell = '' 32 + hello # Run scripts directly 33 + git --version # Use packages 34 + ''; 35 + 36 + # https://devenv.sh/tasks/ 37 + # tasks = { 38 + # "myproj:setup".exec = "mytool build"; 39 + # "devenv:enterShell".after = [ "myproj:setup" ]; 40 + # }; 41 + 42 + # https://devenv.sh/tests/ 43 + enterTest = '' 44 + echo "Running tests" 45 + git --version | grep --color=auto "${pkgs.git.version}" 46 + ''; 47 + 48 + # https://devenv.sh/git-hooks/ 49 + # git-hooks.hooks.shellcheck.enable = true; 50 + 51 + # See full reference at https://devenv.sh/reference/options/ 52 + }
+14
devenv.yaml
··· 1 + # yaml-language-server: $schema=https://devenv.sh/devenv.schema.json 2 + inputs: 3 + nixpkgs: 4 + url: github:cachix/devenv-nixpkgs/rolling 5 + 6 + # If you're using non-OSS software, you can set allowUnfree to true. 7 + allowUnfree: true 8 + # If you're willing to use a package that's vulnerable 9 + # permittedInsecurePackages: 10 + # - "openssl-1.1.1w" 11 + 12 + # If you have more than one devenv you can merge them 13 + #imports: 14 + # - ./backend
+1
index.ts
··· 1 + console.log("Hello via Bun!");
+22
package.json
··· 1 + { 2 + "name": "lexicons", 3 + "module": "index.ts", 4 + "type": "module", 5 + "private": true, 6 + "scripts": { 7 + "lexicon:emit": "bunx prototypey gen-emit ./out ./src/lexicons/**/*.ts && bun run scripts/lint.ts", 8 + "lexicon:import": "bunx prototypey gen-from-json ./src/lexicons ./out/**/*.json", 9 + "lexicon:lint": "bun run scripts/lint.ts", 10 + "lexicon:publish": "bun run scripts/publish.ts" 11 + }, 12 + "devDependencies": { 13 + "@types/bun": "latest" 14 + }, 15 + "peerDependencies": { 16 + "typescript": "^5.9.3" 17 + }, 18 + "dependencies": { 19 + "@atproto/api": "^0.15.27", 20 + "prototypey": "^0.3.8" 21 + } 22 + }
+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()
+1
src/constants.ts
··· 1 + export const MB = 1000000; // 1MB
+44
src/lexicons/social.bragsheet.entry.ts
··· 1 + import { lx } from "prototypey"; 2 + import { MB } from "../constants.ts"; 3 + 4 + export const socialBragsheetEntry = lx.lexicon("social.bragsheet.entry", { 5 + main: lx.record({ 6 + key: "tid", 7 + type: "record", 8 + record: lx.object({ 9 + title: lx.string({ 10 + required: true, 11 + maxLength: 5000, 12 + maxGraphemes: 500, 13 + description: "Title of the entry.", 14 + }), 15 + content: lx.string({ 16 + required: true, 17 + maxLength: 50000, 18 + maxGraphemes: 5000, 19 + description: "The body of the entry.", 20 + }), 21 + rangeStart: lx.string({ 22 + format: "datetime", 23 + description: 24 + "Timestamp representing the beginning of the timeframe for this entry.", 25 + }), 26 + rangeEnd: lx.string({ 27 + format: "datetime", 28 + description: 29 + "Timestamp representing the end of the timeframe for this entry.", 30 + }), 31 + publishedAt: lx.string({ 32 + required: true, 33 + format: "datetime", 34 + description: "Timestamp of the entry's publish time.", 35 + }), 36 + updatedAt: lx.string({ 37 + format: "datetime", 38 + description: "Timestamp of the entry's last edit.", 39 + }), 40 + }), 41 + description: 42 + "A document record representing a published article, blog post, or other content. Documents can belong to a publication or exist independently.", 43 + }), 44 + });
+29
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + // Environment setup & latest features 4 + "lib": ["ESNext"], 5 + "target": "ESNext", 6 + "module": "Preserve", 7 + "moduleDetection": "force", 8 + "jsx": "react-jsx", 9 + "allowJs": true, 10 + 11 + // Bundler mode 12 + "moduleResolution": "bundler", 13 + "allowImportingTsExtensions": true, 14 + "verbatimModuleSyntax": true, 15 + "noEmit": true, 16 + 17 + // Best practices 18 + "strict": true, 19 + "skipLibCheck": true, 20 + "noFallthroughCasesInSwitch": true, 21 + "noUncheckedIndexedAccess": true, 22 + "noImplicitOverride": true, 23 + 24 + // Some stricter flags (disabled by default) 25 + "noUnusedLocals": false, 26 + "noUnusedParameters": false, 27 + "noPropertyAccessFromIndexSignature": false 28 + } 29 + }