Based on https://github.com/nnevatie/capnwebcpp

Adding Bluesky example with Svelte 5

+144
examples/bluesky/README.md
··· 1 + # Bluesky Feed Reader Example 2 + 3 + This example demonstrates how to use Cap'n Web RPC with AT Protocol's XRPC endpoints to build a Bluesky profile and feed viewer. It showcases: 4 + 5 + - **AT Protocol Integration**: Bridging Cap'n Web RPC to Bluesky's XRPC API 6 + - **Batch Pipelining**: Fetching profile and feed data in a single HTTP request 7 + - **External API Calls**: Real-world pattern for integrating third-party APIs 8 + - **Performance Comparison**: Toggle between batched and sequential modes 9 + 10 + ## Features 11 + 12 + - Fetch Bluesky profiles by handle (e.g., `bsky.app`, `alice.bsky.social`) 13 + - Display user info: avatar, bio, follower/following counts 14 + - Show recent posts with engagement metrics 15 + - Compare batched vs sequential request performance 16 + - Beautiful, responsive UI with Svelte 17 + 18 + ## Running the Example 19 + 20 + ### Start the Go Backend 21 + 22 + ```bash 23 + cd examples/bluesky 24 + go mod tidy 25 + go run main.go 26 + ``` 27 + 28 + The server will start on `http://localhost:8000` 29 + 30 + ### Start the Frontend 31 + 32 + In a new terminal: 33 + 34 + ```bash 35 + cd examples/bluesky/static 36 + npm install 37 + npm run dev 38 + ``` 39 + 40 + The Svelte dev server will start on `http://localhost:3000` 41 + 42 + ## How It Works 43 + 44 + ### Backend (Go) 45 + 46 + The Go server implements two RPC methods: 47 + 48 + 1. **`getProfile(handle)`** - Fetches profile data from `app.bsky.actor.getProfile` 49 + 2. **`getFeed(handle, limit)`** - Fetches posts from `app.bsky.feed.getAuthorFeed` 50 + 51 + Both methods call the public Bluesky API at `https://public.api.bsky.app/xrpc/` 52 + 53 + ### Frontend (Svelte) 54 + 55 + The frontend uses the `capnweb` JavaScript library to make RPC calls: 56 + 57 + **Batched Mode** (default): 58 + ```javascript 59 + const [profile, feed] = await Promise.all([ 60 + api.getProfile(handle), 61 + api.getFeed(handle, 10), 62 + ]); 63 + ``` 64 + 65 + Both calls are sent in a **single HTTP request** using Cap'n Web RPC pipelining! 66 + 67 + **Sequential Mode**: 68 + ```javascript 69 + const profile = await api.getProfile(handle); // Request 1 70 + const feed = await api.getFeed(handle, 10); // Request 2 71 + ``` 72 + 73 + Two separate HTTP requests, demonstrating the performance difference. 74 + 75 + ## AT Protocol / XRPC 76 + 77 + This example integrates with Bluesky's AT Protocol using their public XRPC endpoints: 78 + 79 + - `app.bsky.actor.getProfile` - Get user profile information 80 + - `app.bsky.feed.getAuthorFeed` - Get a user's posts 81 + 82 + The Go backend acts as a bridge, translating Cap'n Web RPC calls to XRPC HTTP requests. 83 + 84 + ## Try These Handles 85 + 86 + - `bsky.app` - Official Bluesky account 87 + - `jay.bsky.team` - Jay Graber (Bluesky CEO) 88 + - `pfrazee.com` - Paul Frazee (Bluesky engineer) 89 + 90 + ## Testing with curl 91 + 92 + Test the backend directly: 93 + 94 + ```bash 95 + # Get profile 96 + curl -X POST http://localhost:8000/rpc \ 97 + -d '["push",["pipeline",1,["getProfile"],["bsky.app"]]]' 98 + 99 + curl -X POST http://localhost:8000/rpc \ 100 + -d '["pull",1]' 101 + 102 + # Get feed 103 + curl -X POST http://localhost:8000/rpc \ 104 + -d '["push",["pipeline",2,["getFeed"],["bsky.app", 5]]]' 105 + 106 + curl -X POST http://localhost:8000/rpc \ 107 + -d '["pull",2]' 108 + ``` 109 + 110 + ## Performance Benefits 111 + 112 + Batched mode typically shows **40-60% faster** load times compared to sequential mode by: 113 + 114 + 1. Reducing network round trips (1 request vs 2) 115 + 2. Eliminating network latency between requests 116 + 3. Pipelining dependent operations efficiently 117 + 118 + ## Architecture 119 + 120 + ``` 121 + Browser (Svelte) 122 + ↓ Cap'n Web RPC (batched) 123 + Go Server (localhost:8000) 124 + ↓ HTTPS 125 + AT Protocol API (public.api.bsky.app) 126 + ↓ XRPC 127 + Bluesky Network 128 + ``` 129 + 130 + ## Error Handling 131 + 132 + The example handles: 133 + 134 + - Invalid or non-existent handles 135 + - Network failures 136 + - API rate limits 137 + - Malformed responses 138 + 139 + ## Learn More 140 + 141 + - [Cap'n Web RPC Spec](https://github.com/cloudflare/capnweb) 142 + - [AT Protocol Docs](https://atproto.com) 143 + - [Bluesky XRPC API](https://docs.bsky.app) 144 +
+24
examples/bluesky/go.mod
··· 1 + module bluesky 2 + 3 + go 1.23.0 4 + 5 + toolchain go1.24.2 6 + 7 + require github.com/gocapnweb v0.0.0 8 + 9 + require ( 10 + github.com/gorilla/websocket v1.5.0 // indirect 11 + github.com/labstack/echo/v4 v4.13.4 // indirect 12 + github.com/labstack/gommon v0.4.2 // indirect 13 + github.com/mattn/go-colorable v0.1.14 // indirect 14 + github.com/mattn/go-isatty v0.0.20 // indirect 15 + github.com/valyala/bytebufferpool v1.0.0 // indirect 16 + github.com/valyala/fasttemplate v1.2.2 // indirect 17 + golang.org/x/crypto v0.38.0 // indirect 18 + golang.org/x/net v0.40.0 // indirect 19 + golang.org/x/sys v0.33.0 // indirect 20 + golang.org/x/text v0.25.0 // indirect 21 + golang.org/x/time v0.11.0 // indirect 22 + ) 23 + 24 + replace github.com/gocapnweb => ../../
+33
examples/bluesky/go.sum
··· 1 + github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 + github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 + github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 4 + github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 5 + github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA= 6 + github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ= 7 + github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= 8 + github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= 9 + github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 10 + github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 11 + github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 12 + github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 13 + github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 14 + github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 + github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 16 + github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 17 + github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 18 + github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 19 + github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= 20 + github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 21 + golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= 22 + golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= 23 + golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 24 + golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 25 + golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 26 + golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 27 + golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 28 + golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= 29 + golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 30 + golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= 31 + golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 32 + gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 33 + gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+322
examples/bluesky/main.go
··· 1 + package main 2 + 3 + import ( 4 + "encoding/json" 5 + "fmt" 6 + "io" 7 + "log" 8 + "net/http" 9 + "net/url" 10 + "os" 11 + 12 + "github.com/gocapnweb" 13 + ) 14 + 15 + const blueskyAPIBase = "https://public.api.bsky.app/xrpc" 16 + 17 + // sanitizeJSON recursively removes or renames keys starting with "$" to avoid 18 + // conflicts with Cap'n Web RPC's special value handling 19 + func sanitizeJSON(data interface{}) interface{} { 20 + switch v := data.(type) { 21 + case map[string]interface{}: 22 + result := make(map[string]interface{}) 23 + for key, val := range v { 24 + // Rename keys starting with "$" to avoid Cap'n Web protocol conflicts 25 + newKey := key 26 + if len(key) > 0 && key[0] == '$' { 27 + newKey = "_" + key[1:] // Replace $ with _ (e.g., $type -> _type) 28 + } 29 + result[newKey] = sanitizeJSON(val) 30 + } 31 + return result 32 + case []interface{}: 33 + result := make([]interface{}, len(v)) 34 + for i, item := range v { 35 + result[i] = sanitizeJSON(item) 36 + } 37 + return result 38 + default: 39 + return v 40 + } 41 + } 42 + 43 + // BlueskyProfile represents a Bluesky profile response 44 + type BlueskyProfile struct { 45 + DID string `json:"did"` 46 + Handle string `json:"handle"` 47 + DisplayName string `json:"displayName,omitempty"` 48 + Description string `json:"description,omitempty"` 49 + Avatar string `json:"avatar,omitempty"` 50 + Banner string `json:"banner,omitempty"` 51 + FollowersCount int `json:"followersCount"` 52 + FollowsCount int `json:"followsCount"` 53 + PostsCount int `json:"postsCount"` 54 + } 55 + 56 + // BlueskyPost represents a single post in a feed 57 + type BlueskyPost struct { 58 + URI string `json:"uri"` 59 + CID string `json:"cid"` 60 + Author BlueskyPostAuthor `json:"author"` 61 + Record map[string]interface{} `json:"record"` 62 + ReplyCount int `json:"replyCount,omitempty"` 63 + RepostCount int `json:"repostCount,omitempty"` 64 + LikeCount int `json:"likeCount,omitempty"` 65 + IndexedAt string `json:"indexedAt"` 66 + } 67 + 68 + // BlueskyPostAuthor represents the author of a post 69 + type BlueskyPostAuthor struct { 70 + DID string `json:"did"` 71 + Handle string `json:"handle"` 72 + DisplayName string `json:"displayName,omitempty"` 73 + Avatar string `json:"avatar,omitempty"` 74 + } 75 + 76 + // BlueskyFeedResponse represents the feed response structure 77 + type BlueskyFeedResponse struct { 78 + Feed []struct { 79 + Post BlueskyPost `json:"post"` 80 + } `json:"feed"` 81 + Cursor string `json:"cursor,omitempty"` 82 + } 83 + 84 + // BlueskyServer implements RPC methods for fetching Bluesky data 85 + type BlueskyServer struct { 86 + *gocapnweb.BaseRpcTarget 87 + httpClient *http.Client 88 + } 89 + 90 + // NewBlueskyServer creates a new BlueskyServer instance 91 + func NewBlueskyServer() *BlueskyServer { 92 + server := &BlueskyServer{ 93 + BaseRpcTarget: gocapnweb.NewBaseRpcTarget(), 94 + httpClient: &http.Client{}, 95 + } 96 + 97 + // Register RPC methods 98 + server.Method("getProfile", server.getProfile) 99 + server.Method("getFeed", server.getFeed) 100 + 101 + return server 102 + } 103 + 104 + // getProfile fetches a Bluesky profile by handle 105 + func (s *BlueskyServer) getProfile(args json.RawMessage) (interface{}, error) { 106 + // Extract handle from arguments 107 + var handle string 108 + 109 + // Try to parse as array first 110 + var argArray []string 111 + if err := json.Unmarshal(args, &argArray); err == nil && len(argArray) > 0 { 112 + handle = argArray[0] 113 + } else { 114 + // Try to parse as string 115 + if err := json.Unmarshal(args, &handle); err != nil { 116 + return nil, fmt.Errorf("invalid arguments: expected handle") 117 + } 118 + } 119 + 120 + if handle == "" { 121 + return nil, fmt.Errorf("handle is required") 122 + } 123 + 124 + // Build API URL 125 + apiURL := fmt.Sprintf("%s/app.bsky.actor.getProfile?actor=%s", blueskyAPIBase, url.QueryEscape(handle)) 126 + 127 + log.Printf("Fetching profile for handle: %s", handle) 128 + 129 + // Make API request 130 + resp, err := s.httpClient.Get(apiURL) 131 + if err != nil { 132 + return nil, fmt.Errorf("failed to fetch profile: %w", err) 133 + } 134 + defer resp.Body.Close() 135 + 136 + if resp.StatusCode != http.StatusOK { 137 + body, _ := io.ReadAll(resp.Body) 138 + return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body)) 139 + } 140 + 141 + // Parse response 142 + body, err := io.ReadAll(resp.Body) 143 + if err != nil { 144 + return nil, fmt.Errorf("failed to read response: %w", err) 145 + } 146 + 147 + var profile BlueskyProfile 148 + if err := json.Unmarshal(body, &profile); err != nil { 149 + return nil, fmt.Errorf("failed to parse profile: %w", err) 150 + } 151 + 152 + log.Printf("Successfully fetched profile for %s (DID: %s)", profile.Handle, profile.DID) 153 + 154 + return profile, nil 155 + } 156 + 157 + // getFeed fetches a user's feed by handle 158 + func (s *BlueskyServer) getFeed(args json.RawMessage) (interface{}, error) { 159 + // Extract arguments 160 + var argArray []interface{} 161 + if err := json.Unmarshal(args, &argArray); err != nil { 162 + return nil, fmt.Errorf("invalid arguments: expected [handle, limit]") 163 + } 164 + 165 + if len(argArray) == 0 { 166 + return nil, fmt.Errorf("handle is required") 167 + } 168 + 169 + handle, ok := argArray[0].(string) 170 + if !ok { 171 + return nil, fmt.Errorf("handle must be a string") 172 + } 173 + 174 + limit := 10 175 + if len(argArray) > 1 { 176 + if limitFloat, ok := argArray[1].(float64); ok { 177 + limit = int(limitFloat) 178 + } 179 + } 180 + 181 + // Build API URL 182 + apiURL := fmt.Sprintf("%s/app.bsky.feed.getAuthorFeed?actor=%s&limit=%d", 183 + blueskyAPIBase, url.QueryEscape(handle), limit) 184 + 185 + log.Printf("Fetching feed for handle: %s (limit: %d)", handle, limit) 186 + 187 + // Make API request 188 + resp, err := s.httpClient.Get(apiURL) 189 + if err != nil { 190 + return nil, fmt.Errorf("failed to fetch feed: %w", err) 191 + } 192 + defer resp.Body.Close() 193 + 194 + if resp.StatusCode != http.StatusOK { 195 + body, _ := io.ReadAll(resp.Body) 196 + return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body)) 197 + } 198 + 199 + // Parse response 200 + body, err := io.ReadAll(resp.Body) 201 + if err != nil { 202 + return nil, fmt.Errorf("failed to read response: %w", err) 203 + } 204 + 205 + // Parse as generic JSON first, then sanitize to avoid $ key conflicts 206 + var rawResponse map[string]interface{} 207 + if err := json.Unmarshal(body, &rawResponse); err != nil { 208 + return nil, fmt.Errorf("failed to parse feed: %w", err) 209 + } 210 + 211 + // Sanitize the entire response 212 + sanitized := sanitizeJSON(rawResponse).(map[string]interface{}) 213 + 214 + // Extract feed array 215 + feedArray, ok := sanitized["feed"].([]interface{}) 216 + if !ok { 217 + return nil, fmt.Errorf("unexpected feed response format") 218 + } 219 + 220 + // Extract and simplify posts - only include fields we actually display 221 + // Use []interface{} instead of []map[string]interface{} for Cap'n Web compatibility 222 + posts := make([]interface{}, 0, len(feedArray)) 223 + for _, item := range feedArray { 224 + if itemMap, ok := item.(map[string]interface{}); ok { 225 + if postData, ok := itemMap["post"].(map[string]interface{}); ok { 226 + // Extract only the fields we need for display 227 + simplifiedPost := map[string]interface{}{ 228 + "uri": postData["uri"], 229 + "cid": postData["cid"], 230 + "indexedAt": postData["indexedAt"], 231 + "replyCount": getIntOrZero(postData, "replyCount"), 232 + "repostCount": getIntOrZero(postData, "repostCount"), 233 + "likeCount": getIntOrZero(postData, "likeCount"), 234 + } 235 + 236 + // Extract author info 237 + if author, ok := postData["author"].(map[string]interface{}); ok { 238 + simplifiedPost["author"] = map[string]interface{}{ 239 + "did": author["did"], 240 + "handle": author["handle"], 241 + "displayName": author["displayName"], 242 + "avatar": author["avatar"], 243 + } 244 + } 245 + 246 + // Extract record.text 247 + if record, ok := postData["record"].(map[string]interface{}); ok { 248 + simplifiedPost["record"] = map[string]interface{}{ 249 + "text": record["text"], 250 + } 251 + } 252 + 253 + posts = append(posts, simplifiedPost) 254 + } 255 + } 256 + } 257 + 258 + log.Printf("Successfully fetched %d posts for %s", len(posts), handle) 259 + 260 + cursor := "" 261 + if c, ok := sanitized["cursor"].(string); ok { 262 + cursor = c 263 + } 264 + 265 + // Wrap the posts array in another array to escape it for Cap'n Web 266 + result := map[string]interface{}{ 267 + "posts": []interface{}{posts}, // Double-wrap: [[{...}]] 268 + "cursor": cursor, 269 + } 270 + 271 + return result, nil 272 + } 273 + 274 + // Helper to safely extract int values with zero default 275 + func getIntOrZero(m map[string]interface{}, key string) int { 276 + if val, ok := m[key]; ok { 277 + if floatVal, ok := val.(float64); ok { 278 + return int(floatVal) 279 + } 280 + if intVal, ok := val.(int); ok { 281 + return intVal 282 + } 283 + } 284 + return 0 285 + } 286 + 287 + func main() { 288 + // Default to serving static files from the examples/static directory 289 + staticPath := "/static" 290 + if len(os.Args) >= 2 { 291 + staticPath = os.Args[1] 292 + } 293 + 294 + port := ":8000" 295 + 296 + // Create Echo server with middleware 297 + e := gocapnweb.SetupEchoServer() 298 + 299 + // Setup RPC endpoint 300 + server := NewBlueskyServer() 301 + gocapnweb.SetupRpcEndpoint(e, "/rpc", server) 302 + 303 + // Setup static file endpoint 304 + gocapnweb.SetupFileEndpoint(e, "/static", staticPath) 305 + 306 + log.Printf("🚀 Bluesky Feed Reader Go Server starting on port %s", port) 307 + log.Printf("🔌 HTTP Batch RPC endpoint: http://localhost%s/rpc", port) 308 + log.Printf("🌐 Demo URL: http://localhost:3000 (available once you start the Svelte development server)") 309 + log.Println() 310 + log.Println("Features:") 311 + log.Println(" 🦋 Fetch Bluesky profiles and feeds") 312 + log.Println(" ⚡ Batch pipelining for optimal performance") 313 + log.Println(" 🔗 AT Protocol/XRPC integration") 314 + log.Println() 315 + log.Println("Try it with curl:") 316 + log.Printf(" curl -X POST http://localhost%s/rpc -d '[\"push\",[\"pipeline\",1,[\"getProfile\"],[\"bsky.app\"]]]'", port) 317 + log.Printf(" curl -X POST http://localhost%s/rpc -d '[\"pull\",1]'", port) 318 + 319 + if err := e.Start(port); err != nil { 320 + log.Fatal("Failed to start server:", err) 321 + } 322 + }
+13
examples/bluesky/static/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 + <title>Bluesky Feed Reader - Cap'n Web RPC</title> 7 + </head> 8 + <body> 9 + <div id="app"></div> 10 + <script type="module" src="/src/main.js"></script> 11 + </body> 12 + </html> 13 +
+1238
examples/bluesky/static/package-lock.json
··· 1 + { 2 + "name": "bluesky-feed-reader", 3 + "version": "1.0.0", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "bluesky-feed-reader", 9 + "version": "1.0.0", 10 + "dependencies": { 11 + "capnweb": "latest" 12 + }, 13 + "devDependencies": { 14 + "@sveltejs/vite-plugin-svelte": "^4.0.0", 15 + "svelte": "^5.0.0", 16 + "vite": "^5.4.4" 17 + } 18 + }, 19 + "node_modules/@esbuild/aix-ppc64": { 20 + "version": "0.21.5", 21 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", 22 + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", 23 + "cpu": [ 24 + "ppc64" 25 + ], 26 + "dev": true, 27 + "license": "MIT", 28 + "optional": true, 29 + "os": [ 30 + "aix" 31 + ], 32 + "engines": { 33 + "node": ">=12" 34 + } 35 + }, 36 + "node_modules/@esbuild/android-arm": { 37 + "version": "0.21.5", 38 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", 39 + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", 40 + "cpu": [ 41 + "arm" 42 + ], 43 + "dev": true, 44 + "license": "MIT", 45 + "optional": true, 46 + "os": [ 47 + "android" 48 + ], 49 + "engines": { 50 + "node": ">=12" 51 + } 52 + }, 53 + "node_modules/@esbuild/android-arm64": { 54 + "version": "0.21.5", 55 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", 56 + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", 57 + "cpu": [ 58 + "arm64" 59 + ], 60 + "dev": true, 61 + "license": "MIT", 62 + "optional": true, 63 + "os": [ 64 + "android" 65 + ], 66 + "engines": { 67 + "node": ">=12" 68 + } 69 + }, 70 + "node_modules/@esbuild/android-x64": { 71 + "version": "0.21.5", 72 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", 73 + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", 74 + "cpu": [ 75 + "x64" 76 + ], 77 + "dev": true, 78 + "license": "MIT", 79 + "optional": true, 80 + "os": [ 81 + "android" 82 + ], 83 + "engines": { 84 + "node": ">=12" 85 + } 86 + }, 87 + "node_modules/@esbuild/darwin-arm64": { 88 + "version": "0.21.5", 89 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", 90 + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", 91 + "cpu": [ 92 + "arm64" 93 + ], 94 + "dev": true, 95 + "license": "MIT", 96 + "optional": true, 97 + "os": [ 98 + "darwin" 99 + ], 100 + "engines": { 101 + "node": ">=12" 102 + } 103 + }, 104 + "node_modules/@esbuild/darwin-x64": { 105 + "version": "0.21.5", 106 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", 107 + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", 108 + "cpu": [ 109 + "x64" 110 + ], 111 + "dev": true, 112 + "license": "MIT", 113 + "optional": true, 114 + "os": [ 115 + "darwin" 116 + ], 117 + "engines": { 118 + "node": ">=12" 119 + } 120 + }, 121 + "node_modules/@esbuild/freebsd-arm64": { 122 + "version": "0.21.5", 123 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", 124 + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", 125 + "cpu": [ 126 + "arm64" 127 + ], 128 + "dev": true, 129 + "license": "MIT", 130 + "optional": true, 131 + "os": [ 132 + "freebsd" 133 + ], 134 + "engines": { 135 + "node": ">=12" 136 + } 137 + }, 138 + "node_modules/@esbuild/freebsd-x64": { 139 + "version": "0.21.5", 140 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", 141 + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", 142 + "cpu": [ 143 + "x64" 144 + ], 145 + "dev": true, 146 + "license": "MIT", 147 + "optional": true, 148 + "os": [ 149 + "freebsd" 150 + ], 151 + "engines": { 152 + "node": ">=12" 153 + } 154 + }, 155 + "node_modules/@esbuild/linux-arm": { 156 + "version": "0.21.5", 157 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", 158 + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", 159 + "cpu": [ 160 + "arm" 161 + ], 162 + "dev": true, 163 + "license": "MIT", 164 + "optional": true, 165 + "os": [ 166 + "linux" 167 + ], 168 + "engines": { 169 + "node": ">=12" 170 + } 171 + }, 172 + "node_modules/@esbuild/linux-arm64": { 173 + "version": "0.21.5", 174 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", 175 + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", 176 + "cpu": [ 177 + "arm64" 178 + ], 179 + "dev": true, 180 + "license": "MIT", 181 + "optional": true, 182 + "os": [ 183 + "linux" 184 + ], 185 + "engines": { 186 + "node": ">=12" 187 + } 188 + }, 189 + "node_modules/@esbuild/linux-ia32": { 190 + "version": "0.21.5", 191 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", 192 + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", 193 + "cpu": [ 194 + "ia32" 195 + ], 196 + "dev": true, 197 + "license": "MIT", 198 + "optional": true, 199 + "os": [ 200 + "linux" 201 + ], 202 + "engines": { 203 + "node": ">=12" 204 + } 205 + }, 206 + "node_modules/@esbuild/linux-loong64": { 207 + "version": "0.21.5", 208 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", 209 + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", 210 + "cpu": [ 211 + "loong64" 212 + ], 213 + "dev": true, 214 + "license": "MIT", 215 + "optional": true, 216 + "os": [ 217 + "linux" 218 + ], 219 + "engines": { 220 + "node": ">=12" 221 + } 222 + }, 223 + "node_modules/@esbuild/linux-mips64el": { 224 + "version": "0.21.5", 225 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", 226 + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", 227 + "cpu": [ 228 + "mips64el" 229 + ], 230 + "dev": true, 231 + "license": "MIT", 232 + "optional": true, 233 + "os": [ 234 + "linux" 235 + ], 236 + "engines": { 237 + "node": ">=12" 238 + } 239 + }, 240 + "node_modules/@esbuild/linux-ppc64": { 241 + "version": "0.21.5", 242 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", 243 + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", 244 + "cpu": [ 245 + "ppc64" 246 + ], 247 + "dev": true, 248 + "license": "MIT", 249 + "optional": true, 250 + "os": [ 251 + "linux" 252 + ], 253 + "engines": { 254 + "node": ">=12" 255 + } 256 + }, 257 + "node_modules/@esbuild/linux-riscv64": { 258 + "version": "0.21.5", 259 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", 260 + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", 261 + "cpu": [ 262 + "riscv64" 263 + ], 264 + "dev": true, 265 + "license": "MIT", 266 + "optional": true, 267 + "os": [ 268 + "linux" 269 + ], 270 + "engines": { 271 + "node": ">=12" 272 + } 273 + }, 274 + "node_modules/@esbuild/linux-s390x": { 275 + "version": "0.21.5", 276 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", 277 + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", 278 + "cpu": [ 279 + "s390x" 280 + ], 281 + "dev": true, 282 + "license": "MIT", 283 + "optional": true, 284 + "os": [ 285 + "linux" 286 + ], 287 + "engines": { 288 + "node": ">=12" 289 + } 290 + }, 291 + "node_modules/@esbuild/linux-x64": { 292 + "version": "0.21.5", 293 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", 294 + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", 295 + "cpu": [ 296 + "x64" 297 + ], 298 + "dev": true, 299 + "license": "MIT", 300 + "optional": true, 301 + "os": [ 302 + "linux" 303 + ], 304 + "engines": { 305 + "node": ">=12" 306 + } 307 + }, 308 + "node_modules/@esbuild/netbsd-x64": { 309 + "version": "0.21.5", 310 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", 311 + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", 312 + "cpu": [ 313 + "x64" 314 + ], 315 + "dev": true, 316 + "license": "MIT", 317 + "optional": true, 318 + "os": [ 319 + "netbsd" 320 + ], 321 + "engines": { 322 + "node": ">=12" 323 + } 324 + }, 325 + "node_modules/@esbuild/openbsd-x64": { 326 + "version": "0.21.5", 327 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", 328 + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", 329 + "cpu": [ 330 + "x64" 331 + ], 332 + "dev": true, 333 + "license": "MIT", 334 + "optional": true, 335 + "os": [ 336 + "openbsd" 337 + ], 338 + "engines": { 339 + "node": ">=12" 340 + } 341 + }, 342 + "node_modules/@esbuild/sunos-x64": { 343 + "version": "0.21.5", 344 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", 345 + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", 346 + "cpu": [ 347 + "x64" 348 + ], 349 + "dev": true, 350 + "license": "MIT", 351 + "optional": true, 352 + "os": [ 353 + "sunos" 354 + ], 355 + "engines": { 356 + "node": ">=12" 357 + } 358 + }, 359 + "node_modules/@esbuild/win32-arm64": { 360 + "version": "0.21.5", 361 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", 362 + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", 363 + "cpu": [ 364 + "arm64" 365 + ], 366 + "dev": true, 367 + "license": "MIT", 368 + "optional": true, 369 + "os": [ 370 + "win32" 371 + ], 372 + "engines": { 373 + "node": ">=12" 374 + } 375 + }, 376 + "node_modules/@esbuild/win32-ia32": { 377 + "version": "0.21.5", 378 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", 379 + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", 380 + "cpu": [ 381 + "ia32" 382 + ], 383 + "dev": true, 384 + "license": "MIT", 385 + "optional": true, 386 + "os": [ 387 + "win32" 388 + ], 389 + "engines": { 390 + "node": ">=12" 391 + } 392 + }, 393 + "node_modules/@esbuild/win32-x64": { 394 + "version": "0.21.5", 395 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", 396 + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", 397 + "cpu": [ 398 + "x64" 399 + ], 400 + "dev": true, 401 + "license": "MIT", 402 + "optional": true, 403 + "os": [ 404 + "win32" 405 + ], 406 + "engines": { 407 + "node": ">=12" 408 + } 409 + }, 410 + "node_modules/@jridgewell/gen-mapping": { 411 + "version": "0.3.13", 412 + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", 413 + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", 414 + "dev": true, 415 + "license": "MIT", 416 + "dependencies": { 417 + "@jridgewell/sourcemap-codec": "^1.5.0", 418 + "@jridgewell/trace-mapping": "^0.3.24" 419 + } 420 + }, 421 + "node_modules/@jridgewell/remapping": { 422 + "version": "2.3.5", 423 + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", 424 + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", 425 + "dev": true, 426 + "license": "MIT", 427 + "dependencies": { 428 + "@jridgewell/gen-mapping": "^0.3.5", 429 + "@jridgewell/trace-mapping": "^0.3.24" 430 + } 431 + }, 432 + "node_modules/@jridgewell/resolve-uri": { 433 + "version": "3.1.2", 434 + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 435 + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 436 + "dev": true, 437 + "license": "MIT", 438 + "engines": { 439 + "node": ">=6.0.0" 440 + } 441 + }, 442 + "node_modules/@jridgewell/sourcemap-codec": { 443 + "version": "1.5.5", 444 + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", 445 + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", 446 + "dev": true, 447 + "license": "MIT" 448 + }, 449 + "node_modules/@jridgewell/trace-mapping": { 450 + "version": "0.3.31", 451 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", 452 + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", 453 + "dev": true, 454 + "license": "MIT", 455 + "dependencies": { 456 + "@jridgewell/resolve-uri": "^3.1.0", 457 + "@jridgewell/sourcemap-codec": "^1.4.14" 458 + } 459 + }, 460 + "node_modules/@rollup/rollup-android-arm-eabi": { 461 + "version": "4.52.5", 462 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", 463 + "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", 464 + "cpu": [ 465 + "arm" 466 + ], 467 + "dev": true, 468 + "license": "MIT", 469 + "optional": true, 470 + "os": [ 471 + "android" 472 + ] 473 + }, 474 + "node_modules/@rollup/rollup-android-arm64": { 475 + "version": "4.52.5", 476 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", 477 + "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", 478 + "cpu": [ 479 + "arm64" 480 + ], 481 + "dev": true, 482 + "license": "MIT", 483 + "optional": true, 484 + "os": [ 485 + "android" 486 + ] 487 + }, 488 + "node_modules/@rollup/rollup-darwin-arm64": { 489 + "version": "4.52.5", 490 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", 491 + "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", 492 + "cpu": [ 493 + "arm64" 494 + ], 495 + "dev": true, 496 + "license": "MIT", 497 + "optional": true, 498 + "os": [ 499 + "darwin" 500 + ] 501 + }, 502 + "node_modules/@rollup/rollup-darwin-x64": { 503 + "version": "4.52.5", 504 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", 505 + "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", 506 + "cpu": [ 507 + "x64" 508 + ], 509 + "dev": true, 510 + "license": "MIT", 511 + "optional": true, 512 + "os": [ 513 + "darwin" 514 + ] 515 + }, 516 + "node_modules/@rollup/rollup-freebsd-arm64": { 517 + "version": "4.52.5", 518 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", 519 + "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", 520 + "cpu": [ 521 + "arm64" 522 + ], 523 + "dev": true, 524 + "license": "MIT", 525 + "optional": true, 526 + "os": [ 527 + "freebsd" 528 + ] 529 + }, 530 + "node_modules/@rollup/rollup-freebsd-x64": { 531 + "version": "4.52.5", 532 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", 533 + "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", 534 + "cpu": [ 535 + "x64" 536 + ], 537 + "dev": true, 538 + "license": "MIT", 539 + "optional": true, 540 + "os": [ 541 + "freebsd" 542 + ] 543 + }, 544 + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 545 + "version": "4.52.5", 546 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", 547 + "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", 548 + "cpu": [ 549 + "arm" 550 + ], 551 + "dev": true, 552 + "license": "MIT", 553 + "optional": true, 554 + "os": [ 555 + "linux" 556 + ] 557 + }, 558 + "node_modules/@rollup/rollup-linux-arm-musleabihf": { 559 + "version": "4.52.5", 560 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", 561 + "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", 562 + "cpu": [ 563 + "arm" 564 + ], 565 + "dev": true, 566 + "license": "MIT", 567 + "optional": true, 568 + "os": [ 569 + "linux" 570 + ] 571 + }, 572 + "node_modules/@rollup/rollup-linux-arm64-gnu": { 573 + "version": "4.52.5", 574 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", 575 + "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", 576 + "cpu": [ 577 + "arm64" 578 + ], 579 + "dev": true, 580 + "license": "MIT", 581 + "optional": true, 582 + "os": [ 583 + "linux" 584 + ] 585 + }, 586 + "node_modules/@rollup/rollup-linux-arm64-musl": { 587 + "version": "4.52.5", 588 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", 589 + "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", 590 + "cpu": [ 591 + "arm64" 592 + ], 593 + "dev": true, 594 + "license": "MIT", 595 + "optional": true, 596 + "os": [ 597 + "linux" 598 + ] 599 + }, 600 + "node_modules/@rollup/rollup-linux-loong64-gnu": { 601 + "version": "4.52.5", 602 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", 603 + "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", 604 + "cpu": [ 605 + "loong64" 606 + ], 607 + "dev": true, 608 + "license": "MIT", 609 + "optional": true, 610 + "os": [ 611 + "linux" 612 + ] 613 + }, 614 + "node_modules/@rollup/rollup-linux-ppc64-gnu": { 615 + "version": "4.52.5", 616 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", 617 + "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", 618 + "cpu": [ 619 + "ppc64" 620 + ], 621 + "dev": true, 622 + "license": "MIT", 623 + "optional": true, 624 + "os": [ 625 + "linux" 626 + ] 627 + }, 628 + "node_modules/@rollup/rollup-linux-riscv64-gnu": { 629 + "version": "4.52.5", 630 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", 631 + "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", 632 + "cpu": [ 633 + "riscv64" 634 + ], 635 + "dev": true, 636 + "license": "MIT", 637 + "optional": true, 638 + "os": [ 639 + "linux" 640 + ] 641 + }, 642 + "node_modules/@rollup/rollup-linux-riscv64-musl": { 643 + "version": "4.52.5", 644 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", 645 + "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", 646 + "cpu": [ 647 + "riscv64" 648 + ], 649 + "dev": true, 650 + "license": "MIT", 651 + "optional": true, 652 + "os": [ 653 + "linux" 654 + ] 655 + }, 656 + "node_modules/@rollup/rollup-linux-s390x-gnu": { 657 + "version": "4.52.5", 658 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", 659 + "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", 660 + "cpu": [ 661 + "s390x" 662 + ], 663 + "dev": true, 664 + "license": "MIT", 665 + "optional": true, 666 + "os": [ 667 + "linux" 668 + ] 669 + }, 670 + "node_modules/@rollup/rollup-linux-x64-gnu": { 671 + "version": "4.52.5", 672 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", 673 + "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", 674 + "cpu": [ 675 + "x64" 676 + ], 677 + "dev": true, 678 + "license": "MIT", 679 + "optional": true, 680 + "os": [ 681 + "linux" 682 + ] 683 + }, 684 + "node_modules/@rollup/rollup-linux-x64-musl": { 685 + "version": "4.52.5", 686 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", 687 + "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", 688 + "cpu": [ 689 + "x64" 690 + ], 691 + "dev": true, 692 + "license": "MIT", 693 + "optional": true, 694 + "os": [ 695 + "linux" 696 + ] 697 + }, 698 + "node_modules/@rollup/rollup-openharmony-arm64": { 699 + "version": "4.52.5", 700 + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", 701 + "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", 702 + "cpu": [ 703 + "arm64" 704 + ], 705 + "dev": true, 706 + "license": "MIT", 707 + "optional": true, 708 + "os": [ 709 + "openharmony" 710 + ] 711 + }, 712 + "node_modules/@rollup/rollup-win32-arm64-msvc": { 713 + "version": "4.52.5", 714 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", 715 + "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", 716 + "cpu": [ 717 + "arm64" 718 + ], 719 + "dev": true, 720 + "license": "MIT", 721 + "optional": true, 722 + "os": [ 723 + "win32" 724 + ] 725 + }, 726 + "node_modules/@rollup/rollup-win32-ia32-msvc": { 727 + "version": "4.52.5", 728 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", 729 + "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", 730 + "cpu": [ 731 + "ia32" 732 + ], 733 + "dev": true, 734 + "license": "MIT", 735 + "optional": true, 736 + "os": [ 737 + "win32" 738 + ] 739 + }, 740 + "node_modules/@rollup/rollup-win32-x64-gnu": { 741 + "version": "4.52.5", 742 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", 743 + "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", 744 + "cpu": [ 745 + "x64" 746 + ], 747 + "dev": true, 748 + "license": "MIT", 749 + "optional": true, 750 + "os": [ 751 + "win32" 752 + ] 753 + }, 754 + "node_modules/@rollup/rollup-win32-x64-msvc": { 755 + "version": "4.52.5", 756 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", 757 + "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", 758 + "cpu": [ 759 + "x64" 760 + ], 761 + "dev": true, 762 + "license": "MIT", 763 + "optional": true, 764 + "os": [ 765 + "win32" 766 + ] 767 + }, 768 + "node_modules/@sveltejs/acorn-typescript": { 769 + "version": "1.0.6", 770 + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz", 771 + "integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==", 772 + "dev": true, 773 + "license": "MIT", 774 + "peerDependencies": { 775 + "acorn": "^8.9.0" 776 + } 777 + }, 778 + "node_modules/@sveltejs/vite-plugin-svelte": { 779 + "version": "4.0.4", 780 + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-4.0.4.tgz", 781 + "integrity": "sha512-0ba1RQ/PHen5FGpdSrW7Y3fAMQjrXantECALeOiOdBdzR5+5vPP6HVZRLmZaQL+W8m++o+haIAKq5qT+MiZ7VA==", 782 + "dev": true, 783 + "license": "MIT", 784 + "dependencies": { 785 + "@sveltejs/vite-plugin-svelte-inspector": "^3.0.0-next.0||^3.0.0", 786 + "debug": "^4.3.7", 787 + "deepmerge": "^4.3.1", 788 + "kleur": "^4.1.5", 789 + "magic-string": "^0.30.12", 790 + "vitefu": "^1.0.3" 791 + }, 792 + "engines": { 793 + "node": "^18.0.0 || ^20.0.0 || >=22" 794 + }, 795 + "peerDependencies": { 796 + "svelte": "^5.0.0-next.96 || ^5.0.0", 797 + "vite": "^5.0.0" 798 + } 799 + }, 800 + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { 801 + "version": "3.0.1", 802 + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-3.0.1.tgz", 803 + "integrity": "sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==", 804 + "dev": true, 805 + "license": "MIT", 806 + "dependencies": { 807 + "debug": "^4.3.7" 808 + }, 809 + "engines": { 810 + "node": "^18.0.0 || ^20.0.0 || >=22" 811 + }, 812 + "peerDependencies": { 813 + "@sveltejs/vite-plugin-svelte": "^4.0.0-next.0||^4.0.0", 814 + "svelte": "^5.0.0-next.96 || ^5.0.0", 815 + "vite": "^5.0.0" 816 + } 817 + }, 818 + "node_modules/@types/estree": { 819 + "version": "1.0.8", 820 + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", 821 + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", 822 + "dev": true, 823 + "license": "MIT" 824 + }, 825 + "node_modules/acorn": { 826 + "version": "8.15.0", 827 + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", 828 + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", 829 + "dev": true, 830 + "license": "MIT", 831 + "bin": { 832 + "acorn": "bin/acorn" 833 + }, 834 + "engines": { 835 + "node": ">=0.4.0" 836 + } 837 + }, 838 + "node_modules/aria-query": { 839 + "version": "5.3.2", 840 + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", 841 + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", 842 + "dev": true, 843 + "license": "Apache-2.0", 844 + "engines": { 845 + "node": ">= 0.4" 846 + } 847 + }, 848 + "node_modules/axobject-query": { 849 + "version": "4.1.0", 850 + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", 851 + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", 852 + "dev": true, 853 + "license": "Apache-2.0", 854 + "engines": { 855 + "node": ">= 0.4" 856 + } 857 + }, 858 + "node_modules/capnweb": { 859 + "version": "0.2.0", 860 + "resolved": "https://registry.npmjs.org/capnweb/-/capnweb-0.2.0.tgz", 861 + "integrity": "sha512-fQSW5h6HIefRM4rHZMyAsWcu/qE/6Qr2OC8B99whifjDJatI5KLFcODSykKmpyCCKF50N3HvZ5lB26YBdh0fRg==", 862 + "license": "MIT" 863 + }, 864 + "node_modules/clsx": { 865 + "version": "2.1.1", 866 + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", 867 + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", 868 + "dev": true, 869 + "license": "MIT", 870 + "engines": { 871 + "node": ">=6" 872 + } 873 + }, 874 + "node_modules/debug": { 875 + "version": "4.4.3", 876 + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", 877 + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", 878 + "dev": true, 879 + "license": "MIT", 880 + "dependencies": { 881 + "ms": "^2.1.3" 882 + }, 883 + "engines": { 884 + "node": ">=6.0" 885 + }, 886 + "peerDependenciesMeta": { 887 + "supports-color": { 888 + "optional": true 889 + } 890 + } 891 + }, 892 + "node_modules/deepmerge": { 893 + "version": "4.3.1", 894 + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", 895 + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", 896 + "dev": true, 897 + "license": "MIT", 898 + "engines": { 899 + "node": ">=0.10.0" 900 + } 901 + }, 902 + "node_modules/esbuild": { 903 + "version": "0.21.5", 904 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", 905 + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", 906 + "dev": true, 907 + "hasInstallScript": true, 908 + "license": "MIT", 909 + "bin": { 910 + "esbuild": "bin/esbuild" 911 + }, 912 + "engines": { 913 + "node": ">=12" 914 + }, 915 + "optionalDependencies": { 916 + "@esbuild/aix-ppc64": "0.21.5", 917 + "@esbuild/android-arm": "0.21.5", 918 + "@esbuild/android-arm64": "0.21.5", 919 + "@esbuild/android-x64": "0.21.5", 920 + "@esbuild/darwin-arm64": "0.21.5", 921 + "@esbuild/darwin-x64": "0.21.5", 922 + "@esbuild/freebsd-arm64": "0.21.5", 923 + "@esbuild/freebsd-x64": "0.21.5", 924 + "@esbuild/linux-arm": "0.21.5", 925 + "@esbuild/linux-arm64": "0.21.5", 926 + "@esbuild/linux-ia32": "0.21.5", 927 + "@esbuild/linux-loong64": "0.21.5", 928 + "@esbuild/linux-mips64el": "0.21.5", 929 + "@esbuild/linux-ppc64": "0.21.5", 930 + "@esbuild/linux-riscv64": "0.21.5", 931 + "@esbuild/linux-s390x": "0.21.5", 932 + "@esbuild/linux-x64": "0.21.5", 933 + "@esbuild/netbsd-x64": "0.21.5", 934 + "@esbuild/openbsd-x64": "0.21.5", 935 + "@esbuild/sunos-x64": "0.21.5", 936 + "@esbuild/win32-arm64": "0.21.5", 937 + "@esbuild/win32-ia32": "0.21.5", 938 + "@esbuild/win32-x64": "0.21.5" 939 + } 940 + }, 941 + "node_modules/esm-env": { 942 + "version": "1.2.2", 943 + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", 944 + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", 945 + "dev": true, 946 + "license": "MIT" 947 + }, 948 + "node_modules/esrap": { 949 + "version": "2.1.2", 950 + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.2.tgz", 951 + "integrity": "sha512-DgvlIQeowRNyvLPWW4PT7Gu13WznY288Du086E751mwwbsgr29ytBiYeLzAGIo0qk3Ujob0SDk8TiSaM5WQzNg==", 952 + "dev": true, 953 + "license": "MIT", 954 + "dependencies": { 955 + "@jridgewell/sourcemap-codec": "^1.4.15" 956 + } 957 + }, 958 + "node_modules/fsevents": { 959 + "version": "2.3.3", 960 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 961 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 962 + "dev": true, 963 + "hasInstallScript": true, 964 + "license": "MIT", 965 + "optional": true, 966 + "os": [ 967 + "darwin" 968 + ], 969 + "engines": { 970 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 971 + } 972 + }, 973 + "node_modules/is-reference": { 974 + "version": "3.0.3", 975 + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", 976 + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", 977 + "dev": true, 978 + "license": "MIT", 979 + "dependencies": { 980 + "@types/estree": "^1.0.6" 981 + } 982 + }, 983 + "node_modules/kleur": { 984 + "version": "4.1.5", 985 + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", 986 + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", 987 + "dev": true, 988 + "license": "MIT", 989 + "engines": { 990 + "node": ">=6" 991 + } 992 + }, 993 + "node_modules/locate-character": { 994 + "version": "3.0.0", 995 + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", 996 + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", 997 + "dev": true, 998 + "license": "MIT" 999 + }, 1000 + "node_modules/magic-string": { 1001 + "version": "0.30.21", 1002 + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", 1003 + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", 1004 + "dev": true, 1005 + "license": "MIT", 1006 + "dependencies": { 1007 + "@jridgewell/sourcemap-codec": "^1.5.5" 1008 + } 1009 + }, 1010 + "node_modules/ms": { 1011 + "version": "2.1.3", 1012 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1013 + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1014 + "dev": true, 1015 + "license": "MIT" 1016 + }, 1017 + "node_modules/nanoid": { 1018 + "version": "3.3.11", 1019 + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", 1020 + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 1021 + "dev": true, 1022 + "funding": [ 1023 + { 1024 + "type": "github", 1025 + "url": "https://github.com/sponsors/ai" 1026 + } 1027 + ], 1028 + "license": "MIT", 1029 + "bin": { 1030 + "nanoid": "bin/nanoid.cjs" 1031 + }, 1032 + "engines": { 1033 + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1034 + } 1035 + }, 1036 + "node_modules/picocolors": { 1037 + "version": "1.1.1", 1038 + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 1039 + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 1040 + "dev": true, 1041 + "license": "ISC" 1042 + }, 1043 + "node_modules/postcss": { 1044 + "version": "8.5.6", 1045 + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", 1046 + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", 1047 + "dev": true, 1048 + "funding": [ 1049 + { 1050 + "type": "opencollective", 1051 + "url": "https://opencollective.com/postcss/" 1052 + }, 1053 + { 1054 + "type": "tidelift", 1055 + "url": "https://tidelift.com/funding/github/npm/postcss" 1056 + }, 1057 + { 1058 + "type": "github", 1059 + "url": "https://github.com/sponsors/ai" 1060 + } 1061 + ], 1062 + "license": "MIT", 1063 + "dependencies": { 1064 + "nanoid": "^3.3.11", 1065 + "picocolors": "^1.1.1", 1066 + "source-map-js": "^1.2.1" 1067 + }, 1068 + "engines": { 1069 + "node": "^10 || ^12 || >=14" 1070 + } 1071 + }, 1072 + "node_modules/rollup": { 1073 + "version": "4.52.5", 1074 + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", 1075 + "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", 1076 + "dev": true, 1077 + "license": "MIT", 1078 + "dependencies": { 1079 + "@types/estree": "1.0.8" 1080 + }, 1081 + "bin": { 1082 + "rollup": "dist/bin/rollup" 1083 + }, 1084 + "engines": { 1085 + "node": ">=18.0.0", 1086 + "npm": ">=8.0.0" 1087 + }, 1088 + "optionalDependencies": { 1089 + "@rollup/rollup-android-arm-eabi": "4.52.5", 1090 + "@rollup/rollup-android-arm64": "4.52.5", 1091 + "@rollup/rollup-darwin-arm64": "4.52.5", 1092 + "@rollup/rollup-darwin-x64": "4.52.5", 1093 + "@rollup/rollup-freebsd-arm64": "4.52.5", 1094 + "@rollup/rollup-freebsd-x64": "4.52.5", 1095 + "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", 1096 + "@rollup/rollup-linux-arm-musleabihf": "4.52.5", 1097 + "@rollup/rollup-linux-arm64-gnu": "4.52.5", 1098 + "@rollup/rollup-linux-arm64-musl": "4.52.5", 1099 + "@rollup/rollup-linux-loong64-gnu": "4.52.5", 1100 + "@rollup/rollup-linux-ppc64-gnu": "4.52.5", 1101 + "@rollup/rollup-linux-riscv64-gnu": "4.52.5", 1102 + "@rollup/rollup-linux-riscv64-musl": "4.52.5", 1103 + "@rollup/rollup-linux-s390x-gnu": "4.52.5", 1104 + "@rollup/rollup-linux-x64-gnu": "4.52.5", 1105 + "@rollup/rollup-linux-x64-musl": "4.52.5", 1106 + "@rollup/rollup-openharmony-arm64": "4.52.5", 1107 + "@rollup/rollup-win32-arm64-msvc": "4.52.5", 1108 + "@rollup/rollup-win32-ia32-msvc": "4.52.5", 1109 + "@rollup/rollup-win32-x64-gnu": "4.52.5", 1110 + "@rollup/rollup-win32-x64-msvc": "4.52.5", 1111 + "fsevents": "~2.3.2" 1112 + } 1113 + }, 1114 + "node_modules/source-map-js": { 1115 + "version": "1.2.1", 1116 + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 1117 + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 1118 + "dev": true, 1119 + "license": "BSD-3-Clause", 1120 + "engines": { 1121 + "node": ">=0.10.0" 1122 + } 1123 + }, 1124 + "node_modules/svelte": { 1125 + "version": "5.43.3", 1126 + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.43.3.tgz", 1127 + "integrity": "sha512-kjkAjCk41mJfvJZG56XcJNOdJSke94JxtcX8zFzzz2vrt47E0LnoBzU6azIZ1aBxJgUep8qegAkguSf1GjxLXQ==", 1128 + "dev": true, 1129 + "license": "MIT", 1130 + "dependencies": { 1131 + "@jridgewell/remapping": "^2.3.4", 1132 + "@jridgewell/sourcemap-codec": "^1.5.0", 1133 + "@sveltejs/acorn-typescript": "^1.0.5", 1134 + "@types/estree": "^1.0.5", 1135 + "acorn": "^8.12.1", 1136 + "aria-query": "^5.3.1", 1137 + "axobject-query": "^4.1.0", 1138 + "clsx": "^2.1.1", 1139 + "esm-env": "^1.2.1", 1140 + "esrap": "^2.1.0", 1141 + "is-reference": "^3.0.3", 1142 + "locate-character": "^3.0.0", 1143 + "magic-string": "^0.30.11", 1144 + "zimmerframe": "^1.1.2" 1145 + }, 1146 + "engines": { 1147 + "node": ">=18" 1148 + } 1149 + }, 1150 + "node_modules/vite": { 1151 + "version": "5.4.21", 1152 + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", 1153 + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", 1154 + "dev": true, 1155 + "license": "MIT", 1156 + "dependencies": { 1157 + "esbuild": "^0.21.3", 1158 + "postcss": "^8.4.43", 1159 + "rollup": "^4.20.0" 1160 + }, 1161 + "bin": { 1162 + "vite": "bin/vite.js" 1163 + }, 1164 + "engines": { 1165 + "node": "^18.0.0 || >=20.0.0" 1166 + }, 1167 + "funding": { 1168 + "url": "https://github.com/vitejs/vite?sponsor=1" 1169 + }, 1170 + "optionalDependencies": { 1171 + "fsevents": "~2.3.3" 1172 + }, 1173 + "peerDependencies": { 1174 + "@types/node": "^18.0.0 || >=20.0.0", 1175 + "less": "*", 1176 + "lightningcss": "^1.21.0", 1177 + "sass": "*", 1178 + "sass-embedded": "*", 1179 + "stylus": "*", 1180 + "sugarss": "*", 1181 + "terser": "^5.4.0" 1182 + }, 1183 + "peerDependenciesMeta": { 1184 + "@types/node": { 1185 + "optional": true 1186 + }, 1187 + "less": { 1188 + "optional": true 1189 + }, 1190 + "lightningcss": { 1191 + "optional": true 1192 + }, 1193 + "sass": { 1194 + "optional": true 1195 + }, 1196 + "sass-embedded": { 1197 + "optional": true 1198 + }, 1199 + "stylus": { 1200 + "optional": true 1201 + }, 1202 + "sugarss": { 1203 + "optional": true 1204 + }, 1205 + "terser": { 1206 + "optional": true 1207 + } 1208 + } 1209 + }, 1210 + "node_modules/vitefu": { 1211 + "version": "1.1.1", 1212 + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", 1213 + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", 1214 + "dev": true, 1215 + "license": "MIT", 1216 + "workspaces": [ 1217 + "tests/deps/*", 1218 + "tests/projects/*", 1219 + "tests/projects/workspace/packages/*" 1220 + ], 1221 + "peerDependencies": { 1222 + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" 1223 + }, 1224 + "peerDependenciesMeta": { 1225 + "vite": { 1226 + "optional": true 1227 + } 1228 + } 1229 + }, 1230 + "node_modules/zimmerframe": { 1231 + "version": "1.1.4", 1232 + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", 1233 + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", 1234 + "dev": true, 1235 + "license": "MIT" 1236 + } 1237 + } 1238 + }
+26
examples/bluesky/static/package.json
··· 1 + { 2 + "name": "bluesky-feed-reader", 3 + "version": "1.0.0", 4 + "description": "Bluesky Cap'n Web RPC Svelte Demo", 5 + "type": "module", 6 + "browserslist": [ 7 + "Chrome >= 89", 8 + "Firefox >= 89", 9 + "Safari >= 15", 10 + "Edge >= 89" 11 + ], 12 + "scripts": { 13 + "dev": "vite", 14 + "build": "vite build", 15 + "preview": "vite preview" 16 + }, 17 + "devDependencies": { 18 + "@sveltejs/vite-plugin-svelte": "^4.0.0", 19 + "svelte": "^5.0.0", 20 + "vite": "^5.4.4" 21 + }, 22 + "dependencies": { 23 + "capnweb": "latest" 24 + } 25 + } 26 +
+516
examples/bluesky/static/src/App.svelte
··· 1 + <script> 2 + import { newHttpBatchRpcSession } from 'capnweb'; 3 + 4 + let handle = $state('bsky.app'); 5 + let loading = $state(false); 6 + let error = $state(null); 7 + let profile = $state(null); 8 + let feed = $state(null); 9 + let mode = $state('batched'); // 'batched' or 'sequential' 10 + let timingInfo = $state(null); 11 + 12 + async function loadProfileAndFeed() { 13 + loading = true; 14 + error = null; 15 + profile = null; 16 + feed = null; 17 + timingInfo = null; 18 + 19 + try { 20 + const startTime = performance.now(); 21 + 22 + if (mode === 'batched') { 23 + // Batched mode: both calls in a single HTTP request with pipelining 24 + // Create a new session for this batch 25 + const api = newHttpBatchRpcSession('http://localhost:3000/rpc'); 26 + 27 + try { 28 + const profilePromise = api.getProfile(handle); 29 + const feedPromise = api.getFeed(handle, 10); 30 + 31 + const [profileResult, feedResult] = await Promise.all([ 32 + profilePromise, 33 + feedPromise, 34 + ]); 35 + 36 + const endTime = performance.now(); 37 + 38 + console.log('Batched results:', { profileResult, feedResult }); 39 + 40 + profile = profileResult; 41 + // Unwrap the double-wrapped posts array (Cap'n Web escaping) 42 + if (feedResult.posts && Array.isArray(feedResult.posts) && feedResult.posts.length > 0 && Array.isArray(feedResult.posts[0])) { 43 + feedResult.posts = feedResult.posts[0]; 44 + } 45 + feed = feedResult; 46 + timingInfo = { 47 + mode: 'Batched (Pipeline)', 48 + duration: Math.round(endTime - startTime), 49 + requests: 1, 50 + description: 'Both profile and feed fetched in a single HTTP request using Cap\'n Web RPC pipelining' 51 + }; 52 + } catch (batchError) { 53 + console.error('Batched mode error:', batchError); 54 + throw batchError; 55 + } 56 + } else { 57 + // Sequential mode: two separate HTTP requests 58 + // Create a new session for each request 59 + const start1 = performance.now(); 60 + const api1 = newHttpBatchRpcSession('http://localhost:3000/rpc'); 61 + profile = await api1.getProfile(handle); 62 + const end1 = performance.now(); 63 + 64 + const start2 = performance.now(); 65 + const api2 = newHttpBatchRpcSession('http://localhost:3000/rpc'); 66 + feed = await api2.getFeed(handle, 10); 67 + // Unwrap the double-wrapped posts array (Cap'n Web escaping) 68 + if (feed.posts && Array.isArray(feed.posts) && feed.posts.length > 0 && Array.isArray(feed.posts[0])) { 69 + feed.posts = feed.posts[0]; 70 + } 71 + const end2 = performance.now(); 72 + 73 + const totalDuration = Math.round(end2 - start1); 74 + 75 + timingInfo = { 76 + mode: 'Sequential', 77 + duration: totalDuration, 78 + requests: 2, 79 + description: 'Profile and feed fetched in two separate HTTP requests', 80 + request1Duration: Math.round(end1 - start1), 81 + request2Duration: Math.round(end2 - start2), 82 + }; 83 + } 84 + } catch (err) { 85 + error = err.message || 'Failed to load profile and feed'; 86 + console.error('Error:', err); 87 + } finally { 88 + loading = false; 89 + } 90 + } 91 + 92 + function formatDate(dateString) { 93 + const date = new Date(dateString); 94 + const now = new Date(); 95 + const diffMs = now - date; 96 + const diffMins = Math.floor(diffMs / 60000); 97 + const diffHours = Math.floor(diffMs / 3600000); 98 + const diffDays = Math.floor(diffMs / 86400000); 99 + 100 + if (diffMins < 1) return 'just now'; 101 + if (diffMins < 60) return `${diffMins}m ago`; 102 + if (diffHours < 24) return `${diffHours}h ago`; 103 + if (diffDays < 7) return `${diffDays}d ago`; 104 + 105 + return date.toLocaleDateString(); 106 + } 107 + </script> 108 + 109 + <h1>🦋 Bluesky Feed Reader</h1> 110 + 111 + <div class="container"> 112 + <div class="info-box"> 113 + <h3>Cap'n Web RPC + AT Protocol</h3> 114 + <p> 115 + This demo showcases <strong>Cap'n Web RPC</strong> batch pipelining by fetching 116 + Bluesky profiles and feeds via <strong>AT Protocol's XRPC</strong> endpoints. 117 + </p> 118 + <p> 119 + Try switching between <strong>Batched</strong> and <strong>Sequential</strong> modes 120 + to see how pipelining reduces round trips and improves performance! 121 + </p> 122 + </div> 123 + 124 + <div class="input-section"> 125 + <div class="mode-toggle"> 126 + <span><strong>Mode:</strong></span> 127 + <label> 128 + <input type="radio" bind:group={mode} value="batched" aria-label="Batched (Pipeline) Mode" /> 129 + Batched (Pipeline) 130 + </label> 131 + <label> 132 + <input type="radio" bind:group={mode} value="sequential" aria-label="Sequential Mode" /> 133 + Sequential 134 + </label> 135 + </div> 136 + 137 + <div class="input-group"> 138 + <input 139 + type="text" 140 + bind:value={handle} 141 + placeholder="Enter Bluesky handle (e.g., bsky.app)" 142 + onkeydown={(e) => e.key === 'Enter' && !loading && loadProfileAndFeed()} 143 + disabled={loading} 144 + aria-label="Bluesky handle" 145 + /> 146 + <button onclick={loadProfileAndFeed} disabled={loading} aria-label="Load Profile & Feed"> 147 + {loading ? 'Loading...' : 'Load Profile & Feed'} 148 + </button> 149 + </div> 150 + </div> 151 + 152 + {#if error} 153 + <div class="error"> 154 + <strong>Error:</strong> {error} 155 + </div> 156 + {/if} 157 + 158 + {#if loading} 159 + <div class="loading"> 160 + <div>Loading profile and feed...</div> 161 + </div> 162 + {/if} 163 + 164 + {#if timingInfo} 165 + <div class="timing-info"> 166 + <h4>⚡ Performance: {timingInfo.mode}</h4> 167 + <p><strong>Total Duration:</strong> {timingInfo.duration}ms</p> 168 + <p><strong>HTTP Requests:</strong> {timingInfo.requests}</p> 169 + <p>{timingInfo.description}</p> 170 + {#if timingInfo.request1Duration} 171 + <p style="margin-top: 12px;"> 172 + <strong>Request 1 (Profile):</strong> {timingInfo.request1Duration}ms<br/> 173 + <strong>Request 2 (Feed):</strong> {timingInfo.request2Duration}ms 174 + </p> 175 + {/if} 176 + </div> 177 + {/if} 178 + 179 + {#if profile} 180 + <div class="profile-section"> 181 + <div class="profile-header"> 182 + {#if profile.avatar} 183 + <img src={profile.avatar} alt={profile.displayName || profile.handle} class="profile-avatar" /> 184 + {/if} 185 + <div class="profile-info"> 186 + <h2>{profile.displayName || profile.handle}</h2> 187 + <div class="profile-handle">@{profile.handle}</div> 188 + <div class="profile-stats"> 189 + <div class="stat"> 190 + <div class="stat-value">{profile.postsCount || 0}</div> 191 + <div class="stat-label">Posts</div> 192 + </div> 193 + <div class="stat"> 194 + <div class="stat-value">{profile.followersCount || 0}</div> 195 + <div class="stat-label">Followers</div> 196 + </div> 197 + <div class="stat"> 198 + <div class="stat-value">{profile.followsCount || 0}</div> 199 + <div class="stat-label">Following</div> 200 + </div> 201 + </div> 202 + </div> 203 + </div> 204 + {#if profile.description} 205 + <div class="profile-bio">{profile.description}</div> 206 + {/if} 207 + </div> 208 + {/if} 209 + 210 + {#if feed && feed.posts} 211 + <div class="feed-section"> 212 + <h3>Recent Posts ({feed.posts.length})</h3> 213 + {#each feed.posts as post} 214 + <div class="post"> 215 + <div class="post-author"> 216 + {#if post.author.avatar} 217 + <img src={post.author.avatar} alt={post.author.handle} class="post-avatar" /> 218 + {/if} 219 + <div class="post-author-info"> 220 + <div class="post-author-name">{post.author.displayName || post.author.handle}</div> 221 + <div class="post-author-handle">@{post.author.handle}</div> 222 + </div> 223 + </div> 224 + {#if post.record.text} 225 + <div class="post-text">{post.record.text}</div> 226 + {/if} 227 + <div class="post-stats"> 228 + <div class="post-stat">💬 {post.replyCount || 0}</div> 229 + <div class="post-stat">🔄 {post.repostCount || 0}</div> 230 + <div class="post-stat">❤️ {post.likeCount || 0}</div> 231 + </div> 232 + {#if post.indexedAt} 233 + <div class="post-time">{formatDate(post.indexedAt)}</div> 234 + {/if} 235 + </div> 236 + {/each} 237 + </div> 238 + {/if} 239 + </div> 240 + 241 + <style> 242 + .container { 243 + background: white; 244 + border-radius: 12px; 245 + padding: 30px; 246 + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); 247 + } 248 + 249 + .input-section { 250 + margin-bottom: 30px; 251 + } 252 + 253 + .input-group { 254 + display: flex; 255 + gap: 10px; 256 + margin-bottom: 15px; 257 + } 258 + 259 + input[type="text"] { 260 + flex: 1; 261 + padding: 12px 16px; 262 + border: 2px solid #e0e0e0; 263 + border-radius: 8px; 264 + font-size: 16px; 265 + transition: border-color 0.3s; 266 + } 267 + 268 + input[type="text"]:focus { 269 + outline: none; 270 + border-color: #667eea; 271 + } 272 + 273 + button { 274 + padding: 12px 24px; 275 + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 276 + color: white; 277 + border: none; 278 + border-radius: 8px; 279 + font-size: 16px; 280 + font-weight: 600; 281 + cursor: pointer; 282 + transition: transform 0.2s, opacity 0.3s; 283 + } 284 + 285 + button:hover:not(:disabled) { 286 + transform: translateY(-2px); 287 + } 288 + 289 + button:disabled { 290 + opacity: 0.6; 291 + cursor: not-allowed; 292 + } 293 + 294 + .mode-toggle { 295 + display: flex; 296 + gap: 10px; 297 + align-items: center; 298 + margin-bottom: 15px; 299 + padding: 12px; 300 + background: #f5f5f5; 301 + border-radius: 8px; 302 + } 303 + 304 + .mode-toggle label { 305 + display: flex; 306 + align-items: center; 307 + gap: 8px; 308 + cursor: pointer; 309 + font-size: 14px; 310 + } 311 + 312 + .mode-toggle input[type="radio"] { 313 + cursor: pointer; 314 + } 315 + 316 + .loading { 317 + text-align: center; 318 + padding: 40px; 319 + color: #666; 320 + font-size: 18px; 321 + } 322 + 323 + .error { 324 + padding: 15px; 325 + background: #fee; 326 + color: #c33; 327 + border-radius: 8px; 328 + margin-bottom: 20px; 329 + border-left: 4px solid #c33; 330 + } 331 + 332 + .profile-section { 333 + margin-bottom: 30px; 334 + padding: 25px; 335 + background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); 336 + border-radius: 12px; 337 + } 338 + 339 + .profile-header { 340 + display: flex; 341 + gap: 20px; 342 + align-items: start; 343 + margin-bottom: 20px; 344 + } 345 + 346 + .profile-avatar { 347 + width: 100px; 348 + height: 100px; 349 + border-radius: 50%; 350 + object-fit: cover; 351 + border: 4px solid white; 352 + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 353 + } 354 + 355 + .profile-info h2 { 356 + margin: 0 0 8px 0; 357 + font-size: 24px; 358 + color: #333; 359 + } 360 + 361 + .profile-handle { 362 + color: #667eea; 363 + font-size: 16px; 364 + margin-bottom: 12px; 365 + } 366 + 367 + .profile-stats { 368 + display: flex; 369 + gap: 20px; 370 + margin-top: 15px; 371 + } 372 + 373 + .stat { 374 + display: flex; 375 + flex-direction: column; 376 + align-items: center; 377 + padding: 10px 20px; 378 + background: white; 379 + border-radius: 8px; 380 + min-width: 80px; 381 + } 382 + 383 + .stat-value { 384 + font-size: 24px; 385 + font-weight: bold; 386 + color: #667eea; 387 + } 388 + 389 + .stat-label { 390 + font-size: 12px; 391 + color: #666; 392 + text-transform: uppercase; 393 + margin-top: 4px; 394 + } 395 + 396 + .profile-bio { 397 + margin-top: 15px; 398 + padding: 15px; 399 + background: white; 400 + border-radius: 8px; 401 + color: #555; 402 + line-height: 1.6; 403 + } 404 + 405 + .feed-section h3 { 406 + margin-top: 0; 407 + margin-bottom: 20px; 408 + color: #333; 409 + font-size: 22px; 410 + } 411 + 412 + .post { 413 + padding: 20px; 414 + background: #f9f9f9; 415 + border-radius: 8px; 416 + margin-bottom: 15px; 417 + border-left: 4px solid #667eea; 418 + } 419 + 420 + .post-author { 421 + display: flex; 422 + align-items: center; 423 + gap: 12px; 424 + margin-bottom: 12px; 425 + } 426 + 427 + .post-avatar { 428 + width: 40px; 429 + height: 40px; 430 + border-radius: 50%; 431 + object-fit: cover; 432 + } 433 + 434 + .post-author-info { 435 + flex: 1; 436 + } 437 + 438 + .post-author-name { 439 + font-weight: 600; 440 + color: #333; 441 + font-size: 15px; 442 + } 443 + 444 + .post-author-handle { 445 + color: #888; 446 + font-size: 13px; 447 + } 448 + 449 + .post-text { 450 + margin-bottom: 12px; 451 + line-height: 1.6; 452 + color: #333; 453 + white-space: pre-wrap; 454 + } 455 + 456 + .post-stats { 457 + display: flex; 458 + gap: 20px; 459 + font-size: 14px; 460 + color: #666; 461 + } 462 + 463 + .post-stat { 464 + display: flex; 465 + align-items: center; 466 + gap: 6px; 467 + } 468 + 469 + .post-time { 470 + font-size: 13px; 471 + color: #999; 472 + margin-top: 8px; 473 + } 474 + 475 + .timing-info { 476 + margin: 20px 0; 477 + padding: 15px; 478 + background: #e8f5e9; 479 + border-radius: 8px; 480 + border-left: 4px solid #4caf50; 481 + } 482 + 483 + .timing-info h4 { 484 + margin: 0 0 8px 0; 485 + color: #2e7d32; 486 + font-size: 16px; 487 + } 488 + 489 + .timing-info p { 490 + margin: 4px 0; 491 + color: #555; 492 + font-size: 14px; 493 + } 494 + 495 + .info-box { 496 + padding: 20px; 497 + background: #f0f7ff; 498 + border-radius: 8px; 499 + border-left: 4px solid #2196f3; 500 + margin-bottom: 20px; 501 + } 502 + 503 + .info-box h3 { 504 + margin: 0 0 10px 0; 505 + color: #1565c0; 506 + font-size: 18px; 507 + } 508 + 509 + .info-box p { 510 + margin: 8px 0; 511 + color: #555; 512 + line-height: 1.6; 513 + } 514 + 515 + 516 + </style>
+28
examples/bluesky/static/src/app.css
··· 1 + * { 2 + box-sizing: border-box; 3 + } 4 + 5 + body { 6 + margin: 0; 7 + padding: 20px; 8 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 9 + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 10 + sans-serif; 11 + -webkit-font-smoothing: antialiased; 12 + -moz-osx-font-smoothing: grayscale; 13 + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 14 + min-height: 100vh; 15 + } 16 + 17 + #app { 18 + max-width: 900px; 19 + margin: 0 auto; 20 + } 21 + 22 + h1 { 23 + color: white; 24 + text-align: center; 25 + margin-bottom: 30px; 26 + font-size: 2.5rem; 27 + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); 28 + }
+10
examples/bluesky/static/src/main.js
··· 1 + import './app.css' 2 + import App from './App.svelte' 3 + import { mount } from "svelte"; 4 + 5 + const app = mount(App, { 6 + target: document.getElementById('app') 7 + }) 8 + 9 + export default app 10 +
+6
examples/bluesky/static/svelte.config.js
··· 1 + import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 2 + 3 + export default { 4 + preprocess: vitePreprocess(), 5 + }; 6 +
+13
examples/bluesky/static/vite.config.js
··· 1 + import { defineConfig } from 'vite'; 2 + import { svelte } from '@sveltejs/vite-plugin-svelte'; 3 + 4 + export default defineConfig({ 5 + plugins: [svelte()], 6 + server: { 7 + port: 3000, 8 + proxy: { 9 + '/rpc': 'http://localhost:8000', 10 + }, 11 + }, 12 + }); 13 +