the statusphere demo reworked into a vite/react app in a monorepo

completely rewrite into react/vite app

-10
.env.template
··· 1 - # Environment Configuration 2 - NODE_ENV="development" # Options: 'development', 'production' 3 - PORT="8080" # The port your server will listen on 4 - HOST="localhost" # Hostname for the server 5 - PUBLIC_URL="" # Set when deployed publicly, e.g. "https://mysite.com". Informs OAuth client id. 6 - DB_PATH=":memory:" # The SQLite database path. Leave as ":memory:" to use a temporary in-memory database. 7 - 8 - # Secrets 9 - # Must set this in production. May be generated with `openssl rand -base64 33` 10 - # COOKIE_SECRET=""
+28 -9
.gitignore
··· 1 + # Dependencies 2 + node_modules 3 + .pnp 4 + .pnp.js 5 + 6 + # Build output 7 + dist 8 + build 9 + dist-ssr 10 + .turbo 11 + 12 + # Testing 13 + coverage 14 + 15 + # Environment 16 + .env 17 + .env.local 18 + .env.development.local 19 + .env.test.local 20 + .env.production.local 21 + *.local 22 + 1 23 # Logs 2 24 logs 3 25 *.log ··· 7 29 pnpm-debug.log* 8 30 lerna-debug.log* 9 31 10 - coverage 11 - node_modules 12 - dist 13 - build 14 - dist-ssr 15 - *.local 16 - .env 17 - 18 32 # Editor directories and files 33 + .vscode/* 19 34 !.vscode/extensions.json 20 35 .idea 21 36 .DS_Store ··· 23 38 *.ntvs* 24 39 *.njsproj 25 40 *.sln 26 - *.sw? 41 + *.sw? 42 + 43 + # Database 44 + *.sqlite 45 + *.sqlite-journal
+16 -1
.prettierrc
··· 1 1 { 2 + "plugins": [ 3 + "prettier-plugin-tailwindcss", 4 + "@ianvs/prettier-plugin-sort-imports" 5 + ], 2 6 "singleQuote": true, 3 - "semi": false 7 + "semi": false, 8 + "importOrder": [ 9 + "^react$", 10 + "^react-dom$", 11 + "^react-", 12 + "^@tanstack/", 13 + "<THIRD_PARTY_MODULES>", 14 + "", 15 + "^#/", 16 + "^[./]" 17 + ], 18 + "importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"] 4 19 }
-18
.vscode/launch.json
··· 1 - { 2 - "name": "tsx", 3 - "type": "node", 4 - "request": "launch", 5 - "program": "${workspaceFolder}/src/index.ts", 6 - "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/tsx", 7 - "console": "integratedTerminal", 8 - "internalConsoleOptions": "neverOpen", 9 - "skipFiles": ["<node_internals>/**", "${workspaceFolder}/node_modules/**"], 10 - "configurations": [ 11 - { 12 - "command": "npm start", 13 - "name": "Run npm start", 14 - "request": "launch", 15 - "type": "node-terminal" 16 - } 17 - ] 18 - }
-15
.vscode/settings.json
··· 1 - { 2 - "editor.formatOnSave": true, 3 - "editor.defaultFormatter": "biomejs.biome", 4 - "editor.codeActionsOnSave": { 5 - "quickfix.biome": "explicit", 6 - "source.organizeImports.biome": "explicit", 7 - "source.fixAll": "explicit" 8 - }, 9 - "json.schemas": [ 10 - { 11 - "url": "https://cdn.jsdelivr.net/npm/tsup/schema.json", 12 - "fileMatch": ["package.json", "tsup.config.json"] 13 - } 14 - ] 15 - }
+8
CLAUDE.md
··· 1 + hey buddy :) 2 + 3 + if you're going to undertake multi-file or otherwise complex edits, please write a summary of what you're looking to achieve, so that I can either approve or provide suggestions 4 + 5 + and most importantly, have fun! 6 + 7 + your friend, 8 + mozzius
+53 -11
README.md
··· 1 - # AT Protocol "Statusphere" Example App 1 + # Statusphere React 2 2 3 - An example application covering: 3 + A monorepo for the Statusphere application, which includes a React client and a Node.js backend. 4 + 5 + This is a React refactoring of the [example application](https://atproto.com/guides/applications) covering: 4 6 5 7 - Signin via OAuth 6 8 - Fetch information about users (profiles) 7 9 - Listen to the network firehose for new data 8 10 - Publish data on the user's account using a custom schema 9 11 10 - See https://atproto.com/guides/applications for a guide through the codebase. 12 + ## Structure 11 13 12 - ## Getting Started 14 + - `packages/appview` - Express.js backend that serves API endpoints 15 + - `packages/client` - React frontend using Vite 13 16 14 - ```sh 15 - git clone https://github.com/bluesky-social/statusphere-example-app.git 16 - cd statusphere-example-app 17 - cp .env.template .env 18 - npm install 19 - npm run dev 20 - # Navigate to http://localhost:8080 17 + ## Development 18 + 19 + ```bash 20 + # Install dependencies 21 + pnpm install 22 + 23 + # Option 1: Local development (login won't work due to OAuth requirements) 24 + pnpm dev 25 + 26 + # Option 2: Development with OAuth login support (recommended) 27 + pnpm dev:oauth 21 28 ``` 29 + 30 + ### OAuth Development 31 + 32 + Due to OAuth requirements, HTTPS is needed for development. We've made this easy: 33 + 34 + - `pnpm dev:oauth` - Sets up everything automatically: 35 + 1. Starts ngrok to create an HTTPS tunnel 36 + 2. Configures environment variables with the ngrok URL 37 + 3. Starts both the API server and client app 38 + 4. Handles proper shutdown of all processes 39 + 40 + This all-in-one command makes OAuth development seamless. 41 + 42 + ### Additional Commands 43 + 44 + ```bash 45 + # Build both packages 46 + pnpm build 47 + 48 + # Run typecheck on both packages 49 + pnpm typecheck 50 + 51 + # Format all code 52 + pnpm format 53 + ``` 54 + 55 + ## Requirements 56 + 57 + - Node.js 18+ 58 + - pnpm 9+ 59 + - ngrok (for OAuth development) 60 + 61 + ## License 62 + 63 + MIT
+31
lexicons/app/bsky/profile/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.actor.defs", 4 + "defs": { 5 + "profileView": { 6 + "type": "object", 7 + "required": ["did", "handle"], 8 + "properties": { 9 + "did": { "type": "string", "format": "did" }, 10 + "handle": { "type": "string", "format": "handle" }, 11 + "displayName": { 12 + "type": "string", 13 + "maxGraphemes": 64, 14 + "maxLength": 640 15 + }, 16 + "description": { 17 + "type": "string", 18 + "maxGraphemes": 256, 19 + "maxLength": 2560 20 + }, 21 + "avatar": { "type": "string", "format": "uri" }, 22 + "indexedAt": { "type": "string", "format": "datetime" }, 23 + "createdAt": { "type": "string", "format": "datetime" }, 24 + "labels": { 25 + "type": "array", 26 + "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } 27 + } 28 + } 29 + } 30 + } 31 + }
+156
lexicons/com/atproto/label/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.label.defs", 4 + "defs": { 5 + "label": { 6 + "type": "object", 7 + "description": "Metadata tag on an atproto resource (eg, repo or record).", 8 + "required": ["src", "uri", "val", "cts"], 9 + "properties": { 10 + "ver": { 11 + "type": "integer", 12 + "description": "The AT Protocol version of the label object." 13 + }, 14 + "src": { 15 + "type": "string", 16 + "format": "did", 17 + "description": "DID of the actor who created this label." 18 + }, 19 + "uri": { 20 + "type": "string", 21 + "format": "uri", 22 + "description": "AT URI of the record, repository (account), or other resource that this label applies to." 23 + }, 24 + "cid": { 25 + "type": "string", 26 + "format": "cid", 27 + "description": "Optionally, CID specifying the specific version of 'uri' resource this label applies to." 28 + }, 29 + "val": { 30 + "type": "string", 31 + "maxLength": 128, 32 + "description": "The short string name of the value or type of this label." 33 + }, 34 + "neg": { 35 + "type": "boolean", 36 + "description": "If true, this is a negation label, overwriting a previous label." 37 + }, 38 + "cts": { 39 + "type": "string", 40 + "format": "datetime", 41 + "description": "Timestamp when this label was created." 42 + }, 43 + "exp": { 44 + "type": "string", 45 + "format": "datetime", 46 + "description": "Timestamp at which this label expires (no longer applies)." 47 + }, 48 + "sig": { 49 + "type": "bytes", 50 + "description": "Signature of dag-cbor encoded label." 51 + } 52 + } 53 + }, 54 + "selfLabels": { 55 + "type": "object", 56 + "description": "Metadata tags on an atproto record, published by the author within the record.", 57 + "required": ["values"], 58 + "properties": { 59 + "values": { 60 + "type": "array", 61 + "items": { "type": "ref", "ref": "#selfLabel" }, 62 + "maxLength": 10 63 + } 64 + } 65 + }, 66 + "selfLabel": { 67 + "type": "object", 68 + "description": "Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.", 69 + "required": ["val"], 70 + "properties": { 71 + "val": { 72 + "type": "string", 73 + "maxLength": 128, 74 + "description": "The short string name of the value or type of this label." 75 + } 76 + } 77 + }, 78 + "labelValueDefinition": { 79 + "type": "object", 80 + "description": "Declares a label value and its expected interpretations and behaviors.", 81 + "required": ["identifier", "severity", "blurs", "locales"], 82 + "properties": { 83 + "identifier": { 84 + "type": "string", 85 + "description": "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", 86 + "maxLength": 100, 87 + "maxGraphemes": 100 88 + }, 89 + "severity": { 90 + "type": "string", 91 + "description": "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", 92 + "knownValues": ["inform", "alert", "none"] 93 + }, 94 + "blurs": { 95 + "type": "string", 96 + "description": "What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.", 97 + "knownValues": ["content", "media", "none"] 98 + }, 99 + "defaultSetting": { 100 + "type": "string", 101 + "description": "The default setting for this label.", 102 + "knownValues": ["ignore", "warn", "hide"], 103 + "default": "warn" 104 + }, 105 + "adultOnly": { 106 + "type": "boolean", 107 + "description": "Does the user need to have adult content enabled in order to configure this label?" 108 + }, 109 + "locales": { 110 + "type": "array", 111 + "items": { "type": "ref", "ref": "#labelValueDefinitionStrings" } 112 + } 113 + } 114 + }, 115 + "labelValueDefinitionStrings": { 116 + "type": "object", 117 + "description": "Strings which describe the label in the UI, localized into a specific language.", 118 + "required": ["lang", "name", "description"], 119 + "properties": { 120 + "lang": { 121 + "type": "string", 122 + "description": "The code of the language these strings are written in.", 123 + "format": "language" 124 + }, 125 + "name": { 126 + "type": "string", 127 + "description": "A short human-readable name for the label.", 128 + "maxGraphemes": 64, 129 + "maxLength": 640 130 + }, 131 + "description": { 132 + "type": "string", 133 + "description": "A longer description of what the label means and why it might be applied.", 134 + "maxGraphemes": 10000, 135 + "maxLength": 100000 136 + } 137 + } 138 + }, 139 + "labelValue": { 140 + "type": "string", 141 + "knownValues": [ 142 + "!hide", 143 + "!no-promote", 144 + "!warn", 145 + "!no-unauthenticated", 146 + "dmca-violation", 147 + "doxxing", 148 + "porn", 149 + "sexual", 150 + "nudity", 151 + "nsfl", 152 + "gore" 153 + ] 154 + } 155 + } 156 + }
+131
lexicons/com/atproto/repo/applyWrites.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.applyWrites", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Apply a batch transaction of repository creates, updates, and deletes. Requires auth, implemented by PDS.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["repo", "writes"], 13 + "properties": { 14 + "repo": { 15 + "type": "string", 16 + "format": "at-identifier", 17 + "description": "The handle or DID of the repo (aka, current account)." 18 + }, 19 + "validate": { 20 + "type": "boolean", 21 + "description": "Can be set to 'false' to skip Lexicon schema validation of record data across all operations, 'true' to require it, or leave unset to validate only for known Lexicons." 22 + }, 23 + "writes": { 24 + "type": "array", 25 + "items": { 26 + "type": "union", 27 + "refs": ["#create", "#update", "#delete"], 28 + "closed": true 29 + } 30 + }, 31 + "swapCommit": { 32 + "type": "string", 33 + "description": "If provided, the entire operation will fail if the current repo commit CID does not match this value. Used to prevent conflicting repo mutations.", 34 + "format": "cid" 35 + } 36 + } 37 + } 38 + }, 39 + "output": { 40 + "encoding": "application/json", 41 + "schema": { 42 + "type": "object", 43 + "required": [], 44 + "properties": { 45 + "commit": { 46 + "type": "ref", 47 + "ref": "com.atproto.repo.defs#commitMeta" 48 + }, 49 + "results": { 50 + "type": "array", 51 + "items": { 52 + "type": "union", 53 + "refs": ["#createResult", "#updateResult", "#deleteResult"], 54 + "closed": true 55 + } 56 + } 57 + } 58 + } 59 + }, 60 + "errors": [ 61 + { 62 + "name": "InvalidSwap", 63 + "description": "Indicates that the 'swapCommit' parameter did not match current commit." 64 + } 65 + ] 66 + }, 67 + "create": { 68 + "type": "object", 69 + "description": "Operation which creates a new record.", 70 + "required": ["collection", "value"], 71 + "properties": { 72 + "collection": { "type": "string", "format": "nsid" }, 73 + "rkey": { 74 + "type": "string", 75 + "maxLength": 512, 76 + "format": "record-key", 77 + "description": "NOTE: maxLength is redundant with record-key format. Keeping it temporarily to ensure backwards compatibility." 78 + }, 79 + "value": { "type": "unknown" } 80 + } 81 + }, 82 + "update": { 83 + "type": "object", 84 + "description": "Operation which updates an existing record.", 85 + "required": ["collection", "rkey", "value"], 86 + "properties": { 87 + "collection": { "type": "string", "format": "nsid" }, 88 + "rkey": { "type": "string", "format": "record-key" }, 89 + "value": { "type": "unknown" } 90 + } 91 + }, 92 + "delete": { 93 + "type": "object", 94 + "description": "Operation which deletes an existing record.", 95 + "required": ["collection", "rkey"], 96 + "properties": { 97 + "collection": { "type": "string", "format": "nsid" }, 98 + "rkey": { "type": "string", "format": "record-key" } 99 + } 100 + }, 101 + "createResult": { 102 + "type": "object", 103 + "required": ["uri", "cid"], 104 + "properties": { 105 + "uri": { "type": "string", "format": "at-uri" }, 106 + "cid": { "type": "string", "format": "cid" }, 107 + "validationStatus": { 108 + "type": "string", 109 + "knownValues": ["valid", "unknown"] 110 + } 111 + } 112 + }, 113 + "updateResult": { 114 + "type": "object", 115 + "required": ["uri", "cid"], 116 + "properties": { 117 + "uri": { "type": "string", "format": "at-uri" }, 118 + "cid": { "type": "string", "format": "cid" }, 119 + "validationStatus": { 120 + "type": "string", 121 + "knownValues": ["valid", "unknown"] 122 + } 123 + } 124 + }, 125 + "deleteResult": { 126 + "type": "object", 127 + "required": [], 128 + "properties": {} 129 + } 130 + } 131 + }
+73
lexicons/com/atproto/repo/createRecord.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.createRecord", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Create a single new repository record. Requires auth, implemented by PDS.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["repo", "collection", "record"], 13 + "properties": { 14 + "repo": { 15 + "type": "string", 16 + "format": "at-identifier", 17 + "description": "The handle or DID of the repo (aka, current account)." 18 + }, 19 + "collection": { 20 + "type": "string", 21 + "format": "nsid", 22 + "description": "The NSID of the record collection." 23 + }, 24 + "rkey": { 25 + "type": "string", 26 + "format": "record-key", 27 + "description": "The Record Key.", 28 + "maxLength": 512 29 + }, 30 + "validate": { 31 + "type": "boolean", 32 + "description": "Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons." 33 + }, 34 + "record": { 35 + "type": "unknown", 36 + "description": "The record itself. Must contain a $type field." 37 + }, 38 + "swapCommit": { 39 + "type": "string", 40 + "format": "cid", 41 + "description": "Compare and swap with the previous commit by CID." 42 + } 43 + } 44 + } 45 + }, 46 + "output": { 47 + "encoding": "application/json", 48 + "schema": { 49 + "type": "object", 50 + "required": ["uri", "cid"], 51 + "properties": { 52 + "uri": { "type": "string", "format": "at-uri" }, 53 + "cid": { "type": "string", "format": "cid" }, 54 + "commit": { 55 + "type": "ref", 56 + "ref": "com.atproto.repo.defs#commitMeta" 57 + }, 58 + "validationStatus": { 59 + "type": "string", 60 + "knownValues": ["valid", "unknown"] 61 + } 62 + } 63 + } 64 + }, 65 + "errors": [ 66 + { 67 + "name": "InvalidSwap", 68 + "description": "Indicates that 'swapCommit' didn't match current repo commit." 69 + } 70 + ] 71 + } 72 + } 73 + }
+14
lexicons/com/atproto/repo/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.defs", 4 + "defs": { 5 + "commitMeta": { 6 + "type": "object", 7 + "required": ["cid", "rev"], 8 + "properties": { 9 + "cid": { "type": "string", "format": "cid" }, 10 + "rev": { "type": "string", "format": "tid" } 11 + } 12 + } 13 + } 14 + }
+57
lexicons/com/atproto/repo/deleteRecord.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.deleteRecord", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Delete a repository record, or ensure it doesn't exist. Requires auth, implemented by PDS.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["repo", "collection", "rkey"], 13 + "properties": { 14 + "repo": { 15 + "type": "string", 16 + "format": "at-identifier", 17 + "description": "The handle or DID of the repo (aka, current account)." 18 + }, 19 + "collection": { 20 + "type": "string", 21 + "format": "nsid", 22 + "description": "The NSID of the record collection." 23 + }, 24 + "rkey": { 25 + "type": "string", 26 + "format": "record-key", 27 + "description": "The Record Key." 28 + }, 29 + "swapRecord": { 30 + "type": "string", 31 + "format": "cid", 32 + "description": "Compare and swap with the previous record by CID." 33 + }, 34 + "swapCommit": { 35 + "type": "string", 36 + "format": "cid", 37 + "description": "Compare and swap with the previous commit by CID." 38 + } 39 + } 40 + } 41 + }, 42 + "output": { 43 + "encoding": "application/json", 44 + "schema": { 45 + "type": "object", 46 + "properties": { 47 + "commit": { 48 + "type": "ref", 49 + "ref": "com.atproto.repo.defs#commitMeta" 50 + } 51 + } 52 + } 53 + }, 54 + "errors": [{ "name": "InvalidSwap" }] 55 + } 56 + } 57 + }
+51
lexicons/com/atproto/repo/describeRepo.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.describeRepo", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get information about an account and repository, including the list of collections. Does not require auth.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["repo"], 11 + "properties": { 12 + "repo": { 13 + "type": "string", 14 + "format": "at-identifier", 15 + "description": "The handle or DID of the repo." 16 + } 17 + } 18 + }, 19 + "output": { 20 + "encoding": "application/json", 21 + "schema": { 22 + "type": "object", 23 + "required": [ 24 + "handle", 25 + "did", 26 + "didDoc", 27 + "collections", 28 + "handleIsCorrect" 29 + ], 30 + "properties": { 31 + "handle": { "type": "string", "format": "handle" }, 32 + "did": { "type": "string", "format": "did" }, 33 + "didDoc": { 34 + "type": "unknown", 35 + "description": "The complete DID document for this account." 36 + }, 37 + "collections": { 38 + "type": "array", 39 + "description": "List of all the collections (NSIDs) for which this repo contains at least one record.", 40 + "items": { "type": "string", "format": "nsid" } 41 + }, 42 + "handleIsCorrect": { 43 + "type": "boolean", 44 + "description": "Indicates if handle is currently valid (resolves bi-directionally)" 45 + } 46 + } 47 + } 48 + } 49 + } 50 + } 51 + }
+49
lexicons/com/atproto/repo/getRecord.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.getRecord", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get a single record from a repository. Does not require auth.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["repo", "collection", "rkey"], 11 + "properties": { 12 + "repo": { 13 + "type": "string", 14 + "format": "at-identifier", 15 + "description": "The handle or DID of the repo." 16 + }, 17 + "collection": { 18 + "type": "string", 19 + "format": "nsid", 20 + "description": "The NSID of the record collection." 21 + }, 22 + "rkey": { 23 + "type": "string", 24 + "description": "The Record Key.", 25 + "format": "record-key" 26 + }, 27 + "cid": { 28 + "type": "string", 29 + "format": "cid", 30 + "description": "The CID of the version of the record. If not specified, then return the most recent version." 31 + } 32 + } 33 + }, 34 + "output": { 35 + "encoding": "application/json", 36 + "schema": { 37 + "type": "object", 38 + "required": ["uri", "value"], 39 + "properties": { 40 + "uri": { "type": "string", "format": "at-uri" }, 41 + "cid": { "type": "string", "format": "cid" }, 42 + "value": { "type": "unknown" } 43 + } 44 + } 45 + }, 46 + "errors": [{ "name": "RecordNotFound" }] 47 + } 48 + } 49 + }
+13
lexicons/com/atproto/repo/importRepo.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.importRepo", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Import a repo in the form of a CAR file. Requires Content-Length HTTP header to be set.", 8 + "input": { 9 + "encoding": "application/vnd.ipld.car" 10 + } 11 + } 12 + } 13 + }
+44
lexicons/com/atproto/repo/listMissingBlobs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.listMissingBlobs", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Returns a list of missing blobs for the requesting account. Intended to be used in the account migration flow.", 8 + "parameters": { 9 + "type": "params", 10 + "properties": { 11 + "limit": { 12 + "type": "integer", 13 + "minimum": 1, 14 + "maximum": 1000, 15 + "default": 500 16 + }, 17 + "cursor": { "type": "string" } 18 + } 19 + }, 20 + "output": { 21 + "encoding": "application/json", 22 + "schema": { 23 + "type": "object", 24 + "required": ["blobs"], 25 + "properties": { 26 + "cursor": { "type": "string" }, 27 + "blobs": { 28 + "type": "array", 29 + "items": { "type": "ref", "ref": "#recordBlob" } 30 + } 31 + } 32 + } 33 + } 34 + }, 35 + "recordBlob": { 36 + "type": "object", 37 + "required": ["cid", "recordUri"], 38 + "properties": { 39 + "cid": { "type": "string", "format": "cid" }, 40 + "recordUri": { "type": "string", "format": "at-uri" } 41 + } 42 + } 43 + } 44 + }
+69
lexicons/com/atproto/repo/listRecords.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.listRecords", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "List a range of records in a repository, matching a specific collection. Does not require auth.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["repo", "collection"], 11 + "properties": { 12 + "repo": { 13 + "type": "string", 14 + "format": "at-identifier", 15 + "description": "The handle or DID of the repo." 16 + }, 17 + "collection": { 18 + "type": "string", 19 + "format": "nsid", 20 + "description": "The NSID of the record type." 21 + }, 22 + "limit": { 23 + "type": "integer", 24 + "minimum": 1, 25 + "maximum": 100, 26 + "default": 50, 27 + "description": "The number of records to return." 28 + }, 29 + "cursor": { "type": "string" }, 30 + "rkeyStart": { 31 + "type": "string", 32 + "description": "DEPRECATED: The lowest sort-ordered rkey to start from (exclusive)" 33 + }, 34 + "rkeyEnd": { 35 + "type": "string", 36 + "description": "DEPRECATED: The highest sort-ordered rkey to stop at (exclusive)" 37 + }, 38 + "reverse": { 39 + "type": "boolean", 40 + "description": "Flag to reverse the order of the returned records." 41 + } 42 + } 43 + }, 44 + "output": { 45 + "encoding": "application/json", 46 + "schema": { 47 + "type": "object", 48 + "required": ["records"], 49 + "properties": { 50 + "cursor": { "type": "string" }, 51 + "records": { 52 + "type": "array", 53 + "items": { "type": "ref", "ref": "#record" } 54 + } 55 + } 56 + } 57 + } 58 + }, 59 + "record": { 60 + "type": "object", 61 + "required": ["uri", "cid", "value"], 62 + "properties": { 63 + "uri": { "type": "string", "format": "at-uri" }, 64 + "cid": { "type": "string", "format": "cid" }, 65 + "value": { "type": "unknown" } 66 + } 67 + } 68 + } 69 + }
+74
lexicons/com/atproto/repo/putRecord.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.putRecord", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Write a repository record, creating or updating it as needed. Requires auth, implemented by PDS.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["repo", "collection", "rkey", "record"], 13 + "nullable": ["swapRecord"], 14 + "properties": { 15 + "repo": { 16 + "type": "string", 17 + "format": "at-identifier", 18 + "description": "The handle or DID of the repo (aka, current account)." 19 + }, 20 + "collection": { 21 + "type": "string", 22 + "format": "nsid", 23 + "description": "The NSID of the record collection." 24 + }, 25 + "rkey": { 26 + "type": "string", 27 + "format": "record-key", 28 + "description": "The Record Key.", 29 + "maxLength": 512 30 + }, 31 + "validate": { 32 + "type": "boolean", 33 + "description": "Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons." 34 + }, 35 + "record": { 36 + "type": "unknown", 37 + "description": "The record to write." 38 + }, 39 + "swapRecord": { 40 + "type": "string", 41 + "format": "cid", 42 + "description": "Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation" 43 + }, 44 + "swapCommit": { 45 + "type": "string", 46 + "format": "cid", 47 + "description": "Compare and swap with the previous commit by CID." 48 + } 49 + } 50 + } 51 + }, 52 + "output": { 53 + "encoding": "application/json", 54 + "schema": { 55 + "type": "object", 56 + "required": ["uri", "cid"], 57 + "properties": { 58 + "uri": { "type": "string", "format": "at-uri" }, 59 + "cid": { "type": "string", "format": "cid" }, 60 + "commit": { 61 + "type": "ref", 62 + "ref": "com.atproto.repo.defs#commitMeta" 63 + }, 64 + "validationStatus": { 65 + "type": "string", 66 + "knownValues": ["valid", "unknown"] 67 + } 68 + } 69 + } 70 + }, 71 + "errors": [{ "name": "InvalidSwap" }] 72 + } 73 + } 74 + }
+15
lexicons/com/atproto/repo/strongRef.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.strongRef", 4 + "description": "A URI with a content-hash fingerprint.", 5 + "defs": { 6 + "main": { 7 + "type": "object", 8 + "required": ["uri", "cid"], 9 + "properties": { 10 + "uri": { "type": "string", "format": "at-uri" }, 11 + "cid": { "type": "string", "format": "cid" } 12 + } 13 + } 14 + } 15 + }
+23
lexicons/com/atproto/repo/uploadBlob.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.uploadBlob", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Upload a new blob, to be referenced from a repository record. The blob will be deleted if it is not referenced within a time window (eg, minutes). Blob restrictions (mimetype, size, etc) are enforced when the reference is created. Requires auth, implemented by PDS.", 8 + "input": { 9 + "encoding": "*/*" 10 + }, 11 + "output": { 12 + "encoding": "application/json", 13 + "schema": { 14 + "type": "object", 15 + "required": ["blob"], 16 + "properties": { 17 + "blob": { "type": "blob" } 18 + } 19 + } 20 + } 21 + } 22 + } 23 + }
-156
lexicons/defs.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "com.atproto.label.defs", 4 - "defs": { 5 - "label": { 6 - "type": "object", 7 - "description": "Metadata tag on an atproto resource (eg, repo or record).", 8 - "required": ["src", "uri", "val", "cts"], 9 - "properties": { 10 - "ver": { 11 - "type": "integer", 12 - "description": "The AT Protocol version of the label object." 13 - }, 14 - "src": { 15 - "type": "string", 16 - "format": "did", 17 - "description": "DID of the actor who created this label." 18 - }, 19 - "uri": { 20 - "type": "string", 21 - "format": "uri", 22 - "description": "AT URI of the record, repository (account), or other resource that this label applies to." 23 - }, 24 - "cid": { 25 - "type": "string", 26 - "format": "cid", 27 - "description": "Optionally, CID specifying the specific version of 'uri' resource this label applies to." 28 - }, 29 - "val": { 30 - "type": "string", 31 - "maxLength": 128, 32 - "description": "The short string name of the value or type of this label." 33 - }, 34 - "neg": { 35 - "type": "boolean", 36 - "description": "If true, this is a negation label, overwriting a previous label." 37 - }, 38 - "cts": { 39 - "type": "string", 40 - "format": "datetime", 41 - "description": "Timestamp when this label was created." 42 - }, 43 - "exp": { 44 - "type": "string", 45 - "format": "datetime", 46 - "description": "Timestamp at which this label expires (no longer applies)." 47 - }, 48 - "sig": { 49 - "type": "bytes", 50 - "description": "Signature of dag-cbor encoded label." 51 - } 52 - } 53 - }, 54 - "selfLabels": { 55 - "type": "object", 56 - "description": "Metadata tags on an atproto record, published by the author within the record.", 57 - "required": ["values"], 58 - "properties": { 59 - "values": { 60 - "type": "array", 61 - "items": { "type": "ref", "ref": "#selfLabel" }, 62 - "maxLength": 10 63 - } 64 - } 65 - }, 66 - "selfLabel": { 67 - "type": "object", 68 - "description": "Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.", 69 - "required": ["val"], 70 - "properties": { 71 - "val": { 72 - "type": "string", 73 - "maxLength": 128, 74 - "description": "The short string name of the value or type of this label." 75 - } 76 - } 77 - }, 78 - "labelValueDefinition": { 79 - "type": "object", 80 - "description": "Declares a label value and its expected interpretations and behaviors.", 81 - "required": ["identifier", "severity", "blurs", "locales"], 82 - "properties": { 83 - "identifier": { 84 - "type": "string", 85 - "description": "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", 86 - "maxLength": 100, 87 - "maxGraphemes": 100 88 - }, 89 - "severity": { 90 - "type": "string", 91 - "description": "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", 92 - "knownValues": ["inform", "alert", "none"] 93 - }, 94 - "blurs": { 95 - "type": "string", 96 - "description": "What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.", 97 - "knownValues": ["content", "media", "none"] 98 - }, 99 - "defaultSetting": { 100 - "type": "string", 101 - "description": "The default setting for this label.", 102 - "knownValues": ["ignore", "warn", "hide"], 103 - "default": "warn" 104 - }, 105 - "adultOnly": { 106 - "type": "boolean", 107 - "description": "Does the user need to have adult content enabled in order to configure this label?" 108 - }, 109 - "locales": { 110 - "type": "array", 111 - "items": { "type": "ref", "ref": "#labelValueDefinitionStrings" } 112 - } 113 - } 114 - }, 115 - "labelValueDefinitionStrings": { 116 - "type": "object", 117 - "description": "Strings which describe the label in the UI, localized into a specific language.", 118 - "required": ["lang", "name", "description"], 119 - "properties": { 120 - "lang": { 121 - "type": "string", 122 - "description": "The code of the language these strings are written in.", 123 - "format": "language" 124 - }, 125 - "name": { 126 - "type": "string", 127 - "description": "A short human-readable name for the label.", 128 - "maxGraphemes": 64, 129 - "maxLength": 640 130 - }, 131 - "description": { 132 - "type": "string", 133 - "description": "A longer description of what the label means and why it might be applied.", 134 - "maxGraphemes": 10000, 135 - "maxLength": 100000 136 - } 137 - } 138 - }, 139 - "labelValue": { 140 - "type": "string", 141 - "knownValues": [ 142 - "!hide", 143 - "!no-promote", 144 - "!warn", 145 - "!no-unauthenticated", 146 - "dmca-violation", 147 - "doxxing", 148 - "porn", 149 - "sexual", 150 - "nudity", 151 - "nsfl", 152 - "gore" 153 - ] 154 - } 155 - } 156 - }
+4
lexicons/profile.json lexicons/app/bsky/profile/profile.json
··· 41 41 "type": "ref", 42 42 "ref": "com.atproto.repo.strongRef" 43 43 }, 44 + "pinnedPost": { 45 + "type": "ref", 46 + "ref": "com.atproto.repo.strongRef" 47 + }, 44 48 "createdAt": { "type": "string", "format": "datetime" } 45 49 } 46 50 }
lexicons/status.json lexicons/xyz/statusphere/status.json
-15
lexicons/strongRef.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "com.atproto.repo.strongRef", 4 - "description": "A URI with a content-hash fingerprint.", 5 - "defs": { 6 - "main": { 7 - "type": "object", 8 - "required": ["uri", "cid"], 9 - "properties": { 10 - "uri": { "type": "string", "format": "at-uri" }, 11 - "cid": { "type": "string", "format": "cid" } 12 - } 13 - } 14 - } 15 - }
+29
lexicons/xyz/statusphere/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "xyz.statusphere.defs", 4 + "defs": { 5 + "statusView": { 6 + "type": "object", 7 + "required": ["uri", "status", "profile", "createdAt"], 8 + "properties": { 9 + "uri": { "type": "string", "format": "at-uri" }, 10 + "status": { 11 + "type": "string", 12 + "minLength": 1, 13 + "maxGraphemes": 1, 14 + "maxLength": 32 15 + }, 16 + "createdAt": { "type": "string", "format": "datetime" }, 17 + "profile": { "type": "ref", "ref": "#profileView" } 18 + } 19 + }, 20 + "profileView": { 21 + "type": "object", 22 + "required": ["did", "handle"], 23 + "properties": { 24 + "did": { "type": "string", "format": "did" }, 25 + "handle": { "type": "string", "format": "handle" } 26 + } 27 + } 28 + } 29 + }
+16 -46
package.json
··· 1 1 { 2 - "name": "atproto-example-app", 2 + "name": "statusphere-react", 3 3 "version": "0.0.1", 4 - "description": "", 4 + "description": "Statusphere React monorepo", 5 5 "author": "", 6 6 "license": "MIT", 7 - "main": "index.ts", 8 7 "private": true, 9 8 "scripts": { 10 - "dev": "tsx watch --clear-screen=false src/index.ts | pino-pretty", 11 - "build": "tsup", 12 - "start": "node dist/index.js", 13 - "lexgen": "lex gen-server ./src/lexicon ./lexicons/*", 14 - "clean": "rimraf dist coverage", 15 - "format": "prettier --write src", 16 - "typecheck": "tsc --noEmit" 17 - }, 18 - "dependencies": { 19 - "@atproto/api": "^0.14.7", 20 - "@atproto/common": "^0.4.1", 21 - "@atproto/identity": "^0.4.0", 22 - "@atproto/lexicon": "^0.4.2", 23 - "@atproto/oauth-client-node": "^0.2.2", 24 - "@atproto/sync": "^0.1.4", 25 - "@atproto/syntax": "^0.3.0", 26 - "@atproto/xrpc-server": "^0.7.9", 27 - "better-sqlite3": "^11.1.2", 28 - "dotenv": "^16.4.5", 29 - "envalid": "^8.0.0", 30 - "express": "^4.19.2", 31 - "iron-session": "^8.0.2", 32 - "kysely": "^0.27.4", 33 - "multiformats": "^13.3.2", 34 - "pino": "^9.3.2", 35 - "uhtml": "^4.5.9" 9 + "dev": "concurrently \"pnpm --filter @statusphere/appview dev\" \"pnpm --filter @statusphere/client dev\"", 10 + "dev:appview": "pnpm --filter @statusphere/appview dev", 11 + "dev:client": "pnpm --filter @statusphere/client dev", 12 + "dev:oauth": "node scripts/setup-ngrok.js", 13 + "lexgen": "pnpm --filter @statusphere/lexicon build", 14 + "build": "pnpm -r build", 15 + "start": "pnpm -r start", 16 + "clean": "pnpm -r clean", 17 + "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"", 18 + "typecheck": "pnpm -r typecheck" 36 19 }, 37 20 "devDependencies": { 38 21 "@atproto/lex-cli": "^0.6.1", 39 - "@types/better-sqlite3": "^7.6.11", 40 - "@types/express": "^5.0.0", 41 - "pino-pretty": "^13.0.0", 22 + "@ianvs/prettier-plugin-sort-imports": "^4.4.1", 23 + "concurrently": "^9.1.2", 42 24 "prettier": "^3.5.2", 25 + "prettier-plugin-tailwindcss": "^0.6.11", 43 26 "rimraf": "^6.0.1", 44 - "ts-node": "^10.9.2", 45 - "tsup": "^8.0.2", 46 - "tsx": "^4.7.2", 47 - "typescript": "^5.4.4" 48 - }, 49 - "tsup": { 50 - "entry": [ 51 - "src", 52 - "!src/**/__tests__/**", 53 - "!src/**/*.test.*" 54 - ], 55 - "splitting": false, 56 - "sourcemap": true, 57 - "clean": true 27 + "typescript": "^5.8.2" 58 28 }, 59 29 "packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0" 60 30 }
+64
packages/appview/README.md
··· 1 + # Statusphere AppView 2 + 3 + This is the backend API for the Statusphere application. It provides REST endpoints for the React frontend to consume. 4 + 5 + ## Development 6 + 7 + ```bash 8 + # Install dependencies 9 + pnpm install 10 + 11 + # Start development server 12 + pnpm dev 13 + 14 + # Build for production 15 + pnpm build 16 + 17 + # Start production server 18 + pnpm start 19 + ``` 20 + 21 + ## Environment Variables 22 + 23 + Create a `.env` file in the root of this package with the following variables: 24 + 25 + ``` 26 + NODE_ENV=development 27 + HOST=localhost 28 + PORT=3001 29 + DB_PATH=./data.sqlite 30 + COOKIE_SECRET=your_secret_here_at_least_32_characters_long 31 + ATPROTO_SERVER=https://bsky.social 32 + PUBLIC_URL=http://localhost:3001 33 + NGROK_URL=your_ngrok_url_here 34 + ``` 35 + 36 + ## Using ngrok for OAuth Development 37 + 38 + Due to OAuth requirements, we need to use HTTPS for development. The easiest way to do this is with ngrok: 39 + 40 + 1. Install ngrok: https://ngrok.com/download 41 + 2. Run ngrok to create a tunnel to your local server: 42 + ```bash 43 + ngrok http 3001 44 + ``` 45 + 3. Copy the HTTPS URL provided by ngrok (e.g., `https://abcd-123-45-678-90.ngrok.io`) 46 + 4. Add it to your `.env` file: 47 + ``` 48 + NGROK_URL=https://abcd-123-45-678-90.ngrok.io 49 + ``` 50 + 5. Also update the API URL in the client package: 51 + ``` 52 + # In packages/client/src/services/api.ts 53 + const API_URL = 'https://abcd-123-45-678-90.ngrok.io'; 54 + ``` 55 + 56 + ## API Endpoints 57 + 58 + - `GET /client-metadata.json` - OAuth client metadata 59 + - `GET /oauth/callback` - OAuth callback endpoint 60 + - `POST /login` - Login with handle 61 + - `POST /logout` - Logout current user 62 + - `GET /user` - Get current user info 63 + - `GET /statuses` - Get recent statuses 64 + - `POST /status` - Create a new status
+58
packages/appview/package.json
··· 1 + { 2 + "name": "@statusphere/appview", 3 + "version": "0.0.1", 4 + "description": "Statusphere AppView backend", 5 + "author": "", 6 + "license": "MIT", 7 + "main": "dist/index.js", 8 + "private": true, 9 + "scripts": { 10 + "dev": "tsx watch --clear-screen=false src/index.ts | pino-pretty", 11 + "build": "tsup", 12 + "start": "node dist/index.js", 13 + "clean": "rimraf dist coverage", 14 + "format": "prettier --write src", 15 + "typecheck": "tsc --noEmit" 16 + }, 17 + "dependencies": { 18 + "@atproto/api": "^0.14.7", 19 + "@atproto/common": "^0.4.8", 20 + "@atproto/identity": "^0.4.6", 21 + "@atproto/lexicon": "^0.4.7", 22 + "@atproto/oauth-client-node": "^0.2.11", 23 + "@atproto/sync": "^0.1.15", 24 + "@atproto/syntax": "^0.3.3", 25 + "@atproto/xrpc-server": "^0.7.11", 26 + "@statusphere/lexicon": "workspace:*", 27 + "better-sqlite3": "^11.8.1", 28 + "cors": "^2.8.5", 29 + "dotenv": "^16.4.7", 30 + "envalid": "^8.0.0", 31 + "express": "^4.21.2", 32 + "iron-session": "^8.0.4", 33 + "kysely": "^0.27.5", 34 + "multiformats": "^13.3.2", 35 + "pino": "^9.6.0" 36 + }, 37 + "devDependencies": { 38 + "@types/better-sqlite3": "^7.6.12", 39 + "@types/cors": "^2.8.17", 40 + "@types/express": "^5.0.0", 41 + "@types/node": "^22.13.8", 42 + "pino-pretty": "^13.0.0", 43 + "ts-node": "^10.9.2", 44 + "tsup": "^8.4.0", 45 + "tsx": "^4.19.3", 46 + "typescript": "^5.8.2" 47 + }, 48 + "tsup": { 49 + "entry": [ 50 + "src", 51 + "!src/**/__tests__/**", 52 + "!src/**/*.test.*" 53 + ], 54 + "splitting": false, 55 + "sourcemap": true, 56 + "clean": true 57 + } 58 + }
+40
packages/appview/src/auth/client.ts
··· 1 + import { NodeOAuthClient } from '@atproto/oauth-client-node' 2 + 3 + import type { Database } from '#/db' 4 + import { env } from '#/lib/env' 5 + import { SessionStore, StateStore } from './storage' 6 + 7 + export const createClient = async (db: Database) => { 8 + // Get the ngrok URL from environment variables 9 + const ngrokUrl = env.NGROK_URL 10 + 11 + if (!ngrokUrl) { 12 + console.warn( 13 + 'WARNING: NGROK_URL is not set. OAuth login might not work properly.', 14 + ) 15 + console.warn( 16 + 'You should run ngrok and set the NGROK_URL environment variable.', 17 + ) 18 + console.warn('Example: NGROK_URL=https://abcd-123-45-678-90.ngrok.io') 19 + } 20 + 21 + // The base URL is either the ngrok URL (preferred) or a local URL as fallback 22 + const baseUrl = ngrokUrl || `http://127.0.0.1:${env.PORT}` 23 + 24 + return new NodeOAuthClient({ 25 + clientMetadata: { 26 + client_name: 'Statusphere React App', 27 + client_id: `${baseUrl}/api/client-metadata.json`, 28 + client_uri: baseUrl, 29 + redirect_uris: [`${baseUrl}/api/oauth/callback`], 30 + scope: 'atproto transition:generic', 31 + grant_types: ['authorization_code', 'refresh_token'], 32 + response_types: ['code'], 33 + application_type: 'web', 34 + token_endpoint_auth_method: 'none', 35 + dpop_bound_access_tokens: true, 36 + }, 37 + stateStore: new StateStore(db), 38 + sessionStore: new SessionStore(db), 39 + }) 40 + }
+162
packages/appview/src/index.ts
··· 1 + import events from 'node:events' 2 + import type http from 'node:http' 3 + import type { OAuthClient } from '@atproto/oauth-client-node' 4 + import { Firehose } from '@atproto/sync' 5 + import cors from 'cors' 6 + import express, { type Express } from 'express' 7 + import { pino } from 'pino' 8 + 9 + import { createClient } from '#/auth/client' 10 + import { createDb, migrateToLatest } from '#/db' 11 + import type { Database } from '#/db' 12 + import { 13 + BidirectionalResolver, 14 + createBidirectionalResolver, 15 + createIdResolver, 16 + } from '#/id-resolver' 17 + import { createIngester } from '#/ingester' 18 + import { env } from '#/lib/env' 19 + import { createRouter } from '#/routes' 20 + 21 + // Application state passed to the router and elsewhere 22 + export type AppContext = { 23 + db: Database 24 + ingester: Firehose 25 + logger: pino.Logger 26 + oauthClient: OAuthClient 27 + resolver: BidirectionalResolver 28 + } 29 + 30 + export class Server { 31 + constructor( 32 + public app: express.Application, 33 + public server: http.Server, 34 + public ctx: AppContext, 35 + ) {} 36 + 37 + static async create() { 38 + const { NODE_ENV, HOST, PORT, DB_PATH } = env 39 + const logger = pino({ name: 'server start' }) 40 + 41 + // Set up the SQLite database 42 + const db = createDb(DB_PATH) 43 + await migrateToLatest(db) 44 + 45 + // Create the atproto utilities 46 + const oauthClient = await createClient(db) 47 + const baseIdResolver = createIdResolver() 48 + const ingester = createIngester(db, baseIdResolver) 49 + const resolver = createBidirectionalResolver(baseIdResolver) 50 + const ctx = { 51 + db, 52 + ingester, 53 + logger, 54 + oauthClient, 55 + resolver, 56 + } 57 + 58 + // Subscribe to events on the firehose 59 + ingester.start() 60 + 61 + // Create our server 62 + const app: Express = express() 63 + app.set('trust proxy', true) 64 + 65 + // CORS configuration based on environment 66 + if (env.NODE_ENV === 'development') { 67 + // In development, allow multiple origins including ngrok 68 + app.use( 69 + cors({ 70 + origin: function (origin, callback) { 71 + // Allow requests with no origin (like mobile apps, curl) 72 + if (!origin) return callback(null, true) 73 + 74 + // List of allowed origins 75 + const allowedOrigins = [ 76 + 'http://localhost:3000', // Standard React port 77 + 'http://127.0.0.1:3000', // Alternative React address 78 + ] 79 + 80 + // If we have an ngrok URL defined, add it to allowed origins 81 + if (env.NGROK_URL) { 82 + try { 83 + const ngrokOrigin = new URL(env.NGROK_URL) 84 + const ngrokClientOrigin = `${ngrokOrigin.protocol}//${ngrokOrigin.hostname}:3000` 85 + allowedOrigins.push(ngrokClientOrigin) 86 + } catch (err) { 87 + console.error('Failed to parse NGROK_URL for CORS:', err) 88 + } 89 + } 90 + 91 + // Check if the request origin is in our allowed list or is an ngrok domain 92 + if ( 93 + allowedOrigins.indexOf(origin) !== -1 || 94 + origin.includes('ngrok-free.app') 95 + ) { 96 + callback(null, true) 97 + } else { 98 + console.warn(`⚠️ CORS blocked origin: ${origin}`) 99 + callback(null, false) 100 + } 101 + }, 102 + credentials: true, 103 + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], 104 + allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'], 105 + }), 106 + ) 107 + } else { 108 + // In production, CORS is not needed if frontend and API are on same domain 109 + // But we'll still enable it for flexibility with minimal configuration 110 + app.use( 111 + cors({ 112 + origin: true, // Use req.origin, which means same-origin requests will always be allowed 113 + credentials: true, 114 + }), 115 + ) 116 + } 117 + 118 + // Routes & middlewares 119 + const router = createRouter(ctx) 120 + app.use(express.json()) 121 + app.use(express.urlencoded({ extended: true })) 122 + app.use(router) 123 + app.use('*', (_req, res) => { 124 + res.sendStatus(404) 125 + }) 126 + 127 + // Use the port from env (should be 3001 for the API server) 128 + const server = app.listen(env.PORT) 129 + await events.once(server, 'listening') 130 + logger.info( 131 + `API Server (${NODE_ENV}) running on port http://${HOST}:${env.PORT}`, 132 + ) 133 + 134 + return new Server(app, server, ctx) 135 + } 136 + 137 + async close() { 138 + this.ctx.logger.info('sigint received, shutting down') 139 + await this.ctx.ingester.destroy() 140 + return new Promise<void>((resolve) => { 141 + this.server.close(() => { 142 + this.ctx.logger.info('server closed') 143 + resolve() 144 + }) 145 + }) 146 + } 147 + } 148 + 149 + const run = async () => { 150 + const server = await Server.create() 151 + 152 + const onCloseSignal = async () => { 153 + setTimeout(() => process.exit(1), 10000).unref() // Force shutdown after 10s 154 + await server.close() 155 + process.exit() 156 + } 157 + 158 + process.on('SIGINT', onCloseSignal) 159 + process.on('SIGTERM', onCloseSignal) 160 + } 161 + 162 + run()
+19
packages/appview/src/lib/env.ts
··· 1 + import dotenv from 'dotenv' 2 + import { cleanEnv, host, port, str, testOnly, url } from 'envalid' 3 + 4 + dotenv.config() 5 + 6 + export const env = cleanEnv(process.env, { 7 + NODE_ENV: str({ 8 + devDefault: testOnly('test'), 9 + choices: ['development', 'production', 'test'], 10 + }), 11 + HOST: host({ devDefault: testOnly('localhost') }), 12 + PORT: port({ devDefault: testOnly(3001) }), 13 + DB_PATH: str({ devDefault: ':memory:' }), 14 + COOKIE_SECRET: str({ devDefault: '00000000000000000000000000000000' }), 15 + ATPROTO_SERVER: str({ default: 'https://bsky.social' }), 16 + SERVICE_DID: str({ default: undefined }), 17 + PUBLIC_URL: str({ default: 'http://localhost:3001' }), 18 + NGROK_URL: str({ default: '' }), 19 + })
+21
packages/appview/src/lib/status.ts
··· 1 + import { XyzStatusphereDefs } from '@statusphere/lexicon' 2 + 3 + import { Status } from '#/db' 4 + import { AppContext } from '#/index' 5 + 6 + export async function statusToStatusView( 7 + status: Status, 8 + ctx: AppContext, 9 + ): Promise<XyzStatusphereDefs.StatusView> { 10 + return { 11 + uri: status.uri, 12 + status: status.status, 13 + createdAt: status.createdAt, 14 + profile: { 15 + did: status.authorDid, 16 + handle: await ctx.resolver 17 + .resolveDidToHandle(status.authorDid) 18 + .catch(() => 'invalid.handle'), 19 + }, 20 + } 21 + }
+324
packages/appview/src/routes.ts
··· 1 + import type { IncomingMessage, ServerResponse } from 'node:http' 2 + import { Agent } from '@atproto/api' 3 + import { TID } from '@atproto/common' 4 + import { OAuthResolverError } from '@atproto/oauth-client-node' 5 + import { isValidHandle } from '@atproto/syntax' 6 + import { AppBskyActorProfile, XyzStatusphereStatus } from '@statusphere/lexicon' 7 + import express from 'express' 8 + import { getIronSession, SessionOptions } from 'iron-session' 9 + 10 + import type { AppContext } from '#/index' 11 + import { env } from '#/lib/env' 12 + import { statusToStatusView } from '#/lib/status' 13 + 14 + type Session = { did: string } 15 + 16 + // Common session options 17 + const sessionOptions: SessionOptions = { 18 + cookieName: 'sid', 19 + password: env.COOKIE_SECRET, 20 + cookieOptions: { 21 + secure: env.NODE_ENV === 'production', 22 + httpOnly: true, 23 + sameSite: 'lax', 24 + path: '/', 25 + // Don't set domain explicitly - let browser determine it 26 + domain: undefined, 27 + }, 28 + } 29 + 30 + // Helper function for defining routes 31 + const handler = 32 + ( 33 + fn: ( 34 + req: express.Request, 35 + res: express.Response, 36 + next: express.NextFunction, 37 + ) => Promise<void> | void, 38 + ) => 39 + async ( 40 + req: express.Request, 41 + res: express.Response, 42 + next: express.NextFunction, 43 + ) => { 44 + try { 45 + await fn(req, res, next) 46 + } catch (err) { 47 + next(err) 48 + } 49 + } 50 + 51 + // Helper function to get the Atproto Agent for the active session 52 + async function getSessionAgent( 53 + req: IncomingMessage | express.Request, 54 + res: ServerResponse<IncomingMessage> | express.Response, 55 + ctx: AppContext, 56 + ) { 57 + const session = await getIronSession<Session>(req, res, sessionOptions) 58 + 59 + if (!session.did) { 60 + return null 61 + } 62 + 63 + try { 64 + const oauthSession = await ctx.oauthClient.restore(session.did) 65 + return oauthSession ? new Agent(oauthSession) : null 66 + } catch (err) { 67 + ctx.logger.warn({ err }, 'oauth restore failed') 68 + session.destroy() 69 + return null 70 + } 71 + } 72 + 73 + export const createRouter = (ctx: AppContext) => { 74 + const router = express.Router() 75 + 76 + // Simple CORS configuration for all routes 77 + router.use((req, res, next) => { 78 + // Allow requests from either the specific origin or any origin during development 79 + res.header('Access-Control-Allow-Origin', req.headers.origin || '*') 80 + res.header('Access-Control-Allow-Credentials', 'true') 81 + res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') 82 + res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization') 83 + 84 + if (req.method === 'OPTIONS') { 85 + res.status(200).end() 86 + return 87 + } 88 + next() 89 + }) 90 + 91 + // OAuth metadata 92 + router.get( 93 + '/client-metadata.json', 94 + handler((_req, res) => { 95 + res.json(ctx.oauthClient.clientMetadata) 96 + }), 97 + ) 98 + 99 + // OAuth callback to complete session creation 100 + router.get( 101 + '/oauth/callback', 102 + handler(async (req, res) => { 103 + // Get the query parameters from the URL 104 + const params = new URLSearchParams(req.originalUrl.split('?')[1]) 105 + 106 + try { 107 + const { session } = await ctx.oauthClient.callback(params) 108 + 109 + // Use the common session options 110 + const clientSession = await getIronSession<Session>( 111 + req, 112 + res, 113 + sessionOptions, 114 + ) 115 + 116 + // Set the DID on the session 117 + clientSession.did = session.did 118 + await clientSession.save() 119 + 120 + // Redirect to the frontend oauth-callback page 121 + res.redirect('/oauth-callback') 122 + } catch (err) { 123 + ctx.logger.error({ err }, 'oauth callback failed') 124 + 125 + // Handle error redirect - stay on same domain 126 + res.redirect('/oauth-callback?error=auth') 127 + } 128 + }), 129 + ) 130 + 131 + // Login handler 132 + router.post( 133 + '/login', 134 + handler(async (req, res) => { 135 + // Validate 136 + const handle = req.body?.handle 137 + if (typeof handle !== 'string' || !isValidHandle(handle)) { 138 + res.status(400).json({ error: 'invalid handle' }) 139 + return 140 + } 141 + 142 + // Initiate the OAuth flow 143 + try { 144 + const url = await ctx.oauthClient.authorize(handle, { 145 + scope: 'atproto transition:generic', 146 + }) 147 + res.json({ redirectUrl: url.toString() }) 148 + } catch (err) { 149 + ctx.logger.error({ err }, 'oauth authorize failed') 150 + const errorMsg = 151 + err instanceof OAuthResolverError 152 + ? err.message 153 + : "couldn't initiate login" 154 + res.status(500).json({ error: errorMsg }) 155 + } 156 + }), 157 + ) 158 + 159 + // Logout handler 160 + router.post( 161 + '/logout', 162 + handler(async (req, res) => { 163 + const session = await getIronSession<Session>(req, res, sessionOptions) 164 + session.destroy() 165 + res.json({ success: true }) 166 + }), 167 + ) 168 + 169 + // Get current user info 170 + router.get( 171 + '/user', 172 + handler(async (req, res) => { 173 + const agent = await getSessionAgent(req, res, ctx) 174 + if (!agent) { 175 + res.status(401).json({ error: 'Not logged in' }) 176 + return 177 + } 178 + 179 + const did = agent.assertDid 180 + 181 + // Fetch user profile 182 + try { 183 + const profileResponse = await agent.com.atproto.repo 184 + .getRecord({ 185 + repo: did, 186 + collection: 'app.bsky.actor.profile', 187 + rkey: 'self', 188 + }) 189 + .catch(() => undefined) 190 + 191 + const profileRecord = profileResponse?.data 192 + const profile = 193 + profileRecord && 194 + AppBskyActorProfile.isRecord(profileRecord.value) && 195 + AppBskyActorProfile.validateRecord(profileRecord.value).success 196 + ? profileRecord.value 197 + : ({} as AppBskyActorProfile.Record) 198 + 199 + profile.did = did 200 + profile.handle = await ctx.resolver.resolveDidToHandle(did) 201 + 202 + // Fetch user status 203 + const status = await ctx.db 204 + .selectFrom('status') 205 + .selectAll() 206 + .where('authorDid', '=', did) 207 + .orderBy('indexedAt', 'desc') 208 + .executeTakeFirst() 209 + 210 + res.json({ 211 + did: agent.assertDid, 212 + profile, 213 + status: status ? await statusToStatusView(status, ctx) : undefined, 214 + }) 215 + } catch (err) { 216 + ctx.logger.error({ err }, 'Failed to get user info') 217 + res.status(500).json({ error: 'Failed to get user info' }) 218 + } 219 + }), 220 + ) 221 + 222 + // Get statuses 223 + router.get( 224 + '/statuses', 225 + handler(async (req, res) => { 226 + try { 227 + // Fetch data stored in our SQLite 228 + const statuses = await ctx.db 229 + .selectFrom('status') 230 + .selectAll() 231 + .orderBy('indexedAt', 'desc') 232 + .limit(10) 233 + .execute() 234 + 235 + res.json({ 236 + statuses: await Promise.all( 237 + statuses.map((status) => statusToStatusView(status, ctx)), 238 + ), 239 + }) 240 + } catch (err) { 241 + ctx.logger.error({ err }, 'Failed to get statuses') 242 + res.status(500).json({ error: 'Failed to get statuses' }) 243 + } 244 + }), 245 + ) 246 + 247 + // Create status 248 + router.post( 249 + '/status', 250 + handler(async (req, res) => { 251 + // If the user is signed in, get an agent which communicates with their server 252 + const agent = await getSessionAgent(req, res, ctx) 253 + if (!agent) { 254 + res.status(401).json({ error: 'Session required' }) 255 + return 256 + } 257 + 258 + // Construct & validate their status record 259 + const rkey = TID.nextStr() 260 + const record = { 261 + $type: 'xyz.statusphere.status', 262 + status: req.body?.status, 263 + createdAt: new Date().toISOString(), 264 + } 265 + if (!XyzStatusphereStatus.validateRecord(record).success) { 266 + res.status(400).json({ error: 'Invalid status' }) 267 + return 268 + } 269 + 270 + let uri 271 + try { 272 + // Write the status record to the user's repository 273 + const response = await agent.com.atproto.repo.putRecord({ 274 + repo: agent.assertDid, 275 + collection: 'xyz.statusphere.status', 276 + rkey, 277 + record, 278 + validate: false, 279 + }) 280 + uri = response.data.uri 281 + } catch (err) { 282 + ctx.logger.warn({ err }, 'failed to write record') 283 + res.status(500).json({ error: 'Failed to write record' }) 284 + return 285 + } 286 + 287 + try { 288 + // Optimistically update our SQLite 289 + // This isn't strictly necessary because the write event will be 290 + // handled in #/firehose/ingestor.ts, but it ensures that future reads 291 + // will be up-to-date after this method finishes. 292 + await ctx.db 293 + .insertInto('status') 294 + .values({ 295 + uri, 296 + authorDid: agent.assertDid, 297 + status: record.status, 298 + createdAt: record.createdAt, 299 + indexedAt: new Date().toISOString(), 300 + }) 301 + .execute() 302 + 303 + res.json({ 304 + success: true, 305 + uri, 306 + status: await statusToStatusView(record.status, ctx), 307 + }) 308 + } catch (err) { 309 + ctx.logger.warn( 310 + { err }, 311 + 'failed to update computed view; ignoring as it should be caught by the firehose', 312 + ) 313 + res.json({ 314 + success: true, 315 + uri, 316 + status: await statusToStatusView(record.status, ctx), 317 + warning: 'Database not updated', 318 + }) 319 + } 320 + }), 321 + ) 322 + 323 + return router 324 + }
+18
packages/appview/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "es2020", 4 + "module": "NodeNext", 5 + "moduleResolution": "NodeNext", 6 + "esModuleInterop": true, 7 + "forceConsistentCasingInFileNames": true, 8 + "strict": true, 9 + "skipLibCheck": true, 10 + "baseUrl": ".", 11 + "outDir": "dist", 12 + "paths": { 13 + "#/*": ["./src/*"] 14 + } 15 + }, 16 + "include": ["src/**/*"], 17 + "exclude": ["node_modules", "dist"] 18 + }
+35
packages/client/README.md
··· 1 + # Statusphere Client 2 + 3 + This is the React frontend for the Statusphere application. 4 + 5 + ## Development 6 + 7 + ```bash 8 + # Install dependencies 9 + pnpm install 10 + 11 + # Start development server 12 + pnpm dev 13 + 14 + # Build for production 15 + pnpm build 16 + 17 + # Preview production build 18 + pnpm preview 19 + ``` 20 + 21 + ## Features 22 + 23 + - Display statuses from all users 24 + - Create new statuses 25 + - Login with your Bluesky handle 26 + - View your profile info 27 + - Responsive design 28 + 29 + ## Architecture 30 + 31 + - React 18 with TypeScript 32 + - React Router for navigation 33 + - Context API for state management 34 + - Vite for development and building 35 + - CSS for styling
+13
packages/client/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 + <title>Statusphere React</title> 8 + </head> 9 + <body> 10 + <div id="root"></div> 11 + <script type="module" src="/src/main.tsx"></script> 12 + </body> 13 + </html>
+39
packages/client/package.json
··· 1 + { 2 + "name": "@statusphere/client", 3 + "private": true, 4 + "version": "0.0.1", 5 + "type": "module", 6 + "scripts": { 7 + "dev": "vite", 8 + "build": "tsc && vite build", 9 + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 + "preview": "vite preview", 11 + "clean": "rimraf dist", 12 + "typecheck": "tsc --noEmit" 13 + }, 14 + "dependencies": { 15 + "@atproto/api": "^0.14.7", 16 + "@statusphere/lexicon": "workspace:*", 17 + "@tailwindcss/vite": "^4.0.9", 18 + "@tanstack/react-query": "^5.66.11", 19 + "iron-session": "^8.0.4", 20 + "react": "^19.0.0", 21 + "react-dom": "^19.0.0", 22 + "react-router-dom": "^7.2.0" 23 + }, 24 + "devDependencies": { 25 + "@types/react": "^19.0.10", 26 + "@types/react-dom": "^19.0.4", 27 + "@typescript-eslint/eslint-plugin": "^8.25.0", 28 + "@typescript-eslint/parser": "^8.25.0", 29 + "@vitejs/plugin-react": "^4.3.4", 30 + "autoprefixer": "^10.4.20", 31 + "eslint": "^9.21.0", 32 + "eslint-plugin-react-hooks": "^5.2.0", 33 + "eslint-plugin-react-refresh": "^0.4.19", 34 + "postcss": "^8.5.3", 35 + "tailwindcss": "^4.0.9", 36 + "typescript": "^5.8.2", 37 + "vite": "^6.2.0" 38 + } 39 + }
+5
packages/client/public/favicon.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#3b82f6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 2 + <circle cx="12" cy="12" r="10"></circle> 3 + <line x1="12" y1="8" x2="12" y2="16"></line> 4 + <line x1="8" y1="12" x2="16" y2="12"></line> 5 + </svg>
+24
packages/client/src/App.tsx
··· 1 + import { Route, Routes } from 'react-router-dom' 2 + 3 + import { AuthProvider } from '#/hooks/useAuth' 4 + import HomePage from '#/pages/HomePage' 5 + import LoginPage from '#/pages/LoginPage' 6 + import OAuthCallbackPage from '#/pages/OAuthCallbackPage' 7 + 8 + function App() { 9 + return ( 10 + <div className="bg-gray-50 min-h-screen"> 11 + <div className="max-w-4xl mx-auto p-4 w-full"> 12 + <AuthProvider> 13 + <Routes> 14 + <Route path="/" element={<HomePage />} /> 15 + <Route path="/login" element={<LoginPage />} /> 16 + <Route path="/oauth-callback" element={<OAuthCallbackPage />} /> 17 + </Routes> 18 + </AuthProvider> 19 + </div> 20 + </div> 21 + ) 22 + } 23 + 24 + export default App
+55
packages/client/src/components/Header.tsx
··· 1 + import { Link } from 'react-router-dom' 2 + 3 + import { useAuth } from '#/hooks/useAuth' 4 + 5 + const Header = () => { 6 + const { user, logout } = useAuth() 7 + 8 + const handleLogout = async () => { 9 + try { 10 + await logout() 11 + } catch (error) { 12 + console.error('Logout failed:', error) 13 + } 14 + } 15 + 16 + return ( 17 + <header className="mb-8 border-b border-gray-200 pb-4"> 18 + <div className="flex justify-between items-center"> 19 + <h1 className="m-0 text-2xl font-bold"> 20 + <Link 21 + to="/" 22 + className="no-underline text-inherit hover:text-blue-600 transition-colors" 23 + > 24 + Statusphere 25 + </Link> 26 + </h1> 27 + <nav> 28 + {user ? ( 29 + <div className="flex gap-4 items-center"> 30 + <span className="text-gray-700"> 31 + {user.profile?.displayName || 32 + user.profile?.handle || 33 + user.did.substring(0, 15)} 34 + </span> 35 + <button 36 + onClick={handleLogout} 37 + className="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-md transition-colors" 38 + > 39 + Logout 40 + </button> 41 + </div> 42 + ) : ( 43 + <Link to="/login"> 44 + <button className="px-4 py-2 bg-blue-500 text-white hover:bg-blue-600 rounded-md transition-colors"> 45 + Login 46 + </button> 47 + </Link> 48 + )} 49 + </nav> 50 + </div> 51 + </header> 52 + ) 53 + } 54 + 55 + export default Header
+178
packages/client/src/components/StatusForm.tsx
··· 1 + import { useState } from 'react' 2 + import { useMutation, useQueryClient } from '@tanstack/react-query' 3 + import { XyzStatusphereDefs } from '@statusphere/lexicon' 4 + 5 + import useAuth from '#/hooks/useAuth' 6 + import api from '#/services/api' 7 + 8 + const STATUS_OPTIONS = [ 9 + '👍', 10 + '👎', 11 + '💙', 12 + '🥹', 13 + '😧', 14 + '😤', 15 + '🙃', 16 + '😉', 17 + '😎', 18 + '🤓', 19 + '🤨', 20 + '🥳', 21 + '😭', 22 + '😢', 23 + '🤯', 24 + '🫡', 25 + '💀', 26 + '✊', 27 + '🤘', 28 + '👀', 29 + '🧠', 30 + '👩‍💻', 31 + '🧑‍💻', 32 + '🥷', 33 + '🧌', 34 + '🦋', 35 + '🚀', 36 + ] 37 + 38 + const StatusForm = () => { 39 + const [error, setError] = useState<string | null>(null) 40 + const queryClient = useQueryClient() 41 + const { user } = useAuth() 42 + 43 + // Get current user's status emoji 44 + const currentUserStatus = user?.status?.status || null 45 + 46 + // Use React Query mutation for creating a status 47 + const mutation = useMutation({ 48 + mutationFn: (emoji: string) => api.createStatus(emoji), 49 + onMutate: async (emoji) => { 50 + // Cancel any outgoing refetches so they don't overwrite our optimistic updates 51 + await queryClient.cancelQueries({ queryKey: ['statuses'] }) 52 + await queryClient.cancelQueries({ queryKey: ['currentUser'] }) 53 + 54 + // Snapshot the previous values 55 + const previousStatuses = queryClient.getQueryData(['statuses']) 56 + const previousUser = queryClient.getQueryData(['currentUser']) 57 + 58 + // Optimistically update the statuses 59 + queryClient.setQueryData(['statuses'], (oldData: any) => { 60 + if (!oldData) return oldData 61 + if (!user) return oldData 62 + 63 + // Create a provisional status 64 + const optimisticStatus = { 65 + uri: `optimistic-${Date.now()}`, 66 + profile: { 67 + did: user.did, 68 + handle: user.profile.handle, 69 + }, 70 + status: emoji, 71 + createdAt: new Date().toISOString(), 72 + } satisfies XyzStatusphereDefs.StatusView 73 + 74 + return { 75 + ...oldData, 76 + statuses: [optimisticStatus, ...oldData.statuses], 77 + } 78 + }) 79 + 80 + // Optimistically update the user's profile status 81 + queryClient.setQueryData(['currentUser'], (oldUserData: any) => { 82 + if (!oldUserData) return oldUserData 83 + 84 + return { 85 + ...oldUserData, 86 + status: { 87 + ...oldUserData.status, 88 + status: emoji, 89 + createdAt: new Date().toISOString(), 90 + }, 91 + } 92 + }) 93 + 94 + // Return a context with the previous data 95 + return { previousStatuses, previousUser } 96 + }, 97 + onSuccess: () => { 98 + // Refetch after success to get the correct data 99 + queryClient.invalidateQueries({ queryKey: ['statuses'] }) 100 + }, 101 + onError: (err, _emoji, context) => { 102 + const message = 103 + err instanceof Error ? err.message : 'Failed to create status' 104 + setError(message) 105 + 106 + // If we have a previous context, roll back to it 107 + if (context) { 108 + if (context.previousStatuses) { 109 + queryClient.setQueryData(['statuses'], context.previousStatuses) 110 + } else { 111 + queryClient.invalidateQueries({ queryKey: ['statuses'] }) 112 + } 113 + 114 + if (context.previousUser) { 115 + queryClient.setQueryData(['currentUser'], context.previousUser) 116 + } else { 117 + queryClient.invalidateQueries({ queryKey: ['currentUser'] }) 118 + } 119 + } else { 120 + // Otherwise refresh all the data 121 + queryClient.invalidateQueries({ queryKey: ['statuses'] }) 122 + queryClient.invalidateQueries({ queryKey: ['currentUser'] }) 123 + } 124 + }, 125 + }) 126 + 127 + const handleSubmitStatus = (emoji: string) => { 128 + if (mutation.isPending) return 129 + 130 + setError(null) 131 + mutation.mutate(emoji) 132 + } 133 + 134 + return ( 135 + <div className="bg-white rounded-lg p-4 mb-6 shadow-sm"> 136 + <h2 className="text-xl font-semibold mb-4">How are you feeling?</h2> 137 + {(error || mutation.error) && ( 138 + <div className="text-red-500 mb-4 p-2 bg-red-50 rounded-md"> 139 + {error || 140 + (mutation.error instanceof Error 141 + ? mutation.error.message 142 + : 'Failed to create status')} 143 + </div> 144 + )} 145 + 146 + <div className="flex flex-wrap gap-3 justify-center"> 147 + {STATUS_OPTIONS.map((emoji) => { 148 + const isSelected = mutation.variables === emoji && mutation.isPending 149 + const isCurrentStatus = currentUserStatus === emoji 150 + 151 + return ( 152 + <button 153 + key={emoji} 154 + onClick={() => handleSubmitStatus(emoji)} 155 + disabled={mutation.isPending} 156 + className={` 157 + p-2 rounded-md 158 + text-2xl w-11 h-11 leading-none 159 + flex items-center justify-center 160 + transition-all duration-200 161 + ${isSelected ? 'opacity-60' : 'opacity-100'} 162 + ${!isSelected ? 'hover:bg-gray-100 hover:scale-110' : ''} 163 + ${isCurrentStatus ? 'bg-blue-50 ring-1 ring-blue-200' : ''} 164 + active:scale-95 165 + focus:outline-none focus:ring-2 focus:ring-blue-300 166 + `} 167 + title={isCurrentStatus ? 'Your current status' : undefined} 168 + > 169 + {emoji} 170 + </button> 171 + ) 172 + })} 173 + </div> 174 + </div> 175 + ) 176 + } 177 + 178 + export default StatusForm
+105
packages/client/src/components/StatusList.tsx
··· 1 + import { useQuery } from '@tanstack/react-query' 2 + 3 + import api from '#/services/api' 4 + 5 + const StatusList = () => { 6 + // Use React Query to fetch and cache statuses 7 + const { data, isLoading, isError, error } = useQuery({ 8 + queryKey: ['statuses'], 9 + queryFn: async () => { 10 + const data = await api.getStatuses() 11 + return data 12 + }, 13 + placeholderData: (previousData) => previousData, // Use previous data while refetching 14 + }) 15 + 16 + // Destructure data 17 + const statuses = data?.statuses || [] 18 + 19 + if (isLoading && !data) { 20 + return ( 21 + <div className="py-4 text-center text-gray-500">Loading statuses...</div> 22 + ) 23 + } 24 + 25 + if (isError) { 26 + return ( 27 + <div className="py-4 text-red-500"> 28 + {(error as Error)?.message || 'Failed to load statuses'} 29 + </div> 30 + ) 31 + } 32 + 33 + if (statuses.length === 0) { 34 + return ( 35 + <div className="py-4 text-center text-gray-500">No statuses yet.</div> 36 + ) 37 + } 38 + 39 + // Helper to format dates 40 + const formatDate = (dateString: string) => { 41 + const date = new Date(dateString) 42 + const today = new Date() 43 + const isToday = 44 + date.getDate() === today.getDate() && 45 + date.getMonth() === today.getMonth() && 46 + date.getFullYear() === today.getFullYear() 47 + 48 + if (isToday) { 49 + return 'today' 50 + } else { 51 + return date.toLocaleDateString(undefined, { 52 + year: 'numeric', 53 + month: 'long', 54 + day: 'numeric', 55 + }) 56 + } 57 + } 58 + 59 + return ( 60 + <div className="px-4"> 61 + <div className="relative"> 62 + <div className="absolute left-[20.5px] top-[22.5px] bottom-[22.5px] w-0.5 bg-gray-200"></div> 63 + {statuses.map((status) => { 64 + const handle = 65 + status.profile.handle || status.profile.did.substring(0, 15) + '...' 66 + const formattedDate = formatDate(status.createdAt) 67 + const isToday = formattedDate === 'today' 68 + 69 + return ( 70 + <div 71 + key={status.uri} 72 + className="relative flex items-center gap-5 py-4" 73 + > 74 + <div className="relative z-10 rounded-full bg-white border border-gray-200 h-[45px] w-[45px] flex items-center justify-center shadow-sm"> 75 + <div className="text-2xl">{status.status}</div> 76 + </div> 77 + <div className="flex-1"> 78 + <div className="text-gray-600 text-base"> 79 + <span className="font-medium text-gray-700 hover:underline"> 80 + @{handle} 81 + </span>{' '} 82 + {isToday ? ( 83 + <span> 84 + is feeling{' '} 85 + <span className="font-semibold">{status.status}</span>{' '} 86 + today 87 + </span> 88 + ) : ( 89 + <span> 90 + was feeling{' '} 91 + <span className="font-semibold">{status.status}</span> on{' '} 92 + {formattedDate} 93 + </span> 94 + )} 95 + </div> 96 + </div> 97 + </div> 98 + ) 99 + })} 100 + </div> 101 + </div> 102 + ) 103 + } 104 + 105 + export default StatusList
+130
packages/client/src/hooks/useAuth.tsx
··· 1 + import { createContext, ReactNode, useContext, useState } from 'react' 2 + import { useQuery, useQueryClient } from '@tanstack/react-query' 3 + 4 + import api, { User } from '#/services/api' 5 + 6 + interface AuthContextType { 7 + user: User | null 8 + loading: boolean 9 + error: string | null 10 + login: (handle: string) => Promise<{ redirectUrl: string }> 11 + logout: () => Promise<void> 12 + } 13 + 14 + const AuthContext = createContext<AuthContextType | undefined>(undefined) 15 + 16 + export function AuthProvider({ children }: { children: ReactNode }) { 17 + const [error, setError] = useState<string | null>(null) 18 + const queryClient = useQueryClient() 19 + 20 + // Use React Query to fetch and manage user data 21 + const { 22 + data: user, 23 + isLoading: loading, 24 + error: queryError, 25 + } = useQuery({ 26 + queryKey: ['currentUser'], 27 + queryFn: async () => { 28 + // Check for error parameter in URL (from OAuth redirect) 29 + const urlParams = new URLSearchParams(window.location.search) 30 + const errorParam = urlParams.get('error') 31 + 32 + if (errorParam) { 33 + setError('Authentication failed. Please try again.') 34 + 35 + // Remove the error parameter from the URL 36 + const newUrl = window.location.pathname 37 + window.history.replaceState({}, document.title, newUrl) 38 + return null 39 + } 40 + 41 + try { 42 + const userData = await api.getCurrentUser() 43 + 44 + // Clean up URL if needed 45 + if (window.location.search && userData) { 46 + window.history.replaceState( 47 + {}, 48 + document.title, 49 + window.location.pathname, 50 + ) 51 + } 52 + 53 + return userData 54 + } catch (apiErr) { 55 + console.error('🚫 API error during auth check:', apiErr) 56 + 57 + // If it's a network error, provide a more helpful message 58 + if ( 59 + apiErr instanceof TypeError && 60 + apiErr.message.includes('Failed to fetch') 61 + ) { 62 + throw new Error( 63 + 'Cannot connect to API server. Please check your network connection or server status.', 64 + ) 65 + } 66 + 67 + throw apiErr 68 + } 69 + }, 70 + retry: false, 71 + staleTime: 5 * 60 * 1000, // 5 minutes 72 + }) 73 + 74 + const login = async (handle: string) => { 75 + setError(null) 76 + 77 + try { 78 + const result = await api.login(handle) 79 + return result 80 + } catch (err) { 81 + const message = err instanceof Error ? err.message : 'Login failed' 82 + setError(message) 83 + throw err 84 + } 85 + } 86 + 87 + const logout = async () => { 88 + try { 89 + await api.logout() 90 + // Reset the user data in React Query cache 91 + queryClient.setQueryData(['currentUser'], null) 92 + // Invalidate any user-dependent queries 93 + queryClient.invalidateQueries({ queryKey: ['statuses'] }) 94 + } catch (err) { 95 + const message = err instanceof Error ? err.message : 'Logout failed' 96 + setError(message) 97 + throw err 98 + } 99 + } 100 + 101 + // Combine state error with query error 102 + const combinedError = 103 + error || (queryError instanceof Error ? queryError.message : null) 104 + 105 + return ( 106 + <AuthContext.Provider 107 + value={{ 108 + user: user || null, 109 + loading, 110 + error: combinedError, 111 + login, 112 + logout, 113 + }} 114 + > 115 + {children} 116 + </AuthContext.Provider> 117 + ) 118 + } 119 + 120 + export function useAuth() { 121 + const context = useContext(AuthContext) 122 + 123 + if (context === undefined) { 124 + throw new Error('useAuth must be used within an AuthProvider') 125 + } 126 + 127 + return context 128 + } 129 + 130 + export default useAuth
+11
packages/client/src/index.css
··· 1 + @import 'tailwindcss'; 2 + 3 + @keyframes fadeOut { 4 + 0% { opacity: 1; } 5 + 75% { opacity: 1; } /* Hold full opacity for most of the animation */ 6 + 100% { opacity: 0; } 7 + } 8 + 9 + .status-message-fade { 10 + animation: fadeOut 2s forwards; 11 + }
+20
packages/client/src/main.tsx
··· 1 + import React from 'react' 2 + import ReactDOM from 'react-dom/client' 3 + import { BrowserRouter } from 'react-router-dom' 4 + import { QueryClient, QueryClientProvider } from '@tanstack/react-query' 5 + 6 + import App from '#/App' 7 + 8 + import '#/index.css' 9 + 10 + const queryClient = new QueryClient() 11 + 12 + ReactDOM.createRoot(document.getElementById('root')!).render( 13 + <React.StrictMode> 14 + <QueryClientProvider client={queryClient}> 15 + <BrowserRouter> 16 + <App /> 17 + </BrowserRouter> 18 + </QueryClientProvider> 19 + </React.StrictMode>, 20 + )
+55
packages/client/src/pages/HomePage.tsx
··· 1 + import Header from '#/components/Header' 2 + import StatusForm from '#/components/StatusForm' 3 + import StatusList from '#/components/StatusList' 4 + import { useAuth } from '#/hooks/useAuth' 5 + 6 + const HomePage = () => { 7 + const { user, loading, error } = useAuth() 8 + 9 + if (loading) { 10 + return ( 11 + <div className="flex justify-center items-center py-16"> 12 + <div className="text-center p-6"> 13 + <h2 className="text-2xl font-semibold mb-2 text-gray-800"> 14 + Loading Statusphere... 15 + </h2> 16 + <p className="text-gray-600">Setting up your experience</p> 17 + </div> 18 + </div> 19 + ) 20 + } 21 + 22 + if (error) { 23 + return ( 24 + <div className="flex justify-center items-center py-16"> 25 + <div className="text-center p-6 max-w-md"> 26 + <h2 className="text-2xl font-semibold mb-2 text-gray-800">Error</h2> 27 + <p className="text-red-500 mb-4">{error}</p> 28 + <a 29 + href="/login" 30 + className="inline-block px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors" 31 + > 32 + Try logging in again 33 + </a> 34 + </div> 35 + </div> 36 + ) 37 + } 38 + 39 + return ( 40 + <div className="flex flex-col gap-8 pb-12"> 41 + <Header /> 42 + 43 + {user && <StatusForm />} 44 + 45 + <div> 46 + <h2 className="text-xl font-semibold mb-4 text-gray-800"> 47 + Recent Statuses 48 + </h2> 49 + <StatusList /> 50 + </div> 51 + </div> 52 + ) 53 + } 54 + 55 + export default HomePage
+83
packages/client/src/pages/LoginPage.tsx
··· 1 + import { useState } from 'react' 2 + import { Link } from 'react-router-dom' 3 + 4 + import Header from '#/components/Header' 5 + import { useAuth } from '#/hooks/useAuth' 6 + 7 + const LoginPage = () => { 8 + const [handle, setHandle] = useState('') 9 + const [error, setError] = useState<string | null>(null) 10 + const { login, loading } = useAuth() 11 + 12 + const handleSubmit = async (e: React.FormEvent) => { 13 + e.preventDefault() 14 + 15 + if (!handle.trim()) { 16 + setError('Handle cannot be empty') 17 + return 18 + } 19 + 20 + try { 21 + const { redirectUrl } = await login(handle) 22 + // Redirect to ATProto OAuth flow 23 + window.location.href = redirectUrl 24 + } catch (err) { 25 + const message = err instanceof Error ? err.message : 'Login failed' 26 + setError(message) 27 + } 28 + } 29 + 30 + return ( 31 + <div className="flex flex-col gap-8"> 32 + <Header /> 33 + 34 + <div className="bg-white rounded-lg p-6 shadow-sm max-w-md mx-auto w-full"> 35 + <h2 className="text-xl font-semibold mb-4">Login with your handle</h2> 36 + 37 + {error && ( 38 + <div className="text-red-500 mb-4 p-2 bg-red-50 rounded-md"> 39 + {error} 40 + </div> 41 + )} 42 + 43 + <form onSubmit={handleSubmit}> 44 + <div className="mb-4"> 45 + <label htmlFor="handle" className="block mb-2 text-gray-700"> 46 + Enter your Bluesky handle: 47 + </label> 48 + <input 49 + id="handle" 50 + type="text" 51 + value={handle} 52 + onChange={(e) => setHandle(e.target.value)} 53 + placeholder="example.bsky.social" 54 + disabled={loading} 55 + className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-300" 56 + /> 57 + </div> 58 + 59 + <button 60 + type="submit" 61 + disabled={loading} 62 + className={`w-full px-4 py-2 rounded-md bg-blue-500 text-white font-medium hover:bg-blue-600 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-300 ${ 63 + loading ? 'opacity-70 cursor-not-allowed' : '' 64 + }`} 65 + > 66 + {loading ? 'Logging in...' : 'Login'} 67 + </button> 68 + </form> 69 + 70 + <div className="mt-4 text-center"> 71 + <Link 72 + to="/" 73 + className="text-blue-500 hover:text-blue-700 transition-colors" 74 + > 75 + Cancel 76 + </Link> 77 + </div> 78 + </div> 79 + </div> 80 + ) 81 + } 82 + 83 + export default LoginPage
+99
packages/client/src/pages/OAuthCallbackPage.tsx
··· 1 + import { useEffect, useState } from 'react' 2 + import { useNavigate } from 'react-router-dom' 3 + 4 + import { api } from '../services/api' 5 + 6 + const OAuthCallbackPage = () => { 7 + const [error, setError] = useState<string | null>(null) 8 + const [message, setMessage] = useState<string>('Completing authentication...') 9 + const navigate = useNavigate() 10 + 11 + useEffect(() => { 12 + console.log('OAuth callback page reached') 13 + setMessage('OAuth callback page reached. Checking authentication...') 14 + 15 + const checkAuth = async () => { 16 + try { 17 + // Check if there's an error in the URL 18 + const params = new URLSearchParams(window.location.search) 19 + if (params.get('error')) { 20 + console.error('Auth error detected in URL params') 21 + setError('Authentication failed') 22 + return 23 + } 24 + 25 + // Give cookies a moment to be processed 26 + await new Promise((resolve) => setTimeout(resolve, 500)) 27 + setMessage("Checking if we're authenticated...") 28 + 29 + // Check if we're authenticated by fetching current user 30 + try { 31 + console.log('Checking current user') 32 + console.log( 33 + 'Cookies being sent:', 34 + document.cookie 35 + .split(';') 36 + .map((c) => c.trim()) 37 + .join(', '), 38 + ) 39 + 40 + const user = await api.getCurrentUser() 41 + console.log('Current user check result:', user) 42 + 43 + if (user) { 44 + console.log('Successfully authenticated', user) 45 + setMessage('Authentication successful! Redirecting...') 46 + // Redirect to home after a short delay 47 + setTimeout(() => { 48 + navigate('/') 49 + }, 1000) 50 + } else { 51 + console.error('Auth check returned no user') 52 + setError('Authentication session not found') 53 + } 54 + } catch (apiErr) { 55 + console.error('API error during auth check:', apiErr) 56 + setError('Failed to verify authentication') 57 + } 58 + } catch (err) { 59 + console.error('General error in OAuth callback:', err) 60 + setError('Failed to complete authentication') 61 + } 62 + } 63 + 64 + checkAuth() 65 + }, [navigate]) 66 + 67 + return ( 68 + <div className="flex items-center justify-center py-16"> 69 + <div className="bg-white rounded-lg shadow-sm p-8 max-w-md w-full text-center"> 70 + {error ? ( 71 + <div> 72 + <h2 className="text-2xl font-bold text-red-500 mb-4"> 73 + Authentication Failed 74 + </h2> 75 + <p className="text-red-500 mb-6">{error}</p> 76 + <button 77 + onClick={() => navigate('/login')} 78 + className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors" 79 + > 80 + Try Again 81 + </button> 82 + </div> 83 + ) : ( 84 + <div> 85 + <h2 className="text-2xl font-bold text-gray-800 mb-4"> 86 + Authentication in Progress 87 + </h2> 88 + <div className="flex justify-center mb-4"> 89 + <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div> 90 + </div> 91 + <p className="text-gray-600">{message}</p> 92 + </div> 93 + )} 94 + </div> 95 + </div> 96 + ) 97 + } 98 + 99 + export default OAuthCallbackPage
+160
packages/client/src/services/api.ts
··· 1 + import { AppBskyActorDefs, XyzStatusphereDefs } from '@statusphere/lexicon' 2 + 3 + const API_URL = import.meta.env.VITE_API_URL || '/api' 4 + 5 + // Helper function for logging API actions 6 + function logApiCall( 7 + method: string, 8 + endpoint: string, 9 + status?: number, 10 + error?: any, 11 + ) { 12 + const statusStr = status ? `[${status}]` : '' 13 + const errorStr = error 14 + ? ` - Error: ${error.message || JSON.stringify(error)}` 15 + : '' 16 + console.log(`🔄 API ${method} ${endpoint} ${statusStr}${errorStr}`) 17 + } 18 + 19 + export interface User { 20 + did: string 21 + profile: AppBskyActorDefs.ProfileView 22 + status?: XyzStatusphereDefs.StatusView 23 + } 24 + 25 + // API service 26 + export const api = { 27 + // Get base URL 28 + getBaseUrl() { 29 + return API_URL || '' 30 + }, 31 + // Login 32 + async login(handle: string) { 33 + const url = API_URL ? `${API_URL}/login` : '/login' 34 + logApiCall('POST', url) 35 + 36 + const response = await fetch(url, { 37 + method: 'POST', 38 + headers: { 39 + 'Content-Type': 'application/json', 40 + }, 41 + credentials: 'include', 42 + body: JSON.stringify({ handle }), 43 + }) 44 + 45 + if (!response.ok) { 46 + const error = await response.json() 47 + throw new Error(error.error || 'Login failed') 48 + } 49 + 50 + return response.json() 51 + }, 52 + 53 + // Logout 54 + async logout() { 55 + const url = API_URL ? `${API_URL}/logout` : '/logout' 56 + logApiCall('POST', url) 57 + const response = await fetch(url, { 58 + method: 'POST', 59 + credentials: 'include', 60 + }) 61 + 62 + if (!response.ok) { 63 + throw new Error('Logout failed') 64 + } 65 + 66 + return response.json() 67 + }, 68 + 69 + // Get current user 70 + async getCurrentUser() { 71 + const url = API_URL ? `${API_URL}/user` : '/user' 72 + logApiCall('GET', url) 73 + try { 74 + console.log('📞 Fetching user from:', url, 'with credentials included') 75 + // Debug output - what headers are we sending? 76 + const headers = { 77 + Accept: 'application/json', 78 + } 79 + console.log('📨 Request headers:', headers) 80 + 81 + const response = await fetch(url, { 82 + credentials: 'include', // This is crucial for sending cookies 83 + headers, 84 + cache: 'no-cache', // Don't cache this request 85 + }) 86 + 87 + logApiCall('GET', '/user', response.status) 88 + 89 + if (!response.ok) { 90 + if (response.status === 401) { 91 + return null 92 + } 93 + 94 + // Try to get error details 95 + let errorText = '' 96 + try { 97 + const errorData = await response.text() 98 + errorText = errorData 99 + } catch (e) { 100 + // Ignore error reading error 101 + } 102 + 103 + throw new Error( 104 + `Failed to get user: ${response.status} ${response.statusText} ${errorText}`, 105 + ) 106 + } 107 + 108 + return response.json() 109 + } catch (error) { 110 + logApiCall('GET', '/user', undefined, error) 111 + if ( 112 + error instanceof TypeError && 113 + error.message.includes('Failed to fetch') 114 + ) { 115 + console.error('Network error - Unable to connect to API server') 116 + } 117 + throw error 118 + } 119 + }, 120 + 121 + // Get statuses 122 + async getStatuses() { 123 + const url = API_URL ? `${API_URL}/statuses` : '/statuses' 124 + logApiCall('GET', url) 125 + const response = await fetch(url, { 126 + credentials: 'include', 127 + }) 128 + 129 + if (!response.ok) { 130 + throw new Error('Failed to get statuses') 131 + } 132 + 133 + return response.json() as Promise<{ 134 + statuses: XyzStatusphereDefs.StatusView[] 135 + }> 136 + }, 137 + 138 + // Create status 139 + async createStatus(status: string) { 140 + const url = API_URL ? `${API_URL}/status` : '/status' 141 + logApiCall('POST', url) 142 + const response = await fetch(url, { 143 + method: 'POST', 144 + headers: { 145 + 'Content-Type': 'application/json', 146 + }, 147 + credentials: 'include', 148 + body: JSON.stringify({ status }), 149 + }) 150 + 151 + if (!response.ok) { 152 + const error = await response.json() 153 + throw new Error(error.error || 'Failed to create status') 154 + } 155 + 156 + return response.json() 157 + }, 158 + } 159 + 160 + export default api
+9
packages/client/src/vite-env.d.ts
··· 1 + /// <reference types="vite/client" /> 2 + 3 + interface ImportMetaEnv { 4 + readonly VITE_API_URL: string 5 + } 6 + 7 + interface ImportMeta { 8 + readonly env: ImportMetaEnv 9 + }
+25
packages/client/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2020", 4 + "useDefineForClassFields": true, 5 + "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 + "module": "ESNext", 7 + "skipLibCheck": true, 8 + "moduleResolution": "bundler", 9 + "allowImportingTsExtensions": true, 10 + "resolveJsonModule": true, 11 + "isolatedModules": true, 12 + "noEmit": true, 13 + "jsx": "react-jsx", 14 + "strict": true, 15 + "noUnusedLocals": true, 16 + "noUnusedParameters": true, 17 + "noFallthroughCasesInSwitch": true, 18 + "baseUrl": ".", 19 + "paths": { 20 + "#/*": ["./src/*"] 21 + } 22 + }, 23 + "include": ["src"], 24 + "references": [{ "path": "./tsconfig.node.json" }] 25 + }
+10
packages/client/tsconfig.node.json
··· 1 + { 2 + "compilerOptions": { 3 + "composite": true, 4 + "skipLibCheck": true, 5 + "module": "ESNext", 6 + "moduleResolution": "bundler", 7 + "allowSyntheticDefaultImports": true 8 + }, 9 + "include": ["vite.config.ts"] 10 + }
+26
packages/client/vite.config.ts
··· 1 + import path from 'path' 2 + import tailwindcss from '@tailwindcss/vite' 3 + import react from '@vitejs/plugin-react' 4 + import { defineConfig } from 'vite' 5 + 6 + // https://vitejs.dev/config/ 7 + export default defineConfig({ 8 + plugins: [react(), tailwindcss()], 9 + server: { 10 + port: 3000, 11 + // allow ngrok 12 + allowedHosts: true, 13 + proxy: { 14 + '/api': { 15 + target: 'http://localhost:3001', 16 + changeOrigin: true, 17 + rewrite: (path) => path.replace(/^\/api/, ''), 18 + }, 19 + }, 20 + }, 21 + resolve: { 22 + alias: { 23 + '#': path.resolve(__dirname, './src'), 24 + }, 25 + }, 26 + })
+43
packages/lexicon/package.json
··· 1 + { 2 + "name": "@statusphere/lexicon", 3 + "version": "0.0.1", 4 + "description": "Generated API client for Statusphere lexicons", 5 + "author": "", 6 + "license": "MIT", 7 + "main": "dist/index.js", 8 + "types": "dist/index.d.ts", 9 + "private": true, 10 + "scripts": { 11 + "build": "pnpm run lexgen && tsup", 12 + "dev": "tsup --watch", 13 + "clean": "rimraf dist", 14 + "typecheck": "tsc --noEmit", 15 + "lexgen": "lex gen-api ./src ../../lexicons/xyz/statusphere/* ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/* --yes" 16 + }, 17 + "dependencies": { 18 + "@atproto/api": "^0.14.7", 19 + "@atproto/lexicon": "^0.4.7", 20 + "@atproto/syntax": "^0.3.3", 21 + "@atproto/xrpc": "^0.6.9", 22 + "multiformats": "^13.3.2" 23 + }, 24 + "devDependencies": { 25 + "@atproto/lex-cli": "^0.6.1", 26 + "@types/node": "^22.13.8", 27 + "rimraf": "^6.0.1", 28 + "tsup": "^8.4.0", 29 + "typescript": "^5.8.2" 30 + }, 31 + "tsup": { 32 + "entry": [ 33 + "src/index.ts" 34 + ], 35 + "format": [ 36 + "cjs", 37 + "esm" 38 + ], 39 + "dts": true, 40 + "sourcemap": true, 41 + "clean": true 42 + } 43 + }
+381
packages/lexicon/src/index.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { FetchHandler, FetchHandlerOptions, XrpcClient } from '@atproto/xrpc' 5 + import { CID } from 'multiformats/cid' 6 + 7 + import { schemas } from './lexicons.js' 8 + import * as AppBskyActorDefs from './types/app/bsky/actor/defs.js' 9 + import * as AppBskyActorProfile from './types/app/bsky/actor/profile.js' 10 + import * as ComAtprotoLabelDefs from './types/com/atproto/label/defs.js' 11 + import * as ComAtprotoRepoApplyWrites from './types/com/atproto/repo/applyWrites.js' 12 + import * as ComAtprotoRepoCreateRecord from './types/com/atproto/repo/createRecord.js' 13 + import * as ComAtprotoRepoDefs from './types/com/atproto/repo/defs.js' 14 + import * as ComAtprotoRepoDeleteRecord from './types/com/atproto/repo/deleteRecord.js' 15 + import * as ComAtprotoRepoDescribeRepo from './types/com/atproto/repo/describeRepo.js' 16 + import * as ComAtprotoRepoGetRecord from './types/com/atproto/repo/getRecord.js' 17 + import * as ComAtprotoRepoImportRepo from './types/com/atproto/repo/importRepo.js' 18 + import * as ComAtprotoRepoListMissingBlobs from './types/com/atproto/repo/listMissingBlobs.js' 19 + import * as ComAtprotoRepoListRecords from './types/com/atproto/repo/listRecords.js' 20 + import * as ComAtprotoRepoPutRecord from './types/com/atproto/repo/putRecord.js' 21 + import * as ComAtprotoRepoStrongRef from './types/com/atproto/repo/strongRef.js' 22 + import * as ComAtprotoRepoUploadBlob from './types/com/atproto/repo/uploadBlob.js' 23 + import * as XyzStatusphereDefs from './types/xyz/statusphere/defs.js' 24 + import * as XyzStatusphereStatus from './types/xyz/statusphere/status.js' 25 + import { OmitKey, Un$Typed } from './util.js' 26 + 27 + export * as XyzStatusphereDefs from './types/xyz/statusphere/defs.js' 28 + export * as XyzStatusphereStatus from './types/xyz/statusphere/status.js' 29 + export * as ComAtprotoLabelDefs from './types/com/atproto/label/defs.js' 30 + export * as ComAtprotoRepoApplyWrites from './types/com/atproto/repo/applyWrites.js' 31 + export * as ComAtprotoRepoCreateRecord from './types/com/atproto/repo/createRecord.js' 32 + export * as ComAtprotoRepoDefs from './types/com/atproto/repo/defs.js' 33 + export * as ComAtprotoRepoDeleteRecord from './types/com/atproto/repo/deleteRecord.js' 34 + export * as ComAtprotoRepoDescribeRepo from './types/com/atproto/repo/describeRepo.js' 35 + export * as ComAtprotoRepoGetRecord from './types/com/atproto/repo/getRecord.js' 36 + export * as ComAtprotoRepoImportRepo from './types/com/atproto/repo/importRepo.js' 37 + export * as ComAtprotoRepoListMissingBlobs from './types/com/atproto/repo/listMissingBlobs.js' 38 + export * as ComAtprotoRepoListRecords from './types/com/atproto/repo/listRecords.js' 39 + export * as ComAtprotoRepoPutRecord from './types/com/atproto/repo/putRecord.js' 40 + export * as ComAtprotoRepoStrongRef from './types/com/atproto/repo/strongRef.js' 41 + export * as ComAtprotoRepoUploadBlob from './types/com/atproto/repo/uploadBlob.js' 42 + export * as AppBskyActorDefs from './types/app/bsky/actor/defs.js' 43 + export * as AppBskyActorProfile from './types/app/bsky/actor/profile.js' 44 + 45 + export class AtpBaseClient extends XrpcClient { 46 + xyz: XyzNS 47 + com: ComNS 48 + app: AppNS 49 + 50 + constructor(options: FetchHandler | FetchHandlerOptions) { 51 + super(options, schemas) 52 + this.xyz = new XyzNS(this) 53 + this.com = new ComNS(this) 54 + this.app = new AppNS(this) 55 + } 56 + 57 + /** @deprecated use `this` instead */ 58 + get xrpc(): XrpcClient { 59 + return this 60 + } 61 + } 62 + 63 + export class XyzNS { 64 + _client: XrpcClient 65 + statusphere: XyzStatusphereNS 66 + 67 + constructor(client: XrpcClient) { 68 + this._client = client 69 + this.statusphere = new XyzStatusphereNS(client) 70 + } 71 + } 72 + 73 + export class XyzStatusphereNS { 74 + _client: XrpcClient 75 + status: StatusRecord 76 + 77 + constructor(client: XrpcClient) { 78 + this._client = client 79 + this.status = new StatusRecord(client) 80 + } 81 + } 82 + 83 + export class StatusRecord { 84 + _client: XrpcClient 85 + 86 + constructor(client: XrpcClient) { 87 + this._client = client 88 + } 89 + 90 + async list( 91 + params: OmitKey<ComAtprotoRepoListRecords.QueryParams, 'collection'>, 92 + ): Promise<{ 93 + cursor?: string 94 + records: { uri: string; value: XyzStatusphereStatus.Record }[] 95 + }> { 96 + const res = await this._client.call('com.atproto.repo.listRecords', { 97 + collection: 'xyz.statusphere.status', 98 + ...params, 99 + }) 100 + return res.data 101 + } 102 + 103 + async get( 104 + params: OmitKey<ComAtprotoRepoGetRecord.QueryParams, 'collection'>, 105 + ): Promise<{ uri: string; cid: string; value: XyzStatusphereStatus.Record }> { 106 + const res = await this._client.call('com.atproto.repo.getRecord', { 107 + collection: 'xyz.statusphere.status', 108 + ...params, 109 + }) 110 + return res.data 111 + } 112 + 113 + async create( 114 + params: OmitKey< 115 + ComAtprotoRepoCreateRecord.InputSchema, 116 + 'collection' | 'record' 117 + >, 118 + record: Un$Typed<XyzStatusphereStatus.Record>, 119 + headers?: Record<string, string>, 120 + ): Promise<{ uri: string; cid: string }> { 121 + const collection = 'xyz.statusphere.status' 122 + const res = await this._client.call( 123 + 'com.atproto.repo.createRecord', 124 + undefined, 125 + { collection, ...params, record: { ...record, $type: collection } }, 126 + { encoding: 'application/json', headers }, 127 + ) 128 + return res.data 129 + } 130 + 131 + async delete( 132 + params: OmitKey<ComAtprotoRepoDeleteRecord.InputSchema, 'collection'>, 133 + headers?: Record<string, string>, 134 + ): Promise<void> { 135 + await this._client.call( 136 + 'com.atproto.repo.deleteRecord', 137 + undefined, 138 + { collection: 'xyz.statusphere.status', ...params }, 139 + { headers }, 140 + ) 141 + } 142 + } 143 + 144 + export class ComNS { 145 + _client: XrpcClient 146 + atproto: ComAtprotoNS 147 + 148 + constructor(client: XrpcClient) { 149 + this._client = client 150 + this.atproto = new ComAtprotoNS(client) 151 + } 152 + } 153 + 154 + export class ComAtprotoNS { 155 + _client: XrpcClient 156 + repo: ComAtprotoRepoNS 157 + 158 + constructor(client: XrpcClient) { 159 + this._client = client 160 + this.repo = new ComAtprotoRepoNS(client) 161 + } 162 + } 163 + 164 + export class ComAtprotoRepoNS { 165 + _client: XrpcClient 166 + 167 + constructor(client: XrpcClient) { 168 + this._client = client 169 + } 170 + 171 + applyWrites( 172 + data?: ComAtprotoRepoApplyWrites.InputSchema, 173 + opts?: ComAtprotoRepoApplyWrites.CallOptions, 174 + ): Promise<ComAtprotoRepoApplyWrites.Response> { 175 + return this._client 176 + .call('com.atproto.repo.applyWrites', opts?.qp, data, opts) 177 + .catch((e) => { 178 + throw ComAtprotoRepoApplyWrites.toKnownErr(e) 179 + }) 180 + } 181 + 182 + createRecord( 183 + data?: ComAtprotoRepoCreateRecord.InputSchema, 184 + opts?: ComAtprotoRepoCreateRecord.CallOptions, 185 + ): Promise<ComAtprotoRepoCreateRecord.Response> { 186 + return this._client 187 + .call('com.atproto.repo.createRecord', opts?.qp, data, opts) 188 + .catch((e) => { 189 + throw ComAtprotoRepoCreateRecord.toKnownErr(e) 190 + }) 191 + } 192 + 193 + deleteRecord( 194 + data?: ComAtprotoRepoDeleteRecord.InputSchema, 195 + opts?: ComAtprotoRepoDeleteRecord.CallOptions, 196 + ): Promise<ComAtprotoRepoDeleteRecord.Response> { 197 + return this._client 198 + .call('com.atproto.repo.deleteRecord', opts?.qp, data, opts) 199 + .catch((e) => { 200 + throw ComAtprotoRepoDeleteRecord.toKnownErr(e) 201 + }) 202 + } 203 + 204 + describeRepo( 205 + params?: ComAtprotoRepoDescribeRepo.QueryParams, 206 + opts?: ComAtprotoRepoDescribeRepo.CallOptions, 207 + ): Promise<ComAtprotoRepoDescribeRepo.Response> { 208 + return this._client.call( 209 + 'com.atproto.repo.describeRepo', 210 + params, 211 + undefined, 212 + opts, 213 + ) 214 + } 215 + 216 + getRecord( 217 + params?: ComAtprotoRepoGetRecord.QueryParams, 218 + opts?: ComAtprotoRepoGetRecord.CallOptions, 219 + ): Promise<ComAtprotoRepoGetRecord.Response> { 220 + return this._client 221 + .call('com.atproto.repo.getRecord', params, undefined, opts) 222 + .catch((e) => { 223 + throw ComAtprotoRepoGetRecord.toKnownErr(e) 224 + }) 225 + } 226 + 227 + importRepo( 228 + data?: ComAtprotoRepoImportRepo.InputSchema, 229 + opts?: ComAtprotoRepoImportRepo.CallOptions, 230 + ): Promise<ComAtprotoRepoImportRepo.Response> { 231 + return this._client.call( 232 + 'com.atproto.repo.importRepo', 233 + opts?.qp, 234 + data, 235 + opts, 236 + ) 237 + } 238 + 239 + listMissingBlobs( 240 + params?: ComAtprotoRepoListMissingBlobs.QueryParams, 241 + opts?: ComAtprotoRepoListMissingBlobs.CallOptions, 242 + ): Promise<ComAtprotoRepoListMissingBlobs.Response> { 243 + return this._client.call( 244 + 'com.atproto.repo.listMissingBlobs', 245 + params, 246 + undefined, 247 + opts, 248 + ) 249 + } 250 + 251 + listRecords( 252 + params?: ComAtprotoRepoListRecords.QueryParams, 253 + opts?: ComAtprotoRepoListRecords.CallOptions, 254 + ): Promise<ComAtprotoRepoListRecords.Response> { 255 + return this._client.call( 256 + 'com.atproto.repo.listRecords', 257 + params, 258 + undefined, 259 + opts, 260 + ) 261 + } 262 + 263 + putRecord( 264 + data?: ComAtprotoRepoPutRecord.InputSchema, 265 + opts?: ComAtprotoRepoPutRecord.CallOptions, 266 + ): Promise<ComAtprotoRepoPutRecord.Response> { 267 + return this._client 268 + .call('com.atproto.repo.putRecord', opts?.qp, data, opts) 269 + .catch((e) => { 270 + throw ComAtprotoRepoPutRecord.toKnownErr(e) 271 + }) 272 + } 273 + 274 + uploadBlob( 275 + data?: ComAtprotoRepoUploadBlob.InputSchema, 276 + opts?: ComAtprotoRepoUploadBlob.CallOptions, 277 + ): Promise<ComAtprotoRepoUploadBlob.Response> { 278 + return this._client.call( 279 + 'com.atproto.repo.uploadBlob', 280 + opts?.qp, 281 + data, 282 + opts, 283 + ) 284 + } 285 + } 286 + 287 + export class AppNS { 288 + _client: XrpcClient 289 + bsky: AppBskyNS 290 + 291 + constructor(client: XrpcClient) { 292 + this._client = client 293 + this.bsky = new AppBskyNS(client) 294 + } 295 + } 296 + 297 + export class AppBskyNS { 298 + _client: XrpcClient 299 + actor: AppBskyActorNS 300 + 301 + constructor(client: XrpcClient) { 302 + this._client = client 303 + this.actor = new AppBskyActorNS(client) 304 + } 305 + } 306 + 307 + export class AppBskyActorNS { 308 + _client: XrpcClient 309 + profile: ProfileRecord 310 + 311 + constructor(client: XrpcClient) { 312 + this._client = client 313 + this.profile = new ProfileRecord(client) 314 + } 315 + } 316 + 317 + export class ProfileRecord { 318 + _client: XrpcClient 319 + 320 + constructor(client: XrpcClient) { 321 + this._client = client 322 + } 323 + 324 + async list( 325 + params: OmitKey<ComAtprotoRepoListRecords.QueryParams, 'collection'>, 326 + ): Promise<{ 327 + cursor?: string 328 + records: { uri: string; value: AppBskyActorProfile.Record }[] 329 + }> { 330 + const res = await this._client.call('com.atproto.repo.listRecords', { 331 + collection: 'app.bsky.actor.profile', 332 + ...params, 333 + }) 334 + return res.data 335 + } 336 + 337 + async get( 338 + params: OmitKey<ComAtprotoRepoGetRecord.QueryParams, 'collection'>, 339 + ): Promise<{ uri: string; cid: string; value: AppBskyActorProfile.Record }> { 340 + const res = await this._client.call('com.atproto.repo.getRecord', { 341 + collection: 'app.bsky.actor.profile', 342 + ...params, 343 + }) 344 + return res.data 345 + } 346 + 347 + async create( 348 + params: OmitKey< 349 + ComAtprotoRepoCreateRecord.InputSchema, 350 + 'collection' | 'record' 351 + >, 352 + record: Un$Typed<AppBskyActorProfile.Record>, 353 + headers?: Record<string, string>, 354 + ): Promise<{ uri: string; cid: string }> { 355 + const collection = 'app.bsky.actor.profile' 356 + const res = await this._client.call( 357 + 'com.atproto.repo.createRecord', 358 + undefined, 359 + { 360 + collection, 361 + rkey: 'self', 362 + ...params, 363 + record: { ...record, $type: collection }, 364 + }, 365 + { encoding: 'application/json', headers }, 366 + ) 367 + return res.data 368 + } 369 + 370 + async delete( 371 + params: OmitKey<ComAtprotoRepoDeleteRecord.InputSchema, 'collection'>, 372 + headers?: Record<string, string>, 373 + ): Promise<void> { 374 + await this._client.call( 375 + 'com.atproto.repo.deleteRecord', 376 + undefined, 377 + { collection: 'app.bsky.actor.profile', ...params }, 378 + { headers }, 379 + ) 380 + } 381 + }
+1188
packages/lexicon/src/lexicons.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { 5 + LexiconDoc, 6 + Lexicons, 7 + ValidationError, 8 + ValidationResult, 9 + } from '@atproto/lexicon' 10 + 11 + import { $Typed, is$typed, maybe$typed } from './util.js' 12 + 13 + export const schemaDict = { 14 + XyzStatusphereDefs: { 15 + lexicon: 1, 16 + id: 'xyz.statusphere.defs', 17 + defs: { 18 + statusView: { 19 + type: 'object', 20 + required: ['uri', 'status', 'profile', 'createdAt'], 21 + properties: { 22 + uri: { 23 + type: 'string', 24 + format: 'at-uri', 25 + }, 26 + status: { 27 + type: 'string', 28 + minLength: 1, 29 + maxGraphemes: 1, 30 + maxLength: 32, 31 + }, 32 + createdAt: { 33 + type: 'string', 34 + format: 'datetime', 35 + }, 36 + profile: { 37 + type: 'ref', 38 + ref: 'lex:xyz.statusphere.defs#profileView', 39 + }, 40 + }, 41 + }, 42 + profileView: { 43 + type: 'object', 44 + required: ['did', 'handle'], 45 + properties: { 46 + did: { 47 + type: 'string', 48 + format: 'did', 49 + }, 50 + handle: { 51 + type: 'string', 52 + format: 'handle', 53 + }, 54 + }, 55 + }, 56 + }, 57 + }, 58 + XyzStatusphereStatus: { 59 + lexicon: 1, 60 + id: 'xyz.statusphere.status', 61 + defs: { 62 + main: { 63 + type: 'record', 64 + key: 'tid', 65 + record: { 66 + type: 'object', 67 + required: ['status', 'createdAt'], 68 + properties: { 69 + status: { 70 + type: 'string', 71 + minLength: 1, 72 + maxGraphemes: 1, 73 + maxLength: 32, 74 + }, 75 + createdAt: { 76 + type: 'string', 77 + format: 'datetime', 78 + }, 79 + }, 80 + }, 81 + }, 82 + }, 83 + }, 84 + ComAtprotoLabelDefs: { 85 + lexicon: 1, 86 + id: 'com.atproto.label.defs', 87 + defs: { 88 + label: { 89 + type: 'object', 90 + description: 91 + 'Metadata tag on an atproto resource (eg, repo or record).', 92 + required: ['src', 'uri', 'val', 'cts'], 93 + properties: { 94 + ver: { 95 + type: 'integer', 96 + description: 'The AT Protocol version of the label object.', 97 + }, 98 + src: { 99 + type: 'string', 100 + format: 'did', 101 + description: 'DID of the actor who created this label.', 102 + }, 103 + uri: { 104 + type: 'string', 105 + format: 'uri', 106 + description: 107 + 'AT URI of the record, repository (account), or other resource that this label applies to.', 108 + }, 109 + cid: { 110 + type: 'string', 111 + format: 'cid', 112 + description: 113 + "Optionally, CID specifying the specific version of 'uri' resource this label applies to.", 114 + }, 115 + val: { 116 + type: 'string', 117 + maxLength: 128, 118 + description: 119 + 'The short string name of the value or type of this label.', 120 + }, 121 + neg: { 122 + type: 'boolean', 123 + description: 124 + 'If true, this is a negation label, overwriting a previous label.', 125 + }, 126 + cts: { 127 + type: 'string', 128 + format: 'datetime', 129 + description: 'Timestamp when this label was created.', 130 + }, 131 + exp: { 132 + type: 'string', 133 + format: 'datetime', 134 + description: 135 + 'Timestamp at which this label expires (no longer applies).', 136 + }, 137 + sig: { 138 + type: 'bytes', 139 + description: 'Signature of dag-cbor encoded label.', 140 + }, 141 + }, 142 + }, 143 + selfLabels: { 144 + type: 'object', 145 + description: 146 + 'Metadata tags on an atproto record, published by the author within the record.', 147 + required: ['values'], 148 + properties: { 149 + values: { 150 + type: 'array', 151 + items: { 152 + type: 'ref', 153 + ref: 'lex:com.atproto.label.defs#selfLabel', 154 + }, 155 + maxLength: 10, 156 + }, 157 + }, 158 + }, 159 + selfLabel: { 160 + type: 'object', 161 + description: 162 + 'Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.', 163 + required: ['val'], 164 + properties: { 165 + val: { 166 + type: 'string', 167 + maxLength: 128, 168 + description: 169 + 'The short string name of the value or type of this label.', 170 + }, 171 + }, 172 + }, 173 + labelValueDefinition: { 174 + type: 'object', 175 + description: 176 + 'Declares a label value and its expected interpretations and behaviors.', 177 + required: ['identifier', 'severity', 'blurs', 'locales'], 178 + properties: { 179 + identifier: { 180 + type: 'string', 181 + description: 182 + "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", 183 + maxLength: 100, 184 + maxGraphemes: 100, 185 + }, 186 + severity: { 187 + type: 'string', 188 + description: 189 + "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", 190 + knownValues: ['inform', 'alert', 'none'], 191 + }, 192 + blurs: { 193 + type: 'string', 194 + description: 195 + "What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.", 196 + knownValues: ['content', 'media', 'none'], 197 + }, 198 + defaultSetting: { 199 + type: 'string', 200 + description: 'The default setting for this label.', 201 + knownValues: ['ignore', 'warn', 'hide'], 202 + default: 'warn', 203 + }, 204 + adultOnly: { 205 + type: 'boolean', 206 + description: 207 + 'Does the user need to have adult content enabled in order to configure this label?', 208 + }, 209 + locales: { 210 + type: 'array', 211 + items: { 212 + type: 'ref', 213 + ref: 'lex:com.atproto.label.defs#labelValueDefinitionStrings', 214 + }, 215 + }, 216 + }, 217 + }, 218 + labelValueDefinitionStrings: { 219 + type: 'object', 220 + description: 221 + 'Strings which describe the label in the UI, localized into a specific language.', 222 + required: ['lang', 'name', 'description'], 223 + properties: { 224 + lang: { 225 + type: 'string', 226 + description: 227 + 'The code of the language these strings are written in.', 228 + format: 'language', 229 + }, 230 + name: { 231 + type: 'string', 232 + description: 'A short human-readable name for the label.', 233 + maxGraphemes: 64, 234 + maxLength: 640, 235 + }, 236 + description: { 237 + type: 'string', 238 + description: 239 + 'A longer description of what the label means and why it might be applied.', 240 + maxGraphemes: 10000, 241 + maxLength: 100000, 242 + }, 243 + }, 244 + }, 245 + labelValue: { 246 + type: 'string', 247 + knownValues: [ 248 + '!hide', 249 + '!no-promote', 250 + '!warn', 251 + '!no-unauthenticated', 252 + 'dmca-violation', 253 + 'doxxing', 254 + 'porn', 255 + 'sexual', 256 + 'nudity', 257 + 'nsfl', 258 + 'gore', 259 + ], 260 + }, 261 + }, 262 + }, 263 + ComAtprotoRepoApplyWrites: { 264 + lexicon: 1, 265 + id: 'com.atproto.repo.applyWrites', 266 + defs: { 267 + main: { 268 + type: 'procedure', 269 + description: 270 + 'Apply a batch transaction of repository creates, updates, and deletes. Requires auth, implemented by PDS.', 271 + input: { 272 + encoding: 'application/json', 273 + schema: { 274 + type: 'object', 275 + required: ['repo', 'writes'], 276 + properties: { 277 + repo: { 278 + type: 'string', 279 + format: 'at-identifier', 280 + description: 281 + 'The handle or DID of the repo (aka, current account).', 282 + }, 283 + validate: { 284 + type: 'boolean', 285 + description: 286 + "Can be set to 'false' to skip Lexicon schema validation of record data across all operations, 'true' to require it, or leave unset to validate only for known Lexicons.", 287 + }, 288 + writes: { 289 + type: 'array', 290 + items: { 291 + type: 'union', 292 + refs: [ 293 + 'lex:com.atproto.repo.applyWrites#create', 294 + 'lex:com.atproto.repo.applyWrites#update', 295 + 'lex:com.atproto.repo.applyWrites#delete', 296 + ], 297 + closed: true, 298 + }, 299 + }, 300 + swapCommit: { 301 + type: 'string', 302 + description: 303 + 'If provided, the entire operation will fail if the current repo commit CID does not match this value. Used to prevent conflicting repo mutations.', 304 + format: 'cid', 305 + }, 306 + }, 307 + }, 308 + }, 309 + output: { 310 + encoding: 'application/json', 311 + schema: { 312 + type: 'object', 313 + required: [], 314 + properties: { 315 + commit: { 316 + type: 'ref', 317 + ref: 'lex:com.atproto.repo.defs#commitMeta', 318 + }, 319 + results: { 320 + type: 'array', 321 + items: { 322 + type: 'union', 323 + refs: [ 324 + 'lex:com.atproto.repo.applyWrites#createResult', 325 + 'lex:com.atproto.repo.applyWrites#updateResult', 326 + 'lex:com.atproto.repo.applyWrites#deleteResult', 327 + ], 328 + closed: true, 329 + }, 330 + }, 331 + }, 332 + }, 333 + }, 334 + errors: [ 335 + { 336 + name: 'InvalidSwap', 337 + description: 338 + "Indicates that the 'swapCommit' parameter did not match current commit.", 339 + }, 340 + ], 341 + }, 342 + create: { 343 + type: 'object', 344 + description: 'Operation which creates a new record.', 345 + required: ['collection', 'value'], 346 + properties: { 347 + collection: { 348 + type: 'string', 349 + format: 'nsid', 350 + }, 351 + rkey: { 352 + type: 'string', 353 + maxLength: 512, 354 + format: 'record-key', 355 + description: 356 + 'NOTE: maxLength is redundant with record-key format. Keeping it temporarily to ensure backwards compatibility.', 357 + }, 358 + value: { 359 + type: 'unknown', 360 + }, 361 + }, 362 + }, 363 + update: { 364 + type: 'object', 365 + description: 'Operation which updates an existing record.', 366 + required: ['collection', 'rkey', 'value'], 367 + properties: { 368 + collection: { 369 + type: 'string', 370 + format: 'nsid', 371 + }, 372 + rkey: { 373 + type: 'string', 374 + format: 'record-key', 375 + }, 376 + value: { 377 + type: 'unknown', 378 + }, 379 + }, 380 + }, 381 + delete: { 382 + type: 'object', 383 + description: 'Operation which deletes an existing record.', 384 + required: ['collection', 'rkey'], 385 + properties: { 386 + collection: { 387 + type: 'string', 388 + format: 'nsid', 389 + }, 390 + rkey: { 391 + type: 'string', 392 + format: 'record-key', 393 + }, 394 + }, 395 + }, 396 + createResult: { 397 + type: 'object', 398 + required: ['uri', 'cid'], 399 + properties: { 400 + uri: { 401 + type: 'string', 402 + format: 'at-uri', 403 + }, 404 + cid: { 405 + type: 'string', 406 + format: 'cid', 407 + }, 408 + validationStatus: { 409 + type: 'string', 410 + knownValues: ['valid', 'unknown'], 411 + }, 412 + }, 413 + }, 414 + updateResult: { 415 + type: 'object', 416 + required: ['uri', 'cid'], 417 + properties: { 418 + uri: { 419 + type: 'string', 420 + format: 'at-uri', 421 + }, 422 + cid: { 423 + type: 'string', 424 + format: 'cid', 425 + }, 426 + validationStatus: { 427 + type: 'string', 428 + knownValues: ['valid', 'unknown'], 429 + }, 430 + }, 431 + }, 432 + deleteResult: { 433 + type: 'object', 434 + required: [], 435 + properties: {}, 436 + }, 437 + }, 438 + }, 439 + ComAtprotoRepoCreateRecord: { 440 + lexicon: 1, 441 + id: 'com.atproto.repo.createRecord', 442 + defs: { 443 + main: { 444 + type: 'procedure', 445 + description: 446 + 'Create a single new repository record. Requires auth, implemented by PDS.', 447 + input: { 448 + encoding: 'application/json', 449 + schema: { 450 + type: 'object', 451 + required: ['repo', 'collection', 'record'], 452 + properties: { 453 + repo: { 454 + type: 'string', 455 + format: 'at-identifier', 456 + description: 457 + 'The handle or DID of the repo (aka, current account).', 458 + }, 459 + collection: { 460 + type: 'string', 461 + format: 'nsid', 462 + description: 'The NSID of the record collection.', 463 + }, 464 + rkey: { 465 + type: 'string', 466 + format: 'record-key', 467 + description: 'The Record Key.', 468 + maxLength: 512, 469 + }, 470 + validate: { 471 + type: 'boolean', 472 + description: 473 + "Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons.", 474 + }, 475 + record: { 476 + type: 'unknown', 477 + description: 'The record itself. Must contain a $type field.', 478 + }, 479 + swapCommit: { 480 + type: 'string', 481 + format: 'cid', 482 + description: 483 + 'Compare and swap with the previous commit by CID.', 484 + }, 485 + }, 486 + }, 487 + }, 488 + output: { 489 + encoding: 'application/json', 490 + schema: { 491 + type: 'object', 492 + required: ['uri', 'cid'], 493 + properties: { 494 + uri: { 495 + type: 'string', 496 + format: 'at-uri', 497 + }, 498 + cid: { 499 + type: 'string', 500 + format: 'cid', 501 + }, 502 + commit: { 503 + type: 'ref', 504 + ref: 'lex:com.atproto.repo.defs#commitMeta', 505 + }, 506 + validationStatus: { 507 + type: 'string', 508 + knownValues: ['valid', 'unknown'], 509 + }, 510 + }, 511 + }, 512 + }, 513 + errors: [ 514 + { 515 + name: 'InvalidSwap', 516 + description: 517 + "Indicates that 'swapCommit' didn't match current repo commit.", 518 + }, 519 + ], 520 + }, 521 + }, 522 + }, 523 + ComAtprotoRepoDefs: { 524 + lexicon: 1, 525 + id: 'com.atproto.repo.defs', 526 + defs: { 527 + commitMeta: { 528 + type: 'object', 529 + required: ['cid', 'rev'], 530 + properties: { 531 + cid: { 532 + type: 'string', 533 + format: 'cid', 534 + }, 535 + rev: { 536 + type: 'string', 537 + format: 'tid', 538 + }, 539 + }, 540 + }, 541 + }, 542 + }, 543 + ComAtprotoRepoDeleteRecord: { 544 + lexicon: 1, 545 + id: 'com.atproto.repo.deleteRecord', 546 + defs: { 547 + main: { 548 + type: 'procedure', 549 + description: 550 + "Delete a repository record, or ensure it doesn't exist. Requires auth, implemented by PDS.", 551 + input: { 552 + encoding: 'application/json', 553 + schema: { 554 + type: 'object', 555 + required: ['repo', 'collection', 'rkey'], 556 + properties: { 557 + repo: { 558 + type: 'string', 559 + format: 'at-identifier', 560 + description: 561 + 'The handle or DID of the repo (aka, current account).', 562 + }, 563 + collection: { 564 + type: 'string', 565 + format: 'nsid', 566 + description: 'The NSID of the record collection.', 567 + }, 568 + rkey: { 569 + type: 'string', 570 + format: 'record-key', 571 + description: 'The Record Key.', 572 + }, 573 + swapRecord: { 574 + type: 'string', 575 + format: 'cid', 576 + description: 577 + 'Compare and swap with the previous record by CID.', 578 + }, 579 + swapCommit: { 580 + type: 'string', 581 + format: 'cid', 582 + description: 583 + 'Compare and swap with the previous commit by CID.', 584 + }, 585 + }, 586 + }, 587 + }, 588 + output: { 589 + encoding: 'application/json', 590 + schema: { 591 + type: 'object', 592 + properties: { 593 + commit: { 594 + type: 'ref', 595 + ref: 'lex:com.atproto.repo.defs#commitMeta', 596 + }, 597 + }, 598 + }, 599 + }, 600 + errors: [ 601 + { 602 + name: 'InvalidSwap', 603 + }, 604 + ], 605 + }, 606 + }, 607 + }, 608 + ComAtprotoRepoDescribeRepo: { 609 + lexicon: 1, 610 + id: 'com.atproto.repo.describeRepo', 611 + defs: { 612 + main: { 613 + type: 'query', 614 + description: 615 + 'Get information about an account and repository, including the list of collections. Does not require auth.', 616 + parameters: { 617 + type: 'params', 618 + required: ['repo'], 619 + properties: { 620 + repo: { 621 + type: 'string', 622 + format: 'at-identifier', 623 + description: 'The handle or DID of the repo.', 624 + }, 625 + }, 626 + }, 627 + output: { 628 + encoding: 'application/json', 629 + schema: { 630 + type: 'object', 631 + required: [ 632 + 'handle', 633 + 'did', 634 + 'didDoc', 635 + 'collections', 636 + 'handleIsCorrect', 637 + ], 638 + properties: { 639 + handle: { 640 + type: 'string', 641 + format: 'handle', 642 + }, 643 + did: { 644 + type: 'string', 645 + format: 'did', 646 + }, 647 + didDoc: { 648 + type: 'unknown', 649 + description: 'The complete DID document for this account.', 650 + }, 651 + collections: { 652 + type: 'array', 653 + description: 654 + 'List of all the collections (NSIDs) for which this repo contains at least one record.', 655 + items: { 656 + type: 'string', 657 + format: 'nsid', 658 + }, 659 + }, 660 + handleIsCorrect: { 661 + type: 'boolean', 662 + description: 663 + 'Indicates if handle is currently valid (resolves bi-directionally)', 664 + }, 665 + }, 666 + }, 667 + }, 668 + }, 669 + }, 670 + }, 671 + ComAtprotoRepoGetRecord: { 672 + lexicon: 1, 673 + id: 'com.atproto.repo.getRecord', 674 + defs: { 675 + main: { 676 + type: 'query', 677 + description: 678 + 'Get a single record from a repository. Does not require auth.', 679 + parameters: { 680 + type: 'params', 681 + required: ['repo', 'collection', 'rkey'], 682 + properties: { 683 + repo: { 684 + type: 'string', 685 + format: 'at-identifier', 686 + description: 'The handle or DID of the repo.', 687 + }, 688 + collection: { 689 + type: 'string', 690 + format: 'nsid', 691 + description: 'The NSID of the record collection.', 692 + }, 693 + rkey: { 694 + type: 'string', 695 + description: 'The Record Key.', 696 + format: 'record-key', 697 + }, 698 + cid: { 699 + type: 'string', 700 + format: 'cid', 701 + description: 702 + 'The CID of the version of the record. If not specified, then return the most recent version.', 703 + }, 704 + }, 705 + }, 706 + output: { 707 + encoding: 'application/json', 708 + schema: { 709 + type: 'object', 710 + required: ['uri', 'value'], 711 + properties: { 712 + uri: { 713 + type: 'string', 714 + format: 'at-uri', 715 + }, 716 + cid: { 717 + type: 'string', 718 + format: 'cid', 719 + }, 720 + value: { 721 + type: 'unknown', 722 + }, 723 + }, 724 + }, 725 + }, 726 + errors: [ 727 + { 728 + name: 'RecordNotFound', 729 + }, 730 + ], 731 + }, 732 + }, 733 + }, 734 + ComAtprotoRepoImportRepo: { 735 + lexicon: 1, 736 + id: 'com.atproto.repo.importRepo', 737 + defs: { 738 + main: { 739 + type: 'procedure', 740 + description: 741 + 'Import a repo in the form of a CAR file. Requires Content-Length HTTP header to be set.', 742 + input: { 743 + encoding: 'application/vnd.ipld.car', 744 + }, 745 + }, 746 + }, 747 + }, 748 + ComAtprotoRepoListMissingBlobs: { 749 + lexicon: 1, 750 + id: 'com.atproto.repo.listMissingBlobs', 751 + defs: { 752 + main: { 753 + type: 'query', 754 + description: 755 + 'Returns a list of missing blobs for the requesting account. Intended to be used in the account migration flow.', 756 + parameters: { 757 + type: 'params', 758 + properties: { 759 + limit: { 760 + type: 'integer', 761 + minimum: 1, 762 + maximum: 1000, 763 + default: 500, 764 + }, 765 + cursor: { 766 + type: 'string', 767 + }, 768 + }, 769 + }, 770 + output: { 771 + encoding: 'application/json', 772 + schema: { 773 + type: 'object', 774 + required: ['blobs'], 775 + properties: { 776 + cursor: { 777 + type: 'string', 778 + }, 779 + blobs: { 780 + type: 'array', 781 + items: { 782 + type: 'ref', 783 + ref: 'lex:com.atproto.repo.listMissingBlobs#recordBlob', 784 + }, 785 + }, 786 + }, 787 + }, 788 + }, 789 + }, 790 + recordBlob: { 791 + type: 'object', 792 + required: ['cid', 'recordUri'], 793 + properties: { 794 + cid: { 795 + type: 'string', 796 + format: 'cid', 797 + }, 798 + recordUri: { 799 + type: 'string', 800 + format: 'at-uri', 801 + }, 802 + }, 803 + }, 804 + }, 805 + }, 806 + ComAtprotoRepoListRecords: { 807 + lexicon: 1, 808 + id: 'com.atproto.repo.listRecords', 809 + defs: { 810 + main: { 811 + type: 'query', 812 + description: 813 + 'List a range of records in a repository, matching a specific collection. Does not require auth.', 814 + parameters: { 815 + type: 'params', 816 + required: ['repo', 'collection'], 817 + properties: { 818 + repo: { 819 + type: 'string', 820 + format: 'at-identifier', 821 + description: 'The handle or DID of the repo.', 822 + }, 823 + collection: { 824 + type: 'string', 825 + format: 'nsid', 826 + description: 'The NSID of the record type.', 827 + }, 828 + limit: { 829 + type: 'integer', 830 + minimum: 1, 831 + maximum: 100, 832 + default: 50, 833 + description: 'The number of records to return.', 834 + }, 835 + cursor: { 836 + type: 'string', 837 + }, 838 + rkeyStart: { 839 + type: 'string', 840 + description: 841 + 'DEPRECATED: The lowest sort-ordered rkey to start from (exclusive)', 842 + }, 843 + rkeyEnd: { 844 + type: 'string', 845 + description: 846 + 'DEPRECATED: The highest sort-ordered rkey to stop at (exclusive)', 847 + }, 848 + reverse: { 849 + type: 'boolean', 850 + description: 'Flag to reverse the order of the returned records.', 851 + }, 852 + }, 853 + }, 854 + output: { 855 + encoding: 'application/json', 856 + schema: { 857 + type: 'object', 858 + required: ['records'], 859 + properties: { 860 + cursor: { 861 + type: 'string', 862 + }, 863 + records: { 864 + type: 'array', 865 + items: { 866 + type: 'ref', 867 + ref: 'lex:com.atproto.repo.listRecords#record', 868 + }, 869 + }, 870 + }, 871 + }, 872 + }, 873 + }, 874 + record: { 875 + type: 'object', 876 + required: ['uri', 'cid', 'value'], 877 + properties: { 878 + uri: { 879 + type: 'string', 880 + format: 'at-uri', 881 + }, 882 + cid: { 883 + type: 'string', 884 + format: 'cid', 885 + }, 886 + value: { 887 + type: 'unknown', 888 + }, 889 + }, 890 + }, 891 + }, 892 + }, 893 + ComAtprotoRepoPutRecord: { 894 + lexicon: 1, 895 + id: 'com.atproto.repo.putRecord', 896 + defs: { 897 + main: { 898 + type: 'procedure', 899 + description: 900 + 'Write a repository record, creating or updating it as needed. Requires auth, implemented by PDS.', 901 + input: { 902 + encoding: 'application/json', 903 + schema: { 904 + type: 'object', 905 + required: ['repo', 'collection', 'rkey', 'record'], 906 + nullable: ['swapRecord'], 907 + properties: { 908 + repo: { 909 + type: 'string', 910 + format: 'at-identifier', 911 + description: 912 + 'The handle or DID of the repo (aka, current account).', 913 + }, 914 + collection: { 915 + type: 'string', 916 + format: 'nsid', 917 + description: 'The NSID of the record collection.', 918 + }, 919 + rkey: { 920 + type: 'string', 921 + format: 'record-key', 922 + description: 'The Record Key.', 923 + maxLength: 512, 924 + }, 925 + validate: { 926 + type: 'boolean', 927 + description: 928 + "Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons.", 929 + }, 930 + record: { 931 + type: 'unknown', 932 + description: 'The record to write.', 933 + }, 934 + swapRecord: { 935 + type: 'string', 936 + format: 'cid', 937 + description: 938 + 'Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation', 939 + }, 940 + swapCommit: { 941 + type: 'string', 942 + format: 'cid', 943 + description: 944 + 'Compare and swap with the previous commit by CID.', 945 + }, 946 + }, 947 + }, 948 + }, 949 + output: { 950 + encoding: 'application/json', 951 + schema: { 952 + type: 'object', 953 + required: ['uri', 'cid'], 954 + properties: { 955 + uri: { 956 + type: 'string', 957 + format: 'at-uri', 958 + }, 959 + cid: { 960 + type: 'string', 961 + format: 'cid', 962 + }, 963 + commit: { 964 + type: 'ref', 965 + ref: 'lex:com.atproto.repo.defs#commitMeta', 966 + }, 967 + validationStatus: { 968 + type: 'string', 969 + knownValues: ['valid', 'unknown'], 970 + }, 971 + }, 972 + }, 973 + }, 974 + errors: [ 975 + { 976 + name: 'InvalidSwap', 977 + }, 978 + ], 979 + }, 980 + }, 981 + }, 982 + ComAtprotoRepoStrongRef: { 983 + lexicon: 1, 984 + id: 'com.atproto.repo.strongRef', 985 + description: 'A URI with a content-hash fingerprint.', 986 + defs: { 987 + main: { 988 + type: 'object', 989 + required: ['uri', 'cid'], 990 + properties: { 991 + uri: { 992 + type: 'string', 993 + format: 'at-uri', 994 + }, 995 + cid: { 996 + type: 'string', 997 + format: 'cid', 998 + }, 999 + }, 1000 + }, 1001 + }, 1002 + }, 1003 + ComAtprotoRepoUploadBlob: { 1004 + lexicon: 1, 1005 + id: 'com.atproto.repo.uploadBlob', 1006 + defs: { 1007 + main: { 1008 + type: 'procedure', 1009 + description: 1010 + 'Upload a new blob, to be referenced from a repository record. The blob will be deleted if it is not referenced within a time window (eg, minutes). Blob restrictions (mimetype, size, etc) are enforced when the reference is created. Requires auth, implemented by PDS.', 1011 + input: { 1012 + encoding: '*/*', 1013 + }, 1014 + output: { 1015 + encoding: 'application/json', 1016 + schema: { 1017 + type: 'object', 1018 + required: ['blob'], 1019 + properties: { 1020 + blob: { 1021 + type: 'blob', 1022 + }, 1023 + }, 1024 + }, 1025 + }, 1026 + }, 1027 + }, 1028 + }, 1029 + AppBskyActorDefs: { 1030 + lexicon: 1, 1031 + id: 'app.bsky.actor.defs', 1032 + defs: { 1033 + profileView: { 1034 + type: 'object', 1035 + required: ['did', 'handle'], 1036 + properties: { 1037 + did: { 1038 + type: 'string', 1039 + format: 'did', 1040 + }, 1041 + handle: { 1042 + type: 'string', 1043 + format: 'handle', 1044 + }, 1045 + displayName: { 1046 + type: 'string', 1047 + maxGraphemes: 64, 1048 + maxLength: 640, 1049 + }, 1050 + description: { 1051 + type: 'string', 1052 + maxGraphemes: 256, 1053 + maxLength: 2560, 1054 + }, 1055 + avatar: { 1056 + type: 'string', 1057 + format: 'uri', 1058 + }, 1059 + indexedAt: { 1060 + type: 'string', 1061 + format: 'datetime', 1062 + }, 1063 + createdAt: { 1064 + type: 'string', 1065 + format: 'datetime', 1066 + }, 1067 + labels: { 1068 + type: 'array', 1069 + items: { 1070 + type: 'ref', 1071 + ref: 'lex:com.atproto.label.defs#label', 1072 + }, 1073 + }, 1074 + }, 1075 + }, 1076 + }, 1077 + }, 1078 + AppBskyActorProfile: { 1079 + lexicon: 1, 1080 + id: 'app.bsky.actor.profile', 1081 + defs: { 1082 + main: { 1083 + type: 'record', 1084 + description: 'A declaration of a Bluesky account profile.', 1085 + key: 'literal:self', 1086 + record: { 1087 + type: 'object', 1088 + properties: { 1089 + displayName: { 1090 + type: 'string', 1091 + maxGraphemes: 64, 1092 + maxLength: 640, 1093 + }, 1094 + description: { 1095 + type: 'string', 1096 + description: 'Free-form profile description text.', 1097 + maxGraphemes: 256, 1098 + maxLength: 2560, 1099 + }, 1100 + avatar: { 1101 + type: 'blob', 1102 + description: 1103 + "Small image to be displayed next to posts from account. AKA, 'profile picture'", 1104 + accept: ['image/png', 'image/jpeg'], 1105 + maxSize: 1000000, 1106 + }, 1107 + banner: { 1108 + type: 'blob', 1109 + description: 1110 + 'Larger horizontal image to display behind profile view.', 1111 + accept: ['image/png', 'image/jpeg'], 1112 + maxSize: 1000000, 1113 + }, 1114 + labels: { 1115 + type: 'union', 1116 + description: 1117 + 'Self-label values, specific to the Bluesky application, on the overall account.', 1118 + refs: ['lex:com.atproto.label.defs#selfLabels'], 1119 + }, 1120 + joinedViaStarterPack: { 1121 + type: 'ref', 1122 + ref: 'lex:com.atproto.repo.strongRef', 1123 + }, 1124 + pinnedPost: { 1125 + type: 'ref', 1126 + ref: 'lex:com.atproto.repo.strongRef', 1127 + }, 1128 + createdAt: { 1129 + type: 'string', 1130 + format: 'datetime', 1131 + }, 1132 + }, 1133 + }, 1134 + }, 1135 + }, 1136 + }, 1137 + } as const satisfies Record<string, LexiconDoc> 1138 + 1139 + export const schemas = Object.values(schemaDict) satisfies LexiconDoc[] 1140 + export const lexicons: Lexicons = new Lexicons(schemas) 1141 + 1142 + export function validate<T extends { $type: string }>( 1143 + v: unknown, 1144 + id: string, 1145 + hash: string, 1146 + requiredType: true, 1147 + ): ValidationResult<T> 1148 + export function validate<T extends { $type?: string }>( 1149 + v: unknown, 1150 + id: string, 1151 + hash: string, 1152 + requiredType?: false, 1153 + ): ValidationResult<T> 1154 + export function validate( 1155 + v: unknown, 1156 + id: string, 1157 + hash: string, 1158 + requiredType?: boolean, 1159 + ): ValidationResult { 1160 + return (requiredType ? is$typed : maybe$typed)(v, id, hash) 1161 + ? lexicons.validate(`${id}#${hash}`, v) 1162 + : { 1163 + success: false, 1164 + error: new ValidationError( 1165 + `Must be an object with "${hash === 'main' ? id : `${id}#${hash}`}" $type property`, 1166 + ), 1167 + } 1168 + } 1169 + 1170 + export const ids = { 1171 + XyzStatusphereDefs: 'xyz.statusphere.defs', 1172 + XyzStatusphereStatus: 'xyz.statusphere.status', 1173 + ComAtprotoLabelDefs: 'com.atproto.label.defs', 1174 + ComAtprotoRepoApplyWrites: 'com.atproto.repo.applyWrites', 1175 + ComAtprotoRepoCreateRecord: 'com.atproto.repo.createRecord', 1176 + ComAtprotoRepoDefs: 'com.atproto.repo.defs', 1177 + ComAtprotoRepoDeleteRecord: 'com.atproto.repo.deleteRecord', 1178 + ComAtprotoRepoDescribeRepo: 'com.atproto.repo.describeRepo', 1179 + ComAtprotoRepoGetRecord: 'com.atproto.repo.getRecord', 1180 + ComAtprotoRepoImportRepo: 'com.atproto.repo.importRepo', 1181 + ComAtprotoRepoListMissingBlobs: 'com.atproto.repo.listMissingBlobs', 1182 + ComAtprotoRepoListRecords: 'com.atproto.repo.listRecords', 1183 + ComAtprotoRepoPutRecord: 'com.atproto.repo.putRecord', 1184 + ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef', 1185 + ComAtprotoRepoUploadBlob: 'com.atproto.repo.uploadBlob', 1186 + AppBskyActorDefs: 'app.bsky.actor.defs', 1187 + AppBskyActorProfile: 'app.bsky.actor.profile', 1188 + } as const
+35
packages/lexicon/src/types/app/bsky/actor/defs.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { BlobRef, ValidationResult } from '@atproto/lexicon' 5 + import { CID } from 'multiformats/cid' 6 + 7 + import { validate as _validate } from '../../../../lexicons' 8 + import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' 9 + import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs.js' 10 + 11 + const is$typed = _is$typed, 12 + validate = _validate 13 + const id = 'app.bsky.actor.defs' 14 + 15 + export interface ProfileView { 16 + $type?: 'app.bsky.actor.defs#profileView' 17 + did: string 18 + handle: string 19 + displayName?: string 20 + description?: string 21 + avatar?: string 22 + indexedAt?: string 23 + createdAt?: string 24 + labels?: ComAtprotoLabelDefs.Label[] 25 + } 26 + 27 + const hashProfileView = 'profileView' 28 + 29 + export function isProfileView<V>(v: V) { 30 + return is$typed(v, id, hashProfileView) 31 + } 32 + 33 + export function validateProfileView<V>(v: V) { 34 + return validate<ProfileView & V>(v, id, hashProfileView) 35 + }
+164
packages/lexicon/src/types/com/atproto/repo/applyWrites.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { BlobRef, ValidationResult } from '@atproto/lexicon' 5 + import { HeadersMap, XRPCError } from '@atproto/xrpc' 6 + import { CID } from 'multiformats/cid' 7 + 8 + import { validate as _validate } from '../../../../lexicons' 9 + import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' 10 + import type * as ComAtprotoRepoDefs from './defs.js' 11 + 12 + const is$typed = _is$typed, 13 + validate = _validate 14 + const id = 'com.atproto.repo.applyWrites' 15 + 16 + export interface QueryParams {} 17 + 18 + export interface InputSchema { 19 + /** The handle or DID of the repo (aka, current account). */ 20 + repo: string 21 + /** Can be set to 'false' to skip Lexicon schema validation of record data across all operations, 'true' to require it, or leave unset to validate only for known Lexicons. */ 22 + validate?: boolean 23 + writes: ($Typed<Create> | $Typed<Update> | $Typed<Delete>)[] 24 + /** If provided, the entire operation will fail if the current repo commit CID does not match this value. Used to prevent conflicting repo mutations. */ 25 + swapCommit?: string 26 + } 27 + 28 + export interface OutputSchema { 29 + commit?: ComAtprotoRepoDefs.CommitMeta 30 + results?: ( 31 + | $Typed<CreateResult> 32 + | $Typed<UpdateResult> 33 + | $Typed<DeleteResult> 34 + )[] 35 + } 36 + 37 + export interface CallOptions { 38 + signal?: AbortSignal 39 + headers?: HeadersMap 40 + qp?: QueryParams 41 + encoding?: 'application/json' 42 + } 43 + 44 + export interface Response { 45 + success: boolean 46 + headers: HeadersMap 47 + data: OutputSchema 48 + } 49 + 50 + export class InvalidSwapError extends XRPCError { 51 + constructor(src: XRPCError) { 52 + super(src.status, src.error, src.message, src.headers, { cause: src }) 53 + } 54 + } 55 + 56 + export function toKnownErr(e: any) { 57 + if (e instanceof XRPCError) { 58 + if (e.error === 'InvalidSwap') return new InvalidSwapError(e) 59 + } 60 + 61 + return e 62 + } 63 + 64 + /** Operation which creates a new record. */ 65 + export interface Create { 66 + $type?: 'com.atproto.repo.applyWrites#create' 67 + collection: string 68 + /** NOTE: maxLength is redundant with record-key format. Keeping it temporarily to ensure backwards compatibility. */ 69 + rkey?: string 70 + value: { [_ in string]: unknown } 71 + } 72 + 73 + const hashCreate = 'create' 74 + 75 + export function isCreate<V>(v: V) { 76 + return is$typed(v, id, hashCreate) 77 + } 78 + 79 + export function validateCreate<V>(v: V) { 80 + return validate<Create & V>(v, id, hashCreate) 81 + } 82 + 83 + /** Operation which updates an existing record. */ 84 + export interface Update { 85 + $type?: 'com.atproto.repo.applyWrites#update' 86 + collection: string 87 + rkey: string 88 + value: { [_ in string]: unknown } 89 + } 90 + 91 + const hashUpdate = 'update' 92 + 93 + export function isUpdate<V>(v: V) { 94 + return is$typed(v, id, hashUpdate) 95 + } 96 + 97 + export function validateUpdate<V>(v: V) { 98 + return validate<Update & V>(v, id, hashUpdate) 99 + } 100 + 101 + /** Operation which deletes an existing record. */ 102 + export interface Delete { 103 + $type?: 'com.atproto.repo.applyWrites#delete' 104 + collection: string 105 + rkey: string 106 + } 107 + 108 + const hashDelete = 'delete' 109 + 110 + export function isDelete<V>(v: V) { 111 + return is$typed(v, id, hashDelete) 112 + } 113 + 114 + export function validateDelete<V>(v: V) { 115 + return validate<Delete & V>(v, id, hashDelete) 116 + } 117 + 118 + export interface CreateResult { 119 + $type?: 'com.atproto.repo.applyWrites#createResult' 120 + uri: string 121 + cid: string 122 + validationStatus?: 'valid' | 'unknown' | (string & {}) 123 + } 124 + 125 + const hashCreateResult = 'createResult' 126 + 127 + export function isCreateResult<V>(v: V) { 128 + return is$typed(v, id, hashCreateResult) 129 + } 130 + 131 + export function validateCreateResult<V>(v: V) { 132 + return validate<CreateResult & V>(v, id, hashCreateResult) 133 + } 134 + 135 + export interface UpdateResult { 136 + $type?: 'com.atproto.repo.applyWrites#updateResult' 137 + uri: string 138 + cid: string 139 + validationStatus?: 'valid' | 'unknown' | (string & {}) 140 + } 141 + 142 + const hashUpdateResult = 'updateResult' 143 + 144 + export function isUpdateResult<V>(v: V) { 145 + return is$typed(v, id, hashUpdateResult) 146 + } 147 + 148 + export function validateUpdateResult<V>(v: V) { 149 + return validate<UpdateResult & V>(v, id, hashUpdateResult) 150 + } 151 + 152 + export interface DeleteResult { 153 + $type?: 'com.atproto.repo.applyWrites#deleteResult' 154 + } 155 + 156 + const hashDeleteResult = 'deleteResult' 157 + 158 + export function isDeleteResult<V>(v: V) { 159 + return is$typed(v, id, hashDeleteResult) 160 + } 161 + 162 + export function validateDeleteResult<V>(v: V) { 163 + return validate<DeleteResult & V>(v, id, hashDeleteResult) 164 + }
+65
packages/lexicon/src/types/com/atproto/repo/createRecord.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { BlobRef, ValidationResult } from '@atproto/lexicon' 5 + import { HeadersMap, XRPCError } from '@atproto/xrpc' 6 + import { CID } from 'multiformats/cid' 7 + 8 + import { validate as _validate } from '../../../../lexicons' 9 + import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' 10 + import type * as ComAtprotoRepoDefs from './defs.js' 11 + 12 + const is$typed = _is$typed, 13 + validate = _validate 14 + const id = 'com.atproto.repo.createRecord' 15 + 16 + export interface QueryParams {} 17 + 18 + export interface InputSchema { 19 + /** The handle or DID of the repo (aka, current account). */ 20 + repo: string 21 + /** The NSID of the record collection. */ 22 + collection: string 23 + /** The Record Key. */ 24 + rkey?: string 25 + /** Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons. */ 26 + validate?: boolean 27 + /** The record itself. Must contain a $type field. */ 28 + record: { [_ in string]: unknown } 29 + /** Compare and swap with the previous commit by CID. */ 30 + swapCommit?: string 31 + } 32 + 33 + export interface OutputSchema { 34 + uri: string 35 + cid: string 36 + commit?: ComAtprotoRepoDefs.CommitMeta 37 + validationStatus?: 'valid' | 'unknown' | (string & {}) 38 + } 39 + 40 + export interface CallOptions { 41 + signal?: AbortSignal 42 + headers?: HeadersMap 43 + qp?: QueryParams 44 + encoding?: 'application/json' 45 + } 46 + 47 + export interface Response { 48 + success: boolean 49 + headers: HeadersMap 50 + data: OutputSchema 51 + } 52 + 53 + export class InvalidSwapError extends XRPCError { 54 + constructor(src: XRPCError) { 55 + super(src.status, src.error, src.message, src.headers, { cause: src }) 56 + } 57 + } 58 + 59 + export function toKnownErr(e: any) { 60 + if (e instanceof XRPCError) { 61 + if (e.error === 'InvalidSwap') return new InvalidSwapError(e) 62 + } 63 + 64 + return e 65 + }
+28
packages/lexicon/src/types/com/atproto/repo/defs.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { BlobRef, ValidationResult } from '@atproto/lexicon' 5 + import { CID } from 'multiformats/cid' 6 + 7 + import { validate as _validate } from '../../../../lexicons' 8 + import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' 9 + 10 + const is$typed = _is$typed, 11 + validate = _validate 12 + const id = 'com.atproto.repo.defs' 13 + 14 + export interface CommitMeta { 15 + $type?: 'com.atproto.repo.defs#commitMeta' 16 + cid: string 17 + rev: string 18 + } 19 + 20 + const hashCommitMeta = 'commitMeta' 21 + 22 + export function isCommitMeta<V>(v: V) { 23 + return is$typed(v, id, hashCommitMeta) 24 + } 25 + 26 + export function validateCommitMeta<V>(v: V) { 27 + return validate<CommitMeta & V>(v, id, hashCommitMeta) 28 + }
+60
packages/lexicon/src/types/com/atproto/repo/deleteRecord.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { BlobRef, ValidationResult } from '@atproto/lexicon' 5 + import { HeadersMap, XRPCError } from '@atproto/xrpc' 6 + import { CID } from 'multiformats/cid' 7 + 8 + import { validate as _validate } from '../../../../lexicons' 9 + import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' 10 + import type * as ComAtprotoRepoDefs from './defs.js' 11 + 12 + const is$typed = _is$typed, 13 + validate = _validate 14 + const id = 'com.atproto.repo.deleteRecord' 15 + 16 + export interface QueryParams {} 17 + 18 + export interface InputSchema { 19 + /** The handle or DID of the repo (aka, current account). */ 20 + repo: string 21 + /** The NSID of the record collection. */ 22 + collection: string 23 + /** The Record Key. */ 24 + rkey: string 25 + /** Compare and swap with the previous record by CID. */ 26 + swapRecord?: string 27 + /** Compare and swap with the previous commit by CID. */ 28 + swapCommit?: string 29 + } 30 + 31 + export interface OutputSchema { 32 + commit?: ComAtprotoRepoDefs.CommitMeta 33 + } 34 + 35 + export interface CallOptions { 36 + signal?: AbortSignal 37 + headers?: HeadersMap 38 + qp?: QueryParams 39 + encoding?: 'application/json' 40 + } 41 + 42 + export interface Response { 43 + success: boolean 44 + headers: HeadersMap 45 + data: OutputSchema 46 + } 47 + 48 + export class InvalidSwapError extends XRPCError { 49 + constructor(src: XRPCError) { 50 + super(src.status, src.error, src.message, src.headers, { cause: src }) 51 + } 52 + } 53 + 54 + export function toKnownErr(e: any) { 55 + if (e instanceof XRPCError) { 56 + if (e.error === 'InvalidSwap') return new InvalidSwapError(e) 57 + } 58 + 59 + return e 60 + }
+46
packages/lexicon/src/types/com/atproto/repo/describeRepo.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { BlobRef, ValidationResult } from '@atproto/lexicon' 5 + import { HeadersMap, XRPCError } from '@atproto/xrpc' 6 + import { CID } from 'multiformats/cid' 7 + 8 + import { validate as _validate } from '../../../../lexicons' 9 + import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' 10 + 11 + const is$typed = _is$typed, 12 + validate = _validate 13 + const id = 'com.atproto.repo.describeRepo' 14 + 15 + export interface QueryParams { 16 + /** The handle or DID of the repo. */ 17 + repo: string 18 + } 19 + 20 + export type InputSchema = undefined 21 + 22 + export interface OutputSchema { 23 + handle: string 24 + did: string 25 + /** The complete DID document for this account. */ 26 + didDoc: { [_ in string]: unknown } 27 + /** List of all the collections (NSIDs) for which this repo contains at least one record. */ 28 + collections: string[] 29 + /** Indicates if handle is currently valid (resolves bi-directionally) */ 30 + handleIsCorrect: boolean 31 + } 32 + 33 + export interface CallOptions { 34 + signal?: AbortSignal 35 + headers?: HeadersMap 36 + } 37 + 38 + export interface Response { 39 + success: boolean 40 + headers: HeadersMap 41 + data: OutputSchema 42 + } 43 + 44 + export function toKnownErr(e: any) { 45 + return e 46 + }
+57
packages/lexicon/src/types/com/atproto/repo/getRecord.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { BlobRef, ValidationResult } from '@atproto/lexicon' 5 + import { HeadersMap, XRPCError } from '@atproto/xrpc' 6 + import { CID } from 'multiformats/cid' 7 + 8 + import { validate as _validate } from '../../../../lexicons' 9 + import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' 10 + 11 + const is$typed = _is$typed, 12 + validate = _validate 13 + const id = 'com.atproto.repo.getRecord' 14 + 15 + export interface QueryParams { 16 + /** The handle or DID of the repo. */ 17 + repo: string 18 + /** The NSID of the record collection. */ 19 + collection: string 20 + /** The Record Key. */ 21 + rkey: string 22 + /** The CID of the version of the record. If not specified, then return the most recent version. */ 23 + cid?: string 24 + } 25 + 26 + export type InputSchema = undefined 27 + 28 + export interface OutputSchema { 29 + uri: string 30 + cid?: string 31 + value: { [_ in string]: unknown } 32 + } 33 + 34 + export interface CallOptions { 35 + signal?: AbortSignal 36 + headers?: HeadersMap 37 + } 38 + 39 + export interface Response { 40 + success: boolean 41 + headers: HeadersMap 42 + data: OutputSchema 43 + } 44 + 45 + export class RecordNotFoundError extends XRPCError { 46 + constructor(src: XRPCError) { 47 + super(src.status, src.error, src.message, src.headers, { cause: src }) 48 + } 49 + } 50 + 51 + export function toKnownErr(e: any) { 52 + if (e instanceof XRPCError) { 53 + if (e.error === 'RecordNotFound') return new RecordNotFoundError(e) 54 + } 55 + 56 + return e 57 + }
+33
packages/lexicon/src/types/com/atproto/repo/importRepo.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { BlobRef, ValidationResult } from '@atproto/lexicon' 5 + import { HeadersMap, XRPCError } from '@atproto/xrpc' 6 + import { CID } from 'multiformats/cid' 7 + 8 + import { validate as _validate } from '../../../../lexicons' 9 + import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' 10 + 11 + const is$typed = _is$typed, 12 + validate = _validate 13 + const id = 'com.atproto.repo.importRepo' 14 + 15 + export interface QueryParams {} 16 + 17 + export type InputSchema = string | Uint8Array | Blob 18 + 19 + export interface CallOptions { 20 + signal?: AbortSignal 21 + headers?: HeadersMap 22 + qp?: QueryParams 23 + encoding?: 'application/vnd.ipld.car' 24 + } 25 + 26 + export interface Response { 27 + success: boolean 28 + headers: HeadersMap 29 + } 30 + 31 + export function toKnownErr(e: any) { 32 + return e 33 + }
+56
packages/lexicon/src/types/com/atproto/repo/listMissingBlobs.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { BlobRef, ValidationResult } from '@atproto/lexicon' 5 + import { HeadersMap, XRPCError } from '@atproto/xrpc' 6 + import { CID } from 'multiformats/cid' 7 + 8 + import { validate as _validate } from '../../../../lexicons' 9 + import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' 10 + 11 + const is$typed = _is$typed, 12 + validate = _validate 13 + const id = 'com.atproto.repo.listMissingBlobs' 14 + 15 + export interface QueryParams { 16 + limit?: number 17 + cursor?: string 18 + } 19 + 20 + export type InputSchema = undefined 21 + 22 + export interface OutputSchema { 23 + cursor?: string 24 + blobs: RecordBlob[] 25 + } 26 + 27 + export interface CallOptions { 28 + signal?: AbortSignal 29 + headers?: HeadersMap 30 + } 31 + 32 + export interface Response { 33 + success: boolean 34 + headers: HeadersMap 35 + data: OutputSchema 36 + } 37 + 38 + export function toKnownErr(e: any) { 39 + return e 40 + } 41 + 42 + export interface RecordBlob { 43 + $type?: 'com.atproto.repo.listMissingBlobs#recordBlob' 44 + cid: string 45 + recordUri: string 46 + } 47 + 48 + const hashRecordBlob = 'recordBlob' 49 + 50 + export function isRecordBlob<V>(v: V) { 51 + return is$typed(v, id, hashRecordBlob) 52 + } 53 + 54 + export function validateRecordBlob<V>(v: V) { 55 + return validate<RecordBlob & V>(v, id, hashRecordBlob) 56 + }
+68
packages/lexicon/src/types/com/atproto/repo/listRecords.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { BlobRef, ValidationResult } from '@atproto/lexicon' 5 + import { HeadersMap, XRPCError } from '@atproto/xrpc' 6 + import { CID } from 'multiformats/cid' 7 + 8 + import { validate as _validate } from '../../../../lexicons' 9 + import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' 10 + 11 + const is$typed = _is$typed, 12 + validate = _validate 13 + const id = 'com.atproto.repo.listRecords' 14 + 15 + export interface QueryParams { 16 + /** The handle or DID of the repo. */ 17 + repo: string 18 + /** The NSID of the record type. */ 19 + collection: string 20 + /** The number of records to return. */ 21 + limit?: number 22 + cursor?: string 23 + /** DEPRECATED: The lowest sort-ordered rkey to start from (exclusive) */ 24 + rkeyStart?: string 25 + /** DEPRECATED: The highest sort-ordered rkey to stop at (exclusive) */ 26 + rkeyEnd?: string 27 + /** Flag to reverse the order of the returned records. */ 28 + reverse?: boolean 29 + } 30 + 31 + export type InputSchema = undefined 32 + 33 + export interface OutputSchema { 34 + cursor?: string 35 + records: Record[] 36 + } 37 + 38 + export interface CallOptions { 39 + signal?: AbortSignal 40 + headers?: HeadersMap 41 + } 42 + 43 + export interface Response { 44 + success: boolean 45 + headers: HeadersMap 46 + data: OutputSchema 47 + } 48 + 49 + export function toKnownErr(e: any) { 50 + return e 51 + } 52 + 53 + export interface Record { 54 + $type?: 'com.atproto.repo.listRecords#record' 55 + uri: string 56 + cid: string 57 + value: { [_ in string]: unknown } 58 + } 59 + 60 + const hashRecord = 'record' 61 + 62 + export function isRecord<V>(v: V) { 63 + return is$typed(v, id, hashRecord) 64 + } 65 + 66 + export function validateRecord<V>(v: V) { 67 + return validate<Record & V>(v, id, hashRecord) 68 + }
+67
packages/lexicon/src/types/com/atproto/repo/putRecord.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { BlobRef, ValidationResult } from '@atproto/lexicon' 5 + import { HeadersMap, XRPCError } from '@atproto/xrpc' 6 + import { CID } from 'multiformats/cid' 7 + 8 + import { validate as _validate } from '../../../../lexicons' 9 + import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' 10 + import type * as ComAtprotoRepoDefs from './defs.js' 11 + 12 + const is$typed = _is$typed, 13 + validate = _validate 14 + const id = 'com.atproto.repo.putRecord' 15 + 16 + export interface QueryParams {} 17 + 18 + export interface InputSchema { 19 + /** The handle or DID of the repo (aka, current account). */ 20 + repo: string 21 + /** The NSID of the record collection. */ 22 + collection: string 23 + /** The Record Key. */ 24 + rkey: string 25 + /** Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons. */ 26 + validate?: boolean 27 + /** The record to write. */ 28 + record: { [_ in string]: unknown } 29 + /** Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation */ 30 + swapRecord?: string | null 31 + /** Compare and swap with the previous commit by CID. */ 32 + swapCommit?: string 33 + } 34 + 35 + export interface OutputSchema { 36 + uri: string 37 + cid: string 38 + commit?: ComAtprotoRepoDefs.CommitMeta 39 + validationStatus?: 'valid' | 'unknown' | (string & {}) 40 + } 41 + 42 + export interface CallOptions { 43 + signal?: AbortSignal 44 + headers?: HeadersMap 45 + qp?: QueryParams 46 + encoding?: 'application/json' 47 + } 48 + 49 + export interface Response { 50 + success: boolean 51 + headers: HeadersMap 52 + data: OutputSchema 53 + } 54 + 55 + export class InvalidSwapError extends XRPCError { 56 + constructor(src: XRPCError) { 57 + super(src.status, src.error, src.message, src.headers, { cause: src }) 58 + } 59 + } 60 + 61 + export function toKnownErr(e: any) { 62 + if (e instanceof XRPCError) { 63 + if (e.error === 'InvalidSwap') return new InvalidSwapError(e) 64 + } 65 + 66 + return e 67 + }
+38
packages/lexicon/src/types/com/atproto/repo/uploadBlob.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { BlobRef, ValidationResult } from '@atproto/lexicon' 5 + import { HeadersMap, XRPCError } from '@atproto/xrpc' 6 + import { CID } from 'multiformats/cid' 7 + 8 + import { validate as _validate } from '../../../../lexicons' 9 + import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' 10 + 11 + const is$typed = _is$typed, 12 + validate = _validate 13 + const id = 'com.atproto.repo.uploadBlob' 14 + 15 + export interface QueryParams {} 16 + 17 + export type InputSchema = string | Uint8Array | Blob 18 + 19 + export interface OutputSchema { 20 + blob: BlobRef 21 + } 22 + 23 + export interface CallOptions { 24 + signal?: AbortSignal 25 + headers?: HeadersMap 26 + qp?: QueryParams 27 + encoding?: string 28 + } 29 + 30 + export interface Response { 31 + success: boolean 32 + headers: HeadersMap 33 + data: OutputSchema 34 + } 35 + 36 + export function toKnownErr(e: any) { 37 + return e 38 + }
+46
packages/lexicon/src/types/xyz/statusphere/defs.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { BlobRef, ValidationResult } from '@atproto/lexicon' 5 + import { CID } from 'multiformats/cid' 6 + 7 + import { validate as _validate } from '../../../lexicons' 8 + import { is$typed as _is$typed, $Typed, OmitKey } from '../../../util' 9 + 10 + const is$typed = _is$typed, 11 + validate = _validate 12 + const id = 'xyz.statusphere.defs' 13 + 14 + export interface StatusView { 15 + $type?: 'xyz.statusphere.defs#statusView' 16 + uri: string 17 + status: string 18 + createdAt: string 19 + profile: ProfileView 20 + } 21 + 22 + const hashStatusView = 'statusView' 23 + 24 + export function isStatusView<V>(v: V) { 25 + return is$typed(v, id, hashStatusView) 26 + } 27 + 28 + export function validateStatusView<V>(v: V) { 29 + return validate<StatusView & V>(v, id, hashStatusView) 30 + } 31 + 32 + export interface ProfileView { 33 + $type?: 'xyz.statusphere.defs#profileView' 34 + did: string 35 + handle: string 36 + } 37 + 38 + const hashProfileView = 'profileView' 39 + 40 + export function isProfileView<V>(v: V) { 41 + return is$typed(v, id, hashProfileView) 42 + } 43 + 44 + export function validateProfileView<V>(v: V) { 45 + return validate<ProfileView & V>(v, id, hashProfileView) 46 + }
+17
packages/lexicon/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "es2020", 4 + "module": "NodeNext", 5 + "moduleResolution": "NodeNext", 6 + "esModuleInterop": true, 7 + "forceConsistentCasingInFileNames": true, 8 + "strict": true, 9 + "skipLibCheck": true, 10 + "declaration": true, 11 + "declarationMap": true, 12 + "sourceMap": true, 13 + "outDir": "dist" 14 + }, 15 + "include": ["src/**/*"], 16 + "exclude": ["node_modules", "dist"] 17 + }
+1857 -129
pnpm-lock.yaml
··· 7 7 importers: 8 8 9 9 .: 10 + devDependencies: 11 + '@atproto/lex-cli': 12 + specifier: ^0.6.1 13 + version: 0.6.1 14 + '@ianvs/prettier-plugin-sort-imports': 15 + specifier: ^4.4.1 16 + version: 4.4.1(prettier@3.5.2) 17 + concurrently: 18 + specifier: ^9.1.2 19 + version: 9.1.2 20 + prettier: 21 + specifier: ^3.5.2 22 + version: 3.5.2 23 + prettier-plugin-tailwindcss: 24 + specifier: ^0.6.11 25 + version: 0.6.11(@ianvs/prettier-plugin-sort-imports@4.4.1(prettier@3.5.2))(prettier@3.5.2) 26 + rimraf: 27 + specifier: ^6.0.1 28 + version: 6.0.1 29 + typescript: 30 + specifier: ^5.8.2 31 + version: 5.8.2 32 + 33 + packages/appview: 10 34 dependencies: 11 35 '@atproto/api': 12 36 specifier: ^0.14.7 13 37 version: 0.14.7 14 38 '@atproto/common': 15 - specifier: ^0.4.1 39 + specifier: ^0.4.8 16 40 version: 0.4.8 17 41 '@atproto/identity': 18 - specifier: ^0.4.0 42 + specifier: ^0.4.6 19 43 version: 0.4.6 20 44 '@atproto/lexicon': 21 - specifier: ^0.4.2 45 + specifier: ^0.4.7 22 46 version: 0.4.7 23 47 '@atproto/oauth-client-node': 24 - specifier: ^0.2.2 48 + specifier: ^0.2.11 25 49 version: 0.2.11 26 50 '@atproto/sync': 27 - specifier: ^0.1.4 51 + specifier: ^0.1.15 28 52 version: 0.1.15 29 53 '@atproto/syntax': 30 - specifier: ^0.3.0 54 + specifier: ^0.3.3 31 55 version: 0.3.3 32 56 '@atproto/xrpc-server': 33 - specifier: ^0.7.9 57 + specifier: ^0.7.11 34 58 version: 0.7.11 59 + '@statusphere/lexicon': 60 + specifier: workspace:* 61 + version: link:../lexicon 35 62 better-sqlite3: 36 - specifier: ^11.1.2 63 + specifier: ^11.8.1 37 64 version: 11.8.1 65 + cors: 66 + specifier: ^2.8.5 67 + version: 2.8.5 38 68 dotenv: 39 - specifier: ^16.4.5 69 + specifier: ^16.4.7 40 70 version: 16.4.7 41 71 envalid: 42 72 specifier: ^8.0.0 43 73 version: 8.0.0 44 74 express: 45 - specifier: ^4.19.2 75 + specifier: ^4.21.2 46 76 version: 4.21.2 47 77 iron-session: 48 - specifier: ^8.0.2 78 + specifier: ^8.0.4 49 79 version: 8.0.4 50 80 kysely: 51 - specifier: ^0.27.4 81 + specifier: ^0.27.5 52 82 version: 0.27.5 53 83 multiformats: 54 84 specifier: ^13.3.2 55 85 version: 13.3.2 56 86 pino: 57 - specifier: ^9.3.2 87 + specifier: ^9.6.0 58 88 version: 9.6.0 59 - uhtml: 60 - specifier: ^4.5.9 61 - version: 4.7.0 62 89 devDependencies: 63 - '@atproto/lex-cli': 64 - specifier: ^0.6.1 65 - version: 0.6.1 66 90 '@types/better-sqlite3': 67 - specifier: ^7.6.11 91 + specifier: ^7.6.12 68 92 version: 7.6.12 93 + '@types/cors': 94 + specifier: ^2.8.17 95 + version: 2.8.17 69 96 '@types/express': 70 97 specifier: ^5.0.0 71 98 version: 5.0.0 99 + '@types/node': 100 + specifier: ^22.13.8 101 + version: 22.13.8 72 102 pino-pretty: 73 103 specifier: ^13.0.0 74 104 version: 13.0.0 75 - prettier: 76 - specifier: ^3.5.2 77 - version: 3.5.2 78 - rimraf: 79 - specifier: ^6.0.1 80 - version: 6.0.1 81 105 ts-node: 82 106 specifier: ^10.9.2 83 107 version: 10.9.2(@types/node@22.13.8)(typescript@5.8.2) 84 108 tsup: 85 - specifier: ^8.0.2 86 - version: 8.4.0(tsx@4.19.3)(typescript@5.8.2) 109 + specifier: ^8.4.0 110 + version: 8.4.0(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.3)(typescript@5.8.2) 87 111 tsx: 88 - specifier: ^4.7.2 112 + specifier: ^4.19.3 89 113 version: 4.19.3 90 114 typescript: 91 - specifier: ^5.4.4 115 + specifier: ^5.8.2 116 + version: 5.8.2 117 + 118 + packages/client: 119 + dependencies: 120 + '@atproto/api': 121 + specifier: ^0.14.7 122 + version: 0.14.7 123 + '@statusphere/lexicon': 124 + specifier: workspace:* 125 + version: link:../lexicon 126 + '@tailwindcss/vite': 127 + specifier: ^4.0.9 128 + version: 4.0.9(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.3)) 129 + '@tanstack/react-query': 130 + specifier: ^5.66.11 131 + version: 5.66.11(react@19.0.0) 132 + iron-session: 133 + specifier: ^8.0.4 134 + version: 8.0.4 135 + react: 136 + specifier: ^19.0.0 137 + version: 19.0.0 138 + react-dom: 139 + specifier: ^19.0.0 140 + version: 19.0.0(react@19.0.0) 141 + react-router-dom: 142 + specifier: ^7.2.0 143 + version: 7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) 144 + devDependencies: 145 + '@types/react': 146 + specifier: ^19.0.10 147 + version: 19.0.10 148 + '@types/react-dom': 149 + specifier: ^19.0.4 150 + version: 19.0.4(@types/react@19.0.10) 151 + '@typescript-eslint/eslint-plugin': 152 + specifier: ^8.25.0 153 + version: 8.25.0(@typescript-eslint/parser@8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2) 154 + '@typescript-eslint/parser': 155 + specifier: ^8.25.0 156 + version: 8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2) 157 + '@vitejs/plugin-react': 158 + specifier: ^4.3.4 159 + version: 4.3.4(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.3)) 160 + autoprefixer: 161 + specifier: ^10.4.20 162 + version: 10.4.20(postcss@8.5.3) 163 + eslint: 164 + specifier: ^9.21.0 165 + version: 9.21.0(jiti@2.4.2) 166 + eslint-plugin-react-hooks: 167 + specifier: ^5.2.0 168 + version: 5.2.0(eslint@9.21.0(jiti@2.4.2)) 169 + eslint-plugin-react-refresh: 170 + specifier: ^0.4.19 171 + version: 0.4.19(eslint@9.21.0(jiti@2.4.2)) 172 + postcss: 173 + specifier: ^8.5.3 174 + version: 8.5.3 175 + tailwindcss: 176 + specifier: ^4.0.9 177 + version: 4.0.9 178 + typescript: 179 + specifier: ^5.8.2 180 + version: 5.8.2 181 + vite: 182 + specifier: ^6.2.0 183 + version: 6.2.0(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.3) 184 + 185 + packages/lexicon: 186 + dependencies: 187 + '@atproto/api': 188 + specifier: ^0.14.7 189 + version: 0.14.7 190 + '@atproto/lexicon': 191 + specifier: ^0.4.7 192 + version: 0.4.7 193 + '@atproto/syntax': 194 + specifier: ^0.3.3 195 + version: 0.3.3 196 + '@atproto/xrpc': 197 + specifier: ^0.6.9 198 + version: 0.6.9 199 + multiformats: 200 + specifier: ^13.3.2 201 + version: 13.3.2 202 + devDependencies: 203 + '@atproto/lex-cli': 204 + specifier: ^0.6.1 205 + version: 0.6.1 206 + '@types/node': 207 + specifier: ^22.13.8 208 + version: 22.13.8 209 + rimraf: 210 + specifier: ^6.0.1 211 + version: 6.0.1 212 + tsup: 213 + specifier: ^8.4.0 214 + version: 8.4.0(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.3)(typescript@5.8.2) 215 + typescript: 216 + specifier: ^5.8.2 92 217 version: 5.8.2 93 218 94 219 packages: 95 220 221 + '@ampproject/remapping@2.3.0': 222 + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} 223 + engines: {node: '>=6.0.0'} 224 + 96 225 '@atproto-labs/did-resolver@0.1.10': 97 226 resolution: {integrity: sha512-o/bl5acf3AIPKZuO6Fd5EmO4INGpi/3Pm08ZpHNCy7s4VZXFmAjZaHeCD7hQ8yEL0EtXnLNIECtKrTBTTx8b+A==} 98 227 ··· 188 317 '@atproto/xrpc@0.6.9': 189 318 resolution: {integrity: sha512-vQGA7++DYMNaHx3C7vEjT+2X6hYYLG7JNbBnDLWu0km1/1KYXgRkAz4h+FfYqg1mvzvIorHU7DAs5wevkJDDlw==} 190 319 320 + '@babel/code-frame@7.26.2': 321 + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} 322 + engines: {node: '>=6.9.0'} 323 + 324 + '@babel/compat-data@7.26.8': 325 + resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} 326 + engines: {node: '>=6.9.0'} 327 + 328 + '@babel/core@7.26.9': 329 + resolution: {integrity: sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==} 330 + engines: {node: '>=6.9.0'} 331 + 332 + '@babel/generator@7.26.9': 333 + resolution: {integrity: sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==} 334 + engines: {node: '>=6.9.0'} 335 + 336 + '@babel/helper-compilation-targets@7.26.5': 337 + resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==} 338 + engines: {node: '>=6.9.0'} 339 + 340 + '@babel/helper-module-imports@7.25.9': 341 + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} 342 + engines: {node: '>=6.9.0'} 343 + 344 + '@babel/helper-module-transforms@7.26.0': 345 + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} 346 + engines: {node: '>=6.9.0'} 347 + peerDependencies: 348 + '@babel/core': ^7.0.0 349 + 350 + '@babel/helper-plugin-utils@7.26.5': 351 + resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} 352 + engines: {node: '>=6.9.0'} 353 + 354 + '@babel/helper-string-parser@7.25.9': 355 + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} 356 + engines: {node: '>=6.9.0'} 357 + 358 + '@babel/helper-validator-identifier@7.25.9': 359 + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} 360 + engines: {node: '>=6.9.0'} 361 + 362 + '@babel/helper-validator-option@7.25.9': 363 + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} 364 + engines: {node: '>=6.9.0'} 365 + 366 + '@babel/helpers@7.26.9': 367 + resolution: {integrity: sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==} 368 + engines: {node: '>=6.9.0'} 369 + 370 + '@babel/parser@7.26.9': 371 + resolution: {integrity: sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==} 372 + engines: {node: '>=6.0.0'} 373 + hasBin: true 374 + 375 + '@babel/plugin-transform-react-jsx-self@7.25.9': 376 + resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} 377 + engines: {node: '>=6.9.0'} 378 + peerDependencies: 379 + '@babel/core': ^7.0.0-0 380 + 381 + '@babel/plugin-transform-react-jsx-source@7.25.9': 382 + resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} 383 + engines: {node: '>=6.9.0'} 384 + peerDependencies: 385 + '@babel/core': ^7.0.0-0 386 + 387 + '@babel/template@7.26.9': 388 + resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==} 389 + engines: {node: '>=6.9.0'} 390 + 391 + '@babel/traverse@7.26.9': 392 + resolution: {integrity: sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==} 393 + engines: {node: '>=6.9.0'} 394 + 395 + '@babel/types@7.26.9': 396 + resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==} 397 + engines: {node: '>=6.9.0'} 398 + 191 399 '@cbor-extract/cbor-extract-darwin-arm64@2.2.0': 192 400 resolution: {integrity: sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==} 193 401 cpu: [arm64] ··· 372 580 cpu: [x64] 373 581 os: [win32] 374 582 583 + '@eslint-community/eslint-utils@4.4.1': 584 + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} 585 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 586 + peerDependencies: 587 + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 588 + 589 + '@eslint-community/regexpp@4.12.1': 590 + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} 591 + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 592 + 593 + '@eslint/config-array@0.19.2': 594 + resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} 595 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 596 + 597 + '@eslint/core@0.12.0': 598 + resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} 599 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 600 + 601 + '@eslint/eslintrc@3.3.0': 602 + resolution: {integrity: sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==} 603 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 604 + 605 + '@eslint/js@9.21.0': 606 + resolution: {integrity: sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==} 607 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 608 + 609 + '@eslint/object-schema@2.1.6': 610 + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} 611 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 612 + 613 + '@eslint/plugin-kit@0.2.7': 614 + resolution: {integrity: sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==} 615 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 616 + 617 + '@humanfs/core@0.19.1': 618 + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} 619 + engines: {node: '>=18.18.0'} 620 + 621 + '@humanfs/node@0.16.6': 622 + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} 623 + engines: {node: '>=18.18.0'} 624 + 625 + '@humanwhocodes/module-importer@1.0.1': 626 + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 627 + engines: {node: '>=12.22'} 628 + 629 + '@humanwhocodes/retry@0.3.1': 630 + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} 631 + engines: {node: '>=18.18'} 632 + 633 + '@humanwhocodes/retry@0.4.2': 634 + resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} 635 + engines: {node: '>=18.18'} 636 + 637 + '@ianvs/prettier-plugin-sort-imports@4.4.1': 638 + resolution: {integrity: sha512-F0/Hrcfpy8WuxlQyAWJTEren/uxKhYonOGY4OyWmwRdeTvkh9mMSCxowZLjNkhwi/2ipqCgtXwwOk7tW0mWXkA==} 639 + peerDependencies: 640 + '@vue/compiler-sfc': 2.7.x || 3.x 641 + prettier: 2 || 3 642 + peerDependenciesMeta: 643 + '@vue/compiler-sfc': 644 + optional: true 645 + 375 646 '@ipld/car@3.2.4': 376 647 resolution: {integrity: sha512-rezKd+jk8AsTGOoJKqzfjLJ3WVft7NZNH95f0pfPbicROvzTyvHCNy567HzSUd6gRXZ9im29z5ZEv9Hw49jSYw==} 377 648 ··· 426 697 '@pkgjs/parseargs@0.11.0': 427 698 resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} 428 699 engines: {node: '>=14'} 429 - 430 - '@preact/signals-core@1.8.0': 431 - resolution: {integrity: sha512-OBvUsRZqNmjzCZXWLxkZfhcgT+Fk8DDcT/8vD6a1xhDemodyy87UJRJfASMuSD8FaAIeGgGm85ydXhm7lr4fyA==} 432 700 433 701 '@rollup/rollup-android-arm-eabi@4.34.9': 434 702 resolution: {integrity: sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==} ··· 525 793 cpu: [x64] 526 794 os: [win32] 527 795 796 + '@tailwindcss/node@4.0.9': 797 + resolution: {integrity: sha512-tOJvdI7XfJbARYhxX+0RArAhmuDcczTC46DGCEziqxzzbIaPnfYaIyRT31n4u8lROrsO7Q6u/K9bmQHL2uL1bQ==} 798 + 799 + '@tailwindcss/oxide-android-arm64@4.0.9': 800 + resolution: {integrity: sha512-YBgy6+2flE/8dbtrdotVInhMVIxnHJPbAwa7U1gX4l2ThUIaPUp18LjB9wEH8wAGMBZUb//SzLtdXXNBHPUl6Q==} 801 + engines: {node: '>= 10'} 802 + cpu: [arm64] 803 + os: [android] 804 + 805 + '@tailwindcss/oxide-darwin-arm64@4.0.9': 806 + resolution: {integrity: sha512-pWdl4J2dIHXALgy2jVkwKBmtEb73kqIfMpYmcgESr7oPQ+lbcQ4+tlPeVXaSAmang+vglAfFpXQCOvs/aGSqlw==} 807 + engines: {node: '>= 10'} 808 + cpu: [arm64] 809 + os: [darwin] 810 + 811 + '@tailwindcss/oxide-darwin-x64@4.0.9': 812 + resolution: {integrity: sha512-4Dq3lKp0/C7vrRSkNPtBGVebEyWt9QPPlQctxJ0H3MDyiQYvzVYf8jKow7h5QkWNe8hbatEqljMj/Y0M+ERYJg==} 813 + engines: {node: '>= 10'} 814 + cpu: [x64] 815 + os: [darwin] 816 + 817 + '@tailwindcss/oxide-freebsd-x64@4.0.9': 818 + resolution: {integrity: sha512-k7U1RwRODta8x0uealtVt3RoWAWqA+D5FAOsvVGpYoI6ObgmnzqWW6pnVwz70tL8UZ/QXjeMyiICXyjzB6OGtQ==} 819 + engines: {node: '>= 10'} 820 + cpu: [x64] 821 + os: [freebsd] 822 + 823 + '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.9': 824 + resolution: {integrity: sha512-NDDjVweHz2zo4j+oS8y3KwKL5wGCZoXGA9ruJM982uVJLdsF8/1AeKvUwKRlMBpxHt1EdWJSAh8a0Mfhl28GlQ==} 825 + engines: {node: '>= 10'} 826 + cpu: [arm] 827 + os: [linux] 828 + 829 + '@tailwindcss/oxide-linux-arm64-gnu@4.0.9': 830 + resolution: {integrity: sha512-jk90UZ0jzJl3Dy1BhuFfRZ2KP9wVKMXPjmCtY4U6fF2LvrjP5gWFJj5VHzfzHonJexjrGe1lMzgtjriuZkxagg==} 831 + engines: {node: '>= 10'} 832 + cpu: [arm64] 833 + os: [linux] 834 + 835 + '@tailwindcss/oxide-linux-arm64-musl@4.0.9': 836 + resolution: {integrity: sha512-3eMjyTC6HBxh9nRgOHzrc96PYh1/jWOwHZ3Kk0JN0Kl25BJ80Lj9HEvvwVDNTgPg154LdICwuFLuhfgH9DULmg==} 837 + engines: {node: '>= 10'} 838 + cpu: [arm64] 839 + os: [linux] 840 + 841 + '@tailwindcss/oxide-linux-x64-gnu@4.0.9': 842 + resolution: {integrity: sha512-v0D8WqI/c3WpWH1kq/HP0J899ATLdGZmENa2/emmNjubT0sWtEke9W9+wXeEoACuGAhF9i3PO5MeyditpDCiWQ==} 843 + engines: {node: '>= 10'} 844 + cpu: [x64] 845 + os: [linux] 846 + 847 + '@tailwindcss/oxide-linux-x64-musl@4.0.9': 848 + resolution: {integrity: sha512-Kvp0TCkfeXyeehqLJr7otsc4hd/BUPfcIGrQiwsTVCfaMfjQZCG7DjI+9/QqPZha8YapLA9UoIcUILRYO7NE1Q==} 849 + engines: {node: '>= 10'} 850 + cpu: [x64] 851 + os: [linux] 852 + 853 + '@tailwindcss/oxide-win32-arm64-msvc@4.0.9': 854 + resolution: {integrity: sha512-m3+60T/7YvWekajNq/eexjhV8z10rswcz4BC9bioJ7YaN+7K8W2AmLmG0B79H14m6UHE571qB0XsPus4n0QVgQ==} 855 + engines: {node: '>= 10'} 856 + cpu: [arm64] 857 + os: [win32] 858 + 859 + '@tailwindcss/oxide-win32-x64-msvc@4.0.9': 860 + resolution: {integrity: sha512-dpc05mSlqkwVNOUjGu/ZXd5U1XNch1kHFJ4/cHkZFvaW1RzbHmRt24gvM8/HC6IirMxNarzVw4IXVtvrOoZtxA==} 861 + engines: {node: '>= 10'} 862 + cpu: [x64] 863 + os: [win32] 864 + 865 + '@tailwindcss/oxide@4.0.9': 866 + resolution: {integrity: sha512-eLizHmXFqHswJONwfqi/WZjtmWZpIalpvMlNhTM99/bkHtUs6IqgI1XQ0/W5eO2HiRQcIlXUogI2ycvKhVLNcA==} 867 + engines: {node: '>= 10'} 868 + 869 + '@tailwindcss/vite@4.0.9': 870 + resolution: {integrity: sha512-BIKJO+hwdIsN7V6I7SziMZIVHWWMsV/uCQKYEbeiGRDRld+TkqyRRl9+dQ0MCXbhcVr+D9T/qX2E84kT7V281g==} 871 + peerDependencies: 872 + vite: ^5.2.0 || ^6 873 + 874 + '@tanstack/query-core@5.66.11': 875 + resolution: {integrity: sha512-ZEYxgHUcohj3sHkbRaw0gYwFxjY5O6M3IXOYXEun7E1rqNhsP8fOtqjJTKPZpVHcdIdrmX4lzZctT4+pts0OgA==} 876 + 877 + '@tanstack/react-query@5.66.11': 878 + resolution: {integrity: sha512-uPDiQbZScWkAeihmZ9gAm3wOBA1TmLB1KCB1fJ1hIiEKq3dTT+ja/aYM7wGUD+XiEsY4sDSE7p8VIz/21L2Dow==} 879 + peerDependencies: 880 + react: ^18 || ^19 881 + 528 882 '@ts-morph/common@0.17.0': 529 883 resolution: {integrity: sha512-RMSSvSfs9kb0VzkvQ2NWobwnj7TxCA9vI/IjR9bDHqgAyVbu2T0DN4wiKVqomyDWqO7dPr/tErSfq7urQ1Q37g==} 530 884 ··· 540 894 '@tsconfig/node16@1.0.4': 541 895 resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} 542 896 897 + '@types/babel__core@7.20.5': 898 + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} 899 + 900 + '@types/babel__generator@7.6.8': 901 + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} 902 + 903 + '@types/babel__template@7.4.4': 904 + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} 905 + 906 + '@types/babel__traverse@7.20.6': 907 + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} 908 + 543 909 '@types/better-sqlite3@7.6.12': 544 910 resolution: {integrity: sha512-fnQmj8lELIj7BSrZQAdBMHEHX8OZLYIHXqAKT1O7tDfLxaINzf00PMjw22r3N/xXh0w/sGHlO6SVaCQ2mj78lg==} 545 911 ··· 549 915 '@types/connect@3.4.38': 550 916 resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} 551 917 918 + '@types/cookie@0.6.0': 919 + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} 920 + 921 + '@types/cors@2.8.17': 922 + resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} 923 + 552 924 '@types/estree@1.0.6': 553 925 resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} 554 926 ··· 561 933 '@types/http-errors@2.0.4': 562 934 resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} 563 935 936 + '@types/json-schema@7.0.15': 937 + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} 938 + 564 939 '@types/mime@1.3.5': 565 940 resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} 566 941 ··· 573 948 '@types/range-parser@1.2.7': 574 949 resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} 575 950 951 + '@types/react-dom@19.0.4': 952 + resolution: {integrity: sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==} 953 + peerDependencies: 954 + '@types/react': ^19.0.0 955 + 956 + '@types/react@19.0.10': 957 + resolution: {integrity: sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==} 958 + 576 959 '@types/send@0.17.4': 577 960 resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} 578 961 579 962 '@types/serve-static@1.15.7': 580 963 resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} 581 964 582 - '@webreflection/signal@2.1.2': 583 - resolution: {integrity: sha512-0dW0fstQQkIt588JwhDiPS4xgeeQcQnBHn6MVInrBzmFlnLtzoSJL9G7JqdAlZVVi19tfb8R1QisZIT31cgiug==} 965 + '@typescript-eslint/eslint-plugin@8.25.0': 966 + resolution: {integrity: sha512-VM7bpzAe7JO/BFf40pIT1lJqS/z1F8OaSsUB3rpFJucQA4cOSuH2RVVVkFULN+En0Djgr29/jb4EQnedUo95KA==} 967 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 968 + peerDependencies: 969 + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 970 + eslint: ^8.57.0 || ^9.0.0 971 + typescript: '>=4.8.4 <5.8.0' 972 + 973 + '@typescript-eslint/parser@8.25.0': 974 + resolution: {integrity: sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==} 975 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 976 + peerDependencies: 977 + eslint: ^8.57.0 || ^9.0.0 978 + typescript: '>=4.8.4 <5.8.0' 979 + 980 + '@typescript-eslint/scope-manager@8.25.0': 981 + resolution: {integrity: sha512-6PPeiKIGbgStEyt4NNXa2ru5pMzQ8OYKO1hX1z53HMomrmiSB+R5FmChgQAP1ro8jMtNawz+TRQo/cSXrauTpg==} 982 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 983 + 984 + '@typescript-eslint/type-utils@8.25.0': 985 + resolution: {integrity: sha512-d77dHgHWnxmXOPJuDWO4FDWADmGQkN5+tt6SFRZz/RtCWl4pHgFl3+WdYCn16+3teG09DY6XtEpf3gGD0a186g==} 986 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 987 + peerDependencies: 988 + eslint: ^8.57.0 || ^9.0.0 989 + typescript: '>=4.8.4 <5.8.0' 990 + 991 + '@typescript-eslint/types@8.25.0': 992 + resolution: {integrity: sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw==} 993 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 994 + 995 + '@typescript-eslint/typescript-estree@8.25.0': 996 + resolution: {integrity: sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q==} 997 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 998 + peerDependencies: 999 + typescript: '>=4.8.4 <5.8.0' 1000 + 1001 + '@typescript-eslint/utils@8.25.0': 1002 + resolution: {integrity: sha512-syqRbrEv0J1wywiLsK60XzHnQe/kRViI3zwFALrNEgnntn1l24Ra2KvOAWwWbWZ1lBZxZljPDGOq967dsl6fkA==} 1003 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1004 + peerDependencies: 1005 + eslint: ^8.57.0 || ^9.0.0 1006 + typescript: '>=4.8.4 <5.8.0' 1007 + 1008 + '@typescript-eslint/visitor-keys@8.25.0': 1009 + resolution: {integrity: sha512-kCYXKAum9CecGVHGij7muybDfTS2sD3t0L4bJsEZLkyrXUImiCTq1M3LG2SRtOhiHFwMR9wAFplpT6XHYjTkwQ==} 1010 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 584 1011 585 - '@webreflection/uparser@0.4.0': 586 - resolution: {integrity: sha512-kAFWUEw5eool295y01VDr+DOsyog6lURX9l288JCJAD2gxc0tFk34dYaAi6O3BbJyfSoncVEV+nw87bsssdppQ==} 1012 + '@vitejs/plugin-react@4.3.4': 1013 + resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} 1014 + engines: {node: ^14.18.0 || >=16.0.0} 1015 + peerDependencies: 1016 + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 587 1017 588 1018 abort-controller@3.0.0: 589 1019 resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} ··· 593 1023 resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} 594 1024 engines: {node: '>= 0.6'} 595 1025 1026 + acorn-jsx@5.3.2: 1027 + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 1028 + peerDependencies: 1029 + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 1030 + 596 1031 acorn-walk@8.3.4: 597 1032 resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} 598 1033 engines: {node: '>=0.4.0'} ··· 601 1036 resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} 602 1037 engines: {node: '>=0.4.0'} 603 1038 hasBin: true 1039 + 1040 + ajv@6.12.6: 1041 + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 604 1042 605 1043 ansi-regex@5.0.1: 606 1044 resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} ··· 624 1062 arg@4.1.3: 625 1063 resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} 626 1064 1065 + argparse@2.0.1: 1066 + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 1067 + 627 1068 array-flatten@1.1.1: 628 1069 resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} 629 1070 ··· 631 1072 resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} 632 1073 engines: {node: '>=8.0.0'} 633 1074 1075 + autoprefixer@10.4.20: 1076 + resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} 1077 + engines: {node: ^10 || ^12 || >=14} 1078 + hasBin: true 1079 + peerDependencies: 1080 + postcss: ^8.1.0 1081 + 634 1082 await-lock@2.2.2: 635 1083 resolution: {integrity: sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==} 636 1084 ··· 653 1101 resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} 654 1102 engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} 655 1103 1104 + brace-expansion@1.1.11: 1105 + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 1106 + 656 1107 brace-expansion@2.0.1: 657 1108 resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 658 1109 ··· 660 1111 resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 661 1112 engines: {node: '>=8'} 662 1113 1114 + browserslist@4.24.4: 1115 + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} 1116 + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} 1117 + hasBin: true 1118 + 663 1119 buffer@5.7.1: 664 1120 resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} 665 1121 ··· 688 1144 resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} 689 1145 engines: {node: '>= 0.4'} 690 1146 1147 + callsites@3.1.0: 1148 + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 1149 + engines: {node: '>=6'} 1150 + 1151 + caniuse-lite@1.0.30001701: 1152 + resolution: {integrity: sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==} 1153 + 691 1154 cbor-extract@2.2.0: 692 1155 resolution: {integrity: sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==} 693 1156 hasBin: true ··· 710 1173 chownr@1.1.4: 711 1174 resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} 712 1175 1176 + cliui@8.0.1: 1177 + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} 1178 + engines: {node: '>=12'} 1179 + 713 1180 code-block-writer@11.0.3: 714 1181 resolution: {integrity: sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==} 715 1182 ··· 731 1198 resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} 732 1199 engines: {node: ^12.20.0 || >=14} 733 1200 1201 + concat-map@0.0.1: 1202 + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 1203 + 1204 + concurrently@9.1.2: 1205 + resolution: {integrity: sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==} 1206 + engines: {node: '>=18'} 1207 + hasBin: true 1208 + 734 1209 consola@3.4.0: 735 1210 resolution: {integrity: sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==} 736 1211 engines: {node: ^14.18.0 || >=16.10.0} ··· 742 1217 content-type@1.0.5: 743 1218 resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} 744 1219 engines: {node: '>= 0.6'} 1220 + 1221 + convert-source-map@2.0.0: 1222 + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} 745 1223 746 1224 cookie-signature@1.0.6: 747 1225 resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} ··· 754 1232 resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} 755 1233 engines: {node: '>= 0.6'} 756 1234 1235 + cookie@1.0.2: 1236 + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} 1237 + engines: {node: '>=18'} 1238 + 1239 + cors@2.8.5: 1240 + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} 1241 + engines: {node: '>= 0.10'} 1242 + 757 1243 create-require@1.1.1: 758 1244 resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} 759 1245 ··· 761 1247 resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 762 1248 engines: {node: '>= 8'} 763 1249 764 - custom-function@2.0.0: 765 - resolution: {integrity: sha512-2OPHkZzq3mK1nWpJqWWkGD6Z+0AajNeIxmXl+MRVL8Vysjjf5tf9B5mo713/X2khEwBn/3BKQ7NphpP1vpVKug==} 1250 + csstype@3.1.3: 1251 + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 766 1252 767 1253 dateformat@4.6.3: 768 1254 resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} ··· 791 1277 deep-extend@0.6.0: 792 1278 resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} 793 1279 engines: {node: '>=4.0.0'} 1280 + 1281 + deep-is@0.1.4: 1282 + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 794 1283 795 1284 depd@2.0.0: 796 1285 resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} ··· 800 1289 resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} 801 1290 engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} 802 1291 1292 + detect-libc@1.0.3: 1293 + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} 1294 + engines: {node: '>=0.10'} 1295 + hasBin: true 1296 + 803 1297 detect-libc@2.0.3: 804 1298 resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} 805 1299 engines: {node: '>=8'} ··· 808 1302 resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} 809 1303 engines: {node: '>=0.3.1'} 810 1304 811 - dom-serializer@2.0.0: 812 - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} 813 - 814 - domconstants@1.1.6: 815 - resolution: {integrity: sha512-CuaDrThJ4VM+LyZ4ax8n52k0KbLJZtffyGkuj1WhpTRRcSfcy/9DfOBa68jenhX96oNUTunblSJEUNC4baFdmQ==} 816 - 817 - domelementtype@2.3.0: 818 - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} 819 - 820 - domhandler@5.0.3: 821 - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} 822 - engines: {node: '>= 4'} 823 - 824 - domutils@3.2.2: 825 - resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} 826 - 827 1305 dotenv@16.4.7: 828 1306 resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} 829 1307 engines: {node: '>=12'} ··· 838 1316 ee-first@1.1.1: 839 1317 resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} 840 1318 1319 + electron-to-chromium@1.5.109: 1320 + resolution: {integrity: sha512-AidaH9JETVRr9DIPGfp1kAarm/W6hRJTPuCnkF+2MqhF4KaAgRIcBc8nvjk+YMXZhwfISof/7WG29eS4iGxQLQ==} 1321 + 841 1322 emoji-regex@8.0.0: 842 1323 resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 843 1324 ··· 855 1336 end-of-stream@1.4.4: 856 1337 resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} 857 1338 858 - entities@4.5.0: 859 - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} 860 - engines: {node: '>=0.12'} 1339 + enhanced-resolve@5.18.1: 1340 + resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} 1341 + engines: {node: '>=10.13.0'} 861 1342 862 1343 envalid@8.0.0: 863 1344 resolution: {integrity: sha512-PGeYJnJB5naN0ME6SH8nFcDj9HVbLpYIfg1p5lAyM9T4cH2lwtu2fLbozC/bq+HUUOIFxhX/LP0/GmlqPHT4tQ==} ··· 880 1361 engines: {node: '>=18'} 881 1362 hasBin: true 882 1363 1364 + escalade@3.2.0: 1365 + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} 1366 + engines: {node: '>=6'} 1367 + 883 1368 escape-html@1.0.3: 884 1369 resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} 885 1370 1371 + escape-string-regexp@4.0.0: 1372 + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 1373 + engines: {node: '>=10'} 1374 + 1375 + eslint-plugin-react-hooks@5.2.0: 1376 + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} 1377 + engines: {node: '>=10'} 1378 + peerDependencies: 1379 + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 1380 + 1381 + eslint-plugin-react-refresh@0.4.19: 1382 + resolution: {integrity: sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ==} 1383 + peerDependencies: 1384 + eslint: '>=8.40' 1385 + 1386 + eslint-scope@8.2.0: 1387 + resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} 1388 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1389 + 1390 + eslint-visitor-keys@3.4.3: 1391 + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 1392 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 1393 + 1394 + eslint-visitor-keys@4.2.0: 1395 + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} 1396 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1397 + 1398 + eslint@9.21.0: 1399 + resolution: {integrity: sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==} 1400 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1401 + hasBin: true 1402 + peerDependencies: 1403 + jiti: '*' 1404 + peerDependenciesMeta: 1405 + jiti: 1406 + optional: true 1407 + 1408 + espree@10.3.0: 1409 + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} 1410 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1411 + 1412 + esquery@1.6.0: 1413 + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} 1414 + engines: {node: '>=0.10'} 1415 + 1416 + esrecurse@4.3.0: 1417 + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} 1418 + engines: {node: '>=4.0'} 1419 + 1420 + estraverse@5.3.0: 1421 + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 1422 + engines: {node: '>=4.0'} 1423 + 1424 + esutils@2.0.3: 1425 + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 1426 + engines: {node: '>=0.10.0'} 1427 + 886 1428 etag@1.8.1: 887 1429 resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} 888 1430 engines: {node: '>= 0.6'} ··· 909 1451 fast-copy@3.0.2: 910 1452 resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==} 911 1453 1454 + fast-deep-equal@3.1.3: 1455 + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 1456 + 912 1457 fast-glob@3.3.3: 913 1458 resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} 914 1459 engines: {node: '>=8.6.0'} 1460 + 1461 + fast-json-stable-stringify@2.1.0: 1462 + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 1463 + 1464 + fast-levenshtein@2.0.6: 1465 + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 915 1466 916 1467 fast-redact@3.5.0: 917 1468 resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} ··· 931 1482 picomatch: 932 1483 optional: true 933 1484 1485 + file-entry-cache@8.0.0: 1486 + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} 1487 + engines: {node: '>=16.0.0'} 1488 + 934 1489 file-uri-to-path@1.0.0: 935 1490 resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} 936 1491 ··· 942 1497 resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} 943 1498 engines: {node: '>= 0.8'} 944 1499 1500 + find-up@5.0.0: 1501 + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 1502 + engines: {node: '>=10'} 1503 + 1504 + flat-cache@4.0.1: 1505 + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} 1506 + engines: {node: '>=16'} 1507 + 1508 + flatted@3.3.3: 1509 + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} 1510 + 945 1511 foreground-child@3.3.1: 946 1512 resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} 947 1513 engines: {node: '>=14'} ··· 950 1516 resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} 951 1517 engines: {node: '>= 0.6'} 952 1518 1519 + fraction.js@4.3.7: 1520 + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} 1521 + 953 1522 fresh@0.5.2: 954 1523 resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} 955 1524 engines: {node: '>= 0.6'} ··· 965 1534 function-bind@1.1.2: 966 1535 resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 967 1536 968 - gc-hook@0.4.1: 969 - resolution: {integrity: sha512-uiF+uUftDVLr+VRdudsdsT3/LQYnv2ntwhRH964O7xXDI57Smrek5olv75Wb8Nnz6U+7iVTRXsBlxKcsaDTJTQ==} 1537 + gensync@1.0.0-beta.2: 1538 + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} 1539 + engines: {node: '>=6.9.0'} 1540 + 1541 + get-caller-file@2.0.5: 1542 + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} 1543 + engines: {node: 6.* || 8.* || >= 10.*} 970 1544 971 1545 get-intrinsic@1.3.0: 972 1546 resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} ··· 986 1560 resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 987 1561 engines: {node: '>= 6'} 988 1562 1563 + glob-parent@6.0.2: 1564 + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 1565 + engines: {node: '>=10.13.0'} 1566 + 989 1567 glob@10.4.5: 990 1568 resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} 991 1569 hasBin: true ··· 995 1573 engines: {node: 20 || >=22} 996 1574 hasBin: true 997 1575 1576 + globals@11.12.0: 1577 + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} 1578 + engines: {node: '>=4'} 1579 + 1580 + globals@14.0.0: 1581 + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} 1582 + engines: {node: '>=18'} 1583 + 998 1584 gopd@1.2.0: 999 1585 resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} 1000 1586 engines: {node: '>= 0.4'} 1587 + 1588 + graceful-fs@4.2.11: 1589 + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} 1001 1590 1002 1591 graphemer@1.4.0: 1003 1592 resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} ··· 1017 1606 help-me@5.0.0: 1018 1607 resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} 1019 1608 1020 - html-escaper@3.0.3: 1021 - resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} 1022 - 1023 - htmlparser2@9.1.0: 1024 - resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==} 1025 - 1026 1609 http-errors@2.0.0: 1027 1610 resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} 1028 1611 engines: {node: '>= 0.8'} ··· 1033 1616 1034 1617 ieee754@1.2.1: 1035 1618 resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} 1619 + 1620 + ignore@5.3.2: 1621 + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} 1622 + engines: {node: '>= 4'} 1623 + 1624 + import-fresh@3.3.1: 1625 + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} 1626 + engines: {node: '>=6'} 1627 + 1628 + imurmurhash@0.1.4: 1629 + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 1630 + engines: {node: '>=0.8.19'} 1036 1631 1037 1632 inherits@2.0.4: 1038 1633 resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} ··· 1083 1678 resolution: {integrity: sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==} 1084 1679 engines: {node: 20 || >=22} 1085 1680 1681 + jiti@2.4.2: 1682 + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} 1683 + hasBin: true 1684 + 1086 1685 jose@5.10.0: 1087 1686 resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} 1088 1687 ··· 1090 1689 resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} 1091 1690 engines: {node: '>=10'} 1092 1691 1692 + js-tokens@4.0.0: 1693 + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 1694 + 1695 + js-yaml@4.1.0: 1696 + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} 1697 + hasBin: true 1698 + 1699 + jsesc@3.1.0: 1700 + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} 1701 + engines: {node: '>=6'} 1702 + hasBin: true 1703 + 1704 + json-buffer@3.0.1: 1705 + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} 1706 + 1707 + json-schema-traverse@0.4.1: 1708 + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 1709 + 1710 + json-stable-stringify-without-jsonify@1.0.1: 1711 + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} 1712 + 1713 + json5@2.2.3: 1714 + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} 1715 + engines: {node: '>=6'} 1716 + hasBin: true 1717 + 1718 + keyv@4.5.4: 1719 + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} 1720 + 1093 1721 kysely@0.27.5: 1094 1722 resolution: {integrity: sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA==} 1095 1723 engines: {node: '>=14.0.0'} 1096 1724 1725 + levn@0.4.1: 1726 + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 1727 + engines: {node: '>= 0.8.0'} 1728 + 1729 + lightningcss-darwin-arm64@1.29.1: 1730 + resolution: {integrity: sha512-HtR5XJ5A0lvCqYAoSv2QdZZyoHNttBpa5EP9aNuzBQeKGfbyH5+UipLWvVzpP4Uml5ej4BYs5I9Lco9u1fECqw==} 1731 + engines: {node: '>= 12.0.0'} 1732 + cpu: [arm64] 1733 + os: [darwin] 1734 + 1735 + lightningcss-darwin-x64@1.29.1: 1736 + resolution: {integrity: sha512-k33G9IzKUpHy/J/3+9MCO4e+PzaFblsgBjSGlpAaFikeBFm8B/CkO3cKU9oI4g+fjS2KlkLM/Bza9K/aw8wsNA==} 1737 + engines: {node: '>= 12.0.0'} 1738 + cpu: [x64] 1739 + os: [darwin] 1740 + 1741 + lightningcss-freebsd-x64@1.29.1: 1742 + resolution: {integrity: sha512-0SUW22fv/8kln2LnIdOCmSuXnxgxVC276W5KLTwoehiO0hxkacBxjHOL5EtHD8BAXg2BvuhsJPmVMasvby3LiQ==} 1743 + engines: {node: '>= 12.0.0'} 1744 + cpu: [x64] 1745 + os: [freebsd] 1746 + 1747 + lightningcss-linux-arm-gnueabihf@1.29.1: 1748 + resolution: {integrity: sha512-sD32pFvlR0kDlqsOZmYqH/68SqUMPNj+0pucGxToXZi4XZgZmqeX/NkxNKCPsswAXU3UeYgDSpGhu05eAufjDg==} 1749 + engines: {node: '>= 12.0.0'} 1750 + cpu: [arm] 1751 + os: [linux] 1752 + 1753 + lightningcss-linux-arm64-gnu@1.29.1: 1754 + resolution: {integrity: sha512-0+vClRIZ6mmJl/dxGuRsE197o1HDEeeRk6nzycSy2GofC2JsY4ifCRnvUWf/CUBQmlrvMzt6SMQNMSEu22csWQ==} 1755 + engines: {node: '>= 12.0.0'} 1756 + cpu: [arm64] 1757 + os: [linux] 1758 + 1759 + lightningcss-linux-arm64-musl@1.29.1: 1760 + resolution: {integrity: sha512-UKMFrG4rL/uHNgelBsDwJcBqVpzNJbzsKkbI3Ja5fg00sgQnHw/VrzUTEc4jhZ+AN2BvQYz/tkHu4vt1kLuJyw==} 1761 + engines: {node: '>= 12.0.0'} 1762 + cpu: [arm64] 1763 + os: [linux] 1764 + 1765 + lightningcss-linux-x64-gnu@1.29.1: 1766 + resolution: {integrity: sha512-u1S+xdODy/eEtjADqirA774y3jLcm8RPtYztwReEXoZKdzgsHYPl0s5V52Tst+GKzqjebkULT86XMSxejzfISw==} 1767 + engines: {node: '>= 12.0.0'} 1768 + cpu: [x64] 1769 + os: [linux] 1770 + 1771 + lightningcss-linux-x64-musl@1.29.1: 1772 + resolution: {integrity: sha512-L0Tx0DtaNUTzXv0lbGCLB/c/qEADanHbu4QdcNOXLIe1i8i22rZRpbT3gpWYsCh9aSL9zFujY/WmEXIatWvXbw==} 1773 + engines: {node: '>= 12.0.0'} 1774 + cpu: [x64] 1775 + os: [linux] 1776 + 1777 + lightningcss-win32-arm64-msvc@1.29.1: 1778 + resolution: {integrity: sha512-QoOVnkIEFfbW4xPi+dpdft/zAKmgLgsRHfJalEPYuJDOWf7cLQzYg0DEh8/sn737FaeMJxHZRc1oBreiwZCjog==} 1779 + engines: {node: '>= 12.0.0'} 1780 + cpu: [arm64] 1781 + os: [win32] 1782 + 1783 + lightningcss-win32-x64-msvc@1.29.1: 1784 + resolution: {integrity: sha512-NygcbThNBe4JElP+olyTI/doBNGJvLs3bFCRPdvuCcxZCcCZ71B858IHpdm7L1btZex0FvCmM17FK98Y9MRy1Q==} 1785 + engines: {node: '>= 12.0.0'} 1786 + cpu: [x64] 1787 + os: [win32] 1788 + 1789 + lightningcss@1.29.1: 1790 + resolution: {integrity: sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q==} 1791 + engines: {node: '>= 12.0.0'} 1792 + 1097 1793 lilconfig@3.1.3: 1098 1794 resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} 1099 1795 engines: {node: '>=14'} ··· 1105 1801 resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} 1106 1802 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1107 1803 1804 + locate-path@6.0.0: 1805 + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 1806 + engines: {node: '>=10'} 1807 + 1808 + lodash.merge@4.6.2: 1809 + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 1810 + 1108 1811 lodash.sortby@4.7.0: 1109 1812 resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} 1813 + 1814 + lodash@4.17.21: 1815 + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 1110 1816 1111 1817 lru-cache@10.4.3: 1112 1818 resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} ··· 1115 1821 resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==} 1116 1822 engines: {node: 20 || >=22} 1117 1823 1824 + lru-cache@5.1.1: 1825 + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} 1826 + 1118 1827 make-error@1.3.6: 1119 1828 resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} 1120 1829 ··· 1162 1871 resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} 1163 1872 engines: {node: 20 || >=22} 1164 1873 1874 + minimatch@3.1.2: 1875 + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 1876 + 1165 1877 minimatch@5.1.6: 1166 1878 resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} 1167 1879 engines: {node: '>=10'} ··· 1200 1912 mz@2.7.0: 1201 1913 resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} 1202 1914 1915 + nanoid@3.3.8: 1916 + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} 1917 + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 1918 + hasBin: true 1919 + 1203 1920 napi-build-utils@2.0.0: 1204 1921 resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} 1922 + 1923 + natural-compare@1.4.0: 1924 + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 1205 1925 1206 1926 negotiator@0.6.3: 1207 1927 resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} ··· 1215 1935 resolution: {integrity: sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==} 1216 1936 hasBin: true 1217 1937 1938 + node-releases@2.0.19: 1939 + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} 1940 + 1941 + normalize-range@0.1.2: 1942 + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} 1943 + engines: {node: '>=0.10.0'} 1944 + 1218 1945 object-assign@4.1.1: 1219 1946 resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 1220 1947 engines: {node: '>=0.10.0'} ··· 1234 1961 once@1.4.0: 1235 1962 resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 1236 1963 1964 + optionator@0.9.4: 1965 + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} 1966 + engines: {node: '>= 0.8.0'} 1967 + 1237 1968 p-finally@1.0.0: 1238 1969 resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} 1239 1970 engines: {node: '>=4'} 1240 1971 1972 + p-limit@3.1.0: 1973 + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 1974 + engines: {node: '>=10'} 1975 + 1976 + p-locate@5.0.0: 1977 + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 1978 + engines: {node: '>=10'} 1979 + 1241 1980 p-queue@6.6.2: 1242 1981 resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} 1243 1982 engines: {node: '>=8'} ··· 1249 1988 package-json-from-dist@1.0.1: 1250 1989 resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} 1251 1990 1991 + parent-module@1.0.1: 1992 + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 1993 + engines: {node: '>=6'} 1994 + 1252 1995 parseurl@1.3.3: 1253 1996 resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} 1254 1997 engines: {node: '>= 0.8'} 1255 1998 1256 1999 path-browserify@1.0.1: 1257 2000 resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} 2001 + 2002 + path-exists@4.0.0: 2003 + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 2004 + engines: {node: '>=8'} 1258 2005 1259 2006 path-key@3.1.1: 1260 2007 resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} ··· 1328 2075 yaml: 1329 2076 optional: true 1330 2077 2078 + postcss-value-parser@4.2.0: 2079 + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} 2080 + 2081 + postcss@8.5.3: 2082 + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} 2083 + engines: {node: ^10 || ^12 || >=14} 2084 + 1331 2085 prebuild-install@7.1.3: 1332 2086 resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} 1333 2087 engines: {node: '>=10'} 1334 2088 hasBin: true 1335 2089 2090 + prelude-ls@1.2.1: 2091 + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 2092 + engines: {node: '>= 0.8.0'} 2093 + 2094 + prettier-plugin-tailwindcss@0.6.11: 2095 + resolution: {integrity: sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==} 2096 + engines: {node: '>=14.21.3'} 2097 + peerDependencies: 2098 + '@ianvs/prettier-plugin-sort-imports': '*' 2099 + '@prettier/plugin-pug': '*' 2100 + '@shopify/prettier-plugin-liquid': '*' 2101 + '@trivago/prettier-plugin-sort-imports': '*' 2102 + '@zackad/prettier-plugin-twig': '*' 2103 + prettier: ^3.0 2104 + prettier-plugin-astro: '*' 2105 + prettier-plugin-css-order: '*' 2106 + prettier-plugin-import-sort: '*' 2107 + prettier-plugin-jsdoc: '*' 2108 + prettier-plugin-marko: '*' 2109 + prettier-plugin-multiline-arrays: '*' 2110 + prettier-plugin-organize-attributes: '*' 2111 + prettier-plugin-organize-imports: '*' 2112 + prettier-plugin-sort-imports: '*' 2113 + prettier-plugin-style-order: '*' 2114 + prettier-plugin-svelte: '*' 2115 + peerDependenciesMeta: 2116 + '@ianvs/prettier-plugin-sort-imports': 2117 + optional: true 2118 + '@prettier/plugin-pug': 2119 + optional: true 2120 + '@shopify/prettier-plugin-liquid': 2121 + optional: true 2122 + '@trivago/prettier-plugin-sort-imports': 2123 + optional: true 2124 + '@zackad/prettier-plugin-twig': 2125 + optional: true 2126 + prettier-plugin-astro: 2127 + optional: true 2128 + prettier-plugin-css-order: 2129 + optional: true 2130 + prettier-plugin-import-sort: 2131 + optional: true 2132 + prettier-plugin-jsdoc: 2133 + optional: true 2134 + prettier-plugin-marko: 2135 + optional: true 2136 + prettier-plugin-multiline-arrays: 2137 + optional: true 2138 + prettier-plugin-organize-attributes: 2139 + optional: true 2140 + prettier-plugin-organize-imports: 2141 + optional: true 2142 + prettier-plugin-sort-imports: 2143 + optional: true 2144 + prettier-plugin-style-order: 2145 + optional: true 2146 + prettier-plugin-svelte: 2147 + optional: true 2148 + 1336 2149 prettier@3.5.2: 1337 2150 resolution: {integrity: sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==} 1338 2151 engines: {node: '>=14'} ··· 1387 2200 resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} 1388 2201 hasBin: true 1389 2202 2203 + react-dom@19.0.0: 2204 + resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} 2205 + peerDependencies: 2206 + react: ^19.0.0 2207 + 2208 + react-refresh@0.14.2: 2209 + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} 2210 + engines: {node: '>=0.10.0'} 2211 + 2212 + react-router-dom@7.2.0: 2213 + resolution: {integrity: sha512-cU7lTxETGtQRQbafJubvZKHEn5izNABxZhBY0Jlzdv0gqQhCPQt2J8aN5ZPjS6mQOXn5NnirWNh+FpE8TTYN0Q==} 2214 + engines: {node: '>=20.0.0'} 2215 + peerDependencies: 2216 + react: '>=18' 2217 + react-dom: '>=18' 2218 + 2219 + react-router@7.2.0: 2220 + resolution: {integrity: sha512-fXyqzPgCPZbqhrk7k3hPcCpYIlQ2ugIXDboHUzhJISFVy2DEPsmHgN588MyGmkIOv3jDgNfUE3kJi83L28s/LQ==} 2221 + engines: {node: '>=20.0.0'} 2222 + peerDependencies: 2223 + react: '>=18' 2224 + react-dom: '>=18' 2225 + peerDependenciesMeta: 2226 + react-dom: 2227 + optional: true 2228 + 2229 + react@19.0.0: 2230 + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} 2231 + engines: {node: '>=0.10.0'} 2232 + 1390 2233 readable-stream@3.6.2: 1391 2234 resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} 1392 2235 engines: {node: '>= 6'} ··· 1403 2246 resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} 1404 2247 engines: {node: '>= 12.13.0'} 1405 2248 2249 + require-directory@2.1.1: 2250 + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} 2251 + engines: {node: '>=0.10.0'} 2252 + 2253 + resolve-from@4.0.0: 2254 + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 2255 + engines: {node: '>=4'} 2256 + 1406 2257 resolve-from@5.0.0: 1407 2258 resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} 1408 2259 engines: {node: '>=8'} ··· 1426 2277 1427 2278 run-parallel@1.2.0: 1428 2279 resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 2280 + 2281 + rxjs@7.8.2: 2282 + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} 1429 2283 1430 2284 safe-buffer@5.2.1: 1431 2285 resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} ··· 1437 2291 safer-buffer@2.1.2: 1438 2292 resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} 1439 2293 2294 + scheduler@0.25.0: 2295 + resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} 2296 + 1440 2297 secure-json-parse@2.7.0: 1441 2298 resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} 2299 + 2300 + semver@6.3.1: 2301 + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 2302 + hasBin: true 1442 2303 1443 2304 semver@7.7.1: 1444 2305 resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} ··· 1453 2314 resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} 1454 2315 engines: {node: '>= 0.8.0'} 1455 2316 2317 + set-cookie-parser@2.7.1: 2318 + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} 2319 + 1456 2320 setprototypeof@1.2.0: 1457 2321 resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} 1458 2322 ··· 1463 2327 shebang-regex@3.0.0: 1464 2328 resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1465 2329 engines: {node: '>=8'} 2330 + 2331 + shell-quote@1.8.2: 2332 + resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==} 2333 + engines: {node: '>= 0.4'} 1466 2334 1467 2335 side-channel-list@1.0.0: 1468 2336 resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} ··· 1496 2364 sonic-boom@4.2.0: 1497 2365 resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} 1498 2366 2367 + source-map-js@1.2.1: 2368 + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 2369 + engines: {node: '>=0.10.0'} 2370 + 1499 2371 source-map@0.8.0-beta.0: 1500 2372 resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} 1501 2373 engines: {node: '>= 8'} ··· 1544 2416 resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 1545 2417 engines: {node: '>=8'} 1546 2418 2419 + supports-color@8.1.1: 2420 + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} 2421 + engines: {node: '>=10'} 2422 + 2423 + tailwindcss@4.0.9: 2424 + resolution: {integrity: sha512-12laZu+fv1ONDRoNR9ipTOpUD7RN9essRVkX36sjxuRUInpN7hIiHN4lBd/SIFjbISvnXzp8h/hXzmU8SQQYhw==} 2425 + 2426 + tapable@2.2.1: 2427 + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} 2428 + engines: {node: '>=6'} 2429 + 1547 2430 tar-fs@2.1.2: 1548 2431 resolution: {integrity: sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==} 1549 2432 ··· 1590 2473 resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} 1591 2474 hasBin: true 1592 2475 2476 + ts-api-utils@2.0.1: 2477 + resolution: {integrity: sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==} 2478 + engines: {node: '>=18.12'} 2479 + peerDependencies: 2480 + typescript: '>=4.8.4' 2481 + 1593 2482 ts-interface-checker@0.1.13: 1594 2483 resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} 1595 2484 ··· 1613 2502 tslib@2.6.2: 1614 2503 resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} 1615 2504 2505 + tslib@2.8.1: 2506 + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 2507 + 1616 2508 tsup@8.4.0: 1617 2509 resolution: {integrity: sha512-b+eZbPCjz10fRryaAA7C8xlIHnf8VnsaRqydheLIqwG/Mcpfk8Z5zp3HayX7GaTygkigHl5cBUs+IhcySiIexQ==} 1618 2510 engines: {node: '>=18'} ··· 1640 2532 tunnel-agent@0.6.0: 1641 2533 resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} 1642 2534 2535 + turbo-stream@2.4.0: 2536 + resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==} 2537 + 2538 + type-check@0.4.0: 2539 + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 2540 + engines: {node: '>= 0.8.0'} 2541 + 1643 2542 type-is@1.6.18: 1644 2543 resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} 1645 2544 engines: {node: '>= 0.6'} ··· 1648 2547 resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} 1649 2548 engines: {node: '>=14.17'} 1650 2549 hasBin: true 1651 - 1652 - udomdiff@1.1.2: 1653 - resolution: {integrity: sha512-v+Z8Jal+GtmKGtJ34GIQlCJAxrDt9kbjpNsNvYoAXFyr4gNfWlD4uJJuoNNu/0UTVaKvQwHaSU095YDl71lKPw==} 1654 - 1655 - uhtml@4.7.0: 1656 - resolution: {integrity: sha512-3j0YIvbu863FB27mwnuLcKK0zPsHVQWwUs/GFanVz/QSwsItT/lOcGKmIdpqlcfWpYBCBoMEdfK0vIN/P2kCmg==} 1657 2550 1658 2551 uint8arrays@3.0.0: 1659 2552 resolution: {integrity: sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==} ··· 1672 2565 resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} 1673 2566 engines: {node: '>= 0.8'} 1674 2567 2568 + update-browserslist-db@1.1.3: 2569 + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} 2570 + hasBin: true 2571 + peerDependencies: 2572 + browserslist: '>= 4.21.0' 2573 + 2574 + uri-js@4.4.1: 2575 + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 2576 + 1675 2577 util-deprecate@1.0.2: 1676 2578 resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 1677 2579 ··· 1689 2591 resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} 1690 2592 engines: {node: '>= 0.8'} 1691 2593 2594 + vite@6.2.0: 2595 + resolution: {integrity: sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==} 2596 + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 2597 + hasBin: true 2598 + peerDependencies: 2599 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 2600 + jiti: '>=1.21.0' 2601 + less: '*' 2602 + lightningcss: ^1.21.0 2603 + sass: '*' 2604 + sass-embedded: '*' 2605 + stylus: '*' 2606 + sugarss: '*' 2607 + terser: ^5.16.0 2608 + tsx: ^4.8.1 2609 + yaml: ^2.4.2 2610 + peerDependenciesMeta: 2611 + '@types/node': 2612 + optional: true 2613 + jiti: 2614 + optional: true 2615 + less: 2616 + optional: true 2617 + lightningcss: 2618 + optional: true 2619 + sass: 2620 + optional: true 2621 + sass-embedded: 2622 + optional: true 2623 + stylus: 2624 + optional: true 2625 + sugarss: 2626 + optional: true 2627 + terser: 2628 + optional: true 2629 + tsx: 2630 + optional: true 2631 + yaml: 2632 + optional: true 2633 + 1692 2634 webidl-conversions@4.0.2: 1693 2635 resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} 1694 2636 ··· 1699 2641 resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1700 2642 engines: {node: '>= 8'} 1701 2643 hasBin: true 2644 + 2645 + word-wrap@1.2.5: 2646 + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} 2647 + engines: {node: '>=0.10.0'} 1702 2648 1703 2649 wrap-ansi@7.0.0: 1704 2650 resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} ··· 1723 2669 utf-8-validate: 1724 2670 optional: true 1725 2671 2672 + y18n@5.0.8: 2673 + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} 2674 + engines: {node: '>=10'} 2675 + 2676 + yallist@3.1.1: 2677 + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} 2678 + 2679 + yargs-parser@21.1.1: 2680 + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} 2681 + engines: {node: '>=12'} 2682 + 2683 + yargs@17.7.2: 2684 + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} 2685 + engines: {node: '>=12'} 2686 + 1726 2687 yesno@0.4.0: 1727 2688 resolution: {integrity: sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA==} 1728 2689 ··· 1730 2691 resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} 1731 2692 engines: {node: '>=6'} 1732 2693 2694 + yocto-queue@0.1.0: 2695 + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 2696 + engines: {node: '>=10'} 2697 + 1733 2698 zod@3.24.2: 1734 2699 resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} 1735 2700 1736 2701 snapshots: 2702 + 2703 + '@ampproject/remapping@2.3.0': 2704 + dependencies: 2705 + '@jridgewell/gen-mapping': 0.3.8 2706 + '@jridgewell/trace-mapping': 0.3.25 1737 2707 1738 2708 '@atproto-labs/did-resolver@0.1.10': 1739 2709 dependencies: ··· 1949 2919 '@atproto/lexicon': 0.4.7 1950 2920 zod: 3.24.2 1951 2921 2922 + '@babel/code-frame@7.26.2': 2923 + dependencies: 2924 + '@babel/helper-validator-identifier': 7.25.9 2925 + js-tokens: 4.0.0 2926 + picocolors: 1.1.1 2927 + 2928 + '@babel/compat-data@7.26.8': {} 2929 + 2930 + '@babel/core@7.26.9': 2931 + dependencies: 2932 + '@ampproject/remapping': 2.3.0 2933 + '@babel/code-frame': 7.26.2 2934 + '@babel/generator': 7.26.9 2935 + '@babel/helper-compilation-targets': 7.26.5 2936 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.9) 2937 + '@babel/helpers': 7.26.9 2938 + '@babel/parser': 7.26.9 2939 + '@babel/template': 7.26.9 2940 + '@babel/traverse': 7.26.9 2941 + '@babel/types': 7.26.9 2942 + convert-source-map: 2.0.0 2943 + debug: 4.4.0 2944 + gensync: 1.0.0-beta.2 2945 + json5: 2.2.3 2946 + semver: 6.3.1 2947 + transitivePeerDependencies: 2948 + - supports-color 2949 + 2950 + '@babel/generator@7.26.9': 2951 + dependencies: 2952 + '@babel/parser': 7.26.9 2953 + '@babel/types': 7.26.9 2954 + '@jridgewell/gen-mapping': 0.3.8 2955 + '@jridgewell/trace-mapping': 0.3.25 2956 + jsesc: 3.1.0 2957 + 2958 + '@babel/helper-compilation-targets@7.26.5': 2959 + dependencies: 2960 + '@babel/compat-data': 7.26.8 2961 + '@babel/helper-validator-option': 7.25.9 2962 + browserslist: 4.24.4 2963 + lru-cache: 5.1.1 2964 + semver: 6.3.1 2965 + 2966 + '@babel/helper-module-imports@7.25.9': 2967 + dependencies: 2968 + '@babel/traverse': 7.26.9 2969 + '@babel/types': 7.26.9 2970 + transitivePeerDependencies: 2971 + - supports-color 2972 + 2973 + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.9)': 2974 + dependencies: 2975 + '@babel/core': 7.26.9 2976 + '@babel/helper-module-imports': 7.25.9 2977 + '@babel/helper-validator-identifier': 7.25.9 2978 + '@babel/traverse': 7.26.9 2979 + transitivePeerDependencies: 2980 + - supports-color 2981 + 2982 + '@babel/helper-plugin-utils@7.26.5': {} 2983 + 2984 + '@babel/helper-string-parser@7.25.9': {} 2985 + 2986 + '@babel/helper-validator-identifier@7.25.9': {} 2987 + 2988 + '@babel/helper-validator-option@7.25.9': {} 2989 + 2990 + '@babel/helpers@7.26.9': 2991 + dependencies: 2992 + '@babel/template': 7.26.9 2993 + '@babel/types': 7.26.9 2994 + 2995 + '@babel/parser@7.26.9': 2996 + dependencies: 2997 + '@babel/types': 7.26.9 2998 + 2999 + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.9)': 3000 + dependencies: 3001 + '@babel/core': 7.26.9 3002 + '@babel/helper-plugin-utils': 7.26.5 3003 + 3004 + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.9)': 3005 + dependencies: 3006 + '@babel/core': 7.26.9 3007 + '@babel/helper-plugin-utils': 7.26.5 3008 + 3009 + '@babel/template@7.26.9': 3010 + dependencies: 3011 + '@babel/code-frame': 7.26.2 3012 + '@babel/parser': 7.26.9 3013 + '@babel/types': 7.26.9 3014 + 3015 + '@babel/traverse@7.26.9': 3016 + dependencies: 3017 + '@babel/code-frame': 7.26.2 3018 + '@babel/generator': 7.26.9 3019 + '@babel/parser': 7.26.9 3020 + '@babel/template': 7.26.9 3021 + '@babel/types': 7.26.9 3022 + debug: 4.4.0 3023 + globals: 11.12.0 3024 + transitivePeerDependencies: 3025 + - supports-color 3026 + 3027 + '@babel/types@7.26.9': 3028 + dependencies: 3029 + '@babel/helper-string-parser': 7.25.9 3030 + '@babel/helper-validator-identifier': 7.25.9 3031 + 1952 3032 '@cbor-extract/cbor-extract-darwin-arm64@2.2.0': 1953 3033 optional: true 1954 3034 ··· 2046 3126 '@esbuild/win32-x64@0.25.0': 2047 3127 optional: true 2048 3128 3129 + '@eslint-community/eslint-utils@4.4.1(eslint@9.21.0(jiti@2.4.2))': 3130 + dependencies: 3131 + eslint: 9.21.0(jiti@2.4.2) 3132 + eslint-visitor-keys: 3.4.3 3133 + 3134 + '@eslint-community/regexpp@4.12.1': {} 3135 + 3136 + '@eslint/config-array@0.19.2': 3137 + dependencies: 3138 + '@eslint/object-schema': 2.1.6 3139 + debug: 4.4.0 3140 + minimatch: 3.1.2 3141 + transitivePeerDependencies: 3142 + - supports-color 3143 + 3144 + '@eslint/core@0.12.0': 3145 + dependencies: 3146 + '@types/json-schema': 7.0.15 3147 + 3148 + '@eslint/eslintrc@3.3.0': 3149 + dependencies: 3150 + ajv: 6.12.6 3151 + debug: 4.4.0 3152 + espree: 10.3.0 3153 + globals: 14.0.0 3154 + ignore: 5.3.2 3155 + import-fresh: 3.3.1 3156 + js-yaml: 4.1.0 3157 + minimatch: 3.1.2 3158 + strip-json-comments: 3.1.1 3159 + transitivePeerDependencies: 3160 + - supports-color 3161 + 3162 + '@eslint/js@9.21.0': {} 3163 + 3164 + '@eslint/object-schema@2.1.6': {} 3165 + 3166 + '@eslint/plugin-kit@0.2.7': 3167 + dependencies: 3168 + '@eslint/core': 0.12.0 3169 + levn: 0.4.1 3170 + 3171 + '@humanfs/core@0.19.1': {} 3172 + 3173 + '@humanfs/node@0.16.6': 3174 + dependencies: 3175 + '@humanfs/core': 0.19.1 3176 + '@humanwhocodes/retry': 0.3.1 3177 + 3178 + '@humanwhocodes/module-importer@1.0.1': {} 3179 + 3180 + '@humanwhocodes/retry@0.3.1': {} 3181 + 3182 + '@humanwhocodes/retry@0.4.2': {} 3183 + 3184 + '@ianvs/prettier-plugin-sort-imports@4.4.1(prettier@3.5.2)': 3185 + dependencies: 3186 + '@babel/generator': 7.26.9 3187 + '@babel/parser': 7.26.9 3188 + '@babel/traverse': 7.26.9 3189 + '@babel/types': 7.26.9 3190 + prettier: 3.5.2 3191 + semver: 7.7.1 3192 + transitivePeerDependencies: 3193 + - supports-color 3194 + 2049 3195 '@ipld/car@3.2.4': 2050 3196 dependencies: 2051 3197 '@ipld/dag-cbor': 7.0.3 ··· 2109 3255 '@pkgjs/parseargs@0.11.0': 2110 3256 optional: true 2111 3257 2112 - '@preact/signals-core@1.8.0': 2113 - optional: true 2114 - 2115 3258 '@rollup/rollup-android-arm-eabi@4.34.9': 2116 3259 optional: true 2117 3260 ··· 2169 3312 '@rollup/rollup-win32-x64-msvc@4.34.9': 2170 3313 optional: true 2171 3314 3315 + '@tailwindcss/node@4.0.9': 3316 + dependencies: 3317 + enhanced-resolve: 5.18.1 3318 + jiti: 2.4.2 3319 + tailwindcss: 4.0.9 3320 + 3321 + '@tailwindcss/oxide-android-arm64@4.0.9': 3322 + optional: true 3323 + 3324 + '@tailwindcss/oxide-darwin-arm64@4.0.9': 3325 + optional: true 3326 + 3327 + '@tailwindcss/oxide-darwin-x64@4.0.9': 3328 + optional: true 3329 + 3330 + '@tailwindcss/oxide-freebsd-x64@4.0.9': 3331 + optional: true 3332 + 3333 + '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.9': 3334 + optional: true 3335 + 3336 + '@tailwindcss/oxide-linux-arm64-gnu@4.0.9': 3337 + optional: true 3338 + 3339 + '@tailwindcss/oxide-linux-arm64-musl@4.0.9': 3340 + optional: true 3341 + 3342 + '@tailwindcss/oxide-linux-x64-gnu@4.0.9': 3343 + optional: true 3344 + 3345 + '@tailwindcss/oxide-linux-x64-musl@4.0.9': 3346 + optional: true 3347 + 3348 + '@tailwindcss/oxide-win32-arm64-msvc@4.0.9': 3349 + optional: true 3350 + 3351 + '@tailwindcss/oxide-win32-x64-msvc@4.0.9': 3352 + optional: true 3353 + 3354 + '@tailwindcss/oxide@4.0.9': 3355 + optionalDependencies: 3356 + '@tailwindcss/oxide-android-arm64': 4.0.9 3357 + '@tailwindcss/oxide-darwin-arm64': 4.0.9 3358 + '@tailwindcss/oxide-darwin-x64': 4.0.9 3359 + '@tailwindcss/oxide-freebsd-x64': 4.0.9 3360 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.0.9 3361 + '@tailwindcss/oxide-linux-arm64-gnu': 4.0.9 3362 + '@tailwindcss/oxide-linux-arm64-musl': 4.0.9 3363 + '@tailwindcss/oxide-linux-x64-gnu': 4.0.9 3364 + '@tailwindcss/oxide-linux-x64-musl': 4.0.9 3365 + '@tailwindcss/oxide-win32-arm64-msvc': 4.0.9 3366 + '@tailwindcss/oxide-win32-x64-msvc': 4.0.9 3367 + 3368 + '@tailwindcss/vite@4.0.9(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.3))': 3369 + dependencies: 3370 + '@tailwindcss/node': 4.0.9 3371 + '@tailwindcss/oxide': 4.0.9 3372 + lightningcss: 1.29.1 3373 + tailwindcss: 4.0.9 3374 + vite: 6.2.0(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.3) 3375 + 3376 + '@tanstack/query-core@5.66.11': {} 3377 + 3378 + '@tanstack/react-query@5.66.11(react@19.0.0)': 3379 + dependencies: 3380 + '@tanstack/query-core': 5.66.11 3381 + react: 19.0.0 3382 + 2172 3383 '@ts-morph/common@0.17.0': 2173 3384 dependencies: 2174 3385 fast-glob: 3.3.3 ··· 2184 3395 2185 3396 '@tsconfig/node16@1.0.4': {} 2186 3397 3398 + '@types/babel__core@7.20.5': 3399 + dependencies: 3400 + '@babel/parser': 7.26.9 3401 + '@babel/types': 7.26.9 3402 + '@types/babel__generator': 7.6.8 3403 + '@types/babel__template': 7.4.4 3404 + '@types/babel__traverse': 7.20.6 3405 + 3406 + '@types/babel__generator@7.6.8': 3407 + dependencies: 3408 + '@babel/types': 7.26.9 3409 + 3410 + '@types/babel__template@7.4.4': 3411 + dependencies: 3412 + '@babel/parser': 7.26.9 3413 + '@babel/types': 7.26.9 3414 + 3415 + '@types/babel__traverse@7.20.6': 3416 + dependencies: 3417 + '@babel/types': 7.26.9 3418 + 2187 3419 '@types/better-sqlite3@7.6.12': 2188 3420 dependencies: 2189 3421 '@types/node': 22.13.8 ··· 2197 3429 dependencies: 2198 3430 '@types/node': 22.13.8 2199 3431 3432 + '@types/cookie@0.6.0': {} 3433 + 3434 + '@types/cors@2.8.17': 3435 + dependencies: 3436 + '@types/node': 22.13.8 3437 + 2200 3438 '@types/estree@1.0.6': {} 2201 3439 2202 3440 '@types/express-serve-static-core@5.0.6': ··· 2214 3452 '@types/serve-static': 1.15.7 2215 3453 2216 3454 '@types/http-errors@2.0.4': {} 3455 + 3456 + '@types/json-schema@7.0.15': {} 2217 3457 2218 3458 '@types/mime@1.3.5': {} 2219 3459 ··· 2225 3465 2226 3466 '@types/range-parser@1.2.7': {} 2227 3467 3468 + '@types/react-dom@19.0.4(@types/react@19.0.10)': 3469 + dependencies: 3470 + '@types/react': 19.0.10 3471 + 3472 + '@types/react@19.0.10': 3473 + dependencies: 3474 + csstype: 3.1.3 3475 + 2228 3476 '@types/send@0.17.4': 2229 3477 dependencies: 2230 3478 '@types/mime': 1.3.5 ··· 2236 3484 '@types/node': 22.13.8 2237 3485 '@types/send': 0.17.4 2238 3486 2239 - '@webreflection/signal@2.1.2': 2240 - optional: true 3487 + '@typescript-eslint/eslint-plugin@8.25.0(@typescript-eslint/parser@8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2)': 3488 + dependencies: 3489 + '@eslint-community/regexpp': 4.12.1 3490 + '@typescript-eslint/parser': 8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2) 3491 + '@typescript-eslint/scope-manager': 8.25.0 3492 + '@typescript-eslint/type-utils': 8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2) 3493 + '@typescript-eslint/utils': 8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2) 3494 + '@typescript-eslint/visitor-keys': 8.25.0 3495 + eslint: 9.21.0(jiti@2.4.2) 3496 + graphemer: 1.4.0 3497 + ignore: 5.3.2 3498 + natural-compare: 1.4.0 3499 + ts-api-utils: 2.0.1(typescript@5.8.2) 3500 + typescript: 5.8.2 3501 + transitivePeerDependencies: 3502 + - supports-color 2241 3503 2242 - '@webreflection/uparser@0.4.0': 3504 + '@typescript-eslint/parser@8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2)': 2243 3505 dependencies: 2244 - domconstants: 1.1.6 3506 + '@typescript-eslint/scope-manager': 8.25.0 3507 + '@typescript-eslint/types': 8.25.0 3508 + '@typescript-eslint/typescript-estree': 8.25.0(typescript@5.8.2) 3509 + '@typescript-eslint/visitor-keys': 8.25.0 3510 + debug: 4.4.0 3511 + eslint: 9.21.0(jiti@2.4.2) 3512 + typescript: 5.8.2 3513 + transitivePeerDependencies: 3514 + - supports-color 3515 + 3516 + '@typescript-eslint/scope-manager@8.25.0': 3517 + dependencies: 3518 + '@typescript-eslint/types': 8.25.0 3519 + '@typescript-eslint/visitor-keys': 8.25.0 3520 + 3521 + '@typescript-eslint/type-utils@8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2)': 3522 + dependencies: 3523 + '@typescript-eslint/typescript-estree': 8.25.0(typescript@5.8.2) 3524 + '@typescript-eslint/utils': 8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2) 3525 + debug: 4.4.0 3526 + eslint: 9.21.0(jiti@2.4.2) 3527 + ts-api-utils: 2.0.1(typescript@5.8.2) 3528 + typescript: 5.8.2 3529 + transitivePeerDependencies: 3530 + - supports-color 3531 + 3532 + '@typescript-eslint/types@8.25.0': {} 3533 + 3534 + '@typescript-eslint/typescript-estree@8.25.0(typescript@5.8.2)': 3535 + dependencies: 3536 + '@typescript-eslint/types': 8.25.0 3537 + '@typescript-eslint/visitor-keys': 8.25.0 3538 + debug: 4.4.0 3539 + fast-glob: 3.3.3 3540 + is-glob: 4.0.3 3541 + minimatch: 9.0.5 3542 + semver: 7.7.1 3543 + ts-api-utils: 2.0.1(typescript@5.8.2) 3544 + typescript: 5.8.2 3545 + transitivePeerDependencies: 3546 + - supports-color 3547 + 3548 + '@typescript-eslint/utils@8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2)': 3549 + dependencies: 3550 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.21.0(jiti@2.4.2)) 3551 + '@typescript-eslint/scope-manager': 8.25.0 3552 + '@typescript-eslint/types': 8.25.0 3553 + '@typescript-eslint/typescript-estree': 8.25.0(typescript@5.8.2) 3554 + eslint: 9.21.0(jiti@2.4.2) 3555 + typescript: 5.8.2 3556 + transitivePeerDependencies: 3557 + - supports-color 3558 + 3559 + '@typescript-eslint/visitor-keys@8.25.0': 3560 + dependencies: 3561 + '@typescript-eslint/types': 8.25.0 3562 + eslint-visitor-keys: 4.2.0 3563 + 3564 + '@vitejs/plugin-react@4.3.4(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.3))': 3565 + dependencies: 3566 + '@babel/core': 7.26.9 3567 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.9) 3568 + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.9) 3569 + '@types/babel__core': 7.20.5 3570 + react-refresh: 0.14.2 3571 + vite: 6.2.0(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.3) 3572 + transitivePeerDependencies: 3573 + - supports-color 2245 3574 2246 3575 abort-controller@3.0.0: 2247 3576 dependencies: ··· 2252 3581 mime-types: 2.1.35 2253 3582 negotiator: 0.6.3 2254 3583 3584 + acorn-jsx@5.3.2(acorn@8.14.0): 3585 + dependencies: 3586 + acorn: 8.14.0 3587 + 2255 3588 acorn-walk@8.3.4: 2256 3589 dependencies: 2257 3590 acorn: 8.14.0 2258 3591 2259 3592 acorn@8.14.0: {} 3593 + 3594 + ajv@6.12.6: 3595 + dependencies: 3596 + fast-deep-equal: 3.1.3 3597 + fast-json-stable-stringify: 2.1.0 3598 + json-schema-traverse: 0.4.1 3599 + uri-js: 4.4.1 2260 3600 2261 3601 ansi-regex@5.0.1: {} 2262 3602 ··· 2272 3612 2273 3613 arg@4.1.3: {} 2274 3614 3615 + argparse@2.0.1: {} 3616 + 2275 3617 array-flatten@1.1.1: {} 2276 3618 2277 3619 atomic-sleep@1.0.0: {} 2278 3620 3621 + autoprefixer@10.4.20(postcss@8.5.3): 3622 + dependencies: 3623 + browserslist: 4.24.4 3624 + caniuse-lite: 1.0.30001701 3625 + fraction.js: 4.3.7 3626 + normalize-range: 0.1.2 3627 + picocolors: 1.1.1 3628 + postcss: 8.5.3 3629 + postcss-value-parser: 4.2.0 3630 + 2279 3631 await-lock@2.2.2: {} 2280 3632 2281 3633 balanced-match@1.0.2: {} ··· 2314 3666 transitivePeerDependencies: 2315 3667 - supports-color 2316 3668 3669 + brace-expansion@1.1.11: 3670 + dependencies: 3671 + balanced-match: 1.0.2 3672 + concat-map: 0.0.1 3673 + 2317 3674 brace-expansion@2.0.1: 2318 3675 dependencies: 2319 3676 balanced-match: 1.0.2 ··· 2322 3679 dependencies: 2323 3680 fill-range: 7.1.1 2324 3681 3682 + browserslist@4.24.4: 3683 + dependencies: 3684 + caniuse-lite: 1.0.30001701 3685 + electron-to-chromium: 1.5.109 3686 + node-releases: 2.0.19 3687 + update-browserslist-db: 1.1.3(browserslist@4.24.4) 3688 + 2325 3689 buffer@5.7.1: 2326 3690 dependencies: 2327 3691 base64-js: 1.5.1 ··· 2351 3715 call-bind-apply-helpers: 1.0.2 2352 3716 get-intrinsic: 1.3.0 2353 3717 3718 + callsites@3.1.0: {} 3719 + 3720 + caniuse-lite@1.0.30001701: {} 3721 + 2354 3722 cbor-extract@2.2.0: 2355 3723 dependencies: 2356 3724 node-gyp-build-optional-packages: 5.1.1 ··· 2380 3748 2381 3749 chownr@1.1.4: {} 2382 3750 3751 + cliui@8.0.1: 3752 + dependencies: 3753 + string-width: 4.2.3 3754 + strip-ansi: 6.0.1 3755 + wrap-ansi: 7.0.0 3756 + 2383 3757 code-block-writer@11.0.3: {} 2384 3758 2385 3759 color-convert@2.0.1: ··· 2394 3768 2395 3769 commander@9.5.0: {} 2396 3770 3771 + concat-map@0.0.1: {} 3772 + 3773 + concurrently@9.1.2: 3774 + dependencies: 3775 + chalk: 4.1.2 3776 + lodash: 4.17.21 3777 + rxjs: 7.8.2 3778 + shell-quote: 1.8.2 3779 + supports-color: 8.1.1 3780 + tree-kill: 1.2.2 3781 + yargs: 17.7.2 3782 + 2397 3783 consola@3.4.0: {} 2398 3784 2399 3785 content-disposition@0.5.4: ··· 2402 3788 2403 3789 content-type@1.0.5: {} 2404 3790 3791 + convert-source-map@2.0.0: {} 3792 + 2405 3793 cookie-signature@1.0.6: {} 2406 3794 2407 3795 cookie@0.7.1: {} 2408 3796 2409 3797 cookie@0.7.2: {} 2410 3798 3799 + cookie@1.0.2: {} 3800 + 3801 + cors@2.8.5: 3802 + dependencies: 3803 + object-assign: 4.1.1 3804 + vary: 1.1.2 3805 + 2411 3806 create-require@1.1.1: {} 2412 3807 2413 3808 cross-spawn@7.0.6: ··· 2416 3811 shebang-command: 2.0.0 2417 3812 which: 2.0.2 2418 3813 2419 - custom-function@2.0.0: {} 3814 + csstype@3.1.3: {} 2420 3815 2421 3816 dateformat@4.6.3: {} 2422 3817 ··· 2434 3829 2435 3830 deep-extend@0.6.0: {} 2436 3831 3832 + deep-is@0.1.4: {} 3833 + 2437 3834 depd@2.0.0: {} 2438 3835 2439 3836 destroy@1.2.0: {} 2440 3837 3838 + detect-libc@1.0.3: {} 3839 + 2441 3840 detect-libc@2.0.3: {} 2442 3841 2443 3842 diff@4.0.2: {} 2444 3843 2445 - dom-serializer@2.0.0: 2446 - dependencies: 2447 - domelementtype: 2.3.0 2448 - domhandler: 5.0.3 2449 - entities: 4.5.0 2450 - 2451 - domconstants@1.1.6: {} 2452 - 2453 - domelementtype@2.3.0: {} 2454 - 2455 - domhandler@5.0.3: 2456 - dependencies: 2457 - domelementtype: 2.3.0 2458 - 2459 - domutils@3.2.2: 2460 - dependencies: 2461 - dom-serializer: 2.0.0 2462 - domelementtype: 2.3.0 2463 - domhandler: 5.0.3 2464 - 2465 3844 dotenv@16.4.7: {} 2466 3845 2467 3846 dunder-proto@1.0.1: ··· 2474 3853 2475 3854 ee-first@1.1.1: {} 2476 3855 3856 + electron-to-chromium@1.5.109: {} 3857 + 2477 3858 emoji-regex@8.0.0: {} 2478 3859 2479 3860 emoji-regex@9.2.2: {} ··· 2486 3867 dependencies: 2487 3868 once: 1.4.0 2488 3869 2489 - entities@4.5.0: {} 3870 + enhanced-resolve@5.18.1: 3871 + dependencies: 3872 + graceful-fs: 4.2.11 3873 + tapable: 2.2.1 2490 3874 2491 3875 envalid@8.0.0: 2492 3876 dependencies: ··· 2528 3912 '@esbuild/win32-ia32': 0.25.0 2529 3913 '@esbuild/win32-x64': 0.25.0 2530 3914 3915 + escalade@3.2.0: {} 3916 + 2531 3917 escape-html@1.0.3: {} 2532 3918 3919 + escape-string-regexp@4.0.0: {} 3920 + 3921 + eslint-plugin-react-hooks@5.2.0(eslint@9.21.0(jiti@2.4.2)): 3922 + dependencies: 3923 + eslint: 9.21.0(jiti@2.4.2) 3924 + 3925 + eslint-plugin-react-refresh@0.4.19(eslint@9.21.0(jiti@2.4.2)): 3926 + dependencies: 3927 + eslint: 9.21.0(jiti@2.4.2) 3928 + 3929 + eslint-scope@8.2.0: 3930 + dependencies: 3931 + esrecurse: 4.3.0 3932 + estraverse: 5.3.0 3933 + 3934 + eslint-visitor-keys@3.4.3: {} 3935 + 3936 + eslint-visitor-keys@4.2.0: {} 3937 + 3938 + eslint@9.21.0(jiti@2.4.2): 3939 + dependencies: 3940 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.21.0(jiti@2.4.2)) 3941 + '@eslint-community/regexpp': 4.12.1 3942 + '@eslint/config-array': 0.19.2 3943 + '@eslint/core': 0.12.0 3944 + '@eslint/eslintrc': 3.3.0 3945 + '@eslint/js': 9.21.0 3946 + '@eslint/plugin-kit': 0.2.7 3947 + '@humanfs/node': 0.16.6 3948 + '@humanwhocodes/module-importer': 1.0.1 3949 + '@humanwhocodes/retry': 0.4.2 3950 + '@types/estree': 1.0.6 3951 + '@types/json-schema': 7.0.15 3952 + ajv: 6.12.6 3953 + chalk: 4.1.2 3954 + cross-spawn: 7.0.6 3955 + debug: 4.4.0 3956 + escape-string-regexp: 4.0.0 3957 + eslint-scope: 8.2.0 3958 + eslint-visitor-keys: 4.2.0 3959 + espree: 10.3.0 3960 + esquery: 1.6.0 3961 + esutils: 2.0.3 3962 + fast-deep-equal: 3.1.3 3963 + file-entry-cache: 8.0.0 3964 + find-up: 5.0.0 3965 + glob-parent: 6.0.2 3966 + ignore: 5.3.2 3967 + imurmurhash: 0.1.4 3968 + is-glob: 4.0.3 3969 + json-stable-stringify-without-jsonify: 1.0.1 3970 + lodash.merge: 4.6.2 3971 + minimatch: 3.1.2 3972 + natural-compare: 1.4.0 3973 + optionator: 0.9.4 3974 + optionalDependencies: 3975 + jiti: 2.4.2 3976 + transitivePeerDependencies: 3977 + - supports-color 3978 + 3979 + espree@10.3.0: 3980 + dependencies: 3981 + acorn: 8.14.0 3982 + acorn-jsx: 5.3.2(acorn@8.14.0) 3983 + eslint-visitor-keys: 4.2.0 3984 + 3985 + esquery@1.6.0: 3986 + dependencies: 3987 + estraverse: 5.3.0 3988 + 3989 + esrecurse@4.3.0: 3990 + dependencies: 3991 + estraverse: 5.3.0 3992 + 3993 + estraverse@5.3.0: {} 3994 + 3995 + esutils@2.0.3: {} 3996 + 2533 3997 etag@1.8.1: {} 2534 3998 2535 3999 event-target-shim@5.0.1: {} ··· 2578 4042 2579 4043 fast-copy@3.0.2: {} 2580 4044 4045 + fast-deep-equal@3.1.3: {} 4046 + 2581 4047 fast-glob@3.3.3: 2582 4048 dependencies: 2583 4049 '@nodelib/fs.stat': 2.0.5 ··· 2586 4052 merge2: 1.4.1 2587 4053 micromatch: 4.0.8 2588 4054 4055 + fast-json-stable-stringify@2.1.0: {} 4056 + 4057 + fast-levenshtein@2.0.6: {} 4058 + 2589 4059 fast-redact@3.5.0: {} 2590 4060 2591 4061 fast-safe-stringify@2.1.1: {} ··· 2597 4067 fdir@6.4.3(picomatch@4.0.2): 2598 4068 optionalDependencies: 2599 4069 picomatch: 4.0.2 4070 + 4071 + file-entry-cache@8.0.0: 4072 + dependencies: 4073 + flat-cache: 4.0.1 2600 4074 2601 4075 file-uri-to-path@1.0.0: {} 2602 4076 ··· 2616 4090 transitivePeerDependencies: 2617 4091 - supports-color 2618 4092 4093 + find-up@5.0.0: 4094 + dependencies: 4095 + locate-path: 6.0.0 4096 + path-exists: 4.0.0 4097 + 4098 + flat-cache@4.0.1: 4099 + dependencies: 4100 + flatted: 3.3.3 4101 + keyv: 4.5.4 4102 + 4103 + flatted@3.3.3: {} 4104 + 2619 4105 foreground-child@3.3.1: 2620 4106 dependencies: 2621 4107 cross-spawn: 7.0.6 ··· 2623 4109 2624 4110 forwarded@0.2.0: {} 2625 4111 4112 + fraction.js@4.3.7: {} 4113 + 2626 4114 fresh@0.5.2: {} 2627 4115 2628 4116 fs-constants@1.0.0: {} ··· 2632 4120 2633 4121 function-bind@1.1.2: {} 2634 4122 2635 - gc-hook@0.4.1: {} 4123 + gensync@1.0.0-beta.2: {} 4124 + 4125 + get-caller-file@2.0.5: {} 2636 4126 2637 4127 get-intrinsic@1.3.0: 2638 4128 dependencies: ··· 2662 4152 dependencies: 2663 4153 is-glob: 4.0.3 2664 4154 4155 + glob-parent@6.0.2: 4156 + dependencies: 4157 + is-glob: 4.0.3 4158 + 2665 4159 glob@10.4.5: 2666 4160 dependencies: 2667 4161 foreground-child: 3.3.1 ··· 2680 4174 package-json-from-dist: 1.0.1 2681 4175 path-scurry: 2.0.0 2682 4176 4177 + globals@11.12.0: {} 4178 + 4179 + globals@14.0.0: {} 4180 + 2683 4181 gopd@1.2.0: {} 4182 + 4183 + graceful-fs@4.2.11: {} 2684 4184 2685 4185 graphemer@1.4.0: {} 2686 4186 ··· 2694 4194 2695 4195 help-me@5.0.0: {} 2696 4196 2697 - html-escaper@3.0.3: {} 2698 - 2699 - htmlparser2@9.1.0: 2700 - dependencies: 2701 - domelementtype: 2.3.0 2702 - domhandler: 5.0.3 2703 - domutils: 3.2.2 2704 - entities: 4.5.0 2705 - 2706 4197 http-errors@2.0.0: 2707 4198 dependencies: 2708 4199 depd: 2.0.0 ··· 2716 4207 safer-buffer: 2.1.2 2717 4208 2718 4209 ieee754@1.2.1: {} 4210 + 4211 + ignore@5.3.2: {} 4212 + 4213 + import-fresh@3.3.1: 4214 + dependencies: 4215 + parent-module: 1.0.1 4216 + resolve-from: 4.0.0 4217 + 4218 + imurmurhash@0.1.4: {} 2719 4219 2720 4220 inherits@2.0.4: {} 2721 4221 ··· 2757 4257 dependencies: 2758 4258 '@isaacs/cliui': 8.0.2 2759 4259 4260 + jiti@2.4.2: {} 4261 + 2760 4262 jose@5.10.0: {} 2761 4263 2762 4264 joycon@3.1.1: {} 2763 4265 4266 + js-tokens@4.0.0: {} 4267 + 4268 + js-yaml@4.1.0: 4269 + dependencies: 4270 + argparse: 2.0.1 4271 + 4272 + jsesc@3.1.0: {} 4273 + 4274 + json-buffer@3.0.1: {} 4275 + 4276 + json-schema-traverse@0.4.1: {} 4277 + 4278 + json-stable-stringify-without-jsonify@1.0.1: {} 4279 + 4280 + json5@2.2.3: {} 4281 + 4282 + keyv@4.5.4: 4283 + dependencies: 4284 + json-buffer: 3.0.1 4285 + 2764 4286 kysely@0.27.5: {} 2765 4287 4288 + levn@0.4.1: 4289 + dependencies: 4290 + prelude-ls: 1.2.1 4291 + type-check: 0.4.0 4292 + 4293 + lightningcss-darwin-arm64@1.29.1: 4294 + optional: true 4295 + 4296 + lightningcss-darwin-x64@1.29.1: 4297 + optional: true 4298 + 4299 + lightningcss-freebsd-x64@1.29.1: 4300 + optional: true 4301 + 4302 + lightningcss-linux-arm-gnueabihf@1.29.1: 4303 + optional: true 4304 + 4305 + lightningcss-linux-arm64-gnu@1.29.1: 4306 + optional: true 4307 + 4308 + lightningcss-linux-arm64-musl@1.29.1: 4309 + optional: true 4310 + 4311 + lightningcss-linux-x64-gnu@1.29.1: 4312 + optional: true 4313 + 4314 + lightningcss-linux-x64-musl@1.29.1: 4315 + optional: true 4316 + 4317 + lightningcss-win32-arm64-msvc@1.29.1: 4318 + optional: true 4319 + 4320 + lightningcss-win32-x64-msvc@1.29.1: 4321 + optional: true 4322 + 4323 + lightningcss@1.29.1: 4324 + dependencies: 4325 + detect-libc: 1.0.3 4326 + optionalDependencies: 4327 + lightningcss-darwin-arm64: 1.29.1 4328 + lightningcss-darwin-x64: 1.29.1 4329 + lightningcss-freebsd-x64: 1.29.1 4330 + lightningcss-linux-arm-gnueabihf: 1.29.1 4331 + lightningcss-linux-arm64-gnu: 1.29.1 4332 + lightningcss-linux-arm64-musl: 1.29.1 4333 + lightningcss-linux-x64-gnu: 1.29.1 4334 + lightningcss-linux-x64-musl: 1.29.1 4335 + lightningcss-win32-arm64-msvc: 1.29.1 4336 + lightningcss-win32-x64-msvc: 1.29.1 4337 + 2766 4338 lilconfig@3.1.3: {} 2767 4339 2768 4340 lines-and-columns@1.2.4: {} 2769 4341 2770 4342 load-tsconfig@0.2.5: {} 4343 + 4344 + locate-path@6.0.0: 4345 + dependencies: 4346 + p-locate: 5.0.0 4347 + 4348 + lodash.merge@4.6.2: {} 2771 4349 2772 4350 lodash.sortby@4.7.0: {} 2773 4351 4352 + lodash@4.17.21: {} 4353 + 2774 4354 lru-cache@10.4.3: {} 2775 4355 2776 4356 lru-cache@11.0.2: {} 4357 + 4358 + lru-cache@5.1.1: 4359 + dependencies: 4360 + yallist: 3.1.1 2777 4361 2778 4362 make-error@1.3.6: {} 2779 4363 ··· 2806 4390 dependencies: 2807 4391 brace-expansion: 2.0.1 2808 4392 4393 + minimatch@3.1.2: 4394 + dependencies: 4395 + brace-expansion: 1.1.11 4396 + 2809 4397 minimatch@5.1.6: 2810 4398 dependencies: 2811 4399 brace-expansion: 2.0.1 ··· 2836 4424 object-assign: 4.1.1 2837 4425 thenify-all: 1.6.0 2838 4426 4427 + nanoid@3.3.8: {} 4428 + 2839 4429 napi-build-utils@2.0.0: {} 4430 + 4431 + natural-compare@1.4.0: {} 2840 4432 2841 4433 negotiator@0.6.3: {} 2842 4434 ··· 2849 4441 detect-libc: 2.0.3 2850 4442 optional: true 2851 4443 4444 + node-releases@2.0.19: {} 4445 + 4446 + normalize-range@0.1.2: {} 4447 + 2852 4448 object-assign@4.1.1: {} 2853 4449 2854 4450 object-inspect@1.13.4: {} ··· 2863 4459 dependencies: 2864 4460 wrappy: 1.0.2 2865 4461 4462 + optionator@0.9.4: 4463 + dependencies: 4464 + deep-is: 0.1.4 4465 + fast-levenshtein: 2.0.6 4466 + levn: 0.4.1 4467 + prelude-ls: 1.2.1 4468 + type-check: 0.4.0 4469 + word-wrap: 1.2.5 4470 + 2866 4471 p-finally@1.0.0: {} 2867 4472 4473 + p-limit@3.1.0: 4474 + dependencies: 4475 + yocto-queue: 0.1.0 4476 + 4477 + p-locate@5.0.0: 4478 + dependencies: 4479 + p-limit: 3.1.0 4480 + 2868 4481 p-queue@6.6.2: 2869 4482 dependencies: 2870 4483 eventemitter3: 4.0.7 ··· 2876 4489 2877 4490 package-json-from-dist@1.0.1: {} 2878 4491 4492 + parent-module@1.0.1: 4493 + dependencies: 4494 + callsites: 3.1.0 4495 + 2879 4496 parseurl@1.3.3: {} 2880 4497 2881 4498 path-browserify@1.0.1: {} 4499 + 4500 + path-exists@4.0.0: {} 2882 4501 2883 4502 path-key@3.1.1: {} 2884 4503 ··· 2959 4578 2960 4579 pirates@4.0.6: {} 2961 4580 2962 - postcss-load-config@6.0.1(tsx@4.19.3): 4581 + postcss-load-config@6.0.1(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.3): 2963 4582 dependencies: 2964 4583 lilconfig: 3.1.3 2965 4584 optionalDependencies: 4585 + jiti: 2.4.2 4586 + postcss: 8.5.3 2966 4587 tsx: 4.19.3 2967 4588 4589 + postcss-value-parser@4.2.0: {} 4590 + 4591 + postcss@8.5.3: 4592 + dependencies: 4593 + nanoid: 3.3.8 4594 + picocolors: 1.1.1 4595 + source-map-js: 1.2.1 4596 + 2968 4597 prebuild-install@7.1.3: 2969 4598 dependencies: 2970 4599 detect-libc: 2.0.3 ··· 2979 4608 simple-get: 4.0.1 2980 4609 tar-fs: 2.1.2 2981 4610 tunnel-agent: 0.6.0 4611 + 4612 + prelude-ls@1.2.1: {} 4613 + 4614 + prettier-plugin-tailwindcss@0.6.11(@ianvs/prettier-plugin-sort-imports@4.4.1(prettier@3.5.2))(prettier@3.5.2): 4615 + dependencies: 4616 + prettier: 3.5.2 4617 + optionalDependencies: 4618 + '@ianvs/prettier-plugin-sort-imports': 4.4.1(prettier@3.5.2) 2982 4619 2983 4620 prettier@3.5.2: {} 2984 4621 ··· 3030 4667 minimist: 1.2.8 3031 4668 strip-json-comments: 2.0.1 3032 4669 4670 + react-dom@19.0.0(react@19.0.0): 4671 + dependencies: 4672 + react: 19.0.0 4673 + scheduler: 0.25.0 4674 + 4675 + react-refresh@0.14.2: {} 4676 + 4677 + react-router-dom@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): 4678 + dependencies: 4679 + react: 19.0.0 4680 + react-dom: 19.0.0(react@19.0.0) 4681 + react-router: 7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) 4682 + 4683 + react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): 4684 + dependencies: 4685 + '@types/cookie': 0.6.0 4686 + cookie: 1.0.2 4687 + react: 19.0.0 4688 + set-cookie-parser: 2.7.1 4689 + turbo-stream: 2.4.0 4690 + optionalDependencies: 4691 + react-dom: 19.0.0(react@19.0.0) 4692 + 4693 + react@19.0.0: {} 4694 + 3033 4695 readable-stream@3.6.2: 3034 4696 dependencies: 3035 4697 inherits: 2.0.4 ··· 3047 4709 readdirp@4.1.2: {} 3048 4710 3049 4711 real-require@0.2.0: {} 4712 + 4713 + require-directory@2.1.1: {} 4714 + 4715 + resolve-from@4.0.0: {} 3050 4716 3051 4717 resolve-from@5.0.0: {} 3052 4718 ··· 3088 4754 dependencies: 3089 4755 queue-microtask: 1.2.3 3090 4756 4757 + rxjs@7.8.2: 4758 + dependencies: 4759 + tslib: 2.8.1 4760 + 3091 4761 safe-buffer@5.2.1: {} 3092 4762 3093 4763 safe-stable-stringify@2.5.0: {} 3094 4764 3095 4765 safer-buffer@2.1.2: {} 3096 4766 4767 + scheduler@0.25.0: {} 4768 + 3097 4769 secure-json-parse@2.7.0: {} 4770 + 4771 + semver@6.3.1: {} 3098 4772 3099 4773 semver@7.7.1: {} 3100 4774 ··· 3125 4799 transitivePeerDependencies: 3126 4800 - supports-color 3127 4801 4802 + set-cookie-parser@2.7.1: {} 4803 + 3128 4804 setprototypeof@1.2.0: {} 3129 4805 3130 4806 shebang-command@2.0.0: ··· 3133 4809 3134 4810 shebang-regex@3.0.0: {} 3135 4811 4812 + shell-quote@1.8.2: {} 4813 + 3136 4814 side-channel-list@1.0.0: 3137 4815 dependencies: 3138 4816 es-errors: 1.3.0 ··· 3178 4856 sonic-boom@4.2.0: 3179 4857 dependencies: 3180 4858 atomic-sleep: 1.0.0 4859 + 4860 + source-map-js@1.2.1: {} 3181 4861 3182 4862 source-map@0.8.0-beta.0: 3183 4863 dependencies: ··· 3229 4909 dependencies: 3230 4910 has-flag: 4.0.0 3231 4911 4912 + supports-color@8.1.1: 4913 + dependencies: 4914 + has-flag: 4.0.0 4915 + 4916 + tailwindcss@4.0.9: {} 4917 + 4918 + tapable@2.2.1: {} 4919 + 3232 4920 tar-fs@2.1.2: 3233 4921 dependencies: 3234 4922 chownr: 1.1.4 ··· 3281 4969 3282 4970 tree-kill@1.2.2: {} 3283 4971 4972 + ts-api-utils@2.0.1(typescript@5.8.2): 4973 + dependencies: 4974 + typescript: 5.8.2 4975 + 3284 4976 ts-interface-checker@0.1.13: {} 3285 4977 3286 4978 ts-morph@16.0.0: ··· 3308 5000 3309 5001 tslib@2.6.2: {} 3310 5002 3311 - tsup@8.4.0(tsx@4.19.3)(typescript@5.8.2): 5003 + tslib@2.8.1: {} 5004 + 5005 + tsup@8.4.0(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.3)(typescript@5.8.2): 3312 5006 dependencies: 3313 5007 bundle-require: 5.1.0(esbuild@0.25.0) 3314 5008 cac: 6.7.14 ··· 3318 5012 esbuild: 0.25.0 3319 5013 joycon: 3.1.1 3320 5014 picocolors: 1.1.1 3321 - postcss-load-config: 6.0.1(tsx@4.19.3) 5015 + postcss-load-config: 6.0.1(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.3) 3322 5016 resolve-from: 5.0.0 3323 5017 rollup: 4.34.9 3324 5018 source-map: 0.8.0-beta.0 ··· 3327 5021 tinyglobby: 0.2.12 3328 5022 tree-kill: 1.2.2 3329 5023 optionalDependencies: 5024 + postcss: 8.5.3 3330 5025 typescript: 5.8.2 3331 5026 transitivePeerDependencies: 3332 5027 - jiti ··· 3345 5040 dependencies: 3346 5041 safe-buffer: 5.2.1 3347 5042 5043 + turbo-stream@2.4.0: {} 5044 + 5045 + type-check@0.4.0: 5046 + dependencies: 5047 + prelude-ls: 1.2.1 5048 + 3348 5049 type-is@1.6.18: 3349 5050 dependencies: 3350 5051 media-typer: 0.3.0 ··· 3352 5053 3353 5054 typescript@5.8.2: {} 3354 5055 3355 - udomdiff@1.1.2: {} 3356 - 3357 - uhtml@4.7.0: 3358 - dependencies: 3359 - '@webreflection/uparser': 0.4.0 3360 - custom-function: 2.0.0 3361 - domconstants: 1.1.6 3362 - gc-hook: 0.4.1 3363 - html-escaper: 3.0.3 3364 - htmlparser2: 9.1.0 3365 - udomdiff: 1.1.2 3366 - optionalDependencies: 3367 - '@preact/signals-core': 1.8.0 3368 - '@webreflection/signal': 2.1.2 3369 - 3370 5056 uint8arrays@3.0.0: 3371 5057 dependencies: 3372 5058 multiformats: 9.9.0 ··· 3379 5065 3380 5066 unpipe@1.0.0: {} 3381 5067 5068 + update-browserslist-db@1.1.3(browserslist@4.24.4): 5069 + dependencies: 5070 + browserslist: 4.24.4 5071 + escalade: 3.2.0 5072 + picocolors: 1.1.1 5073 + 5074 + uri-js@4.4.1: 5075 + dependencies: 5076 + punycode: 2.3.1 5077 + 3382 5078 util-deprecate@1.0.2: {} 3383 5079 3384 5080 utils-merge@1.0.1: {} ··· 3389 5085 3390 5086 vary@1.1.2: {} 3391 5087 5088 + vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.3): 5089 + dependencies: 5090 + esbuild: 0.25.0 5091 + postcss: 8.5.3 5092 + rollup: 4.34.9 5093 + optionalDependencies: 5094 + '@types/node': 22.13.8 5095 + fsevents: 2.3.3 5096 + jiti: 2.4.2 5097 + lightningcss: 1.29.1 5098 + tsx: 4.19.3 5099 + 3392 5100 webidl-conversions@4.0.2: {} 3393 5101 3394 5102 whatwg-url@7.1.0: ··· 3401 5109 dependencies: 3402 5110 isexe: 2.0.0 3403 5111 5112 + word-wrap@1.2.5: {} 5113 + 3404 5114 wrap-ansi@7.0.0: 3405 5115 dependencies: 3406 5116 ansi-styles: 4.3.0 ··· 3417 5127 3418 5128 ws@8.18.1: {} 3419 5129 5130 + y18n@5.0.8: {} 5131 + 5132 + yallist@3.1.1: {} 5133 + 5134 + yargs-parser@21.1.1: {} 5135 + 5136 + yargs@17.7.2: 5137 + dependencies: 5138 + cliui: 8.0.1 5139 + escalade: 3.2.0 5140 + get-caller-file: 2.0.5 5141 + require-directory: 2.1.1 5142 + string-width: 4.2.3 5143 + y18n: 5.0.8 5144 + yargs-parser: 21.1.1 5145 + 3420 5146 yesno@0.4.0: {} 3421 5147 3422 5148 yn@3.1.1: {} 5149 + 5150 + yocto-queue@0.1.0: {} 3423 5151 3424 5152 zod@3.24.2: {}
+2
pnpm-workspace.yaml
··· 1 + packages: 2 + - 'packages/*'
+281
scripts/setup-ngrok.js
··· 1 + #!/usr/bin/env node 2 + 3 + /** 4 + * This script automatically sets up ngrok for development. 5 + * It: 6 + * 1. Starts ngrok to tunnel to localhost:3001 7 + * 2. Gets the public HTTPS URL via ngrok's API 8 + * 3. Updates the appview .env file with the ngrok URL 9 + * 4. Starts both the API server and client app 10 + */ 11 + 12 + const { execSync, spawn } = require('child_process') 13 + const fs = require('fs') 14 + const path = require('path') 15 + const http = require('http') 16 + const { URL } = require('url') 17 + 18 + const appviewEnvPath = path.join(__dirname, '..', 'packages', 'appview', '.env') 19 + const clientEnvPath = path.join(__dirname, '..', 'packages', 'client', '.env') 20 + 21 + // Check if ngrok is installed 22 + try { 23 + execSync('ngrok --version', { stdio: 'ignore' }) 24 + } catch (error) { 25 + console.error('❌ ngrok is not installed or not in your PATH.') 26 + console.error('Please install ngrok from https://ngrok.com/download') 27 + process.exit(1) 28 + } 29 + 30 + // Kill any existing ngrok processes 31 + try { 32 + if (process.platform === 'win32') { 33 + execSync('taskkill /f /im ngrok.exe', { stdio: 'ignore' }) 34 + } else { 35 + execSync('pkill -f ngrok', { stdio: 'ignore' }) 36 + } 37 + // Wait for processes to terminate 38 + try { 39 + execSync('sleep 1') 40 + } catch (e) {} 41 + } catch (error) { 42 + // If no process was found, it will throw an error, which we can ignore 43 + } 44 + 45 + console.log('🚀 Starting ngrok...') 46 + 47 + // Start ngrok process - now we're exposing the client (3000) instead of the API (3001) 48 + // This way the whole app will be served through ngrok 49 + const ngrokProcess = spawn('ngrok', ['http', '3000'], { 50 + stdio: ['ignore', 'pipe', 'pipe'], // Allow stdout, stderr 51 + }) 52 + 53 + let devProcesses = null 54 + 55 + // Helper function to update .env files 56 + function updateEnvFile(filePath, ngrokUrl) { 57 + if (!fs.existsSync(filePath)) { 58 + fs.writeFileSync(filePath, '') 59 + } 60 + 61 + const content = fs.readFileSync(filePath, 'utf8') 62 + 63 + if (filePath.includes('appview')) { 64 + // Update NGROK_URL in the appview package 65 + const varName = 'NGROK_URL' 66 + const publicUrlName = 'PUBLIC_URL' 67 + const regex = new RegExp(`^${varName}=.*$`, 'm') 68 + const publicUrlRegex = new RegExp(`^${publicUrlName}=.*$`, 'm') 69 + 70 + // Update content 71 + let updatedContent = content 72 + 73 + // Update or add NGROK_URL 74 + if (regex.test(updatedContent)) { 75 + updatedContent = updatedContent.replace(regex, `${varName}=${ngrokUrl}`) 76 + } else { 77 + updatedContent = `${updatedContent}\n${varName}=${ngrokUrl}\n` 78 + } 79 + 80 + // Update or add PUBLIC_URL - set it to the ngrok URL too 81 + if (publicUrlRegex.test(updatedContent)) { 82 + updatedContent = updatedContent.replace( 83 + publicUrlRegex, 84 + `${publicUrlName}=${ngrokUrl}`, 85 + ) 86 + } else { 87 + updatedContent = `${updatedContent}\n${publicUrlName}=${ngrokUrl}\n` 88 + } 89 + 90 + fs.writeFileSync(filePath, updatedContent) 91 + console.log( 92 + `✅ Updated ${path.basename(filePath)} with ${varName}=${ngrokUrl} and ${publicUrlName}=${ngrokUrl}`, 93 + ) 94 + } else if (filePath.includes('client')) { 95 + // For client, set VITE_API_URL to "/api" - this ensures it uses the proxy setup 96 + const varName = 'VITE_API_URL' 97 + const regex = new RegExp(`^${varName}=.*$`, 'm') 98 + 99 + let updatedContent 100 + if (regex.test(content)) { 101 + // Update existing variable 102 + updatedContent = content.replace(regex, `${varName}=/api`) 103 + } else { 104 + // Add new variable 105 + updatedContent = `${content}\n${varName}=/api\n` 106 + } 107 + 108 + fs.writeFileSync(filePath, updatedContent) 109 + console.log( 110 + `✅ Updated ${path.basename(filePath)} with ${varName}=/api (proxy to API server)`, 111 + ) 112 + } 113 + } 114 + 115 + // Function to start the development servers 116 + function startDevServers() { 117 + console.log('🚀 Starting development servers...') 118 + 119 + // Free port 3001 if it's in use 120 + try { 121 + if (process.platform !== 'win32') { 122 + // Kill any process using port 3001 123 + execSync('kill $(lsof -t -i:3001 2>/dev/null) 2>/dev/null || true') 124 + // Wait for port to be released 125 + execSync('sleep 1') 126 + } 127 + } catch (error) { 128 + // Ignore errors 129 + } 130 + 131 + // Start both servers 132 + devProcesses = spawn('pnpm', ['--filter', '@statusphere/appview', 'dev'], { 133 + stdio: 'inherit', 134 + detached: false, 135 + }) 136 + 137 + const clientProcess = spawn( 138 + 'pnpm', 139 + ['--filter', '@statusphere/client', 'dev'], 140 + { 141 + stdio: 'inherit', 142 + detached: false, 143 + }, 144 + ) 145 + 146 + devProcesses.on('close', (code) => { 147 + console.log(`API server exited with code ${code}`) 148 + killAllProcesses() 149 + }) 150 + 151 + clientProcess.on('close', (code) => { 152 + console.log(`Client app exited with code ${code}`) 153 + killAllProcesses() 154 + }) 155 + } 156 + 157 + // Function to get the ngrok URL from its API 158 + function getNgrokUrl() { 159 + return new Promise((resolve, reject) => { 160 + // Wait a bit for ngrok to start its API server 161 + setTimeout(() => { 162 + http 163 + .get('http://localhost:4040/api/tunnels', (res) => { 164 + let data = '' 165 + 166 + res.on('data', (chunk) => { 167 + data += chunk 168 + }) 169 + 170 + res.on('end', () => { 171 + try { 172 + const tunnels = JSON.parse(data).tunnels 173 + if (tunnels && tunnels.length > 0) { 174 + // Find HTTPS tunnel 175 + const httpsTunnel = tunnels.find((t) => t.proto === 'https') 176 + if (httpsTunnel) { 177 + resolve(httpsTunnel.public_url) 178 + } else { 179 + reject(new Error('No HTTPS tunnel found')) 180 + } 181 + } else { 182 + reject(new Error('No tunnels found')) 183 + } 184 + } catch (error) { 185 + reject(error) 186 + } 187 + }) 188 + }) 189 + .on('error', (err) => { 190 + reject(err) 191 + }) 192 + }, 2000) // Give ngrok a couple seconds to start 193 + }) 194 + } 195 + 196 + // Poll the ngrok API until we get a URL 197 + function pollNgrokApi() { 198 + getNgrokUrl() 199 + .then((ngrokUrl) => { 200 + console.log(`🌍 ngrok URL: ${ngrokUrl}`) 201 + 202 + // Update .env files with the ngrok URL 203 + updateEnvFile(appviewEnvPath, ngrokUrl) 204 + // We'll still call this but it will be skipped per our updated logic 205 + updateEnvFile(clientEnvPath, ngrokUrl) 206 + 207 + // Start development servers 208 + startDevServers() 209 + }) 210 + .catch(() => { 211 + // Try again in 1 second 212 + setTimeout(pollNgrokApi, 1000) 213 + }) 214 + } 215 + 216 + // Start polling after a short delay 217 + setTimeout(pollNgrokApi, 1000) 218 + 219 + // Handle errors 220 + ngrokProcess.stderr.on('data', (data) => { 221 + console.error('------- NGROK ERROR -------') 222 + console.error(data.toString()) 223 + console.error('---------------------------') 224 + }) 225 + 226 + // Handle ngrok process exit 227 + ngrokProcess.on('close', (code) => { 228 + console.log(`ngrok process exited with code ${code}`) 229 + // Call our kill function to ensure everything is properly cleaned up 230 + killAllProcesses() 231 + }) 232 + 233 + // Function to properly terminate all child processes 234 + function killAllProcesses() { 235 + console.log('\nShutting down development environment...') 236 + 237 + // Get ngrok process PID for force kill if needed 238 + const ngrokPid = ngrokProcess.pid 239 + 240 + // Kill main processes with a normal signal first 241 + if (devProcesses) { 242 + try { 243 + devProcesses.kill() 244 + } catch (e) {} 245 + } 246 + 247 + try { 248 + ngrokProcess.kill() 249 + } catch (e) {} 250 + 251 + // Force kill ngrok if normal kill fails 252 + try { 253 + if (process.platform === 'win32') { 254 + execSync(`taskkill /F /PID ${ngrokPid} 2>nul`, { stdio: 'ignore' }) 255 + } else { 256 + execSync(`kill -9 ${ngrokPid} 2>/dev/null || true`, { stdio: 'ignore' }) 257 + // Also kill any remaining ngrok processes 258 + execSync('pkill -9 -f ngrok 2>/dev/null || true', { stdio: 'ignore' }) 259 + } 260 + } catch (e) { 261 + // Ignore errors if processes are already gone 262 + } 263 + 264 + // Kill any process on port 3001 to ensure clean exit 265 + try { 266 + if (process.platform !== 'win32') { 267 + execSync('kill $(lsof -t -i:3001 2>/dev/null) 2>/dev/null || true', { 268 + stdio: 'ignore', 269 + }) 270 + } 271 + } catch (e) { 272 + // Ignore errors 273 + } 274 + 275 + process.exit(0) 276 + } 277 + 278 + // Handle various termination signals 279 + process.on('SIGINT', killAllProcesses) // Ctrl+C 280 + process.on('SIGTERM', killAllProcesses) // Kill command 281 + process.on('SIGHUP', killAllProcesses) // Terminal closed
-28
src/auth/client.ts
··· 1 - import { NodeOAuthClient } from '@atproto/oauth-client-node' 2 - import type { Database } from '#/db' 3 - import { env } from '#/lib/env' 4 - import { SessionStore, StateStore } from './storage' 5 - 6 - export const createClient = async (db: Database) => { 7 - const publicUrl = env.PUBLIC_URL 8 - const url = publicUrl || `http://127.0.0.1:${env.PORT}` 9 - const enc = encodeURIComponent 10 - return new NodeOAuthClient({ 11 - clientMetadata: { 12 - client_name: 'AT Protocol Express App', 13 - client_id: publicUrl 14 - ? `${url}/client-metadata.json` 15 - : `http://localhost?redirect_uri=${enc(`${url}/oauth/callback`)}&scope=${enc('atproto transition:generic')}`, 16 - client_uri: url, 17 - redirect_uris: [`${url}/oauth/callback`], 18 - scope: 'atproto transition:generic', 19 - grant_types: ['authorization_code', 'refresh_token'], 20 - response_types: ['code'], 21 - application_type: 'web', 22 - token_endpoint_auth_method: 'none', 23 - dpop_bound_access_tokens: true, 24 - }, 25 - stateStore: new StateStore(db), 26 - sessionStore: new SessionStore(db), 27 - }) 28 - }
+1
src/auth/storage.ts packages/appview/src/auth/storage.ts
··· 4 4 NodeSavedState, 5 5 NodeSavedStateStore, 6 6 } from '@atproto/oauth-client-node' 7 + 7 8 import type { Database } from '#/db' 8 9 9 10 export class StateStore implements NodeSavedStateStore {
+2 -2
src/db.ts packages/appview/src/db.ts
··· 1 1 import SqliteDb from 'better-sqlite3' 2 2 import { 3 3 Kysely, 4 + Migration, 5 + MigrationProvider, 4 6 Migrator, 5 7 SqliteDialect, 6 - Migration, 7 - MigrationProvider, 8 8 } from 'kysely' 9 9 10 10 // Types
src/id-resolver.ts packages/appview/src/id-resolver.ts
-107
src/index.ts
··· 1 - import events from 'node:events' 2 - import type http from 'node:http' 3 - import express, { type Express } from 'express' 4 - import { pino } from 'pino' 5 - import type { OAuthClient } from '@atproto/oauth-client-node' 6 - import { Firehose } from '@atproto/sync' 7 - 8 - import { createDb, migrateToLatest } from '#/db' 9 - import { env } from '#/lib/env' 10 - import { createIngester } from '#/ingester' 11 - import { createRouter } from '#/routes' 12 - import { createClient } from '#/auth/client' 13 - import { 14 - createBidirectionalResolver, 15 - createIdResolver, 16 - BidirectionalResolver, 17 - } from '#/id-resolver' 18 - import type { Database } from '#/db' 19 - import { IdResolver, MemoryCache } from '@atproto/identity' 20 - 21 - // Application state passed to the router and elsewhere 22 - export type AppContext = { 23 - db: Database 24 - ingester: Firehose 25 - logger: pino.Logger 26 - oauthClient: OAuthClient 27 - resolver: BidirectionalResolver 28 - } 29 - 30 - export class Server { 31 - constructor( 32 - public app: express.Application, 33 - public server: http.Server, 34 - public ctx: AppContext, 35 - ) {} 36 - 37 - static async create() { 38 - const { NODE_ENV, HOST, PORT, DB_PATH } = env 39 - const logger = pino({ name: 'server start' }) 40 - 41 - // Set up the SQLite database 42 - const db = createDb(DB_PATH) 43 - await migrateToLatest(db) 44 - 45 - // Create the atproto utilities 46 - const oauthClient = await createClient(db) 47 - const baseIdResolver = createIdResolver() 48 - const ingester = createIngester(db, baseIdResolver) 49 - const resolver = createBidirectionalResolver(baseIdResolver) 50 - const ctx = { 51 - db, 52 - ingester, 53 - logger, 54 - oauthClient, 55 - resolver, 56 - } 57 - 58 - // Subscribe to events on the firehose 59 - ingester.start() 60 - 61 - // Create our server 62 - const app: Express = express() 63 - app.set('trust proxy', true) 64 - 65 - // Routes & middlewares 66 - const router = createRouter(ctx) 67 - app.use(express.json()) 68 - app.use(express.urlencoded({ extended: true })) 69 - app.use(router) 70 - app.use('*', (_req, res) => { 71 - res.sendStatus(404) 72 - }) 73 - 74 - // Bind our server to the port 75 - const server = app.listen(env.PORT) 76 - await events.once(server, 'listening') 77 - logger.info(`Server (${NODE_ENV}) running on port http://${HOST}:${PORT}`) 78 - 79 - return new Server(app, server, ctx) 80 - } 81 - 82 - async close() { 83 - this.ctx.logger.info('sigint received, shutting down') 84 - await this.ctx.ingester.destroy() 85 - return new Promise<void>((resolve) => { 86 - this.server.close(() => { 87 - this.ctx.logger.info('server closed') 88 - resolve() 89 - }) 90 - }) 91 - } 92 - } 93 - 94 - const run = async () => { 95 - const server = await Server.create() 96 - 97 - const onCloseSignal = async () => { 98 - setTimeout(() => process.exit(1), 10000).unref() // Force shutdown after 10s 99 - await server.close() 100 - process.exit() 101 - } 102 - 103 - process.on('SIGINT', onCloseSignal) 104 - process.on('SIGTERM', onCloseSignal) 105 - } 106 - 107 - run()
+5 -4
src/ingester.ts packages/appview/src/ingester.ts
··· 1 - import pino from 'pino' 2 1 import { IdResolver } from '@atproto/identity' 3 2 import { Firehose, type Event } from '@atproto/sync' 3 + import { XyzStatusphereStatus } from '@statusphere/lexicon' 4 + import pino from 'pino' 5 + 4 6 import type { Database } from '#/db' 5 - import * as Status from '#/lexicon/types/xyz/statusphere/status' 6 7 7 8 export function createIngester(db: Database, idResolver: IdResolver) { 8 9 const logger = pino({ name: 'firehose ingestion' }) ··· 17 18 // If the write is a valid status update 18 19 if ( 19 20 evt.collection === 'xyz.statusphere.status' && 20 - Status.isRecord(record) 21 + XyzStatusphereStatus.isRecord(record) 21 22 ) { 22 - const validatedRecord = Status.validateRecord(record) 23 + const validatedRecord = XyzStatusphereStatus.validateRecord(record) 23 24 if (!validatedRecord.success) return 24 25 // Store the status in our SQLite 25 26 await db
-129
src/lexicon/index.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import { 5 - createServer as createXrpcServer, 6 - Server as XrpcServer, 7 - Options as XrpcOptions, 8 - AuthVerifier, 9 - StreamAuthVerifier, 10 - } from '@atproto/xrpc-server' 11 - import { schemas } from './lexicons.js' 12 - 13 - export function createServer(options?: XrpcOptions): Server { 14 - return new Server(options) 15 - } 16 - 17 - export class Server { 18 - xrpc: XrpcServer 19 - app: AppNS 20 - xyz: XyzNS 21 - com: ComNS 22 - 23 - constructor(options?: XrpcOptions) { 24 - this.xrpc = createXrpcServer(schemas, options) 25 - this.app = new AppNS(this) 26 - this.xyz = new XyzNS(this) 27 - this.com = new ComNS(this) 28 - } 29 - } 30 - 31 - export class AppNS { 32 - _server: Server 33 - bsky: AppBskyNS 34 - 35 - constructor(server: Server) { 36 - this._server = server 37 - this.bsky = new AppBskyNS(server) 38 - } 39 - } 40 - 41 - export class AppBskyNS { 42 - _server: Server 43 - actor: AppBskyActorNS 44 - 45 - constructor(server: Server) { 46 - this._server = server 47 - this.actor = new AppBskyActorNS(server) 48 - } 49 - } 50 - 51 - export class AppBskyActorNS { 52 - _server: Server 53 - 54 - constructor(server: Server) { 55 - this._server = server 56 - } 57 - } 58 - 59 - export class XyzNS { 60 - _server: Server 61 - statusphere: XyzStatusphereNS 62 - 63 - constructor(server: Server) { 64 - this._server = server 65 - this.statusphere = new XyzStatusphereNS(server) 66 - } 67 - } 68 - 69 - export class XyzStatusphereNS { 70 - _server: Server 71 - 72 - constructor(server: Server) { 73 - this._server = server 74 - } 75 - } 76 - 77 - export class ComNS { 78 - _server: Server 79 - atproto: ComAtprotoNS 80 - 81 - constructor(server: Server) { 82 - this._server = server 83 - this.atproto = new ComAtprotoNS(server) 84 - } 85 - } 86 - 87 - export class ComAtprotoNS { 88 - _server: Server 89 - repo: ComAtprotoRepoNS 90 - 91 - constructor(server: Server) { 92 - this._server = server 93 - this.repo = new ComAtprotoRepoNS(server) 94 - } 95 - } 96 - 97 - export class ComAtprotoRepoNS { 98 - _server: Server 99 - 100 - constructor(server: Server) { 101 - this._server = server 102 - } 103 - } 104 - 105 - type SharedRateLimitOpts<T> = { 106 - name: string 107 - calcKey?: (ctx: T) => string | null 108 - calcPoints?: (ctx: T) => number 109 - } 110 - type RouteRateLimitOpts<T> = { 111 - durationMs: number 112 - points: number 113 - calcKey?: (ctx: T) => string | null 114 - calcPoints?: (ctx: T) => number 115 - } 116 - type HandlerOpts = { blobLimit?: number } 117 - type HandlerRateLimitOpts<T> = SharedRateLimitOpts<T> | RouteRateLimitOpts<T> 118 - type ConfigOf<Auth, Handler, ReqCtx> = 119 - | Handler 120 - | { 121 - auth?: Auth 122 - opts?: HandlerOpts 123 - rateLimit?: HandlerRateLimitOpts<ReqCtx> | HandlerRateLimitOpts<ReqCtx>[] 124 - handler: Handler 125 - } 126 - type ExtractAuth<AV extends AuthVerifier | StreamAuthVerifier> = Extract< 127 - Awaited<ReturnType<AV>>, 128 - { credentials: unknown } 129 - >
-332
src/lexicon/lexicons.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import { 5 - LexiconDoc, 6 - Lexicons, 7 - ValidationError, 8 - ValidationResult, 9 - } from '@atproto/lexicon' 10 - import { $Typed, is$typed, maybe$typed } from './util.js' 11 - 12 - export const schemaDict = { 13 - ComAtprotoLabelDefs: { 14 - lexicon: 1, 15 - id: 'com.atproto.label.defs', 16 - defs: { 17 - label: { 18 - type: 'object', 19 - description: 20 - 'Metadata tag on an atproto resource (eg, repo or record).', 21 - required: ['src', 'uri', 'val', 'cts'], 22 - properties: { 23 - ver: { 24 - type: 'integer', 25 - description: 'The AT Protocol version of the label object.', 26 - }, 27 - src: { 28 - type: 'string', 29 - format: 'did', 30 - description: 'DID of the actor who created this label.', 31 - }, 32 - uri: { 33 - type: 'string', 34 - format: 'uri', 35 - description: 36 - 'AT URI of the record, repository (account), or other resource that this label applies to.', 37 - }, 38 - cid: { 39 - type: 'string', 40 - format: 'cid', 41 - description: 42 - "Optionally, CID specifying the specific version of 'uri' resource this label applies to.", 43 - }, 44 - val: { 45 - type: 'string', 46 - maxLength: 128, 47 - description: 48 - 'The short string name of the value or type of this label.', 49 - }, 50 - neg: { 51 - type: 'boolean', 52 - description: 53 - 'If true, this is a negation label, overwriting a previous label.', 54 - }, 55 - cts: { 56 - type: 'string', 57 - format: 'datetime', 58 - description: 'Timestamp when this label was created.', 59 - }, 60 - exp: { 61 - type: 'string', 62 - format: 'datetime', 63 - description: 64 - 'Timestamp at which this label expires (no longer applies).', 65 - }, 66 - sig: { 67 - type: 'bytes', 68 - description: 'Signature of dag-cbor encoded label.', 69 - }, 70 - }, 71 - }, 72 - selfLabels: { 73 - type: 'object', 74 - description: 75 - 'Metadata tags on an atproto record, published by the author within the record.', 76 - required: ['values'], 77 - properties: { 78 - values: { 79 - type: 'array', 80 - items: { 81 - type: 'ref', 82 - ref: 'lex:com.atproto.label.defs#selfLabel', 83 - }, 84 - maxLength: 10, 85 - }, 86 - }, 87 - }, 88 - selfLabel: { 89 - type: 'object', 90 - description: 91 - 'Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.', 92 - required: ['val'], 93 - properties: { 94 - val: { 95 - type: 'string', 96 - maxLength: 128, 97 - description: 98 - 'The short string name of the value or type of this label.', 99 - }, 100 - }, 101 - }, 102 - labelValueDefinition: { 103 - type: 'object', 104 - description: 105 - 'Declares a label value and its expected interpretations and behaviors.', 106 - required: ['identifier', 'severity', 'blurs', 'locales'], 107 - properties: { 108 - identifier: { 109 - type: 'string', 110 - description: 111 - "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", 112 - maxLength: 100, 113 - maxGraphemes: 100, 114 - }, 115 - severity: { 116 - type: 'string', 117 - description: 118 - "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", 119 - knownValues: ['inform', 'alert', 'none'], 120 - }, 121 - blurs: { 122 - type: 'string', 123 - description: 124 - "What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.", 125 - knownValues: ['content', 'media', 'none'], 126 - }, 127 - defaultSetting: { 128 - type: 'string', 129 - description: 'The default setting for this label.', 130 - knownValues: ['ignore', 'warn', 'hide'], 131 - default: 'warn', 132 - }, 133 - adultOnly: { 134 - type: 'boolean', 135 - description: 136 - 'Does the user need to have adult content enabled in order to configure this label?', 137 - }, 138 - locales: { 139 - type: 'array', 140 - items: { 141 - type: 'ref', 142 - ref: 'lex:com.atproto.label.defs#labelValueDefinitionStrings', 143 - }, 144 - }, 145 - }, 146 - }, 147 - labelValueDefinitionStrings: { 148 - type: 'object', 149 - description: 150 - 'Strings which describe the label in the UI, localized into a specific language.', 151 - required: ['lang', 'name', 'description'], 152 - properties: { 153 - lang: { 154 - type: 'string', 155 - description: 156 - 'The code of the language these strings are written in.', 157 - format: 'language', 158 - }, 159 - name: { 160 - type: 'string', 161 - description: 'A short human-readable name for the label.', 162 - maxGraphemes: 64, 163 - maxLength: 640, 164 - }, 165 - description: { 166 - type: 'string', 167 - description: 168 - 'A longer description of what the label means and why it might be applied.', 169 - maxGraphemes: 10000, 170 - maxLength: 100000, 171 - }, 172 - }, 173 - }, 174 - labelValue: { 175 - type: 'string', 176 - knownValues: [ 177 - '!hide', 178 - '!no-promote', 179 - '!warn', 180 - '!no-unauthenticated', 181 - 'dmca-violation', 182 - 'doxxing', 183 - 'porn', 184 - 'sexual', 185 - 'nudity', 186 - 'nsfl', 187 - 'gore', 188 - ], 189 - }, 190 - }, 191 - }, 192 - AppBskyActorProfile: { 193 - lexicon: 1, 194 - id: 'app.bsky.actor.profile', 195 - defs: { 196 - main: { 197 - type: 'record', 198 - description: 'A declaration of a Bluesky account profile.', 199 - key: 'literal:self', 200 - record: { 201 - type: 'object', 202 - properties: { 203 - displayName: { 204 - type: 'string', 205 - maxGraphemes: 64, 206 - maxLength: 640, 207 - }, 208 - description: { 209 - type: 'string', 210 - description: 'Free-form profile description text.', 211 - maxGraphemes: 256, 212 - maxLength: 2560, 213 - }, 214 - avatar: { 215 - type: 'blob', 216 - description: 217 - "Small image to be displayed next to posts from account. AKA, 'profile picture'", 218 - accept: ['image/png', 'image/jpeg'], 219 - maxSize: 1000000, 220 - }, 221 - banner: { 222 - type: 'blob', 223 - description: 224 - 'Larger horizontal image to display behind profile view.', 225 - accept: ['image/png', 'image/jpeg'], 226 - maxSize: 1000000, 227 - }, 228 - labels: { 229 - type: 'union', 230 - description: 231 - 'Self-label values, specific to the Bluesky application, on the overall account.', 232 - refs: ['lex:com.atproto.label.defs#selfLabels'], 233 - }, 234 - joinedViaStarterPack: { 235 - type: 'ref', 236 - ref: 'lex:com.atproto.repo.strongRef', 237 - }, 238 - createdAt: { 239 - type: 'string', 240 - format: 'datetime', 241 - }, 242 - }, 243 - }, 244 - }, 245 - }, 246 - }, 247 - XyzStatusphereStatus: { 248 - lexicon: 1, 249 - id: 'xyz.statusphere.status', 250 - defs: { 251 - main: { 252 - type: 'record', 253 - key: 'tid', 254 - record: { 255 - type: 'object', 256 - required: ['status', 'createdAt'], 257 - properties: { 258 - status: { 259 - type: 'string', 260 - minLength: 1, 261 - maxGraphemes: 1, 262 - maxLength: 32, 263 - }, 264 - createdAt: { 265 - type: 'string', 266 - format: 'datetime', 267 - }, 268 - }, 269 - }, 270 - }, 271 - }, 272 - }, 273 - ComAtprotoRepoStrongRef: { 274 - lexicon: 1, 275 - id: 'com.atproto.repo.strongRef', 276 - description: 'A URI with a content-hash fingerprint.', 277 - defs: { 278 - main: { 279 - type: 'object', 280 - required: ['uri', 'cid'], 281 - properties: { 282 - uri: { 283 - type: 'string', 284 - format: 'at-uri', 285 - }, 286 - cid: { 287 - type: 'string', 288 - format: 'cid', 289 - }, 290 - }, 291 - }, 292 - }, 293 - }, 294 - } as const satisfies Record<string, LexiconDoc> 295 - 296 - export const schemas = Object.values(schemaDict) satisfies LexiconDoc[] 297 - export const lexicons: Lexicons = new Lexicons(schemas) 298 - 299 - export function validate<T extends { $type: string }>( 300 - v: unknown, 301 - id: string, 302 - hash: string, 303 - requiredType: true, 304 - ): ValidationResult<T> 305 - export function validate<T extends { $type?: string }>( 306 - v: unknown, 307 - id: string, 308 - hash: string, 309 - requiredType?: false, 310 - ): ValidationResult<T> 311 - export function validate( 312 - v: unknown, 313 - id: string, 314 - hash: string, 315 - requiredType?: boolean, 316 - ): ValidationResult { 317 - return (requiredType ? is$typed : maybe$typed)(v, id, hash) 318 - ? lexicons.validate(`${id}#${hash}`, v) 319 - : { 320 - success: false, 321 - error: new ValidationError( 322 - `Must be an object with "${hash === 'main' ? id : `${id}#${hash}`}" $type property`, 323 - ), 324 - } 325 - } 326 - 327 - export const ids = { 328 - ComAtprotoLabelDefs: 'com.atproto.label.defs', 329 - AppBskyActorProfile: 'app.bsky.actor.profile', 330 - XyzStatusphereStatus: 'xyz.statusphere.status', 331 - ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef', 332 - } as const
+4 -2
src/lexicon/types/app/bsky/actor/profile.ts packages/lexicon/src/types/app/bsky/actor/profile.ts
··· 1 1 /** 2 2 * GENERATED CODE - DO NOT MODIFY 3 3 */ 4 - import { ValidationResult, BlobRef } from '@atproto/lexicon' 4 + import { BlobRef, ValidationResult } from '@atproto/lexicon' 5 5 import { CID } from 'multiformats/cid' 6 + 6 7 import { validate as _validate } from '../../../../lexicons' 7 - import { $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' 8 + import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' 8 9 import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs.js' 9 10 import type * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef.js' 10 11 ··· 23 24 banner?: BlobRef 24 25 labels?: $Typed<ComAtprotoLabelDefs.SelfLabels> | { $type: string } 25 26 joinedViaStarterPack?: ComAtprotoRepoStrongRef.Main 27 + pinnedPost?: ComAtprotoRepoStrongRef.Main 26 28 createdAt?: string 27 29 [k: string]: unknown 28 30 }
+3 -2
src/lexicon/types/com/atproto/label/defs.ts packages/lexicon/src/types/com/atproto/label/defs.ts
··· 1 1 /** 2 2 * GENERATED CODE - DO NOT MODIFY 3 3 */ 4 - import { ValidationResult, BlobRef } from '@atproto/lexicon' 4 + import { BlobRef, ValidationResult } from '@atproto/lexicon' 5 5 import { CID } from 'multiformats/cid' 6 + 6 7 import { validate as _validate } from '../../../../lexicons' 7 - import { $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' 8 + import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' 8 9 9 10 const is$typed = _is$typed, 10 11 validate = _validate
+3 -2
src/lexicon/types/com/atproto/repo/strongRef.ts packages/lexicon/src/types/com/atproto/repo/strongRef.ts
··· 1 1 /** 2 2 * GENERATED CODE - DO NOT MODIFY 3 3 */ 4 - import { ValidationResult, BlobRef } from '@atproto/lexicon' 4 + import { BlobRef, ValidationResult } from '@atproto/lexicon' 5 5 import { CID } from 'multiformats/cid' 6 + 6 7 import { validate as _validate } from '../../../../lexicons' 7 - import { $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' 8 + import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' 8 9 9 10 const is$typed = _is$typed, 10 11 validate = _validate
+3 -2
src/lexicon/types/xyz/statusphere/status.ts packages/lexicon/src/types/xyz/statusphere/status.ts
··· 1 1 /** 2 2 * GENERATED CODE - DO NOT MODIFY 3 3 */ 4 - import { ValidationResult, BlobRef } from '@atproto/lexicon' 4 + import { BlobRef, ValidationResult } from '@atproto/lexicon' 5 5 import { CID } from 'multiformats/cid' 6 + 6 7 import { validate as _validate } from '../../../lexicons' 7 - import { $Typed, is$typed as _is$typed, OmitKey } from '../../../util' 8 + import { is$typed as _is$typed, $Typed, OmitKey } from '../../../util' 8 9 9 10 const is$typed = _is$typed, 10 11 validate = _validate
src/lexicon/util.ts packages/lexicon/src/util.ts
-16
src/lib/env.ts
··· 1 - import dotenv from 'dotenv' 2 - import { cleanEnv, host, port, str, testOnly } from 'envalid' 3 - 4 - dotenv.config() 5 - 6 - export const env = cleanEnv(process.env, { 7 - NODE_ENV: str({ 8 - devDefault: testOnly('test'), 9 - choices: ['development', 'production', 'test'], 10 - }), 11 - HOST: host({ devDefault: testOnly('localhost') }), 12 - PORT: port({ devDefault: testOnly(3000) }), 13 - PUBLIC_URL: str({}), 14 - DB_PATH: str({ devDefault: ':memory:' }), 15 - COOKIE_SECRET: str({ devDefault: '00000000000000000000000000000000' }), 16 - })
-12
src/lib/view.ts
··· 1 - // @ts-ignore 2 - import ssr from 'uhtml/ssr' 3 - import type initSSR from 'uhtml/types/init-ssr' 4 - import type { Hole } from 'uhtml/types/keyed' 5 - 6 - export type { Hole } 7 - 8 - export const { html }: ReturnType<typeof initSSR> = ssr() 9 - 10 - export function page(hole: Hole) { 11 - return `<!DOCTYPE html>\n${hole.toDOM().toString()}` 12 - }
-121
src/pages/home.ts
··· 1 - import type { Status } from '#/db' 2 - import { html } from '../lib/view' 3 - import { shell } from './shell' 4 - 5 - const TODAY = new Date().toDateString() 6 - 7 - const STATUS_OPTIONS = [ 8 - '👍', 9 - '👎', 10 - '💙', 11 - '🥹', 12 - '😧', 13 - '😤', 14 - '🙃', 15 - '😉', 16 - '😎', 17 - '🤓', 18 - '🤨', 19 - '🥳', 20 - '😭', 21 - '😤', 22 - '🤯', 23 - '🫡', 24 - '💀', 25 - '✊', 26 - '🤘', 27 - '👀', 28 - '🧠', 29 - '👩‍💻', 30 - '🧑‍💻', 31 - '🥷', 32 - '🧌', 33 - '🦋', 34 - '🚀', 35 - ] 36 - 37 - type Props = { 38 - statuses: Status[] 39 - didHandleMap: Record<string, string> 40 - profile?: { displayName?: string } 41 - myStatus?: Status 42 - } 43 - 44 - export function home(props: Props) { 45 - return shell({ 46 - title: 'Home', 47 - content: content(props), 48 - }) 49 - } 50 - 51 - function content({ statuses, didHandleMap, profile, myStatus }: Props) { 52 - return html`<div id="root"> 53 - <div class="error"></div> 54 - <div id="header"> 55 - <h1>Statusphere</h1> 56 - <p>Set your status on the Atmosphere.</p> 57 - </div> 58 - <div class="container"> 59 - <div class="card"> 60 - ${profile 61 - ? html`<form action="/logout" method="post" class="session-form"> 62 - <div> 63 - Hi, <strong>${profile.displayName || 'friend'}</strong>. What's 64 - your status today? 65 - </div> 66 - <div> 67 - <button type="submit">Log out</button> 68 - </div> 69 - </form>` 70 - : html`<div class="session-form"> 71 - <div><a href="/login">Log in</a> to set your status!</div> 72 - <div> 73 - <a href="/login" class="button">Log in</a> 74 - </div> 75 - </div>`} 76 - </div> 77 - <form action="/status" method="post" class="status-options"> 78 - ${STATUS_OPTIONS.map( 79 - (status) => 80 - html`<button 81 - class=${myStatus?.status === status 82 - ? 'status-option selected' 83 - : 'status-option'} 84 - name="status" 85 - value="${status}" 86 - > 87 - ${status} 88 - </button>`, 89 - )} 90 - </form> 91 - ${statuses.map((status, i) => { 92 - const handle = didHandleMap[status.authorDid] || status.authorDid 93 - const date = ts(status) 94 - return html` 95 - <div class=${i === 0 ? 'status-line no-line' : 'status-line'}> 96 - <div> 97 - <div class="status">${status.status}</div> 98 - </div> 99 - <div class="desc"> 100 - <a class="author" href=${toBskyLink(handle)}>@${handle}</a> 101 - ${date === TODAY 102 - ? `is feeling ${status.status} today` 103 - : `was feeling ${status.status} on ${date}`} 104 - </div> 105 - </div> 106 - ` 107 - })} 108 - </div> 109 - </div>` 110 - } 111 - 112 - function toBskyLink(did: string) { 113 - return `https://bsky.app/profile/${did}` 114 - } 115 - 116 - function ts(status: Status) { 117 - const createdAt = new Date(status.createdAt) 118 - const indexedAt = new Date(status.indexedAt) 119 - if (createdAt < indexedAt) return createdAt.toDateString() 120 - return indexedAt.toDateString() 121 - }
-36
src/pages/login.ts
··· 1 - import { html } from '../lib/view' 2 - import { shell } from './shell' 3 - 4 - type Props = { error?: string } 5 - 6 - export function login(props: Props) { 7 - return shell({ 8 - title: 'Log in', 9 - content: content(props), 10 - }) 11 - } 12 - 13 - function content({ error }: Props) { 14 - return html`<div id="root"> 15 - <div id="header"> 16 - <h1>Statusphere</h1> 17 - <p>Set your status on the Atmosphere.</p> 18 - </div> 19 - <div class="container"> 20 - <form action="/login" method="post" class="login-form"> 21 - <input 22 - type="text" 23 - name="handle" 24 - placeholder="Enter your handle (eg alice.bsky.social)" 25 - required 26 - /> 27 - <button type="submit">Log in</button> 28 - ${error ? html`<p>Error: <i>${error}</i></p>` : undefined} 29 - </form> 30 - <div class="signup-cta"> 31 - Don't have an account on the Atmosphere? 32 - <a href="https://bsky.app">Sign up for Bluesky</a> to create one now! 33 - </div> 34 - </div> 35 - </div>` 36 - }
-230
src/pages/public/styles.css
··· 1 - body { 2 - font-family: Arial, Helvetica, sans-serif; 3 - 4 - --border-color: #ddd; 5 - --gray-100: #fafafa; 6 - --gray-500: #666; 7 - --gray-700: #333; 8 - --primary-100: #d2e7ff; 9 - --primary-200: #b1d3fa; 10 - --primary-400: #2e8fff; 11 - --primary-500: #0078ff; 12 - --primary-600: #0066db; 13 - --error-500: #f00; 14 - --error-100: #fee; 15 - } 16 - 17 - /* 18 - Josh's Custom CSS Reset 19 - https://www.joshwcomeau.com/css/custom-css-reset/ 20 - */ 21 - *, 22 - *::before, 23 - *::after { 24 - box-sizing: border-box; 25 - } 26 - * { 27 - margin: 0; 28 - } 29 - body { 30 - line-height: 1.5; 31 - -webkit-font-smoothing: antialiased; 32 - } 33 - img, 34 - picture, 35 - video, 36 - canvas, 37 - svg { 38 - display: block; 39 - max-width: 100%; 40 - } 41 - input, 42 - button, 43 - textarea, 44 - select { 45 - font: inherit; 46 - } 47 - p, 48 - h1, 49 - h2, 50 - h3, 51 - h4, 52 - h5, 53 - h6 { 54 - overflow-wrap: break-word; 55 - } 56 - #root, 57 - #__next { 58 - isolation: isolate; 59 - } 60 - 61 - /* 62 - Common components 63 - */ 64 - button, 65 - .button { 66 - display: inline-block; 67 - border: 0; 68 - background-color: var(--primary-500); 69 - border-radius: 50px; 70 - color: #fff; 71 - padding: 2px 10px; 72 - cursor: pointer; 73 - text-decoration: none; 74 - } 75 - button:hover, 76 - .button:hover { 77 - background: var(--primary-400); 78 - } 79 - 80 - /* 81 - Custom components 82 - */ 83 - .error { 84 - background-color: var(--error-100); 85 - color: var(--error-500); 86 - text-align: center; 87 - padding: 1rem; 88 - display: none; 89 - } 90 - .error.visible { 91 - display: block; 92 - } 93 - 94 - #header { 95 - background-color: #fff; 96 - text-align: center; 97 - padding: 0.5rem 0 1.5rem; 98 - } 99 - 100 - #header h1 { 101 - font-size: 5rem; 102 - } 103 - 104 - .container { 105 - display: flex; 106 - flex-direction: column; 107 - gap: 4px; 108 - margin: 0 auto; 109 - max-width: 600px; 110 - padding: 20px; 111 - } 112 - 113 - .card { 114 - /* border: 1px solid var(--border-color); */ 115 - border-radius: 6px; 116 - padding: 10px 16px; 117 - background-color: #fff; 118 - } 119 - .card > :first-child { 120 - margin-top: 0; 121 - } 122 - .card > :last-child { 123 - margin-bottom: 0; 124 - } 125 - 126 - .session-form { 127 - display: flex; 128 - flex-direction: row; 129 - align-items: center; 130 - justify-content: space-between; 131 - } 132 - 133 - .login-form { 134 - display: flex; 135 - flex-direction: row; 136 - gap: 6px; 137 - border: 1px solid var(--border-color); 138 - border-radius: 6px; 139 - padding: 10px 16px; 140 - background-color: #fff; 141 - } 142 - 143 - .login-form input { 144 - flex: 1; 145 - border: 0; 146 - } 147 - 148 - .status-options { 149 - display: flex; 150 - flex-direction: row; 151 - flex-wrap: wrap; 152 - gap: 8px; 153 - margin: 10px 0; 154 - } 155 - 156 - .status-option { 157 - font-size: 2rem; 158 - width: 3rem; 159 - height: 3rem; 160 - padding: 0; 161 - background-color: #fff; 162 - border: 1px solid var(--border-color); 163 - border-radius: 3rem; 164 - text-align: center; 165 - box-shadow: 0 1px 4px #0001; 166 - cursor: pointer; 167 - } 168 - 169 - .status-option:hover { 170 - background-color: var(--primary-100); 171 - box-shadow: 0 0 0 1px var(--primary-400); 172 - } 173 - 174 - .status-option.selected { 175 - box-shadow: 0 0 0 1px var(--primary-500); 176 - background-color: var(--primary-100); 177 - } 178 - 179 - .status-option.selected:hover { 180 - background-color: var(--primary-200); 181 - } 182 - 183 - .status-line { 184 - display: flex; 185 - flex-direction: row; 186 - align-items: center; 187 - gap: 10px; 188 - position: relative; 189 - margin-top: 15px; 190 - } 191 - 192 - .status-line:not(.no-line)::before { 193 - content: ''; 194 - position: absolute; 195 - width: 2px; 196 - background-color: var(--border-color); 197 - left: 1.45rem; 198 - bottom: calc(100% + 2px); 199 - height: 15px; 200 - } 201 - 202 - .status-line .status { 203 - font-size: 2rem; 204 - background-color: #fff; 205 - width: 3rem; 206 - height: 3rem; 207 - border-radius: 1.5rem; 208 - text-align: center; 209 - border: 1px solid var(--border-color); 210 - } 211 - 212 - .status-line .desc { 213 - color: var(--gray-500); 214 - } 215 - 216 - .status-line .author { 217 - color: var(--gray-700); 218 - font-weight: 600; 219 - text-decoration: none; 220 - } 221 - 222 - .status-line .author:hover { 223 - text-decoration: underline; 224 - } 225 - 226 - .signup-cta { 227 - text-align: center; 228 - text-wrap: balance; 229 - margin-top: 1rem; 230 - }
-13
src/pages/shell.ts
··· 1 - import { type Hole, html } from '../lib/view' 2 - 3 - export function shell({ title, content }: { title: string; content: Hole }) { 4 - return html`<html> 5 - <head> 6 - <title>${title}</title> 7 - <link rel="stylesheet" href="/public/styles.css" /> 8 - </head> 9 - <body> 10 - ${content} 11 - </body> 12 - </html>` 13 - }
-288
src/routes.ts
··· 1 - import assert from 'node:assert' 2 - import path from 'node:path' 3 - import type { IncomingMessage, ServerResponse } from 'node:http' 4 - import { OAuthResolverError } from '@atproto/oauth-client-node' 5 - import { isValidHandle } from '@atproto/syntax' 6 - import { TID } from '@atproto/common' 7 - import { Agent } from '@atproto/api' 8 - import express from 'express' 9 - import { getIronSession } from 'iron-session' 10 - import type { AppContext } from '#/index' 11 - import { home } from '#/pages/home' 12 - import { login } from '#/pages/login' 13 - import { env } from '#/lib/env' 14 - import { page } from '#/lib/view' 15 - import * as Status from '#/lexicon/types/xyz/statusphere/status' 16 - import * as Profile from '#/lexicon/types/app/bsky/actor/profile' 17 - 18 - type Session = { did: string } 19 - 20 - // Helper function for defining routes 21 - const handler = 22 - (fn: (req: express.Request, res: express.Response, next: express.NextFunction) => Promise<void> | void) => 23 - async ( 24 - req: express.Request, 25 - res: express.Response, 26 - next: express.NextFunction, 27 - ) => { 28 - try { 29 - await fn(req, res, next) 30 - } catch (err) { 31 - next(err) 32 - } 33 - } 34 - 35 - // Helper function to get the Atproto Agent for the active session 36 - async function getSessionAgent( 37 - req: IncomingMessage, 38 - res: ServerResponse<IncomingMessage>, 39 - ctx: AppContext, 40 - ) { 41 - const session = await getIronSession<Session>(req, res, { 42 - cookieName: 'sid', 43 - password: env.COOKIE_SECRET, 44 - }) 45 - if (!session.did) return null 46 - try { 47 - const oauthSession = await ctx.oauthClient.restore(session.did) 48 - return oauthSession ? new Agent(oauthSession) : null 49 - } catch (err) { 50 - ctx.logger.warn({ err }, 'oauth restore failed') 51 - await session.destroy() 52 - return null 53 - } 54 - } 55 - 56 - export const createRouter = (ctx: AppContext) => { 57 - const router = express.Router() 58 - 59 - // Static assets 60 - router.use('/public', express.static(path.join(__dirname, 'pages', 'public'))) 61 - 62 - // OAuth metadata 63 - router.get( 64 - '/client-metadata.json', 65 - handler((_req, res) => { 66 - res.json(ctx.oauthClient.clientMetadata) 67 - }), 68 - ) 69 - 70 - // OAuth callback to complete session creation 71 - router.get( 72 - '/oauth/callback', 73 - handler(async (req, res) => { 74 - const params = new URLSearchParams(req.originalUrl.split('?')[1]) 75 - try { 76 - const { session } = await ctx.oauthClient.callback(params) 77 - const clientSession = await getIronSession<Session>(req, res, { 78 - cookieName: 'sid', 79 - password: env.COOKIE_SECRET, 80 - }) 81 - assert(!clientSession.did, 'session already exists') 82 - clientSession.did = session.did 83 - await clientSession.save() 84 - res.redirect('/') 85 - } catch (err) { 86 - ctx.logger.error({ err }, 'oauth callback failed') 87 - res.redirect('/?error') 88 - } 89 - }), 90 - ) 91 - 92 - // Login page 93 - router.get( 94 - '/login', 95 - handler(async (_req, res) => { 96 - res.type('html').send(page(login({}))) 97 - }), 98 - ) 99 - 100 - // Login handler 101 - router.post( 102 - '/login', 103 - handler(async (req, res) => { 104 - // Validate 105 - const handle = req.body?.handle 106 - if (typeof handle !== 'string' || !isValidHandle(handle)) { 107 - res.type('html').send(page(login({ error: 'invalid handle' }))) 108 - return 109 - } 110 - 111 - // Initiate the OAuth flow 112 - try { 113 - const url = await ctx.oauthClient.authorize(handle, { 114 - scope: 'atproto transition:generic', 115 - }) 116 - res.redirect(url.toString()) 117 - } catch (err) { 118 - ctx.logger.error({ err }, 'oauth authorize failed') 119 - res.type('html').send( 120 - page( 121 - login({ 122 - error: 123 - err instanceof OAuthResolverError 124 - ? err.message 125 - : "couldn't initiate login", 126 - }), 127 - ), 128 - ) 129 - } 130 - }), 131 - ) 132 - 133 - // Logout handler 134 - router.post( 135 - '/logout', 136 - handler(async (req, res) => { 137 - const session = await getIronSession<Session>(req, res, { 138 - cookieName: 'sid', 139 - password: env.COOKIE_SECRET, 140 - }) 141 - await session.destroy() 142 - res.redirect('/') 143 - }), 144 - ) 145 - 146 - // Homepage 147 - router.get( 148 - '/', 149 - handler(async (req, res) => { 150 - // If the user is signed in, get an agent which communicates with their server 151 - const agent = await getSessionAgent(req, res, ctx) 152 - 153 - // Fetch data stored in our SQLite 154 - const statuses = await ctx.db 155 - .selectFrom('status') 156 - .selectAll() 157 - .orderBy('indexedAt', 'desc') 158 - .limit(10) 159 - .execute() 160 - const myStatus = agent 161 - ? await ctx.db 162 - .selectFrom('status') 163 - .selectAll() 164 - .where('authorDid', '=', agent.assertDid) 165 - .orderBy('indexedAt', 'desc') 166 - .executeTakeFirst() 167 - : undefined 168 - 169 - // Map user DIDs to their domain-name handles 170 - const didHandleMap = await ctx.resolver.resolveDidsToHandles( 171 - statuses.map((s) => s.authorDid), 172 - ) 173 - 174 - if (!agent) { 175 - // Serve the logged-out view 176 - res.type('html').send(page(home({ statuses, didHandleMap }))) 177 - return 178 - } 179 - 180 - // Fetch additional information about the logged-in user 181 - const profileResponse = await agent.com.atproto.repo 182 - .getRecord({ 183 - repo: agent.assertDid, 184 - collection: 'app.bsky.actor.profile', 185 - rkey: 'self', 186 - }) 187 - .catch(() => undefined) 188 - 189 - const profileRecord = profileResponse?.data 190 - 191 - const profile = 192 - profileRecord && 193 - Profile.isRecord(profileRecord.value) && 194 - Profile.validateRecord(profileRecord.value).success 195 - ? profileRecord.value 196 - : {} 197 - 198 - // Serve the logged-in view 199 - res.type('html').send( 200 - page( 201 - home({ 202 - statuses, 203 - didHandleMap, 204 - profile, 205 - myStatus, 206 - }), 207 - ), 208 - ) 209 - }), 210 - ) 211 - 212 - // "Set status" handler 213 - router.post( 214 - '/status', 215 - handler(async (req, res) => { 216 - // If the user is signed in, get an agent which communicates with their server 217 - const agent = await getSessionAgent(req, res, ctx) 218 - if (!agent) { 219 - res 220 - .status(401) 221 - .type('html') 222 - .send('<h1>Error: Session required</h1>') 223 - return 224 - } 225 - 226 - // Construct & validate their status record 227 - const rkey = TID.nextStr() 228 - const record = { 229 - $type: 'xyz.statusphere.status', 230 - status: req.body?.status, 231 - createdAt: new Date().toISOString(), 232 - } 233 - if (!Status.validateRecord(record).success) { 234 - res 235 - .status(400) 236 - .type('html') 237 - .send('<h1>Error: Invalid status</h1>') 238 - return 239 - } 240 - 241 - let uri 242 - try { 243 - // Write the status record to the user's repository 244 - const response = await agent.com.atproto.repo.putRecord({ 245 - repo: agent.assertDid, 246 - collection: 'xyz.statusphere.status', 247 - rkey, 248 - record, 249 - validate: false, 250 - }) 251 - uri = response.data.uri 252 - } catch (err) { 253 - ctx.logger.warn({ err }, 'failed to write record') 254 - res 255 - .status(500) 256 - .type('html') 257 - .send('<h1>Error: Failed to write record</h1>') 258 - return 259 - } 260 - 261 - try { 262 - // Optimistically update our SQLite 263 - // This isn't strictly necessary because the write event will be 264 - // handled in #/firehose/ingestor.ts, but it ensures that future reads 265 - // will be up-to-date after this method finishes. 266 - await ctx.db 267 - .insertInto('status') 268 - .values({ 269 - uri, 270 - authorDid: agent.assertDid, 271 - status: record.status, 272 - createdAt: record.createdAt, 273 - indexedAt: new Date().toISOString(), 274 - }) 275 - .execute() 276 - } catch (err) { 277 - ctx.logger.warn( 278 - { err }, 279 - 'failed to update computed view; ignoring as it should be caught by the firehose', 280 - ) 281 - } 282 - 283 - res.redirect('/') 284 - }), 285 - ) 286 - 287 - return router 288 - }
-19
tsconfig.json
··· 1 - { 2 - "compilerOptions": { 3 - "target": "ESNext", 4 - "module": "CommonJS", 5 - "baseUrl": ".", 6 - "paths": { 7 - "#/*": ["src/*"] 8 - }, 9 - "moduleResolution": "Node10", 10 - "outDir": "dist", 11 - "importsNotUsedAsValues": "remove", 12 - "strict": true, 13 - "esModuleInterop": true, 14 - "skipLibCheck": true, 15 - "forceConsistentCasingInFileNames": true 16 - }, 17 - "include": ["src/**/*"], 18 - "exclude": ["node_modules"] 19 - }