Scratch space for learning atproto app development

Compare changes

Choose any two refs to compare.

+12 -7
.env.template
···
# 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.
+
NODE_ENV="development" # Options: 'development', 'production'
+
PORT="8080" # The port your server will listen on
+
DB_PATH=":memory:" # The SQLite database path. Set as ":memory:" to use a temporary in-memory database.
+
# PUBLIC_URL="" # Set when deployed publicly, e.g. "https://mysite.com". Informs OAuth client id.
+
# LOG_LEVEL="info" # Options: 'fatal', 'error', 'warn', 'info', 'debug'
+
# PDS_URL="https://my.pds" # The default PDS for login and sign-ups
+
+
# Secrets below *MUST* be set in production
-
# Secrets
-
# Must set this in production. May be generated with `openssl rand -base64 33`
+
# May be generated with `openssl rand -base64 33`
# COOKIE_SECRET=""
+
+
# May be generated with `./bin/gen-jwk` (requires `npm install` once first)
+
# PRIVATE_KEYS='[{"kty":"EC","kid":"123",...}]'
+1
.gitignore
···
dist-ssr
*.local
.env
+
*.sqlite
# Editor directories and files
!.vscode/extensions.json
+7
.prettierrc
···
+
{
+
"trailingComma": "all",
+
"tabWidth": 2,
+
"semi": false,
+
"singleQuote": true,
+
"useTabs": false
+
}
+1 -1
.vscode/settings.json
···
{
"editor.formatOnSave": true,
-
"editor.defaultFormatter": "biomejs.biome",
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"quickfix.biome": "explicit",
"source.organizeImports.biome": "explicit",
+21
LICENSE
···
+
MIT License
+
+
Copyright (c) 2025 Bluesky PBC, and Contributors
+
+
Permission is hereby granted, free of charge, to any person obtaining a copy
+
of this software and associated documentation files (the "Software"), to deal
+
in the Software without restriction, including without limitation the rights
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+
copies of the Software, and to permit persons to whom the Software is
+
furnished to do so, subject to the following conditions:
+
+
The above copyright notice and this permission notice shall be included in all
+
copies or substantial portions of the Software.
+
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+
SOFTWARE.
+56 -8
README.md
···
-
# AT Protocol Express App
+
# AT Protocol "Statusphere" Example App
-
A demo application covering:
-
- public firehose ingestion
-
- identity and login with OAuth
-
- writing to the network
+
An example application 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.
## Getting Started
-
### Development
+
```sh
-
pnpm i
+
git clone https://github.com/bluesky-social/statusphere-example-app.git
+
cd statusphere-example-app
cp .env.template .env
-
pnpm run dev
+
npm install
+
npm run dev
# Navigate to http://localhost:8080
```
+
+
## Deploying
+
+
In production, you will need a private key to sign OAuth tokens request. Use the
+
following command to generate a new private key:
+
+
```sh
+
./bin/gen-jwk
+
```
+
+
The generated key must be added to the environment variables (`.env` file) in `PRIVATE_KEYS`.
+
+
```env
+
PRIVATE_KEYS='[{"kty":"EC","kid":"12",...}]'
+
```
+
+
> [!NOTE]
+
>
+
> The `PRIVATE_KEYS` can contain multiple keys. The first key in the array is
+
> the most recent one, and it will be used to sign new tokens. When a key is
+
> removed, all associated sessions will be invalidated.
+
+
Make sure to also set the `COOKIE_SECRET`, which is used to sign session
+
cookies, in your environment variables (`.env` file). You should use a random
+
string for this:
+
+
```sh
+
openssl rand -base64 33
+
```
+
+
Finally, set the `PUBLIC_URL` to the URL where your app will be accessible. This
+
will allow the authorization servers to download the app's public keys.
+
+
```env
+
PUBLIC_URL="https://your-app-url.com"
+
```
+
+
> [!NOTE]
+
>
+
> You can use services like [ngrok](https://ngrok.com/) to expose your local
+
> server to the internet for testing purposes. Just set the `PUBLIC_URL` to the
+
> ngrok URL.
+15
bin/gen-jwk
···
+
#!/usr/bin/env node
+
+
'use strict'
+
+
const { JoseKey } = require('@atproto/oauth-client-node')
+
+
async function main() {
+
const kid = Date.now().toString()
+
const key = await JoseKey.generate(['ES256'], kid)
+
const jwk = key.privateJwk
+
+
console.log(JSON.stringify(jwk))
+
}
+
+
main()
+156
lexicons/defs.json
···
+
{
+
"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"
+
]
+
}
+
}
+
}
+49
lexicons/profile.json
···
+
{
+
"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": ["com.atproto.label.defs#selfLabels"]
+
},
+
"joinedViaStarterPack": {
+
"type": "ref",
+
"ref": "com.atproto.repo.strongRef"
+
},
+
"createdAt": { "type": "string", "format": "datetime" }
+
}
+
}
+
}
+
}
+
}
+4 -4
lexicons/status.json
···
{
"lexicon": 1,
-
"id": "com.example.status",
+
"id": "xyz.statusphere.status",
"defs": {
"main": {
"type": "record",
-
"key": "literal:self",
+
"key": "tid",
"record": {
"type": "object",
-
"required": ["status", "updatedAt"],
+
"required": ["status", "createdAt"],
"properties": {
"status": {
"type": "string",
···
"maxGraphemes": 1,
"maxLength": 32
},
-
"updatedAt": { "type": "string", "format": "datetime" }
+
"createdAt": { "type": "string", "format": "datetime" }
}
}
}
+15
lexicons/strongRef.json
···
+
{
+
"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" }
+
}
+
}
+
}
+
}
+1235 -691
package-lock.json
···
"version": "0.0.1",
"license": "MIT",
"dependencies": {
-
"@atproto/identity": "^0.4.0",
-
"@atproto/lexicon": "0.4.1-rc.0",
-
"@atproto/oauth-client-node": "0.0.2-rc.2",
-
"@atproto/repo": "0.4.2-rc.0",
-
"@atproto/sync": "^0.1.0",
-
"@atproto/syntax": "^0.3.0",
-
"@atproto/xrpc-server": "0.5.4-rc.0",
+
"@atproto/api": "^0.15.6",
+
"@atproto/common": "^0.4.11",
+
"@atproto/identity": "^0.4.8",
+
"@atproto/lexicon": "^0.4.11",
+
"@atproto/oauth-client-node": "^0.3.1",
+
"@atproto/sync": "^0.1.26",
+
"@atproto/syntax": "^0.4.0",
+
"@atproto/xrpc-server": "^0.8.0",
"better-sqlite3": "^11.1.2",
"dotenv": "^16.4.5",
"envalid": "^8.0.0",
"express": "^4.19.2",
+
"http-terminator": "^3.2.0",
"iron-session": "^8.0.2",
"kysely": "^0.27.4",
"multiformats": "^9.9.0",
"pino": "^9.3.2",
-
"uhtml": "^4.5.9"
+
"uhtml": "^4.5.9",
+
"zod": "^3.25.67"
},
"devDependencies": {
"@atproto/lex-cli": "^0.4.1",
···
}
},
"node_modules/@atproto-labs/did-resolver": {
-
"version": "0.1.2-rc.0",
-
"resolved": "https://registry.npmjs.org/@atproto-labs/did-resolver/-/did-resolver-0.1.2-rc.0.tgz",
-
"integrity": "sha512-5lVxhLG9P1G1XjGXQr7fhk6mBM5vpbCalrfuVXqU5xQADvObLjEtpxpJuLheAacaV2pUMFDml+53ZLYWXCgFIg==",
+
"version": "0.2.0",
+
"resolved": "https://registry.npmjs.org/@atproto-labs/did-resolver/-/did-resolver-0.2.0.tgz",
+
"integrity": "sha512-y9GOx2gUETynDKmANnBrU5DTf+u0AwKBJpGns1vDDOYMdLdRCFIeYy3UH+TI8YOkcEazjgF5Q3m+LjwriE1KqQ==",
+
"license": "MIT",
"dependencies": {
-
"@atproto-labs/fetch": "0.1.0",
-
"@atproto-labs/pipe": "0.1.0",
-
"@atproto-labs/simple-store": "0.1.1",
-
"@atproto-labs/simple-store-memory": "0.1.1",
-
"@atproto/did": "0.1.1-rc.0",
+
"@atproto-labs/fetch": "0.2.3",
+
"@atproto-labs/pipe": "0.1.1",
+
"@atproto-labs/simple-store": "0.2.0",
+
"@atproto-labs/simple-store-memory": "0.1.3",
+
"@atproto/did": "0.1.5",
"zod": "^3.23.8"
}
},
"node_modules/@atproto-labs/fetch": {
-
"version": "0.1.0",
-
"resolved": "https://registry.npmjs.org/@atproto-labs/fetch/-/fetch-0.1.0.tgz",
-
"integrity": "sha512-uirja+uA/C4HNk7vayM+AJqsccxQn2wVziUHxbsjJGt/K6Q8ZOKDaEX2+GrcXvpUVcqUKh+94JFjuzH+CAEUlg==",
+
"version": "0.2.3",
+
"resolved": "https://registry.npmjs.org/@atproto-labs/fetch/-/fetch-0.2.3.tgz",
+
"integrity": "sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw==",
+
"license": "MIT",
"dependencies": {
-
"@atproto-labs/pipe": "0.1.0"
-
},
-
"optionalDependencies": {
-
"zod": "^3.23.8"
+
"@atproto-labs/pipe": "0.1.1"
}
},
"node_modules/@atproto-labs/fetch-node": {
-
"version": "0.1.0",
-
"resolved": "https://registry.npmjs.org/@atproto-labs/fetch-node/-/fetch-node-0.1.0.tgz",
-
"integrity": "sha512-DUHgaGw8LBqiGg51pUDuWK/alMcmNbpcK7ALzlF2Gw//TNLTsgrj0qY9aEtK+np9rEC+x/o3bN4SGnuQEpgqIg==",
+
"version": "0.1.9",
+
"resolved": "https://registry.npmjs.org/@atproto-labs/fetch-node/-/fetch-node-0.1.9.tgz",
+
"integrity": "sha512-8sHDDXZEzQptLu8ddUU/8U+THS6dumgPynVX0/1PjUYd4S/FWyPcz6yMIiVChTfzKnZvYRRz47+qvOKhydrHQw==",
+
"license": "MIT",
"dependencies": {
-
"@atproto-labs/fetch": "0.1.0",
-
"@atproto-labs/pipe": "0.1.0",
+
"@atproto-labs/fetch": "0.2.3",
+
"@atproto-labs/pipe": "0.1.1",
"ipaddr.js": "^2.1.0",
-
"psl": "^1.9.0",
"undici": "^6.14.1"
+
},
+
"engines": {
+
"node": ">=18.7.0"
+
}
+
},
+
"node_modules/@atproto-labs/fetch-node/node_modules/ipaddr.js": {
+
"version": "2.2.0",
+
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
+
"integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==",
+
"license": "MIT",
+
"engines": {
+
"node": ">= 10"
}
},
"node_modules/@atproto-labs/handle-resolver": {
-
"version": "0.1.2-rc.0",
-
"resolved": "https://registry.npmjs.org/@atproto-labs/handle-resolver/-/handle-resolver-0.1.2-rc.0.tgz",
-
"integrity": "sha512-sxk/Zr1hWyBBcg1HhZ8N/Tw1Iue/6+V6bzu2c8zYhO9VfKgCBp3FFU1/i3MpgR2AlsEqZpcjv6zj4KAnMHiLUg==",
+
"version": "0.3.0",
+
"resolved": "https://registry.npmjs.org/@atproto-labs/handle-resolver/-/handle-resolver-0.3.0.tgz",
+
"integrity": "sha512-TREelvXB6P2eHxx6QjINRkBzUZu/aXWrdY9iN57shQe3C8rzsHNEHHuTVvRa33Hc7vFdQbZN0TnCgKveoyiL/A==",
+
"license": "MIT",
"dependencies": {
-
"@atproto-labs/simple-store": "0.1.1",
-
"@atproto-labs/simple-store-memory": "0.1.1",
-
"@atproto/did": "0.1.1-rc.0",
+
"@atproto-labs/simple-store": "0.2.0",
+
"@atproto-labs/simple-store-memory": "0.1.3",
+
"@atproto/did": "0.1.5",
"zod": "^3.23.8"
}
},
"node_modules/@atproto-labs/handle-resolver-node": {
-
"version": "0.1.2-rc.0",
-
"resolved": "https://registry.npmjs.org/@atproto-labs/handle-resolver-node/-/handle-resolver-node-0.1.2-rc.0.tgz",
-
"integrity": "sha512-wP1c0fqxdhnIQVxFgD3Z6fiToq1ri9ECTCSPoy/1zbNJ+KWrr0V6BSONF/I5MytEbQaICBh8bvZuurvX0OjbNw==",
+
"version": "0.1.18",
+
"resolved": "https://registry.npmjs.org/@atproto-labs/handle-resolver-node/-/handle-resolver-node-0.1.18.tgz",
+
"integrity": "sha512-/qo14c3I+kagT1UWSp3lTIzwDetfkxvF3Y3VlX2NyQ2jHwgtIAJ81KFNqe7t82NpQDjWiM5h4bdjvdbFIh5djQ==",
+
"license": "MIT",
"dependencies": {
-
"@atproto-labs/fetch-node": "0.1.0",
-
"@atproto-labs/handle-resolver": "0.1.2-rc.0",
-
"@atproto/did": "0.1.1-rc.0"
+
"@atproto-labs/fetch-node": "0.1.9",
+
"@atproto-labs/handle-resolver": "0.3.0",
+
"@atproto/did": "0.1.5"
+
},
+
"engines": {
+
"node": ">=18.7.0"
}
},
"node_modules/@atproto-labs/identity-resolver": {
-
"version": "0.1.2-rc.0",
-
"resolved": "https://registry.npmjs.org/@atproto-labs/identity-resolver/-/identity-resolver-0.1.2-rc.0.tgz",
-
"integrity": "sha512-4TLjNRbufeGduac3c/No4teJ411qNgyBQck7eY5e2K8XrzS2a/xX/bq3JP91DrvERHiP3yE22PB6ATQkuALgXA==",
+
"version": "0.2.0",
+
"resolved": "https://registry.npmjs.org/@atproto-labs/identity-resolver/-/identity-resolver-0.2.0.tgz",
+
"integrity": "sha512-X4UpU9qSgbuBVRXw0kpYqdVRtjNGezmaetyQIwWHNdUl1+ILu4GhinSk1MBXamzgg/07/BVCU0r4LRIPg2Wiow==",
+
"license": "MIT",
"dependencies": {
-
"@atproto-labs/did-resolver": "0.1.2-rc.0",
-
"@atproto-labs/handle-resolver": "0.1.2-rc.0",
-
"@atproto/syntax": "0.3.0"
+
"@atproto-labs/did-resolver": "0.2.0",
+
"@atproto-labs/handle-resolver": "0.3.0"
}
},
"node_modules/@atproto-labs/pipe": {
-
"version": "0.1.0",
-
"resolved": "https://registry.npmjs.org/@atproto-labs/pipe/-/pipe-0.1.0.tgz",
-
"integrity": "sha512-ghOqHFyJlQVFPESzlVHjKroP0tPzbmG5Jms0dNI9yLDEfL8xp4OFPWLX4f6T8mRq69wWs4nIDM3sSsFbFqLa1w=="
+
"version": "0.1.1",
+
"resolved": "https://registry.npmjs.org/@atproto-labs/pipe/-/pipe-0.1.1.tgz",
+
"integrity": "sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg==",
+
"license": "MIT"
},
"node_modules/@atproto-labs/simple-store": {
-
"version": "0.1.1",
-
"resolved": "https://registry.npmjs.org/@atproto-labs/simple-store/-/simple-store-0.1.1.tgz",
-
"integrity": "sha512-WKILW2b3QbAYKh+w5U2x6p5FqqLl0nAeLwGeDY+KjX01K4Dq3vQTR9b/qNp0jZm48CabPQVrqCv0PPU9LgRRRg=="
+
"version": "0.2.0",
+
"resolved": "https://registry.npmjs.org/@atproto-labs/simple-store/-/simple-store-0.2.0.tgz",
+
"integrity": "sha512-0bRbAlI8Ayh03wRwncAMEAyUKtZ+AuTS1jgPrfym1WVOAOiottI/ZmgccqLl6w5MbxVcClNQF7WYGKvGwGoIhA==",
+
"license": "MIT"
},
"node_modules/@atproto-labs/simple-store-memory": {
-
"version": "0.1.1",
-
"resolved": "https://registry.npmjs.org/@atproto-labs/simple-store-memory/-/simple-store-memory-0.1.1.tgz",
-
"integrity": "sha512-PCRqhnZ8NBNBvLku53O56T0lsVOtclfIrQU/rwLCc4+p45/SBPrRYNBi6YFq5rxZbK6Njos9MCmILV/KLQxrWA==",
+
"version": "0.1.3",
+
"resolved": "https://registry.npmjs.org/@atproto-labs/simple-store-memory/-/simple-store-memory-0.1.3.tgz",
+
"integrity": "sha512-jkitT9+AtU+0b28DoN92iURLaCt/q/q4yX8q6V+9LSwYlUTqKoj/5NFKvF7x6EBuG+gpUdlcycbH7e60gjOhRQ==",
+
"license": "MIT",
"dependencies": {
-
"@atproto-labs/simple-store": "0.1.1",
+
"@atproto-labs/simple-store": "0.2.0",
"lru-cache": "^10.2.0"
}
},
"node_modules/@atproto/api": {
-
"version": "0.13.0-rc.1",
-
"resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.13.0-rc.1.tgz",
-
"integrity": "sha512-h2+M6OoMLnNzqf2KDxsbRkg3/1k2IMWH33PQI31GkiQHIdt3B+MIXvJwXePu0KnMUL/Lvv2Zk01BKiDnjd4LEw==",
+
"version": "0.15.16",
+
"resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.15.16.tgz",
+
"integrity": "sha512-ZNBrzBg2l0lHreKik1lJn8lrhAktwlY8NUPBU/hO9dwjAnDHQTiSzNFZt65dp9djmqZ75sX/VJ+heNuaJBvnhQ==",
+
"license": "MIT",
"dependencies": {
-
"@atproto/common-web": "^0.3.0",
-
"@atproto/lexicon": "^0.4.1-rc.0",
-
"@atproto/syntax": "^0.3.0",
-
"@atproto/xrpc": "^0.6.0-rc.0",
+
"@atproto/common-web": "^0.4.2",
+
"@atproto/lexicon": "^0.4.11",
+
"@atproto/syntax": "^0.4.0",
+
"@atproto/xrpc": "^0.7.0",
"await-lock": "^2.2.2",
"multiformats": "^9.9.0",
-
"tlds": "^1.234.0"
+
"tlds": "^1.234.0",
+
"zod": "^3.23.8"
}
},
"node_modules/@atproto/common": {
-
"version": "0.4.1",
-
"resolved": "https://registry.npmjs.org/@atproto/common/-/common-0.4.1.tgz",
-
"integrity": "sha512-uL7kQIcBTbvkBDNfxMXL6lBH4fO2DQpHd2BryJxMtbw/4iEPKe9xBYApwECHhEIk9+zhhpTRZ15FJ3gxTXN82Q==",
+
"version": "0.4.11",
+
"resolved": "https://registry.npmjs.org/@atproto/common/-/common-0.4.11.tgz",
+
"integrity": "sha512-Knv0viYXNMfCdIE7jLUiWJKnnMfEwg+vz2epJQi8WOjqtqCFb3W/3Jn72ZiuovIfpdm13MaOiny6w2NErUQC6g==",
+
"license": "MIT",
"dependencies": {
-
"@atproto/common-web": "^0.3.0",
+
"@atproto/common-web": "^0.4.2",
"@ipld/dag-cbor": "^7.0.3",
"cbor-x": "^1.5.1",
"iso-datestring-validator": "^2.2.2",
"multiformats": "^9.9.0",
"pino": "^8.21.0"
+
},
+
"engines": {
+
"node": ">=18.7.0"
}
},
"node_modules/@atproto/common-web": {
-
"version": "0.3.0",
-
"resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.3.0.tgz",
-
"integrity": "sha512-67VnV6JJyX+ZWyjV7xFQMypAgDmjVaR9ZCuU/QW+mqlqI7fex2uL4Fv+7/jHadgzhuJHVd6OHOvNn0wR5WZYtA==",
+
"version": "0.4.2",
+
"resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.2.tgz",
+
"integrity": "sha512-vrXwGNoFGogodjQvJDxAeP3QbGtawgZute2ed1XdRO0wMixLk3qewtikZm06H259QDJVu6voKC5mubml+WgQUw==",
+
"license": "MIT",
"dependencies": {
"graphemer": "^1.4.0",
"multiformats": "^9.9.0",
"uint8arrays": "3.0.0",
-
"zod": "^3.21.4"
+
"zod": "^3.23.8"
}
},
"node_modules/@atproto/common/node_modules/pino": {
···
}
},
"node_modules/@atproto/crypto": {
-
"version": "0.4.1",
-
"resolved": "https://registry.npmjs.org/@atproto/crypto/-/crypto-0.4.1.tgz",
-
"integrity": "sha512-7pQNHWYyx8jGhYdPbmcuPD9W73nd/5v3mfBlncO0sBzxnPbmA6aXAWOz+fNVZwHwBJPeb/Gzf/FT/uDx7/eYFg==",
+
"version": "0.4.4",
+
"resolved": "https://registry.npmjs.org/@atproto/crypto/-/crypto-0.4.4.tgz",
+
"integrity": "sha512-Yq9+crJ7WQl7sxStVpHgie5Z51R05etaK9DLWYG/7bR5T4bhdcIgF6IfklLShtZwLYdVVj+K15s0BqW9a8PSDA==",
+
"license": "MIT",
"dependencies": {
-
"@noble/curves": "^1.1.0",
-
"@noble/hashes": "^1.3.1",
+
"@noble/curves": "^1.7.0",
+
"@noble/hashes": "^1.6.1",
"uint8arrays": "3.0.0"
+
},
+
"engines": {
+
"node": ">=18.7.0"
}
},
"node_modules/@atproto/did": {
-
"version": "0.1.1-rc.0",
-
"resolved": "https://registry.npmjs.org/@atproto/did/-/did-0.1.1-rc.0.tgz",
-
"integrity": "sha512-rbO6kQv/bKsMGqAqr1M4o7cmJf893gYzabr1CmJ0rr/FNdXHfr0b9s2lRphA6zCS0wPdT4/mw6/LWiCrnBmi9w==",
+
"version": "0.1.5",
+
"resolved": "https://registry.npmjs.org/@atproto/did/-/did-0.1.5.tgz",
+
"integrity": "sha512-8+1D08QdGE5TF0bB0vV8HLVrVZJeLNITpRTUVEoABNMRaUS7CoYSVb0+JNQDeJIVmqMjOL8dOjvCUDkp3gEaGQ==",
+
"license": "MIT",
"dependencies": {
"zod": "^3.23.8"
}
},
"node_modules/@atproto/identity": {
-
"version": "0.4.1",
-
"resolved": "https://registry.npmjs.org/@atproto/identity/-/identity-0.4.1.tgz",
-
"integrity": "sha512-5AoPJDSD0rAay/6Sib+n/FjfwGulM/+xCNxwwDLR9QI4EoeUlvIH8g5BNdix812v312/Qd42kJrLpCNTZ5rvew==",
+
"version": "0.4.8",
+
"resolved": "https://registry.npmjs.org/@atproto/identity/-/identity-0.4.8.tgz",
+
"integrity": "sha512-Z0sLnJ87SeNdAifT+rqpgE1Rc3layMMW25gfWNo4u40RGuRODbdfAZlTwBSU2r+Vk45hU+iE+xeQspfednCEnA==",
+
"license": "MIT",
"dependencies": {
-
"@atproto/common-web": "^0.3.0",
-
"@atproto/crypto": "^0.4.1",
-
"axios": "^0.27.2"
+
"@atproto/common-web": "^0.4.2",
+
"@atproto/crypto": "^0.4.4"
+
},
+
"engines": {
+
"node": ">=18.7.0"
}
},
"node_modules/@atproto/jwk": {
-
"version": "0.1.1",
-
"resolved": "https://registry.npmjs.org/@atproto/jwk/-/jwk-0.1.1.tgz",
-
"integrity": "sha512-6h/bj1APUk7QcV9t/oA6+9DB5NZx9SZru9x+/pV5oHFI9Xz4ZuM5+dq1PfsJV54pZyqdnZ6W6M717cxoC7q7og==",
+
"version": "0.4.0",
+
"resolved": "https://registry.npmjs.org/@atproto/jwk/-/jwk-0.4.0.tgz",
+
"integrity": "sha512-tvp4iZrzqEzKCeTOKz50/o6WdsZzOuWmWjF6On5QAp04fLwLpsFu2Hixgx/lA1KBO0O4sns7YSGcAqSSX6Rdog==",
+
"license": "MIT",
"dependencies": {
"multiformats": "^9.9.0",
"zod": "^3.23.8"
}
},
"node_modules/@atproto/jwk-jose": {
-
"version": "0.1.2-rc.0",
-
"resolved": "https://registry.npmjs.org/@atproto/jwk-jose/-/jwk-jose-0.1.2-rc.0.tgz",
-
"integrity": "sha512-guqGhgQjOx6OxxDWBENRa30G3CJ91Rqw+5NEwiv4GfhmmM/szS983kZIydmXpySpyyZhGAPZfkOfHai+HrLsXg==",
+
"version": "0.1.9",
+
"resolved": "https://registry.npmjs.org/@atproto/jwk-jose/-/jwk-jose-0.1.9.tgz",
+
"integrity": "sha512-HT9GcUe6htDxI5OSYXWdeS6QZ9lpuDDvJk508ppi8a48E/1f8eumoM0QhgbFRF9IKAnnFrtnZDOAvljQzFKwwQ==",
+
"license": "MIT",
"dependencies": {
-
"@atproto/jwk": "0.1.1",
+
"@atproto/jwk": "0.4.0",
"jose": "^5.2.0"
}
},
"node_modules/@atproto/jwk-webcrypto": {
-
"version": "0.1.2-rc.0",
-
"resolved": "https://registry.npmjs.org/@atproto/jwk-webcrypto/-/jwk-webcrypto-0.1.2-rc.0.tgz",
-
"integrity": "sha512-TlLaJulKDWDhXQ8Wujte4l2RPe/Ym+jAnFR/+lwZbcGQHAUsatBMCKzvYVv3TtqXL3B5gIC9ry12+C7oQ5yE/Q==",
+
"version": "0.1.9",
+
"resolved": "https://registry.npmjs.org/@atproto/jwk-webcrypto/-/jwk-webcrypto-0.1.9.tgz",
+
"integrity": "sha512-ecciePHT0JEDZNAbMKSkdqoBYsjvhwuVno0jsS600SZmuvi2fAMhGraDZ5ZOO5M0hHHBiDbN7Ar/qcnIwyoxsA==",
+
"license": "MIT",
"dependencies": {
-
"@atproto/jwk": "0.1.1",
-
"@atproto/jwk-jose": "0.1.2-rc.0"
+
"@atproto/jwk": "0.4.0",
+
"@atproto/jwk-jose": "0.1.9",
+
"zod": "^3.23.8"
}
},
"node_modules/@atproto/lex-cli": {
···
"lex": "dist/index.js"
}
},
-
"node_modules/@atproto/lex-cli/node_modules/@atproto/lexicon": {
-
"version": "0.4.1",
-
"resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.4.1.tgz",
-
"integrity": "sha512-bzyr+/VHXLQWbumViX5L7h1NKQObfs8Z+XZJl43OUK8nYFUI4e/sW1IZKRNfw7Wvi5YVNK+J+yP3DWIBZhkCYA==",
+
"node_modules/@atproto/lex-cli/node_modules/@atproto/syntax": {
+
"version": "0.3.4",
+
"resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.3.4.tgz",
+
"integrity": "sha512-8CNmi5DipOLaVeSMPggMe7FCksVag0aO6XZy9WflbduTKM4dFZVCs4686UeMLfGRXX+X966XgwECHoLYrovMMg==",
"dev": true,
-
"dependencies": {
-
"@atproto/common-web": "^0.3.0",
-
"@atproto/syntax": "^0.3.0",
-
"iso-datestring-validator": "^2.2.2",
-
"multiformats": "^9.9.0",
-
"zod": "^3.23.8"
-
}
+
"license": "MIT"
},
"node_modules/@atproto/lexicon": {
-
"version": "0.4.1-rc.0",
-
"resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.4.1-rc.0.tgz",
-
"integrity": "sha512-CSYO8MWbxTXTLQMEJ1mTXD2pDxIXO2oCK/FVw9T/BeXLMcvwmeVgKAaytd1AGFkapX8IMAAtjBB3cnaltuHwbg==",
+
"version": "0.4.11",
+
"resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.4.11.tgz",
+
"integrity": "sha512-btefdnvNz2Ao2I+qbmj0F06HC8IlrM/IBz6qOBS50r0S6uDf5tOO+Mv2tSVdimFkdzyDdLtBI1sV36ONxz2cOw==",
+
"license": "MIT",
"dependencies": {
-
"@atproto/common-web": "^0.3.0",
-
"@atproto/syntax": "^0.3.0",
+
"@atproto/common-web": "^0.4.2",
+
"@atproto/syntax": "^0.4.0",
"iso-datestring-validator": "^2.2.2",
"multiformats": "^9.9.0",
"zod": "^3.23.8"
}
},
"node_modules/@atproto/oauth-client": {
-
"version": "0.1.2-rc.2",
-
"resolved": "https://registry.npmjs.org/@atproto/oauth-client/-/oauth-client-0.1.2-rc.2.tgz",
-
"integrity": "sha512-FBYyEKEU1BFoW1ASFzsmw1oOpVPj/nkoR753OZItgNwl9i+Tr4kAA9TqeXGa6Ol3dh7K67oaxHw7DChdEqbtSg==",
+
"version": "0.4.2",
+
"resolved": "https://registry.npmjs.org/@atproto/oauth-client/-/oauth-client-0.4.2.tgz",
+
"integrity": "sha512-wHRYcrh+iKQvMramYqE6PHs5Y/L2LYFzrEnyUMf83CjD3GYFwbSN5pwot6EFXONxRwuRjxpXsCSlFzZwx9YFvw==",
+
"license": "MIT",
"dependencies": {
-
"@atproto-labs/did-resolver": "0.1.2-rc.0",
-
"@atproto-labs/fetch": "0.1.0",
-
"@atproto-labs/handle-resolver": "0.1.2-rc.0",
-
"@atproto-labs/identity-resolver": "0.1.2-rc.0",
-
"@atproto-labs/simple-store": "0.1.1",
-
"@atproto-labs/simple-store-memory": "0.1.1",
-
"@atproto/api": "0.13.0-rc.1",
-
"@atproto/did": "0.1.1-rc.0",
-
"@atproto/jwk": "0.1.1",
-
"@atproto/oauth-types": "0.1.2-rc.0",
-
"@atproto/xrpc": "0.6.0-rc.0",
+
"@atproto-labs/did-resolver": "0.2.0",
+
"@atproto-labs/fetch": "0.2.3",
+
"@atproto-labs/handle-resolver": "0.3.0",
+
"@atproto-labs/identity-resolver": "0.2.0",
+
"@atproto-labs/simple-store": "0.2.0",
+
"@atproto-labs/simple-store-memory": "0.1.3",
+
"@atproto/did": "0.1.5",
+
"@atproto/jwk": "0.4.0",
+
"@atproto/oauth-types": "0.3.1",
+
"@atproto/xrpc": "0.7.0",
"multiformats": "^9.9.0",
"zod": "^3.23.8"
}
},
"node_modules/@atproto/oauth-client-node": {
-
"version": "0.0.2-rc.2",
-
"resolved": "https://registry.npmjs.org/@atproto/oauth-client-node/-/oauth-client-node-0.0.2-rc.2.tgz",
-
"integrity": "sha512-MxR2C84h6XjTB28RpXfctKLvB6Ot68tiOlsOSigeSTKnNJ5SRD2wISz2647P8dxOec81ugMu8wa5BKcZ5Ry7nw==",
+
"version": "0.3.1",
+
"resolved": "https://registry.npmjs.org/@atproto/oauth-client-node/-/oauth-client-node-0.3.1.tgz",
+
"integrity": "sha512-k37YC7Ke4+btX05oAqHqkkM8r2Ya/tssWANx7/GMwN3PXPP5PK1C/pkxJrGsN/hpjn3I4W9lVTOlC7nigEX7sw==",
+
"license": "MIT",
"dependencies": {
-
"@atproto-labs/did-resolver": "0.1.2-rc.0",
-
"@atproto-labs/handle-resolver-node": "0.1.2-rc.0",
-
"@atproto-labs/simple-store": "0.1.1",
-
"@atproto/did": "0.1.1-rc.0",
-
"@atproto/jwk": "0.1.1",
-
"@atproto/jwk-jose": "0.1.2-rc.0",
-
"@atproto/jwk-webcrypto": "0.1.2-rc.0",
-
"@atproto/oauth-client": "0.1.2-rc.2",
-
"@atproto/oauth-types": "0.1.2-rc.0"
+
"@atproto-labs/did-resolver": "0.2.0",
+
"@atproto-labs/handle-resolver-node": "0.1.18",
+
"@atproto-labs/simple-store": "0.2.0",
+
"@atproto/did": "0.1.5",
+
"@atproto/jwk": "0.4.0",
+
"@atproto/jwk-jose": "0.1.9",
+
"@atproto/jwk-webcrypto": "0.1.9",
+
"@atproto/oauth-client": "0.4.2",
+
"@atproto/oauth-types": "0.3.1"
+
},
+
"engines": {
+
"node": ">=18.7.0"
}
},
"node_modules/@atproto/oauth-types": {
-
"version": "0.1.2-rc.0",
-
"resolved": "https://registry.npmjs.org/@atproto/oauth-types/-/oauth-types-0.1.2-rc.0.tgz",
-
"integrity": "sha512-q/AxPSdLf2xTgC4K1cU35HVl6T4T0LJ/QJmvqXwjpbiNWEqooIQIP9sTp2CqqSLsWpe26z3fIoA3R+oTR1EJsA==",
+
"version": "0.3.1",
+
"resolved": "https://registry.npmjs.org/@atproto/oauth-types/-/oauth-types-0.3.1.tgz",
+
"integrity": "sha512-l8ahtm74lmBOs5boi5q7mqzF2D37+cIYqVmbCrpexNeJfg2BXu0sBxREt0ADxP25Td9pX+u6FnefCOQtI/YAZw==",
+
"license": "MIT",
"dependencies": {
-
"@atproto/jwk": "0.1.1",
+
"@atproto/jwk": "0.4.0",
"zod": "^3.23.8"
}
},
"node_modules/@atproto/repo": {
-
"version": "0.4.2-rc.0",
-
"resolved": "https://registry.npmjs.org/@atproto/repo/-/repo-0.4.2-rc.0.tgz",
-
"integrity": "sha512-y8zXAR23r6qlsTmbzXaBEHYjvlgeNlAKj9eJ6V17JtT+4FVdW246alhsgSsglJ2Uv/e24RC1r90yNJNRxqDzXw==",
+
"version": "0.8.2",
+
"resolved": "https://registry.npmjs.org/@atproto/repo/-/repo-0.8.2.tgz",
+
"integrity": "sha512-lP0g5Uw3TUC2Tc7te8YKCpRoIhBYI+Uvn11fupGEaMcMjgLdYtB0Kc0AiqWXF42KqlBG9dAEoJITi2GRzDNHUg==",
+
"license": "MIT",
"dependencies": {
-
"@atproto/common": "^0.4.1",
-
"@atproto/common-web": "^0.3.0",
-
"@atproto/crypto": "^0.4.0",
-
"@atproto/lexicon": "^0.4.1-rc.0",
-
"@ipld/car": "^3.2.3",
+
"@atproto/common": "^0.4.11",
+
"@atproto/common-web": "^0.4.2",
+
"@atproto/crypto": "^0.4.4",
+
"@atproto/lexicon": "^0.4.11",
"@ipld/dag-cbor": "^7.0.0",
"multiformats": "^9.9.0",
"uint8arrays": "3.0.0",
+
"varint": "^6.0.0",
"zod": "^3.23.8"
+
},
+
"engines": {
+
"node": ">=18.7.0"
}
},
"node_modules/@atproto/sync": {
-
"version": "0.1.0",
-
"resolved": "https://registry.npmjs.org/@atproto/sync/-/sync-0.1.0.tgz",
-
"integrity": "sha512-2O1UPaeZfL0agitE9rp2mjYVezvZsao3DgJwWCSid1S0N7Y2pOdc7/fSLH/OHn96QhG7g0FGpWzTEcdekRuT0g==",
+
"version": "0.1.26",
+
"resolved": "https://registry.npmjs.org/@atproto/sync/-/sync-0.1.26.tgz",
+
"integrity": "sha512-bpUIajtPrE3RgFW8mIfrI4EM/LJ4JjQhI5fsqc78zCHZawuflpllf1aH70roDWWiskMWoiLWnVRxdYXdeEgbXA==",
+
"license": "MIT",
"dependencies": {
-
"@atproto/common": "^0.4.1",
-
"@atproto/identity": "^0.4.1",
-
"@atproto/lexicon": "^0.4.1",
-
"@atproto/repo": "^0.5.0",
-
"@atproto/syntax": "^0.3.0",
-
"@atproto/xrpc-server": "^0.6.3",
-
"multiformats": "^9.9.0",
-
"p-queue": "^6.6.2"
-
}
-
},
-
"node_modules/@atproto/sync/node_modules/@atproto/lexicon": {
-
"version": "0.4.1",
-
"resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.4.1.tgz",
-
"integrity": "sha512-bzyr+/VHXLQWbumViX5L7h1NKQObfs8Z+XZJl43OUK8nYFUI4e/sW1IZKRNfw7Wvi5YVNK+J+yP3DWIBZhkCYA==",
-
"dependencies": {
-
"@atproto/common-web": "^0.3.0",
-
"@atproto/syntax": "^0.3.0",
-
"iso-datestring-validator": "^2.2.2",
-
"multiformats": "^9.9.0",
-
"zod": "^3.23.8"
-
}
-
},
-
"node_modules/@atproto/sync/node_modules/@atproto/repo": {
-
"version": "0.5.0",
-
"resolved": "https://registry.npmjs.org/@atproto/repo/-/repo-0.5.0.tgz",
-
"integrity": "sha512-kZbj4wW5eFrDjkSTS9z+6bT4OTr5K4GrqWukWbfdBJtZPXsRDm75AV0C9ItoHDTdbBXn65TK6kqaJTrf89osCg==",
-
"dependencies": {
-
"@atproto/common": "^0.4.1",
-
"@atproto/common-web": "^0.3.0",
-
"@atproto/crypto": "^0.4.1",
-
"@atproto/lexicon": "^0.4.1",
-
"@ipld/car": "^3.2.3",
-
"@ipld/dag-cbor": "^7.0.0",
+
"@atproto/common": "^0.4.11",
+
"@atproto/identity": "^0.4.8",
+
"@atproto/lexicon": "^0.4.11",
+
"@atproto/repo": "^0.8.2",
+
"@atproto/syntax": "^0.4.0",
+
"@atproto/xrpc-server": "^0.8.0",
"multiformats": "^9.9.0",
-
"uint8arrays": "3.0.0",
-
"zod": "^3.23.8"
-
}
-
},
-
"node_modules/@atproto/sync/node_modules/@atproto/xrpc": {
-
"version": "0.6.1",
-
"resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.6.1.tgz",
-
"integrity": "sha512-Zy5ydXEdk6sY7FDUZcEVfCL1jvbL4tXu5CcdPqbEaW6LQtk9GLds/DK1bCX9kswTGaBC88EMuqQMfkxOhp2t4A==",
-
"dependencies": {
-
"@atproto/lexicon": "^0.4.1",
-
"zod": "^3.23.8"
-
}
-
},
-
"node_modules/@atproto/sync/node_modules/@atproto/xrpc-server": {
-
"version": "0.6.3",
-
"resolved": "https://registry.npmjs.org/@atproto/xrpc-server/-/xrpc-server-0.6.3.tgz",
-
"integrity": "sha512-0YXeBM9NjiIlR5eXWo8qzArRcBOKhwVimpH+ajKgZzlncPO53brVZ9+3BUnD5J1PG8mEQFRERi+Jt77QyF89qA==",
-
"dependencies": {
-
"@atproto/common": "^0.4.1",
-
"@atproto/crypto": "^0.4.1",
-
"@atproto/lexicon": "^0.4.1",
-
"@atproto/xrpc": "^0.6.1",
-
"cbor-x": "^1.5.1",
-
"express": "^4.17.2",
-
"http-errors": "^2.0.0",
-
"mime-types": "^2.1.35",
-
"rate-limiter-flexible": "^2.4.1",
-
"uint8arrays": "3.0.0",
-
"ws": "^8.12.0",
-
"zod": "^3.23.8"
+
"p-queue": "^6.6.2",
+
"ws": "^8.12.0"
+
},
+
"engines": {
+
"node": ">=18.7.0"
}
},
"node_modules/@atproto/syntax": {
-
"version": "0.3.0",
-
"resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.3.0.tgz",
-
"integrity": "sha512-Weq0ZBxffGHDXHl9U7BQc2BFJi/e23AL+k+i5+D9hUq/bzT4yjGsrCejkjq0xt82xXDjmhhvQSZ0LqxyZ5woxA=="
+
"version": "0.4.0",
+
"resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.0.tgz",
+
"integrity": "sha512-b9y5ceHS8YKOfP3mdKmwAx5yVj9294UN7FG2XzP6V5aKUdFazEYRnR9m5n5ZQFKa3GNvz7de9guZCJ/sUTcOAA==",
+
"license": "MIT"
},
"node_modules/@atproto/xrpc": {
-
"version": "0.6.0-rc.0",
-
"resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.6.0-rc.0.tgz",
-
"integrity": "sha512-TOmynXvbA57Y6KR050UeiDfdzQoAnmgB0zu0qrvhYiu7oeg64fYzvOa7stWxSIP1nhrGqgexxICR1CnOnCEHjg==",
+
"version": "0.7.0",
+
"resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.7.0.tgz",
+
"integrity": "sha512-SfhP9dGx2qclaScFDb58Jnrmim5nk4geZXCqg6sB0I/KZhZEkr9iIx1hLCp+sxkIfEsmEJjeWO4B0rjUIJW5cw==",
+
"license": "MIT",
"dependencies": {
-
"@atproto/lexicon": "^0.4.1-rc.0",
+
"@atproto/lexicon": "^0.4.11",
"zod": "^3.23.8"
}
},
"node_modules/@atproto/xrpc-server": {
-
"version": "0.5.4-rc.0",
-
"resolved": "https://registry.npmjs.org/@atproto/xrpc-server/-/xrpc-server-0.5.4-rc.0.tgz",
-
"integrity": "sha512-Vrx1gEoZfJtYoZhSxkbWQsU2r0DuJO/BuvMQGw9Nd66owmF5nPDVvYVd0pJhIDoaSxImTTIEeDWlNNl3WCSBPA==",
+
"version": "0.8.0",
+
"resolved": "https://registry.npmjs.org/@atproto/xrpc-server/-/xrpc-server-0.8.0.tgz",
+
"integrity": "sha512-jDAEVHVhM4IvC0y491gXBuD4b1D9/XrM3HaEronRneAdNZ0qE0nsiJNqiHfQ6r4BvFdHnABM9KyHV9EQTvmxfg==",
+
"license": "MIT",
"dependencies": {
-
"@atproto/common": "^0.4.1",
-
"@atproto/crypto": "^0.4.0",
-
"@atproto/lexicon": "^0.4.1-rc.0",
-
"@atproto/xrpc": "^0.6.0-rc.0",
+
"@atproto/common": "^0.4.11",
+
"@atproto/crypto": "^0.4.4",
+
"@atproto/lexicon": "^0.4.11",
+
"@atproto/xrpc": "^0.7.0",
"cbor-x": "^1.5.1",
"express": "^4.17.2",
"http-errors": "^2.0.0",
···
"uint8arrays": "3.0.0",
"ws": "^8.12.0",
"zod": "^3.23.8"
+
},
+
"engines": {
+
"node": ">=18.7.0"
}
},
"node_modules/@cbor-extract/cbor-extract-darwin-arm64": {
···
"darwin"
]
},
+
"node_modules/@cbor-extract/cbor-extract-darwin-x64": {
+
"version": "2.2.0",
+
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.0.tgz",
+
"integrity": "sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==",
+
"cpu": [
+
"x64"
+
],
+
"optional": true,
+
"os": [
+
"darwin"
+
]
+
},
+
"node_modules/@cbor-extract/cbor-extract-linux-arm": {
+
"version": "2.2.0",
+
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.0.tgz",
+
"integrity": "sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==",
+
"cpu": [
+
"arm"
+
],
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@cbor-extract/cbor-extract-linux-arm64": {
+
"version": "2.2.0",
+
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.0.tgz",
+
"integrity": "sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==",
+
"cpu": [
+
"arm64"
+
],
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@cbor-extract/cbor-extract-linux-x64": {
+
"version": "2.2.0",
+
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.0.tgz",
+
"integrity": "sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==",
+
"cpu": [
+
"x64"
+
],
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@cbor-extract/cbor-extract-win32-x64": {
+
"version": "2.2.0",
+
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz",
+
"integrity": "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==",
+
"cpu": [
+
"x64"
+
],
+
"optional": true,
+
"os": [
+
"win32"
+
]
+
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
···
"node": ">=12"
}
},
+
"node_modules/@esbuild/aix-ppc64": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
+
"integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
+
"cpu": [
+
"ppc64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"aix"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/android-arm": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
+
"integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
+
"cpu": [
+
"arm"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"android"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/android-arm64": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
+
"integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"android"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/android-x64": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
+
"integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"android"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
"node_modules/@esbuild/darwin-arm64": {
-
"version": "0.23.0",
-
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz",
-
"integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==",
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
+
"integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
"cpu": [
"arm64"
],
···
"node": ">=18"
}
},
-
"node_modules/@ipld/car": {
-
"version": "3.2.4",
-
"resolved": "https://registry.npmjs.org/@ipld/car/-/car-3.2.4.tgz",
-
"integrity": "sha512-rezKd+jk8AsTGOoJKqzfjLJ3WVft7NZNH95f0pfPbicROvzTyvHCNy567HzSUd6gRXZ9im29z5ZEv9Hw49jSYw==",
-
"dependencies": {
-
"@ipld/dag-cbor": "^7.0.0",
-
"multiformats": "^9.5.4",
-
"varint": "^6.0.0"
+
"node_modules/@esbuild/darwin-x64": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
+
"integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"darwin"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/freebsd-arm64": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
+
"integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"freebsd"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/freebsd-x64": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
+
"integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"freebsd"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/linux-arm": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
+
"integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
+
"cpu": [
+
"arm"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/linux-arm64": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
+
"integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/linux-ia32": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
+
"integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
+
"cpu": [
+
"ia32"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/linux-loong64": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
+
"integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
+
"cpu": [
+
"loong64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/linux-mips64el": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
+
"integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
+
"cpu": [
+
"mips64el"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/linux-ppc64": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
+
"integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
+
"cpu": [
+
"ppc64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/linux-riscv64": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
+
"integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
+
"cpu": [
+
"riscv64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/linux-s390x": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
+
"integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
+
"cpu": [
+
"s390x"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/linux-x64": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
+
"integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"linux"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/netbsd-x64": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
+
"integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"netbsd"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/openbsd-arm64": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
+
"integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"openbsd"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/openbsd-x64": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
+
"integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"openbsd"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/sunos-x64": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
+
"integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"sunos"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/win32-arm64": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
+
"integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"win32"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/win32-ia32": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
+
"integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
+
"cpu": [
+
"ia32"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"win32"
+
],
+
"engines": {
+
"node": ">=18"
+
}
+
},
+
"node_modules/@esbuild/win32-x64": {
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
+
"integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"win32"
+
],
+
"engines": {
+
"node": ">=18"
}
},
"node_modules/@ipld/dag-cbor": {
···
"node": ">=12"
}
},
-
"node_modules/@isaacs/cliui/node_modules/ansi-styles": {
-
"version": "6.2.1",
-
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
-
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
-
"dev": true,
-
"engines": {
-
"node": ">=12"
-
},
-
"funding": {
-
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
-
}
-
},
-
"node_modules/@isaacs/cliui/node_modules/emoji-regex": {
-
"version": "9.2.2",
-
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
-
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
-
"dev": true
-
},
-
"node_modules/@isaacs/cliui/node_modules/string-width": {
-
"version": "5.1.2",
-
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
-
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
-
"dev": true,
-
"dependencies": {
-
"eastasianwidth": "^0.2.0",
-
"emoji-regex": "^9.2.2",
-
"strip-ansi": "^7.0.1"
-
},
-
"engines": {
-
"node": ">=12"
-
},
-
"funding": {
-
"url": "https://github.com/sponsors/sindresorhus"
-
}
-
},
-
"node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
-
"version": "8.1.0",
-
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
-
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
-
"dev": true,
-
"dependencies": {
-
"ansi-styles": "^6.1.0",
-
"string-width": "^5.0.1",
-
"strip-ansi": "^7.0.1"
-
},
-
"engines": {
-
"node": ">=12"
-
},
-
"funding": {
-
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
-
}
-
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
···
}
},
"node_modules/@noble/curves": {
-
"version": "1.5.0",
-
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.5.0.tgz",
-
"integrity": "sha512-J5EKamIHnKPyClwVrzmaf5wSdQXgdHcPZIZLu3bwnbeCx8/7NPK5q2ZBWF+5FvYGByjiQQsJYX6jfgB2wDPn3A==",
+
"version": "1.9.2",
+
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.2.tgz",
+
"integrity": "sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==",
+
"license": "MIT",
"dependencies": {
-
"@noble/hashes": "1.4.0"
+
"@noble/hashes": "1.8.0"
+
},
+
"engines": {
+
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/hashes": {
-
"version": "1.4.0",
-
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
-
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
+
"version": "1.8.0",
+
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
+
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
+
"license": "MIT",
"engines": {
-
"node": ">= 16"
+
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
···
"url": "https://opencollective.com/preact"
}
},
+
"node_modules/@rollup/rollup-android-arm-eabi": {
+
"version": "4.21.2",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.2.tgz",
+
"integrity": "sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==",
+
"cpu": [
+
"arm"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"android"
+
]
+
},
+
"node_modules/@rollup/rollup-android-arm64": {
+
"version": "4.21.2",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.2.tgz",
+
"integrity": "sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"android"
+
]
+
},
"node_modules/@rollup/rollup-darwin-arm64": {
-
"version": "4.20.0",
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz",
-
"integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==",
+
"version": "4.21.2",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.2.tgz",
+
"integrity": "sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==",
"cpu": [
"arm64"
],
···
"darwin"
]
},
+
"node_modules/@rollup/rollup-darwin-x64": {
+
"version": "4.21.2",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.2.tgz",
+
"integrity": "sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"darwin"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+
"version": "4.21.2",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.2.tgz",
+
"integrity": "sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==",
+
"cpu": [
+
"arm"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
+
"version": "4.21.2",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.2.tgz",
+
"integrity": "sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==",
+
"cpu": [
+
"arm"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-arm64-gnu": {
+
"version": "4.21.2",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.2.tgz",
+
"integrity": "sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-arm64-musl": {
+
"version": "4.21.2",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.2.tgz",
+
"integrity": "sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+
"version": "4.21.2",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.2.tgz",
+
"integrity": "sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==",
+
"cpu": [
+
"ppc64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
+
"version": "4.21.2",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.2.tgz",
+
"integrity": "sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==",
+
"cpu": [
+
"riscv64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-s390x-gnu": {
+
"version": "4.21.2",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.2.tgz",
+
"integrity": "sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==",
+
"cpu": [
+
"s390x"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-x64-gnu": {
+
"version": "4.21.2",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.2.tgz",
+
"integrity": "sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-linux-x64-musl": {
+
"version": "4.21.2",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.2.tgz",
+
"integrity": "sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"linux"
+
]
+
},
+
"node_modules/@rollup/rollup-win32-arm64-msvc": {
+
"version": "4.21.2",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.2.tgz",
+
"integrity": "sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==",
+
"cpu": [
+
"arm64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"win32"
+
]
+
},
+
"node_modules/@rollup/rollup-win32-ia32-msvc": {
+
"version": "4.21.2",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.2.tgz",
+
"integrity": "sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==",
+
"cpu": [
+
"ia32"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"win32"
+
]
+
},
+
"node_modules/@rollup/rollup-win32-x64-msvc": {
+
"version": "4.21.2",
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.2.tgz",
+
"integrity": "sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==",
+
"cpu": [
+
"x64"
+
],
+
"dev": true,
+
"optional": true,
+
"os": [
+
"win32"
+
]
+
},
"node_modules/@ts-morph/common": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.17.0.tgz",
···
"dev": true
},
"node_modules/@types/node": {
-
"version": "22.2.0",
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.2.0.tgz",
-
"integrity": "sha512-bm6EG6/pCpkxDf/0gDNDdtDILMOHgaQBVOJGdwsqClnxA3xL6jtMv76rLBc006RVMWbmaf0xbmom4Z/5o2nRkQ==",
+
"version": "22.5.4",
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
+
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
"dev": true,
"dependencies": {
-
"undici-types": "~6.13.0"
+
"undici-types": "~6.19.2"
}
},
"node_modules/@types/qs": {
···
"node": ">=8"
},
-
"node_modules/asynckit": {
-
"version": "0.4.0",
-
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
-
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
-
},
"node_modules/atomic-sleep": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
···
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/await-lock/-/await-lock-2.2.2.tgz",
"integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw=="
-
},
-
"node_modules/axios": {
-
"version": "0.27.2",
-
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
-
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
-
"dependencies": {
-
"follow-redirects": "^1.14.9",
-
"form-data": "^4.0.0"
-
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
···
},
"node_modules/better-sqlite3": {
-
"version": "11.1.2",
-
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.1.2.tgz",
-
"integrity": "sha512-gujtFwavWU4MSPT+h9B+4pkvZdyOUkH54zgLdIrMmmmd4ZqiBIrRNBzNzYVFO417xo882uP5HBu4GjOfaSrIQw==",
+
"version": "11.2.1",
+
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.2.1.tgz",
+
"integrity": "sha512-Xbt1d68wQnUuFIEVsbt6V+RG30zwgbtCGQ4QOcXVrOH0FE4eHk64FWZ9NUfRHS4/x1PXqwz/+KOrnXD7f0WieA==",
"hasInstallScript": true,
"dependencies": {
"bindings": "^1.5.0",
···
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
"dev": true
},
-
"node_modules/combined-stream": {
-
"version": "1.0.8",
-
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
-
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
-
"dependencies": {
-
"delayed-stream": "~1.0.0"
-
},
-
"engines": {
-
"node": ">= 0.8"
-
}
-
},
"node_modules/commander": {
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
···
"url": "https://github.com/sponsors/ljharb"
},
-
"node_modules/delayed-stream": {
-
"version": "1.0.0",
-
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
-
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+
"node_modules/delay": {
+
"version": "5.0.0",
+
"resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz",
+
"integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==",
+
"license": "MIT",
"engines": {
-
"node": ">=0.4.0"
+
"node": ">=10"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/sindresorhus"
},
"node_modules/depd": {
···
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
+
"node_modules/emoji-regex": {
+
"version": "9.2.2",
+
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+
"dev": true
+
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
···
},
"node_modules/esbuild": {
-
"version": "0.23.0",
-
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz",
-
"integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==",
+
"version": "0.23.1",
+
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
+
"integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
"dev": true,
"hasInstallScript": true,
"bin": {
···
"node": ">=18"
},
"optionalDependencies": {
-
"@esbuild/aix-ppc64": "0.23.0",
-
"@esbuild/android-arm": "0.23.0",
-
"@esbuild/android-arm64": "0.23.0",
-
"@esbuild/android-x64": "0.23.0",
-
"@esbuild/darwin-arm64": "0.23.0",
-
"@esbuild/darwin-x64": "0.23.0",
-
"@esbuild/freebsd-arm64": "0.23.0",
-
"@esbuild/freebsd-x64": "0.23.0",
-
"@esbuild/linux-arm": "0.23.0",
-
"@esbuild/linux-arm64": "0.23.0",
-
"@esbuild/linux-ia32": "0.23.0",
-
"@esbuild/linux-loong64": "0.23.0",
-
"@esbuild/linux-mips64el": "0.23.0",
-
"@esbuild/linux-ppc64": "0.23.0",
-
"@esbuild/linux-riscv64": "0.23.0",
-
"@esbuild/linux-s390x": "0.23.0",
-
"@esbuild/linux-x64": "0.23.0",
-
"@esbuild/netbsd-x64": "0.23.0",
-
"@esbuild/openbsd-arm64": "0.23.0",
-
"@esbuild/openbsd-x64": "0.23.0",
-
"@esbuild/sunos-x64": "0.23.0",
-
"@esbuild/win32-arm64": "0.23.0",
-
"@esbuild/win32-ia32": "0.23.0",
-
"@esbuild/win32-x64": "0.23.0"
+
"@esbuild/aix-ppc64": "0.23.1",
+
"@esbuild/android-arm": "0.23.1",
+
"@esbuild/android-arm64": "0.23.1",
+
"@esbuild/android-x64": "0.23.1",
+
"@esbuild/darwin-arm64": "0.23.1",
+
"@esbuild/darwin-x64": "0.23.1",
+
"@esbuild/freebsd-arm64": "0.23.1",
+
"@esbuild/freebsd-x64": "0.23.1",
+
"@esbuild/linux-arm": "0.23.1",
+
"@esbuild/linux-arm64": "0.23.1",
+
"@esbuild/linux-ia32": "0.23.1",
+
"@esbuild/linux-loong64": "0.23.1",
+
"@esbuild/linux-mips64el": "0.23.1",
+
"@esbuild/linux-ppc64": "0.23.1",
+
"@esbuild/linux-riscv64": "0.23.1",
+
"@esbuild/linux-s390x": "0.23.1",
+
"@esbuild/linux-x64": "0.23.1",
+
"@esbuild/netbsd-x64": "0.23.1",
+
"@esbuild/openbsd-arm64": "0.23.1",
+
"@esbuild/openbsd-x64": "0.23.1",
+
"@esbuild/sunos-x64": "0.23.1",
+
"@esbuild/win32-arm64": "0.23.1",
+
"@esbuild/win32-ia32": "0.23.1",
+
"@esbuild/win32-x64": "0.23.1"
},
"node_modules/escape-html": {
···
"engines": {
"node": ">=0.8.x"
+
},
+
"node_modules/execa": {
+
"version": "5.1.1",
+
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+
"integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+
"dev": true,
+
"dependencies": {
+
"cross-spawn": "^7.0.3",
+
"get-stream": "^6.0.0",
+
"human-signals": "^2.1.0",
+
"is-stream": "^2.0.0",
+
"merge-stream": "^2.0.0",
+
"npm-run-path": "^4.0.1",
+
"onetime": "^5.1.2",
+
"signal-exit": "^3.0.3",
+
"strip-final-newline": "^2.0.0"
+
},
+
"engines": {
+
"node": ">=10"
+
},
+
"funding": {
+
"url": "https://github.com/sindresorhus/execa?sponsor=1"
+
}
+
},
+
"node_modules/execa/node_modules/signal-exit": {
+
"version": "3.0.7",
+
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+
"dev": true
},
"node_modules/expand-template": {
"version": "2.0.3",
···
"node": ">=8.6.0"
},
+
"node_modules/fast-printf": {
+
"version": "1.6.10",
+
"resolved": "https://registry.npmjs.org/fast-printf/-/fast-printf-1.6.10.tgz",
+
"integrity": "sha512-GwTgG9O4FVIdShhbVF3JxOgSBY2+ePGsu2V/UONgoCPzF9VY6ZdBMKsHKCYQHZwNk3qNouUolRDsgVxcVA5G1w==",
+
"license": "BSD-3-Clause",
+
"engines": {
+
"node": ">=10.0"
+
}
+
},
"node_modules/fast-redact": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz",
···
"node": ">= 0.8"
},
-
"node_modules/follow-redirects": {
-
"version": "1.15.6",
-
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
-
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
-
"funding": [
-
{
-
"type": "individual",
-
"url": "https://github.com/sponsors/RubenVerborgh"
-
}
-
],
-
"engines": {
-
"node": ">=4.0"
-
},
-
"peerDependenciesMeta": {
-
"debug": {
-
"optional": true
-
}
-
}
-
},
"node_modules/foreground-child": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
···
"url": "https://github.com/sponsors/isaacs"
},
-
"node_modules/form-data": {
-
"version": "4.0.0",
-
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
-
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
-
"dependencies": {
-
"asynckit": "^0.4.0",
-
"combined-stream": "^1.0.8",
-
"mime-types": "^2.1.12"
-
},
-
"engines": {
-
"node": ">= 6"
-
}
-
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
···
},
"node_modules/gc-hook": {
-
"version": "0.3.1",
-
"resolved": "https://registry.npmjs.org/gc-hook/-/gc-hook-0.3.1.tgz",
-
"integrity": "sha512-E5M+O/h2o7eZzGhzRZGex6hbB3k4NWqO0eA+OzLRLXxhdbYPajZnynPwAtphnh+cRHPwsj5Z80dqZlfI4eK55A=="
+
"version": "0.4.1",
+
"resolved": "https://registry.npmjs.org/gc-hook/-/gc-hook-0.4.1.tgz",
+
"integrity": "sha512-uiF+uUftDVLr+VRdudsdsT3/LQYnv2ntwhRH964O7xXDI57Smrek5olv75Wb8Nnz6U+7iVTRXsBlxKcsaDTJTQ=="
},
"node_modules/get-intrinsic": {
"version": "1.2.4",
···
"url": "https://github.com/sponsors/ljharb"
},
+
"node_modules/get-stream": {
+
"version": "6.0.1",
+
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+
"dev": true,
+
"engines": {
+
"node": ">=10"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/sindresorhus"
+
}
+
},
"node_modules/get-tsconfig": {
-
"version": "4.7.6",
-
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.6.tgz",
-
"integrity": "sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==",
+
"version": "4.8.0",
+
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.0.tgz",
+
"integrity": "sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw==",
"dev": true,
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
···
"node": ">= 0.8"
},
+
"node_modules/http-terminator": {
+
"version": "3.2.0",
+
"resolved": "https://registry.npmjs.org/http-terminator/-/http-terminator-3.2.0.tgz",
+
"integrity": "sha512-JLjck1EzPaWjsmIf8bziM3p9fgR1Y3JoUKAkyYEbZmFrIvJM6I8vVJfBGWlEtV9IWOvzNnaTtjuwZeBY2kwB4g==",
+
"license": "BSD-3-Clause",
+
"dependencies": {
+
"delay": "^5.0.0",
+
"p-wait-for": "^3.2.0",
+
"roarr": "^7.0.4",
+
"type-fest": "^2.3.3"
+
},
+
"engines": {
+
"node": ">=14"
+
}
+
},
+
"node_modules/human-signals": {
+
"version": "2.1.0",
+
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+
"dev": true,
+
"engines": {
+
"node": ">=10.17.0"
+
}
+
},
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
···
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
},
"node_modules/ipaddr.js": {
-
"version": "2.2.0",
-
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
-
"integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==",
+
"version": "1.9.1",
+
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"engines": {
-
"node": ">= 10"
+
"node": ">= 0.10"
},
"node_modules/iron-session": {
-
"version": "8.0.2",
-
"resolved": "https://registry.npmjs.org/iron-session/-/iron-session-8.0.2.tgz",
-
"integrity": "sha512-p4Yf1moQr6gnCcXu5vCaxVKRKDmR9PZcQDfp7ZOgbsSHUsgaNti6OgDB2BdgxC2aS6V/6Hu4O0wYlj92sbdIJg==",
+
"version": "8.0.3",
+
"resolved": "https://registry.npmjs.org/iron-session/-/iron-session-8.0.3.tgz",
+
"integrity": "sha512-WtDX0griBliMoR6hGoU3SlefW+VSbfHrIVqURQ0Nbg/Pd+nj7VDsKV+sx0FHjyUCaO02YoYV5v+kW0PqvFJISQ==",
"funding": [
"https://github.com/sponsors/vvo",
"https://github.com/sponsors/brc-dd"
···
"node": ">=0.10.0"
},
+
"node_modules/is-fullwidth-code-point": {
+
"version": "3.0.0",
+
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+
"dev": true,
+
"engines": {
+
"node": ">=8"
+
}
+
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
···
"node": ">=0.12.0"
},
+
"node_modules/is-stream": {
+
"version": "2.0.1",
+
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+
"dev": true,
+
"engines": {
+
"node": ">=8"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/sindresorhus"
+
}
+
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
···
},
"node_modules/jose": {
-
"version": "5.6.3",
-
"resolved": "https://registry.npmjs.org/jose/-/jose-5.6.3.tgz",
-
"integrity": "sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==",
+
"version": "5.8.0",
+
"resolved": "https://registry.npmjs.org/jose/-/jose-5.8.0.tgz",
+
"integrity": "sha512-E7CqYpL/t7MMnfGnK/eg416OsFCVUrU/Y3Vwe7QjKhu/BkS1Ms455+2xsqZQVN57/U2MHMBvEb5SrmAZWAIntA==",
"funding": {
"url": "https://github.com/sponsors/panva"
···
},
"node_modules/micromatch": {
-
"version": "4.0.7",
-
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
-
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
+
"version": "4.0.8",
+
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"dependencies": {
"braces": "^3.0.3",
···
"node": ">= 0.6"
},
+
"node_modules/mimic-fn": {
+
"version": "2.1.0",
+
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+
"dev": true,
+
"engines": {
+
"node": ">=6"
+
}
+
},
"node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
···
"thenify-all": "^1.0.0"
},
-
"node_modules/nanoid": {
-
"version": "3.3.7",
-
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
-
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
-
"dev": true,
-
"funding": [
-
{
-
"type": "github",
-
"url": "https://github.com/sponsors/ai"
-
}
-
],
-
"optional": true,
-
"peer": true,
-
"bin": {
-
"nanoid": "bin/nanoid.cjs"
-
},
-
"engines": {
-
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
-
}
-
},
"node_modules/napi-build-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
···
},
"node_modules/node-abi": {
-
"version": "3.65.0",
-
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.65.0.tgz",
-
"integrity": "sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==",
+
"version": "3.67.0",
+
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.67.0.tgz",
+
"integrity": "sha512-bLn/fU/ALVBE9wj+p4Y21ZJWYFjUXLXPi/IewyLZkx3ApxKDNBWCKdReeKOtD8dWpOdDCeMyLh6ZewzcLsG2Nw==",
"dependencies": {
"semver": "^7.3.5"
},
···
"dev": true,
"engines": {
"node": ">=0.10.0"
+
}
+
},
+
"node_modules/npm-run-path": {
+
"version": "4.0.1",
+
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+
"integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+
"dev": true,
+
"dependencies": {
+
"path-key": "^3.0.0"
+
},
+
"engines": {
+
"node": ">=8"
},
"node_modules/object-assign": {
···
"wrappy": "1"
},
+
"node_modules/onetime": {
+
"version": "5.1.2",
+
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+
"dev": true,
+
"dependencies": {
+
"mimic-fn": "^2.1.0"
+
},
+
"engines": {
+
"node": ">=6"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/sindresorhus"
+
}
+
},
"node_modules/p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
···
},
"engines": {
"node": ">=8"
+
}
+
},
+
"node_modules/p-wait-for": {
+
"version": "3.2.0",
+
"resolved": "https://registry.npmjs.org/p-wait-for/-/p-wait-for-3.2.0.tgz",
+
"integrity": "sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA==",
+
"license": "MIT",
+
"dependencies": {
+
"p-timeout": "^3.0.0"
+
},
+
"engines": {
+
"node": ">=8"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/sindresorhus"
},
"node_modules/package-json-from-dist": {
···
},
"node_modules/picocolors": {
-
"version": "1.0.1",
-
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
-
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
+
"version": "1.1.0",
+
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
+
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
"dev": true
},
"node_modules/picomatch": {
···
},
"node_modules/pino": {
-
"version": "9.3.2",
-
"resolved": "https://registry.npmjs.org/pino/-/pino-9.3.2.tgz",
-
"integrity": "sha512-WtARBjgZ7LNEkrGWxMBN/jvlFiE17LTbBoH0konmBU684Kd0uIiDwBXlcTCW7iJnA6HfIKwUssS/2AC6cDEanw==",
+
"version": "9.4.0",
+
"resolved": "https://registry.npmjs.org/pino/-/pino-9.4.0.tgz",
+
"integrity": "sha512-nbkQb5+9YPhQRz/BeQmrWpEknAaqjpAqRK8NwJpmrX/JHu7JuZC5G1CeAwJDJfGes4h+YihC6in3Q2nGb+Y09w==",
"dependencies": {
"atomic-sleep": "^1.0.0",
"fast-redact": "^3.1.1",
···
"node": ">= 6"
},
-
"node_modules/postcss": {
-
"version": "8.4.41",
-
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
-
"integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==",
-
"dev": true,
-
"funding": [
-
{
-
"type": "opencollective",
-
"url": "https://opencollective.com/postcss/"
-
},
-
{
-
"type": "tidelift",
-
"url": "https://tidelift.com/funding/github/npm/postcss"
-
},
-
{
-
"type": "github",
-
"url": "https://github.com/sponsors/ai"
-
}
-
],
-
"optional": true,
-
"peer": true,
-
"dependencies": {
-
"nanoid": "^3.3.7",
-
"picocolors": "^1.0.1",
-
"source-map-js": "^1.2.0"
-
},
-
"engines": {
-
"node": "^10 || ^12 || >=14"
-
}
-
},
"node_modules/postcss-load-config": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
···
"node": ">= 0.10"
},
-
"node_modules/proxy-addr/node_modules/ipaddr.js": {
-
"version": "1.9.1",
-
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
-
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
-
"engines": {
-
"node": ">= 0.10"
-
}
-
},
-
"node_modules/psl": {
-
"version": "1.9.0",
-
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
-
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
-
},
"node_modules/pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
···
"node_modules/rate-limiter-flexible": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-2.4.2.tgz",
-
"integrity": "sha512-rMATGGOdO1suFyf/mI5LYhts71g1sbdhmd6YvdiXO2gJnd42Tt6QS4JUKJKSWVVkMtBacm6l40FR7Trjo6Iruw=="
+
"integrity": "sha512-rMATGGOdO1suFyf/mI5LYhts71g1sbdhmd6YvdiXO2gJnd42Tt6QS4JUKJKSWVVkMtBacm6l40FR7Trjo6Iruw==",
+
"license": "ISC"
},
"node_modules/raw-body": {
"version": "2.5.2",
···
"url": "https://github.com/sponsors/isaacs"
},
+
"node_modules/roarr": {
+
"version": "7.21.1",
+
"resolved": "https://registry.npmjs.org/roarr/-/roarr-7.21.1.tgz",
+
"integrity": "sha512-3niqt5bXFY1InKU8HKWqqYTYjtrBaxBMnXELXCXUYgtNYGUtZM5rB46HIC430AyacL95iEniGf7RgqsesykLmQ==",
+
"license": "BSD-3-Clause",
+
"dependencies": {
+
"fast-printf": "^1.6.9",
+
"safe-stable-stringify": "^2.4.3",
+
"semver-compare": "^1.0.0"
+
},
+
"engines": {
+
"node": ">=18.0"
+
}
+
},
"node_modules/rollup": {
-
"version": "4.20.0",
-
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz",
-
"integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==",
+
"version": "4.21.2",
+
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz",
+
"integrity": "sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.5"
···
"npm": ">=8.0.0"
},
"optionalDependencies": {
-
"@rollup/rollup-android-arm-eabi": "4.20.0",
-
"@rollup/rollup-android-arm64": "4.20.0",
-
"@rollup/rollup-darwin-arm64": "4.20.0",
-
"@rollup/rollup-darwin-x64": "4.20.0",
-
"@rollup/rollup-linux-arm-gnueabihf": "4.20.0",
-
"@rollup/rollup-linux-arm-musleabihf": "4.20.0",
-
"@rollup/rollup-linux-arm64-gnu": "4.20.0",
-
"@rollup/rollup-linux-arm64-musl": "4.20.0",
-
"@rollup/rollup-linux-powerpc64le-gnu": "4.20.0",
-
"@rollup/rollup-linux-riscv64-gnu": "4.20.0",
-
"@rollup/rollup-linux-s390x-gnu": "4.20.0",
-
"@rollup/rollup-linux-x64-gnu": "4.20.0",
-
"@rollup/rollup-linux-x64-musl": "4.20.0",
-
"@rollup/rollup-win32-arm64-msvc": "4.20.0",
-
"@rollup/rollup-win32-ia32-msvc": "4.20.0",
-
"@rollup/rollup-win32-x64-msvc": "4.20.0",
+
"@rollup/rollup-android-arm-eabi": "4.21.2",
+
"@rollup/rollup-android-arm64": "4.21.2",
+
"@rollup/rollup-darwin-arm64": "4.21.2",
+
"@rollup/rollup-darwin-x64": "4.21.2",
+
"@rollup/rollup-linux-arm-gnueabihf": "4.21.2",
+
"@rollup/rollup-linux-arm-musleabihf": "4.21.2",
+
"@rollup/rollup-linux-arm64-gnu": "4.21.2",
+
"@rollup/rollup-linux-arm64-musl": "4.21.2",
+
"@rollup/rollup-linux-powerpc64le-gnu": "4.21.2",
+
"@rollup/rollup-linux-riscv64-gnu": "4.21.2",
+
"@rollup/rollup-linux-s390x-gnu": "4.21.2",
+
"@rollup/rollup-linux-x64-gnu": "4.21.2",
+
"@rollup/rollup-linux-x64-musl": "4.21.2",
+
"@rollup/rollup-win32-arm64-msvc": "4.21.2",
+
"@rollup/rollup-win32-ia32-msvc": "4.21.2",
+
"@rollup/rollup-win32-x64-msvc": "4.21.2",
"fsevents": "~2.3.2"
},
···
},
"node_modules/safe-stable-stringify": {
-
"version": "2.4.3",
-
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz",
-
"integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==",
+
"version": "2.5.0",
+
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
+
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
"engines": {
"node": ">=10"
···
"engines": {
"node": ">=10"
+
},
+
"node_modules/semver-compare": {
+
"version": "1.0.0",
+
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
+
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
+
"license": "MIT"
},
"node_modules/send": {
"version": "0.18.0",
···
},
"node_modules/sonic-boom": {
-
"version": "4.0.1",
-
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.0.1.tgz",
-
"integrity": "sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==",
+
"version": "4.1.0",
+
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.1.0.tgz",
+
"integrity": "sha512-NGipjjRicyJJ03rPiZCJYjwlsuP2d1/5QUviozRXC7S3WdVWNK5e3Ojieb9CCyfhq2UC+3+SRd9nG3I2lPRvUw==",
"dependencies": {
"atomic-sleep": "^1.0.0"
···
"node": ">= 8"
},
-
"node_modules/source-map-js": {
-
"version": "1.2.0",
-
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
-
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
-
"dev": true,
-
"optional": true,
-
"peer": true,
-
"engines": {
-
"node": ">=0.10.0"
-
}
-
},
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
···
"safe-buffer": "~5.2.0"
},
+
"node_modules/string-width": {
+
"version": "5.1.2",
+
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+
"dev": true,
+
"dependencies": {
+
"eastasianwidth": "^0.2.0",
+
"emoji-regex": "^9.2.2",
+
"strip-ansi": "^7.0.1"
+
},
+
"engines": {
+
"node": ">=12"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/sindresorhus"
+
}
+
},
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
···
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
-
},
-
"node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": {
-
"version": "3.0.0",
-
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
-
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
-
"dev": true,
-
"engines": {
-
"node": ">=8"
-
}
},
"node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
···
"dev": true,
"engines": {
"node": ">=8"
+
}
+
},
+
"node_modules/strip-final-newline": {
+
"version": "2.0.0",
+
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+
"dev": true,
+
"engines": {
+
"node": ">=6"
},
"node_modules/strip-json-comments": {
···
},
-
"node_modules/tsup/node_modules/execa": {
-
"version": "5.1.1",
-
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
-
"integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
-
"dev": true,
-
"dependencies": {
-
"cross-spawn": "^7.0.3",
-
"get-stream": "^6.0.0",
-
"human-signals": "^2.1.0",
-
"is-stream": "^2.0.0",
-
"merge-stream": "^2.0.0",
-
"npm-run-path": "^4.0.1",
-
"onetime": "^5.1.2",
-
"signal-exit": "^3.0.3",
-
"strip-final-newline": "^2.0.0"
-
},
-
"engines": {
-
"node": ">=10"
-
},
-
"funding": {
-
"url": "https://github.com/sindresorhus/execa?sponsor=1"
-
}
-
},
-
"node_modules/tsup/node_modules/get-stream": {
-
"version": "6.0.1",
-
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
-
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
-
"dev": true,
-
"engines": {
-
"node": ">=10"
-
},
-
"funding": {
-
"url": "https://github.com/sponsors/sindresorhus"
-
}
-
},
-
"node_modules/tsup/node_modules/human-signals": {
-
"version": "2.1.0",
-
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
-
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
-
"dev": true,
-
"engines": {
-
"node": ">=10.17.0"
-
}
-
},
-
"node_modules/tsup/node_modules/is-stream": {
-
"version": "2.0.1",
-
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
-
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
-
"dev": true,
-
"engines": {
-
"node": ">=8"
-
},
-
"funding": {
-
"url": "https://github.com/sponsors/sindresorhus"
-
}
-
},
-
"node_modules/tsup/node_modules/mimic-fn": {
-
"version": "2.1.0",
-
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
-
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
-
"dev": true,
-
"engines": {
-
"node": ">=6"
-
}
-
},
"node_modules/tsup/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
-
"node_modules/tsup/node_modules/npm-run-path": {
-
"version": "4.0.1",
-
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
-
"integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
-
"dev": true,
-
"dependencies": {
-
"path-key": "^3.0.0"
-
},
-
"engines": {
-
"node": ">=8"
-
}
-
},
-
"node_modules/tsup/node_modules/onetime": {
-
"version": "5.1.2",
-
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
-
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
-
"dev": true,
-
"dependencies": {
-
"mimic-fn": "^2.1.0"
-
},
-
"engines": {
-
"node": ">=6"
-
},
-
"funding": {
-
"url": "https://github.com/sponsors/sindresorhus"
-
}
-
},
-
"node_modules/tsup/node_modules/signal-exit": {
-
"version": "3.0.7",
-
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
-
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
-
"dev": true
-
},
-
"node_modules/tsup/node_modules/strip-final-newline": {
-
"version": "2.0.0",
-
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
-
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
-
"dev": true,
-
"engines": {
-
"node": ">=6"
-
}
-
},
"node_modules/tsx": {
-
"version": "4.17.0",
-
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.17.0.tgz",
-
"integrity": "sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==",
+
"version": "4.19.0",
+
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.0.tgz",
+
"integrity": "sha512-bV30kM7bsLZKZIOCHeMNVMJ32/LuJzLVajkQI/qf92J2Qr08ueLQvW00PUZGiuLPP760UINwupgUj8qrSCPUKg==",
"dev": true,
"dependencies": {
"esbuild": "~0.23.0",
···
"node": "*"
},
+
"node_modules/type-fest": {
+
"version": "2.19.0",
+
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+
"integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+
"license": "(MIT OR CC0-1.0)",
+
"engines": {
+
"node": ">=12.20"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/sindresorhus"
+
}
+
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
···
"integrity": "sha512-aqjTs5x/wsShZBkVagdafJkP8S3UMGhkHKszsu1cszjjZ7iOp86+Qb3QOFYh01oWjPMy5ZTuxD6hw5uTKxd+VA=="
},
"node_modules/uhtml": {
-
"version": "4.5.9",
-
"resolved": "https://registry.npmjs.org/uhtml/-/uhtml-4.5.9.tgz",
-
"integrity": "sha512-WAfIK/E3ZJpaFl0MSzGSB54r7I8Vc8ZyUlOsN8GnLnEaxuioOUyKAS6q/N/xQ5GD9vFFBnx6q+3N3Eq9KNCvTQ==",
+
"version": "4.5.11",
+
"resolved": "https://registry.npmjs.org/uhtml/-/uhtml-4.5.11.tgz",
+
"integrity": "sha512-Jbcrdmc5rwLUJotyX7mi1jBkAnGjjQ9hg0xomKXl7JfHL5KMvpOUJCAWA7FY+IMcAWqZM2NsJMVlwJQjLK4gNw==",
"dependencies": {
"@webreflection/uparser": "^0.3.3",
"custom-function": "^1.0.6",
"domconstants": "^1.1.6",
-
"gc-hook": "^0.3.1",
+
"gc-hook": "^0.4.1",
"html-escaper": "^3.0.3",
"htmlparser2": "^9.1.0",
"udomdiff": "^1.1.0"
},
"optionalDependencies": {
-
"@preact/signals-core": "^1.6.0",
+
"@preact/signals-core": "^1.8.0",
"@webreflection/signal": "^2.1.2"
},
···
"integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="
},
"node_modules/undici": {
-
"version": "6.19.7",
-
"resolved": "https://registry.npmjs.org/undici/-/undici-6.19.7.tgz",
-
"integrity": "sha512-HR3W/bMGPSr90i8AAp2C4DM3wChFdJPLrWYpIS++LxS8K+W535qftjt+4MyjNYHeWabMj1nvtmLIi7l++iq91A==",
+
"version": "6.21.3",
+
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
+
"integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
+
"license": "MIT",
"engines": {
"node": ">=18.17"
},
"node_modules/undici-types": {
-
"version": "6.13.0",
-
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz",
-
"integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==",
+
"version": "6.19.8",
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true
},
"node_modules/unpipe": {
···
"node_modules/varint": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
-
"integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="
+
"integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==",
+
"license": "MIT"
},
"node_modules/vary": {
"version": "1.1.2",
···
"node": ">= 8"
},
+
"node_modules/wrap-ansi": {
+
"version": "8.1.0",
+
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+
"dev": true,
+
"dependencies": {
+
"ansi-styles": "^6.1.0",
+
"string-width": "^5.0.1",
+
"strip-ansi": "^7.0.1"
+
},
+
"engines": {
+
"node": ">=12"
+
},
+
"funding": {
+
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+
}
+
},
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
···
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
-
"node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": {
-
"version": "3.0.0",
-
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
-
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
-
"dev": true,
-
"engines": {
-
"node": ">=8"
-
}
-
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
···
"node": ">=8"
},
+
"node_modules/wrap-ansi/node_modules/ansi-styles": {
+
"version": "6.2.1",
+
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+
"dev": true,
+
"engines": {
+
"node": ">=12"
+
},
+
"funding": {
+
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
+
}
+
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
-
"version": "8.18.0",
-
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
-
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+
"version": "8.18.2",
+
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
+
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
+
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
···
},
-
"node_modules/yaml": {
-
"version": "2.5.0",
-
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz",
-
"integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==",
-
"dev": true,
-
"optional": true,
-
"peer": true,
-
"bin": {
-
"yaml": "bin.mjs"
-
},
-
"engines": {
-
"node": ">= 14"
-
}
-
},
"node_modules/yesno": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/yesno/-/yesno-0.4.0.tgz",
···
},
"node_modules/zod": {
-
"version": "3.23.8",
-
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
-
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
+
"version": "3.25.67",
+
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz",
+
"integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==",
+
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
+10 -8
package.json
···
"clean": "rimraf dist coverage"
},
"dependencies": {
-
"@atproto/identity": "^0.4.0",
-
"@atproto/lexicon": "0.4.1-rc.0",
-
"@atproto/oauth-client-node": "0.0.2-rc.2",
-
"@atproto/repo": "0.4.2-rc.0",
-
"@atproto/sync": "^0.1.0",
-
"@atproto/syntax": "^0.3.0",
-
"@atproto/xrpc-server": "0.5.4-rc.0",
+
"@atproto/api": "^0.15.6",
+
"@atproto/common": "^0.4.11",
+
"@atproto/identity": "^0.4.8",
+
"@atproto/lexicon": "^0.4.11",
+
"@atproto/oauth-client-node": "^0.3.1",
+
"@atproto/sync": "^0.1.26",
+
"@atproto/xrpc-server": "^0.8.0",
"better-sqlite3": "^11.1.2",
"dotenv": "^16.4.5",
"envalid": "^8.0.0",
"express": "^4.19.2",
+
"http-terminator": "^3.2.0",
"iron-session": "^8.0.2",
"kysely": "^0.27.4",
"multiformats": "^9.9.0",
"pino": "^9.3.2",
-
"uhtml": "^4.5.9"
+
"uhtml": "^4.5.9",
+
"zod": "^3.25.67"
},
"devDependencies": {
"@atproto/lex-cli": "^0.4.1",
+58 -19
src/auth/client.ts
···
-
import { NodeOAuthClient } from '@atproto/oauth-client-node'
+
import {
+
Keyset,
+
JoseKey,
+
atprotoLoopbackClientMetadata,
+
NodeOAuthClient,
+
OAuthClientMetadataInput,
+
} from '@atproto/oauth-client-node'
+
import assert from 'node:assert'
+
import type { Database } from '#/db'
-
import { env } from '#/lib/env'
+
import { env } from '#/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}`
+
export async function createOAuthClient(db: Database) {
+
// Confidential client require a keyset accessible on the internet. Non
+
// internet clients (e.g. development) cannot expose a keyset on the internet
+
// so they can't be private..
+
const keyset =
+
env.PUBLIC_URL && env.PRIVATE_KEYS
+
? new Keyset(
+
await Promise.all(
+
env.PRIVATE_KEYS.map((jwk) => JoseKey.fromJWK(jwk)),
+
),
+
)
+
: undefined
+
+
assert(
+
!env.PUBLIC_URL || keyset?.size,
+
'ATProto requires backend clients to be confidential. Make sure to set the PRIVATE_KEYS environment variable.',
+
)
+
+
// If a keyset is defined (meaning the client is confidential). Let's make
+
// sure it has a private key for signing. Note: findPrivateKey will throw if
+
// the keyset does not contain a suitable private key.
+
const pk = keyset?.findPrivateKey({ use: 'sig' })
+
+
const clientMetadata: OAuthClientMetadataInput = env.PUBLIC_URL
+
? {
+
client_name: 'Statusphere Example App',
+
client_id: `${env.PUBLIC_URL}/oauth-client-metadata.json`,
+
jwks_uri: `${env.PUBLIC_URL}/.well-known/jwks.json`,
+
redirect_uris: [`${env.PUBLIC_URL}/oauth/callback`],
+
scope: 'atproto transition:generic',
+
grant_types: ['authorization_code', 'refresh_token'],
+
response_types: ['code'],
+
application_type: 'web',
+
token_endpoint_auth_method: pk ? 'private_key_jwt' : 'none',
+
token_endpoint_auth_signing_alg: pk ? pk.alg : undefined,
+
dpop_bound_access_tokens: true,
+
}
+
: atprotoLoopbackClientMetadata(
+
`http://localhost?${new URLSearchParams([
+
['redirect_uri', `http://127.0.0.1:${env.PORT}/oauth/callback`],
+
['scope', `atproto transition:generic`],
+
])}`,
+
)
+
return new NodeOAuthClient({
-
clientMetadata: {
-
client_name: 'AT Protocol Express App',
-
client_id: publicUrl
-
? `${url}/client-metadata.json`
-
: `http://localhost?redirect_uri=${encodeURIComponent(`${url}/oauth/callback`)}`,
-
client_uri: url,
-
redirect_uris: [`${url}/oauth/callback`],
-
scope: 'profile offline_access',
-
grant_types: ['authorization_code', 'refresh_token'],
-
response_types: ['code'],
-
application_type: 'web',
-
token_endpoint_auth_method: 'none',
-
dpop_bound_access_tokens: true,
-
},
+
keyset,
+
clientMetadata,
stateStore: new StateStore(db),
sessionStore: new SessionStore(db),
+
plcDirectoryUrl: env.PLC_URL,
+
handleResolver: env.PDS_URL,
})
}
-61
src/auth/session.ts
···
-
import assert from 'node:assert'
-
import type { IncomingMessage, ServerResponse } from 'node:http'
-
import { getIronSession } from 'iron-session'
-
import { env } from '#/lib/env'
-
import { AppContext } from '#/index'
-
-
export type Session = { did: string }
-
-
export async function createSession(
-
req: IncomingMessage,
-
res: ServerResponse<IncomingMessage>,
-
did: string
-
) {
-
const session = await getSessionRaw(req, res)
-
assert(!session.did, 'session already exists')
-
session.did = did
-
await session.save()
-
return { did: session.did }
-
}
-
-
export async function destroySession(
-
req: IncomingMessage,
-
res: ServerResponse<IncomingMessage>
-
) {
-
const session = await getSessionRaw(req, res)
-
await session.destroy()
-
return null
-
}
-
-
export async function getSession(
-
req: IncomingMessage,
-
res: ServerResponse<IncomingMessage>
-
) {
-
const session = await getSessionRaw(req, res)
-
if (!session.did) return null
-
return { did: session.did }
-
}
-
-
export async function getSessionAgent(
-
req: IncomingMessage,
-
res: ServerResponse<IncomingMessage>,
-
ctx: AppContext
-
) {
-
const session = await getSessionRaw(req, res)
-
if (!session.did) return null
-
return await ctx.oauthClient.restore(session.did).catch(async (err) => {
-
ctx.logger.warn({ err }, 'oauth restore failed')
-
await destroySession(req, res)
-
return null
-
})
-
}
-
-
async function getSessionRaw(
-
req: IncomingMessage,
-
res: ServerResponse<IncomingMessage>
-
) {
-
return await getIronSession<Session>(req, res, {
-
cookieName: 'sid',
-
password: env.COOKIE_SECRET,
-
})
-
}
+46
src/context.ts
···
+
import { NodeOAuthClient } from '@atproto/oauth-client-node'
+
import { Firehose } from '@atproto/sync'
+
import { pino } from 'pino'
+
+
import { createOAuthClient } from '#/auth/client'
+
import { createDb, Database, migrateToLatest } from '#/db'
+
import { createIngester } from '#/ingester'
+
import { env } from '#/env'
+
import {
+
BidirectionalResolver,
+
createBidirectionalResolver,
+
} from '#/id-resolver'
+
+
/**
+
* Application state passed to the router and elsewhere
+
*/
+
export type AppContext = {
+
db: Database
+
ingester: Firehose
+
logger: pino.Logger
+
oauthClient: NodeOAuthClient
+
resolver: BidirectionalResolver
+
destroy: () => Promise<void>
+
}
+
+
export async function createAppContext(): Promise<AppContext> {
+
const db = createDb(env.DB_PATH)
+
await migrateToLatest(db)
+
const oauthClient = await createOAuthClient(db)
+
const ingester = createIngester(db)
+
const logger = pino({ name: 'server', level: env.LOG_LEVEL })
+
const resolver = createBidirectionalResolver(oauthClient)
+
+
return {
+
db,
+
ingester,
+
logger,
+
oauthClient,
+
resolver,
+
+
async destroy() {
+
await ingester.destroy()
+
await db.destroy()
+
},
+
}
+
}
-20
src/db/index.ts
···
-
import SqliteDb from 'better-sqlite3'
-
import { Kysely, Migrator, SqliteDialect } from 'kysely'
-
import { migrationProvider } from './migrations'
-
import type { DatabaseSchema } from './schema'
-
-
export const createDb = (location: string): Database => {
-
return new Kysely<DatabaseSchema>({
-
dialect: new SqliteDialect({
-
database: new SqliteDb(location),
-
}),
-
})
-
}
-
-
export const migrateToLatest = async (db: Database) => {
-
const migrator = new Migrator({ db, provider: migrationProvider })
-
const { error } = await migrator.migrateToLatest()
-
if (error) throw error
-
}
-
-
export type Database = Kysely<DatabaseSchema>
-36
src/db/migrations.ts
···
-
import type { Kysely, Migration, MigrationProvider } from 'kysely'
-
-
const migrations: Record<string, Migration> = {}
-
-
export const migrationProvider: MigrationProvider = {
-
async getMigrations() {
-
return migrations
-
},
-
}
-
-
migrations['001'] = {
-
async up(db: Kysely<unknown>) {
-
await db.schema
-
.createTable('status')
-
.addColumn('authorDid', 'varchar', (col) => col.primaryKey())
-
.addColumn('status', 'varchar', (col) => col.notNull())
-
.addColumn('updatedAt', 'varchar', (col) => col.notNull())
-
.addColumn('indexedAt', 'varchar', (col) => col.notNull())
-
.execute()
-
await db.schema
-
.createTable('auth_session')
-
.addColumn('key', 'varchar', (col) => col.primaryKey())
-
.addColumn('session', 'varchar', (col) => col.notNull())
-
.execute()
-
await db.schema
-
.createTable('auth_state')
-
.addColumn('key', 'varchar', (col) => col.primaryKey())
-
.addColumn('state', 'varchar', (col) => col.notNull())
-
.execute()
-
},
-
async down(db: Kysely<unknown>) {
-
await db.schema.dropTable('auth_state').execute()
-
await db.schema.dropTable('auth_session').execute()
-
await db.schema.dropTable('status').execute()
-
},
-
}
-26
src/db/schema.ts
···
-
export type DatabaseSchema = {
-
status: Status
-
auth_session: AuthSession
-
auth_state: AuthState
-
}
-
-
export type Status = {
-
authorDid: string
-
status: string
-
updatedAt: string
-
indexedAt: string
-
}
-
-
export type AuthSession = {
-
key: string
-
session: AuthSessionJson
-
}
-
-
export type AuthState = {
-
key: string
-
state: AuthStateJson
-
}
-
-
type AuthStateJson = string
-
-
type AuthSessionJson = string
+94
src/db.ts
···
+
import SqliteDb from 'better-sqlite3'
+
import {
+
Kysely,
+
Migrator,
+
SqliteDialect,
+
Migration,
+
MigrationProvider,
+
} from 'kysely'
+
+
// Types
+
+
export type DatabaseSchema = {
+
status: Status
+
auth_session: AuthSession
+
auth_state: AuthState
+
}
+
+
export type Status = {
+
uri: string
+
authorDid: string
+
status: string
+
createdAt: string
+
indexedAt: string
+
}
+
+
export type AuthSession = {
+
key: string
+
session: AuthSessionJson
+
}
+
+
export type AuthState = {
+
key: string
+
state: AuthStateJson
+
}
+
+
type AuthStateJson = string
+
+
type AuthSessionJson = string
+
+
// Migrations
+
+
const migrations: Record<string, Migration> = {}
+
+
const migrationProvider: MigrationProvider = {
+
async getMigrations() {
+
return migrations
+
},
+
}
+
+
migrations['001'] = {
+
async up(db: Kysely<unknown>) {
+
await db.schema
+
.createTable('status')
+
.addColumn('uri', 'varchar', (col) => col.primaryKey())
+
.addColumn('authorDid', 'varchar', (col) => col.notNull())
+
.addColumn('status', 'varchar', (col) => col.notNull())
+
.addColumn('createdAt', 'varchar', (col) => col.notNull())
+
.addColumn('indexedAt', 'varchar', (col) => col.notNull())
+
.execute()
+
await db.schema
+
.createTable('auth_session')
+
.addColumn('key', 'varchar', (col) => col.primaryKey())
+
.addColumn('session', 'varchar', (col) => col.notNull())
+
.execute()
+
await db.schema
+
.createTable('auth_state')
+
.addColumn('key', 'varchar', (col) => col.primaryKey())
+
.addColumn('state', 'varchar', (col) => col.notNull())
+
.execute()
+
},
+
async down(db: Kysely<unknown>) {
+
await db.schema.dropTable('auth_state').execute()
+
await db.schema.dropTable('auth_session').execute()
+
await db.schema.dropTable('status').execute()
+
},
+
}
+
+
// APIs
+
+
export const createDb = (location: string): Database => {
+
return new Kysely<DatabaseSchema>({
+
dialect: new SqliteDialect({
+
database: new SqliteDb(location),
+
}),
+
})
+
}
+
+
export const migrateToLatest = async (db: Database) => {
+
const migrator = new Migrator({ db, provider: migrationProvider })
+
const { error } = await migrator.migrateToLatest()
+
if (error) throw error
+
}
+
+
export type Database = Kysely<DatabaseSchema>
+25
src/env.ts
···
+
import dotenv from 'dotenv'
+
import { cleanEnv, port, str, testOnly, url } from 'envalid'
+
import { envalidJsonWebKeys as keys } from '#/lib/jwk'
+
+
dotenv.config()
+
+
export const env = cleanEnv(process.env, {
+
NODE_ENV: str({
+
devDefault: testOnly('test'),
+
choices: ['development', 'production', 'test'],
+
}),
+
PORT: port({ devDefault: testOnly(3000) }),
+
PUBLIC_URL: url({ default: undefined }),
+
DB_PATH: str({ devDefault: ':memory:' }),
+
COOKIE_SECRET: str({ devDefault: '00000000000000000000000000000000' }),
+
PRIVATE_KEYS: keys({ default: undefined }),
+
LOG_LEVEL: str({
+
devDefault: 'debug',
+
default: 'info',
+
choices: ['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent'],
+
}),
+
PDS_URL: url({ default: undefined }),
+
PLC_URL: url({ default: undefined }),
+
FIREHOSE_URL: url({ default: undefined }),
+
})
+24 -29
src/id-resolver.ts
···
-
import { IdResolver, MemoryCache } from '@atproto/identity'
-
-
const HOUR = 60e3 * 60
-
const DAY = HOUR * 24
-
-
-
export function createIdResolver() {
-
return new IdResolver({
-
didCache: new MemoryCache(HOUR, DAY),
-
})
-
}
+
import { OAuthClient } from '@atproto/oauth-client-node'
export interface BidirectionalResolver {
-
resolveDidToHandle(did: string): Promise<string>
-
resolveDidsToHandles(dids: string[]): Promise<Record<string, string>>
+
resolveDidToHandle(did: string): Promise<string | undefined>
+
resolveDidsToHandles(
+
dids: string[],
+
): Promise<Record<string, string | undefined>>
}
-
export function createBidirectionalResolver(resolver: IdResolver) {
+
export function createBidirectionalResolver({
+
identityResolver,
+
}: OAuthClient): BidirectionalResolver {
return {
-
async resolveDidToHandle(did: string): Promise<string> {
-
const didDoc = await resolver.did.resolveAtprotoData(did)
-
const resolvedHandle = await resolver.handle.resolve(didDoc.handle)
-
if (resolvedHandle === did) {
-
return didDoc.handle
+
async resolveDidToHandle(did: string): Promise<string | undefined> {
+
try {
+
const { handle } = await identityResolver.resolve(did)
+
if (handle) return handle
+
} catch {
+
// Ignore
}
-
return did
},
async resolveDidsToHandles(
-
dids: string[]
-
): Promise<Record<string, string>> {
-
const didHandleMap: Record<string, string> = {}
-
const resolves = await Promise.all(
-
dids.map((did) => this.resolveDidToHandle(did).catch((_) => did))
+
dids: string[],
+
): Promise<Record<string, string | undefined>> {
+
const uniqueDids = [...new Set(dids)]
+
+
return Object.fromEntries(
+
await Promise.all(
+
uniqueDids.map((did) =>
+
this.resolveDidToHandle(did).then((handle) => [did, handle]),
+
),
+
),
)
-
for (let i = 0; i < dids.length; i++) {
-
didHandleMap[dids[i]] = resolves[i]
-
}
-
return didHandleMap
},
}
}
+24 -91
src/index.ts
···
-
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 { once } from 'node:events'
-
import { createDb, migrateToLatest } from '#/db'
-
import { env } from '#/lib/env'
-
import { createIngester } from '#/ingester'
+
import { createAppContext } from '#/context'
+
import { env } from '#/env'
+
import { startServer } from '#/lib/http'
+
import { run } from '#/lib/process'
import { createRouter } from '#/routes'
-
import { createClient } from '#/auth/client'
-
import { createBidirectionalResolver, createIdResolver, BidirectionalResolver } from '#/id-resolver'
-
import type { Database } from '#/db'
-
import { IdResolver, MemoryCache } from '@atproto/identity'
-
// Application state passed to the router and elsewhere
-
export type AppContext = {
-
db: Database
-
ingester: Firehose
-
logger: pino.Logger
-
oauthClient: OAuthClient
-
resolver: BidirectionalResolver
-
}
+
run(async (killSignal) => {
+
// Create the application context
+
const ctx = await createAppContext()
-
export class Server {
-
constructor(
-
public app: express.Application,
-
public server: http.Server,
-
public ctx: AppContext
-
) {}
+
// Create the HTTP router
+
const router = createRouter(ctx)
-
static async create() {
-
const { NODE_ENV, HOST, PORT, DB_PATH } = env
-
const logger = pino({ name: 'server start' })
+
// Start the HTTP server
+
const { terminate } = await startServer(router, { port: env.PORT })
-
// Set up the SQLite database
-
const db = createDb(DB_PATH)
-
await migrateToLatest(db)
+
const url = env.PUBLIC_URL || `http://localhost:${env.PORT}`
+
ctx.logger.info(`Server (${env.NODE_ENV}) running at ${url}`)
-
// Create the atproto utilities
-
const oauthClient = await createClient(db)
-
const baseIdResolver = createIdResolver()
-
const ingester = createIngester(db, baseIdResolver)
-
const resolver = createBidirectionalResolver(baseIdResolver)
-
const ctx = {
-
db,
-
ingester,
-
logger,
-
oauthClient,
-
resolver,
-
}
+
// Subscribe to events on the firehose
+
ctx.ingester.start()
-
// Subscribe to events on the firehose
-
ingester.start()
+
// Wait for a termination signal
+
if (!killSignal.aborted) await once(killSignal, 'abort')
+
ctx.logger.info(`Signal received, shutting down...`)
-
// Create our server
-
const app: Express = express()
-
app.set('trust proxy', true)
+
// Gracefully shutdown the http server
+
await terminate()
-
// Routes & middlewares
-
const router = createRouter(ctx)
-
app.use(express.json())
-
app.use(express.urlencoded({ extended: true }))
-
app.use(router)
-
app.use((_req, res) => res.sendStatus(404))
-
-
// Bind our server to the port
-
const server = app.listen(env.PORT)
-
await events.once(server, 'listening')
-
logger.info(`Server (${NODE_ENV}) running on port http://${HOST}:${PORT}`)
-
-
return new Server(app, server, ctx)
-
}
-
-
async close() {
-
this.ctx.logger.info('sigint received, shutting down')
-
await this.ctx.ingester.destroy()
-
return new Promise<void>((resolve) => {
-
this.server.close(() => {
-
this.ctx.logger.info('server closed')
-
resolve()
-
})
-
})
-
}
-
}
-
-
const run = async () => {
-
const server = await Server.create()
-
-
const onCloseSignal = async () => {
-
setTimeout(() => process.exit(1), 10000).unref() // Force shutdown after 10s
-
await server.close()
-
process.exit()
-
}
-
-
process.on('SIGINT', onCloseSignal)
-
process.on('SIGTERM', onCloseSignal)
-
}
-
-
run()
+
// Gracefully shutdown the application context
+
await ctx.destroy()
+
})
+47 -19
src/ingester.ts
···
+
import type { Database } from '#/db'
+
import * as Status from '#/lexicon/types/xyz/statusphere/status'
+
import { IdResolver, MemoryCache } from '@atproto/identity'
+
import { Event, Firehose } from '@atproto/sync'
import pino from 'pino'
-
import { IdResolver } from '@atproto/identity'
-
import { Firehose } from '@atproto/sync'
-
import type { Database } from '#/db'
-
import * as Status from '#/lexicon/types/com/example/status'
+
import { env } from './env'
+
+
const HOUR = 60e3 * 60
+
const DAY = HOUR * 24
-
export function createIngester(db: Database, idResolver: IdResolver) {
-
const logger = pino({ name: 'firehose ingestion' })
+
export function createIngester(db: Database) {
+
const logger = pino({ name: 'firehose', level: env.LOG_LEVEL })
return new Firehose({
-
idResolver,
-
handleEvent: async(evt) => {
+
filterCollections: ['xyz.statusphere.status'],
+
handleEvent: async (evt: Event) => {
// Watch for write events
if (evt.event === 'create' || evt.event === 'update') {
+
const now = new Date()
const record = evt.record
// If the write is a valid status update
if (
-
evt.collection === 'com.example.status' &&
+
evt.collection === 'xyz.statusphere.status' &&
Status.isRecord(record) &&
Status.validateRecord(record).success
) {
+
logger.debug(
+
{ uri: evt.uri.toString(), status: record.status },
+
'ingesting status',
+
)
+
// Store the status in our SQLite
await db
.insertInto('status')
.values({
+
uri: evt.uri.toString(),
authorDid: evt.did,
status: record.status,
-
updatedAt: record.updatedAt,
-
indexedAt: new Date().toISOString(),
+
createdAt: record.createdAt,
+
indexedAt: now.toISOString(),
})
.onConflict((oc) =>
-
oc.column('authorDid').doUpdateSet({
+
oc.column('uri').doUpdateSet({
status: record.status,
-
updatedAt: record.updatedAt,
-
indexedAt: new Date().toISOString(),
-
})
+
indexedAt: now.toISOString(),
+
}),
)
.execute()
}
+
} else if (
+
evt.event === 'delete' &&
+
evt.collection === 'xyz.statusphere.status'
+
) {
+
logger.debug(
+
{ uri: evt.uri.toString(), did: evt.did },
+
'deleting status',
+
)
+
+
// Remove the status from our SQLite
+
await db
+
.deleteFrom('status')
+
.where('uri', '=', evt.uri.toString())
+
.execute()
}
},
-
onError: (err) => {
-
logger.error({err}, 'error on firehose ingestion')
+
onError: (err: unknown) => {
+
logger.error({ err }, 'error on firehose ingestion')
},
-
filterCollections: ['com.example.status'],
excludeIdentity: true,
excludeAccount: true,
+
service: env.FIREHOSE_URL,
+
idResolver: new IdResolver({
+
plcUrl: env.PLC_URL,
+
didCache: new MemoryCache(HOUR, DAY),
+
}),
})
-
}
+
}
+63 -3
src/lexicon/index.ts
···
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
-
example: ComExampleNS
+
atproto: ComAtprotoNS
constructor(server: Server) {
this._server = server
-
this.example = new ComExampleNS(server)
+
this.atproto = new ComAtprotoNS(server)
}
}
-
export class ComExampleNS {
+
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) {
+265 -5
src/lexicon/lexicons.ts
···
import { LexiconDoc, Lexicons } from '@atproto/lexicon'
export const schemaDict = {
-
ComExampleStatus: {
+
ComAtprotoLabelDefs: {
lexicon: 1,
-
id: 'com.example.status',
+
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',
-
required: ['status', 'updatedAt'],
+
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',
···
maxGraphemes: 1,
maxLength: 32,
},
-
updatedAt: {
+
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',
+
},
+
},
+
},
+
},
+
},
}
export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[]
export const lexicons: Lexicons = new Lexicons(schemas)
-
export const ids = { ComExampleStatus: 'com.example.status' }
+
export const ids = {
+
ComAtprotoLabelDefs: 'com.atproto.label.defs',
+
AppBskyActorProfile: 'app.bsky.actor.profile',
+
XyzStatusphereStatus: 'xyz.statusphere.status',
+
ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef',
+
}
+38
src/lexicon/types/app/bsky/actor/profile.ts
···
+
/**
+
* GENERATED CODE - DO NOT MODIFY
+
*/
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
+
import { lexicons } from '../../../../lexicons'
+
import { isObj, hasProp } from '../../../../util'
+
import { CID } from 'multiformats/cid'
+
import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs'
+
import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef'
+
+
export interface Record {
+
displayName?: string
+
/** Free-form profile description text. */
+
description?: string
+
/** Small image to be displayed next to posts from account. AKA, 'profile picture' */
+
avatar?: BlobRef
+
/** Larger horizontal image to display behind profile view. */
+
banner?: BlobRef
+
labels?:
+
| ComAtprotoLabelDefs.SelfLabels
+
| { $type: string; [k: string]: unknown }
+
joinedViaStarterPack?: ComAtprotoRepoStrongRef.Main
+
createdAt?: string
+
[k: string]: unknown
+
}
+
+
export function isRecord(v: unknown): v is Record {
+
return (
+
isObj(v) &&
+
hasProp(v, '$type') &&
+
(v.$type === 'app.bsky.actor.profile#main' ||
+
v.$type === 'app.bsky.actor.profile')
+
)
+
}
+
+
export function validateRecord(v: unknown): ValidationResult {
+
return lexicons.validate('app.bsky.actor.profile#main', v)
+
}
+151
src/lexicon/types/com/atproto/label/defs.ts
···
+
/**
+
* GENERATED CODE - DO NOT MODIFY
+
*/
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
+
import { lexicons } from '../../../../lexicons'
+
import { isObj, hasProp } from '../../../../util'
+
import { CID } from 'multiformats/cid'
+
+
/** Metadata tag on an atproto resource (eg, repo or record). */
+
export interface Label {
+
/** The AT Protocol version of the label object. */
+
ver?: number
+
/** DID of the actor who created this label. */
+
src: string
+
/** AT URI of the record, repository (account), or other resource that this label applies to. */
+
uri: string
+
/** Optionally, CID specifying the specific version of 'uri' resource this label applies to. */
+
cid?: string
+
/** The short string name of the value or type of this label. */
+
val: string
+
/** If true, this is a negation label, overwriting a previous label. */
+
neg?: boolean
+
/** Timestamp when this label was created. */
+
cts: string
+
/** Timestamp at which this label expires (no longer applies). */
+
exp?: string
+
/** Signature of dag-cbor encoded label. */
+
sig?: Uint8Array
+
[k: string]: unknown
+
}
+
+
export function isLabel(v: unknown): v is Label {
+
return (
+
isObj(v) &&
+
hasProp(v, '$type') &&
+
v.$type === 'com.atproto.label.defs#label'
+
)
+
}
+
+
export function validateLabel(v: unknown): ValidationResult {
+
return lexicons.validate('com.atproto.label.defs#label', v)
+
}
+
+
/** Metadata tags on an atproto record, published by the author within the record. */
+
export interface SelfLabels {
+
values: SelfLabel[]
+
[k: string]: unknown
+
}
+
+
export function isSelfLabels(v: unknown): v is SelfLabels {
+
return (
+
isObj(v) &&
+
hasProp(v, '$type') &&
+
v.$type === 'com.atproto.label.defs#selfLabels'
+
)
+
}
+
+
export function validateSelfLabels(v: unknown): ValidationResult {
+
return lexicons.validate('com.atproto.label.defs#selfLabels', v)
+
}
+
+
/** Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel. */
+
export interface SelfLabel {
+
/** The short string name of the value or type of this label. */
+
val: string
+
[k: string]: unknown
+
}
+
+
export function isSelfLabel(v: unknown): v is SelfLabel {
+
return (
+
isObj(v) &&
+
hasProp(v, '$type') &&
+
v.$type === 'com.atproto.label.defs#selfLabel'
+
)
+
}
+
+
export function validateSelfLabel(v: unknown): ValidationResult {
+
return lexicons.validate('com.atproto.label.defs#selfLabel', v)
+
}
+
+
/** Declares a label value and its expected interpretations and behaviors. */
+
export interface LabelValueDefinition {
+
/** The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). */
+
identifier: string
+
/** How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing. */
+
severity: 'inform' | 'alert' | 'none' | (string & {})
+
/** 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. */
+
blurs: 'content' | 'media' | 'none' | (string & {})
+
/** The default setting for this label. */
+
defaultSetting: 'ignore' | 'warn' | 'hide' | (string & {})
+
/** Does the user need to have adult content enabled in order to configure this label? */
+
adultOnly?: boolean
+
locales: LabelValueDefinitionStrings[]
+
[k: string]: unknown
+
}
+
+
export function isLabelValueDefinition(v: unknown): v is LabelValueDefinition {
+
return (
+
isObj(v) &&
+
hasProp(v, '$type') &&
+
v.$type === 'com.atproto.label.defs#labelValueDefinition'
+
)
+
}
+
+
export function validateLabelValueDefinition(v: unknown): ValidationResult {
+
return lexicons.validate('com.atproto.label.defs#labelValueDefinition', v)
+
}
+
+
/** Strings which describe the label in the UI, localized into a specific language. */
+
export interface LabelValueDefinitionStrings {
+
/** The code of the language these strings are written in. */
+
lang: string
+
/** A short human-readable name for the label. */
+
name: string
+
/** A longer description of what the label means and why it might be applied. */
+
description: string
+
[k: string]: unknown
+
}
+
+
export function isLabelValueDefinitionStrings(
+
v: unknown,
+
): v is LabelValueDefinitionStrings {
+
return (
+
isObj(v) &&
+
hasProp(v, '$type') &&
+
v.$type === 'com.atproto.label.defs#labelValueDefinitionStrings'
+
)
+
}
+
+
export function validateLabelValueDefinitionStrings(
+
v: unknown,
+
): ValidationResult {
+
return lexicons.validate(
+
'com.atproto.label.defs#labelValueDefinitionStrings',
+
v,
+
)
+
}
+
+
export type LabelValue =
+
| '!hide'
+
| '!no-promote'
+
| '!warn'
+
| '!no-unauthenticated'
+
| 'dmca-violation'
+
| 'doxxing'
+
| 'porn'
+
| 'sexual'
+
| 'nudity'
+
| 'nsfl'
+
| 'gore'
+
| (string & {})
+26
src/lexicon/types/com/atproto/repo/strongRef.ts
···
+
/**
+
* GENERATED CODE - DO NOT MODIFY
+
*/
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
+
import { lexicons } from '../../../../lexicons'
+
import { isObj, hasProp } from '../../../../util'
+
import { CID } from 'multiformats/cid'
+
+
export interface Main {
+
uri: string
+
cid: string
+
[k: string]: unknown
+
}
+
+
export function isMain(v: unknown): v is Main {
+
return (
+
isObj(v) &&
+
hasProp(v, '$type') &&
+
(v.$type === 'com.atproto.repo.strongRef#main' ||
+
v.$type === 'com.atproto.repo.strongRef')
+
)
+
}
+
+
export function validateMain(v: unknown): ValidationResult {
+
return lexicons.validate('com.atproto.repo.strongRef#main', v)
+
}
-25
src/lexicon/types/com/example/status.ts
···
-
/**
-
* GENERATED CODE - DO NOT MODIFY
-
*/
-
import { ValidationResult, BlobRef } from '@atproto/lexicon'
-
import { lexicons } from '../../../lexicons'
-
import { isObj, hasProp } from '../../../util'
-
import { CID } from 'multiformats/cid'
-
-
export interface Record {
-
status: string
-
updatedAt: string
-
[k: string]: unknown
-
}
-
-
export function isRecord(v: unknown): v is Record {
-
return (
-
isObj(v) &&
-
hasProp(v, '$type') &&
-
(v.$type === 'com.example.status#main' || v.$type === 'com.example.status')
-
)
-
}
-
-
export function validateRecord(v: unknown): ValidationResult {
-
return lexicons.validate('com.example.status#main', v)
-
}
+26
src/lexicon/types/xyz/statusphere/status.ts
···
+
/**
+
* GENERATED CODE - DO NOT MODIFY
+
*/
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
+
import { lexicons } from '../../../lexicons'
+
import { isObj, hasProp } from '../../../util'
+
import { CID } from 'multiformats/cid'
+
+
export interface Record {
+
status: string
+
createdAt: string
+
[k: string]: unknown
+
}
+
+
export function isRecord(v: unknown): v is Record {
+
return (
+
isObj(v) &&
+
hasProp(v, '$type') &&
+
(v.$type === 'xyz.statusphere.status#main' ||
+
v.$type === 'xyz.statusphere.status')
+
)
+
}
+
+
export function validateRecord(v: unknown): ValidationResult {
+
return lexicons.validate('xyz.statusphere.status#main', v)
+
}
-18
src/lib/env.ts
···
-
import dotenv from 'dotenv'
-
import { cleanEnv, host, num, port, str, testOnly } from 'envalid'
-
-
dotenv.config()
-
-
export const env = cleanEnv(process.env, {
-
NODE_ENV: str({
-
devDefault: testOnly('test'),
-
choices: ['development', 'production', 'test'],
-
}),
-
HOST: host({ devDefault: testOnly('localhost') }),
-
PORT: port({ devDefault: testOnly(3000) }),
-
PUBLIC_URL: str({}),
-
DB_PATH: str({ devDefault: ':memory:' }),
-
COOKIE_SECRET: str({ devDefault: '00000000000000000000000000000000' }),
-
COMMON_RATE_LIMIT_MAX_REQUESTS: num({ devDefault: testOnly(1000) }),
-
COMMON_RATE_LIMIT_WINDOW_MS: num({ devDefault: testOnly(1000) }),
-
})
+59
src/lib/http.ts
···
+
import { Request, Response } from 'express'
+
import { createHttpTerminator } from 'http-terminator'
+
import { once } from 'node:events'
+
import type {
+
IncomingMessage,
+
RequestListener,
+
ServerResponse,
+
} from 'node:http'
+
import { createServer } from 'node:http'
+
+
export type NextFunction = (err?: unknown) => void
+
+
export type Middleware<
+
Req extends IncomingMessage = IncomingMessage,
+
Res extends ServerResponse = ServerResponse,
+
> = (req: Req, res: Res, next: NextFunction) => void
+
+
export type Handler<
+
Req extends IncomingMessage = IncomingMessage,
+
Res extends ServerResponse = ServerResponse,
+
> = (req: Req, res: Res) => unknown | Promise<unknown>
+
/**
+
* Wraps a request handler middleware to ensure that `next` is called if it
+
* throws or returns a promise that rejects.
+
*/
+
export function handler<
+
Req extends IncomingMessage = Request,
+
Res extends ServerResponse = Response,
+
>(fn: Handler<Req, Res>): Middleware<Req, Res> {
+
return async (req, res, next) => {
+
try {
+
await fn(req, res)
+
} catch (err) {
+
next(err)
+
}
+
}
+
}
+
+
/**
+
* Create an HTTP server with the provided request listener, ensuring that it
+
* can bind the listening port, and returns a termination function that allows
+
* graceful termination of HTTP connections.
+
*/
+
export async function startServer(
+
requestListener: RequestListener,
+
{
+
port,
+
gracefulTerminationTimeout,
+
}: { port?: number; gracefulTerminationTimeout?: number } = {},
+
) {
+
const server = createServer(requestListener)
+
const { terminate } = createHttpTerminator({
+
gracefulTerminationTimeout,
+
server,
+
})
+
server.listen(port)
+
await once(server, 'listening')
+
return { server, terminate }
+
}
+17
src/lib/jwk.ts
···
+
import { Jwk, jwkValidator } from '@atproto/oauth-client-node'
+
import { makeValidator } from 'envalid'
+
import { z } from 'zod'
+
+
export type JsonWebKey = Jwk & { kid: string }
+
+
const jsonWebKeySchema = z.intersection(
+
jwkValidator,
+
z.object({ kid: z.string().nonempty() }),
+
) satisfies z.ZodType<JsonWebKey>
+
+
const jsonWebKeysSchema = z.array(jsonWebKeySchema).nonempty()
+
+
export const envalidJsonWebKeys = makeValidator((input) => {
+
const value = JSON.parse(input)
+
return jsonWebKeysSchema.parse(value)
+
})
+24
src/lib/process.ts
···
+
const SIGNALS = ['SIGINT', 'SIGTERM'] as const
+
+
/**
+
* Runs a function with an abort signal that will be triggered when the process
+
* receives a termination signal.
+
*/
+
export async function run<F extends (signal: AbortSignal) => Promise<void>>(
+
fn: F,
+
): Promise<void> {
+
const killController = new AbortController()
+
+
const abort = (signal?: string) => {
+
for (const sig of SIGNALS) process.off(sig, abort)
+
killController.abort(signal)
+
}
+
+
for (const sig of SIGNALS) process.on(sig, abort)
+
+
try {
+
await fn(killController.signal)
+
} finally {
+
abort()
+
}
+
}
+4
src/lib/util.ts
···
+
export function ifString<T>(value: T): (T & string) | undefined {
+
if (typeof value === 'string') return value
+
return undefined
+
}
+14 -16
src/pages/home.ts
···
-
import type { Status } from '#/db/schema'
+
import type { Status } from '#/db'
import { html } from '../lib/view'
import { shell } from './shell'
const TODAY = new Date().toDateString()
-
const STATUS_OPTIONS = [
+
export const STATUS_OPTIONS = [
'๐Ÿ‘',
'๐Ÿ‘Ž',
'๐Ÿ’™',
'๐Ÿฅน',
'๐Ÿ˜ง',
-
'๐Ÿ˜ค',
'๐Ÿ™ƒ',
'๐Ÿ˜‰',
'๐Ÿ˜Ž',
···
type Props = {
statuses: Status[]
-
didHandleMap: Record<string, string>
-
profile?: { displayName?: string; handle: string }
+
didHandleMap: Record<string, string | undefined>
+
profile?: { displayName?: string }
myStatus?: Status
}
···
${profile
? html`<form action="/logout" method="post" class="session-form">
<div>
-
Hi, <strong>${profile.displayName || profile.handle}</strong>.
-
what's your status today?
+
Hi, <strong>${profile.displayName || 'friend'}</strong>. What's
+
your status today?
</div>
<div>
<button type="submit">Log out</button>
···
</div>
</div>`}
</div>
-
<div class="status-options">
+
<form action="/status" method="post" class="status-options">
${STATUS_OPTIONS.map(
(status) =>
-
html`<div
+
html`<button
class=${myStatus?.status === status
? 'status-option selected'
: 'status-option'}
-
data-value="${status}"
-
data-authed=${profile ? '1' : '0'}
+
name="status"
+
value="${status}"
>
${status}
-
</div>`
+
</button>`,
)}
-
</div>
+
</form>
${statuses.map((status, i) => {
const handle = didHandleMap[status.authorDid] || status.authorDid
const date = ts(status)
···
`
})}
</div>
-
<script src="/public/home.js"></script>
</div>`
}
···
}
function ts(status: Status) {
+
const createdAt = new Date(status.createdAt)
const indexedAt = new Date(status.indexedAt)
-
const updatedAt = new Date(status.updatedAt)
-
if (updatedAt > indexedAt) return updatedAt.toDateString()
+
if (createdAt < indexedAt) return createdAt.toDateString()
return indexedAt.toDateString()
}
+14 -6
src/pages/login.ts
···
+
import { env } from '#/env'
import { html } from '../lib/view'
import { shell } from './shell'
···
}
function content({ error }: Props) {
+
const signupService =
+
!env.PDS_URL || env.PDS_URL === 'https://bsky.social'
+
? 'Bluesky'
+
: new URL(env.PDS_URL).hostname
+
return html`<div id="root">
<div id="header">
<h1>Statusphere</h1>
···
<form action="/login" method="post" class="login-form">
<input
type="text"
-
name="handle"
+
name="input"
placeholder="Enter your handle (eg alice.bsky.social)"
required
/>
+
<button type="submit">Log in</button>
-
${error ? html`<p>Error: <i>${error}</i></p>` : undefined}
</form>
-
<div class="signup-cta">
-
Don't have an account on the Atmosphere?
-
<a href="https://bsky.app">Sign up for Bluesky</a> to create one now!
-
</div>
+
+
<a href="/signup" class="button signup-cta">
+
Login or Sign up with a ${signupService} account
+
</a>
+
+
${error ? html`<p>Error: <i>${error}</i></p>` : undefined}
</div>
</div>`
}
-32
src/pages/public/home.js
···
-
Array.from(document.querySelectorAll('.status-option'), (el) => {
-
el.addEventListener('click', async (ev) => {
-
setError('')
-
-
if (el.dataset.authed !== '1') {
-
window.location = '/login'
-
return
-
}
-
-
const res = await fetch('/status', {
-
method: 'POST',
-
headers: { 'content-type': 'application/json' },
-
body: JSON.stringify({ status: el.dataset.value }),
-
})
-
const body = await res.json()
-
if (body?.error) {
-
setError(body.error)
-
} else {
-
location.reload()
-
}
-
})
-
})
-
-
function setError(str) {
-
const errMsg = document.querySelector('.error')
-
if (str) {
-
errMsg.classList.add('visible')
-
errMsg.textContent = str
-
} else {
-
errMsg.classList.remove('visible')
-
}
-
}
+29 -9
src/pages/public/styles.css
···
Josh's Custom CSS Reset
https://www.joshwcomeau.com/css/custom-css-reset/
*/
-
*, *::before, *::after {
+
*,
+
*::before,
+
*::after {
box-sizing: border-box;
}
* {
···
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
-
img, picture, video, canvas, svg {
+
img,
+
picture,
+
video,
+
canvas,
+
svg {
display: block;
max-width: 100%;
}
-
input, button, textarea, select {
+
input,
+
button,
+
textarea,
+
select {
font: inherit;
}
-
p, h1, h2, h3, h4, h5, h6 {
+
p,
+
h1,
+
h2,
+
h3,
+
h4,
+
h5,
+
h6 {
overflow-wrap: break-word;
}
-
#root, #__next {
+
#root,
+
#__next {
isolation: isolate;
}
/*
Common components
*/
-
button, .button {
+
button,
+
.button {
display: inline-block;
border: 0;
background-color: var(--primary-500);
···
cursor: pointer;
text-decoration: none;
}
-
button:hover, .button:hover {
+
button:hover,
+
.button:hover {
background: var(--primary-400);
}
···
font-size: 2rem;
width: 3rem;
height: 3rem;
+
padding: 0;
background-color: #fff;
border: 1px solid var(--border-color);
border-radius: 3rem;
···
.signup-cta {
text-align: center;
-
text-wrap: balance;
+
width: 100%;
+
display: block;
margin-top: 1rem;
-
}
+
}
+214 -77
src/routes.ts
···
+
import { Agent } from '@atproto/api'
+
import { TID } from '@atproto/common'
+
import { OAuthResolverError } from '@atproto/oauth-client-node'
+
import express, { Request, Response } from 'express'
+
import { getIronSession } from 'iron-session'
+
import type {
+
IncomingMessage,
+
RequestListener,
+
ServerResponse,
+
} from 'node:http'
import path from 'node:path'
-
import { OAuthResolverError } from '@atproto/oauth-client-node'
-
import { isValidHandle } from '@atproto/syntax'
-
import express from 'express'
-
import { createSession, destroySession, getSessionAgent } from '#/auth/session'
-
import type { AppContext } from '#/index'
+
+
import type { AppContext } from '#/context'
+
import { env } from '#/env'
+
import * as Profile from '#/lexicon/types/app/bsky/actor/profile'
+
import * as Status from '#/lexicon/types/xyz/statusphere/status'
+
import { handler } from '#/lib/http'
+
import { ifString } from '#/lib/util'
+
import { page } from '#/lib/view'
import { home } from '#/pages/home'
import { login } from '#/pages/login'
-
import { page } from '#/lib/view'
-
import * as Status from '#/lexicon/types/com/example/status'
+
+
// Max age, in seconds, for static routes and assets
+
const MAX_AGE = env.NODE_ENV === 'production' ? 60 : 0
+
+
type Session = { did?: string }
+
+
// Helper function to get the Atproto Agent for the active session
+
async function getSessionAgent(
+
req: IncomingMessage,
+
res: ServerResponse,
+
ctx: AppContext,
+
) {
+
res.setHeader('Vary', 'Cookie')
+
+
const session = await getIronSession<Session>(req, res, {
+
cookieName: 'sid',
+
password: env.COOKIE_SECRET,
+
})
+
if (!session.did) return null
-
// Helper function for defining routes
-
const handler =
-
(fn: express.Handler) =>
-
async (
-
req: express.Request,
-
res: express.Response,
-
next: express.NextFunction
-
) => {
-
try {
-
await fn(req, res, next)
-
} catch (err) {
-
next(err)
-
}
+
// This page is dynamic and should not be cached publicly
+
res.setHeader('cache-control', `max-age=${MAX_AGE}, private`)
+
+
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()
+
export const createRouter = (ctx: AppContext): RequestListener => {
+
const router = express()
// Static assets
-
router.use('/public', express.static(path.join(__dirname, 'pages', 'public')))
+
router.use(
+
'/public',
+
express.static(path.join(__dirname, 'pages', 'public'), {
+
maxAge: MAX_AGE * 1000,
+
}),
+
)
// OAuth metadata
router.get(
-
'/client-metadata.json',
-
handler((_req, res) => {
-
return res.json(ctx.oauthClient.clientMetadata)
-
})
+
'/oauth-client-metadata.json',
+
handler((req, res) => {
+
res.setHeader('cache-control', `max-age=${MAX_AGE}, public`)
+
res.json(ctx.oauthClient.clientMetadata)
+
}),
+
)
+
+
// Public keys
+
router.get(
+
'/.well-known/jwks.json',
+
handler((req, res) => {
+
res.setHeader('cache-control', `max-age=${MAX_AGE}, public`)
+
res.json(ctx.oauthClient.jwks)
+
}),
)
// OAuth callback to complete session creation
router.get(
'/oauth/callback',
handler(async (req, res) => {
+
res.setHeader('cache-control', 'no-store')
+
const params = new URLSearchParams(req.originalUrl.split('?')[1])
try {
-
const { agent } = await ctx.oauthClient.callback(params)
-
await createSession(req, res, agent.accountDid)
+
// Load the session cookie
+
const session = await getIronSession<Session>(req, res, {
+
cookieName: 'sid',
+
password: env.COOKIE_SECRET,
+
})
+
+
// If the user is already signed in, destroy the old credentials
+
if (session.did) {
+
try {
+
const oauthSession = await ctx.oauthClient.restore(session.did)
+
if (oauthSession) oauthSession.signOut()
+
} catch (err) {
+
ctx.logger.warn({ err }, 'oauth restore failed')
+
}
+
}
+
+
// Complete the OAuth flow
+
const oauth = await ctx.oauthClient.callback(params)
+
+
// Update the session cookie
+
session.did = oauth.session.did
+
+
await session.save()
} catch (err) {
ctx.logger.error({ err }, 'oauth callback failed')
-
return res.redirect('/?error')
}
+
return res.redirect('/')
-
})
+
}),
)
// Login page
router.get(
'/login',
-
handler(async (_req, res) => {
-
return res.type('html').send(page(login({})))
-
})
+
handler(async (req, res) => {
+
res.setHeader('cache-control', `max-age=${MAX_AGE}, public`)
+
res.type('html').send(page(login({})))
+
}),
)
// Login handler
router.post(
'/login',
+
express.urlencoded(),
handler(async (req, res) => {
-
// Validate
-
const handle = req.body?.handle
-
if (typeof handle !== 'string' || !isValidHandle(handle)) {
-
return res.type('html').send(page(login({ error: 'invalid handle' })))
+
// Never store this route
+
res.setHeader('cache-control', 'no-store')
+
+
// Initiate the OAuth flow
+
try {
+
// Validate input: can be a handle, a DID or a service URL (PDS).
+
const input = ifString(req.body.input)
+
if (!input) {
+
throw new Error('Invalid input')
+
}
+
+
// Initiate the OAuth flow
+
const url = await ctx.oauthClient.authorize(input, {
+
scope: 'atproto transition:generic',
+
})
+
+
res.redirect(url.toString())
+
} catch (err) {
+
ctx.logger.error({ err }, 'oauth authorize failed')
+
+
const error = err instanceof Error ? err.message : 'unexpected error'
+
+
return res.type('html').send(page(login({ error })))
}
+
}),
+
)
-
// Initiate the OAuth flow
+
// Signup
+
router.get(
+
'/signup',
+
handler(async (req, res) => {
+
res.setHeader('cache-control', `max-age=${MAX_AGE}, public`)
+
try {
-
const url = await ctx.oauthClient.authorize(handle)
-
return res.redirect(url.toString())
+
const service = env.PDS_URL ?? 'https://bsky.social'
+
const url = await ctx.oauthClient.authorize(service, {
+
scope: 'atproto transition:generic',
+
})
+
res.redirect(url.toString())
} catch (err) {
ctx.logger.error({ err }, 'oauth authorize failed')
-
return res.type('html').send(
+
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) => {
-
await destroySession(req, res)
+
// Never store this route
+
res.setHeader('cache-control', 'no-store')
+
+
const session = await getIronSession<Session>(req, res, {
+
cookieName: 'sid',
+
password: env.COOKIE_SECRET,
+
})
+
+
// Revoke credentials on the server
+
if (session.did) {
+
try {
+
const oauthSession = await ctx.oauthClient.restore(session.did)
+
if (oauthSession) await oauthSession.signOut()
+
} catch (err) {
+
ctx.logger.warn({ err }, 'Failed to revoke credentials')
+
}
+
}
+
+
session.destroy()
+
return res.redirect('/')
-
})
+
}),
)
// Homepage
···
? await ctx.db
.selectFrom('status')
.selectAll()
-
.where('authorDid', '=', agent.accountDid)
+
.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)
+
statuses.map((s) => s.authorDid),
)
if (!agent) {
···
}
// Fetch additional information about the logged-in user
-
const { data: profile } = await agent.getProfile({
-
actor: agent.accountDid,
-
})
-
didHandleMap[profile.handle] = agent.accountDid
+
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
-
return res
+
res
.type('html')
.send(page(home({ statuses, didHandleMap, profile, myStatus })))
-
})
+
}),
)
// "Set status" handler
router.post(
'/status',
+
express.urlencoded(),
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) {
-
return res.status(401).json({ error: 'Session required' })
+
return res
+
.status(401)
+
.type('html')
+
.send('<h1>Error: Session required</h1>')
}
-
// Construct & validate their status record
+
// Construct their status record
const record = {
-
$type: 'com.example.status',
+
$type: 'xyz.statusphere.status',
status: req.body?.status,
-
updatedAt: new Date().toISOString(),
+
createdAt: new Date().toISOString(),
}
+
+
// Make sure the record generated from the input is valid
if (!Status.validateRecord(record).success) {
-
return res.status(400).json({ error: 'Invalid status' })
+
return res
+
.status(400)
+
.type('html')
+
.send('<h1>Error: Invalid status</h1>')
}
+
let uri
try {
// Write the status record to the user's repository
-
await agent.com.atproto.repo.putRecord({
-
repo: agent.accountDid,
-
collection: 'com.example.status',
-
rkey: 'self',
+
const res = await agent.com.atproto.repo.putRecord({
+
repo: agent.assertDid,
+
collection: 'xyz.statusphere.status',
+
rkey: TID.nextStr(),
record,
validate: false,
})
+
uri = res.data.uri
} catch (err) {
ctx.logger.warn({ err }, 'failed to write record')
-
return res.status(500).json({ error: 'Failed to write record' })
+
return res
+
.status(500)
+
.type('html')
+
.send('<h1>Error: Failed to write record</h1>')
}
try {
···
await ctx.db
.insertInto('status')
.values({
-
authorDid: agent.accountDid,
+
uri,
+
authorDid: agent.assertDid,
status: record.status,
-
updatedAt: record.updatedAt,
+
createdAt: record.createdAt,
indexedAt: new Date().toISOString(),
})
-
.onConflict((oc) =>
-
oc.column('authorDid').doUpdateSet({
-
status: record.status,
-
updatedAt: record.updatedAt,
-
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'
+
'failed to update computed view; ignoring as it should be caught by the firehose',
)
}
-
res.status(200).json({})
-
})
+
return res.redirect('/')
+
}),
)
return router