From b5bda68c9932bcf5c2c0e25207346dbd49ed9aad Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Sun, 2 Mar 2025 04:00:57 +0000 Subject: [PATCH] completely rewrite into react/vite app --- .env.template | 10 - .gitignore | 37 +- .prettierrc | 17 +- .vscode/launch.json | 18 - .vscode/settings.json | 15 - CLAUDE.md | 8 + README.md | 64 +- lexicons/app/bsky/profile/defs.json | 31 + lexicons/{ => app/bsky/profile}/profile.json | 4 + lexicons/com/atproto/label/defs.json | 156 ++ lexicons/com/atproto/repo/applyWrites.json | 131 ++ lexicons/com/atproto/repo/createRecord.json | 73 + lexicons/com/atproto/repo/defs.json | 14 + lexicons/com/atproto/repo/deleteRecord.json | 57 + lexicons/com/atproto/repo/describeRepo.json | 51 + lexicons/com/atproto/repo/getRecord.json | 49 + lexicons/com/atproto/repo/importRepo.json | 13 + .../com/atproto/repo/listMissingBlobs.json | 44 + lexicons/com/atproto/repo/listRecords.json | 69 + lexicons/com/atproto/repo/putRecord.json | 74 + lexicons/com/atproto/repo/strongRef.json | 15 + lexicons/com/atproto/repo/uploadBlob.json | 23 + lexicons/defs.json | 156 -- lexicons/strongRef.json | 15 - lexicons/xyz/statusphere/defs.json | 29 + lexicons/{ => xyz/statusphere}/status.json | 0 package.json | 62 +- packages/appview/README.md | 64 + packages/appview/package.json | 58 + packages/appview/src/auth/client.ts | 40 + {src => packages/appview/src}/auth/storage.ts | 1 + {src => packages/appview/src}/db.ts | 4 +- {src => packages/appview/src}/id-resolver.ts | 0 {src => packages/appview/src}/index.ts | 77 +- {src => packages/appview/src}/ingester.ts | 9 +- {src => packages/appview/src}/lib/env.ts | 9 +- packages/appview/src/lib/status.ts | 21 + packages/appview/src/routes.ts | 324 +++ packages/appview/tsconfig.json | 18 + packages/client/README.md | 35 + packages/client/index.html | 13 + packages/client/package.json | 39 + packages/client/public/favicon.svg | 5 + packages/client/src/App.tsx | 24 + packages/client/src/components/Header.tsx | 55 + packages/client/src/components/StatusForm.tsx | 178 ++ packages/client/src/components/StatusList.tsx | 105 + packages/client/src/hooks/useAuth.tsx | 130 ++ packages/client/src/index.css | 11 + packages/client/src/main.tsx | 20 + packages/client/src/pages/HomePage.tsx | 55 + packages/client/src/pages/LoginPage.tsx | 83 + .../client/src/pages/OAuthCallbackPage.tsx | 99 + packages/client/src/services/api.ts | 160 ++ packages/client/src/vite-env.d.ts | 9 + packages/client/tsconfig.json | 25 + packages/client/tsconfig.node.json | 10 + packages/client/vite.config.ts | 26 + packages/lexicon/package.json | 43 + packages/lexicon/src/index.ts | 381 ++++ packages/lexicon/src/lexicons.ts | 1188 ++++++++++ .../lexicon/src/types/app/bsky/actor/defs.ts | 35 + .../src}/types/app/bsky/actor/profile.ts | 6 +- .../src}/types/com/atproto/label/defs.ts | 5 +- .../src/types/com/atproto/repo/applyWrites.ts | 164 ++ .../types/com/atproto/repo/createRecord.ts | 65 + .../src/types/com/atproto/repo/defs.ts | 28 + .../types/com/atproto/repo/deleteRecord.ts | 60 + .../types/com/atproto/repo/describeRepo.ts | 46 + .../src/types/com/atproto/repo/getRecord.ts | 57 + .../src/types/com/atproto/repo/importRepo.ts | 33 + .../com/atproto/repo/listMissingBlobs.ts | 56 + .../src/types/com/atproto/repo/listRecords.ts | 68 + .../src/types/com/atproto/repo/putRecord.ts | 67 + .../src}/types/com/atproto/repo/strongRef.ts | 5 +- .../src/types/com/atproto/repo/uploadBlob.ts | 38 + .../lexicon/src/types/xyz/statusphere/defs.ts | 46 + .../src}/types/xyz/statusphere/status.ts | 5 +- {src/lexicon => packages/lexicon/src}/util.ts | 0 packages/lexicon/tsconfig.json | 17 + pnpm-lock.yaml | 2010 +++++++++++++++-- pnpm-workspace.yaml | 2 + scripts/setup-ngrok.js | 281 +++ src/auth/client.ts | 28 - src/lexicon/index.ts | 129 -- src/lexicon/lexicons.ts | 332 --- src/lib/view.ts | 12 - src/pages/home.ts | 121 - src/pages/login.ts | 36 - src/pages/public/styles.css | 230 -- src/pages/shell.ts | 13 - src/routes.ts | 288 --- tsconfig.json | 19 - 93 files changed, 7198 insertions(+), 1658 deletions(-) delete mode 100644 .env.template delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/settings.json create mode 100644 CLAUDE.md create mode 100644 lexicons/app/bsky/profile/defs.json rename lexicons/{ => app/bsky/profile}/profile.json (93%) create mode 100644 lexicons/com/atproto/label/defs.json create mode 100644 lexicons/com/atproto/repo/applyWrites.json create mode 100644 lexicons/com/atproto/repo/createRecord.json create mode 100644 lexicons/com/atproto/repo/defs.json create mode 100644 lexicons/com/atproto/repo/deleteRecord.json create mode 100644 lexicons/com/atproto/repo/describeRepo.json create mode 100644 lexicons/com/atproto/repo/getRecord.json create mode 100644 lexicons/com/atproto/repo/importRepo.json create mode 100644 lexicons/com/atproto/repo/listMissingBlobs.json create mode 100644 lexicons/com/atproto/repo/listRecords.json create mode 100644 lexicons/com/atproto/repo/putRecord.json create mode 100644 lexicons/com/atproto/repo/strongRef.json create mode 100644 lexicons/com/atproto/repo/uploadBlob.json delete mode 100644 lexicons/defs.json delete mode 100644 lexicons/strongRef.json create mode 100644 lexicons/xyz/statusphere/defs.json rename lexicons/{ => xyz/statusphere}/status.json (100%) create mode 100644 packages/appview/README.md create mode 100644 packages/appview/package.json create mode 100644 packages/appview/src/auth/client.ts rename {src => packages/appview/src}/auth/storage.ts (99%) rename {src => packages/appview/src}/db.ts (100%) rename {src => packages/appview/src}/id-resolver.ts (100%) rename {src => packages/appview/src}/index.ts (54%) mode change 100755 => 100644 rename {src => packages/appview/src}/ingester.ts (90%) rename {src => packages/appview/src}/lib/env.ts (54%) create mode 100644 packages/appview/src/lib/status.ts create mode 100644 packages/appview/src/routes.ts create mode 100644 packages/appview/tsconfig.json create mode 100644 packages/client/README.md create mode 100644 packages/client/index.html create mode 100644 packages/client/package.json create mode 100644 packages/client/public/favicon.svg create mode 100644 packages/client/src/App.tsx create mode 100644 packages/client/src/components/Header.tsx create mode 100644 packages/client/src/components/StatusForm.tsx create mode 100644 packages/client/src/components/StatusList.tsx create mode 100644 packages/client/src/hooks/useAuth.tsx create mode 100644 packages/client/src/index.css create mode 100644 packages/client/src/main.tsx create mode 100644 packages/client/src/pages/HomePage.tsx create mode 100644 packages/client/src/pages/LoginPage.tsx create mode 100644 packages/client/src/pages/OAuthCallbackPage.tsx create mode 100644 packages/client/src/services/api.ts create mode 100644 packages/client/src/vite-env.d.ts create mode 100644 packages/client/tsconfig.json create mode 100644 packages/client/tsconfig.node.json create mode 100644 packages/client/vite.config.ts create mode 100644 packages/lexicon/package.json create mode 100644 packages/lexicon/src/index.ts create mode 100644 packages/lexicon/src/lexicons.ts create mode 100644 packages/lexicon/src/types/app/bsky/actor/defs.ts rename {src/lexicon => packages/lexicon/src}/types/app/bsky/actor/profile.ts (86%) rename {src/lexicon => packages/lexicon/src}/types/com/atproto/label/defs.ts (97%) create mode 100644 packages/lexicon/src/types/com/atproto/repo/applyWrites.ts create mode 100644 packages/lexicon/src/types/com/atproto/repo/createRecord.ts create mode 100644 packages/lexicon/src/types/com/atproto/repo/defs.ts create mode 100644 packages/lexicon/src/types/com/atproto/repo/deleteRecord.ts create mode 100644 packages/lexicon/src/types/com/atproto/repo/describeRepo.ts create mode 100644 packages/lexicon/src/types/com/atproto/repo/getRecord.ts create mode 100644 packages/lexicon/src/types/com/atproto/repo/importRepo.ts create mode 100644 packages/lexicon/src/types/com/atproto/repo/listMissingBlobs.ts create mode 100644 packages/lexicon/src/types/com/atproto/repo/listRecords.ts create mode 100644 packages/lexicon/src/types/com/atproto/repo/putRecord.ts rename {src/lexicon => packages/lexicon/src}/types/com/atproto/repo/strongRef.ts (80%) create mode 100644 packages/lexicon/src/types/com/atproto/repo/uploadBlob.ts create mode 100644 packages/lexicon/src/types/xyz/statusphere/defs.ts rename {src/lexicon => packages/lexicon/src}/types/xyz/statusphere/status.ts (81%) rename {src/lexicon => packages/lexicon/src}/util.ts (100%) create mode 100644 packages/lexicon/tsconfig.json create mode 100644 pnpm-workspace.yaml create mode 100755 scripts/setup-ngrok.js delete mode 100644 src/auth/client.ts delete mode 100644 src/lexicon/index.ts delete mode 100644 src/lexicon/lexicons.ts delete mode 100644 src/lib/view.ts delete mode 100644 src/pages/home.ts delete mode 100644 src/pages/login.ts delete mode 100644 src/pages/public/styles.css delete mode 100644 src/pages/shell.ts delete mode 100644 src/routes.ts delete mode 100644 tsconfig.json diff --git a/.env.template b/.env.template deleted file mode 100644 index 7e3ef3d..0000000 --- a/.env.template +++ /dev/null @@ -1,10 +0,0 @@ -# Environment Configuration -NODE_ENV="development" # Options: 'development', 'production' -PORT="8080" # The port your server will listen on -HOST="localhost" # Hostname for the server -PUBLIC_URL="" # Set when deployed publicly, e.g. "https://mysite.com". Informs OAuth client id. -DB_PATH=":memory:" # The SQLite database path. Leave as ":memory:" to use a temporary in-memory database. - -# Secrets -# Must set this in production. May be generated with `openssl rand -base64 33` -# COOKIE_SECRET="" diff --git a/.gitignore b/.gitignore index c240efa..f65ac41 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,25 @@ +# Dependencies +node_modules +.pnp +.pnp.js + +# Build output +dist +build +dist-ssr +.turbo + +# Testing +coverage + +# Environment +.env +.env.local +.env.development.local +.env.test.local +.env.production.local +*.local + # Logs logs *.log @@ -7,15 +29,8 @@ yarn-error.log* pnpm-debug.log* lerna-debug.log* -coverage -node_modules -dist -build -dist-ssr -*.local -.env - # Editor directories and files +.vscode/* !.vscode/extensions.json .idea .DS_Store @@ -23,4 +38,8 @@ dist-ssr *.ntvs* *.njsproj *.sln -*.sw? \ No newline at end of file +*.sw? + +# Database +*.sqlite +*.sqlite-journal \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index fd496a8..bbfad2e 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,19 @@ { + "plugins": [ + "prettier-plugin-tailwindcss", + "@ianvs/prettier-plugin-sort-imports" + ], "singleQuote": true, - "semi": false + "semi": false, + "importOrder": [ + "^react$", + "^react-dom$", + "^react-", + "^@tanstack/", + "", + "", + "^#/", + "^[./]" + ], + "importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"] } diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 6f2ec56..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "tsx", - "type": "node", - "request": "launch", - "program": "${workspaceFolder}/src/index.ts", - "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/tsx", - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen", - "skipFiles": ["/**", "${workspaceFolder}/node_modules/**"], - "configurations": [ - { - "command": "npm start", - "name": "Run npm start", - "request": "launch", - "type": "node-terminal" - } - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index d6fdf47..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "editor.formatOnSave": true, - "editor.defaultFormatter": "biomejs.biome", - "editor.codeActionsOnSave": { - "quickfix.biome": "explicit", - "source.organizeImports.biome": "explicit", - "source.fixAll": "explicit" - }, - "json.schemas": [ - { - "url": "https://cdn.jsdelivr.net/npm/tsup/schema.json", - "fileMatch": ["package.json", "tsup.config.json"] - } - ] -} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..512f28a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,8 @@ +hey buddy :) + +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 + +and most importantly, have fun! + +your friend, +mozzius diff --git a/README.md b/README.md index 68dcf6d..7b178ba 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,63 @@ -# AT Protocol "Statusphere" Example App +# Statusphere React -An example application covering: +A monorepo for the Statusphere application, which includes a React client and a Node.js backend. + +This is a React refactoring of the [example application](https://atproto.com/guides/applications) covering: - Signin via OAuth - Fetch information about users (profiles) - Listen to the network firehose for new data - Publish data on the user's account using a custom schema -See https://atproto.com/guides/applications for a guide through the codebase. +## Structure + +- `packages/appview` - Express.js backend that serves API endpoints +- `packages/client` - React frontend using Vite + +## Development -## Getting Started +```bash +# Install dependencies +pnpm install -```sh -git clone https://github.com/bluesky-social/statusphere-example-app.git -cd statusphere-example-app -cp .env.template .env -npm install -npm run dev -# Navigate to http://localhost:8080 +# Option 1: Local development (login won't work due to OAuth requirements) +pnpm dev + +# Option 2: Development with OAuth login support (recommended) +pnpm dev:oauth ``` + +### OAuth Development + +Due to OAuth requirements, HTTPS is needed for development. We've made this easy: + +- `pnpm dev:oauth` - Sets up everything automatically: + 1. Starts ngrok to create an HTTPS tunnel + 2. Configures environment variables with the ngrok URL + 3. Starts both the API server and client app + 4. Handles proper shutdown of all processes + +This all-in-one command makes OAuth development seamless. + +### Additional Commands + +```bash +# Build both packages +pnpm build + +# Run typecheck on both packages +pnpm typecheck + +# Format all code +pnpm format +``` + +## Requirements + +- Node.js 18+ +- pnpm 9+ +- ngrok (for OAuth development) + +## License + +MIT diff --git a/lexicons/app/bsky/profile/defs.json b/lexicons/app/bsky/profile/defs.json new file mode 100644 index 0000000..b2ce1f5 --- /dev/null +++ b/lexicons/app/bsky/profile/defs.json @@ -0,0 +1,31 @@ +{ + "lexicon": 1, + "id": "app.bsky.actor.defs", + "defs": { + "profileView": { + "type": "object", + "required": ["did", "handle"], + "properties": { + "did": { "type": "string", "format": "did" }, + "handle": { "type": "string", "format": "handle" }, + "displayName": { + "type": "string", + "maxGraphemes": 64, + "maxLength": 640 + }, + "description": { + "type": "string", + "maxGraphemes": 256, + "maxLength": 2560 + }, + "avatar": { "type": "string", "format": "uri" }, + "indexedAt": { "type": "string", "format": "datetime" }, + "createdAt": { "type": "string", "format": "datetime" }, + "labels": { + "type": "array", + "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } + } + } + } + } +} diff --git a/lexicons/profile.json b/lexicons/app/bsky/profile/profile.json similarity index 93% rename from lexicons/profile.json rename to lexicons/app/bsky/profile/profile.json index 4363a02..911d7a0 100644 --- a/lexicons/profile.json +++ b/lexicons/app/bsky/profile/profile.json @@ -41,6 +41,10 @@ "type": "ref", "ref": "com.atproto.repo.strongRef" }, + "pinnedPost": { + "type": "ref", + "ref": "com.atproto.repo.strongRef" + }, "createdAt": { "type": "string", "format": "datetime" } } } diff --git a/lexicons/com/atproto/label/defs.json b/lexicons/com/atproto/label/defs.json new file mode 100644 index 0000000..6f4c1ab --- /dev/null +++ b/lexicons/com/atproto/label/defs.json @@ -0,0 +1,156 @@ +{ + "lexicon": 1, + "id": "com.atproto.label.defs", + "defs": { + "label": { + "type": "object", + "description": "Metadata tag on an atproto resource (eg, repo or record).", + "required": ["src", "uri", "val", "cts"], + "properties": { + "ver": { + "type": "integer", + "description": "The AT Protocol version of the label object." + }, + "src": { + "type": "string", + "format": "did", + "description": "DID of the actor who created this label." + }, + "uri": { + "type": "string", + "format": "uri", + "description": "AT URI of the record, repository (account), or other resource that this label applies to." + }, + "cid": { + "type": "string", + "format": "cid", + "description": "Optionally, CID specifying the specific version of 'uri' resource this label applies to." + }, + "val": { + "type": "string", + "maxLength": 128, + "description": "The short string name of the value or type of this label." + }, + "neg": { + "type": "boolean", + "description": "If true, this is a negation label, overwriting a previous label." + }, + "cts": { + "type": "string", + "format": "datetime", + "description": "Timestamp when this label was created." + }, + "exp": { + "type": "string", + "format": "datetime", + "description": "Timestamp at which this label expires (no longer applies)." + }, + "sig": { + "type": "bytes", + "description": "Signature of dag-cbor encoded label." + } + } + }, + "selfLabels": { + "type": "object", + "description": "Metadata tags on an atproto record, published by the author within the record.", + "required": ["values"], + "properties": { + "values": { + "type": "array", + "items": { "type": "ref", "ref": "#selfLabel" }, + "maxLength": 10 + } + } + }, + "selfLabel": { + "type": "object", + "description": "Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.", + "required": ["val"], + "properties": { + "val": { + "type": "string", + "maxLength": 128, + "description": "The short string name of the value or type of this label." + } + } + }, + "labelValueDefinition": { + "type": "object", + "description": "Declares a label value and its expected interpretations and behaviors.", + "required": ["identifier", "severity", "blurs", "locales"], + "properties": { + "identifier": { + "type": "string", + "description": "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", + "maxLength": 100, + "maxGraphemes": 100 + }, + "severity": { + "type": "string", + "description": "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", + "knownValues": ["inform", "alert", "none"] + }, + "blurs": { + "type": "string", + "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.", + "knownValues": ["content", "media", "none"] + }, + "defaultSetting": { + "type": "string", + "description": "The default setting for this label.", + "knownValues": ["ignore", "warn", "hide"], + "default": "warn" + }, + "adultOnly": { + "type": "boolean", + "description": "Does the user need to have adult content enabled in order to configure this label?" + }, + "locales": { + "type": "array", + "items": { "type": "ref", "ref": "#labelValueDefinitionStrings" } + } + } + }, + "labelValueDefinitionStrings": { + "type": "object", + "description": "Strings which describe the label in the UI, localized into a specific language.", + "required": ["lang", "name", "description"], + "properties": { + "lang": { + "type": "string", + "description": "The code of the language these strings are written in.", + "format": "language" + }, + "name": { + "type": "string", + "description": "A short human-readable name for the label.", + "maxGraphemes": 64, + "maxLength": 640 + }, + "description": { + "type": "string", + "description": "A longer description of what the label means and why it might be applied.", + "maxGraphemes": 10000, + "maxLength": 100000 + } + } + }, + "labelValue": { + "type": "string", + "knownValues": [ + "!hide", + "!no-promote", + "!warn", + "!no-unauthenticated", + "dmca-violation", + "doxxing", + "porn", + "sexual", + "nudity", + "nsfl", + "gore" + ] + } + } +} diff --git a/lexicons/com/atproto/repo/applyWrites.json b/lexicons/com/atproto/repo/applyWrites.json new file mode 100644 index 0000000..11a1f1f --- /dev/null +++ b/lexicons/com/atproto/repo/applyWrites.json @@ -0,0 +1,131 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.applyWrites", + "defs": { + "main": { + "type": "procedure", + "description": "Apply a batch transaction of repository creates, updates, and deletes. Requires auth, implemented by PDS.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["repo", "writes"], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo (aka, current account)." + }, + "validate": { + "type": "boolean", + "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." + }, + "writes": { + "type": "array", + "items": { + "type": "union", + "refs": ["#create", "#update", "#delete"], + "closed": true + } + }, + "swapCommit": { + "type": "string", + "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.", + "format": "cid" + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [], + "properties": { + "commit": { + "type": "ref", + "ref": "com.atproto.repo.defs#commitMeta" + }, + "results": { + "type": "array", + "items": { + "type": "union", + "refs": ["#createResult", "#updateResult", "#deleteResult"], + "closed": true + } + } + } + } + }, + "errors": [ + { + "name": "InvalidSwap", + "description": "Indicates that the 'swapCommit' parameter did not match current commit." + } + ] + }, + "create": { + "type": "object", + "description": "Operation which creates a new record.", + "required": ["collection", "value"], + "properties": { + "collection": { "type": "string", "format": "nsid" }, + "rkey": { + "type": "string", + "maxLength": 512, + "format": "record-key", + "description": "NOTE: maxLength is redundant with record-key format. Keeping it temporarily to ensure backwards compatibility." + }, + "value": { "type": "unknown" } + } + }, + "update": { + "type": "object", + "description": "Operation which updates an existing record.", + "required": ["collection", "rkey", "value"], + "properties": { + "collection": { "type": "string", "format": "nsid" }, + "rkey": { "type": "string", "format": "record-key" }, + "value": { "type": "unknown" } + } + }, + "delete": { + "type": "object", + "description": "Operation which deletes an existing record.", + "required": ["collection", "rkey"], + "properties": { + "collection": { "type": "string", "format": "nsid" }, + "rkey": { "type": "string", "format": "record-key" } + } + }, + "createResult": { + "type": "object", + "required": ["uri", "cid"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "validationStatus": { + "type": "string", + "knownValues": ["valid", "unknown"] + } + } + }, + "updateResult": { + "type": "object", + "required": ["uri", "cid"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "validationStatus": { + "type": "string", + "knownValues": ["valid", "unknown"] + } + } + }, + "deleteResult": { + "type": "object", + "required": [], + "properties": {} + } + } +} diff --git a/lexicons/com/atproto/repo/createRecord.json b/lexicons/com/atproto/repo/createRecord.json new file mode 100644 index 0000000..de5f408 --- /dev/null +++ b/lexicons/com/atproto/repo/createRecord.json @@ -0,0 +1,73 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.createRecord", + "defs": { + "main": { + "type": "procedure", + "description": "Create a single new repository record. Requires auth, implemented by PDS.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["repo", "collection", "record"], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo (aka, current account)." + }, + "collection": { + "type": "string", + "format": "nsid", + "description": "The NSID of the record collection." + }, + "rkey": { + "type": "string", + "format": "record-key", + "description": "The Record Key.", + "maxLength": 512 + }, + "validate": { + "type": "boolean", + "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." + }, + "record": { + "type": "unknown", + "description": "The record itself. Must contain a $type field." + }, + "swapCommit": { + "type": "string", + "format": "cid", + "description": "Compare and swap with the previous commit by CID." + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["uri", "cid"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "commit": { + "type": "ref", + "ref": "com.atproto.repo.defs#commitMeta" + }, + "validationStatus": { + "type": "string", + "knownValues": ["valid", "unknown"] + } + } + } + }, + "errors": [ + { + "name": "InvalidSwap", + "description": "Indicates that 'swapCommit' didn't match current repo commit." + } + ] + } + } +} diff --git a/lexicons/com/atproto/repo/defs.json b/lexicons/com/atproto/repo/defs.json new file mode 100644 index 0000000..8752599 --- /dev/null +++ b/lexicons/com/atproto/repo/defs.json @@ -0,0 +1,14 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.defs", + "defs": { + "commitMeta": { + "type": "object", + "required": ["cid", "rev"], + "properties": { + "cid": { "type": "string", "format": "cid" }, + "rev": { "type": "string", "format": "tid" } + } + } + } +} diff --git a/lexicons/com/atproto/repo/deleteRecord.json b/lexicons/com/atproto/repo/deleteRecord.json new file mode 100644 index 0000000..9831da1 --- /dev/null +++ b/lexicons/com/atproto/repo/deleteRecord.json @@ -0,0 +1,57 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.deleteRecord", + "defs": { + "main": { + "type": "procedure", + "description": "Delete a repository record, or ensure it doesn't exist. Requires auth, implemented by PDS.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["repo", "collection", "rkey"], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo (aka, current account)." + }, + "collection": { + "type": "string", + "format": "nsid", + "description": "The NSID of the record collection." + }, + "rkey": { + "type": "string", + "format": "record-key", + "description": "The Record Key." + }, + "swapRecord": { + "type": "string", + "format": "cid", + "description": "Compare and swap with the previous record by CID." + }, + "swapCommit": { + "type": "string", + "format": "cid", + "description": "Compare and swap with the previous commit by CID." + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "ref", + "ref": "com.atproto.repo.defs#commitMeta" + } + } + } + }, + "errors": [{ "name": "InvalidSwap" }] + } + } +} diff --git a/lexicons/com/atproto/repo/describeRepo.json b/lexicons/com/atproto/repo/describeRepo.json new file mode 100644 index 0000000..b1ce2b6 --- /dev/null +++ b/lexicons/com/atproto/repo/describeRepo.json @@ -0,0 +1,51 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.describeRepo", + "defs": { + "main": { + "type": "query", + "description": "Get information about an account and repository, including the list of collections. Does not require auth.", + "parameters": { + "type": "params", + "required": ["repo"], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "handle", + "did", + "didDoc", + "collections", + "handleIsCorrect" + ], + "properties": { + "handle": { "type": "string", "format": "handle" }, + "did": { "type": "string", "format": "did" }, + "didDoc": { + "type": "unknown", + "description": "The complete DID document for this account." + }, + "collections": { + "type": "array", + "description": "List of all the collections (NSIDs) for which this repo contains at least one record.", + "items": { "type": "string", "format": "nsid" } + }, + "handleIsCorrect": { + "type": "boolean", + "description": "Indicates if handle is currently valid (resolves bi-directionally)" + } + } + } + } + } + } +} diff --git a/lexicons/com/atproto/repo/getRecord.json b/lexicons/com/atproto/repo/getRecord.json new file mode 100644 index 0000000..388b9c5 --- /dev/null +++ b/lexicons/com/atproto/repo/getRecord.json @@ -0,0 +1,49 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.getRecord", + "defs": { + "main": { + "type": "query", + "description": "Get a single record from a repository. Does not require auth.", + "parameters": { + "type": "params", + "required": ["repo", "collection", "rkey"], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo." + }, + "collection": { + "type": "string", + "format": "nsid", + "description": "The NSID of the record collection." + }, + "rkey": { + "type": "string", + "description": "The Record Key.", + "format": "record-key" + }, + "cid": { + "type": "string", + "format": "cid", + "description": "The CID of the version of the record. If not specified, then return the most recent version." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["uri", "value"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "value": { "type": "unknown" } + } + } + }, + "errors": [{ "name": "RecordNotFound" }] + } + } +} diff --git a/lexicons/com/atproto/repo/importRepo.json b/lexicons/com/atproto/repo/importRepo.json new file mode 100644 index 0000000..fc850b1 --- /dev/null +++ b/lexicons/com/atproto/repo/importRepo.json @@ -0,0 +1,13 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.importRepo", + "defs": { + "main": { + "type": "procedure", + "description": "Import a repo in the form of a CAR file. Requires Content-Length HTTP header to be set.", + "input": { + "encoding": "application/vnd.ipld.car" + } + } + } +} diff --git a/lexicons/com/atproto/repo/listMissingBlobs.json b/lexicons/com/atproto/repo/listMissingBlobs.json new file mode 100644 index 0000000..c39913d --- /dev/null +++ b/lexicons/com/atproto/repo/listMissingBlobs.json @@ -0,0 +1,44 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.listMissingBlobs", + "defs": { + "main": { + "type": "query", + "description": "Returns a list of missing blobs for the requesting account. Intended to be used in the account migration flow.", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 500 + }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["blobs"], + "properties": { + "cursor": { "type": "string" }, + "blobs": { + "type": "array", + "items": { "type": "ref", "ref": "#recordBlob" } + } + } + } + } + }, + "recordBlob": { + "type": "object", + "required": ["cid", "recordUri"], + "properties": { + "cid": { "type": "string", "format": "cid" }, + "recordUri": { "type": "string", "format": "at-uri" } + } + } + } +} diff --git a/lexicons/com/atproto/repo/listRecords.json b/lexicons/com/atproto/repo/listRecords.json new file mode 100644 index 0000000..bc91c95 --- /dev/null +++ b/lexicons/com/atproto/repo/listRecords.json @@ -0,0 +1,69 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.listRecords", + "defs": { + "main": { + "type": "query", + "description": "List a range of records in a repository, matching a specific collection. Does not require auth.", + "parameters": { + "type": "params", + "required": ["repo", "collection"], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo." + }, + "collection": { + "type": "string", + "format": "nsid", + "description": "The NSID of the record type." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50, + "description": "The number of records to return." + }, + "cursor": { "type": "string" }, + "rkeyStart": { + "type": "string", + "description": "DEPRECATED: The lowest sort-ordered rkey to start from (exclusive)" + }, + "rkeyEnd": { + "type": "string", + "description": "DEPRECATED: The highest sort-ordered rkey to stop at (exclusive)" + }, + "reverse": { + "type": "boolean", + "description": "Flag to reverse the order of the returned records." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["records"], + "properties": { + "cursor": { "type": "string" }, + "records": { + "type": "array", + "items": { "type": "ref", "ref": "#record" } + } + } + } + } + }, + "record": { + "type": "object", + "required": ["uri", "cid", "value"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "value": { "type": "unknown" } + } + } + } +} diff --git a/lexicons/com/atproto/repo/putRecord.json b/lexicons/com/atproto/repo/putRecord.json new file mode 100644 index 0000000..830fade --- /dev/null +++ b/lexicons/com/atproto/repo/putRecord.json @@ -0,0 +1,74 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.putRecord", + "defs": { + "main": { + "type": "procedure", + "description": "Write a repository record, creating or updating it as needed. Requires auth, implemented by PDS.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["repo", "collection", "rkey", "record"], + "nullable": ["swapRecord"], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo (aka, current account)." + }, + "collection": { + "type": "string", + "format": "nsid", + "description": "The NSID of the record collection." + }, + "rkey": { + "type": "string", + "format": "record-key", + "description": "The Record Key.", + "maxLength": 512 + }, + "validate": { + "type": "boolean", + "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." + }, + "record": { + "type": "unknown", + "description": "The record to write." + }, + "swapRecord": { + "type": "string", + "format": "cid", + "description": "Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation" + }, + "swapCommit": { + "type": "string", + "format": "cid", + "description": "Compare and swap with the previous commit by CID." + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["uri", "cid"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "commit": { + "type": "ref", + "ref": "com.atproto.repo.defs#commitMeta" + }, + "validationStatus": { + "type": "string", + "knownValues": ["valid", "unknown"] + } + } + } + }, + "errors": [{ "name": "InvalidSwap" }] + } + } +} diff --git a/lexicons/com/atproto/repo/strongRef.json b/lexicons/com/atproto/repo/strongRef.json new file mode 100644 index 0000000..cb79625 --- /dev/null +++ b/lexicons/com/atproto/repo/strongRef.json @@ -0,0 +1,15 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.strongRef", + "description": "A URI with a content-hash fingerprint.", + "defs": { + "main": { + "type": "object", + "required": ["uri", "cid"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" } + } + } + } +} diff --git a/lexicons/com/atproto/repo/uploadBlob.json b/lexicons/com/atproto/repo/uploadBlob.json new file mode 100644 index 0000000..547a995 --- /dev/null +++ b/lexicons/com/atproto/repo/uploadBlob.json @@ -0,0 +1,23 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.uploadBlob", + "defs": { + "main": { + "type": "procedure", + "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.", + "input": { + "encoding": "*/*" + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["blob"], + "properties": { + "blob": { "type": "blob" } + } + } + } + } + } +} diff --git a/lexicons/defs.json b/lexicons/defs.json deleted file mode 100644 index 733e77e..0000000 --- a/lexicons/defs.json +++ /dev/null @@ -1,156 +0,0 @@ -{ - "lexicon": 1, - "id": "com.atproto.label.defs", - "defs": { - "label": { - "type": "object", - "description": "Metadata tag on an atproto resource (eg, repo or record).", - "required": ["src", "uri", "val", "cts"], - "properties": { - "ver": { - "type": "integer", - "description": "The AT Protocol version of the label object." - }, - "src": { - "type": "string", - "format": "did", - "description": "DID of the actor who created this label." - }, - "uri": { - "type": "string", - "format": "uri", - "description": "AT URI of the record, repository (account), or other resource that this label applies to." - }, - "cid": { - "type": "string", - "format": "cid", - "description": "Optionally, CID specifying the specific version of 'uri' resource this label applies to." - }, - "val": { - "type": "string", - "maxLength": 128, - "description": "The short string name of the value or type of this label." - }, - "neg": { - "type": "boolean", - "description": "If true, this is a negation label, overwriting a previous label." - }, - "cts": { - "type": "string", - "format": "datetime", - "description": "Timestamp when this label was created." - }, - "exp": { - "type": "string", - "format": "datetime", - "description": "Timestamp at which this label expires (no longer applies)." - }, - "sig": { - "type": "bytes", - "description": "Signature of dag-cbor encoded label." - } - } - }, - "selfLabels": { - "type": "object", - "description": "Metadata tags on an atproto record, published by the author within the record.", - "required": ["values"], - "properties": { - "values": { - "type": "array", - "items": { "type": "ref", "ref": "#selfLabel" }, - "maxLength": 10 - } - } - }, - "selfLabel": { - "type": "object", - "description": "Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.", - "required": ["val"], - "properties": { - "val": { - "type": "string", - "maxLength": 128, - "description": "The short string name of the value or type of this label." - } - } - }, - "labelValueDefinition": { - "type": "object", - "description": "Declares a label value and its expected interpretations and behaviors.", - "required": ["identifier", "severity", "blurs", "locales"], - "properties": { - "identifier": { - "type": "string", - "description": "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", - "maxLength": 100, - "maxGraphemes": 100 - }, - "severity": { - "type": "string", - "description": "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", - "knownValues": ["inform", "alert", "none"] - }, - "blurs": { - "type": "string", - "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.", - "knownValues": ["content", "media", "none"] - }, - "defaultSetting": { - "type": "string", - "description": "The default setting for this label.", - "knownValues": ["ignore", "warn", "hide"], - "default": "warn" - }, - "adultOnly": { - "type": "boolean", - "description": "Does the user need to have adult content enabled in order to configure this label?" - }, - "locales": { - "type": "array", - "items": { "type": "ref", "ref": "#labelValueDefinitionStrings" } - } - } - }, - "labelValueDefinitionStrings": { - "type": "object", - "description": "Strings which describe the label in the UI, localized into a specific language.", - "required": ["lang", "name", "description"], - "properties": { - "lang": { - "type": "string", - "description": "The code of the language these strings are written in.", - "format": "language" - }, - "name": { - "type": "string", - "description": "A short human-readable name for the label.", - "maxGraphemes": 64, - "maxLength": 640 - }, - "description": { - "type": "string", - "description": "A longer description of what the label means and why it might be applied.", - "maxGraphemes": 10000, - "maxLength": 100000 - } - } - }, - "labelValue": { - "type": "string", - "knownValues": [ - "!hide", - "!no-promote", - "!warn", - "!no-unauthenticated", - "dmca-violation", - "doxxing", - "porn", - "sexual", - "nudity", - "nsfl", - "gore" - ] - } - } - } \ No newline at end of file diff --git a/lexicons/strongRef.json b/lexicons/strongRef.json deleted file mode 100644 index 3a495f2..0000000 --- a/lexicons/strongRef.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "lexicon": 1, - "id": "com.atproto.repo.strongRef", - "description": "A URI with a content-hash fingerprint.", - "defs": { - "main": { - "type": "object", - "required": ["uri", "cid"], - "properties": { - "uri": { "type": "string", "format": "at-uri" }, - "cid": { "type": "string", "format": "cid" } - } - } - } - } \ No newline at end of file diff --git a/lexicons/xyz/statusphere/defs.json b/lexicons/xyz/statusphere/defs.json new file mode 100644 index 0000000..90c4488 --- /dev/null +++ b/lexicons/xyz/statusphere/defs.json @@ -0,0 +1,29 @@ +{ + "lexicon": 1, + "id": "xyz.statusphere.defs", + "defs": { + "statusView": { + "type": "object", + "required": ["uri", "status", "profile", "createdAt"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "status": { + "type": "string", + "minLength": 1, + "maxGraphemes": 1, + "maxLength": 32 + }, + "createdAt": { "type": "string", "format": "datetime" }, + "profile": { "type": "ref", "ref": "#profileView" } + } + }, + "profileView": { + "type": "object", + "required": ["did", "handle"], + "properties": { + "did": { "type": "string", "format": "did" }, + "handle": { "type": "string", "format": "handle" } + } + } + } +} diff --git a/lexicons/status.json b/lexicons/xyz/statusphere/status.json similarity index 100% rename from lexicons/status.json rename to lexicons/xyz/statusphere/status.json diff --git a/package.json b/package.json index 2922480..b040d02 100644 --- a/package.json +++ b/package.json @@ -1,60 +1,30 @@ { - "name": "atproto-example-app", + "name": "statusphere-react", "version": "0.0.1", - "description": "", + "description": "Statusphere React monorepo", "author": "", "license": "MIT", - "main": "index.ts", "private": true, "scripts": { - "dev": "tsx watch --clear-screen=false src/index.ts | pino-pretty", - "build": "tsup", - "start": "node dist/index.js", - "lexgen": "lex gen-server ./src/lexicon ./lexicons/*", - "clean": "rimraf dist coverage", - "format": "prettier --write src", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@atproto/api": "^0.14.7", - "@atproto/common": "^0.4.1", - "@atproto/identity": "^0.4.0", - "@atproto/lexicon": "^0.4.2", - "@atproto/oauth-client-node": "^0.2.2", - "@atproto/sync": "^0.1.4", - "@atproto/syntax": "^0.3.0", - "@atproto/xrpc-server": "^0.7.9", - "better-sqlite3": "^11.1.2", - "dotenv": "^16.4.5", - "envalid": "^8.0.0", - "express": "^4.19.2", - "iron-session": "^8.0.2", - "kysely": "^0.27.4", - "multiformats": "^13.3.2", - "pino": "^9.3.2", - "uhtml": "^4.5.9" + "dev": "concurrently \"pnpm --filter @statusphere/appview dev\" \"pnpm --filter @statusphere/client dev\"", + "dev:appview": "pnpm --filter @statusphere/appview dev", + "dev:client": "pnpm --filter @statusphere/client dev", + "dev:oauth": "node scripts/setup-ngrok.js", + "lexgen": "pnpm --filter @statusphere/lexicon build", + "build": "pnpm -r build", + "start": "pnpm -r start", + "clean": "pnpm -r clean", + "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"", + "typecheck": "pnpm -r typecheck" }, "devDependencies": { "@atproto/lex-cli": "^0.6.1", - "@types/better-sqlite3": "^7.6.11", - "@types/express": "^5.0.0", - "pino-pretty": "^13.0.0", + "@ianvs/prettier-plugin-sort-imports": "^4.4.1", + "concurrently": "^9.1.2", "prettier": "^3.5.2", + "prettier-plugin-tailwindcss": "^0.6.11", "rimraf": "^6.0.1", - "ts-node": "^10.9.2", - "tsup": "^8.0.2", - "tsx": "^4.7.2", - "typescript": "^5.4.4" - }, - "tsup": { - "entry": [ - "src", - "!src/**/__tests__/**", - "!src/**/*.test.*" - ], - "splitting": false, - "sourcemap": true, - "clean": true + "typescript": "^5.8.2" }, "packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0" } diff --git a/packages/appview/README.md b/packages/appview/README.md new file mode 100644 index 0000000..1074f35 --- /dev/null +++ b/packages/appview/README.md @@ -0,0 +1,64 @@ +# Statusphere AppView + +This is the backend API for the Statusphere application. It provides REST endpoints for the React frontend to consume. + +## Development + +```bash +# Install dependencies +pnpm install + +# Start development server +pnpm dev + +# Build for production +pnpm build + +# Start production server +pnpm start +``` + +## Environment Variables + +Create a `.env` file in the root of this package with the following variables: + +``` +NODE_ENV=development +HOST=localhost +PORT=3001 +DB_PATH=./data.sqlite +COOKIE_SECRET=your_secret_here_at_least_32_characters_long +ATPROTO_SERVER=https://bsky.social +PUBLIC_URL=http://localhost:3001 +NGROK_URL=your_ngrok_url_here +``` + +## Using ngrok for OAuth Development + +Due to OAuth requirements, we need to use HTTPS for development. The easiest way to do this is with ngrok: + +1. Install ngrok: https://ngrok.com/download +2. Run ngrok to create a tunnel to your local server: + ```bash + ngrok http 3001 + ``` +3. Copy the HTTPS URL provided by ngrok (e.g., `https://abcd-123-45-678-90.ngrok.io`) +4. Add it to your `.env` file: + ``` + NGROK_URL=https://abcd-123-45-678-90.ngrok.io + ``` +5. Also update the API URL in the client package: + ``` + # In packages/client/src/services/api.ts + const API_URL = 'https://abcd-123-45-678-90.ngrok.io'; + ``` + +## API Endpoints + +- `GET /client-metadata.json` - OAuth client metadata +- `GET /oauth/callback` - OAuth callback endpoint +- `POST /login` - Login with handle +- `POST /logout` - Logout current user +- `GET /user` - Get current user info +- `GET /statuses` - Get recent statuses +- `POST /status` - Create a new status diff --git a/packages/appview/package.json b/packages/appview/package.json new file mode 100644 index 0000000..37fb89f --- /dev/null +++ b/packages/appview/package.json @@ -0,0 +1,58 @@ +{ + "name": "@statusphere/appview", + "version": "0.0.1", + "description": "Statusphere AppView backend", + "author": "", + "license": "MIT", + "main": "dist/index.js", + "private": true, + "scripts": { + "dev": "tsx watch --clear-screen=false src/index.ts | pino-pretty", + "build": "tsup", + "start": "node dist/index.js", + "clean": "rimraf dist coverage", + "format": "prettier --write src", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@atproto/api": "^0.14.7", + "@atproto/common": "^0.4.8", + "@atproto/identity": "^0.4.6", + "@atproto/lexicon": "^0.4.7", + "@atproto/oauth-client-node": "^0.2.11", + "@atproto/sync": "^0.1.15", + "@atproto/syntax": "^0.3.3", + "@atproto/xrpc-server": "^0.7.11", + "@statusphere/lexicon": "workspace:*", + "better-sqlite3": "^11.8.1", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "envalid": "^8.0.0", + "express": "^4.21.2", + "iron-session": "^8.0.4", + "kysely": "^0.27.5", + "multiformats": "^13.3.2", + "pino": "^9.6.0" + }, + "devDependencies": { + "@types/better-sqlite3": "^7.6.12", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/node": "^22.13.8", + "pino-pretty": "^13.0.0", + "ts-node": "^10.9.2", + "tsup": "^8.4.0", + "tsx": "^4.19.3", + "typescript": "^5.8.2" + }, + "tsup": { + "entry": [ + "src", + "!src/**/__tests__/**", + "!src/**/*.test.*" + ], + "splitting": false, + "sourcemap": true, + "clean": true + } +} diff --git a/packages/appview/src/auth/client.ts b/packages/appview/src/auth/client.ts new file mode 100644 index 0000000..2f9d6a3 --- /dev/null +++ b/packages/appview/src/auth/client.ts @@ -0,0 +1,40 @@ +import { NodeOAuthClient } from '@atproto/oauth-client-node' + +import type { Database } from '#/db' +import { env } from '#/lib/env' +import { SessionStore, StateStore } from './storage' + +export const createClient = async (db: Database) => { + // Get the ngrok URL from environment variables + const ngrokUrl = env.NGROK_URL + + if (!ngrokUrl) { + console.warn( + 'WARNING: NGROK_URL is not set. OAuth login might not work properly.', + ) + console.warn( + 'You should run ngrok and set the NGROK_URL environment variable.', + ) + console.warn('Example: NGROK_URL=https://abcd-123-45-678-90.ngrok.io') + } + + // The base URL is either the ngrok URL (preferred) or a local URL as fallback + const baseUrl = ngrokUrl || `http://127.0.0.1:${env.PORT}` + + return new NodeOAuthClient({ + clientMetadata: { + client_name: 'Statusphere React App', + client_id: `${baseUrl}/api/client-metadata.json`, + client_uri: baseUrl, + redirect_uris: [`${baseUrl}/api/oauth/callback`], + scope: 'atproto transition:generic', + grant_types: ['authorization_code', 'refresh_token'], + response_types: ['code'], + application_type: 'web', + token_endpoint_auth_method: 'none', + dpop_bound_access_tokens: true, + }, + stateStore: new StateStore(db), + sessionStore: new SessionStore(db), + }) +} diff --git a/src/auth/storage.ts b/packages/appview/src/auth/storage.ts similarity index 99% rename from src/auth/storage.ts rename to packages/appview/src/auth/storage.ts index f24966c..194d3f2 100644 --- a/src/auth/storage.ts +++ b/packages/appview/src/auth/storage.ts @@ -4,6 +4,7 @@ import type { NodeSavedState, NodeSavedStateStore, } from '@atproto/oauth-client-node' + import type { Database } from '#/db' export class StateStore implements NodeSavedStateStore { diff --git a/src/db.ts b/packages/appview/src/db.ts similarity index 100% rename from src/db.ts rename to packages/appview/src/db.ts index f21d863..fc177ec 100644 --- a/src/db.ts +++ b/packages/appview/src/db.ts @@ -1,10 +1,10 @@ import SqliteDb from 'better-sqlite3' import { Kysely, - Migrator, - SqliteDialect, Migration, MigrationProvider, + Migrator, + SqliteDialect, } from 'kysely' // Types diff --git a/src/id-resolver.ts b/packages/appview/src/id-resolver.ts similarity index 100% rename from src/id-resolver.ts rename to packages/appview/src/id-resolver.ts diff --git a/src/index.ts b/packages/appview/src/index.ts old mode 100755 new mode 100644 similarity index 54% rename from src/index.ts rename to packages/appview/src/index.ts index c3f395b..5f0de08 --- a/src/index.ts +++ b/packages/appview/src/index.ts @@ -1,22 +1,22 @@ import events from 'node:events' import type http from 'node:http' -import express, { type Express } from 'express' -import { pino } from 'pino' import type { OAuthClient } from '@atproto/oauth-client-node' import { Firehose } from '@atproto/sync' +import cors from 'cors' +import express, { type Express } from 'express' +import { pino } from 'pino' -import { createDb, migrateToLatest } from '#/db' -import { env } from '#/lib/env' -import { createIngester } from '#/ingester' -import { createRouter } from '#/routes' import { createClient } from '#/auth/client' +import { createDb, migrateToLatest } from '#/db' +import type { Database } from '#/db' import { + BidirectionalResolver, createBidirectionalResolver, createIdResolver, - BidirectionalResolver, } from '#/id-resolver' -import type { Database } from '#/db' -import { IdResolver, MemoryCache } from '@atproto/identity' +import { createIngester } from '#/ingester' +import { env } from '#/lib/env' +import { createRouter } from '#/routes' // Application state passed to the router and elsewhere export type AppContext = { @@ -62,6 +62,59 @@ export class Server { const app: Express = express() app.set('trust proxy', true) + // CORS configuration based on environment + if (env.NODE_ENV === 'development') { + // In development, allow multiple origins including ngrok + app.use( + cors({ + origin: function (origin, callback) { + // Allow requests with no origin (like mobile apps, curl) + if (!origin) return callback(null, true) + + // List of allowed origins + const allowedOrigins = [ + 'http://localhost:3000', // Standard React port + 'http://127.0.0.1:3000', // Alternative React address + ] + + // If we have an ngrok URL defined, add it to allowed origins + if (env.NGROK_URL) { + try { + const ngrokOrigin = new URL(env.NGROK_URL) + const ngrokClientOrigin = `${ngrokOrigin.protocol}//${ngrokOrigin.hostname}:3000` + allowedOrigins.push(ngrokClientOrigin) + } catch (err) { + console.error('Failed to parse NGROK_URL for CORS:', err) + } + } + + // Check if the request origin is in our allowed list or is an ngrok domain + if ( + allowedOrigins.indexOf(origin) !== -1 || + origin.includes('ngrok-free.app') + ) { + callback(null, true) + } else { + console.warn(`โš ๏ธ CORS blocked origin: ${origin}`) + callback(null, false) + } + }, + credentials: true, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'], + }), + ) + } else { + // In production, CORS is not needed if frontend and API are on same domain + // But we'll still enable it for flexibility with minimal configuration + app.use( + cors({ + origin: true, // Use req.origin, which means same-origin requests will always be allowed + credentials: true, + }), + ) + } + // Routes & middlewares const router = createRouter(ctx) app.use(express.json()) @@ -71,10 +124,12 @@ export class Server { res.sendStatus(404) }) - // Bind our server to the port + // Use the port from env (should be 3001 for the API server) const server = app.listen(env.PORT) await events.once(server, 'listening') - logger.info(`Server (${NODE_ENV}) running on port http://${HOST}:${PORT}`) + logger.info( + `API Server (${NODE_ENV}) running on port http://${HOST}:${env.PORT}`, + ) return new Server(app, server, ctx) } diff --git a/src/ingester.ts b/packages/appview/src/ingester.ts similarity index 90% rename from src/ingester.ts rename to packages/appview/src/ingester.ts index a977556..43aeae8 100644 --- a/src/ingester.ts +++ b/packages/appview/src/ingester.ts @@ -1,8 +1,9 @@ -import pino from 'pino' import { IdResolver } from '@atproto/identity' import { Firehose, type Event } from '@atproto/sync' +import { XyzStatusphereStatus } from '@statusphere/lexicon' +import pino from 'pino' + import type { Database } from '#/db' -import * as Status from '#/lexicon/types/xyz/statusphere/status' export function createIngester(db: Database, idResolver: IdResolver) { const logger = pino({ name: 'firehose ingestion' }) @@ -17,9 +18,9 @@ export function createIngester(db: Database, idResolver: IdResolver) { // If the write is a valid status update if ( evt.collection === 'xyz.statusphere.status' && - Status.isRecord(record) + XyzStatusphereStatus.isRecord(record) ) { - const validatedRecord = Status.validateRecord(record) + const validatedRecord = XyzStatusphereStatus.validateRecord(record) if (!validatedRecord.success) return // Store the status in our SQLite await db diff --git a/src/lib/env.ts b/packages/appview/src/lib/env.ts similarity index 54% rename from src/lib/env.ts rename to packages/appview/src/lib/env.ts index cf4d75e..01b0099 100644 --- a/src/lib/env.ts +++ b/packages/appview/src/lib/env.ts @@ -1,5 +1,5 @@ import dotenv from 'dotenv' -import { cleanEnv, host, port, str, testOnly } from 'envalid' +import { cleanEnv, host, port, str, testOnly, url } from 'envalid' dotenv.config() @@ -9,8 +9,11 @@ export const env = cleanEnv(process.env, { choices: ['development', 'production', 'test'], }), HOST: host({ devDefault: testOnly('localhost') }), - PORT: port({ devDefault: testOnly(3000) }), - PUBLIC_URL: str({}), + PORT: port({ devDefault: testOnly(3001) }), DB_PATH: str({ devDefault: ':memory:' }), COOKIE_SECRET: str({ devDefault: '00000000000000000000000000000000' }), + ATPROTO_SERVER: str({ default: 'https://bsky.social' }), + SERVICE_DID: str({ default: undefined }), + PUBLIC_URL: str({ default: 'http://localhost:3001' }), + NGROK_URL: str({ default: '' }), }) diff --git a/packages/appview/src/lib/status.ts b/packages/appview/src/lib/status.ts new file mode 100644 index 0000000..6153af2 --- /dev/null +++ b/packages/appview/src/lib/status.ts @@ -0,0 +1,21 @@ +import { XyzStatusphereDefs } from '@statusphere/lexicon' + +import { Status } from '#/db' +import { AppContext } from '#/index' + +export async function statusToStatusView( + status: Status, + ctx: AppContext, +): Promise { + return { + uri: status.uri, + status: status.status, + createdAt: status.createdAt, + profile: { + did: status.authorDid, + handle: await ctx.resolver + .resolveDidToHandle(status.authorDid) + .catch(() => 'invalid.handle'), + }, + } +} diff --git a/packages/appview/src/routes.ts b/packages/appview/src/routes.ts new file mode 100644 index 0000000..cc1b27b --- /dev/null +++ b/packages/appview/src/routes.ts @@ -0,0 +1,324 @@ +import type { IncomingMessage, ServerResponse } from 'node:http' +import { Agent } from '@atproto/api' +import { TID } from '@atproto/common' +import { OAuthResolverError } from '@atproto/oauth-client-node' +import { isValidHandle } from '@atproto/syntax' +import { AppBskyActorProfile, XyzStatusphereStatus } from '@statusphere/lexicon' +import express from 'express' +import { getIronSession, SessionOptions } from 'iron-session' + +import type { AppContext } from '#/index' +import { env } from '#/lib/env' +import { statusToStatusView } from '#/lib/status' + +type Session = { did: string } + +// Common session options +const sessionOptions: SessionOptions = { + cookieName: 'sid', + password: env.COOKIE_SECRET, + cookieOptions: { + secure: env.NODE_ENV === 'production', + httpOnly: true, + sameSite: 'lax', + path: '/', + // Don't set domain explicitly - let browser determine it + domain: undefined, + }, +} + +// Helper function for defining routes +const handler = + ( + fn: ( + req: express.Request, + res: express.Response, + next: express.NextFunction, + ) => Promise | void, + ) => + async ( + req: express.Request, + res: express.Response, + next: express.NextFunction, + ) => { + try { + await fn(req, res, next) + } catch (err) { + next(err) + } + } + +// Helper function to get the Atproto Agent for the active session +async function getSessionAgent( + req: IncomingMessage | express.Request, + res: ServerResponse | express.Response, + ctx: AppContext, +) { + const session = await getIronSession(req, res, sessionOptions) + + if (!session.did) { + return null + } + + try { + const oauthSession = await ctx.oauthClient.restore(session.did) + return oauthSession ? new Agent(oauthSession) : null + } catch (err) { + ctx.logger.warn({ err }, 'oauth restore failed') + session.destroy() + return null + } +} + +export const createRouter = (ctx: AppContext) => { + const router = express.Router() + + // Simple CORS configuration for all routes + router.use((req, res, next) => { + // Allow requests from either the specific origin or any origin during development + res.header('Access-Control-Allow-Origin', req.headers.origin || '*') + res.header('Access-Control-Allow-Credentials', 'true') + res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') + res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization') + + if (req.method === 'OPTIONS') { + res.status(200).end() + return + } + next() + }) + + // OAuth metadata + router.get( + '/client-metadata.json', + handler((_req, res) => { + res.json(ctx.oauthClient.clientMetadata) + }), + ) + + // OAuth callback to complete session creation + router.get( + '/oauth/callback', + handler(async (req, res) => { + // Get the query parameters from the URL + const params = new URLSearchParams(req.originalUrl.split('?')[1]) + + try { + const { session } = await ctx.oauthClient.callback(params) + + // Use the common session options + const clientSession = await getIronSession( + req, + res, + sessionOptions, + ) + + // Set the DID on the session + clientSession.did = session.did + await clientSession.save() + + // Redirect to the frontend oauth-callback page + res.redirect('/oauth-callback') + } catch (err) { + ctx.logger.error({ err }, 'oauth callback failed') + + // Handle error redirect - stay on same domain + res.redirect('/oauth-callback?error=auth') + } + }), + ) + + // Login handler + router.post( + '/login', + handler(async (req, res) => { + // Validate + const handle = req.body?.handle + if (typeof handle !== 'string' || !isValidHandle(handle)) { + res.status(400).json({ error: 'invalid handle' }) + return + } + + // Initiate the OAuth flow + try { + const url = await ctx.oauthClient.authorize(handle, { + scope: 'atproto transition:generic', + }) + res.json({ redirectUrl: url.toString() }) + } catch (err) { + ctx.logger.error({ err }, 'oauth authorize failed') + const errorMsg = + err instanceof OAuthResolverError + ? err.message + : "couldn't initiate login" + res.status(500).json({ error: errorMsg }) + } + }), + ) + + // Logout handler + router.post( + '/logout', + handler(async (req, res) => { + const session = await getIronSession(req, res, sessionOptions) + session.destroy() + res.json({ success: true }) + }), + ) + + // Get current user info + router.get( + '/user', + handler(async (req, res) => { + const agent = await getSessionAgent(req, res, ctx) + if (!agent) { + res.status(401).json({ error: 'Not logged in' }) + return + } + + const did = agent.assertDid + + // Fetch user profile + try { + const profileResponse = await agent.com.atproto.repo + .getRecord({ + repo: did, + collection: 'app.bsky.actor.profile', + rkey: 'self', + }) + .catch(() => undefined) + + const profileRecord = profileResponse?.data + const profile = + profileRecord && + AppBskyActorProfile.isRecord(profileRecord.value) && + AppBskyActorProfile.validateRecord(profileRecord.value).success + ? profileRecord.value + : ({} as AppBskyActorProfile.Record) + + profile.did = did + profile.handle = await ctx.resolver.resolveDidToHandle(did) + + // Fetch user status + const status = await ctx.db + .selectFrom('status') + .selectAll() + .where('authorDid', '=', did) + .orderBy('indexedAt', 'desc') + .executeTakeFirst() + + res.json({ + did: agent.assertDid, + profile, + status: status ? await statusToStatusView(status, ctx) : undefined, + }) + } catch (err) { + ctx.logger.error({ err }, 'Failed to get user info') + res.status(500).json({ error: 'Failed to get user info' }) + } + }), + ) + + // Get statuses + router.get( + '/statuses', + handler(async (req, res) => { + try { + // Fetch data stored in our SQLite + const statuses = await ctx.db + .selectFrom('status') + .selectAll() + .orderBy('indexedAt', 'desc') + .limit(10) + .execute() + + res.json({ + statuses: await Promise.all( + statuses.map((status) => statusToStatusView(status, ctx)), + ), + }) + } catch (err) { + ctx.logger.error({ err }, 'Failed to get statuses') + res.status(500).json({ error: 'Failed to get statuses' }) + } + }), + ) + + // Create status + router.post( + '/status', + handler(async (req, res) => { + // If the user is signed in, get an agent which communicates with their server + const agent = await getSessionAgent(req, res, ctx) + if (!agent) { + res.status(401).json({ error: 'Session required' }) + return + } + + // Construct & validate their status record + const rkey = TID.nextStr() + const record = { + $type: 'xyz.statusphere.status', + status: req.body?.status, + createdAt: new Date().toISOString(), + } + if (!XyzStatusphereStatus.validateRecord(record).success) { + res.status(400).json({ error: 'Invalid status' }) + return + } + + let uri + try { + // Write the status record to the user's repository + const response = await agent.com.atproto.repo.putRecord({ + repo: agent.assertDid, + collection: 'xyz.statusphere.status', + rkey, + record, + validate: false, + }) + uri = response.data.uri + } catch (err) { + ctx.logger.warn({ err }, 'failed to write record') + res.status(500).json({ error: 'Failed to write record' }) + return + } + + try { + // Optimistically update our SQLite + // This isn't strictly necessary because the write event will be + // handled in #/firehose/ingestor.ts, but it ensures that future reads + // will be up-to-date after this method finishes. + await ctx.db + .insertInto('status') + .values({ + uri, + authorDid: agent.assertDid, + status: record.status, + createdAt: record.createdAt, + indexedAt: new Date().toISOString(), + }) + .execute() + + res.json({ + success: true, + uri, + status: await statusToStatusView(record.status, ctx), + }) + } catch (err) { + ctx.logger.warn( + { err }, + 'failed to update computed view; ignoring as it should be caught by the firehose', + ) + res.json({ + success: true, + uri, + status: await statusToStatusView(record.status, ctx), + warning: 'Database not updated', + }) + } + }), + ) + + return router +} diff --git a/packages/appview/tsconfig.json b/packages/appview/tsconfig.json new file mode 100644 index 0000000..0b277ed --- /dev/null +++ b/packages/appview/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "baseUrl": ".", + "outDir": "dist", + "paths": { + "#/*": ["./src/*"] + } + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/client/README.md b/packages/client/README.md new file mode 100644 index 0000000..49771f9 --- /dev/null +++ b/packages/client/README.md @@ -0,0 +1,35 @@ +# Statusphere Client + +This is the React frontend for the Statusphere application. + +## Development + +```bash +# Install dependencies +pnpm install + +# Start development server +pnpm dev + +# Build for production +pnpm build + +# Preview production build +pnpm preview +``` + +## Features + +- Display statuses from all users +- Create new statuses +- Login with your Bluesky handle +- View your profile info +- Responsive design + +## Architecture + +- React 18 with TypeScript +- React Router for navigation +- Context API for state management +- Vite for development and building +- CSS for styling diff --git a/packages/client/index.html b/packages/client/index.html new file mode 100644 index 0000000..67ff778 --- /dev/null +++ b/packages/client/index.html @@ -0,0 +1,13 @@ + + + + + + + Statusphere React + + +
+ + + \ No newline at end of file diff --git a/packages/client/package.json b/packages/client/package.json new file mode 100644 index 0000000..857da82 --- /dev/null +++ b/packages/client/package.json @@ -0,0 +1,39 @@ +{ + "name": "@statusphere/client", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview", + "clean": "rimraf dist", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@atproto/api": "^0.14.7", + "@statusphere/lexicon": "workspace:*", + "@tailwindcss/vite": "^4.0.9", + "@tanstack/react-query": "^5.66.11", + "iron-session": "^8.0.4", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.2.0" + }, + "devDependencies": { + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@typescript-eslint/eslint-plugin": "^8.25.0", + "@typescript-eslint/parser": "^8.25.0", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "eslint": "^9.21.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.19", + "postcss": "^8.5.3", + "tailwindcss": "^4.0.9", + "typescript": "^5.8.2", + "vite": "^6.2.0" + } +} diff --git a/packages/client/public/favicon.svg b/packages/client/public/favicon.svg new file mode 100644 index 0000000..b1e8c67 --- /dev/null +++ b/packages/client/public/favicon.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx new file mode 100644 index 0000000..308e7ca --- /dev/null +++ b/packages/client/src/App.tsx @@ -0,0 +1,24 @@ +import { Route, Routes } from 'react-router-dom' + +import { AuthProvider } from '#/hooks/useAuth' +import HomePage from '#/pages/HomePage' +import LoginPage from '#/pages/LoginPage' +import OAuthCallbackPage from '#/pages/OAuthCallbackPage' + +function App() { + return ( +
+
+ + + } /> + } /> + } /> + + +
+
+ ) +} + +export default App diff --git a/packages/client/src/components/Header.tsx b/packages/client/src/components/Header.tsx new file mode 100644 index 0000000..80d0e9f --- /dev/null +++ b/packages/client/src/components/Header.tsx @@ -0,0 +1,55 @@ +import { Link } from 'react-router-dom' + +import { useAuth } from '#/hooks/useAuth' + +const Header = () => { + const { user, logout } = useAuth() + + const handleLogout = async () => { + try { + await logout() + } catch (error) { + console.error('Logout failed:', error) + } + } + + return ( +
+
+

+ + Statusphere + +

+ +
+
+ ) +} + +export default Header diff --git a/packages/client/src/components/StatusForm.tsx b/packages/client/src/components/StatusForm.tsx new file mode 100644 index 0000000..45efa72 --- /dev/null +++ b/packages/client/src/components/StatusForm.tsx @@ -0,0 +1,178 @@ +import { useState } from 'react' +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { XyzStatusphereDefs } from '@statusphere/lexicon' + +import useAuth from '#/hooks/useAuth' +import api from '#/services/api' + +const STATUS_OPTIONS = [ + '๐Ÿ‘', + '๐Ÿ‘Ž', + '๐Ÿ’™', + '๐Ÿฅน', + '๐Ÿ˜ง', + '๐Ÿ˜ค', + '๐Ÿ™ƒ', + '๐Ÿ˜‰', + '๐Ÿ˜Ž', + '๐Ÿค“', + '๐Ÿคจ', + '๐Ÿฅณ', + '๐Ÿ˜ญ', + '๐Ÿ˜ข', + '๐Ÿคฏ', + '๐Ÿซก', + '๐Ÿ’€', + 'โœŠ', + '๐Ÿค˜', + '๐Ÿ‘€', + '๐Ÿง ', + '๐Ÿ‘ฉโ€๐Ÿ’ป', + '๐Ÿง‘โ€๐Ÿ’ป', + '๐Ÿฅท', + '๐ŸงŒ', + '๐Ÿฆ‹', + '๐Ÿš€', +] + +const StatusForm = () => { + const [error, setError] = useState(null) + const queryClient = useQueryClient() + const { user } = useAuth() + + // Get current user's status emoji + const currentUserStatus = user?.status?.status || null + + // Use React Query mutation for creating a status + const mutation = useMutation({ + mutationFn: (emoji: string) => api.createStatus(emoji), + onMutate: async (emoji) => { + // Cancel any outgoing refetches so they don't overwrite our optimistic updates + await queryClient.cancelQueries({ queryKey: ['statuses'] }) + await queryClient.cancelQueries({ queryKey: ['currentUser'] }) + + // Snapshot the previous values + const previousStatuses = queryClient.getQueryData(['statuses']) + const previousUser = queryClient.getQueryData(['currentUser']) + + // Optimistically update the statuses + queryClient.setQueryData(['statuses'], (oldData: any) => { + if (!oldData) return oldData + if (!user) return oldData + + // Create a provisional status + const optimisticStatus = { + uri: `optimistic-${Date.now()}`, + profile: { + did: user.did, + handle: user.profile.handle, + }, + status: emoji, + createdAt: new Date().toISOString(), + } satisfies XyzStatusphereDefs.StatusView + + return { + ...oldData, + statuses: [optimisticStatus, ...oldData.statuses], + } + }) + + // Optimistically update the user's profile status + queryClient.setQueryData(['currentUser'], (oldUserData: any) => { + if (!oldUserData) return oldUserData + + return { + ...oldUserData, + status: { + ...oldUserData.status, + status: emoji, + createdAt: new Date().toISOString(), + }, + } + }) + + // Return a context with the previous data + return { previousStatuses, previousUser } + }, + onSuccess: () => { + // Refetch after success to get the correct data + queryClient.invalidateQueries({ queryKey: ['statuses'] }) + }, + onError: (err, _emoji, context) => { + const message = + err instanceof Error ? err.message : 'Failed to create status' + setError(message) + + // If we have a previous context, roll back to it + if (context) { + if (context.previousStatuses) { + queryClient.setQueryData(['statuses'], context.previousStatuses) + } else { + queryClient.invalidateQueries({ queryKey: ['statuses'] }) + } + + if (context.previousUser) { + queryClient.setQueryData(['currentUser'], context.previousUser) + } else { + queryClient.invalidateQueries({ queryKey: ['currentUser'] }) + } + } else { + // Otherwise refresh all the data + queryClient.invalidateQueries({ queryKey: ['statuses'] }) + queryClient.invalidateQueries({ queryKey: ['currentUser'] }) + } + }, + }) + + const handleSubmitStatus = (emoji: string) => { + if (mutation.isPending) return + + setError(null) + mutation.mutate(emoji) + } + + return ( +
+

How are you feeling?

+ {(error || mutation.error) && ( +
+ {error || + (mutation.error instanceof Error + ? mutation.error.message + : 'Failed to create status')} +
+ )} + +
+ {STATUS_OPTIONS.map((emoji) => { + const isSelected = mutation.variables === emoji && mutation.isPending + const isCurrentStatus = currentUserStatus === emoji + + return ( + + ) + })} +
+
+ ) +} + +export default StatusForm diff --git a/packages/client/src/components/StatusList.tsx b/packages/client/src/components/StatusList.tsx new file mode 100644 index 0000000..62d67d8 --- /dev/null +++ b/packages/client/src/components/StatusList.tsx @@ -0,0 +1,105 @@ +import { useQuery } from '@tanstack/react-query' + +import api from '#/services/api' + +const StatusList = () => { + // Use React Query to fetch and cache statuses + const { data, isLoading, isError, error } = useQuery({ + queryKey: ['statuses'], + queryFn: async () => { + const data = await api.getStatuses() + return data + }, + placeholderData: (previousData) => previousData, // Use previous data while refetching + }) + + // Destructure data + const statuses = data?.statuses || [] + + if (isLoading && !data) { + return ( +
Loading statuses...
+ ) + } + + if (isError) { + return ( +
+ {(error as Error)?.message || 'Failed to load statuses'} +
+ ) + } + + if (statuses.length === 0) { + return ( +
No statuses yet.
+ ) + } + + // Helper to format dates + const formatDate = (dateString: string) => { + const date = new Date(dateString) + const today = new Date() + const isToday = + date.getDate() === today.getDate() && + date.getMonth() === today.getMonth() && + date.getFullYear() === today.getFullYear() + + if (isToday) { + return 'today' + } else { + return date.toLocaleDateString(undefined, { + year: 'numeric', + month: 'long', + day: 'numeric', + }) + } + } + + return ( +
+
+
+ {statuses.map((status) => { + const handle = + status.profile.handle || status.profile.did.substring(0, 15) + '...' + const formattedDate = formatDate(status.createdAt) + const isToday = formattedDate === 'today' + + return ( +
+
+
{status.status}
+
+
+
+ + @{handle} + {' '} + {isToday ? ( + + is feeling{' '} + {status.status}{' '} + today + + ) : ( + + was feeling{' '} + {status.status} on{' '} + {formattedDate} + + )} +
+
+
+ ) + })} +
+
+ ) +} + +export default StatusList diff --git a/packages/client/src/hooks/useAuth.tsx b/packages/client/src/hooks/useAuth.tsx new file mode 100644 index 0000000..7fe187e --- /dev/null +++ b/packages/client/src/hooks/useAuth.tsx @@ -0,0 +1,130 @@ +import { createContext, ReactNode, useContext, useState } from 'react' +import { useQuery, useQueryClient } from '@tanstack/react-query' + +import api, { User } from '#/services/api' + +interface AuthContextType { + user: User | null + loading: boolean + error: string | null + login: (handle: string) => Promise<{ redirectUrl: string }> + logout: () => Promise +} + +const AuthContext = createContext(undefined) + +export function AuthProvider({ children }: { children: ReactNode }) { + const [error, setError] = useState(null) + const queryClient = useQueryClient() + + // Use React Query to fetch and manage user data + const { + data: user, + isLoading: loading, + error: queryError, + } = useQuery({ + queryKey: ['currentUser'], + queryFn: async () => { + // Check for error parameter in URL (from OAuth redirect) + const urlParams = new URLSearchParams(window.location.search) + const errorParam = urlParams.get('error') + + if (errorParam) { + setError('Authentication failed. Please try again.') + + // Remove the error parameter from the URL + const newUrl = window.location.pathname + window.history.replaceState({}, document.title, newUrl) + return null + } + + try { + const userData = await api.getCurrentUser() + + // Clean up URL if needed + if (window.location.search && userData) { + window.history.replaceState( + {}, + document.title, + window.location.pathname, + ) + } + + return userData + } catch (apiErr) { + console.error('๐Ÿšซ API error during auth check:', apiErr) + + // If it's a network error, provide a more helpful message + if ( + apiErr instanceof TypeError && + apiErr.message.includes('Failed to fetch') + ) { + throw new Error( + 'Cannot connect to API server. Please check your network connection or server status.', + ) + } + + throw apiErr + } + }, + retry: false, + staleTime: 5 * 60 * 1000, // 5 minutes + }) + + const login = async (handle: string) => { + setError(null) + + try { + const result = await api.login(handle) + return result + } catch (err) { + const message = err instanceof Error ? err.message : 'Login failed' + setError(message) + throw err + } + } + + const logout = async () => { + try { + await api.logout() + // Reset the user data in React Query cache + queryClient.setQueryData(['currentUser'], null) + // Invalidate any user-dependent queries + queryClient.invalidateQueries({ queryKey: ['statuses'] }) + } catch (err) { + const message = err instanceof Error ? err.message : 'Logout failed' + setError(message) + throw err + } + } + + // Combine state error with query error + const combinedError = + error || (queryError instanceof Error ? queryError.message : null) + + return ( + + {children} + + ) +} + +export function useAuth() { + const context = useContext(AuthContext) + + if (context === undefined) { + throw new Error('useAuth must be used within an AuthProvider') + } + + return context +} + +export default useAuth diff --git a/packages/client/src/index.css b/packages/client/src/index.css new file mode 100644 index 0000000..53bed96 --- /dev/null +++ b/packages/client/src/index.css @@ -0,0 +1,11 @@ +@import 'tailwindcss'; + +@keyframes fadeOut { + 0% { opacity: 1; } + 75% { opacity: 1; } /* Hold full opacity for most of the animation */ + 100% { opacity: 0; } +} + +.status-message-fade { + animation: fadeOut 2s forwards; +} diff --git a/packages/client/src/main.tsx b/packages/client/src/main.tsx new file mode 100644 index 0000000..eb786e2 --- /dev/null +++ b/packages/client/src/main.tsx @@ -0,0 +1,20 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import { BrowserRouter } from 'react-router-dom' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' + +import App from '#/App' + +import '#/index.css' + +const queryClient = new QueryClient() + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + + , +) diff --git a/packages/client/src/pages/HomePage.tsx b/packages/client/src/pages/HomePage.tsx new file mode 100644 index 0000000..3d4928b --- /dev/null +++ b/packages/client/src/pages/HomePage.tsx @@ -0,0 +1,55 @@ +import Header from '#/components/Header' +import StatusForm from '#/components/StatusForm' +import StatusList from '#/components/StatusList' +import { useAuth } from '#/hooks/useAuth' + +const HomePage = () => { + const { user, loading, error } = useAuth() + + if (loading) { + return ( +
+
+

+ Loading Statusphere... +

+

Setting up your experience

+
+
+ ) + } + + if (error) { + return ( +
+
+

Error

+

{error}

+ + Try logging in again + +
+
+ ) + } + + return ( +
+
+ + {user && } + +
+

+ Recent Statuses +

+ +
+
+ ) +} + +export default HomePage diff --git a/packages/client/src/pages/LoginPage.tsx b/packages/client/src/pages/LoginPage.tsx new file mode 100644 index 0000000..f38e7a4 --- /dev/null +++ b/packages/client/src/pages/LoginPage.tsx @@ -0,0 +1,83 @@ +import { useState } from 'react' +import { Link } from 'react-router-dom' + +import Header from '#/components/Header' +import { useAuth } from '#/hooks/useAuth' + +const LoginPage = () => { + const [handle, setHandle] = useState('') + const [error, setError] = useState(null) + const { login, loading } = useAuth() + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + + if (!handle.trim()) { + setError('Handle cannot be empty') + return + } + + try { + const { redirectUrl } = await login(handle) + // Redirect to ATProto OAuth flow + window.location.href = redirectUrl + } catch (err) { + const message = err instanceof Error ? err.message : 'Login failed' + setError(message) + } + } + + return ( +
+
+ +
+

Login with your handle

+ + {error && ( +
+ {error} +
+ )} + +
+
+ + setHandle(e.target.value)} + placeholder="example.bsky.social" + disabled={loading} + className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-300" + /> +
+ + +
+ +
+ + Cancel + +
+
+
+ ) +} + +export default LoginPage diff --git a/packages/client/src/pages/OAuthCallbackPage.tsx b/packages/client/src/pages/OAuthCallbackPage.tsx new file mode 100644 index 0000000..06ef718 --- /dev/null +++ b/packages/client/src/pages/OAuthCallbackPage.tsx @@ -0,0 +1,99 @@ +import { useEffect, useState } from 'react' +import { useNavigate } from 'react-router-dom' + +import { api } from '../services/api' + +const OAuthCallbackPage = () => { + const [error, setError] = useState(null) + const [message, setMessage] = useState('Completing authentication...') + const navigate = useNavigate() + + useEffect(() => { + console.log('OAuth callback page reached') + setMessage('OAuth callback page reached. Checking authentication...') + + const checkAuth = async () => { + try { + // Check if there's an error in the URL + const params = new URLSearchParams(window.location.search) + if (params.get('error')) { + console.error('Auth error detected in URL params') + setError('Authentication failed') + return + } + + // Give cookies a moment to be processed + await new Promise((resolve) => setTimeout(resolve, 500)) + setMessage("Checking if we're authenticated...") + + // Check if we're authenticated by fetching current user + try { + console.log('Checking current user') + console.log( + 'Cookies being sent:', + document.cookie + .split(';') + .map((c) => c.trim()) + .join(', '), + ) + + const user = await api.getCurrentUser() + console.log('Current user check result:', user) + + if (user) { + console.log('Successfully authenticated', user) + setMessage('Authentication successful! Redirecting...') + // Redirect to home after a short delay + setTimeout(() => { + navigate('/') + }, 1000) + } else { + console.error('Auth check returned no user') + setError('Authentication session not found') + } + } catch (apiErr) { + console.error('API error during auth check:', apiErr) + setError('Failed to verify authentication') + } + } catch (err) { + console.error('General error in OAuth callback:', err) + setError('Failed to complete authentication') + } + } + + checkAuth() + }, [navigate]) + + return ( +
+
+ {error ? ( +
+

+ Authentication Failed +

+

{error}

+ +
+ ) : ( +
+

+ Authentication in Progress +

+
+
+
+

{message}

+
+ )} +
+
+ ) +} + +export default OAuthCallbackPage diff --git a/packages/client/src/services/api.ts b/packages/client/src/services/api.ts new file mode 100644 index 0000000..882d942 --- /dev/null +++ b/packages/client/src/services/api.ts @@ -0,0 +1,160 @@ +import { AppBskyActorDefs, XyzStatusphereDefs } from '@statusphere/lexicon' + +const API_URL = import.meta.env.VITE_API_URL || '/api' + +// Helper function for logging API actions +function logApiCall( + method: string, + endpoint: string, + status?: number, + error?: any, +) { + const statusStr = status ? `[${status}]` : '' + const errorStr = error + ? ` - Error: ${error.message || JSON.stringify(error)}` + : '' + console.log(`๐Ÿ”„ API ${method} ${endpoint} ${statusStr}${errorStr}`) +} + +export interface User { + did: string + profile: AppBskyActorDefs.ProfileView + status?: XyzStatusphereDefs.StatusView +} + +// API service +export const api = { + // Get base URL + getBaseUrl() { + return API_URL || '' + }, + // Login + async login(handle: string) { + const url = API_URL ? `${API_URL}/login` : '/login' + logApiCall('POST', url) + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ handle }), + }) + + if (!response.ok) { + const error = await response.json() + throw new Error(error.error || 'Login failed') + } + + return response.json() + }, + + // Logout + async logout() { + const url = API_URL ? `${API_URL}/logout` : '/logout' + logApiCall('POST', url) + const response = await fetch(url, { + method: 'POST', + credentials: 'include', + }) + + if (!response.ok) { + throw new Error('Logout failed') + } + + return response.json() + }, + + // Get current user + async getCurrentUser() { + const url = API_URL ? `${API_URL}/user` : '/user' + logApiCall('GET', url) + try { + console.log('๐Ÿ“ž Fetching user from:', url, 'with credentials included') + // Debug output - what headers are we sending? + const headers = { + Accept: 'application/json', + } + console.log('๐Ÿ“จ Request headers:', headers) + + const response = await fetch(url, { + credentials: 'include', // This is crucial for sending cookies + headers, + cache: 'no-cache', // Don't cache this request + }) + + logApiCall('GET', '/user', response.status) + + if (!response.ok) { + if (response.status === 401) { + return null + } + + // Try to get error details + let errorText = '' + try { + const errorData = await response.text() + errorText = errorData + } catch (e) { + // Ignore error reading error + } + + throw new Error( + `Failed to get user: ${response.status} ${response.statusText} ${errorText}`, + ) + } + + return response.json() + } catch (error) { + logApiCall('GET', '/user', undefined, error) + if ( + error instanceof TypeError && + error.message.includes('Failed to fetch') + ) { + console.error('Network error - Unable to connect to API server') + } + throw error + } + }, + + // Get statuses + async getStatuses() { + const url = API_URL ? `${API_URL}/statuses` : '/statuses' + logApiCall('GET', url) + const response = await fetch(url, { + credentials: 'include', + }) + + if (!response.ok) { + throw new Error('Failed to get statuses') + } + + return response.json() as Promise<{ + statuses: XyzStatusphereDefs.StatusView[] + }> + }, + + // Create status + async createStatus(status: string) { + const url = API_URL ? `${API_URL}/status` : '/status' + logApiCall('POST', url) + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ status }), + }) + + if (!response.ok) { + const error = await response.json() + throw new Error(error.error || 'Failed to create status') + } + + return response.json() + }, +} + +export default api diff --git a/packages/client/src/vite-env.d.ts b/packages/client/src/vite-env.d.ts new file mode 100644 index 0000000..b54b4c9 --- /dev/null +++ b/packages/client/src/vite-env.d.ts @@ -0,0 +1,9 @@ +/// + +interface ImportMetaEnv { + readonly VITE_API_URL: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json new file mode 100644 index 0000000..dee539f --- /dev/null +++ b/packages/client/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { + "#/*": ["./src/*"] + } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/client/tsconfig.node.json b/packages/client/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/packages/client/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/client/vite.config.ts b/packages/client/vite.config.ts new file mode 100644 index 0000000..c2ff370 --- /dev/null +++ b/packages/client/vite.config.ts @@ -0,0 +1,26 @@ +import path from 'path' +import tailwindcss from '@tailwindcss/vite' +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react(), tailwindcss()], + server: { + port: 3000, + // allow ngrok + allowedHosts: true, + proxy: { + '/api': { + target: 'http://localhost:3001', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, ''), + }, + }, + }, + resolve: { + alias: { + '#': path.resolve(__dirname, './src'), + }, + }, +}) diff --git a/packages/lexicon/package.json b/packages/lexicon/package.json new file mode 100644 index 0000000..8c084c6 --- /dev/null +++ b/packages/lexicon/package.json @@ -0,0 +1,43 @@ +{ + "name": "@statusphere/lexicon", + "version": "0.0.1", + "description": "Generated API client for Statusphere lexicons", + "author": "", + "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "private": true, + "scripts": { + "build": "pnpm run lexgen && tsup", + "dev": "tsup --watch", + "clean": "rimraf dist", + "typecheck": "tsc --noEmit", + "lexgen": "lex gen-api ./src ../../lexicons/xyz/statusphere/* ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/* --yes" + }, + "dependencies": { + "@atproto/api": "^0.14.7", + "@atproto/lexicon": "^0.4.7", + "@atproto/syntax": "^0.3.3", + "@atproto/xrpc": "^0.6.9", + "multiformats": "^13.3.2" + }, + "devDependencies": { + "@atproto/lex-cli": "^0.6.1", + "@types/node": "^22.13.8", + "rimraf": "^6.0.1", + "tsup": "^8.4.0", + "typescript": "^5.8.2" + }, + "tsup": { + "entry": [ + "src/index.ts" + ], + "format": [ + "cjs", + "esm" + ], + "dts": true, + "sourcemap": true, + "clean": true + } +} diff --git a/packages/lexicon/src/index.ts b/packages/lexicon/src/index.ts new file mode 100644 index 0000000..f7ff1c1 --- /dev/null +++ b/packages/lexicon/src/index.ts @@ -0,0 +1,381 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { FetchHandler, FetchHandlerOptions, XrpcClient } from '@atproto/xrpc' +import { CID } from 'multiformats/cid' + +import { schemas } from './lexicons.js' +import * as AppBskyActorDefs from './types/app/bsky/actor/defs.js' +import * as AppBskyActorProfile from './types/app/bsky/actor/profile.js' +import * as ComAtprotoLabelDefs from './types/com/atproto/label/defs.js' +import * as ComAtprotoRepoApplyWrites from './types/com/atproto/repo/applyWrites.js' +import * as ComAtprotoRepoCreateRecord from './types/com/atproto/repo/createRecord.js' +import * as ComAtprotoRepoDefs from './types/com/atproto/repo/defs.js' +import * as ComAtprotoRepoDeleteRecord from './types/com/atproto/repo/deleteRecord.js' +import * as ComAtprotoRepoDescribeRepo from './types/com/atproto/repo/describeRepo.js' +import * as ComAtprotoRepoGetRecord from './types/com/atproto/repo/getRecord.js' +import * as ComAtprotoRepoImportRepo from './types/com/atproto/repo/importRepo.js' +import * as ComAtprotoRepoListMissingBlobs from './types/com/atproto/repo/listMissingBlobs.js' +import * as ComAtprotoRepoListRecords from './types/com/atproto/repo/listRecords.js' +import * as ComAtprotoRepoPutRecord from './types/com/atproto/repo/putRecord.js' +import * as ComAtprotoRepoStrongRef from './types/com/atproto/repo/strongRef.js' +import * as ComAtprotoRepoUploadBlob from './types/com/atproto/repo/uploadBlob.js' +import * as XyzStatusphereDefs from './types/xyz/statusphere/defs.js' +import * as XyzStatusphereStatus from './types/xyz/statusphere/status.js' +import { OmitKey, Un$Typed } from './util.js' + +export * as XyzStatusphereDefs from './types/xyz/statusphere/defs.js' +export * as XyzStatusphereStatus from './types/xyz/statusphere/status.js' +export * as ComAtprotoLabelDefs from './types/com/atproto/label/defs.js' +export * as ComAtprotoRepoApplyWrites from './types/com/atproto/repo/applyWrites.js' +export * as ComAtprotoRepoCreateRecord from './types/com/atproto/repo/createRecord.js' +export * as ComAtprotoRepoDefs from './types/com/atproto/repo/defs.js' +export * as ComAtprotoRepoDeleteRecord from './types/com/atproto/repo/deleteRecord.js' +export * as ComAtprotoRepoDescribeRepo from './types/com/atproto/repo/describeRepo.js' +export * as ComAtprotoRepoGetRecord from './types/com/atproto/repo/getRecord.js' +export * as ComAtprotoRepoImportRepo from './types/com/atproto/repo/importRepo.js' +export * as ComAtprotoRepoListMissingBlobs from './types/com/atproto/repo/listMissingBlobs.js' +export * as ComAtprotoRepoListRecords from './types/com/atproto/repo/listRecords.js' +export * as ComAtprotoRepoPutRecord from './types/com/atproto/repo/putRecord.js' +export * as ComAtprotoRepoStrongRef from './types/com/atproto/repo/strongRef.js' +export * as ComAtprotoRepoUploadBlob from './types/com/atproto/repo/uploadBlob.js' +export * as AppBskyActorDefs from './types/app/bsky/actor/defs.js' +export * as AppBskyActorProfile from './types/app/bsky/actor/profile.js' + +export class AtpBaseClient extends XrpcClient { + xyz: XyzNS + com: ComNS + app: AppNS + + constructor(options: FetchHandler | FetchHandlerOptions) { + super(options, schemas) + this.xyz = new XyzNS(this) + this.com = new ComNS(this) + this.app = new AppNS(this) + } + + /** @deprecated use `this` instead */ + get xrpc(): XrpcClient { + return this + } +} + +export class XyzNS { + _client: XrpcClient + statusphere: XyzStatusphereNS + + constructor(client: XrpcClient) { + this._client = client + this.statusphere = new XyzStatusphereNS(client) + } +} + +export class XyzStatusphereNS { + _client: XrpcClient + status: StatusRecord + + constructor(client: XrpcClient) { + this._client = client + this.status = new StatusRecord(client) + } +} + +export class StatusRecord { + _client: XrpcClient + + constructor(client: XrpcClient) { + this._client = client + } + + async list( + params: OmitKey, + ): Promise<{ + cursor?: string + records: { uri: string; value: XyzStatusphereStatus.Record }[] + }> { + const res = await this._client.call('com.atproto.repo.listRecords', { + collection: 'xyz.statusphere.status', + ...params, + }) + return res.data + } + + async get( + params: OmitKey, + ): Promise<{ uri: string; cid: string; value: XyzStatusphereStatus.Record }> { + const res = await this._client.call('com.atproto.repo.getRecord', { + collection: 'xyz.statusphere.status', + ...params, + }) + return res.data + } + + async create( + params: OmitKey< + ComAtprotoRepoCreateRecord.InputSchema, + 'collection' | 'record' + >, + record: Un$Typed, + headers?: Record, + ): Promise<{ uri: string; cid: string }> { + const collection = 'xyz.statusphere.status' + const res = await this._client.call( + 'com.atproto.repo.createRecord', + undefined, + { collection, ...params, record: { ...record, $type: collection } }, + { encoding: 'application/json', headers }, + ) + return res.data + } + + async delete( + params: OmitKey, + headers?: Record, + ): Promise { + await this._client.call( + 'com.atproto.repo.deleteRecord', + undefined, + { collection: 'xyz.statusphere.status', ...params }, + { headers }, + ) + } +} + +export class ComNS { + _client: XrpcClient + atproto: ComAtprotoNS + + constructor(client: XrpcClient) { + this._client = client + this.atproto = new ComAtprotoNS(client) + } +} + +export class ComAtprotoNS { + _client: XrpcClient + repo: ComAtprotoRepoNS + + constructor(client: XrpcClient) { + this._client = client + this.repo = new ComAtprotoRepoNS(client) + } +} + +export class ComAtprotoRepoNS { + _client: XrpcClient + + constructor(client: XrpcClient) { + this._client = client + } + + applyWrites( + data?: ComAtprotoRepoApplyWrites.InputSchema, + opts?: ComAtprotoRepoApplyWrites.CallOptions, + ): Promise { + return this._client + .call('com.atproto.repo.applyWrites', opts?.qp, data, opts) + .catch((e) => { + throw ComAtprotoRepoApplyWrites.toKnownErr(e) + }) + } + + createRecord( + data?: ComAtprotoRepoCreateRecord.InputSchema, + opts?: ComAtprotoRepoCreateRecord.CallOptions, + ): Promise { + return this._client + .call('com.atproto.repo.createRecord', opts?.qp, data, opts) + .catch((e) => { + throw ComAtprotoRepoCreateRecord.toKnownErr(e) + }) + } + + deleteRecord( + data?: ComAtprotoRepoDeleteRecord.InputSchema, + opts?: ComAtprotoRepoDeleteRecord.CallOptions, + ): Promise { + return this._client + .call('com.atproto.repo.deleteRecord', opts?.qp, data, opts) + .catch((e) => { + throw ComAtprotoRepoDeleteRecord.toKnownErr(e) + }) + } + + describeRepo( + params?: ComAtprotoRepoDescribeRepo.QueryParams, + opts?: ComAtprotoRepoDescribeRepo.CallOptions, + ): Promise { + return this._client.call( + 'com.atproto.repo.describeRepo', + params, + undefined, + opts, + ) + } + + getRecord( + params?: ComAtprotoRepoGetRecord.QueryParams, + opts?: ComAtprotoRepoGetRecord.CallOptions, + ): Promise { + return this._client + .call('com.atproto.repo.getRecord', params, undefined, opts) + .catch((e) => { + throw ComAtprotoRepoGetRecord.toKnownErr(e) + }) + } + + importRepo( + data?: ComAtprotoRepoImportRepo.InputSchema, + opts?: ComAtprotoRepoImportRepo.CallOptions, + ): Promise { + return this._client.call( + 'com.atproto.repo.importRepo', + opts?.qp, + data, + opts, + ) + } + + listMissingBlobs( + params?: ComAtprotoRepoListMissingBlobs.QueryParams, + opts?: ComAtprotoRepoListMissingBlobs.CallOptions, + ): Promise { + return this._client.call( + 'com.atproto.repo.listMissingBlobs', + params, + undefined, + opts, + ) + } + + listRecords( + params?: ComAtprotoRepoListRecords.QueryParams, + opts?: ComAtprotoRepoListRecords.CallOptions, + ): Promise { + return this._client.call( + 'com.atproto.repo.listRecords', + params, + undefined, + opts, + ) + } + + putRecord( + data?: ComAtprotoRepoPutRecord.InputSchema, + opts?: ComAtprotoRepoPutRecord.CallOptions, + ): Promise { + return this._client + .call('com.atproto.repo.putRecord', opts?.qp, data, opts) + .catch((e) => { + throw ComAtprotoRepoPutRecord.toKnownErr(e) + }) + } + + uploadBlob( + data?: ComAtprotoRepoUploadBlob.InputSchema, + opts?: ComAtprotoRepoUploadBlob.CallOptions, + ): Promise { + return this._client.call( + 'com.atproto.repo.uploadBlob', + opts?.qp, + data, + opts, + ) + } +} + +export class AppNS { + _client: XrpcClient + bsky: AppBskyNS + + constructor(client: XrpcClient) { + this._client = client + this.bsky = new AppBskyNS(client) + } +} + +export class AppBskyNS { + _client: XrpcClient + actor: AppBskyActorNS + + constructor(client: XrpcClient) { + this._client = client + this.actor = new AppBskyActorNS(client) + } +} + +export class AppBskyActorNS { + _client: XrpcClient + profile: ProfileRecord + + constructor(client: XrpcClient) { + this._client = client + this.profile = new ProfileRecord(client) + } +} + +export class ProfileRecord { + _client: XrpcClient + + constructor(client: XrpcClient) { + this._client = client + } + + async list( + params: OmitKey, + ): Promise<{ + cursor?: string + records: { uri: string; value: AppBskyActorProfile.Record }[] + }> { + const res = await this._client.call('com.atproto.repo.listRecords', { + collection: 'app.bsky.actor.profile', + ...params, + }) + return res.data + } + + async get( + params: OmitKey, + ): Promise<{ uri: string; cid: string; value: AppBskyActorProfile.Record }> { + const res = await this._client.call('com.atproto.repo.getRecord', { + collection: 'app.bsky.actor.profile', + ...params, + }) + return res.data + } + + async create( + params: OmitKey< + ComAtprotoRepoCreateRecord.InputSchema, + 'collection' | 'record' + >, + record: Un$Typed, + headers?: Record, + ): Promise<{ uri: string; cid: string }> { + const collection = 'app.bsky.actor.profile' + const res = await this._client.call( + 'com.atproto.repo.createRecord', + undefined, + { + collection, + rkey: 'self', + ...params, + record: { ...record, $type: collection }, + }, + { encoding: 'application/json', headers }, + ) + return res.data + } + + async delete( + params: OmitKey, + headers?: Record, + ): Promise { + await this._client.call( + 'com.atproto.repo.deleteRecord', + undefined, + { collection: 'app.bsky.actor.profile', ...params }, + { headers }, + ) + } +} diff --git a/packages/lexicon/src/lexicons.ts b/packages/lexicon/src/lexicons.ts new file mode 100644 index 0000000..7e49a04 --- /dev/null +++ b/packages/lexicon/src/lexicons.ts @@ -0,0 +1,1188 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { + LexiconDoc, + Lexicons, + ValidationError, + ValidationResult, +} from '@atproto/lexicon' + +import { $Typed, is$typed, maybe$typed } from './util.js' + +export const schemaDict = { + XyzStatusphereDefs: { + lexicon: 1, + id: 'xyz.statusphere.defs', + defs: { + statusView: { + type: 'object', + required: ['uri', 'status', 'profile', 'createdAt'], + properties: { + uri: { + type: 'string', + format: 'at-uri', + }, + status: { + type: 'string', + minLength: 1, + maxGraphemes: 1, + maxLength: 32, + }, + createdAt: { + type: 'string', + format: 'datetime', + }, + profile: { + type: 'ref', + ref: 'lex:xyz.statusphere.defs#profileView', + }, + }, + }, + profileView: { + type: 'object', + required: ['did', 'handle'], + properties: { + did: { + type: 'string', + format: 'did', + }, + handle: { + type: 'string', + format: 'handle', + }, + }, + }, + }, + }, + XyzStatusphereStatus: { + lexicon: 1, + id: 'xyz.statusphere.status', + defs: { + main: { + type: 'record', + key: 'tid', + record: { + type: 'object', + required: ['status', 'createdAt'], + properties: { + status: { + type: 'string', + minLength: 1, + maxGraphemes: 1, + maxLength: 32, + }, + createdAt: { + type: 'string', + format: 'datetime', + }, + }, + }, + }, + }, + }, + ComAtprotoLabelDefs: { + lexicon: 1, + id: 'com.atproto.label.defs', + defs: { + label: { + type: 'object', + description: + 'Metadata tag on an atproto resource (eg, repo or record).', + required: ['src', 'uri', 'val', 'cts'], + properties: { + ver: { + type: 'integer', + description: 'The AT Protocol version of the label object.', + }, + src: { + type: 'string', + format: 'did', + description: 'DID of the actor who created this label.', + }, + uri: { + type: 'string', + format: 'uri', + description: + 'AT URI of the record, repository (account), or other resource that this label applies to.', + }, + cid: { + type: 'string', + format: 'cid', + description: + "Optionally, CID specifying the specific version of 'uri' resource this label applies to.", + }, + val: { + type: 'string', + maxLength: 128, + description: + 'The short string name of the value or type of this label.', + }, + neg: { + type: 'boolean', + description: + 'If true, this is a negation label, overwriting a previous label.', + }, + cts: { + type: 'string', + format: 'datetime', + description: 'Timestamp when this label was created.', + }, + exp: { + type: 'string', + format: 'datetime', + description: + 'Timestamp at which this label expires (no longer applies).', + }, + sig: { + type: 'bytes', + description: 'Signature of dag-cbor encoded label.', + }, + }, + }, + selfLabels: { + type: 'object', + description: + 'Metadata tags on an atproto record, published by the author within the record.', + required: ['values'], + properties: { + values: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:com.atproto.label.defs#selfLabel', + }, + maxLength: 10, + }, + }, + }, + selfLabel: { + type: 'object', + description: + 'Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.', + required: ['val'], + properties: { + val: { + type: 'string', + maxLength: 128, + description: + 'The short string name of the value or type of this label.', + }, + }, + }, + labelValueDefinition: { + type: 'object', + description: + 'Declares a label value and its expected interpretations and behaviors.', + required: ['identifier', 'severity', 'blurs', 'locales'], + properties: { + identifier: { + type: 'string', + description: + "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", + maxLength: 100, + maxGraphemes: 100, + }, + severity: { + type: 'string', + description: + "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", + knownValues: ['inform', 'alert', 'none'], + }, + blurs: { + type: 'string', + 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.", + knownValues: ['content', 'media', 'none'], + }, + defaultSetting: { + type: 'string', + description: 'The default setting for this label.', + knownValues: ['ignore', 'warn', 'hide'], + default: 'warn', + }, + adultOnly: { + type: 'boolean', + description: + 'Does the user need to have adult content enabled in order to configure this label?', + }, + locales: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:com.atproto.label.defs#labelValueDefinitionStrings', + }, + }, + }, + }, + labelValueDefinitionStrings: { + type: 'object', + description: + 'Strings which describe the label in the UI, localized into a specific language.', + required: ['lang', 'name', 'description'], + properties: { + lang: { + type: 'string', + description: + 'The code of the language these strings are written in.', + format: 'language', + }, + name: { + type: 'string', + description: 'A short human-readable name for the label.', + maxGraphemes: 64, + maxLength: 640, + }, + description: { + type: 'string', + description: + 'A longer description of what the label means and why it might be applied.', + maxGraphemes: 10000, + maxLength: 100000, + }, + }, + }, + labelValue: { + type: 'string', + knownValues: [ + '!hide', + '!no-promote', + '!warn', + '!no-unauthenticated', + 'dmca-violation', + 'doxxing', + 'porn', + 'sexual', + 'nudity', + 'nsfl', + 'gore', + ], + }, + }, + }, + ComAtprotoRepoApplyWrites: { + lexicon: 1, + id: 'com.atproto.repo.applyWrites', + defs: { + main: { + type: 'procedure', + description: + 'Apply a batch transaction of repository creates, updates, and deletes. Requires auth, implemented by PDS.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['repo', 'writes'], + properties: { + repo: { + type: 'string', + format: 'at-identifier', + description: + 'The handle or DID of the repo (aka, current account).', + }, + validate: { + type: 'boolean', + 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.", + }, + writes: { + type: 'array', + items: { + type: 'union', + refs: [ + 'lex:com.atproto.repo.applyWrites#create', + 'lex:com.atproto.repo.applyWrites#update', + 'lex:com.atproto.repo.applyWrites#delete', + ], + closed: true, + }, + }, + swapCommit: { + type: 'string', + 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.', + format: 'cid', + }, + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: [], + properties: { + commit: { + type: 'ref', + ref: 'lex:com.atproto.repo.defs#commitMeta', + }, + results: { + type: 'array', + items: { + type: 'union', + refs: [ + 'lex:com.atproto.repo.applyWrites#createResult', + 'lex:com.atproto.repo.applyWrites#updateResult', + 'lex:com.atproto.repo.applyWrites#deleteResult', + ], + closed: true, + }, + }, + }, + }, + }, + errors: [ + { + name: 'InvalidSwap', + description: + "Indicates that the 'swapCommit' parameter did not match current commit.", + }, + ], + }, + create: { + type: 'object', + description: 'Operation which creates a new record.', + required: ['collection', 'value'], + properties: { + collection: { + type: 'string', + format: 'nsid', + }, + rkey: { + type: 'string', + maxLength: 512, + format: 'record-key', + description: + 'NOTE: maxLength is redundant with record-key format. Keeping it temporarily to ensure backwards compatibility.', + }, + value: { + type: 'unknown', + }, + }, + }, + update: { + type: 'object', + description: 'Operation which updates an existing record.', + required: ['collection', 'rkey', 'value'], + properties: { + collection: { + type: 'string', + format: 'nsid', + }, + rkey: { + type: 'string', + format: 'record-key', + }, + value: { + type: 'unknown', + }, + }, + }, + delete: { + type: 'object', + description: 'Operation which deletes an existing record.', + required: ['collection', 'rkey'], + properties: { + collection: { + type: 'string', + format: 'nsid', + }, + rkey: { + type: 'string', + format: 'record-key', + }, + }, + }, + createResult: { + type: 'object', + required: ['uri', 'cid'], + properties: { + uri: { + type: 'string', + format: 'at-uri', + }, + cid: { + type: 'string', + format: 'cid', + }, + validationStatus: { + type: 'string', + knownValues: ['valid', 'unknown'], + }, + }, + }, + updateResult: { + type: 'object', + required: ['uri', 'cid'], + properties: { + uri: { + type: 'string', + format: 'at-uri', + }, + cid: { + type: 'string', + format: 'cid', + }, + validationStatus: { + type: 'string', + knownValues: ['valid', 'unknown'], + }, + }, + }, + deleteResult: { + type: 'object', + required: [], + properties: {}, + }, + }, + }, + ComAtprotoRepoCreateRecord: { + lexicon: 1, + id: 'com.atproto.repo.createRecord', + defs: { + main: { + type: 'procedure', + description: + 'Create a single new repository record. Requires auth, implemented by PDS.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['repo', 'collection', 'record'], + properties: { + repo: { + type: 'string', + format: 'at-identifier', + description: + 'The handle or DID of the repo (aka, current account).', + }, + collection: { + type: 'string', + format: 'nsid', + description: 'The NSID of the record collection.', + }, + rkey: { + type: 'string', + format: 'record-key', + description: 'The Record Key.', + maxLength: 512, + }, + validate: { + type: 'boolean', + 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.", + }, + record: { + type: 'unknown', + description: 'The record itself. Must contain a $type field.', + }, + swapCommit: { + type: 'string', + format: 'cid', + description: + 'Compare and swap with the previous commit by CID.', + }, + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['uri', 'cid'], + properties: { + uri: { + type: 'string', + format: 'at-uri', + }, + cid: { + type: 'string', + format: 'cid', + }, + commit: { + type: 'ref', + ref: 'lex:com.atproto.repo.defs#commitMeta', + }, + validationStatus: { + type: 'string', + knownValues: ['valid', 'unknown'], + }, + }, + }, + }, + errors: [ + { + name: 'InvalidSwap', + description: + "Indicates that 'swapCommit' didn't match current repo commit.", + }, + ], + }, + }, + }, + ComAtprotoRepoDefs: { + lexicon: 1, + id: 'com.atproto.repo.defs', + defs: { + commitMeta: { + type: 'object', + required: ['cid', 'rev'], + properties: { + cid: { + type: 'string', + format: 'cid', + }, + rev: { + type: 'string', + format: 'tid', + }, + }, + }, + }, + }, + ComAtprotoRepoDeleteRecord: { + lexicon: 1, + id: 'com.atproto.repo.deleteRecord', + defs: { + main: { + type: 'procedure', + description: + "Delete a repository record, or ensure it doesn't exist. Requires auth, implemented by PDS.", + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['repo', 'collection', 'rkey'], + properties: { + repo: { + type: 'string', + format: 'at-identifier', + description: + 'The handle or DID of the repo (aka, current account).', + }, + collection: { + type: 'string', + format: 'nsid', + description: 'The NSID of the record collection.', + }, + rkey: { + type: 'string', + format: 'record-key', + description: 'The Record Key.', + }, + swapRecord: { + type: 'string', + format: 'cid', + description: + 'Compare and swap with the previous record by CID.', + }, + swapCommit: { + type: 'string', + format: 'cid', + description: + 'Compare and swap with the previous commit by CID.', + }, + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + properties: { + commit: { + type: 'ref', + ref: 'lex:com.atproto.repo.defs#commitMeta', + }, + }, + }, + }, + errors: [ + { + name: 'InvalidSwap', + }, + ], + }, + }, + }, + ComAtprotoRepoDescribeRepo: { + lexicon: 1, + id: 'com.atproto.repo.describeRepo', + defs: { + main: { + type: 'query', + description: + 'Get information about an account and repository, including the list of collections. Does not require auth.', + parameters: { + type: 'params', + required: ['repo'], + properties: { + repo: { + type: 'string', + format: 'at-identifier', + description: 'The handle or DID of the repo.', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: [ + 'handle', + 'did', + 'didDoc', + 'collections', + 'handleIsCorrect', + ], + properties: { + handle: { + type: 'string', + format: 'handle', + }, + did: { + type: 'string', + format: 'did', + }, + didDoc: { + type: 'unknown', + description: 'The complete DID document for this account.', + }, + collections: { + type: 'array', + description: + 'List of all the collections (NSIDs) for which this repo contains at least one record.', + items: { + type: 'string', + format: 'nsid', + }, + }, + handleIsCorrect: { + type: 'boolean', + description: + 'Indicates if handle is currently valid (resolves bi-directionally)', + }, + }, + }, + }, + }, + }, + }, + ComAtprotoRepoGetRecord: { + lexicon: 1, + id: 'com.atproto.repo.getRecord', + defs: { + main: { + type: 'query', + description: + 'Get a single record from a repository. Does not require auth.', + parameters: { + type: 'params', + required: ['repo', 'collection', 'rkey'], + properties: { + repo: { + type: 'string', + format: 'at-identifier', + description: 'The handle or DID of the repo.', + }, + collection: { + type: 'string', + format: 'nsid', + description: 'The NSID of the record collection.', + }, + rkey: { + type: 'string', + description: 'The Record Key.', + format: 'record-key', + }, + cid: { + type: 'string', + format: 'cid', + description: + 'The CID of the version of the record. If not specified, then return the most recent version.', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['uri', 'value'], + properties: { + uri: { + type: 'string', + format: 'at-uri', + }, + cid: { + type: 'string', + format: 'cid', + }, + value: { + type: 'unknown', + }, + }, + }, + }, + errors: [ + { + name: 'RecordNotFound', + }, + ], + }, + }, + }, + ComAtprotoRepoImportRepo: { + lexicon: 1, + id: 'com.atproto.repo.importRepo', + defs: { + main: { + type: 'procedure', + description: + 'Import a repo in the form of a CAR file. Requires Content-Length HTTP header to be set.', + input: { + encoding: 'application/vnd.ipld.car', + }, + }, + }, + }, + ComAtprotoRepoListMissingBlobs: { + lexicon: 1, + id: 'com.atproto.repo.listMissingBlobs', + defs: { + main: { + type: 'query', + description: + 'Returns a list of missing blobs for the requesting account. Intended to be used in the account migration flow.', + parameters: { + type: 'params', + properties: { + limit: { + type: 'integer', + minimum: 1, + maximum: 1000, + default: 500, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['blobs'], + properties: { + cursor: { + type: 'string', + }, + blobs: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:com.atproto.repo.listMissingBlobs#recordBlob', + }, + }, + }, + }, + }, + }, + recordBlob: { + type: 'object', + required: ['cid', 'recordUri'], + properties: { + cid: { + type: 'string', + format: 'cid', + }, + recordUri: { + type: 'string', + format: 'at-uri', + }, + }, + }, + }, + }, + ComAtprotoRepoListRecords: { + lexicon: 1, + id: 'com.atproto.repo.listRecords', + defs: { + main: { + type: 'query', + description: + 'List a range of records in a repository, matching a specific collection. Does not require auth.', + parameters: { + type: 'params', + required: ['repo', 'collection'], + properties: { + repo: { + type: 'string', + format: 'at-identifier', + description: 'The handle or DID of the repo.', + }, + collection: { + type: 'string', + format: 'nsid', + description: 'The NSID of the record type.', + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 50, + description: 'The number of records to return.', + }, + cursor: { + type: 'string', + }, + rkeyStart: { + type: 'string', + description: + 'DEPRECATED: The lowest sort-ordered rkey to start from (exclusive)', + }, + rkeyEnd: { + type: 'string', + description: + 'DEPRECATED: The highest sort-ordered rkey to stop at (exclusive)', + }, + reverse: { + type: 'boolean', + description: 'Flag to reverse the order of the returned records.', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['records'], + properties: { + cursor: { + type: 'string', + }, + records: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:com.atproto.repo.listRecords#record', + }, + }, + }, + }, + }, + }, + record: { + type: 'object', + required: ['uri', 'cid', 'value'], + properties: { + uri: { + type: 'string', + format: 'at-uri', + }, + cid: { + type: 'string', + format: 'cid', + }, + value: { + type: 'unknown', + }, + }, + }, + }, + }, + ComAtprotoRepoPutRecord: { + lexicon: 1, + id: 'com.atproto.repo.putRecord', + defs: { + main: { + type: 'procedure', + description: + 'Write a repository record, creating or updating it as needed. Requires auth, implemented by PDS.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['repo', 'collection', 'rkey', 'record'], + nullable: ['swapRecord'], + properties: { + repo: { + type: 'string', + format: 'at-identifier', + description: + 'The handle or DID of the repo (aka, current account).', + }, + collection: { + type: 'string', + format: 'nsid', + description: 'The NSID of the record collection.', + }, + rkey: { + type: 'string', + format: 'record-key', + description: 'The Record Key.', + maxLength: 512, + }, + validate: { + type: 'boolean', + 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.", + }, + record: { + type: 'unknown', + description: 'The record to write.', + }, + swapRecord: { + type: 'string', + format: 'cid', + description: + 'Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation', + }, + swapCommit: { + type: 'string', + format: 'cid', + description: + 'Compare and swap with the previous commit by CID.', + }, + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['uri', 'cid'], + properties: { + uri: { + type: 'string', + format: 'at-uri', + }, + cid: { + type: 'string', + format: 'cid', + }, + commit: { + type: 'ref', + ref: 'lex:com.atproto.repo.defs#commitMeta', + }, + validationStatus: { + type: 'string', + knownValues: ['valid', 'unknown'], + }, + }, + }, + }, + errors: [ + { + name: 'InvalidSwap', + }, + ], + }, + }, + }, + ComAtprotoRepoStrongRef: { + lexicon: 1, + id: 'com.atproto.repo.strongRef', + description: 'A URI with a content-hash fingerprint.', + defs: { + main: { + type: 'object', + required: ['uri', 'cid'], + properties: { + uri: { + type: 'string', + format: 'at-uri', + }, + cid: { + type: 'string', + format: 'cid', + }, + }, + }, + }, + }, + ComAtprotoRepoUploadBlob: { + lexicon: 1, + id: 'com.atproto.repo.uploadBlob', + defs: { + main: { + type: 'procedure', + 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.', + input: { + encoding: '*/*', + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['blob'], + properties: { + blob: { + type: 'blob', + }, + }, + }, + }, + }, + }, + }, + AppBskyActorDefs: { + lexicon: 1, + id: 'app.bsky.actor.defs', + defs: { + profileView: { + type: 'object', + required: ['did', 'handle'], + properties: { + did: { + type: 'string', + format: 'did', + }, + handle: { + type: 'string', + format: 'handle', + }, + displayName: { + type: 'string', + maxGraphemes: 64, + maxLength: 640, + }, + description: { + type: 'string', + maxGraphemes: 256, + maxLength: 2560, + }, + avatar: { + type: 'string', + format: 'uri', + }, + indexedAt: { + type: 'string', + format: 'datetime', + }, + createdAt: { + type: 'string', + format: 'datetime', + }, + labels: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:com.atproto.label.defs#label', + }, + }, + }, + }, + }, + }, + AppBskyActorProfile: { + lexicon: 1, + id: 'app.bsky.actor.profile', + defs: { + main: { + type: 'record', + description: 'A declaration of a Bluesky account profile.', + key: 'literal:self', + record: { + type: 'object', + properties: { + displayName: { + type: 'string', + maxGraphemes: 64, + maxLength: 640, + }, + description: { + type: 'string', + description: 'Free-form profile description text.', + maxGraphemes: 256, + maxLength: 2560, + }, + avatar: { + type: 'blob', + description: + "Small image to be displayed next to posts from account. AKA, 'profile picture'", + accept: ['image/png', 'image/jpeg'], + maxSize: 1000000, + }, + banner: { + type: 'blob', + description: + 'Larger horizontal image to display behind profile view.', + accept: ['image/png', 'image/jpeg'], + maxSize: 1000000, + }, + labels: { + type: 'union', + description: + 'Self-label values, specific to the Bluesky application, on the overall account.', + refs: ['lex:com.atproto.label.defs#selfLabels'], + }, + joinedViaStarterPack: { + type: 'ref', + ref: 'lex:com.atproto.repo.strongRef', + }, + pinnedPost: { + type: 'ref', + ref: 'lex:com.atproto.repo.strongRef', + }, + createdAt: { + type: 'string', + format: 'datetime', + }, + }, + }, + }, + }, + }, +} as const satisfies Record + +export const schemas = Object.values(schemaDict) satisfies LexiconDoc[] +export const lexicons: Lexicons = new Lexicons(schemas) + +export function validate( + v: unknown, + id: string, + hash: string, + requiredType: true, +): ValidationResult +export function validate( + v: unknown, + id: string, + hash: string, + requiredType?: false, +): ValidationResult +export function validate( + v: unknown, + id: string, + hash: string, + requiredType?: boolean, +): ValidationResult { + return (requiredType ? is$typed : maybe$typed)(v, id, hash) + ? lexicons.validate(`${id}#${hash}`, v) + : { + success: false, + error: new ValidationError( + `Must be an object with "${hash === 'main' ? id : `${id}#${hash}`}" $type property`, + ), + } +} + +export const ids = { + XyzStatusphereDefs: 'xyz.statusphere.defs', + XyzStatusphereStatus: 'xyz.statusphere.status', + ComAtprotoLabelDefs: 'com.atproto.label.defs', + ComAtprotoRepoApplyWrites: 'com.atproto.repo.applyWrites', + ComAtprotoRepoCreateRecord: 'com.atproto.repo.createRecord', + ComAtprotoRepoDefs: 'com.atproto.repo.defs', + ComAtprotoRepoDeleteRecord: 'com.atproto.repo.deleteRecord', + ComAtprotoRepoDescribeRepo: 'com.atproto.repo.describeRepo', + ComAtprotoRepoGetRecord: 'com.atproto.repo.getRecord', + ComAtprotoRepoImportRepo: 'com.atproto.repo.importRepo', + ComAtprotoRepoListMissingBlobs: 'com.atproto.repo.listMissingBlobs', + ComAtprotoRepoListRecords: 'com.atproto.repo.listRecords', + ComAtprotoRepoPutRecord: 'com.atproto.repo.putRecord', + ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef', + ComAtprotoRepoUploadBlob: 'com.atproto.repo.uploadBlob', + AppBskyActorDefs: 'app.bsky.actor.defs', + AppBskyActorProfile: 'app.bsky.actor.profile', +} as const diff --git a/packages/lexicon/src/types/app/bsky/actor/defs.ts b/packages/lexicon/src/types/app/bsky/actor/defs.ts new file mode 100644 index 0000000..5f65150 --- /dev/null +++ b/packages/lexicon/src/types/app/bsky/actor/defs.ts @@ -0,0 +1,35 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { BlobRef, ValidationResult } from '@atproto/lexicon' +import { CID } from 'multiformats/cid' + +import { validate as _validate } from '../../../../lexicons' +import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' +import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs.js' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.actor.defs' + +export interface ProfileView { + $type?: 'app.bsky.actor.defs#profileView' + did: string + handle: string + displayName?: string + description?: string + avatar?: string + indexedAt?: string + createdAt?: string + labels?: ComAtprotoLabelDefs.Label[] +} + +const hashProfileView = 'profileView' + +export function isProfileView(v: V) { + return is$typed(v, id, hashProfileView) +} + +export function validateProfileView(v: V) { + return validate(v, id, hashProfileView) +} diff --git a/src/lexicon/types/app/bsky/actor/profile.ts b/packages/lexicon/src/types/app/bsky/actor/profile.ts similarity index 86% rename from src/lexicon/types/app/bsky/actor/profile.ts rename to packages/lexicon/src/types/app/bsky/actor/profile.ts index d4a592e..579bcae 100644 --- a/src/lexicon/types/app/bsky/actor/profile.ts +++ b/packages/lexicon/src/types/app/bsky/actor/profile.ts @@ -1,10 +1,11 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { BlobRef, ValidationResult } from '@atproto/lexicon' import { CID } from 'multiformats/cid' + import { validate as _validate } from '../../../../lexicons' -import { $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs.js' import type * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef.js' @@ -23,6 +24,7 @@ export interface Record { banner?: BlobRef labels?: $Typed | { $type: string } joinedViaStarterPack?: ComAtprotoRepoStrongRef.Main + pinnedPost?: ComAtprotoRepoStrongRef.Main createdAt?: string [k: string]: unknown } diff --git a/src/lexicon/types/com/atproto/label/defs.ts b/packages/lexicon/src/types/com/atproto/label/defs.ts similarity index 97% rename from src/lexicon/types/com/atproto/label/defs.ts rename to packages/lexicon/src/types/com/atproto/label/defs.ts index b5f75bf..af45f4a 100644 --- a/src/lexicon/types/com/atproto/label/defs.ts +++ b/packages/lexicon/src/types/com/atproto/label/defs.ts @@ -1,10 +1,11 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { BlobRef, ValidationResult } from '@atproto/lexicon' import { CID } from 'multiformats/cid' + import { validate as _validate } from '../../../../lexicons' -import { $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' const is$typed = _is$typed, validate = _validate diff --git a/packages/lexicon/src/types/com/atproto/repo/applyWrites.ts b/packages/lexicon/src/types/com/atproto/repo/applyWrites.ts new file mode 100644 index 0000000..a15aaf3 --- /dev/null +++ b/packages/lexicon/src/types/com/atproto/repo/applyWrites.ts @@ -0,0 +1,164 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { BlobRef, ValidationResult } from '@atproto/lexicon' +import { HeadersMap, XRPCError } from '@atproto/xrpc' +import { CID } from 'multiformats/cid' + +import { validate as _validate } from '../../../../lexicons' +import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' +import type * as ComAtprotoRepoDefs from './defs.js' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.repo.applyWrites' + +export interface QueryParams {} + +export interface InputSchema { + /** The handle or DID of the repo (aka, current account). */ + repo: string + /** 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. */ + validate?: boolean + writes: ($Typed | $Typed | $Typed)[] + /** If provided, the entire operation will fail if the current repo commit CID does not match this value. Used to prevent conflicting repo mutations. */ + swapCommit?: string +} + +export interface OutputSchema { + commit?: ComAtprotoRepoDefs.CommitMeta + results?: ( + | $Typed + | $Typed + | $Typed + )[] +} + +export interface CallOptions { + signal?: AbortSignal + headers?: HeadersMap + qp?: QueryParams + encoding?: 'application/json' +} + +export interface Response { + success: boolean + headers: HeadersMap + data: OutputSchema +} + +export class InvalidSwapError extends XRPCError { + constructor(src: XRPCError) { + super(src.status, src.error, src.message, src.headers, { cause: src }) + } +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + if (e.error === 'InvalidSwap') return new InvalidSwapError(e) + } + + return e +} + +/** Operation which creates a new record. */ +export interface Create { + $type?: 'com.atproto.repo.applyWrites#create' + collection: string + /** NOTE: maxLength is redundant with record-key format. Keeping it temporarily to ensure backwards compatibility. */ + rkey?: string + value: { [_ in string]: unknown } +} + +const hashCreate = 'create' + +export function isCreate(v: V) { + return is$typed(v, id, hashCreate) +} + +export function validateCreate(v: V) { + return validate(v, id, hashCreate) +} + +/** Operation which updates an existing record. */ +export interface Update { + $type?: 'com.atproto.repo.applyWrites#update' + collection: string + rkey: string + value: { [_ in string]: unknown } +} + +const hashUpdate = 'update' + +export function isUpdate(v: V) { + return is$typed(v, id, hashUpdate) +} + +export function validateUpdate(v: V) { + return validate(v, id, hashUpdate) +} + +/** Operation which deletes an existing record. */ +export interface Delete { + $type?: 'com.atproto.repo.applyWrites#delete' + collection: string + rkey: string +} + +const hashDelete = 'delete' + +export function isDelete(v: V) { + return is$typed(v, id, hashDelete) +} + +export function validateDelete(v: V) { + return validate(v, id, hashDelete) +} + +export interface CreateResult { + $type?: 'com.atproto.repo.applyWrites#createResult' + uri: string + cid: string + validationStatus?: 'valid' | 'unknown' | (string & {}) +} + +const hashCreateResult = 'createResult' + +export function isCreateResult(v: V) { + return is$typed(v, id, hashCreateResult) +} + +export function validateCreateResult(v: V) { + return validate(v, id, hashCreateResult) +} + +export interface UpdateResult { + $type?: 'com.atproto.repo.applyWrites#updateResult' + uri: string + cid: string + validationStatus?: 'valid' | 'unknown' | (string & {}) +} + +const hashUpdateResult = 'updateResult' + +export function isUpdateResult(v: V) { + return is$typed(v, id, hashUpdateResult) +} + +export function validateUpdateResult(v: V) { + return validate(v, id, hashUpdateResult) +} + +export interface DeleteResult { + $type?: 'com.atproto.repo.applyWrites#deleteResult' +} + +const hashDeleteResult = 'deleteResult' + +export function isDeleteResult(v: V) { + return is$typed(v, id, hashDeleteResult) +} + +export function validateDeleteResult(v: V) { + return validate(v, id, hashDeleteResult) +} diff --git a/packages/lexicon/src/types/com/atproto/repo/createRecord.ts b/packages/lexicon/src/types/com/atproto/repo/createRecord.ts new file mode 100644 index 0000000..7b3ca5c --- /dev/null +++ b/packages/lexicon/src/types/com/atproto/repo/createRecord.ts @@ -0,0 +1,65 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { BlobRef, ValidationResult } from '@atproto/lexicon' +import { HeadersMap, XRPCError } from '@atproto/xrpc' +import { CID } from 'multiformats/cid' + +import { validate as _validate } from '../../../../lexicons' +import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' +import type * as ComAtprotoRepoDefs from './defs.js' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.repo.createRecord' + +export interface QueryParams {} + +export interface InputSchema { + /** The handle or DID of the repo (aka, current account). */ + repo: string + /** The NSID of the record collection. */ + collection: string + /** The Record Key. */ + rkey?: string + /** 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. */ + validate?: boolean + /** The record itself. Must contain a $type field. */ + record: { [_ in string]: unknown } + /** Compare and swap with the previous commit by CID. */ + swapCommit?: string +} + +export interface OutputSchema { + uri: string + cid: string + commit?: ComAtprotoRepoDefs.CommitMeta + validationStatus?: 'valid' | 'unknown' | (string & {}) +} + +export interface CallOptions { + signal?: AbortSignal + headers?: HeadersMap + qp?: QueryParams + encoding?: 'application/json' +} + +export interface Response { + success: boolean + headers: HeadersMap + data: OutputSchema +} + +export class InvalidSwapError extends XRPCError { + constructor(src: XRPCError) { + super(src.status, src.error, src.message, src.headers, { cause: src }) + } +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + if (e.error === 'InvalidSwap') return new InvalidSwapError(e) + } + + return e +} diff --git a/packages/lexicon/src/types/com/atproto/repo/defs.ts b/packages/lexicon/src/types/com/atproto/repo/defs.ts new file mode 100644 index 0000000..c65a050 --- /dev/null +++ b/packages/lexicon/src/types/com/atproto/repo/defs.ts @@ -0,0 +1,28 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { BlobRef, ValidationResult } from '@atproto/lexicon' +import { CID } from 'multiformats/cid' + +import { validate as _validate } from '../../../../lexicons' +import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.repo.defs' + +export interface CommitMeta { + $type?: 'com.atproto.repo.defs#commitMeta' + cid: string + rev: string +} + +const hashCommitMeta = 'commitMeta' + +export function isCommitMeta(v: V) { + return is$typed(v, id, hashCommitMeta) +} + +export function validateCommitMeta(v: V) { + return validate(v, id, hashCommitMeta) +} diff --git a/packages/lexicon/src/types/com/atproto/repo/deleteRecord.ts b/packages/lexicon/src/types/com/atproto/repo/deleteRecord.ts new file mode 100644 index 0000000..d3194aa --- /dev/null +++ b/packages/lexicon/src/types/com/atproto/repo/deleteRecord.ts @@ -0,0 +1,60 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { BlobRef, ValidationResult } from '@atproto/lexicon' +import { HeadersMap, XRPCError } from '@atproto/xrpc' +import { CID } from 'multiformats/cid' + +import { validate as _validate } from '../../../../lexicons' +import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' +import type * as ComAtprotoRepoDefs from './defs.js' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.repo.deleteRecord' + +export interface QueryParams {} + +export interface InputSchema { + /** The handle or DID of the repo (aka, current account). */ + repo: string + /** The NSID of the record collection. */ + collection: string + /** The Record Key. */ + rkey: string + /** Compare and swap with the previous record by CID. */ + swapRecord?: string + /** Compare and swap with the previous commit by CID. */ + swapCommit?: string +} + +export interface OutputSchema { + commit?: ComAtprotoRepoDefs.CommitMeta +} + +export interface CallOptions { + signal?: AbortSignal + headers?: HeadersMap + qp?: QueryParams + encoding?: 'application/json' +} + +export interface Response { + success: boolean + headers: HeadersMap + data: OutputSchema +} + +export class InvalidSwapError extends XRPCError { + constructor(src: XRPCError) { + super(src.status, src.error, src.message, src.headers, { cause: src }) + } +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + if (e.error === 'InvalidSwap') return new InvalidSwapError(e) + } + + return e +} diff --git a/packages/lexicon/src/types/com/atproto/repo/describeRepo.ts b/packages/lexicon/src/types/com/atproto/repo/describeRepo.ts new file mode 100644 index 0000000..121ee35 --- /dev/null +++ b/packages/lexicon/src/types/com/atproto/repo/describeRepo.ts @@ -0,0 +1,46 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { BlobRef, ValidationResult } from '@atproto/lexicon' +import { HeadersMap, XRPCError } from '@atproto/xrpc' +import { CID } from 'multiformats/cid' + +import { validate as _validate } from '../../../../lexicons' +import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.repo.describeRepo' + +export interface QueryParams { + /** The handle or DID of the repo. */ + repo: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + handle: string + did: string + /** The complete DID document for this account. */ + didDoc: { [_ in string]: unknown } + /** List of all the collections (NSIDs) for which this repo contains at least one record. */ + collections: string[] + /** Indicates if handle is currently valid (resolves bi-directionally) */ + handleIsCorrect: boolean +} + +export interface CallOptions { + signal?: AbortSignal + headers?: HeadersMap +} + +export interface Response { + success: boolean + headers: HeadersMap + data: OutputSchema +} + +export function toKnownErr(e: any) { + return e +} diff --git a/packages/lexicon/src/types/com/atproto/repo/getRecord.ts b/packages/lexicon/src/types/com/atproto/repo/getRecord.ts new file mode 100644 index 0000000..177d3b5 --- /dev/null +++ b/packages/lexicon/src/types/com/atproto/repo/getRecord.ts @@ -0,0 +1,57 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { BlobRef, ValidationResult } from '@atproto/lexicon' +import { HeadersMap, XRPCError } from '@atproto/xrpc' +import { CID } from 'multiformats/cid' + +import { validate as _validate } from '../../../../lexicons' +import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.repo.getRecord' + +export interface QueryParams { + /** The handle or DID of the repo. */ + repo: string + /** The NSID of the record collection. */ + collection: string + /** The Record Key. */ + rkey: string + /** The CID of the version of the record. If not specified, then return the most recent version. */ + cid?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + uri: string + cid?: string + value: { [_ in string]: unknown } +} + +export interface CallOptions { + signal?: AbortSignal + headers?: HeadersMap +} + +export interface Response { + success: boolean + headers: HeadersMap + data: OutputSchema +} + +export class RecordNotFoundError extends XRPCError { + constructor(src: XRPCError) { + super(src.status, src.error, src.message, src.headers, { cause: src }) + } +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + if (e.error === 'RecordNotFound') return new RecordNotFoundError(e) + } + + return e +} diff --git a/packages/lexicon/src/types/com/atproto/repo/importRepo.ts b/packages/lexicon/src/types/com/atproto/repo/importRepo.ts new file mode 100644 index 0000000..796118f --- /dev/null +++ b/packages/lexicon/src/types/com/atproto/repo/importRepo.ts @@ -0,0 +1,33 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { BlobRef, ValidationResult } from '@atproto/lexicon' +import { HeadersMap, XRPCError } from '@atproto/xrpc' +import { CID } from 'multiformats/cid' + +import { validate as _validate } from '../../../../lexicons' +import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.repo.importRepo' + +export interface QueryParams {} + +export type InputSchema = string | Uint8Array | Blob + +export interface CallOptions { + signal?: AbortSignal + headers?: HeadersMap + qp?: QueryParams + encoding?: 'application/vnd.ipld.car' +} + +export interface Response { + success: boolean + headers: HeadersMap +} + +export function toKnownErr(e: any) { + return e +} diff --git a/packages/lexicon/src/types/com/atproto/repo/listMissingBlobs.ts b/packages/lexicon/src/types/com/atproto/repo/listMissingBlobs.ts new file mode 100644 index 0000000..0ac701c --- /dev/null +++ b/packages/lexicon/src/types/com/atproto/repo/listMissingBlobs.ts @@ -0,0 +1,56 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { BlobRef, ValidationResult } from '@atproto/lexicon' +import { HeadersMap, XRPCError } from '@atproto/xrpc' +import { CID } from 'multiformats/cid' + +import { validate as _validate } from '../../../../lexicons' +import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.repo.listMissingBlobs' + +export interface QueryParams { + limit?: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + blobs: RecordBlob[] +} + +export interface CallOptions { + signal?: AbortSignal + headers?: HeadersMap +} + +export interface Response { + success: boolean + headers: HeadersMap + data: OutputSchema +} + +export function toKnownErr(e: any) { + return e +} + +export interface RecordBlob { + $type?: 'com.atproto.repo.listMissingBlobs#recordBlob' + cid: string + recordUri: string +} + +const hashRecordBlob = 'recordBlob' + +export function isRecordBlob(v: V) { + return is$typed(v, id, hashRecordBlob) +} + +export function validateRecordBlob(v: V) { + return validate(v, id, hashRecordBlob) +} diff --git a/packages/lexicon/src/types/com/atproto/repo/listRecords.ts b/packages/lexicon/src/types/com/atproto/repo/listRecords.ts new file mode 100644 index 0000000..86be3d9 --- /dev/null +++ b/packages/lexicon/src/types/com/atproto/repo/listRecords.ts @@ -0,0 +1,68 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { BlobRef, ValidationResult } from '@atproto/lexicon' +import { HeadersMap, XRPCError } from '@atproto/xrpc' +import { CID } from 'multiformats/cid' + +import { validate as _validate } from '../../../../lexicons' +import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.repo.listRecords' + +export interface QueryParams { + /** The handle or DID of the repo. */ + repo: string + /** The NSID of the record type. */ + collection: string + /** The number of records to return. */ + limit?: number + cursor?: string + /** DEPRECATED: The lowest sort-ordered rkey to start from (exclusive) */ + rkeyStart?: string + /** DEPRECATED: The highest sort-ordered rkey to stop at (exclusive) */ + rkeyEnd?: string + /** Flag to reverse the order of the returned records. */ + reverse?: boolean +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + records: Record[] +} + +export interface CallOptions { + signal?: AbortSignal + headers?: HeadersMap +} + +export interface Response { + success: boolean + headers: HeadersMap + data: OutputSchema +} + +export function toKnownErr(e: any) { + return e +} + +export interface Record { + $type?: 'com.atproto.repo.listRecords#record' + uri: string + cid: string + value: { [_ in string]: unknown } +} + +const hashRecord = 'record' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) +} + +export function validateRecord(v: V) { + return validate(v, id, hashRecord) +} diff --git a/packages/lexicon/src/types/com/atproto/repo/putRecord.ts b/packages/lexicon/src/types/com/atproto/repo/putRecord.ts new file mode 100644 index 0000000..2c71f59 --- /dev/null +++ b/packages/lexicon/src/types/com/atproto/repo/putRecord.ts @@ -0,0 +1,67 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { BlobRef, ValidationResult } from '@atproto/lexicon' +import { HeadersMap, XRPCError } from '@atproto/xrpc' +import { CID } from 'multiformats/cid' + +import { validate as _validate } from '../../../../lexicons' +import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' +import type * as ComAtprotoRepoDefs from './defs.js' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.repo.putRecord' + +export interface QueryParams {} + +export interface InputSchema { + /** The handle or DID of the repo (aka, current account). */ + repo: string + /** The NSID of the record collection. */ + collection: string + /** The Record Key. */ + rkey: string + /** 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. */ + validate?: boolean + /** The record to write. */ + record: { [_ in string]: unknown } + /** Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation */ + swapRecord?: string | null + /** Compare and swap with the previous commit by CID. */ + swapCommit?: string +} + +export interface OutputSchema { + uri: string + cid: string + commit?: ComAtprotoRepoDefs.CommitMeta + validationStatus?: 'valid' | 'unknown' | (string & {}) +} + +export interface CallOptions { + signal?: AbortSignal + headers?: HeadersMap + qp?: QueryParams + encoding?: 'application/json' +} + +export interface Response { + success: boolean + headers: HeadersMap + data: OutputSchema +} + +export class InvalidSwapError extends XRPCError { + constructor(src: XRPCError) { + super(src.status, src.error, src.message, src.headers, { cause: src }) + } +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + if (e.error === 'InvalidSwap') return new InvalidSwapError(e) + } + + return e +} diff --git a/src/lexicon/types/com/atproto/repo/strongRef.ts b/packages/lexicon/src/types/com/atproto/repo/strongRef.ts similarity index 80% rename from src/lexicon/types/com/atproto/repo/strongRef.ts rename to packages/lexicon/src/types/com/atproto/repo/strongRef.ts index e2652df..d62c752 100644 --- a/src/lexicon/types/com/atproto/repo/strongRef.ts +++ b/packages/lexicon/src/types/com/atproto/repo/strongRef.ts @@ -1,10 +1,11 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { BlobRef, ValidationResult } from '@atproto/lexicon' import { CID } from 'multiformats/cid' + import { validate as _validate } from '../../../../lexicons' -import { $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' const is$typed = _is$typed, validate = _validate diff --git a/packages/lexicon/src/types/com/atproto/repo/uploadBlob.ts b/packages/lexicon/src/types/com/atproto/repo/uploadBlob.ts new file mode 100644 index 0000000..3fd5297 --- /dev/null +++ b/packages/lexicon/src/types/com/atproto/repo/uploadBlob.ts @@ -0,0 +1,38 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { BlobRef, ValidationResult } from '@atproto/lexicon' +import { HeadersMap, XRPCError } from '@atproto/xrpc' +import { CID } from 'multiformats/cid' + +import { validate as _validate } from '../../../../lexicons' +import { is$typed as _is$typed, $Typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.repo.uploadBlob' + +export interface QueryParams {} + +export type InputSchema = string | Uint8Array | Blob + +export interface OutputSchema { + blob: BlobRef +} + +export interface CallOptions { + signal?: AbortSignal + headers?: HeadersMap + qp?: QueryParams + encoding?: string +} + +export interface Response { + success: boolean + headers: HeadersMap + data: OutputSchema +} + +export function toKnownErr(e: any) { + return e +} diff --git a/packages/lexicon/src/types/xyz/statusphere/defs.ts b/packages/lexicon/src/types/xyz/statusphere/defs.ts new file mode 100644 index 0000000..fa257b7 --- /dev/null +++ b/packages/lexicon/src/types/xyz/statusphere/defs.ts @@ -0,0 +1,46 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { BlobRef, ValidationResult } from '@atproto/lexicon' +import { CID } from 'multiformats/cid' + +import { validate as _validate } from '../../../lexicons' +import { is$typed as _is$typed, $Typed, OmitKey } from '../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'xyz.statusphere.defs' + +export interface StatusView { + $type?: 'xyz.statusphere.defs#statusView' + uri: string + status: string + createdAt: string + profile: ProfileView +} + +const hashStatusView = 'statusView' + +export function isStatusView(v: V) { + return is$typed(v, id, hashStatusView) +} + +export function validateStatusView(v: V) { + return validate(v, id, hashStatusView) +} + +export interface ProfileView { + $type?: 'xyz.statusphere.defs#profileView' + did: string + handle: string +} + +const hashProfileView = 'profileView' + +export function isProfileView(v: V) { + return is$typed(v, id, hashProfileView) +} + +export function validateProfileView(v: V) { + return validate(v, id, hashProfileView) +} diff --git a/src/lexicon/types/xyz/statusphere/status.ts b/packages/lexicon/src/types/xyz/statusphere/status.ts similarity index 81% rename from src/lexicon/types/xyz/statusphere/status.ts rename to packages/lexicon/src/types/xyz/statusphere/status.ts index adc26fe..d4ca24e 100644 --- a/src/lexicon/types/xyz/statusphere/status.ts +++ b/packages/lexicon/src/types/xyz/statusphere/status.ts @@ -1,10 +1,11 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { BlobRef, ValidationResult } from '@atproto/lexicon' import { CID } from 'multiformats/cid' + import { validate as _validate } from '../../../lexicons' -import { $Typed, is$typed as _is$typed, OmitKey } from '../../../util' +import { is$typed as _is$typed, $Typed, OmitKey } from '../../../util' const is$typed = _is$typed, validate = _validate diff --git a/src/lexicon/util.ts b/packages/lexicon/src/util.ts similarity index 100% rename from src/lexicon/util.ts rename to packages/lexicon/src/util.ts diff --git a/packages/lexicon/tsconfig.json b/packages/lexicon/tsconfig.json new file mode 100644 index 0000000..94a90a1 --- /dev/null +++ b/packages/lexicon/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "dist" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7b70d37..86a44de 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,92 +7,221 @@ settings: importers: .: + devDependencies: + '@atproto/lex-cli': + specifier: ^0.6.1 + version: 0.6.1 + '@ianvs/prettier-plugin-sort-imports': + specifier: ^4.4.1 + version: 4.4.1(prettier@3.5.2) + concurrently: + specifier: ^9.1.2 + version: 9.1.2 + prettier: + specifier: ^3.5.2 + version: 3.5.2 + prettier-plugin-tailwindcss: + specifier: ^0.6.11 + version: 0.6.11(@ianvs/prettier-plugin-sort-imports@4.4.1(prettier@3.5.2))(prettier@3.5.2) + rimraf: + specifier: ^6.0.1 + version: 6.0.1 + typescript: + specifier: ^5.8.2 + version: 5.8.2 + + packages/appview: dependencies: '@atproto/api': specifier: ^0.14.7 version: 0.14.7 '@atproto/common': - specifier: ^0.4.1 + specifier: ^0.4.8 version: 0.4.8 '@atproto/identity': - specifier: ^0.4.0 + specifier: ^0.4.6 version: 0.4.6 '@atproto/lexicon': - specifier: ^0.4.2 + specifier: ^0.4.7 version: 0.4.7 '@atproto/oauth-client-node': - specifier: ^0.2.2 + specifier: ^0.2.11 version: 0.2.11 '@atproto/sync': - specifier: ^0.1.4 + specifier: ^0.1.15 version: 0.1.15 '@atproto/syntax': - specifier: ^0.3.0 + specifier: ^0.3.3 version: 0.3.3 '@atproto/xrpc-server': - specifier: ^0.7.9 + specifier: ^0.7.11 version: 0.7.11 + '@statusphere/lexicon': + specifier: workspace:* + version: link:../lexicon better-sqlite3: - specifier: ^11.1.2 + specifier: ^11.8.1 version: 11.8.1 + cors: + specifier: ^2.8.5 + version: 2.8.5 dotenv: - specifier: ^16.4.5 + specifier: ^16.4.7 version: 16.4.7 envalid: specifier: ^8.0.0 version: 8.0.0 express: - specifier: ^4.19.2 + specifier: ^4.21.2 version: 4.21.2 iron-session: - specifier: ^8.0.2 + specifier: ^8.0.4 version: 8.0.4 kysely: - specifier: ^0.27.4 + specifier: ^0.27.5 version: 0.27.5 multiformats: specifier: ^13.3.2 version: 13.3.2 pino: - specifier: ^9.3.2 + specifier: ^9.6.0 version: 9.6.0 - uhtml: - specifier: ^4.5.9 - version: 4.7.0 devDependencies: - '@atproto/lex-cli': - specifier: ^0.6.1 - version: 0.6.1 '@types/better-sqlite3': - specifier: ^7.6.11 + specifier: ^7.6.12 version: 7.6.12 + '@types/cors': + specifier: ^2.8.17 + version: 2.8.17 '@types/express': specifier: ^5.0.0 version: 5.0.0 + '@types/node': + specifier: ^22.13.8 + version: 22.13.8 pino-pretty: specifier: ^13.0.0 version: 13.0.0 - prettier: - specifier: ^3.5.2 - version: 3.5.2 - rimraf: - specifier: ^6.0.1 - version: 6.0.1 ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@22.13.8)(typescript@5.8.2) tsup: - specifier: ^8.0.2 - version: 8.4.0(tsx@4.19.3)(typescript@5.8.2) + specifier: ^8.4.0 + version: 8.4.0(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.3)(typescript@5.8.2) tsx: - specifier: ^4.7.2 + specifier: ^4.19.3 version: 4.19.3 typescript: - specifier: ^5.4.4 + specifier: ^5.8.2 + version: 5.8.2 + + packages/client: + dependencies: + '@atproto/api': + specifier: ^0.14.7 + version: 0.14.7 + '@statusphere/lexicon': + specifier: workspace:* + version: link:../lexicon + '@tailwindcss/vite': + specifier: ^4.0.9 + 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)) + '@tanstack/react-query': + specifier: ^5.66.11 + version: 5.66.11(react@19.0.0) + iron-session: + specifier: ^8.0.4 + version: 8.0.4 + react: + specifier: ^19.0.0 + version: 19.0.0 + react-dom: + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) + react-router-dom: + specifier: ^7.2.0 + version: 7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + devDependencies: + '@types/react': + specifier: ^19.0.10 + version: 19.0.10 + '@types/react-dom': + specifier: ^19.0.4 + version: 19.0.4(@types/react@19.0.10) + '@typescript-eslint/eslint-plugin': + specifier: ^8.25.0 + 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) + '@typescript-eslint/parser': + specifier: ^8.25.0 + version: 8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2) + '@vitejs/plugin-react': + specifier: ^4.3.4 + 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)) + autoprefixer: + specifier: ^10.4.20 + version: 10.4.20(postcss@8.5.3) + eslint: + specifier: ^9.21.0 + version: 9.21.0(jiti@2.4.2) + eslint-plugin-react-hooks: + specifier: ^5.2.0 + version: 5.2.0(eslint@9.21.0(jiti@2.4.2)) + eslint-plugin-react-refresh: + specifier: ^0.4.19 + version: 0.4.19(eslint@9.21.0(jiti@2.4.2)) + postcss: + specifier: ^8.5.3 + version: 8.5.3 + tailwindcss: + specifier: ^4.0.9 + version: 4.0.9 + typescript: + specifier: ^5.8.2 + version: 5.8.2 + vite: + specifier: ^6.2.0 + version: 6.2.0(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.3) + + packages/lexicon: + dependencies: + '@atproto/api': + specifier: ^0.14.7 + version: 0.14.7 + '@atproto/lexicon': + specifier: ^0.4.7 + version: 0.4.7 + '@atproto/syntax': + specifier: ^0.3.3 + version: 0.3.3 + '@atproto/xrpc': + specifier: ^0.6.9 + version: 0.6.9 + multiformats: + specifier: ^13.3.2 + version: 13.3.2 + devDependencies: + '@atproto/lex-cli': + specifier: ^0.6.1 + version: 0.6.1 + '@types/node': + specifier: ^22.13.8 + version: 22.13.8 + rimraf: + specifier: ^6.0.1 + version: 6.0.1 + tsup: + specifier: ^8.4.0 + version: 8.4.0(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.3)(typescript@5.8.2) + typescript: + specifier: ^5.8.2 version: 5.8.2 packages: + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + '@atproto-labs/did-resolver@0.1.10': resolution: {integrity: sha512-o/bl5acf3AIPKZuO6Fd5EmO4INGpi/3Pm08ZpHNCy7s4VZXFmAjZaHeCD7hQ8yEL0EtXnLNIECtKrTBTTx8b+A==} @@ -188,6 +317,85 @@ packages: '@atproto/xrpc@0.6.9': resolution: {integrity: sha512-vQGA7++DYMNaHx3C7vEjT+2X6hYYLG7JNbBnDLWu0km1/1KYXgRkAz4h+FfYqg1mvzvIorHU7DAs5wevkJDDlw==} + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.26.8': + resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.26.9': + resolution: {integrity: sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.26.9': + resolution: {integrity: sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.26.5': + resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.26.5': + resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.26.9': + resolution: {integrity: sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.26.9': + resolution: {integrity: sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.25.9': + resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.25.9': + resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.26.9': + resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.26.9': + resolution: {integrity: sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.26.9': + resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==} + engines: {node: '>=6.9.0'} + '@cbor-extract/cbor-extract-darwin-arm64@2.2.0': resolution: {integrity: sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==} cpu: [arm64] @@ -372,6 +580,69 @@ packages: cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.4.1': + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.19.2': + resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.12.0': + resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.0': + resolution: {integrity: sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.21.0': + resolution: {integrity: sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.7': + resolution: {integrity: sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.2': + resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} + engines: {node: '>=18.18'} + + '@ianvs/prettier-plugin-sort-imports@4.4.1': + resolution: {integrity: sha512-F0/Hrcfpy8WuxlQyAWJTEren/uxKhYonOGY4OyWmwRdeTvkh9mMSCxowZLjNkhwi/2ipqCgtXwwOk7tW0mWXkA==} + peerDependencies: + '@vue/compiler-sfc': 2.7.x || 3.x + prettier: 2 || 3 + peerDependenciesMeta: + '@vue/compiler-sfc': + optional: true + '@ipld/car@3.2.4': resolution: {integrity: sha512-rezKd+jk8AsTGOoJKqzfjLJ3WVft7NZNH95f0pfPbicROvzTyvHCNy567HzSUd6gRXZ9im29z5ZEv9Hw49jSYw==} @@ -427,9 +698,6 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@preact/signals-core@1.8.0': - resolution: {integrity: sha512-OBvUsRZqNmjzCZXWLxkZfhcgT+Fk8DDcT/8vD6a1xhDemodyy87UJRJfASMuSD8FaAIeGgGm85ydXhm7lr4fyA==} - '@rollup/rollup-android-arm-eabi@4.34.9': resolution: {integrity: sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==} cpu: [arm] @@ -525,6 +793,92 @@ packages: cpu: [x64] os: [win32] + '@tailwindcss/node@4.0.9': + resolution: {integrity: sha512-tOJvdI7XfJbARYhxX+0RArAhmuDcczTC46DGCEziqxzzbIaPnfYaIyRT31n4u8lROrsO7Q6u/K9bmQHL2uL1bQ==} + + '@tailwindcss/oxide-android-arm64@4.0.9': + resolution: {integrity: sha512-YBgy6+2flE/8dbtrdotVInhMVIxnHJPbAwa7U1gX4l2ThUIaPUp18LjB9wEH8wAGMBZUb//SzLtdXXNBHPUl6Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.0.9': + resolution: {integrity: sha512-pWdl4J2dIHXALgy2jVkwKBmtEb73kqIfMpYmcgESr7oPQ+lbcQ4+tlPeVXaSAmang+vglAfFpXQCOvs/aGSqlw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.0.9': + resolution: {integrity: sha512-4Dq3lKp0/C7vrRSkNPtBGVebEyWt9QPPlQctxJ0H3MDyiQYvzVYf8jKow7h5QkWNe8hbatEqljMj/Y0M+ERYJg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.0.9': + resolution: {integrity: sha512-k7U1RwRODta8x0uealtVt3RoWAWqA+D5FAOsvVGpYoI6ObgmnzqWW6pnVwz70tL8UZ/QXjeMyiICXyjzB6OGtQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.9': + resolution: {integrity: sha512-NDDjVweHz2zo4j+oS8y3KwKL5wGCZoXGA9ruJM982uVJLdsF8/1AeKvUwKRlMBpxHt1EdWJSAh8a0Mfhl28GlQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.0.9': + resolution: {integrity: sha512-jk90UZ0jzJl3Dy1BhuFfRZ2KP9wVKMXPjmCtY4U6fF2LvrjP5gWFJj5VHzfzHonJexjrGe1lMzgtjriuZkxagg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.0.9': + resolution: {integrity: sha512-3eMjyTC6HBxh9nRgOHzrc96PYh1/jWOwHZ3Kk0JN0Kl25BJ80Lj9HEvvwVDNTgPg154LdICwuFLuhfgH9DULmg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.0.9': + resolution: {integrity: sha512-v0D8WqI/c3WpWH1kq/HP0J899ATLdGZmENa2/emmNjubT0sWtEke9W9+wXeEoACuGAhF9i3PO5MeyditpDCiWQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.0.9': + resolution: {integrity: sha512-Kvp0TCkfeXyeehqLJr7otsc4hd/BUPfcIGrQiwsTVCfaMfjQZCG7DjI+9/QqPZha8YapLA9UoIcUILRYO7NE1Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-win32-arm64-msvc@4.0.9': + resolution: {integrity: sha512-m3+60T/7YvWekajNq/eexjhV8z10rswcz4BC9bioJ7YaN+7K8W2AmLmG0B79H14m6UHE571qB0XsPus4n0QVgQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.0.9': + resolution: {integrity: sha512-dpc05mSlqkwVNOUjGu/ZXd5U1XNch1kHFJ4/cHkZFvaW1RzbHmRt24gvM8/HC6IirMxNarzVw4IXVtvrOoZtxA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.0.9': + resolution: {integrity: sha512-eLizHmXFqHswJONwfqi/WZjtmWZpIalpvMlNhTM99/bkHtUs6IqgI1XQ0/W5eO2HiRQcIlXUogI2ycvKhVLNcA==} + engines: {node: '>= 10'} + + '@tailwindcss/vite@4.0.9': + resolution: {integrity: sha512-BIKJO+hwdIsN7V6I7SziMZIVHWWMsV/uCQKYEbeiGRDRld+TkqyRRl9+dQ0MCXbhcVr+D9T/qX2E84kT7V281g==} + peerDependencies: + vite: ^5.2.0 || ^6 + + '@tanstack/query-core@5.66.11': + resolution: {integrity: sha512-ZEYxgHUcohj3sHkbRaw0gYwFxjY5O6M3IXOYXEun7E1rqNhsP8fOtqjJTKPZpVHcdIdrmX4lzZctT4+pts0OgA==} + + '@tanstack/react-query@5.66.11': + resolution: {integrity: sha512-uPDiQbZScWkAeihmZ9gAm3wOBA1TmLB1KCB1fJ1hIiEKq3dTT+ja/aYM7wGUD+XiEsY4sDSE7p8VIz/21L2Dow==} + peerDependencies: + react: ^18 || ^19 + '@ts-morph/common@0.17.0': resolution: {integrity: sha512-RMSSvSfs9kb0VzkvQ2NWobwnj7TxCA9vI/IjR9bDHqgAyVbu2T0DN4wiKVqomyDWqO7dPr/tErSfq7urQ1Q37g==} @@ -540,6 +894,18 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.6': + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + '@types/better-sqlite3@7.6.12': resolution: {integrity: sha512-fnQmj8lELIj7BSrZQAdBMHEHX8OZLYIHXqAKT1O7tDfLxaINzf00PMjw22r3N/xXh0w/sGHlO6SVaCQ2mj78lg==} @@ -549,6 +915,12 @@ packages: '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + + '@types/cors@2.8.17': + resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} + '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} @@ -561,6 +933,9 @@ packages: '@types/http-errors@2.0.4': resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} @@ -573,17 +948,72 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/react-dom@19.0.4': + resolution: {integrity: sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==} + peerDependencies: + '@types/react': ^19.0.0 + + '@types/react@19.0.10': + resolution: {integrity: sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==} + '@types/send@0.17.4': resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} '@types/serve-static@1.15.7': resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} - '@webreflection/signal@2.1.2': - resolution: {integrity: sha512-0dW0fstQQkIt588JwhDiPS4xgeeQcQnBHn6MVInrBzmFlnLtzoSJL9G7JqdAlZVVi19tfb8R1QisZIT31cgiug==} + '@typescript-eslint/eslint-plugin@8.25.0': + resolution: {integrity: sha512-VM7bpzAe7JO/BFf40pIT1lJqS/z1F8OaSsUB3rpFJucQA4cOSuH2RVVVkFULN+En0Djgr29/jb4EQnedUo95KA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/parser@8.25.0': + resolution: {integrity: sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/scope-manager@8.25.0': + resolution: {integrity: sha512-6PPeiKIGbgStEyt4NNXa2ru5pMzQ8OYKO1hX1z53HMomrmiSB+R5FmChgQAP1ro8jMtNawz+TRQo/cSXrauTpg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.25.0': + resolution: {integrity: sha512-d77dHgHWnxmXOPJuDWO4FDWADmGQkN5+tt6SFRZz/RtCWl4pHgFl3+WdYCn16+3teG09DY6XtEpf3gGD0a186g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/types@8.25.0': + resolution: {integrity: sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.25.0': + resolution: {integrity: sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/utils@8.25.0': + resolution: {integrity: sha512-syqRbrEv0J1wywiLsK60XzHnQe/kRViI3zwFALrNEgnntn1l24Ra2KvOAWwWbWZ1lBZxZljPDGOq967dsl6fkA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' - '@webreflection/uparser@0.4.0': - resolution: {integrity: sha512-kAFWUEw5eool295y01VDr+DOsyog6lURX9l288JCJAD2gxc0tFk34dYaAi6O3BbJyfSoncVEV+nw87bsssdppQ==} + '@typescript-eslint/visitor-keys@8.25.0': + resolution: {integrity: sha512-kCYXKAum9CecGVHGij7muybDfTS2sD3t0L4bJsEZLkyrXUImiCTq1M3LG2SRtOhiHFwMR9wAFplpT6XHYjTkwQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react@4.3.4': + resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} @@ -593,6 +1023,11 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-walk@8.3.4: resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} engines: {node: '>=0.4.0'} @@ -602,6 +1037,9 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -624,6 +1062,9 @@ packages: arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} @@ -631,6 +1072,13 @@ packages: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} + autoprefixer@10.4.20: + resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + await-lock@2.2.2: resolution: {integrity: sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==} @@ -653,6 +1101,9 @@ packages: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} @@ -660,6 +1111,11 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -688,6 +1144,13 @@ packages: resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} engines: {node: '>= 0.4'} + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001701: + resolution: {integrity: sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==} + cbor-extract@2.2.0: resolution: {integrity: sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==} hasBin: true @@ -710,6 +1173,10 @@ packages: chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + code-block-writer@11.0.3: resolution: {integrity: sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==} @@ -731,6 +1198,14 @@ packages: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + concurrently@9.1.2: + resolution: {integrity: sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==} + engines: {node: '>=18'} + hasBin: true + consola@3.4.0: resolution: {integrity: sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==} engines: {node: ^14.18.0 || >=16.10.0} @@ -743,6 +1218,9 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} @@ -754,6 +1232,14 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -761,8 +1247,8 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - custom-function@2.0.0: - resolution: {integrity: sha512-2OPHkZzq3mK1nWpJqWWkGD6Z+0AajNeIxmXl+MRVL8Vysjjf5tf9B5mo713/X2khEwBn/3BKQ7NphpP1vpVKug==} + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} dateformat@4.6.3: resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} @@ -792,6 +1278,9 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -800,6 +1289,11 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + detect-libc@2.0.3: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} @@ -808,22 +1302,6 @@ packages: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} - dom-serializer@2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} - - domconstants@1.1.6: - resolution: {integrity: sha512-CuaDrThJ4VM+LyZ4ax8n52k0KbLJZtffyGkuj1WhpTRRcSfcy/9DfOBa68jenhX96oNUTunblSJEUNC4baFdmQ==} - - domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - - domhandler@5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} - engines: {node: '>= 4'} - - domutils@3.2.2: - resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} - dotenv@16.4.7: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} @@ -838,6 +1316,9 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + electron-to-chromium@1.5.109: + resolution: {integrity: sha512-AidaH9JETVRr9DIPGfp1kAarm/W6hRJTPuCnkF+2MqhF4KaAgRIcBc8nvjk+YMXZhwfISof/7WG29eS4iGxQLQ==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -855,9 +1336,9 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} + enhanced-resolve@5.18.1: + resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} + engines: {node: '>=10.13.0'} envalid@8.0.0: resolution: {integrity: sha512-PGeYJnJB5naN0ME6SH8nFcDj9HVbLpYIfg1p5lAyM9T4cH2lwtu2fLbozC/bq+HUUOIFxhX/LP0/GmlqPHT4tQ==} @@ -880,9 +1361,70 @@ packages: engines: {node: '>=18'} hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@5.2.0: + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.4.19: + resolution: {integrity: sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ==} + peerDependencies: + eslint: '>=8.40' + + eslint-scope@8.2.0: + resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.21.0: + resolution: {integrity: sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} @@ -909,10 +1451,19 @@ packages: fast-copy@3.0.2: resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-redact@3.5.0: resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} engines: {node: '>=6'} @@ -931,6 +1482,10 @@ packages: picomatch: optional: true + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -942,6 +1497,17 @@ packages: resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -950,6 +1516,9 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} @@ -965,8 +1534,13 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - gc-hook@0.4.1: - resolution: {integrity: sha512-uiF+uUftDVLr+VRdudsdsT3/LQYnv2ntwhRH964O7xXDI57Smrek5olv75Wb8Nnz6U+7iVTRXsBlxKcsaDTJTQ==} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} @@ -986,6 +1560,10 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true @@ -995,10 +1573,21 @@ packages: engines: {node: 20 || >=22} hasBin: true + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -1017,12 +1606,6 @@ packages: help-me@5.0.0: resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} - html-escaper@3.0.3: - resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} - - htmlparser2@9.1.0: - resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==} - http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -1034,6 +1617,18 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -1083,6 +1678,10 @@ packages: resolution: {integrity: sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==} engines: {node: 20 || >=22} + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + jose@5.10.0: resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} @@ -1090,10 +1689,107 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kysely@0.27.5: resolution: {integrity: sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA==} engines: {node: '>=14.0.0'} + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-darwin-arm64@1.29.1: + resolution: {integrity: sha512-HtR5XJ5A0lvCqYAoSv2QdZZyoHNttBpa5EP9aNuzBQeKGfbyH5+UipLWvVzpP4Uml5ej4BYs5I9Lco9u1fECqw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.29.1: + resolution: {integrity: sha512-k33G9IzKUpHy/J/3+9MCO4e+PzaFblsgBjSGlpAaFikeBFm8B/CkO3cKU9oI4g+fjS2KlkLM/Bza9K/aw8wsNA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.29.1: + resolution: {integrity: sha512-0SUW22fv/8kln2LnIdOCmSuXnxgxVC276W5KLTwoehiO0hxkacBxjHOL5EtHD8BAXg2BvuhsJPmVMasvby3LiQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.29.1: + resolution: {integrity: sha512-sD32pFvlR0kDlqsOZmYqH/68SqUMPNj+0pucGxToXZi4XZgZmqeX/NkxNKCPsswAXU3UeYgDSpGhu05eAufjDg==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.29.1: + resolution: {integrity: sha512-0+vClRIZ6mmJl/dxGuRsE197o1HDEeeRk6nzycSy2GofC2JsY4ifCRnvUWf/CUBQmlrvMzt6SMQNMSEu22csWQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.29.1: + resolution: {integrity: sha512-UKMFrG4rL/uHNgelBsDwJcBqVpzNJbzsKkbI3Ja5fg00sgQnHw/VrzUTEc4jhZ+AN2BvQYz/tkHu4vt1kLuJyw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.29.1: + resolution: {integrity: sha512-u1S+xdODy/eEtjADqirA774y3jLcm8RPtYztwReEXoZKdzgsHYPl0s5V52Tst+GKzqjebkULT86XMSxejzfISw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.29.1: + resolution: {integrity: sha512-L0Tx0DtaNUTzXv0lbGCLB/c/qEADanHbu4QdcNOXLIe1i8i22rZRpbT3gpWYsCh9aSL9zFujY/WmEXIatWvXbw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.29.1: + resolution: {integrity: sha512-QoOVnkIEFfbW4xPi+dpdft/zAKmgLgsRHfJalEPYuJDOWf7cLQzYg0DEh8/sn737FaeMJxHZRc1oBreiwZCjog==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.29.1: + resolution: {integrity: sha512-NygcbThNBe4JElP+olyTI/doBNGJvLs3bFCRPdvuCcxZCcCZ71B858IHpdm7L1btZex0FvCmM17FK98Y9MRy1Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.29.1: + resolution: {integrity: sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q==} + engines: {node: '>= 12.0.0'} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -1105,9 +1801,19 @@ packages: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -1115,6 +1821,9 @@ packages: resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==} engines: {node: 20 || >=22} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} @@ -1162,6 +1871,9 @@ packages: resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} engines: {node: 20 || >=22} + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@5.1.6: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} @@ -1200,9 +1912,17 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + napi-build-utils@2.0.0: resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -1215,6 +1935,13 @@ packages: resolution: {integrity: sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==} hasBin: true + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -1234,10 +1961,22 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + p-finally@1.0.0: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + p-queue@6.6.2: resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} engines: {node: '>=8'} @@ -1249,6 +1988,10 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -1256,6 +1999,10 @@ packages: path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1328,11 +2075,77 @@ packages: yaml: optional: true + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} + prebuild-install@7.1.3: resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} engines: {node: '>=10'} hasBin: true + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-plugin-tailwindcss@0.6.11: + resolution: {integrity: sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==} + engines: {node: '>=14.21.3'} + peerDependencies: + '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-pug': '*' + '@shopify/prettier-plugin-liquid': '*' + '@trivago/prettier-plugin-sort-imports': '*' + '@zackad/prettier-plugin-twig': '*' + prettier: ^3.0 + prettier-plugin-astro: '*' + prettier-plugin-css-order: '*' + prettier-plugin-import-sort: '*' + prettier-plugin-jsdoc: '*' + prettier-plugin-marko: '*' + prettier-plugin-multiline-arrays: '*' + prettier-plugin-organize-attributes: '*' + prettier-plugin-organize-imports: '*' + prettier-plugin-sort-imports: '*' + prettier-plugin-style-order: '*' + prettier-plugin-svelte: '*' + peerDependenciesMeta: + '@ianvs/prettier-plugin-sort-imports': + optional: true + '@prettier/plugin-pug': + optional: true + '@shopify/prettier-plugin-liquid': + optional: true + '@trivago/prettier-plugin-sort-imports': + optional: true + '@zackad/prettier-plugin-twig': + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-css-order: + optional: true + prettier-plugin-import-sort: + optional: true + prettier-plugin-jsdoc: + optional: true + prettier-plugin-marko: + optional: true + prettier-plugin-multiline-arrays: + optional: true + prettier-plugin-organize-attributes: + optional: true + prettier-plugin-organize-imports: + optional: true + prettier-plugin-sort-imports: + optional: true + prettier-plugin-style-order: + optional: true + prettier-plugin-svelte: + optional: true + prettier@3.5.2: resolution: {integrity: sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==} engines: {node: '>=14'} @@ -1387,6 +2200,36 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true + react-dom@19.0.0: + resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} + peerDependencies: + react: ^19.0.0 + + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + + react-router-dom@7.2.0: + resolution: {integrity: sha512-cU7lTxETGtQRQbafJubvZKHEn5izNABxZhBY0Jlzdv0gqQhCPQt2J8aN5ZPjS6mQOXn5NnirWNh+FpE8TTYN0Q==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.2.0: + resolution: {integrity: sha512-fXyqzPgCPZbqhrk7k3hPcCpYIlQ2ugIXDboHUzhJISFVy2DEPsmHgN588MyGmkIOv3jDgNfUE3kJi83L28s/LQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react@19.0.0: + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} + engines: {node: '>=0.10.0'} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -1403,6 +2246,14 @@ packages: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -1427,6 +2278,9 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -1437,9 +2291,16 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.25.0: + resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} + secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + semver@7.7.1: resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} @@ -1453,6 +2314,9 @@ packages: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -1464,6 +2328,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shell-quote@1.8.2: + resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==} + engines: {node: '>= 0.4'} + side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} @@ -1496,6 +2364,10 @@ packages: sonic-boom@4.2.0: resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} @@ -1544,6 +2416,17 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + tailwindcss@4.0.9: + resolution: {integrity: sha512-12laZu+fv1ONDRoNR9ipTOpUD7RN9essRVkX36sjxuRUInpN7hIiHN4lBd/SIFjbISvnXzp8h/hXzmU8SQQYhw==} + + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + tar-fs@2.1.2: resolution: {integrity: sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==} @@ -1590,6 +2473,12 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + ts-api-utils@2.0.1: + resolution: {integrity: sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -1613,6 +2502,9 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsup@8.4.0: resolution: {integrity: sha512-b+eZbPCjz10fRryaAA7C8xlIHnf8VnsaRqydheLIqwG/Mcpfk8Z5zp3HayX7GaTygkigHl5cBUs+IhcySiIexQ==} engines: {node: '>=18'} @@ -1640,6 +2532,13 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + turbo-stream@2.4.0: + resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -1649,12 +2548,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - udomdiff@1.1.2: - resolution: {integrity: sha512-v+Z8Jal+GtmKGtJ34GIQlCJAxrDt9kbjpNsNvYoAXFyr4gNfWlD4uJJuoNNu/0UTVaKvQwHaSU095YDl71lKPw==} - - uhtml@4.7.0: - resolution: {integrity: sha512-3j0YIvbu863FB27mwnuLcKK0zPsHVQWwUs/GFanVz/QSwsItT/lOcGKmIdpqlcfWpYBCBoMEdfK0vIN/P2kCmg==} - uint8arrays@3.0.0: resolution: {integrity: sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==} @@ -1672,6 +2565,15 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -1689,6 +2591,46 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vite@6.2.0: + resolution: {integrity: sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} @@ -1700,6 +2642,10 @@ packages: engines: {node: '>= 8'} hasBin: true + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -1723,6 +2669,21 @@ packages: utf-8-validate: optional: true + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + yesno@0.4.0: resolution: {integrity: sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA==} @@ -1730,11 +2691,20 @@ packages: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + zod@3.24.2: resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} snapshots: + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + '@atproto-labs/did-resolver@0.1.10': dependencies: '@atproto-labs/fetch': 0.2.1 @@ -1949,6 +2919,116 @@ snapshots: '@atproto/lexicon': 0.4.7 zod: 3.24.2 + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.26.8': {} + + '@babel/core@7.26.9': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.9 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.9) + '@babel/helpers': 7.26.9 + '@babel/parser': 7.26.9 + '@babel/template': 7.26.9 + '@babel/traverse': 7.26.9 + '@babel/types': 7.26.9 + convert-source-map: 2.0.0 + debug: 4.4.0 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.26.9': + dependencies: + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.26.5': + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.4 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.26.9 + '@babel/types': 7.26.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.9)': + dependencies: + '@babel/core': 7.26.9 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.26.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.26.5': {} + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helpers@7.26.9': + dependencies: + '@babel/template': 7.26.9 + '@babel/types': 7.26.9 + + '@babel/parser@7.26.9': + dependencies: + '@babel/types': 7.26.9 + + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.9)': + dependencies: + '@babel/core': 7.26.9 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.9)': + dependencies: + '@babel/core': 7.26.9 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/template@7.26.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 + + '@babel/traverse@7.26.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.9 + '@babel/parser': 7.26.9 + '@babel/template': 7.26.9 + '@babel/types': 7.26.9 + debug: 4.4.0 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.26.9': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@cbor-extract/cbor-extract-darwin-arm64@2.2.0': optional: true @@ -2046,6 +3126,72 @@ snapshots: '@esbuild/win32-x64@0.25.0': optional: true + '@eslint-community/eslint-utils@4.4.1(eslint@9.21.0(jiti@2.4.2))': + dependencies: + eslint: 9.21.0(jiti@2.4.2) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.19.2': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/core@0.12.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.0': + dependencies: + ajv: 6.12.6 + debug: 4.4.0 + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.21.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.2.7': + dependencies: + '@eslint/core': 0.12.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.2': {} + + '@ianvs/prettier-plugin-sort-imports@4.4.1(prettier@3.5.2)': + dependencies: + '@babel/generator': 7.26.9 + '@babel/parser': 7.26.9 + '@babel/traverse': 7.26.9 + '@babel/types': 7.26.9 + prettier: 3.5.2 + semver: 7.7.1 + transitivePeerDependencies: + - supports-color + '@ipld/car@3.2.4': dependencies: '@ipld/dag-cbor': 7.0.3 @@ -2109,9 +3255,6 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@preact/signals-core@1.8.0': - optional: true - '@rollup/rollup-android-arm-eabi@4.34.9': optional: true @@ -2169,6 +3312,74 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.34.9': optional: true + '@tailwindcss/node@4.0.9': + dependencies: + enhanced-resolve: 5.18.1 + jiti: 2.4.2 + tailwindcss: 4.0.9 + + '@tailwindcss/oxide-android-arm64@4.0.9': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.0.9': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.0.9': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.0.9': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.9': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.0.9': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.0.9': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.0.9': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.0.9': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.0.9': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.0.9': + optional: true + + '@tailwindcss/oxide@4.0.9': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.0.9 + '@tailwindcss/oxide-darwin-arm64': 4.0.9 + '@tailwindcss/oxide-darwin-x64': 4.0.9 + '@tailwindcss/oxide-freebsd-x64': 4.0.9 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.0.9 + '@tailwindcss/oxide-linux-arm64-gnu': 4.0.9 + '@tailwindcss/oxide-linux-arm64-musl': 4.0.9 + '@tailwindcss/oxide-linux-x64-gnu': 4.0.9 + '@tailwindcss/oxide-linux-x64-musl': 4.0.9 + '@tailwindcss/oxide-win32-arm64-msvc': 4.0.9 + '@tailwindcss/oxide-win32-x64-msvc': 4.0.9 + + '@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))': + dependencies: + '@tailwindcss/node': 4.0.9 + '@tailwindcss/oxide': 4.0.9 + lightningcss: 1.29.1 + tailwindcss: 4.0.9 + vite: 6.2.0(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.3) + + '@tanstack/query-core@5.66.11': {} + + '@tanstack/react-query@5.66.11(react@19.0.0)': + dependencies: + '@tanstack/query-core': 5.66.11 + react: 19.0.0 + '@ts-morph/common@0.17.0': dependencies: fast-glob: 3.3.3 @@ -2184,6 +3395,27 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 + + '@types/babel__generator@7.6.8': + dependencies: + '@babel/types': 7.26.9 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 + + '@types/babel__traverse@7.20.6': + dependencies: + '@babel/types': 7.26.9 + '@types/better-sqlite3@7.6.12': dependencies: '@types/node': 22.13.8 @@ -2197,6 +3429,12 @@ snapshots: dependencies: '@types/node': 22.13.8 + '@types/cookie@0.6.0': {} + + '@types/cors@2.8.17': + dependencies: + '@types/node': 22.13.8 + '@types/estree@1.0.6': {} '@types/express-serve-static-core@5.0.6': @@ -2215,33 +3453,124 @@ snapshots: '@types/http-errors@2.0.4': {} - '@types/mime@1.3.5': {} + '@types/json-schema@7.0.15': {} + + '@types/mime@1.3.5': {} + + '@types/node@22.13.8': + dependencies: + undici-types: 6.20.0 + + '@types/qs@6.9.18': {} + + '@types/range-parser@1.2.7': {} + + '@types/react-dom@19.0.4(@types/react@19.0.10)': + dependencies: + '@types/react': 19.0.10 + + '@types/react@19.0.10': + dependencies: + csstype: 3.1.3 + + '@types/send@0.17.4': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 22.13.8 + + '@types/serve-static@1.15.7': + dependencies: + '@types/http-errors': 2.0.4 + '@types/node': 22.13.8 + '@types/send': 0.17.4 + + '@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)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2) + '@typescript-eslint/scope-manager': 8.25.0 + '@typescript-eslint/type-utils': 8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2) + '@typescript-eslint/utils': 8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2) + '@typescript-eslint/visitor-keys': 8.25.0 + eslint: 9.21.0(jiti@2.4.2) + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 2.0.1(typescript@5.8.2) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2)': + dependencies: + '@typescript-eslint/scope-manager': 8.25.0 + '@typescript-eslint/types': 8.25.0 + '@typescript-eslint/typescript-estree': 8.25.0(typescript@5.8.2) + '@typescript-eslint/visitor-keys': 8.25.0 + debug: 4.4.0 + eslint: 9.21.0(jiti@2.4.2) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color - '@types/node@22.13.8': + '@typescript-eslint/scope-manager@8.25.0': dependencies: - undici-types: 6.20.0 + '@typescript-eslint/types': 8.25.0 + '@typescript-eslint/visitor-keys': 8.25.0 - '@types/qs@6.9.18': {} + '@typescript-eslint/type-utils@8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2)': + dependencies: + '@typescript-eslint/typescript-estree': 8.25.0(typescript@5.8.2) + '@typescript-eslint/utils': 8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2) + debug: 4.4.0 + eslint: 9.21.0(jiti@2.4.2) + ts-api-utils: 2.0.1(typescript@5.8.2) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color - '@types/range-parser@1.2.7': {} + '@typescript-eslint/types@8.25.0': {} - '@types/send@0.17.4': + '@typescript-eslint/typescript-estree@8.25.0(typescript@5.8.2)': dependencies: - '@types/mime': 1.3.5 - '@types/node': 22.13.8 + '@typescript-eslint/types': 8.25.0 + '@typescript-eslint/visitor-keys': 8.25.0 + debug: 4.4.0 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.1 + ts-api-utils: 2.0.1(typescript@5.8.2) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color - '@types/serve-static@1.15.7': + '@typescript-eslint/utils@8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2)': dependencies: - '@types/http-errors': 2.0.4 - '@types/node': 22.13.8 - '@types/send': 0.17.4 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.21.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.25.0 + '@typescript-eslint/types': 8.25.0 + '@typescript-eslint/typescript-estree': 8.25.0(typescript@5.8.2) + eslint: 9.21.0(jiti@2.4.2) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color - '@webreflection/signal@2.1.2': - optional: true + '@typescript-eslint/visitor-keys@8.25.0': + dependencies: + '@typescript-eslint/types': 8.25.0 + eslint-visitor-keys: 4.2.0 - '@webreflection/uparser@0.4.0': + '@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))': dependencies: - domconstants: 1.1.6 + '@babel/core': 7.26.9 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.9) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 6.2.0(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.3) + transitivePeerDependencies: + - supports-color abort-controller@3.0.0: dependencies: @@ -2252,12 +3581,23 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 + acorn-jsx@5.3.2(acorn@8.14.0): + dependencies: + acorn: 8.14.0 + acorn-walk@8.3.4: dependencies: acorn: 8.14.0 acorn@8.14.0: {} + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -2272,10 +3612,22 @@ snapshots: arg@4.1.3: {} + argparse@2.0.1: {} + array-flatten@1.1.1: {} atomic-sleep@1.0.0: {} + autoprefixer@10.4.20(postcss@8.5.3): + dependencies: + browserslist: 4.24.4 + caniuse-lite: 1.0.30001701 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.5.3 + postcss-value-parser: 4.2.0 + await-lock@2.2.2: {} balanced-match@1.0.2: {} @@ -2314,6 +3666,11 @@ snapshots: transitivePeerDependencies: - supports-color + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 @@ -2322,6 +3679,13 @@ snapshots: dependencies: fill-range: 7.1.1 + browserslist@4.24.4: + dependencies: + caniuse-lite: 1.0.30001701 + electron-to-chromium: 1.5.109 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.24.4) + buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -2351,6 +3715,10 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + callsites@3.1.0: {} + + caniuse-lite@1.0.30001701: {} + cbor-extract@2.2.0: dependencies: node-gyp-build-optional-packages: 5.1.1 @@ -2380,6 +3748,12 @@ snapshots: chownr@1.1.4: {} + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + code-block-writer@11.0.3: {} color-convert@2.0.1: @@ -2394,6 +3768,18 @@ snapshots: commander@9.5.0: {} + concat-map@0.0.1: {} + + concurrently@9.1.2: + dependencies: + chalk: 4.1.2 + lodash: 4.17.21 + rxjs: 7.8.2 + shell-quote: 1.8.2 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + consola@3.4.0: {} content-disposition@0.5.4: @@ -2402,12 +3788,21 @@ snapshots: content-type@1.0.5: {} + convert-source-map@2.0.0: {} + cookie-signature@1.0.6: {} cookie@0.7.1: {} cookie@0.7.2: {} + cookie@1.0.2: {} + + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + create-require@1.1.1: {} cross-spawn@7.0.6: @@ -2416,7 +3811,7 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - custom-function@2.0.0: {} + csstype@3.1.3: {} dateformat@4.6.3: {} @@ -2434,34 +3829,18 @@ snapshots: deep-extend@0.6.0: {} + deep-is@0.1.4: {} + depd@2.0.0: {} destroy@1.2.0: {} + detect-libc@1.0.3: {} + detect-libc@2.0.3: {} diff@4.0.2: {} - dom-serializer@2.0.0: - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - entities: 4.5.0 - - domconstants@1.1.6: {} - - domelementtype@2.3.0: {} - - domhandler@5.0.3: - dependencies: - domelementtype: 2.3.0 - - domutils@3.2.2: - dependencies: - dom-serializer: 2.0.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 - dotenv@16.4.7: {} dunder-proto@1.0.1: @@ -2474,6 +3853,8 @@ snapshots: ee-first@1.1.1: {} + electron-to-chromium@1.5.109: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -2486,7 +3867,10 @@ snapshots: dependencies: once: 1.4.0 - entities@4.5.0: {} + enhanced-resolve@5.18.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 envalid@8.0.0: dependencies: @@ -2528,8 +3912,88 @@ snapshots: '@esbuild/win32-ia32': 0.25.0 '@esbuild/win32-x64': 0.25.0 + escalade@3.2.0: {} + escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@5.2.0(eslint@9.21.0(jiti@2.4.2)): + dependencies: + eslint: 9.21.0(jiti@2.4.2) + + eslint-plugin-react-refresh@0.4.19(eslint@9.21.0(jiti@2.4.2)): + dependencies: + eslint: 9.21.0(jiti@2.4.2) + + eslint-scope@8.2.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@9.21.0(jiti@2.4.2): + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.21.0(jiti@2.4.2)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.19.2 + '@eslint/core': 0.12.0 + '@eslint/eslintrc': 3.3.0 + '@eslint/js': 9.21.0 + '@eslint/plugin-kit': 0.2.7 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.2 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + escape-string-regexp: 4.0.0 + eslint-scope: 8.2.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.4.2 + transitivePeerDependencies: + - supports-color + + espree@10.3.0: + dependencies: + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 4.2.0 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + etag@1.8.1: {} event-target-shim@5.0.1: {} @@ -2578,6 +4042,8 @@ snapshots: fast-copy@3.0.2: {} + fast-deep-equal@3.1.3: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2586,6 +4052,10 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + fast-redact@3.5.0: {} fast-safe-stringify@2.1.1: {} @@ -2598,6 +4068,10 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + file-uri-to-path@1.0.0: {} fill-range@7.1.1: @@ -2616,6 +4090,18 @@ snapshots: transitivePeerDependencies: - supports-color + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -2623,6 +4109,8 @@ snapshots: forwarded@0.2.0: {} + fraction.js@4.3.7: {} + fresh@0.5.2: {} fs-constants@1.0.0: {} @@ -2632,7 +4120,9 @@ snapshots: function-bind@1.1.2: {} - gc-hook@0.4.1: {} + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} get-intrinsic@1.3.0: dependencies: @@ -2662,6 +4152,10 @@ snapshots: dependencies: is-glob: 4.0.3 + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + glob@10.4.5: dependencies: foreground-child: 3.3.1 @@ -2680,8 +4174,14 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 2.0.0 + globals@11.12.0: {} + + globals@14.0.0: {} + gopd@1.2.0: {} + graceful-fs@4.2.11: {} + graphemer@1.4.0: {} has-flag@4.0.0: {} @@ -2694,15 +4194,6 @@ snapshots: help-me@5.0.0: {} - html-escaper@3.0.3: {} - - htmlparser2@9.1.0: - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - domutils: 3.2.2 - entities: 4.5.0 - http-errors@2.0.0: dependencies: depd: 2.0.0 @@ -2717,6 +4208,15 @@ snapshots: ieee754@1.2.1: {} + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + inherits@2.0.4: {} ini@1.3.8: {} @@ -2757,24 +4257,108 @@ snapshots: dependencies: '@isaacs/cliui': 8.0.2 + jiti@2.4.2: {} + jose@5.10.0: {} joycon@3.1.1: {} + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + kysely@0.27.5: {} + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-darwin-arm64@1.29.1: + optional: true + + lightningcss-darwin-x64@1.29.1: + optional: true + + lightningcss-freebsd-x64@1.29.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.29.1: + optional: true + + lightningcss-linux-arm64-gnu@1.29.1: + optional: true + + lightningcss-linux-arm64-musl@1.29.1: + optional: true + + lightningcss-linux-x64-gnu@1.29.1: + optional: true + + lightningcss-linux-x64-musl@1.29.1: + optional: true + + lightningcss-win32-arm64-msvc@1.29.1: + optional: true + + lightningcss-win32-x64-msvc@1.29.1: + optional: true + + lightningcss@1.29.1: + dependencies: + detect-libc: 1.0.3 + optionalDependencies: + lightningcss-darwin-arm64: 1.29.1 + lightningcss-darwin-x64: 1.29.1 + lightningcss-freebsd-x64: 1.29.1 + lightningcss-linux-arm-gnueabihf: 1.29.1 + lightningcss-linux-arm64-gnu: 1.29.1 + lightningcss-linux-arm64-musl: 1.29.1 + lightningcss-linux-x64-gnu: 1.29.1 + lightningcss-linux-x64-musl: 1.29.1 + lightningcss-win32-arm64-msvc: 1.29.1 + lightningcss-win32-x64-msvc: 1.29.1 + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} load-tsconfig@0.2.5: {} + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + lodash.sortby@4.7.0: {} + lodash@4.17.21: {} + lru-cache@10.4.3: {} lru-cache@11.0.2: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + make-error@1.3.6: {} math-intrinsics@1.1.0: {} @@ -2806,6 +4390,10 @@ snapshots: dependencies: brace-expansion: 2.0.1 + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + minimatch@5.1.6: dependencies: brace-expansion: 2.0.1 @@ -2836,8 +4424,12 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 + nanoid@3.3.8: {} + napi-build-utils@2.0.0: {} + natural-compare@1.4.0: {} + negotiator@0.6.3: {} node-abi@3.74.0: @@ -2849,6 +4441,10 @@ snapshots: detect-libc: 2.0.3 optional: true + node-releases@2.0.19: {} + + normalize-range@0.1.2: {} + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -2863,8 +4459,25 @@ snapshots: dependencies: wrappy: 1.0.2 + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + p-finally@1.0.0: {} + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + p-queue@6.6.2: dependencies: eventemitter3: 4.0.7 @@ -2876,10 +4489,16 @@ snapshots: package-json-from-dist@1.0.1: {} + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + parseurl@1.3.3: {} path-browserify@1.0.1: {} + path-exists@4.0.0: {} + path-key@3.1.1: {} path-scurry@1.11.1: @@ -2959,12 +4578,22 @@ snapshots: pirates@4.0.6: {} - postcss-load-config@6.0.1(tsx@4.19.3): + postcss-load-config@6.0.1(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.3): dependencies: lilconfig: 3.1.3 optionalDependencies: + jiti: 2.4.2 + postcss: 8.5.3 tsx: 4.19.3 + postcss-value-parser@4.2.0: {} + + postcss@8.5.3: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + prebuild-install@7.1.3: dependencies: detect-libc: 2.0.3 @@ -2980,6 +4609,14 @@ snapshots: tar-fs: 2.1.2 tunnel-agent: 0.6.0 + prelude-ls@1.2.1: {} + + prettier-plugin-tailwindcss@0.6.11(@ianvs/prettier-plugin-sort-imports@4.4.1(prettier@3.5.2))(prettier@3.5.2): + dependencies: + prettier: 3.5.2 + optionalDependencies: + '@ianvs/prettier-plugin-sort-imports': 4.4.1(prettier@3.5.2) + prettier@3.5.2: {} process-warning@3.0.0: {} @@ -3030,6 +4667,31 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 + react-dom@19.0.0(react@19.0.0): + dependencies: + react: 19.0.0 + scheduler: 0.25.0 + + react-refresh@0.14.2: {} + + react-router-dom@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-router: 7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + + react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@types/cookie': 0.6.0 + cookie: 1.0.2 + react: 19.0.0 + set-cookie-parser: 2.7.1 + turbo-stream: 2.4.0 + optionalDependencies: + react-dom: 19.0.0(react@19.0.0) + + react@19.0.0: {} + readable-stream@3.6.2: dependencies: inherits: 2.0.4 @@ -3048,6 +4710,10 @@ snapshots: real-require@0.2.0: {} + require-directory@2.1.1: {} + + resolve-from@4.0.0: {} + resolve-from@5.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -3088,14 +4754,22 @@ snapshots: dependencies: queue-microtask: 1.2.3 + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + safe-buffer@5.2.1: {} safe-stable-stringify@2.5.0: {} safer-buffer@2.1.2: {} + scheduler@0.25.0: {} + secure-json-parse@2.7.0: {} + semver@6.3.1: {} + semver@7.7.1: {} send@0.19.0: @@ -3125,6 +4799,8 @@ snapshots: transitivePeerDependencies: - supports-color + set-cookie-parser@2.7.1: {} + setprototypeof@1.2.0: {} shebang-command@2.0.0: @@ -3133,6 +4809,8 @@ snapshots: shebang-regex@3.0.0: {} + shell-quote@1.8.2: {} + side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 @@ -3179,6 +4857,8 @@ snapshots: dependencies: atomic-sleep: 1.0.0 + source-map-js@1.2.1: {} + source-map@0.8.0-beta.0: dependencies: whatwg-url: 7.1.0 @@ -3229,6 +4909,14 @@ snapshots: dependencies: has-flag: 4.0.0 + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + tailwindcss@4.0.9: {} + + tapable@2.2.1: {} + tar-fs@2.1.2: dependencies: chownr: 1.1.4 @@ -3281,6 +4969,10 @@ snapshots: tree-kill@1.2.2: {} + ts-api-utils@2.0.1(typescript@5.8.2): + dependencies: + typescript: 5.8.2 + ts-interface-checker@0.1.13: {} ts-morph@16.0.0: @@ -3308,7 +5000,9 @@ snapshots: tslib@2.6.2: {} - tsup@8.4.0(tsx@4.19.3)(typescript@5.8.2): + tslib@2.8.1: {} + + tsup@8.4.0(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.3)(typescript@5.8.2): dependencies: bundle-require: 5.1.0(esbuild@0.25.0) cac: 6.7.14 @@ -3318,7 +5012,7 @@ snapshots: esbuild: 0.25.0 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(tsx@4.19.3) + postcss-load-config: 6.0.1(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.3) resolve-from: 5.0.0 rollup: 4.34.9 source-map: 0.8.0-beta.0 @@ -3327,6 +5021,7 @@ snapshots: tinyglobby: 0.2.12 tree-kill: 1.2.2 optionalDependencies: + postcss: 8.5.3 typescript: 5.8.2 transitivePeerDependencies: - jiti @@ -3345,6 +5040,12 @@ snapshots: dependencies: safe-buffer: 5.2.1 + turbo-stream@2.4.0: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + type-is@1.6.18: dependencies: media-typer: 0.3.0 @@ -3352,21 +5053,6 @@ snapshots: typescript@5.8.2: {} - udomdiff@1.1.2: {} - - uhtml@4.7.0: - dependencies: - '@webreflection/uparser': 0.4.0 - custom-function: 2.0.0 - domconstants: 1.1.6 - gc-hook: 0.4.1 - html-escaper: 3.0.3 - htmlparser2: 9.1.0 - udomdiff: 1.1.2 - optionalDependencies: - '@preact/signals-core': 1.8.0 - '@webreflection/signal': 2.1.2 - uint8arrays@3.0.0: dependencies: multiformats: 9.9.0 @@ -3379,6 +5065,16 @@ snapshots: unpipe@1.0.0: {} + update-browserslist-db@1.1.3(browserslist@4.24.4): + dependencies: + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + util-deprecate@1.0.2: {} utils-merge@1.0.1: {} @@ -3389,6 +5085,18 @@ snapshots: vary@1.1.2: {} + vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.3): + dependencies: + esbuild: 0.25.0 + postcss: 8.5.3 + rollup: 4.34.9 + optionalDependencies: + '@types/node': 22.13.8 + fsevents: 2.3.3 + jiti: 2.4.2 + lightningcss: 1.29.1 + tsx: 4.19.3 + webidl-conversions@4.0.2: {} whatwg-url@7.1.0: @@ -3401,6 +5109,8 @@ snapshots: dependencies: isexe: 2.0.0 + word-wrap@1.2.5: {} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -3417,8 +5127,26 @@ snapshots: ws@8.18.1: {} + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yesno@0.4.0: {} yn@3.1.1: {} + yocto-queue@0.1.0: {} + zod@3.24.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..4340350 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'packages/*' \ No newline at end of file diff --git a/scripts/setup-ngrok.js b/scripts/setup-ngrok.js new file mode 100755 index 0000000..e983908 --- /dev/null +++ b/scripts/setup-ngrok.js @@ -0,0 +1,281 @@ +#!/usr/bin/env node + +/** + * This script automatically sets up ngrok for development. + * It: + * 1. Starts ngrok to tunnel to localhost:3001 + * 2. Gets the public HTTPS URL via ngrok's API + * 3. Updates the appview .env file with the ngrok URL + * 4. Starts both the API server and client app + */ + +const { execSync, spawn } = require('child_process') +const fs = require('fs') +const path = require('path') +const http = require('http') +const { URL } = require('url') + +const appviewEnvPath = path.join(__dirname, '..', 'packages', 'appview', '.env') +const clientEnvPath = path.join(__dirname, '..', 'packages', 'client', '.env') + +// Check if ngrok is installed +try { + execSync('ngrok --version', { stdio: 'ignore' }) +} catch (error) { + console.error('โŒ ngrok is not installed or not in your PATH.') + console.error('Please install ngrok from https://ngrok.com/download') + process.exit(1) +} + +// Kill any existing ngrok processes +try { + if (process.platform === 'win32') { + execSync('taskkill /f /im ngrok.exe', { stdio: 'ignore' }) + } else { + execSync('pkill -f ngrok', { stdio: 'ignore' }) + } + // Wait for processes to terminate + try { + execSync('sleep 1') + } catch (e) {} +} catch (error) { + // If no process was found, it will throw an error, which we can ignore +} + +console.log('๐Ÿš€ Starting ngrok...') + +// Start ngrok process - now we're exposing the client (3000) instead of the API (3001) +// This way the whole app will be served through ngrok +const ngrokProcess = spawn('ngrok', ['http', '3000'], { + stdio: ['ignore', 'pipe', 'pipe'], // Allow stdout, stderr +}) + +let devProcesses = null + +// Helper function to update .env files +function updateEnvFile(filePath, ngrokUrl) { + if (!fs.existsSync(filePath)) { + fs.writeFileSync(filePath, '') + } + + const content = fs.readFileSync(filePath, 'utf8') + + if (filePath.includes('appview')) { + // Update NGROK_URL in the appview package + const varName = 'NGROK_URL' + const publicUrlName = 'PUBLIC_URL' + const regex = new RegExp(`^${varName}=.*$`, 'm') + const publicUrlRegex = new RegExp(`^${publicUrlName}=.*$`, 'm') + + // Update content + let updatedContent = content + + // Update or add NGROK_URL + if (regex.test(updatedContent)) { + updatedContent = updatedContent.replace(regex, `${varName}=${ngrokUrl}`) + } else { + updatedContent = `${updatedContent}\n${varName}=${ngrokUrl}\n` + } + + // Update or add PUBLIC_URL - set it to the ngrok URL too + if (publicUrlRegex.test(updatedContent)) { + updatedContent = updatedContent.replace( + publicUrlRegex, + `${publicUrlName}=${ngrokUrl}`, + ) + } else { + updatedContent = `${updatedContent}\n${publicUrlName}=${ngrokUrl}\n` + } + + fs.writeFileSync(filePath, updatedContent) + console.log( + `โœ… Updated ${path.basename(filePath)} with ${varName}=${ngrokUrl} and ${publicUrlName}=${ngrokUrl}`, + ) + } else if (filePath.includes('client')) { + // For client, set VITE_API_URL to "/api" - this ensures it uses the proxy setup + const varName = 'VITE_API_URL' + const regex = new RegExp(`^${varName}=.*$`, 'm') + + let updatedContent + if (regex.test(content)) { + // Update existing variable + updatedContent = content.replace(regex, `${varName}=/api`) + } else { + // Add new variable + updatedContent = `${content}\n${varName}=/api\n` + } + + fs.writeFileSync(filePath, updatedContent) + console.log( + `โœ… Updated ${path.basename(filePath)} with ${varName}=/api (proxy to API server)`, + ) + } +} + +// Function to start the development servers +function startDevServers() { + console.log('๐Ÿš€ Starting development servers...') + + // Free port 3001 if it's in use + try { + if (process.platform !== 'win32') { + // Kill any process using port 3001 + execSync('kill $(lsof -t -i:3001 2>/dev/null) 2>/dev/null || true') + // Wait for port to be released + execSync('sleep 1') + } + } catch (error) { + // Ignore errors + } + + // Start both servers + devProcesses = spawn('pnpm', ['--filter', '@statusphere/appview', 'dev'], { + stdio: 'inherit', + detached: false, + }) + + const clientProcess = spawn( + 'pnpm', + ['--filter', '@statusphere/client', 'dev'], + { + stdio: 'inherit', + detached: false, + }, + ) + + devProcesses.on('close', (code) => { + console.log(`API server exited with code ${code}`) + killAllProcesses() + }) + + clientProcess.on('close', (code) => { + console.log(`Client app exited with code ${code}`) + killAllProcesses() + }) +} + +// Function to get the ngrok URL from its API +function getNgrokUrl() { + return new Promise((resolve, reject) => { + // Wait a bit for ngrok to start its API server + setTimeout(() => { + http + .get('http://localhost:4040/api/tunnels', (res) => { + let data = '' + + res.on('data', (chunk) => { + data += chunk + }) + + res.on('end', () => { + try { + const tunnels = JSON.parse(data).tunnels + if (tunnels && tunnels.length > 0) { + // Find HTTPS tunnel + const httpsTunnel = tunnels.find((t) => t.proto === 'https') + if (httpsTunnel) { + resolve(httpsTunnel.public_url) + } else { + reject(new Error('No HTTPS tunnel found')) + } + } else { + reject(new Error('No tunnels found')) + } + } catch (error) { + reject(error) + } + }) + }) + .on('error', (err) => { + reject(err) + }) + }, 2000) // Give ngrok a couple seconds to start + }) +} + +// Poll the ngrok API until we get a URL +function pollNgrokApi() { + getNgrokUrl() + .then((ngrokUrl) => { + console.log(`๐ŸŒ ngrok URL: ${ngrokUrl}`) + + // Update .env files with the ngrok URL + updateEnvFile(appviewEnvPath, ngrokUrl) + // We'll still call this but it will be skipped per our updated logic + updateEnvFile(clientEnvPath, ngrokUrl) + + // Start development servers + startDevServers() + }) + .catch(() => { + // Try again in 1 second + setTimeout(pollNgrokApi, 1000) + }) +} + +// Start polling after a short delay +setTimeout(pollNgrokApi, 1000) + +// Handle errors +ngrokProcess.stderr.on('data', (data) => { + console.error('------- NGROK ERROR -------') + console.error(data.toString()) + console.error('---------------------------') +}) + +// Handle ngrok process exit +ngrokProcess.on('close', (code) => { + console.log(`ngrok process exited with code ${code}`) + // Call our kill function to ensure everything is properly cleaned up + killAllProcesses() +}) + +// Function to properly terminate all child processes +function killAllProcesses() { + console.log('\nShutting down development environment...') + + // Get ngrok process PID for force kill if needed + const ngrokPid = ngrokProcess.pid + + // Kill main processes with a normal signal first + if (devProcesses) { + try { + devProcesses.kill() + } catch (e) {} + } + + try { + ngrokProcess.kill() + } catch (e) {} + + // Force kill ngrok if normal kill fails + try { + if (process.platform === 'win32') { + execSync(`taskkill /F /PID ${ngrokPid} 2>nul`, { stdio: 'ignore' }) + } else { + execSync(`kill -9 ${ngrokPid} 2>/dev/null || true`, { stdio: 'ignore' }) + // Also kill any remaining ngrok processes + execSync('pkill -9 -f ngrok 2>/dev/null || true', { stdio: 'ignore' }) + } + } catch (e) { + // Ignore errors if processes are already gone + } + + // Kill any process on port 3001 to ensure clean exit + try { + if (process.platform !== 'win32') { + execSync('kill $(lsof -t -i:3001 2>/dev/null) 2>/dev/null || true', { + stdio: 'ignore', + }) + } + } catch (e) { + // Ignore errors + } + + process.exit(0) +} + +// Handle various termination signals +process.on('SIGINT', killAllProcesses) // Ctrl+C +process.on('SIGTERM', killAllProcesses) // Kill command +process.on('SIGHUP', killAllProcesses) // Terminal closed diff --git a/src/auth/client.ts b/src/auth/client.ts deleted file mode 100644 index 3eb6711..0000000 --- a/src/auth/client.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { NodeOAuthClient } from '@atproto/oauth-client-node' -import type { Database } from '#/db' -import { env } from '#/lib/env' -import { SessionStore, StateStore } from './storage' - -export const createClient = async (db: Database) => { - const publicUrl = env.PUBLIC_URL - const url = publicUrl || `http://127.0.0.1:${env.PORT}` - const enc = encodeURIComponent - return new NodeOAuthClient({ - clientMetadata: { - client_name: 'AT Protocol Express App', - client_id: publicUrl - ? `${url}/client-metadata.json` - : `http://localhost?redirect_uri=${enc(`${url}/oauth/callback`)}&scope=${enc('atproto transition:generic')}`, - client_uri: url, - redirect_uris: [`${url}/oauth/callback`], - scope: 'atproto transition:generic', - grant_types: ['authorization_code', 'refresh_token'], - response_types: ['code'], - application_type: 'web', - token_endpoint_auth_method: 'none', - dpop_bound_access_tokens: true, - }, - stateStore: new StateStore(db), - sessionStore: new SessionStore(db), - }) -} diff --git a/src/lexicon/index.ts b/src/lexicon/index.ts deleted file mode 100644 index 961ce11..0000000 --- a/src/lexicon/index.ts +++ /dev/null @@ -1,129 +0,0 @@ -/** - * GENERATED CODE - DO NOT MODIFY - */ -import { - createServer as createXrpcServer, - Server as XrpcServer, - Options as XrpcOptions, - AuthVerifier, - StreamAuthVerifier, -} from '@atproto/xrpc-server' -import { schemas } from './lexicons.js' - -export function createServer(options?: XrpcOptions): Server { - return new Server(options) -} - -export class Server { - xrpc: XrpcServer - app: AppNS - xyz: XyzNS - com: ComNS - - constructor(options?: XrpcOptions) { - this.xrpc = createXrpcServer(schemas, options) - this.app = new AppNS(this) - this.xyz = new XyzNS(this) - this.com = new ComNS(this) - } -} - -export class AppNS { - _server: Server - bsky: AppBskyNS - - constructor(server: Server) { - this._server = server - this.bsky = new AppBskyNS(server) - } -} - -export class AppBskyNS { - _server: Server - actor: AppBskyActorNS - - constructor(server: Server) { - this._server = server - this.actor = new AppBskyActorNS(server) - } -} - -export class AppBskyActorNS { - _server: Server - - constructor(server: Server) { - this._server = server - } -} - -export class XyzNS { - _server: Server - statusphere: XyzStatusphereNS - - constructor(server: Server) { - this._server = server - this.statusphere = new XyzStatusphereNS(server) - } -} - -export class XyzStatusphereNS { - _server: Server - - constructor(server: Server) { - this._server = server - } -} - -export class ComNS { - _server: Server - atproto: ComAtprotoNS - - constructor(server: Server) { - this._server = server - this.atproto = new ComAtprotoNS(server) - } -} - -export class ComAtprotoNS { - _server: Server - repo: ComAtprotoRepoNS - - constructor(server: Server) { - this._server = server - this.repo = new ComAtprotoRepoNS(server) - } -} - -export class ComAtprotoRepoNS { - _server: Server - - constructor(server: Server) { - this._server = server - } -} - -type SharedRateLimitOpts = { - name: string - calcKey?: (ctx: T) => string | null - calcPoints?: (ctx: T) => number -} -type RouteRateLimitOpts = { - durationMs: number - points: number - calcKey?: (ctx: T) => string | null - calcPoints?: (ctx: T) => number -} -type HandlerOpts = { blobLimit?: number } -type HandlerRateLimitOpts = SharedRateLimitOpts | RouteRateLimitOpts -type ConfigOf = - | Handler - | { - auth?: Auth - opts?: HandlerOpts - rateLimit?: HandlerRateLimitOpts | HandlerRateLimitOpts[] - handler: Handler - } -type ExtractAuth = Extract< - Awaited>, - { credentials: unknown } -> diff --git a/src/lexicon/lexicons.ts b/src/lexicon/lexicons.ts deleted file mode 100644 index 25864cb..0000000 --- a/src/lexicon/lexicons.ts +++ /dev/null @@ -1,332 +0,0 @@ -/** - * GENERATED CODE - DO NOT MODIFY - */ -import { - LexiconDoc, - Lexicons, - ValidationError, - ValidationResult, -} from '@atproto/lexicon' -import { $Typed, is$typed, maybe$typed } from './util.js' - -export const schemaDict = { - ComAtprotoLabelDefs: { - lexicon: 1, - id: 'com.atproto.label.defs', - defs: { - label: { - type: 'object', - description: - 'Metadata tag on an atproto resource (eg, repo or record).', - required: ['src', 'uri', 'val', 'cts'], - properties: { - ver: { - type: 'integer', - description: 'The AT Protocol version of the label object.', - }, - src: { - type: 'string', - format: 'did', - description: 'DID of the actor who created this label.', - }, - uri: { - type: 'string', - format: 'uri', - description: - 'AT URI of the record, repository (account), or other resource that this label applies to.', - }, - cid: { - type: 'string', - format: 'cid', - description: - "Optionally, CID specifying the specific version of 'uri' resource this label applies to.", - }, - val: { - type: 'string', - maxLength: 128, - description: - 'The short string name of the value or type of this label.', - }, - neg: { - type: 'boolean', - description: - 'If true, this is a negation label, overwriting a previous label.', - }, - cts: { - type: 'string', - format: 'datetime', - description: 'Timestamp when this label was created.', - }, - exp: { - type: 'string', - format: 'datetime', - description: - 'Timestamp at which this label expires (no longer applies).', - }, - sig: { - type: 'bytes', - description: 'Signature of dag-cbor encoded label.', - }, - }, - }, - selfLabels: { - type: 'object', - description: - 'Metadata tags on an atproto record, published by the author within the record.', - required: ['values'], - properties: { - values: { - type: 'array', - items: { - type: 'ref', - ref: 'lex:com.atproto.label.defs#selfLabel', - }, - maxLength: 10, - }, - }, - }, - selfLabel: { - type: 'object', - description: - 'Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.', - required: ['val'], - properties: { - val: { - type: 'string', - maxLength: 128, - description: - 'The short string name of the value or type of this label.', - }, - }, - }, - labelValueDefinition: { - type: 'object', - description: - 'Declares a label value and its expected interpretations and behaviors.', - required: ['identifier', 'severity', 'blurs', 'locales'], - properties: { - identifier: { - type: 'string', - description: - "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", - maxLength: 100, - maxGraphemes: 100, - }, - severity: { - type: 'string', - description: - "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", - knownValues: ['inform', 'alert', 'none'], - }, - blurs: { - type: 'string', - 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.", - knownValues: ['content', 'media', 'none'], - }, - defaultSetting: { - type: 'string', - description: 'The default setting for this label.', - knownValues: ['ignore', 'warn', 'hide'], - default: 'warn', - }, - adultOnly: { - type: 'boolean', - description: - 'Does the user need to have adult content enabled in order to configure this label?', - }, - locales: { - type: 'array', - items: { - type: 'ref', - ref: 'lex:com.atproto.label.defs#labelValueDefinitionStrings', - }, - }, - }, - }, - labelValueDefinitionStrings: { - type: 'object', - description: - 'Strings which describe the label in the UI, localized into a specific language.', - required: ['lang', 'name', 'description'], - properties: { - lang: { - type: 'string', - description: - 'The code of the language these strings are written in.', - format: 'language', - }, - name: { - type: 'string', - description: 'A short human-readable name for the label.', - maxGraphemes: 64, - maxLength: 640, - }, - description: { - type: 'string', - description: - 'A longer description of what the label means and why it might be applied.', - maxGraphemes: 10000, - maxLength: 100000, - }, - }, - }, - labelValue: { - type: 'string', - knownValues: [ - '!hide', - '!no-promote', - '!warn', - '!no-unauthenticated', - 'dmca-violation', - 'doxxing', - 'porn', - 'sexual', - 'nudity', - 'nsfl', - 'gore', - ], - }, - }, - }, - AppBskyActorProfile: { - lexicon: 1, - id: 'app.bsky.actor.profile', - defs: { - main: { - type: 'record', - description: 'A declaration of a Bluesky account profile.', - key: 'literal:self', - record: { - type: 'object', - properties: { - displayName: { - type: 'string', - maxGraphemes: 64, - maxLength: 640, - }, - description: { - type: 'string', - description: 'Free-form profile description text.', - maxGraphemes: 256, - maxLength: 2560, - }, - avatar: { - type: 'blob', - description: - "Small image to be displayed next to posts from account. AKA, 'profile picture'", - accept: ['image/png', 'image/jpeg'], - maxSize: 1000000, - }, - banner: { - type: 'blob', - description: - 'Larger horizontal image to display behind profile view.', - accept: ['image/png', 'image/jpeg'], - maxSize: 1000000, - }, - labels: { - type: 'union', - description: - 'Self-label values, specific to the Bluesky application, on the overall account.', - refs: ['lex:com.atproto.label.defs#selfLabels'], - }, - joinedViaStarterPack: { - type: 'ref', - ref: 'lex:com.atproto.repo.strongRef', - }, - createdAt: { - type: 'string', - format: 'datetime', - }, - }, - }, - }, - }, - }, - XyzStatusphereStatus: { - lexicon: 1, - id: 'xyz.statusphere.status', - defs: { - main: { - type: 'record', - key: 'tid', - record: { - type: 'object', - required: ['status', 'createdAt'], - properties: { - status: { - type: 'string', - minLength: 1, - maxGraphemes: 1, - maxLength: 32, - }, - createdAt: { - type: 'string', - format: 'datetime', - }, - }, - }, - }, - }, - }, - ComAtprotoRepoStrongRef: { - lexicon: 1, - id: 'com.atproto.repo.strongRef', - description: 'A URI with a content-hash fingerprint.', - defs: { - main: { - type: 'object', - required: ['uri', 'cid'], - properties: { - uri: { - type: 'string', - format: 'at-uri', - }, - cid: { - type: 'string', - format: 'cid', - }, - }, - }, - }, - }, -} as const satisfies Record - -export const schemas = Object.values(schemaDict) satisfies LexiconDoc[] -export const lexicons: Lexicons = new Lexicons(schemas) - -export function validate( - v: unknown, - id: string, - hash: string, - requiredType: true, -): ValidationResult -export function validate( - v: unknown, - id: string, - hash: string, - requiredType?: false, -): ValidationResult -export function validate( - v: unknown, - id: string, - hash: string, - requiredType?: boolean, -): ValidationResult { - return (requiredType ? is$typed : maybe$typed)(v, id, hash) - ? lexicons.validate(`${id}#${hash}`, v) - : { - success: false, - error: new ValidationError( - `Must be an object with "${hash === 'main' ? id : `${id}#${hash}`}" $type property`, - ), - } -} - -export const ids = { - ComAtprotoLabelDefs: 'com.atproto.label.defs', - AppBskyActorProfile: 'app.bsky.actor.profile', - XyzStatusphereStatus: 'xyz.statusphere.status', - ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef', -} as const diff --git a/src/lib/view.ts b/src/lib/view.ts deleted file mode 100644 index 9d7b27a..0000000 --- a/src/lib/view.ts +++ /dev/null @@ -1,12 +0,0 @@ -// @ts-ignore -import ssr from 'uhtml/ssr' -import type initSSR from 'uhtml/types/init-ssr' -import type { Hole } from 'uhtml/types/keyed' - -export type { Hole } - -export const { html }: ReturnType = ssr() - -export function page(hole: Hole) { - return `\n${hole.toDOM().toString()}` -} diff --git a/src/pages/home.ts b/src/pages/home.ts deleted file mode 100644 index 80611ea..0000000 --- a/src/pages/home.ts +++ /dev/null @@ -1,121 +0,0 @@ -import type { Status } from '#/db' -import { html } from '../lib/view' -import { shell } from './shell' - -const TODAY = new Date().toDateString() - -const STATUS_OPTIONS = [ - '๐Ÿ‘', - '๐Ÿ‘Ž', - '๐Ÿ’™', - '๐Ÿฅน', - '๐Ÿ˜ง', - '๐Ÿ˜ค', - '๐Ÿ™ƒ', - '๐Ÿ˜‰', - '๐Ÿ˜Ž', - '๐Ÿค“', - '๐Ÿคจ', - '๐Ÿฅณ', - '๐Ÿ˜ญ', - '๐Ÿ˜ค', - '๐Ÿคฏ', - '๐Ÿซก', - '๐Ÿ’€', - 'โœŠ', - '๐Ÿค˜', - '๐Ÿ‘€', - '๐Ÿง ', - '๐Ÿ‘ฉโ€๐Ÿ’ป', - '๐Ÿง‘โ€๐Ÿ’ป', - '๐Ÿฅท', - '๐ŸงŒ', - '๐Ÿฆ‹', - '๐Ÿš€', -] - -type Props = { - statuses: Status[] - didHandleMap: Record - profile?: { displayName?: string } - myStatus?: Status -} - -export function home(props: Props) { - return shell({ - title: 'Home', - content: content(props), - }) -} - -function content({ statuses, didHandleMap, profile, myStatus }: Props) { - return html`
-
- -
-
- ${profile - ? html`
-
- Hi, ${profile.displayName || 'friend'}. What's - your status today? -
-
- -
-
` - : html`
-
Log in to set your status!
-
- Log in -
-
`} -
-
- ${STATUS_OPTIONS.map( - (status) => - html``, - )} -
- ${statuses.map((status, i) => { - const handle = didHandleMap[status.authorDid] || status.authorDid - const date = ts(status) - return html` -
-
-
${status.status}
-
-
- @${handle} - ${date === TODAY - ? `is feeling ${status.status} today` - : `was feeling ${status.status} on ${date}`} -
-
- ` - })} -
-
` -} - -function toBskyLink(did: string) { - return `https://bsky.app/profile/${did}` -} - -function ts(status: Status) { - const createdAt = new Date(status.createdAt) - const indexedAt = new Date(status.indexedAt) - if (createdAt < indexedAt) return createdAt.toDateString() - return indexedAt.toDateString() -} diff --git a/src/pages/login.ts b/src/pages/login.ts deleted file mode 100644 index 55cb166..0000000 --- a/src/pages/login.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { html } from '../lib/view' -import { shell } from './shell' - -type Props = { error?: string } - -export function login(props: Props) { - return shell({ - title: 'Log in', - content: content(props), - }) -} - -function content({ error }: Props) { - return html`
- -
- - -
-
` -} diff --git a/src/pages/public/styles.css b/src/pages/public/styles.css deleted file mode 100644 index 24e52e3..0000000 --- a/src/pages/public/styles.css +++ /dev/null @@ -1,230 +0,0 @@ -body { - font-family: Arial, Helvetica, sans-serif; - - --border-color: #ddd; - --gray-100: #fafafa; - --gray-500: #666; - --gray-700: #333; - --primary-100: #d2e7ff; - --primary-200: #b1d3fa; - --primary-400: #2e8fff; - --primary-500: #0078ff; - --primary-600: #0066db; - --error-500: #f00; - --error-100: #fee; -} - -/* - Josh's Custom CSS Reset - https://www.joshwcomeau.com/css/custom-css-reset/ -*/ -*, -*::before, -*::after { - box-sizing: border-box; -} -* { - margin: 0; -} -body { - line-height: 1.5; - -webkit-font-smoothing: antialiased; -} -img, -picture, -video, -canvas, -svg { - display: block; - max-width: 100%; -} -input, -button, -textarea, -select { - font: inherit; -} -p, -h1, -h2, -h3, -h4, -h5, -h6 { - overflow-wrap: break-word; -} -#root, -#__next { - isolation: isolate; -} - -/* - Common components -*/ -button, -.button { - display: inline-block; - border: 0; - background-color: var(--primary-500); - border-radius: 50px; - color: #fff; - padding: 2px 10px; - cursor: pointer; - text-decoration: none; -} -button:hover, -.button:hover { - background: var(--primary-400); -} - -/* - Custom components -*/ -.error { - background-color: var(--error-100); - color: var(--error-500); - text-align: center; - padding: 1rem; - display: none; -} -.error.visible { - display: block; -} - -#header { - background-color: #fff; - text-align: center; - padding: 0.5rem 0 1.5rem; -} - -#header h1 { - font-size: 5rem; -} - -.container { - display: flex; - flex-direction: column; - gap: 4px; - margin: 0 auto; - max-width: 600px; - padding: 20px; -} - -.card { - /* border: 1px solid var(--border-color); */ - border-radius: 6px; - padding: 10px 16px; - background-color: #fff; -} -.card > :first-child { - margin-top: 0; -} -.card > :last-child { - margin-bottom: 0; -} - -.session-form { - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; -} - -.login-form { - display: flex; - flex-direction: row; - gap: 6px; - border: 1px solid var(--border-color); - border-radius: 6px; - padding: 10px 16px; - background-color: #fff; -} - -.login-form input { - flex: 1; - border: 0; -} - -.status-options { - display: flex; - flex-direction: row; - flex-wrap: wrap; - gap: 8px; - margin: 10px 0; -} - -.status-option { - font-size: 2rem; - width: 3rem; - height: 3rem; - padding: 0; - background-color: #fff; - border: 1px solid var(--border-color); - border-radius: 3rem; - text-align: center; - box-shadow: 0 1px 4px #0001; - cursor: pointer; -} - -.status-option:hover { - background-color: var(--primary-100); - box-shadow: 0 0 0 1px var(--primary-400); -} - -.status-option.selected { - box-shadow: 0 0 0 1px var(--primary-500); - background-color: var(--primary-100); -} - -.status-option.selected:hover { - background-color: var(--primary-200); -} - -.status-line { - display: flex; - flex-direction: row; - align-items: center; - gap: 10px; - position: relative; - margin-top: 15px; -} - -.status-line:not(.no-line)::before { - content: ''; - position: absolute; - width: 2px; - background-color: var(--border-color); - left: 1.45rem; - bottom: calc(100% + 2px); - height: 15px; -} - -.status-line .status { - font-size: 2rem; - background-color: #fff; - width: 3rem; - height: 3rem; - border-radius: 1.5rem; - text-align: center; - border: 1px solid var(--border-color); -} - -.status-line .desc { - color: var(--gray-500); -} - -.status-line .author { - color: var(--gray-700); - font-weight: 600; - text-decoration: none; -} - -.status-line .author:hover { - text-decoration: underline; -} - -.signup-cta { - text-align: center; - text-wrap: balance; - margin-top: 1rem; -} diff --git a/src/pages/shell.ts b/src/pages/shell.ts deleted file mode 100644 index ddb94aa..0000000 --- a/src/pages/shell.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { type Hole, html } from '../lib/view' - -export function shell({ title, content }: { title: string; content: Hole }) { - return html` - - ${title} - - - - ${content} - - ` -} diff --git a/src/routes.ts b/src/routes.ts deleted file mode 100644 index b918dc0..0000000 --- a/src/routes.ts +++ /dev/null @@ -1,288 +0,0 @@ -import assert from 'node:assert' -import path from 'node:path' -import type { IncomingMessage, ServerResponse } from 'node:http' -import { OAuthResolverError } from '@atproto/oauth-client-node' -import { isValidHandle } from '@atproto/syntax' -import { TID } from '@atproto/common' -import { Agent } from '@atproto/api' -import express from 'express' -import { getIronSession } from 'iron-session' -import type { AppContext } from '#/index' -import { home } from '#/pages/home' -import { login } from '#/pages/login' -import { env } from '#/lib/env' -import { page } from '#/lib/view' -import * as Status from '#/lexicon/types/xyz/statusphere/status' -import * as Profile from '#/lexicon/types/app/bsky/actor/profile' - -type Session = { did: string } - -// Helper function for defining routes -const handler = - (fn: (req: express.Request, res: express.Response, next: express.NextFunction) => Promise | void) => - async ( - req: express.Request, - res: express.Response, - next: express.NextFunction, - ) => { - try { - await fn(req, res, next) - } catch (err) { - next(err) - } - } - -// Helper function to get the Atproto Agent for the active session -async function getSessionAgent( - req: IncomingMessage, - res: ServerResponse, - ctx: AppContext, -) { - const session = await getIronSession(req, res, { - cookieName: 'sid', - password: env.COOKIE_SECRET, - }) - if (!session.did) return null - try { - const oauthSession = await ctx.oauthClient.restore(session.did) - return oauthSession ? new Agent(oauthSession) : null - } catch (err) { - ctx.logger.warn({ err }, 'oauth restore failed') - await session.destroy() - return null - } -} - -export const createRouter = (ctx: AppContext) => { - const router = express.Router() - - // Static assets - router.use('/public', express.static(path.join(__dirname, 'pages', 'public'))) - - // OAuth metadata - router.get( - '/client-metadata.json', - handler((_req, res) => { - res.json(ctx.oauthClient.clientMetadata) - }), - ) - - // OAuth callback to complete session creation - router.get( - '/oauth/callback', - handler(async (req, res) => { - const params = new URLSearchParams(req.originalUrl.split('?')[1]) - try { - const { session } = await ctx.oauthClient.callback(params) - const clientSession = await getIronSession(req, res, { - cookieName: 'sid', - password: env.COOKIE_SECRET, - }) - assert(!clientSession.did, 'session already exists') - clientSession.did = session.did - await clientSession.save() - res.redirect('/') - } catch (err) { - ctx.logger.error({ err }, 'oauth callback failed') - res.redirect('/?error') - } - }), - ) - - // Login page - router.get( - '/login', - handler(async (_req, res) => { - res.type('html').send(page(login({}))) - }), - ) - - // Login handler - router.post( - '/login', - handler(async (req, res) => { - // Validate - const handle = req.body?.handle - if (typeof handle !== 'string' || !isValidHandle(handle)) { - res.type('html').send(page(login({ error: 'invalid handle' }))) - return - } - - // Initiate the OAuth flow - try { - const url = await ctx.oauthClient.authorize(handle, { - scope: 'atproto transition:generic', - }) - res.redirect(url.toString()) - } catch (err) { - ctx.logger.error({ err }, 'oauth authorize failed') - res.type('html').send( - page( - login({ - error: - err instanceof OAuthResolverError - ? err.message - : "couldn't initiate login", - }), - ), - ) - } - }), - ) - - // Logout handler - router.post( - '/logout', - handler(async (req, res) => { - const session = await getIronSession(req, res, { - cookieName: 'sid', - password: env.COOKIE_SECRET, - }) - await session.destroy() - res.redirect('/') - }), - ) - - // Homepage - router.get( - '/', - handler(async (req, res) => { - // If the user is signed in, get an agent which communicates with their server - const agent = await getSessionAgent(req, res, ctx) - - // Fetch data stored in our SQLite - const statuses = await ctx.db - .selectFrom('status') - .selectAll() - .orderBy('indexedAt', 'desc') - .limit(10) - .execute() - const myStatus = agent - ? await ctx.db - .selectFrom('status') - .selectAll() - .where('authorDid', '=', agent.assertDid) - .orderBy('indexedAt', 'desc') - .executeTakeFirst() - : undefined - - // Map user DIDs to their domain-name handles - const didHandleMap = await ctx.resolver.resolveDidsToHandles( - statuses.map((s) => s.authorDid), - ) - - if (!agent) { - // Serve the logged-out view - res.type('html').send(page(home({ statuses, didHandleMap }))) - return - } - - // Fetch additional information about the logged-in user - const profileResponse = await agent.com.atproto.repo - .getRecord({ - repo: agent.assertDid, - collection: 'app.bsky.actor.profile', - rkey: 'self', - }) - .catch(() => undefined) - - const profileRecord = profileResponse?.data - - const profile = - profileRecord && - Profile.isRecord(profileRecord.value) && - Profile.validateRecord(profileRecord.value).success - ? profileRecord.value - : {} - - // Serve the logged-in view - res.type('html').send( - page( - home({ - statuses, - didHandleMap, - profile, - myStatus, - }), - ), - ) - }), - ) - - // "Set status" handler - router.post( - '/status', - handler(async (req, res) => { - // If the user is signed in, get an agent which communicates with their server - const agent = await getSessionAgent(req, res, ctx) - if (!agent) { - res - .status(401) - .type('html') - .send('

Error: Session required

') - return - } - - // Construct & validate their status record - const rkey = TID.nextStr() - const record = { - $type: 'xyz.statusphere.status', - status: req.body?.status, - createdAt: new Date().toISOString(), - } - if (!Status.validateRecord(record).success) { - res - .status(400) - .type('html') - .send('

Error: Invalid status

') - return - } - - let uri - try { - // Write the status record to the user's repository - const response = await agent.com.atproto.repo.putRecord({ - repo: agent.assertDid, - collection: 'xyz.statusphere.status', - rkey, - record, - validate: false, - }) - uri = response.data.uri - } catch (err) { - ctx.logger.warn({ err }, 'failed to write record') - res - .status(500) - .type('html') - .send('

Error: Failed to write record

') - return - } - - try { - // Optimistically update our SQLite - // This isn't strictly necessary because the write event will be - // handled in #/firehose/ingestor.ts, but it ensures that future reads - // will be up-to-date after this method finishes. - await ctx.db - .insertInto('status') - .values({ - uri, - authorDid: agent.assertDid, - status: record.status, - createdAt: record.createdAt, - indexedAt: new Date().toISOString(), - }) - .execute() - } catch (err) { - ctx.logger.warn( - { err }, - 'failed to update computed view; ignoring as it should be caught by the firehose', - ) - } - - res.redirect('/') - }), - ) - - return router -} diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 3fd9b94..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "module": "CommonJS", - "baseUrl": ".", - "paths": { - "#/*": ["src/*"] - }, - "moduleResolution": "Node10", - "outDir": "dist", - "importsNotUsedAsValues": "remove", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules"] -} -- 2.51.2