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