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"
+
]
+
}
+
}
+
}
+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" }
+
}
+
}
+
}
+
}
+1279 -649
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/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",
-
"pino-http": "^10.0.0",
-
"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.0",
-
"resolved": "https://registry.npmjs.org/@atproto/crypto/-/crypto-0.4.0.tgz",
-
"integrity": "sha512-Kj/4VgJ7hzzXvE42L0rjzP6lM0tai+OfPnP1rxJ+UZg/YUDtuewL4uapnVoWXvlNceKgaLZH98g5n9gXBVTe5Q==",
+
"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.0",
-
"resolved": "https://registry.npmjs.org/@atproto/identity/-/identity-0.4.0.tgz",
-
"integrity": "sha512-KKdVlqBgkFuTUx3KFiiQe0LuK9kopej1bhKm6SHRPEYbSEPFmRZQMY9TAjWJQrvQt8DpQzz6kVGjASFEjd3teQ==",
+
"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.0",
-
"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.26",
+
"resolved": "https://registry.npmjs.org/@atproto/sync/-/sync-0.1.26.tgz",
+
"integrity": "sha512-bpUIajtPrE3RgFW8mIfrI4EM/LJ4JjQhI5fsqc78zCHZawuflpllf1aH70roDWWiskMWoiLWnVRxdYXdeEgbXA==",
+
"license": "MIT",
+
"dependencies": {
+
"@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",
+
"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": {
···
"node": ">=6"
},
+
"node_modules/eventemitter3": {
+
"version": "4.0.7",
+
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
+
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
···
"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=="
-
},
-
"node_modules/get-caller-file": {
-
"version": "2.0.5",
-
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
-
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
-
"engines": {
-
"node": "6.* || 8.* || >= 10.*"
-
}
+
"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"
···
"dev": true,
"engines": {
"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": {
···
"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"
},
···
"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": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
···
"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",
+
"integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==",
+
"engines": {
+
"node": ">=4"
+
}
+
},
+
"node_modules/p-queue": {
+
"version": "6.6.2",
+
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz",
+
"integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==",
+
"dependencies": {
+
"eventemitter3": "^4.0.4",
+
"p-timeout": "^3.2.0"
+
},
+
"engines": {
+
"node": ">=8"
+
},
+
"funding": {
+
"url": "https://github.com/sponsors/sindresorhus"
+
}
+
},
+
"node_modules/p-timeout": {
+
"version": "3.2.0",
+
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
+
"integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
+
"dependencies": {
+
"p-finally": "^1.0.0"
+
},
+
"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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
···
},
"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",
···
"split2": "^4.0.0"
},
-
"node_modules/pino-http": {
-
"version": "10.2.0",
-
"resolved": "https://registry.npmjs.org/pino-http/-/pino-http-10.2.0.tgz",
-
"integrity": "sha512-am03BxnV3Ckx68OkbH0iZs3indsrH78wncQ6w1w51KroIbvJZNImBKX2X1wjdY8lSyaJ0UrX/dnO2DY3cTeCRw==",
-
"dependencies": {
-
"get-caller-file": "^2.0.5",
-
"pino": "^9.0.0",
-
"pino-std-serializers": "^7.0.0",
-
"process-warning": "^3.0.0"
-
}
-
},
-
"node_modules/pino-http/node_modules/process-warning": {
-
"version": "3.0.0",
-
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz",
-
"integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ=="
-
},
"node_modules/pino-pretty": {
"version": "11.2.2",
"resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.2.2.tgz",
···
"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 -7
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/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,
})
}
+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()
+
},
+
}
+
}
+5 -3
src/db.ts
···
}
export type Status = {
+
uri: string
authorDid: string
status: string
-
updatedAt: string
+
createdAt: string
indexedAt: string
}
···
async up(db: Kysely<unknown>) {
await db.schema
.createTable('status')
-
.addColumn('authorDid', 'varchar', (col) => col.primaryKey())
+
.addColumn('uri', 'varchar', (col) => col.primaryKey())
+
.addColumn('authorDid', 'varchar', (col) => col.notNull())
.addColumn('status', 'varchar', (col) => col.notNull())
-
.addColumn('updatedAt', 'varchar', (col) => col.notNull())
+
.addColumn('createdAt', 'varchar', (col) => col.notNull())
.addColumn('indexedAt', 'varchar', (col) => col.notNull())
.execute()
await db.schema
+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 }),
+
})
-194
src/firehose/firehose.ts
···
-
import type { RepoRecord } from '@atproto/lexicon'
-
import { cborToLexRecord, readCar } from '@atproto/repo'
-
import { AtUri } from '@atproto/syntax'
-
import { Subscription } from '@atproto/xrpc-server'
-
import type { CID } from 'multiformats/cid'
-
import {
-
type Account,
-
type Commit,
-
type Identity,
-
type RepoEvent,
-
isAccount,
-
isCommit,
-
isIdentity,
-
isValidRepoEvent,
-
} from './lexicons'
-
-
type Opts = {
-
service?: string
-
getCursor?: () => Promise<number | undefined>
-
setCursor?: (cursor: number) => Promise<void>
-
subscriptionReconnectDelay?: number
-
filterCollections?: string[]
-
excludeIdentity?: boolean
-
excludeAccount?: boolean
-
excludeCommit?: boolean
-
}
-
-
export class Firehose {
-
public sub: Subscription<RepoEvent>
-
private abortController: AbortController
-
-
constructor(public opts: Opts) {
-
this.abortController = new AbortController()
-
this.sub = new Subscription({
-
service: opts.service ?? 'https://bsky.network',
-
method: 'com.atproto.sync.subscribeRepos',
-
signal: this.abortController.signal,
-
getParams: async () => {
-
if (!opts.getCursor) return undefined
-
const cursor = await opts.getCursor()
-
return { cursor }
-
},
-
validate: (value: unknown) => {
-
try {
-
return isValidRepoEvent(value)
-
} catch (err) {
-
console.error('repo subscription skipped invalid message', err)
-
}
-
},
-
})
-
}
-
-
async *run(): AsyncGenerator<Event> {
-
try {
-
for await (const evt of this.sub) {
-
try {
-
if (isCommit(evt) && !this.opts.excludeCommit) {
-
const parsed = await parseCommit(evt)
-
for (const write of parsed) {
-
if (
-
!this.opts.filterCollections ||
-
this.opts.filterCollections.includes(write.uri.collection)
-
) {
-
yield write
-
}
-
}
-
} else if (isAccount(evt) && !this.opts.excludeAccount) {
-
const parsed = parseAccount(evt)
-
if (parsed) {
-
yield parsed
-
}
-
} else if (isIdentity(evt) && !this.opts.excludeIdentity) {
-
yield parseIdentity(evt)
-
}
-
} catch (err) {
-
console.error('repo subscription could not handle message', err)
-
}
-
if (this.opts.setCursor && typeof evt.seq === 'number') {
-
await this.opts.setCursor(evt.seq)
-
}
-
}
-
} catch (err) {
-
console.error('repo subscription errored', err)
-
setTimeout(() => this.run(), this.opts.subscriptionReconnectDelay ?? 3000)
-
}
-
}
-
-
destroy() {
-
this.abortController.abort()
-
}
-
}
-
-
export const parseCommit = async (evt: Commit): Promise<CommitEvt[]> => {
-
const car = await readCar(evt.blocks)
-
-
const evts: CommitEvt[] = []
-
-
for (const op of evt.ops) {
-
const uri = new AtUri(`at://${evt.repo}/${op.path}`)
-
-
const meta: CommitMeta = {
-
uri,
-
author: uri.host,
-
collection: uri.collection,
-
rkey: uri.rkey,
-
}
-
-
if (op.action === 'create' || op.action === 'update') {
-
if (!op.cid) continue
-
const recordBytes = car.blocks.get(op.cid)
-
if (!recordBytes) continue
-
const record = cborToLexRecord(recordBytes)
-
evts.push({
-
...meta,
-
event: op.action as 'create' | 'update',
-
cid: op.cid,
-
record,
-
})
-
}
-
-
if (op.action === 'delete') {
-
evts.push({
-
...meta,
-
event: 'delete',
-
})
-
}
-
}
-
-
return evts
-
}
-
-
export const parseIdentity = (evt: Identity): IdentityEvt => {
-
return {
-
event: 'identity',
-
did: evt.did,
-
handle: evt.handle,
-
}
-
}
-
-
export const parseAccount = (evt: Account): AccountEvt | undefined => {
-
if (evt.status && !isValidStatus(evt.status)) return
-
return {
-
event: 'account',
-
did: evt.did,
-
active: evt.active,
-
status: evt.status as AccountStatus,
-
}
-
}
-
-
const isValidStatus = (str: string): str is AccountStatus => {
-
return ['takendown', 'suspended', 'deleted', 'deactivated'].includes(str)
-
}
-
-
type Event = CommitEvt | IdentityEvt | AccountEvt
-
-
type CommitMeta = {
-
uri: AtUri
-
author: string
-
collection: string
-
rkey: string
-
}
-
-
type CommitEvt = Create | Update | Delete
-
-
type Create = CommitMeta & {
-
event: 'create'
-
record: RepoRecord
-
cid: CID
-
}
-
-
type Update = CommitMeta & {
-
event: 'update'
-
record: RepoRecord
-
cid: CID
-
}
-
-
type Delete = CommitMeta & {
-
event: 'delete'
-
}
-
-
type IdentityEvt = {
-
event: 'identity'
-
did: string
-
handle?: string
-
}
-
-
type AccountEvt = {
-
event: 'account'
-
did: string
-
active: boolean
-
status?: AccountStatus
-
}
-
-
type AccountStatus = 'takendown' | 'suspended' | 'deleted' | 'deactivated'
-48
src/firehose/ingester.ts
···
-
import type { Database } from '#/db'
-
import { Firehose } from '#/firehose/firehose'
-
import * as Status from '#/lexicon/types/com/example/status'
-
-
export class Ingester {
-
firehose: Firehose | undefined
-
constructor(public db: Database) {}
-
-
async start() {
-
const firehose = new Firehose({})
-
-
for await (const evt of firehose.run()) {
-
// Watch for write events
-
if (evt.event === 'create' || evt.event === 'update') {
-
const record = evt.record
-
-
// If the write is a valid status update
-
if (
-
evt.collection === 'com.example.status' &&
-
Status.isRecord(record) &&
-
Status.validateRecord(record).success
-
) {
-
// Store the status in our SQLite
-
await this.db
-
.insertInto('status')
-
.values({
-
authorDid: evt.author,
-
status: record.status,
-
updatedAt: record.updatedAt,
-
indexedAt: new Date().toISOString(),
-
})
-
.onConflict((oc) =>
-
oc.column('authorDid').doUpdateSet({
-
status: record.status,
-
updatedAt: record.updatedAt,
-
indexedAt: new Date().toISOString(),
-
})
-
)
-
.execute()
-
}
-
}
-
}
-
}
-
-
destroy() {
-
this.firehose?.destroy()
-
}
-
}
-355
src/firehose/lexicons.ts
···
-
import type { IncomingMessage } from 'node:http'
-
-
import { type LexiconDoc, Lexicons } from '@atproto/lexicon'
-
import type { ErrorFrame, HandlerAuth } from '@atproto/xrpc-server'
-
import type { CID } from 'multiformats/cid'
-
-
// @NOTE: this file is an ugly copy job of codegen output. I'd like to clean this whole thing up
-
-
export function isObj(v: unknown): v is Record<string, unknown> {
-
return typeof v === 'object' && v !== null
-
}
-
-
export function hasProp<K extends PropertyKey>(data: object, prop: K): data is Record<K, unknown> {
-
return prop in data
-
}
-
-
export interface QueryParams {
-
/** The last known event seq number to backfill from. */
-
cursor?: number
-
}
-
-
export type RepoEvent =
-
| Commit
-
| Identity
-
| Account
-
| Handle
-
| Migrate
-
| Tombstone
-
| Info
-
| { $type: string; [k: string]: unknown }
-
export type HandlerError = ErrorFrame<'FutureCursor' | 'ConsumerTooSlow'>
-
export type HandlerOutput = HandlerError | RepoEvent
-
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
-
auth: HA
-
params: QueryParams
-
req: IncomingMessage
-
signal: AbortSignal
-
}
-
export type Handler<HA extends HandlerAuth = never> = (ctx: HandlerReqCtx<HA>) => AsyncIterable<HandlerOutput>
-
-
/** Represents an update of repository state. Note that empty commits are allowed, which include no repo data changes, but an update to rev and signature. */
-
export interface Commit {
-
/** The stream sequence number of this message. */
-
seq: number
-
/** DEPRECATED -- unused */
-
rebase: boolean
-
/** Indicates that this commit contained too many ops, or data size was too large. Consumers will need to make a separate request to get missing data. */
-
tooBig: boolean
-
/** The repo this event comes from. */
-
repo: string
-
/** Repo commit object CID. */
-
commit: CID
-
/** DEPRECATED -- unused. WARNING -- nullable and optional; stick with optional to ensure golang interoperability. */
-
prev?: CID | null
-
/** The rev of the emitted commit. Note that this information is also in the commit object included in blocks, unless this is a tooBig event. */
-
rev: string
-
/** The rev of the last emitted commit from this repo (if any). */
-
since: string | null
-
/** CAR file containing relevant blocks, as a diff since the previous repo state. */
-
blocks: Uint8Array
-
ops: RepoOp[]
-
blobs: CID[]
-
/** Timestamp of when this message was originally broadcast. */
-
time: string
-
[k: string]: unknown
-
}
-
-
export function isCommit(v: unknown): v is Commit {
-
return isObj(v) && hasProp(v, '$type') && v.$type === 'com.atproto.sync.subscribeRepos#commit'
-
}
-
-
/** Represents a change to an account's identity. Could be an updated handle, signing key, or pds hosting endpoint. Serves as a prod to all downstream services to refresh their identity cache. */
-
export interface Identity {
-
seq: number
-
did: string
-
time: string
-
/** The current handle for the account, or 'handle.invalid' if validation fails. This field is optional, might have been validated or passed-through from an upstream source. Semantics and behaviors for PDS vs Relay may evolve in the future; see atproto specs for more details. */
-
handle?: string
-
[k: string]: unknown
-
}
-
-
export function isIdentity(v: unknown): v is Identity {
-
return isObj(v) && hasProp(v, '$type') && v.$type === 'com.atproto.sync.subscribeRepos#identity'
-
}
-
-
/** Represents a change to an account's status on a host (eg, PDS or Relay). The semantics of this event are that the status is at the host which emitted the event, not necessarily that at the currently active PDS. Eg, a Relay takedown would emit a takedown with active=false, even if the PDS is still active. */
-
export interface Account {
-
seq: number
-
did: string
-
time: string
-
/** Indicates that the account has a repository which can be fetched from the host that emitted this event. */
-
active: boolean
-
/** If active=false, this optional field indicates a reason for why the account is not active. */
-
status?: 'takendown' | 'suspended' | 'deleted' | 'deactivated' | (string & {})
-
[k: string]: unknown
-
}
-
-
export function isAccount(v: unknown): v is Account {
-
return isObj(v) && hasProp(v, '$type') && v.$type === 'com.atproto.sync.subscribeRepos#account'
-
}
-
-
/** DEPRECATED -- Use #identity event instead */
-
export interface Handle {
-
seq: number
-
did: string
-
handle: string
-
time: string
-
[k: string]: unknown
-
}
-
-
export function isHandle(v: unknown): v is Handle {
-
return isObj(v) && hasProp(v, '$type') && v.$type === 'com.atproto.sync.subscribeRepos#handle'
-
}
-
-
/** DEPRECATED -- Use #account event instead */
-
export interface Migrate {
-
seq: number
-
did: string
-
migrateTo: string | null
-
time: string
-
[k: string]: unknown
-
}
-
-
export function isMigrate(v: unknown): v is Migrate {
-
return isObj(v) && hasProp(v, '$type') && v.$type === 'com.atproto.sync.subscribeRepos#migrate'
-
}
-
-
/** DEPRECATED -- Use #account event instead */
-
export interface Tombstone {
-
seq: number
-
did: string
-
time: string
-
[k: string]: unknown
-
}
-
-
export function isTombstone(v: unknown): v is Tombstone {
-
return isObj(v) && hasProp(v, '$type') && v.$type === 'com.atproto.sync.subscribeRepos#tombstone'
-
}
-
-
export interface Info {
-
name: 'OutdatedCursor' | (string & {})
-
message?: string
-
[k: string]: unknown
-
}
-
-
export function isInfo(v: unknown): v is Info {
-
return isObj(v) && hasProp(v, '$type') && v.$type === 'com.atproto.sync.subscribeRepos#info'
-
}
-
-
/** A repo operation, ie a mutation of a single record. */
-
export interface RepoOp {
-
action: 'create' | 'update' | 'delete' | (string & {})
-
path: string
-
/** For creates and updates, the new record CID. For deletions, null. */
-
cid: CID | null
-
[k: string]: unknown
-
}
-
-
export function isRepoOp(v: unknown): v is RepoOp {
-
return isObj(v) && hasProp(v, '$type') && v.$type === 'com.atproto.sync.subscribeRepos#repoOp'
-
}
-
-
export const ComAtprotoSyncSubscribeRepos: LexiconDoc = {
-
lexicon: 1,
-
id: 'com.atproto.sync.subscribeRepos',
-
defs: {
-
main: {
-
type: 'subscription',
-
description: 'Subscribe to repo updates',
-
parameters: {
-
type: 'params',
-
properties: {
-
cursor: {
-
type: 'integer',
-
description: 'The last known event to backfill from.',
-
},
-
},
-
},
-
message: {
-
schema: {
-
type: 'union',
-
refs: [
-
'lex:com.atproto.sync.subscribeRepos#commit',
-
'lex:com.atproto.sync.subscribeRepos#handle',
-
'lex:com.atproto.sync.subscribeRepos#migrate',
-
'lex:com.atproto.sync.subscribeRepos#tombstone',
-
'lex:com.atproto.sync.subscribeRepos#info',
-
],
-
},
-
},
-
errors: [
-
{
-
name: 'FutureCursor',
-
},
-
{
-
name: 'ConsumerTooSlow',
-
},
-
],
-
},
-
commit: {
-
type: 'object',
-
required: ['seq', 'rebase', 'tooBig', 'repo', 'commit', 'rev', 'since', 'blocks', 'ops', 'blobs', 'time'],
-
nullable: ['prev', 'since'],
-
properties: {
-
seq: {
-
type: 'integer',
-
},
-
rebase: {
-
type: 'boolean',
-
},
-
tooBig: {
-
type: 'boolean',
-
},
-
repo: {
-
type: 'string',
-
format: 'did',
-
},
-
commit: {
-
type: 'cid-link',
-
},
-
prev: {
-
type: 'cid-link',
-
},
-
rev: {
-
type: 'string',
-
description: 'The rev of the emitted commit',
-
},
-
since: {
-
type: 'string',
-
description: 'The rev of the last emitted commit from this repo',
-
},
-
blocks: {
-
type: 'bytes',
-
description: 'CAR file containing relevant blocks',
-
maxLength: 1000000,
-
},
-
ops: {
-
type: 'array',
-
items: {
-
type: 'ref',
-
ref: 'lex:com.atproto.sync.subscribeRepos#repoOp',
-
},
-
maxLength: 200,
-
},
-
blobs: {
-
type: 'array',
-
items: {
-
type: 'cid-link',
-
},
-
},
-
time: {
-
type: 'string',
-
format: 'datetime',
-
},
-
},
-
},
-
handle: {
-
type: 'object',
-
required: ['seq', 'did', 'handle', 'time'],
-
properties: {
-
seq: {
-
type: 'integer',
-
},
-
did: {
-
type: 'string',
-
format: 'did',
-
},
-
handle: {
-
type: 'string',
-
format: 'handle',
-
},
-
time: {
-
type: 'string',
-
format: 'datetime',
-
},
-
},
-
},
-
migrate: {
-
type: 'object',
-
required: ['seq', 'did', 'migrateTo', 'time'],
-
nullable: ['migrateTo'],
-
properties: {
-
seq: {
-
type: 'integer',
-
},
-
did: {
-
type: 'string',
-
format: 'did',
-
},
-
migrateTo: {
-
type: 'string',
-
},
-
time: {
-
type: 'string',
-
format: 'datetime',
-
},
-
},
-
},
-
tombstone: {
-
type: 'object',
-
required: ['seq', 'did', 'time'],
-
properties: {
-
seq: {
-
type: 'integer',
-
},
-
did: {
-
type: 'string',
-
format: 'did',
-
},
-
time: {
-
type: 'string',
-
format: 'datetime',
-
},
-
},
-
},
-
info: {
-
type: 'object',
-
required: ['name'],
-
properties: {
-
name: {
-
type: 'string',
-
knownValues: ['OutdatedCursor'],
-
},
-
message: {
-
type: 'string',
-
},
-
},
-
},
-
repoOp: {
-
type: 'object',
-
description:
-
"A repo operation, ie a write of a single record. For creates and updates, cid is the record's CID as of this operation. For deletes, it's null.",
-
required: ['action', 'path', 'cid'],
-
nullable: ['cid'],
-
properties: {
-
action: {
-
type: 'string',
-
knownValues: ['create', 'update', 'delete'],
-
},
-
path: {
-
type: 'string',
-
},
-
cid: {
-
type: 'cid-link',
-
},
-
},
-
},
-
},
-
}
-
-
const lexicons = new Lexicons([ComAtprotoSyncSubscribeRepos])
-
-
export const isValidRepoEvent = (evt: unknown) => {
-
return lexicons.assertValidXrpcMessage<RepoEvent>('com.atproto.sync.subscribeRepos', evt)
-
}
-39
src/firehose/resolver.ts
···
-
import { IdResolver, MemoryCache } from '@atproto/identity'
-
-
const HOUR = 60e3 * 60
-
const DAY = HOUR * 24
-
-
export interface Resolver {
-
resolveDidToHandle(did: string): Promise<string>
-
resolveDidsToHandles(dids: string[]): Promise<Record<string, string>>
-
}
-
-
export function createResolver() {
-
const resolver = new IdResolver({
-
didCache: new MemoryCache(HOUR, DAY),
-
})
-
-
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
-
}
-
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))
-
)
-
for (let i = 0; i < dids.length; i++) {
-
didHandleMap[dids[i]] = resolves[i]
-
}
-
return didHandleMap
-
},
-
}
-
}
+37
src/id-resolver.ts
···
+
import { OAuthClient } from '@atproto/oauth-client-node'
+
+
export interface BidirectionalResolver {
+
resolveDidToHandle(did: string): Promise<string | undefined>
+
resolveDidsToHandles(
+
dids: string[],
+
): Promise<Record<string, string | undefined>>
+
}
+
+
export function createBidirectionalResolver({
+
identityResolver,
+
}: OAuthClient): BidirectionalResolver {
+
return {
+
async resolveDidToHandle(did: string): Promise<string | undefined> {
+
try {
+
const { handle } = await identityResolver.resolve(did)
+
if (handle) return handle
+
} catch {
+
// Ignore
+
}
+
},
+
+
async resolveDidsToHandles(
+
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]),
+
),
+
),
+
)
+
},
+
}
+
}
+24 -88
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 { once } from 'node:events'
-
import { createDb, migrateToLatest } from '#/db'
-
import { env } from '#/lib/env'
-
import { Ingester } from '#/firehose/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 { createResolver, Resolver } from '#/firehose/resolver'
-
import type { Database } from '#/db'
-
// Application state passed to the router and elsewhere
-
export type AppContext = {
-
db: Database
-
ingester: Ingester
-
logger: pino.Logger
-
oauthClient: OAuthClient
-
resolver: Resolver
-
}
-
-
export class Server {
-
constructor(
-
public app: express.Application,
-
public server: http.Server,
-
public ctx: AppContext
-
) {}
-
-
static async create() {
-
const { NODE_ENV, HOST, PORT, DB_PATH } = env
-
const logger = pino({ name: 'server start' })
-
-
// Set up the SQLite database
-
const db = createDb(DB_PATH)
-
await migrateToLatest(db)
-
-
// Create the atproto utilities
-
const oauthClient = await createClient(db)
-
const ingester = new Ingester(db)
-
const resolver = createResolver()
-
const ctx = {
-
db,
-
ingester,
-
logger,
-
oauthClient,
-
resolver,
-
}
-
-
// Subscribe to events on the firehose
-
ingester.start()
-
-
// Create our server
-
const app: Express = express()
-
app.set('trust proxy', true)
-
-
// 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))
+
run(async (killSignal) => {
+
// Create the application context
+
const ctx = await createAppContext()
-
// 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}`)
+
// Create the HTTP router
+
const router = createRouter(ctx)
-
return new Server(app, server, ctx)
-
}
+
// Start the HTTP server
+
const { terminate } = await startServer(router, { port: env.PORT })
-
async close() {
-
this.ctx.logger.info('sigint received, shutting down')
-
this.ctx.ingester.destroy()
-
return new Promise<void>((resolve) => {
-
this.server.close(() => {
-
this.ctx.logger.info('server closed')
-
resolve()
-
})
-
})
-
}
-
}
+
const url = env.PUBLIC_URL || `http://localhost:${env.PORT}`
+
ctx.logger.info(`Server (${env.NODE_ENV}) running at ${url}`)
-
const run = async () => {
-
const server = await Server.create()
+
// Subscribe to events on the firehose
+
ctx.ingester.start()
-
const onCloseSignal = async () => {
-
setTimeout(() => process.exit(1), 10000).unref() // Force shutdown after 10s
-
await server.close()
-
process.exit()
-
}
+
// Wait for a termination signal
+
if (!killSignal.aborted) await once(killSignal, 'abort')
+
ctx.logger.info(`Signal received, shutting down...`)
-
process.on('SIGINT', onCloseSignal)
-
process.on('SIGTERM', onCloseSignal)
-
}
+
// Gracefully shutdown the http server
+
await terminate()
-
run()
+
// Gracefully shutdown the application context
+
await ctx.destroy()
+
})
+77
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 { env } from './env'
+
+
const HOUR = 60e3 * 60
+
const DAY = HOUR * 24
+
+
export function createIngester(db: Database) {
+
const logger = pino({ name: 'firehose', level: env.LOG_LEVEL })
+
return new Firehose({
+
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 === '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,
+
createdAt: record.createdAt,
+
indexedAt: now.toISOString(),
+
})
+
.onConflict((oc) =>
+
oc.column('uri').doUpdateSet({
+
status: record.status,
+
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: unknown) => {
+
logger.error({ err }, 'error on firehose ingestion')
+
},
+
excludeIdentity: true,
+
excludeAccount: true,
+
service: env.FIREHOSE_URL,
+
idResolver: new IdResolver({
+
plcUrl: env.PLC_URL,
+
didCache: new MemoryCache(HOUR, DAY),
+
}),
+
})
+
}
+33 -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 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) {
+208 -6
src/lexicon/lexicons.ts
···
import { LexiconDoc, Lexicons } from '@atproto/lexicon'
export const schemaDict = {
+
ComAtprotoLabelDefs: {
+
lexicon: 1,
+
id: 'com.atproto.label.defs',
+
defs: {
+
label: {
+
type: 'object',
+
description:
+
'Metadata tag on an atproto resource (eg, repo or record).',
+
required: ['src', 'uri', 'val', 'cts'],
+
properties: {
+
ver: {
+
type: 'integer',
+
description: 'The AT Protocol version of the label object.',
+
},
+
src: {
+
type: 'string',
+
format: 'did',
+
description: 'DID of the actor who created this label.',
+
},
+
uri: {
+
type: 'string',
+
format: 'uri',
+
description:
+
'AT URI of the record, repository (account), or other resource that this label applies to.',
+
},
+
cid: {
+
type: 'string',
+
format: 'cid',
+
description:
+
"Optionally, CID specifying the specific version of 'uri' resource this label applies to.",
+
},
+
val: {
+
type: 'string',
+
maxLength: 128,
+
description:
+
'The short string name of the value or type of this label.',
+
},
+
neg: {
+
type: 'boolean',
+
description:
+
'If true, this is a negation label, overwriting a previous label.',
+
},
+
cts: {
+
type: 'string',
+
format: 'datetime',
+
description: 'Timestamp when this label was created.',
+
},
+
exp: {
+
type: 'string',
+
format: 'datetime',
+
description:
+
'Timestamp at which this label expires (no longer applies).',
+
},
+
sig: {
+
type: 'bytes',
+
description: 'Signature of dag-cbor encoded label.',
+
},
+
},
+
},
+
selfLabels: {
+
type: 'object',
+
description:
+
'Metadata tags on an atproto record, published by the author within the record.',
+
required: ['values'],
+
properties: {
+
values: {
+
type: 'array',
+
items: {
+
type: 'ref',
+
ref: 'lex:com.atproto.label.defs#selfLabel',
+
},
+
maxLength: 10,
+
},
+
},
+
},
+
selfLabel: {
+
type: 'object',
+
description:
+
'Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.',
+
required: ['val'],
+
properties: {
+
val: {
+
type: 'string',
+
maxLength: 128,
+
description:
+
'The short string name of the value or type of this label.',
+
},
+
},
+
},
+
labelValueDefinition: {
+
type: 'object',
+
description:
+
'Declares a label value and its expected interpretations and behaviors.',
+
required: ['identifier', 'severity', 'blurs', 'locales'],
+
properties: {
+
identifier: {
+
type: 'string',
+
description:
+
"The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).",
+
maxLength: 100,
+
maxGraphemes: 100,
+
},
+
severity: {
+
type: 'string',
+
description:
+
"How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.",
+
knownValues: ['inform', 'alert', 'none'],
+
},
+
blurs: {
+
type: 'string',
+
description:
+
"What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.",
+
knownValues: ['content', 'media', 'none'],
+
},
+
defaultSetting: {
+
type: 'string',
+
description: 'The default setting for this label.',
+
knownValues: ['ignore', 'warn', 'hide'],
+
default: 'warn',
+
},
+
adultOnly: {
+
type: 'boolean',
+
description:
+
'Does the user need to have adult content enabled in order to configure this label?',
+
},
+
locales: {
+
type: 'array',
+
items: {
+
type: 'ref',
+
ref: 'lex:com.atproto.label.defs#labelValueDefinitionStrings',
+
},
+
},
+
},
+
},
+
labelValueDefinitionStrings: {
+
type: 'object',
+
description:
+
'Strings which describe the label in the UI, localized into a specific language.',
+
required: ['lang', 'name', 'description'],
+
properties: {
+
lang: {
+
type: 'string',
+
description:
+
'The code of the language these strings are written in.',
+
format: 'language',
+
},
+
name: {
+
type: 'string',
+
description: 'A short human-readable name for the label.',
+
maxGraphemes: 64,
+
maxLength: 640,
+
},
+
description: {
+
type: 'string',
+
description:
+
'A longer description of what the label means and why it might be applied.',
+
maxGraphemes: 10000,
+
maxLength: 100000,
+
},
+
},
+
},
+
labelValue: {
+
type: 'string',
+
knownValues: [
+
'!hide',
+
'!no-promote',
+
'!warn',
+
'!no-unauthenticated',
+
'dmca-violation',
+
'doxxing',
+
'porn',
+
'sexual',
+
'nudity',
+
'nsfl',
+
'gore',
+
],
+
},
+
},
+
},
AppBskyActorProfile: {
lexicon: 1,
id: 'app.bsky.actor.profile',
···
},
},
},
-
ComExampleStatus: {
+
XyzStatusphereStatus: {
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: {
+
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 = {
+
ComAtprotoLabelDefs: 'com.atproto.label.defs',
AppBskyActorProfile: 'app.bsky.actor.profile',
-
ComExampleStatus: 'com.example.status',
+
XyzStatusphereStatus: 'xyz.statusphere.status',
+
ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef',
}
+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
+
}
+10 -12
src/pages/home.ts
···
const TODAY = new Date().toDateString()
-
const STATUS_OPTIONS = [
+
export const STATUS_OPTIONS = [
'๐Ÿ‘',
'๐Ÿ‘Ž',
'๐Ÿ’™',
'๐Ÿฅน',
'๐Ÿ˜ง',
-
'๐Ÿ˜ค',
'๐Ÿ™ƒ',
'๐Ÿ˜‰',
'๐Ÿ˜Ž',
···
type Props = {
statuses: Status[]
-
didHandleMap: Record<string, string>
+
didHandleMap: Record<string, string | undefined>
profile?: { displayName?: string }
myStatus?: Status
}
···
</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;
-
}
+
}
+187 -97
src/routes.ts
···
-
import assert from 'node:assert'
-
import path from 'node:path'
-
import type { IncomingMessage, ServerResponse } from 'node:http'
+
import { Agent } from '@atproto/api'
+
import { TID } from '@atproto/common'
import { OAuthResolverError } from '@atproto/oauth-client-node'
-
import { isValidHandle } from '@atproto/syntax'
-
import express from 'express'
+
import express, { Request, Response } from 'express'
import { getIronSession } from 'iron-session'
-
import type { AppContext } from '#/index'
+
import type {
+
IncomingMessage,
+
RequestListener,
+
ServerResponse,
+
} from 'node:http'
+
import path from 'node:path'
+
+
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 { env } from '#/lib/env'
-
import { page } from '#/lib/view'
-
import * as Status from '#/lexicon/types/com/example/status'
-
import * as Profile from '#/lexicon/types/app/bsky/actor/profile'
-
type Session = { did: string }
+
// Max age, in seconds, for static routes and assets
+
const MAX_AGE = env.NODE_ENV === 'production' ? 60 : 0
-
// 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)
-
}
-
}
+
type Session = { did?: string }
// Helper function to get the Atproto Agent for the active session
async function getSessionAgent(
req: IncomingMessage,
-
res: ServerResponse<IncomingMessage>,
-
ctx: AppContext
+
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
-
return await ctx.oauthClient.restore(session.did).catch(async (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)
+
// Load the session cookie
const session = await getIronSession<Session>(req, res, {
cookieName: 'sid',
password: env.COOKIE_SECRET,
})
-
assert(!session.did, 'session already exists')
-
session.did = agent.accountDid
+
+
// 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 {
-
const url = await ctx.oauthClient.authorize(handle)
-
return res.redirect(url.toString())
+
// 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')
-
return res.type('html').send(
+
+
const error = err instanceof Error ? err.message : 'unexpected error'
+
+
return res.type('html').send(page(login({ error })))
+
}
+
}),
+
)
+
+
// Signup
+
router.get(
+
'/signup',
+
handler(async (req, res) => {
+
res.setHeader('cache-control', `max-age=${MAX_AGE}, public`)
+
+
try {
+
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')
+
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) => {
+
// Never store this route
+
res.setHeader('cache-control', 'no-store')
+
const session = await getIronSession<Session>(req, res, {
cookieName: 'sid',
password: env.COOKIE_SECRET,
})
-
await session.destroy()
+
+
// 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: profileRecord } = await agent.com.atproto.repo.getRecord({
-
repo: agent.accountDid,
-
collection: 'app.bsky.actor.profile',
-
rkey: 'self',
-
})
+
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.type('html').send(
-
page(
-
home({
-
statuses,
-
didHandleMap,
-
profile,
-
myStatus,
-
})
-
)
-
)
-
})
+
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