atmo.rsvp
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

add follow activity

Florian 34984622 19146f29

+1541 -81
+39 -37
lexicons/generated/index.ts
··· 3 3 // at `/xrpc/<namespace>.lexicons` for consumer apps to typegen against. 4 4 5 5 import _0 from "../pulled/app/bsky/actor/profile.json"; 6 - import _1 from "../pulled/community/lexicon/calendar/event.json"; 7 - import _2 from "../pulled/community/lexicon/calendar/rsvp.json"; 8 - import _3 from "../pulled/community/lexicon/location/address.json"; 9 - import _4 from "../pulled/community/lexicon/location/fsq.json"; 10 - import _5 from "../pulled/community/lexicon/location/geo.json"; 11 - import _6 from "../pulled/community/lexicon/location/hthree.json"; 12 - import _7 from "./rsvp/atmo/authFull.json"; 13 - import _8 from "./rsvp/atmo/event/getRecord.json"; 14 - import _9 from "./rsvp/atmo/event/listRecords.json"; 15 - import _10 from "./rsvp/atmo/getCursor.json"; 16 - import _11 from "./rsvp/atmo/getOverview.json"; 17 - import _12 from "./rsvp/atmo/getProfile.json"; 18 - import _13 from "./rsvp/atmo/invite/create.json"; 19 - import _14 from "./rsvp/atmo/invite/defs.json"; 20 - import _15 from "./rsvp/atmo/invite/list.json"; 21 - import _16 from "./rsvp/atmo/invite/redeem.json"; 22 - import _17 from "./rsvp/atmo/invite/revoke.json"; 23 - import _18 from "./rsvp/atmo/notifyOfUpdate.json"; 24 - import _19 from "./rsvp/atmo/rsvp/getRecord.json"; 25 - import _20 from "./rsvp/atmo/rsvp/listRecords.json"; 26 - import _21 from "./rsvp/atmo/space/addMember.json"; 27 - import _22 from "./rsvp/atmo/space/createSpace.json"; 28 - import _23 from "./rsvp/atmo/space/defs.json"; 29 - import _24 from "./rsvp/atmo/space/deleteRecord.json"; 30 - import _25 from "./rsvp/atmo/space/getBlob.json"; 31 - import _26 from "./rsvp/atmo/space/getRecord.json"; 32 - import _27 from "./rsvp/atmo/space/getSpace.json"; 33 - import _28 from "./rsvp/atmo/space/leaveSpace.json"; 34 - import _29 from "./rsvp/atmo/space/listBlobs.json"; 35 - import _30 from "./rsvp/atmo/space/listMembers.json"; 36 - import _31 from "./rsvp/atmo/space/listRecords.json"; 37 - import _32 from "./rsvp/atmo/space/listSpaces.json"; 38 - import _33 from "./rsvp/atmo/space/putRecord.json"; 39 - import _34 from "./rsvp/atmo/space/removeMember.json"; 40 - import _35 from "./rsvp/atmo/space/uploadBlob.json"; 41 - import _36 from "./rsvp/atmo/spaceExt/whoami.json"; 6 + import _1 from "../pulled/app/bsky/graph/follow.json"; 7 + import _2 from "../pulled/community/lexicon/calendar/event.json"; 8 + import _3 from "../pulled/community/lexicon/calendar/rsvp.json"; 9 + import _4 from "../pulled/community/lexicon/location/address.json"; 10 + import _5 from "../pulled/community/lexicon/location/fsq.json"; 11 + import _6 from "../pulled/community/lexicon/location/geo.json"; 12 + import _7 from "../pulled/community/lexicon/location/hthree.json"; 13 + import _8 from "./rsvp/atmo/authFull.json"; 14 + import _9 from "./rsvp/atmo/event/getRecord.json"; 15 + import _10 from "./rsvp/atmo/event/listRecords.json"; 16 + import _11 from "./rsvp/atmo/getCursor.json"; 17 + import _12 from "./rsvp/atmo/getFeed.json"; 18 + import _13 from "./rsvp/atmo/getOverview.json"; 19 + import _14 from "./rsvp/atmo/getProfile.json"; 20 + import _15 from "./rsvp/atmo/invite/create.json"; 21 + import _16 from "./rsvp/atmo/invite/defs.json"; 22 + import _17 from "./rsvp/atmo/invite/list.json"; 23 + import _18 from "./rsvp/atmo/invite/redeem.json"; 24 + import _19 from "./rsvp/atmo/invite/revoke.json"; 25 + import _20 from "./rsvp/atmo/notifyOfUpdate.json"; 26 + import _21 from "./rsvp/atmo/rsvp/getRecord.json"; 27 + import _22 from "./rsvp/atmo/rsvp/listRecords.json"; 28 + import _23 from "./rsvp/atmo/space/addMember.json"; 29 + import _24 from "./rsvp/atmo/space/createSpace.json"; 30 + import _25 from "./rsvp/atmo/space/defs.json"; 31 + import _26 from "./rsvp/atmo/space/deleteRecord.json"; 32 + import _27 from "./rsvp/atmo/space/getBlob.json"; 33 + import _28 from "./rsvp/atmo/space/getRecord.json"; 34 + import _29 from "./rsvp/atmo/space/getSpace.json"; 35 + import _30 from "./rsvp/atmo/space/leaveSpace.json"; 36 + import _31 from "./rsvp/atmo/space/listBlobs.json"; 37 + import _32 from "./rsvp/atmo/space/listMembers.json"; 38 + import _33 from "./rsvp/atmo/space/listRecords.json"; 39 + import _34 from "./rsvp/atmo/space/listSpaces.json"; 40 + import _35 from "./rsvp/atmo/space/putRecord.json"; 41 + import _36 from "./rsvp/atmo/space/removeMember.json"; 42 + import _37 from "./rsvp/atmo/space/uploadBlob.json"; 43 + import _38 from "./rsvp/atmo/spaceExt/whoami.json"; 42 44 43 - export const lexicons: object[] = [_0, _1, _2, _3, _4, _5, _6, _7, _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]; 45 + export const lexicons: object[] = [_0, _1, _2, _3, _4, _5, _6, _7, _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, _38];
+1
lexicons/generated/rsvp/atmo/authFull.json
··· 15 15 "rsvp.atmo.event.getRecord", 16 16 "rsvp.atmo.event.listRecords", 17 17 "rsvp.atmo.getCursor", 18 + "rsvp.atmo.getFeed", 18 19 "rsvp.atmo.getOverview", 19 20 "rsvp.atmo.getProfile", 20 21 "rsvp.atmo.invite.create",
+512
lexicons/generated/rsvp/atmo/getFeed.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "rsvp.atmo.getFeed", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get a personalized feed based on followed users' activity", 8 + "parameters": { 9 + "type": "params", 10 + "required": [ 11 + "feed", 12 + "actor" 13 + ], 14 + "properties": { 15 + "feed": { 16 + "type": "string", 17 + "knownValues": [ 18 + "network" 19 + ], 20 + "description": "Feed name" 21 + }, 22 + "actor": { 23 + "type": "string", 24 + "format": "at-identifier", 25 + "description": "DID or handle of the requesting user" 26 + }, 27 + "collection": { 28 + "type": "string", 29 + "knownValues": [ 30 + "community.lexicon.calendar.event", 31 + "community.lexicon.calendar.rsvp" 32 + ], 33 + "description": "Filter by target collection (defaults to first target)" 34 + }, 35 + "limit": { 36 + "type": "integer", 37 + "minimum": 1, 38 + "maximum": 200, 39 + "default": 50 40 + }, 41 + "cursor": { 42 + "type": "string" 43 + }, 44 + "profiles": { 45 + "type": "boolean", 46 + "description": "Include profile + identity info keyed by DID" 47 + }, 48 + "search": { 49 + "type": "string", 50 + "description": "Full-text search" 51 + }, 52 + "mode": { 53 + "type": "string", 54 + "description": "Filter by mode" 55 + }, 56 + "name": { 57 + "type": "string", 58 + "description": "Filter by name" 59 + }, 60 + "status": { 61 + "type": "string", 62 + "description": "Filter by status" 63 + }, 64 + "description": { 65 + "type": "string", 66 + "description": "Filter by description" 67 + }, 68 + "preferencesShowInDiscovery": { 69 + "type": "string", 70 + "description": "Filter by preferences.showInDiscovery" 71 + }, 72 + "startsAtMin": { 73 + "type": "string", 74 + "description": "Minimum value for startsAt" 75 + }, 76 + "startsAtMax": { 77 + "type": "string", 78 + "description": "Maximum value for startsAt" 79 + }, 80 + "endsAtMin": { 81 + "type": "string", 82 + "description": "Minimum value for endsAt" 83 + }, 84 + "endsAtMax": { 85 + "type": "string", 86 + "description": "Maximum value for endsAt" 87 + }, 88 + "createdAtMin": { 89 + "type": "string", 90 + "description": "Minimum value for createdAt" 91 + }, 92 + "createdAtMax": { 93 + "type": "string", 94 + "description": "Maximum value for createdAt" 95 + }, 96 + "rsvpsCountMin": { 97 + "type": "integer", 98 + "description": "Minimum total rsvps count" 99 + }, 100 + "hydrateRsvps": { 101 + "type": "integer", 102 + "minimum": 1, 103 + "maximum": 50, 104 + "description": "Number of rsvps records to embed per record" 105 + }, 106 + "rsvpsInterestedCountMin": { 107 + "type": "integer", 108 + "description": "Minimum rsvps count where status = interested" 109 + }, 110 + "rsvpsGoingCountMin": { 111 + "type": "integer", 112 + "description": "Minimum rsvps count where status = going" 113 + }, 114 + "rsvpsNotgoingCountMin": { 115 + "type": "integer", 116 + "description": "Minimum rsvps count where status = notgoing" 117 + }, 118 + "subjectUri": { 119 + "type": "string", 120 + "description": "Filter by subject.uri" 121 + }, 122 + "hydrateEvent": { 123 + "type": "boolean", 124 + "description": "Embed the referenced event record" 125 + }, 126 + "sort": { 127 + "type": "string", 128 + "knownValues": [ 129 + "mode", 130 + "name", 131 + "status", 132 + "description", 133 + "preferencesShowInDiscovery", 134 + "startsAt", 135 + "endsAt", 136 + "createdAt", 137 + "rsvpsCount", 138 + "rsvpsInterestedCount", 139 + "rsvpsGoingCount", 140 + "rsvpsNotgoingCount", 141 + "subjectUri" 142 + ], 143 + "description": "Field to sort by (default: time_us)" 144 + }, 145 + "order": { 146 + "type": "string", 147 + "knownValues": [ 148 + "asc", 149 + "desc" 150 + ], 151 + "description": "Sort direction" 152 + } 153 + } 154 + }, 155 + "output": { 156 + "encoding": "application/json", 157 + "schema": { 158 + "type": "object", 159 + "required": [ 160 + "records" 161 + ], 162 + "properties": { 163 + "records": { 164 + "type": "array", 165 + "items": { 166 + "type": "union", 167 + "refs": [ 168 + "#feedRecord_event", 169 + "#feedRecord_rsvp" 170 + ] 171 + } 172 + }, 173 + "cursor": { 174 + "type": "string" 175 + }, 176 + "profiles": { 177 + "type": "array", 178 + "items": { 179 + "type": "ref", 180 + "ref": "#profileEntry" 181 + } 182 + } 183 + } 184 + } 185 + } 186 + }, 187 + "feedRecord_event": { 188 + "type": "object", 189 + "required": [ 190 + "uri", 191 + "cid", 192 + "value", 193 + "did", 194 + "collection", 195 + "rkey", 196 + "time_us" 197 + ], 198 + "properties": { 199 + "uri": { 200 + "type": "string", 201 + "format": "at-uri" 202 + }, 203 + "cid": { 204 + "type": "string", 205 + "format": "cid" 206 + }, 207 + "value": { 208 + "type": "ref", 209 + "ref": "community.lexicon.calendar.event#main" 210 + }, 211 + "did": { 212 + "type": "string", 213 + "format": "did" 214 + }, 215 + "collection": { 216 + "type": "string", 217 + "format": "nsid" 218 + }, 219 + "rkey": { 220 + "type": "string" 221 + }, 222 + "time_us": { 223 + "type": "integer" 224 + }, 225 + "space": { 226 + "type": "string", 227 + "description": "Present when the record was read from a permissioned space; its value is the `ats://` space URI." 228 + }, 229 + "rsvpsCount": { 230 + "type": "integer", 231 + "description": "Total rsvps count" 232 + }, 233 + "rsvpsInterestedCount": { 234 + "type": "integer", 235 + "description": "rsvps count where status = interested" 236 + }, 237 + "rsvpsGoingCount": { 238 + "type": "integer", 239 + "description": "rsvps count where status = going" 240 + }, 241 + "rsvpsNotgoingCount": { 242 + "type": "integer", 243 + "description": "rsvps count where status = notgoing" 244 + }, 245 + "rsvps": { 246 + "type": "ref", 247 + "ref": "#hydrateRsvps" 248 + } 249 + } 250 + }, 251 + "feedRecord_rsvp": { 252 + "type": "object", 253 + "required": [ 254 + "uri", 255 + "cid", 256 + "value", 257 + "did", 258 + "collection", 259 + "rkey", 260 + "time_us" 261 + ], 262 + "properties": { 263 + "uri": { 264 + "type": "string", 265 + "format": "at-uri" 266 + }, 267 + "cid": { 268 + "type": "string", 269 + "format": "cid" 270 + }, 271 + "value": { 272 + "type": "ref", 273 + "ref": "community.lexicon.calendar.rsvp#main" 274 + }, 275 + "did": { 276 + "type": "string", 277 + "format": "did" 278 + }, 279 + "collection": { 280 + "type": "string", 281 + "format": "nsid" 282 + }, 283 + "rkey": { 284 + "type": "string" 285 + }, 286 + "time_us": { 287 + "type": "integer" 288 + }, 289 + "space": { 290 + "type": "string", 291 + "description": "Present when the record was read from a permissioned space; its value is the `ats://` space URI." 292 + }, 293 + "event": { 294 + "type": "ref", 295 + "ref": "#refEventRecord" 296 + } 297 + } 298 + }, 299 + "hydrateRsvpsRecord": { 300 + "type": "object", 301 + "required": [ 302 + "uri", 303 + "did", 304 + "collection", 305 + "rkey", 306 + "time_us" 307 + ], 308 + "properties": { 309 + "uri": { 310 + "type": "string", 311 + "format": "at-uri" 312 + }, 313 + "did": { 314 + "type": "string", 315 + "format": "did" 316 + }, 317 + "collection": { 318 + "type": "string", 319 + "format": "nsid" 320 + }, 321 + "rkey": { 322 + "type": "string" 323 + }, 324 + "cid": { 325 + "type": "string" 326 + }, 327 + "record": { 328 + "type": "ref", 329 + "ref": "community.lexicon.calendar.rsvp#main" 330 + }, 331 + "time_us": { 332 + "type": "integer" 333 + }, 334 + "space": { 335 + "type": "string", 336 + "description": "Present when the record was read from a permissioned space; `ats://` URI." 337 + } 338 + } 339 + }, 340 + "hydrateRsvps": { 341 + "type": "object", 342 + "properties": { 343 + "interested": { 344 + "type": "array", 345 + "items": { 346 + "type": "ref", 347 + "ref": "#hydrateRsvpsRecord" 348 + } 349 + }, 350 + "going": { 351 + "type": "array", 352 + "items": { 353 + "type": "ref", 354 + "ref": "#hydrateRsvpsRecord" 355 + } 356 + }, 357 + "notgoing": { 358 + "type": "array", 359 + "items": { 360 + "type": "ref", 361 + "ref": "#hydrateRsvpsRecord" 362 + } 363 + }, 364 + "other": { 365 + "type": "array", 366 + "items": { 367 + "type": "ref", 368 + "ref": "#hydrateRsvpsRecord" 369 + } 370 + } 371 + } 372 + }, 373 + "refEventRecord": { 374 + "type": "object", 375 + "required": [ 376 + "uri", 377 + "did", 378 + "collection", 379 + "rkey", 380 + "time_us" 381 + ], 382 + "properties": { 383 + "uri": { 384 + "type": "string", 385 + "format": "at-uri" 386 + }, 387 + "did": { 388 + "type": "string", 389 + "format": "did" 390 + }, 391 + "collection": { 392 + "type": "string", 393 + "format": "nsid" 394 + }, 395 + "rkey": { 396 + "type": "string" 397 + }, 398 + "cid": { 399 + "type": "string" 400 + }, 401 + "record": { 402 + "type": "ref", 403 + "ref": "community.lexicon.calendar.event#main" 404 + }, 405 + "time_us": { 406 + "type": "integer" 407 + }, 408 + "space": { 409 + "type": "string", 410 + "description": "Present when the record was read from a permissioned space; `ats://` URI." 411 + } 412 + } 413 + }, 414 + "profileEntry": { 415 + "type": "object", 416 + "required": [ 417 + "did" 418 + ], 419 + "properties": { 420 + "did": { 421 + "type": "string", 422 + "format": "did" 423 + }, 424 + "handle": { 425 + "type": "string" 426 + }, 427 + "uri": { 428 + "type": "string", 429 + "format": "at-uri" 430 + }, 431 + "cid": { 432 + "type": "string", 433 + "format": "cid" 434 + }, 435 + "value": { 436 + "type": "ref", 437 + "ref": "#appBskyActorProfile" 438 + }, 439 + "collection": { 440 + "type": "string", 441 + "format": "nsid" 442 + }, 443 + "rkey": { 444 + "type": "string" 445 + } 446 + } 447 + }, 448 + "appBskyActorProfile": { 449 + "type": "object", 450 + "properties": { 451 + "avatar": { 452 + "type": "blob", 453 + "accept": [ 454 + "image/png", 455 + "image/jpeg" 456 + ], 457 + "maxSize": 1000000, 458 + "description": "Small image to be displayed next to posts from account. AKA, 'profile picture'" 459 + }, 460 + "banner": { 461 + "type": "blob", 462 + "accept": [ 463 + "image/png", 464 + "image/jpeg" 465 + ], 466 + "maxSize": 1000000, 467 + "description": "Larger horizontal image to display behind profile view." 468 + }, 469 + "labels": { 470 + "refs": [ 471 + "com.atproto.label.defs#selfLabels" 472 + ], 473 + "type": "union", 474 + "description": "Self-label values, specific to the Bluesky application, on the overall account." 475 + }, 476 + "website": { 477 + "type": "string", 478 + "format": "uri" 479 + }, 480 + "pronouns": { 481 + "type": "string", 482 + "maxLength": 200, 483 + "description": "Free-form pronouns text.", 484 + "maxGraphemes": 20 485 + }, 486 + "createdAt": { 487 + "type": "string", 488 + "format": "datetime" 489 + }, 490 + "pinnedPost": { 491 + "ref": "com.atproto.repo.strongRef", 492 + "type": "ref" 493 + }, 494 + "description": { 495 + "type": "string", 496 + "maxLength": 2560, 497 + "description": "Free-form profile description text.", 498 + "maxGraphemes": 256 499 + }, 500 + "displayName": { 501 + "type": "string", 502 + "maxLength": 640, 503 + "maxGraphemes": 64 504 + }, 505 + "joinedViaStarterPack": { 506 + "ref": "com.atproto.repo.strongRef", 507 + "type": "ref" 508 + } 509 + } 510 + } 511 + } 512 + }
+30
lexicons/pulled/app/bsky/graph/follow.json
··· 1 + { 2 + "id": "app.bsky.graph.follow", 3 + "defs": { 4 + "main": { 5 + "key": "tid", 6 + "type": "record", 7 + "record": { 8 + "type": "object", 9 + "required": ["subject", "createdAt"], 10 + "properties": { 11 + "via": { 12 + "ref": "com.atproto.repo.strongRef", 13 + "type": "ref" 14 + }, 15 + "subject": { 16 + "type": "string", 17 + "format": "did" 18 + }, 19 + "createdAt": { 20 + "type": "string", 21 + "format": "datetime" 22 + } 23 + } 24 + }, 25 + "description": "Record declaring a social 'follow' relationship of another account. Duplicate follows will be ignored by the AppView." 26 + } 27 + }, 28 + "$type": "com.atproto.lexicon.schema", 29 + "lexicon": 1 30 + }
+2 -2
package.json
··· 31 31 "@atcute/lexicons": "^1.2.9", 32 32 "@atcute/oauth-node-client": "^1.1.0", 33 33 "@atcute/tid": "^1.1.2", 34 - "@atmo-dev/contrail-lexicons": "^0.4.3", 34 + "@atmo-dev/contrail-lexicons": "^0.4.4", 35 35 "@cloudflare/workers-types": "^4.20260317.1", 36 36 "@eslint/compat": "^2.0.3", 37 37 "@eslint/js": "^10.0.1", ··· 62 62 "dependencies": { 63 63 "@atcute/bluesky-richtext-parser": "^2.1.1", 64 64 "@atcute/jetstream": "^1.1.2", 65 - "@atmo-dev/contrail": "^0.4.2", 65 + "@atmo-dev/contrail": "^0.5.0", 66 66 "@ethercorps/sveltekit-og": "^4.2.1", 67 67 "@foxui/colors": "^0.8.5", 68 68 "@foxui/core": "^0.9.1",
+11 -11
pnpm-lock.yaml
··· 15 15 specifier: ^1.1.2 16 16 version: 1.1.2 17 17 '@atmo-dev/contrail': 18 - specifier: ^0.4.2 19 - version: 0.4.2(wrangler@4.77.0(@cloudflare/workers-types@4.20260317.1)) 18 + specifier: ^0.5.0 19 + version: 0.5.0(wrangler@4.77.0(@cloudflare/workers-types@4.20260317.1)) 20 20 '@ethercorps/sveltekit-og': 21 21 specifier: ^4.2.1 22 22 version: 4.2.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.0)(vite@8.0.3(@types/node@25.0.10)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.0)(typescript@6.0.2)(vite@8.0.3(@types/node@25.0.10)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))) ··· 100 100 specifier: ^1.1.2 101 101 version: 1.1.2 102 102 '@atmo-dev/contrail-lexicons': 103 - specifier: ^0.4.3 104 - version: 0.4.3(wrangler@4.77.0(@cloudflare/workers-types@4.20260317.1)) 103 + specifier: ^0.4.4 104 + version: 0.4.4(wrangler@4.77.0(@cloudflare/workers-types@4.20260317.1)) 105 105 '@cloudflare/workers-types': 106 106 specifier: ^4.20260317.1 107 107 version: 4.20260317.1 ··· 276 276 '@atcute/xrpc-server@0.1.12': 277 277 resolution: {integrity: sha512-70KIerQlljp5+s6t0u6YNN9klEboQUZa2hhoi/hmXIO1cIKEORettTMctnyjfcCJaSfAuj42dxPu51GTZBlm8w==} 278 278 279 - '@atmo-dev/contrail-lexicons@0.4.3': 280 - resolution: {integrity: sha512-j9eEiiOWj9usOqEK0hFoVcl/NSMM29X0S/vO7SlmE5jEF2UoWwXAxIr81uZ3HODtlWn5ztgWS7Ls8a6GGIU/lw==} 279 + '@atmo-dev/contrail-lexicons@0.4.4': 280 + resolution: {integrity: sha512-rb22K1H0NYp9Y7Okui/ny42T3X4xfj795p+pPrtWeBsio2ZsK9uuFtn3E+XnB314sGSUQQSe0FtkgVCUjLT+uQ==} 281 281 hasBin: true 282 282 283 - '@atmo-dev/contrail@0.4.2': 284 - resolution: {integrity: sha512-ixPuxovCMnXQtjhxFhGzlDLxFt3r5FfozrQ0e9ABOjujqzP7nYpfCT1ltPJxSt2nHQJ43IUnVqqkPvV9GO0AaA==} 283 + '@atmo-dev/contrail@0.5.0': 284 + resolution: {integrity: sha512-PeeA3Q6NDwaho2dgaW4QANPRvhjKBml17MMxjcq6DwQG7p0MTUqoa2RM1G7DpA8CAIPrRXTVcosb6ybqshlWtQ==} 285 285 hasBin: true 286 286 peerDependencies: 287 287 pg: ^8.0.0 ··· 3168 3168 '@badrap/valita': 0.4.6 3169 3169 nanoid: 5.1.7 3170 3170 3171 - '@atmo-dev/contrail-lexicons@0.4.3(wrangler@4.77.0(@cloudflare/workers-types@4.20260317.1))': 3171 + '@atmo-dev/contrail-lexicons@0.4.4(wrangler@4.77.0(@cloudflare/workers-types@4.20260317.1))': 3172 3172 dependencies: 3173 3173 '@atcute/lex-cli': 2.5.3 3174 - '@atmo-dev/contrail': 0.4.2(wrangler@4.77.0(@cloudflare/workers-types@4.20260317.1)) 3174 + '@atmo-dev/contrail': 0.5.0(wrangler@4.77.0(@cloudflare/workers-types@4.20260317.1)) 3175 3175 transitivePeerDependencies: 3176 3176 - pg 3177 3177 - react 3178 3178 - wrangler 3179 3179 3180 - '@atmo-dev/contrail@0.4.2(wrangler@4.77.0(@cloudflare/workers-types@4.20260317.1))': 3180 + '@atmo-dev/contrail@0.5.0(wrangler@4.77.0(@cloudflare/workers-types@4.20260317.1))': 3181 3181 dependencies: 3182 3182 '@atcute/atproto': 3.1.10 3183 3183 '@atcute/cbor': 2.3.2
+4
src/app.d.ts
··· 37 37 DB: D1Database; 38 38 CRON_SECRET: string; 39 39 }; 40 + /** Cloudflare Worker execution context. Use `ctx.waitUntil(promise)` to 41 + * let the worker keep a fire-and-forget task alive after the response 42 + * has been sent. Optional in dev (wrangler proxy may not provide it). */ 43 + ctx?: { waitUntil(promise: Promise<unknown>): void }; 40 44 } 41 45 } 42 46 }
+2
src/lexicon-types/index.ts
··· 1 1 export * as AppBskyActorProfile from "./types/app/bsky/actor/profile.js"; 2 + export * as AppBskyGraphFollow from "./types/app/bsky/graph/follow.js"; 2 3 export * as CommunityLexiconCalendarEvent from "./types/community/lexicon/calendar/event.js"; 3 4 export * as CommunityLexiconCalendarRsvp from "./types/community/lexicon/calendar/rsvp.js"; 4 5 export * as CommunityLexiconLocationAddress from "./types/community/lexicon/location/address.js"; ··· 8 9 export * as RsvpAtmoEventGetRecord from "./types/rsvp/atmo/event/getRecord.js"; 9 10 export * as RsvpAtmoEventListRecords from "./types/rsvp/atmo/event/listRecords.js"; 10 11 export * as RsvpAtmoGetCursor from "./types/rsvp/atmo/getCursor.js"; 12 + export * as RsvpAtmoGetFeed from "./types/rsvp/atmo/getFeed.js"; 11 13 export * as RsvpAtmoGetOverview from "./types/rsvp/atmo/getOverview.js"; 12 14 export * as RsvpAtmoGetProfile from "./types/rsvp/atmo/getProfile.js"; 13 15 export * as RsvpAtmoInviteCreate from "./types/rsvp/atmo/invite/create.js";
+30
src/lexicon-types/types/app/bsky/graph/follow.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + import * as ComAtprotoRepoStrongRef from "@atcute/atproto/types/repo/strongRef"; 5 + 6 + const _mainSchema = /*#__PURE__*/ v.record( 7 + /*#__PURE__*/ v.tidString(), 8 + /*#__PURE__*/ v.object({ 9 + $type: /*#__PURE__*/ v.literal("app.bsky.graph.follow"), 10 + createdAt: /*#__PURE__*/ v.datetimeString(), 11 + subject: /*#__PURE__*/ v.didString(), 12 + get via() { 13 + return /*#__PURE__*/ v.optional(ComAtprotoRepoStrongRef.mainSchema); 14 + }, 15 + }), 16 + ); 17 + 18 + type main$schematype = typeof _mainSchema; 19 + 20 + export interface mainSchema extends main$schematype {} 21 + 22 + export const mainSchema = _mainSchema as mainSchema; 23 + 24 + export interface Main extends v.InferInput<typeof mainSchema> {} 25 + 26 + declare module "@atcute/lexicons/ambient" { 27 + interface Records { 28 + "app.bsky.graph.follow": mainSchema; 29 + } 30 + }
+156
src/lexicon-types/types/rsvp/atmo/follow/getRecord.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + import * as AppBskyGraphFollow from "../../../app/bsky/graph/follow.js"; 5 + import * as ComAtprotoLabelDefs from "@atcute/atproto/types/label/defs"; 6 + import * as ComAtprotoRepoStrongRef from "@atcute/atproto/types/repo/strongRef"; 7 + 8 + const _appBskyActorProfileSchema = /*#__PURE__*/ v.object({ 9 + $type: /*#__PURE__*/ v.optional( 10 + /*#__PURE__*/ v.literal("rsvp.atmo.follow.getRecord#appBskyActorProfile"), 11 + ), 12 + /** 13 + * Small image to be displayed next to posts from account. AKA, 'profile picture' 14 + * @accept image/png, image/jpeg 15 + * @maxSize 1000000 16 + */ 17 + avatar: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.blob()), 18 + /** 19 + * Larger horizontal image to display behind profile view. 20 + * @accept image/png, image/jpeg 21 + * @maxSize 1000000 22 + */ 23 + banner: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.blob()), 24 + createdAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 25 + /** 26 + * Free-form profile description text. 27 + * @maxLength 2560 28 + * @maxGraphemes 256 29 + */ 30 + description: /*#__PURE__*/ v.optional( 31 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 32 + /*#__PURE__*/ v.stringLength(0, 2560), 33 + /*#__PURE__*/ v.stringGraphemes(0, 256), 34 + ]), 35 + ), 36 + /** 37 + * @maxLength 640 38 + * @maxGraphemes 64 39 + */ 40 + displayName: /*#__PURE__*/ v.optional( 41 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 42 + /*#__PURE__*/ v.stringLength(0, 640), 43 + /*#__PURE__*/ v.stringGraphemes(0, 64), 44 + ]), 45 + ), 46 + get joinedViaStarterPack() { 47 + return /*#__PURE__*/ v.optional(ComAtprotoRepoStrongRef.mainSchema); 48 + }, 49 + /** 50 + * Self-label values, specific to the Bluesky application, on the overall account. 51 + */ 52 + get labels() { 53 + return /*#__PURE__*/ v.optional( 54 + /*#__PURE__*/ v.variant([ComAtprotoLabelDefs.selfLabelsSchema]), 55 + ); 56 + }, 57 + get pinnedPost() { 58 + return /*#__PURE__*/ v.optional(ComAtprotoRepoStrongRef.mainSchema); 59 + }, 60 + /** 61 + * Free-form pronouns text. 62 + * @maxLength 200 63 + * @maxGraphemes 20 64 + */ 65 + pronouns: /*#__PURE__*/ v.optional( 66 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 67 + /*#__PURE__*/ v.stringLength(0, 200), 68 + /*#__PURE__*/ v.stringGraphemes(0, 20), 69 + ]), 70 + ), 71 + website: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), 72 + }); 73 + const _mainSchema = /*#__PURE__*/ v.query("rsvp.atmo.follow.getRecord", { 74 + params: /*#__PURE__*/ v.object({ 75 + /** 76 + * Read-grant invite token for anonymous bearer access. Replaces JWT auth when supplied. 77 + */ 78 + inviteToken: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 79 + /** 80 + * Include profile + identity info keyed by DID 81 + */ 82 + profiles: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), 83 + /** 84 + * If set, fetch from this permissioned space (requires service-auth JWT or a read-grant invite token). `ats://` URI. 85 + */ 86 + spaceUri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 87 + /** 88 + * AT URI of the record 89 + */ 90 + uri: /*#__PURE__*/ v.resourceUriString(), 91 + }), 92 + output: { 93 + type: "lex", 94 + schema: /*#__PURE__*/ v.object({ 95 + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.cidString()), 96 + collection: /*#__PURE__*/ v.nsidString(), 97 + did: /*#__PURE__*/ v.didString(), 98 + get profiles() { 99 + return /*#__PURE__*/ v.optional( 100 + /*#__PURE__*/ v.array(profileEntrySchema), 101 + ); 102 + }, 103 + rkey: /*#__PURE__*/ v.string(), 104 + /** 105 + * Present when the record was read from a permissioned space; its value is the `ats://` space URI. 106 + */ 107 + space: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 108 + time_us: /*#__PURE__*/ v.integer(), 109 + uri: /*#__PURE__*/ v.resourceUriString(), 110 + get value() { 111 + return AppBskyGraphFollow.mainSchema; 112 + }, 113 + }), 114 + }, 115 + }); 116 + const _profileEntrySchema = /*#__PURE__*/ v.object({ 117 + $type: /*#__PURE__*/ v.optional( 118 + /*#__PURE__*/ v.literal("rsvp.atmo.follow.getRecord#profileEntry"), 119 + ), 120 + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.cidString()), 121 + collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), 122 + did: /*#__PURE__*/ v.didString(), 123 + handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 124 + rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 125 + uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), 126 + get value() { 127 + return /*#__PURE__*/ v.optional(appBskyActorProfileSchema); 128 + }, 129 + }); 130 + 131 + type appBskyActorProfile$schematype = typeof _appBskyActorProfileSchema; 132 + type main$schematype = typeof _mainSchema; 133 + type profileEntry$schematype = typeof _profileEntrySchema; 134 + 135 + export interface appBskyActorProfileSchema extends appBskyActorProfile$schematype {} 136 + export interface mainSchema extends main$schematype {} 137 + export interface profileEntrySchema extends profileEntry$schematype {} 138 + 139 + export const appBskyActorProfileSchema = 140 + _appBskyActorProfileSchema as appBskyActorProfileSchema; 141 + export const mainSchema = _mainSchema as mainSchema; 142 + export const profileEntrySchema = _profileEntrySchema as profileEntrySchema; 143 + 144 + export interface AppBskyActorProfile extends v.InferInput< 145 + typeof appBskyActorProfileSchema 146 + > {} 147 + export interface ProfileEntry extends v.InferInput<typeof profileEntrySchema> {} 148 + 149 + export interface $params extends v.InferInput<mainSchema["params"]> {} 150 + export interface $output extends v.InferXRPCBodyInput<mainSchema["output"]> {} 151 + 152 + declare module "@atcute/lexicons/ambient" { 153 + interface XRPCQueries { 154 + "rsvp.atmo.follow.getRecord": mainSchema; 155 + } 156 + }
+185
src/lexicon-types/types/rsvp/atmo/follow/listRecords.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + import * as AppBskyGraphFollow from "../../../app/bsky/graph/follow.js"; 5 + import * as ComAtprotoLabelDefs from "@atcute/atproto/types/label/defs"; 6 + import * as ComAtprotoRepoStrongRef from "@atcute/atproto/types/repo/strongRef"; 7 + 8 + const _appBskyActorProfileSchema = /*#__PURE__*/ v.object({ 9 + $type: /*#__PURE__*/ v.optional( 10 + /*#__PURE__*/ v.literal("rsvp.atmo.follow.listRecords#appBskyActorProfile"), 11 + ), 12 + /** 13 + * Small image to be displayed next to posts from account. AKA, 'profile picture' 14 + * @accept image/png, image/jpeg 15 + * @maxSize 1000000 16 + */ 17 + avatar: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.blob()), 18 + /** 19 + * Larger horizontal image to display behind profile view. 20 + * @accept image/png, image/jpeg 21 + * @maxSize 1000000 22 + */ 23 + banner: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.blob()), 24 + createdAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 25 + /** 26 + * Free-form profile description text. 27 + * @maxLength 2560 28 + * @maxGraphemes 256 29 + */ 30 + description: /*#__PURE__*/ v.optional( 31 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 32 + /*#__PURE__*/ v.stringLength(0, 2560), 33 + /*#__PURE__*/ v.stringGraphemes(0, 256), 34 + ]), 35 + ), 36 + /** 37 + * @maxLength 640 38 + * @maxGraphemes 64 39 + */ 40 + displayName: /*#__PURE__*/ v.optional( 41 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 42 + /*#__PURE__*/ v.stringLength(0, 640), 43 + /*#__PURE__*/ v.stringGraphemes(0, 64), 44 + ]), 45 + ), 46 + get joinedViaStarterPack() { 47 + return /*#__PURE__*/ v.optional(ComAtprotoRepoStrongRef.mainSchema); 48 + }, 49 + /** 50 + * Self-label values, specific to the Bluesky application, on the overall account. 51 + */ 52 + get labels() { 53 + return /*#__PURE__*/ v.optional( 54 + /*#__PURE__*/ v.variant([ComAtprotoLabelDefs.selfLabelsSchema]), 55 + ); 56 + }, 57 + get pinnedPost() { 58 + return /*#__PURE__*/ v.optional(ComAtprotoRepoStrongRef.mainSchema); 59 + }, 60 + /** 61 + * Free-form pronouns text. 62 + * @maxLength 200 63 + * @maxGraphemes 20 64 + */ 65 + pronouns: /*#__PURE__*/ v.optional( 66 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 67 + /*#__PURE__*/ v.stringLength(0, 200), 68 + /*#__PURE__*/ v.stringGraphemes(0, 20), 69 + ]), 70 + ), 71 + website: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), 72 + }); 73 + const _mainSchema = /*#__PURE__*/ v.query("rsvp.atmo.follow.listRecords", { 74 + params: /*#__PURE__*/ v.object({ 75 + /** 76 + * Filter by DID or handle (triggers on-demand backfill) 77 + */ 78 + actor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.actorIdentifierString()), 79 + /** 80 + * Only used with spaceUri — filter to records authored by this DID. 81 + */ 82 + byUser: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.didString()), 83 + cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 84 + /** 85 + * Read-grant invite token for anonymous bearer access. Replaces JWT auth when supplied. 86 + */ 87 + inviteToken: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 88 + /** 89 + * @minimum 1 90 + * @maximum 200 91 + * @default 50 92 + */ 93 + limit: /*#__PURE__*/ v.optional( 94 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.integer(), [ 95 + /*#__PURE__*/ v.integerRange(1, 200), 96 + ]), 97 + 50, 98 + ), 99 + /** 100 + * Include profile + identity info keyed by DID 101 + */ 102 + profiles: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), 103 + /** 104 + * If set, query records inside this permissioned space (requires service-auth JWT or a read-grant invite token). `ats://` URI. 105 + */ 106 + spaceUri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 107 + }), 108 + output: { 109 + type: "lex", 110 + schema: /*#__PURE__*/ v.object({ 111 + cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 112 + get profiles() { 113 + return /*#__PURE__*/ v.optional( 114 + /*#__PURE__*/ v.array(profileEntrySchema), 115 + ); 116 + }, 117 + get records() { 118 + return /*#__PURE__*/ v.array(recordSchema); 119 + }, 120 + }), 121 + }, 122 + }); 123 + const _profileEntrySchema = /*#__PURE__*/ v.object({ 124 + $type: /*#__PURE__*/ v.optional( 125 + /*#__PURE__*/ v.literal("rsvp.atmo.follow.listRecords#profileEntry"), 126 + ), 127 + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.cidString()), 128 + collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), 129 + did: /*#__PURE__*/ v.didString(), 130 + handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 131 + rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 132 + uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), 133 + get value() { 134 + return /*#__PURE__*/ v.optional(appBskyActorProfileSchema); 135 + }, 136 + }); 137 + const _recordSchema = /*#__PURE__*/ v.object({ 138 + $type: /*#__PURE__*/ v.optional( 139 + /*#__PURE__*/ v.literal("rsvp.atmo.follow.listRecords#record"), 140 + ), 141 + cid: /*#__PURE__*/ v.cidString(), 142 + collection: /*#__PURE__*/ v.nsidString(), 143 + did: /*#__PURE__*/ v.didString(), 144 + rkey: /*#__PURE__*/ v.string(), 145 + /** 146 + * Present when the record was read from a permissioned space; its value is the `ats://` space URI. 147 + */ 148 + space: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 149 + time_us: /*#__PURE__*/ v.integer(), 150 + uri: /*#__PURE__*/ v.resourceUriString(), 151 + get value() { 152 + return AppBskyGraphFollow.mainSchema; 153 + }, 154 + }); 155 + 156 + type appBskyActorProfile$schematype = typeof _appBskyActorProfileSchema; 157 + type main$schematype = typeof _mainSchema; 158 + type profileEntry$schematype = typeof _profileEntrySchema; 159 + type record$schematype = typeof _recordSchema; 160 + 161 + export interface appBskyActorProfileSchema extends appBskyActorProfile$schematype {} 162 + export interface mainSchema extends main$schematype {} 163 + export interface profileEntrySchema extends profileEntry$schematype {} 164 + export interface recordSchema extends record$schematype {} 165 + 166 + export const appBskyActorProfileSchema = 167 + _appBskyActorProfileSchema as appBskyActorProfileSchema; 168 + export const mainSchema = _mainSchema as mainSchema; 169 + export const profileEntrySchema = _profileEntrySchema as profileEntrySchema; 170 + export const recordSchema = _recordSchema as recordSchema; 171 + 172 + export interface AppBskyActorProfile extends v.InferInput< 173 + typeof appBskyActorProfileSchema 174 + > {} 175 + export interface ProfileEntry extends v.InferInput<typeof profileEntrySchema> {} 176 + export interface Record extends v.InferInput<typeof recordSchema> {} 177 + 178 + export interface $params extends v.InferInput<mainSchema["params"]> {} 179 + export interface $output extends v.InferXRPCBodyInput<mainSchema["output"]> {} 180 + 181 + declare module "@atcute/lexicons/ambient" { 182 + interface XRPCQueries { 183 + "rsvp.atmo.follow.listRecords": mainSchema; 184 + } 185 + }
+435
src/lexicon-types/types/rsvp/atmo/getFeed.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + import * as ComAtprotoLabelDefs from "@atcute/atproto/types/label/defs"; 5 + import * as ComAtprotoRepoStrongRef from "@atcute/atproto/types/repo/strongRef"; 6 + import * as CommunityLexiconCalendarEvent from "../../community/lexicon/calendar/event.js"; 7 + import * as CommunityLexiconCalendarRsvp from "../../community/lexicon/calendar/rsvp.js"; 8 + 9 + const _appBskyActorProfileSchema = /*#__PURE__*/ v.object({ 10 + $type: /*#__PURE__*/ v.optional( 11 + /*#__PURE__*/ v.literal("rsvp.atmo.getFeed#appBskyActorProfile"), 12 + ), 13 + /** 14 + * Small image to be displayed next to posts from account. AKA, 'profile picture' 15 + * @accept image/png, image/jpeg 16 + * @maxSize 1000000 17 + */ 18 + avatar: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.blob()), 19 + /** 20 + * Larger horizontal image to display behind profile view. 21 + * @accept image/png, image/jpeg 22 + * @maxSize 1000000 23 + */ 24 + banner: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.blob()), 25 + createdAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 26 + /** 27 + * Free-form profile description text. 28 + * @maxLength 2560 29 + * @maxGraphemes 256 30 + */ 31 + description: /*#__PURE__*/ v.optional( 32 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 33 + /*#__PURE__*/ v.stringLength(0, 2560), 34 + /*#__PURE__*/ v.stringGraphemes(0, 256), 35 + ]), 36 + ), 37 + /** 38 + * @maxLength 640 39 + * @maxGraphemes 64 40 + */ 41 + displayName: /*#__PURE__*/ v.optional( 42 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 43 + /*#__PURE__*/ v.stringLength(0, 640), 44 + /*#__PURE__*/ v.stringGraphemes(0, 64), 45 + ]), 46 + ), 47 + get joinedViaStarterPack() { 48 + return /*#__PURE__*/ v.optional(ComAtprotoRepoStrongRef.mainSchema); 49 + }, 50 + /** 51 + * Self-label values, specific to the Bluesky application, on the overall account. 52 + */ 53 + get labels() { 54 + return /*#__PURE__*/ v.optional( 55 + /*#__PURE__*/ v.variant([ComAtprotoLabelDefs.selfLabelsSchema]), 56 + ); 57 + }, 58 + get pinnedPost() { 59 + return /*#__PURE__*/ v.optional(ComAtprotoRepoStrongRef.mainSchema); 60 + }, 61 + /** 62 + * Free-form pronouns text. 63 + * @maxLength 200 64 + * @maxGraphemes 20 65 + */ 66 + pronouns: /*#__PURE__*/ v.optional( 67 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 68 + /*#__PURE__*/ v.stringLength(0, 200), 69 + /*#__PURE__*/ v.stringGraphemes(0, 20), 70 + ]), 71 + ), 72 + website: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), 73 + }); 74 + const _feedRecord_eventSchema = /*#__PURE__*/ v.object({ 75 + $type: /*#__PURE__*/ v.optional( 76 + /*#__PURE__*/ v.literal("rsvp.atmo.getFeed#feedRecord_event"), 77 + ), 78 + cid: /*#__PURE__*/ v.cidString(), 79 + collection: /*#__PURE__*/ v.nsidString(), 80 + did: /*#__PURE__*/ v.didString(), 81 + rkey: /*#__PURE__*/ v.string(), 82 + get rsvps() { 83 + return /*#__PURE__*/ v.optional(hydrateRsvpsSchema); 84 + }, 85 + /** 86 + * Total rsvps count 87 + */ 88 + rsvpsCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 89 + /** 90 + * rsvps count where status = going 91 + */ 92 + rsvpsGoingCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 93 + /** 94 + * rsvps count where status = interested 95 + */ 96 + rsvpsInterestedCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 97 + /** 98 + * rsvps count where status = notgoing 99 + */ 100 + rsvpsNotgoingCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 101 + /** 102 + * Present when the record was read from a permissioned space; its value is the `ats://` space URI. 103 + */ 104 + space: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 105 + time_us: /*#__PURE__*/ v.integer(), 106 + uri: /*#__PURE__*/ v.resourceUriString(), 107 + get value() { 108 + return CommunityLexiconCalendarEvent.mainSchema; 109 + }, 110 + }); 111 + const _feedRecord_rsvpSchema = /*#__PURE__*/ v.object({ 112 + $type: /*#__PURE__*/ v.optional( 113 + /*#__PURE__*/ v.literal("rsvp.atmo.getFeed#feedRecord_rsvp"), 114 + ), 115 + cid: /*#__PURE__*/ v.cidString(), 116 + collection: /*#__PURE__*/ v.nsidString(), 117 + did: /*#__PURE__*/ v.didString(), 118 + get event() { 119 + return /*#__PURE__*/ v.optional(refEventRecordSchema); 120 + }, 121 + rkey: /*#__PURE__*/ v.string(), 122 + /** 123 + * Present when the record was read from a permissioned space; its value is the `ats://` space URI. 124 + */ 125 + space: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 126 + time_us: /*#__PURE__*/ v.integer(), 127 + uri: /*#__PURE__*/ v.resourceUriString(), 128 + get value() { 129 + return CommunityLexiconCalendarRsvp.mainSchema; 130 + }, 131 + }); 132 + const _hydrateRsvpsSchema = /*#__PURE__*/ v.object({ 133 + $type: /*#__PURE__*/ v.optional( 134 + /*#__PURE__*/ v.literal("rsvp.atmo.getFeed#hydrateRsvps"), 135 + ), 136 + get going() { 137 + return /*#__PURE__*/ v.optional( 138 + /*#__PURE__*/ v.array(hydrateRsvpsRecordSchema), 139 + ); 140 + }, 141 + get interested() { 142 + return /*#__PURE__*/ v.optional( 143 + /*#__PURE__*/ v.array(hydrateRsvpsRecordSchema), 144 + ); 145 + }, 146 + get notgoing() { 147 + return /*#__PURE__*/ v.optional( 148 + /*#__PURE__*/ v.array(hydrateRsvpsRecordSchema), 149 + ); 150 + }, 151 + get other() { 152 + return /*#__PURE__*/ v.optional( 153 + /*#__PURE__*/ v.array(hydrateRsvpsRecordSchema), 154 + ); 155 + }, 156 + }); 157 + const _hydrateRsvpsRecordSchema = /*#__PURE__*/ v.object({ 158 + $type: /*#__PURE__*/ v.optional( 159 + /*#__PURE__*/ v.literal("rsvp.atmo.getFeed#hydrateRsvpsRecord"), 160 + ), 161 + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 162 + collection: /*#__PURE__*/ v.nsidString(), 163 + did: /*#__PURE__*/ v.didString(), 164 + get record() { 165 + return /*#__PURE__*/ v.optional(CommunityLexiconCalendarRsvp.mainSchema); 166 + }, 167 + rkey: /*#__PURE__*/ v.string(), 168 + /** 169 + * Present when the record was read from a permissioned space; `ats://` URI. 170 + */ 171 + space: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 172 + time_us: /*#__PURE__*/ v.integer(), 173 + uri: /*#__PURE__*/ v.resourceUriString(), 174 + }); 175 + const _mainSchema = /*#__PURE__*/ v.query("rsvp.atmo.getFeed", { 176 + params: /*#__PURE__*/ v.object({ 177 + /** 178 + * DID or handle of the requesting user 179 + */ 180 + actor: /*#__PURE__*/ v.actorIdentifierString(), 181 + /** 182 + * Filter by target collection (defaults to first target) 183 + */ 184 + collection: /*#__PURE__*/ v.optional( 185 + /*#__PURE__*/ v.string< 186 + | "community.lexicon.calendar.event" 187 + | "community.lexicon.calendar.rsvp" 188 + | (string & {}) 189 + >(), 190 + ), 191 + /** 192 + * Maximum value for createdAt 193 + */ 194 + createdAtMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 195 + /** 196 + * Minimum value for createdAt 197 + */ 198 + createdAtMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 199 + cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 200 + /** 201 + * Filter by description 202 + */ 203 + description: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 204 + /** 205 + * Maximum value for endsAt 206 + */ 207 + endsAtMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 208 + /** 209 + * Minimum value for endsAt 210 + */ 211 + endsAtMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 212 + /** 213 + * Feed name 214 + */ 215 + feed: /*#__PURE__*/ v.string<"network" | (string & {})>(), 216 + /** 217 + * Embed the referenced event record 218 + */ 219 + hydrateEvent: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), 220 + /** 221 + * Number of rsvps records to embed per record 222 + * @minimum 1 223 + * @maximum 50 224 + */ 225 + hydrateRsvps: /*#__PURE__*/ v.optional( 226 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.integer(), [ 227 + /*#__PURE__*/ v.integerRange(1, 50), 228 + ]), 229 + ), 230 + /** 231 + * @minimum 1 232 + * @maximum 200 233 + * @default 50 234 + */ 235 + limit: /*#__PURE__*/ v.optional( 236 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.integer(), [ 237 + /*#__PURE__*/ v.integerRange(1, 200), 238 + ]), 239 + 50, 240 + ), 241 + /** 242 + * Filter by mode 243 + */ 244 + mode: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 245 + /** 246 + * Filter by name 247 + */ 248 + name: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 249 + /** 250 + * Sort direction 251 + */ 252 + order: /*#__PURE__*/ v.optional( 253 + /*#__PURE__*/ v.string<"asc" | "desc" | (string & {})>(), 254 + ), 255 + /** 256 + * Filter by preferences.showInDiscovery 257 + */ 258 + preferencesShowInDiscovery: /*#__PURE__*/ v.optional( 259 + /*#__PURE__*/ v.string(), 260 + ), 261 + /** 262 + * Include profile + identity info keyed by DID 263 + */ 264 + profiles: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), 265 + /** 266 + * Minimum total rsvps count 267 + */ 268 + rsvpsCountMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 269 + /** 270 + * Minimum rsvps count where status = going 271 + */ 272 + rsvpsGoingCountMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 273 + /** 274 + * Minimum rsvps count where status = interested 275 + */ 276 + rsvpsInterestedCountMin: /*#__PURE__*/ v.optional( 277 + /*#__PURE__*/ v.integer(), 278 + ), 279 + /** 280 + * Minimum rsvps count where status = notgoing 281 + */ 282 + rsvpsNotgoingCountMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 283 + /** 284 + * Full-text search 285 + */ 286 + search: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 287 + /** 288 + * Field to sort by (default: time_us) 289 + */ 290 + sort: /*#__PURE__*/ v.optional( 291 + /*#__PURE__*/ v.string< 292 + | "createdAt" 293 + | "description" 294 + | "endsAt" 295 + | "mode" 296 + | "name" 297 + | "preferencesShowInDiscovery" 298 + | "rsvpsCount" 299 + | "rsvpsGoingCount" 300 + | "rsvpsInterestedCount" 301 + | "rsvpsNotgoingCount" 302 + | "startsAt" 303 + | "status" 304 + | "subjectUri" 305 + | (string & {}) 306 + >(), 307 + ), 308 + /** 309 + * Maximum value for startsAt 310 + */ 311 + startsAtMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 312 + /** 313 + * Minimum value for startsAt 314 + */ 315 + startsAtMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 316 + /** 317 + * Filter by status 318 + */ 319 + status: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 320 + /** 321 + * Filter by subject.uri 322 + */ 323 + subjectUri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 324 + }), 325 + output: { 326 + type: "lex", 327 + schema: /*#__PURE__*/ v.object({ 328 + cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 329 + get profiles() { 330 + return /*#__PURE__*/ v.optional( 331 + /*#__PURE__*/ v.array(profileEntrySchema), 332 + ); 333 + }, 334 + get records() { 335 + return /*#__PURE__*/ v.array( 336 + /*#__PURE__*/ v.variant([ 337 + feedRecord_eventSchema, 338 + feedRecord_rsvpSchema, 339 + ]), 340 + ); 341 + }, 342 + }), 343 + }, 344 + }); 345 + const _profileEntrySchema = /*#__PURE__*/ v.object({ 346 + $type: /*#__PURE__*/ v.optional( 347 + /*#__PURE__*/ v.literal("rsvp.atmo.getFeed#profileEntry"), 348 + ), 349 + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.cidString()), 350 + collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), 351 + did: /*#__PURE__*/ v.didString(), 352 + handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 353 + rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 354 + uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), 355 + get value() { 356 + return /*#__PURE__*/ v.optional(appBskyActorProfileSchema); 357 + }, 358 + }); 359 + const _refEventRecordSchema = /*#__PURE__*/ v.object({ 360 + $type: /*#__PURE__*/ v.optional( 361 + /*#__PURE__*/ v.literal("rsvp.atmo.getFeed#refEventRecord"), 362 + ), 363 + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 364 + collection: /*#__PURE__*/ v.nsidString(), 365 + did: /*#__PURE__*/ v.didString(), 366 + get record() { 367 + return /*#__PURE__*/ v.optional(CommunityLexiconCalendarEvent.mainSchema); 368 + }, 369 + rkey: /*#__PURE__*/ v.string(), 370 + /** 371 + * Present when the record was read from a permissioned space; `ats://` URI. 372 + */ 373 + space: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 374 + time_us: /*#__PURE__*/ v.integer(), 375 + uri: /*#__PURE__*/ v.resourceUriString(), 376 + }); 377 + 378 + type appBskyActorProfile$schematype = typeof _appBskyActorProfileSchema; 379 + type feedRecord_event$schematype = typeof _feedRecord_eventSchema; 380 + type feedRecord_rsvp$schematype = typeof _feedRecord_rsvpSchema; 381 + type hydrateRsvps$schematype = typeof _hydrateRsvpsSchema; 382 + type hydrateRsvpsRecord$schematype = typeof _hydrateRsvpsRecordSchema; 383 + type main$schematype = typeof _mainSchema; 384 + type profileEntry$schematype = typeof _profileEntrySchema; 385 + type refEventRecord$schematype = typeof _refEventRecordSchema; 386 + 387 + export interface appBskyActorProfileSchema extends appBskyActorProfile$schematype {} 388 + export interface feedRecord_eventSchema extends feedRecord_event$schematype {} 389 + export interface feedRecord_rsvpSchema extends feedRecord_rsvp$schematype {} 390 + export interface hydrateRsvpsSchema extends hydrateRsvps$schematype {} 391 + export interface hydrateRsvpsRecordSchema extends hydrateRsvpsRecord$schematype {} 392 + export interface mainSchema extends main$schematype {} 393 + export interface profileEntrySchema extends profileEntry$schematype {} 394 + export interface refEventRecordSchema extends refEventRecord$schematype {} 395 + 396 + export const appBskyActorProfileSchema = 397 + _appBskyActorProfileSchema as appBskyActorProfileSchema; 398 + export const feedRecord_eventSchema = 399 + _feedRecord_eventSchema as feedRecord_eventSchema; 400 + export const feedRecord_rsvpSchema = 401 + _feedRecord_rsvpSchema as feedRecord_rsvpSchema; 402 + export const hydrateRsvpsSchema = _hydrateRsvpsSchema as hydrateRsvpsSchema; 403 + export const hydrateRsvpsRecordSchema = 404 + _hydrateRsvpsRecordSchema as hydrateRsvpsRecordSchema; 405 + export const mainSchema = _mainSchema as mainSchema; 406 + export const profileEntrySchema = _profileEntrySchema as profileEntrySchema; 407 + export const refEventRecordSchema = 408 + _refEventRecordSchema as refEventRecordSchema; 409 + 410 + export interface AppBskyActorProfile extends v.InferInput< 411 + typeof appBskyActorProfileSchema 412 + > {} 413 + export interface FeedRecord_event extends v.InferInput< 414 + typeof feedRecord_eventSchema 415 + > {} 416 + export interface FeedRecord_rsvp extends v.InferInput< 417 + typeof feedRecord_rsvpSchema 418 + > {} 419 + export interface HydrateRsvps extends v.InferInput<typeof hydrateRsvpsSchema> {} 420 + export interface HydrateRsvpsRecord extends v.InferInput< 421 + typeof hydrateRsvpsRecordSchema 422 + > {} 423 + export interface ProfileEntry extends v.InferInput<typeof profileEntrySchema> {} 424 + export interface RefEventRecord extends v.InferInput< 425 + typeof refEventRecordSchema 426 + > {} 427 + 428 + export interface $params extends v.InferInput<mainSchema["params"]> {} 429 + export interface $output extends v.InferXRPCBodyInput<mainSchema["output"]> {} 430 + 431 + declare module "@atcute/lexicons/ambient" { 432 + interface XRPCQueries { 433 + "rsvp.atmo.getFeed": mainSchema; 434 + } 435 + }
+10 -4
src/lib/contrail.config.ts
··· 62 62 queryable: { 63 63 status: {}, 64 64 'subject.uri': {}, 65 - // Sortable so the home-page activity feed can order by when the user 66 - // actually RSVP'd (record.createdAt) — not by when contrail indexed 67 - // it (time_us). Without this, a fresh backfill clusters thousands of 68 - // historical RSVPs at the top of time_us order, drowning live activity. 69 65 createdAt: { type: 'range' } 70 66 }, 71 67 references: { ··· 74 70 field: 'subject.uri' 75 71 } 76 72 } 73 + } 74 + // `follow` (app.bsky.graph.follow) is auto-added by contrail 0.5+ when 75 + // `feeds` is configured: discover:false, subjectField:'subject' (so only 76 + // follows whose subject is already in identities are indexed). 77 + }, 78 + feeds: { 79 + // Exposed as rsvp.atmo.getFeed?feed=network&actor=<did>&collection=<nsid>. 80 + // Powers the home-page "from people you follow" surface. 81 + network: { 82 + targets: ['event', 'rsvp'] 77 83 } 78 84 } 79 85 };
+11 -1
src/lib/contrail.ts
··· 175 175 }; 176 176 } 177 177 178 + /** Structural minimum buildAttendee needs — every contrail profile-entry 179 + * shape (event.listRecords, rsvp.listRecords, getProfile, getFeed) widens to 180 + * this. Using a structural type avoids a union of nominally-distinct lex types 181 + * whose `$type` literals don't match. */ 182 + type AttendeeProfileEntry = { 183 + did: string; 184 + handle?: string; 185 + value?: { displayName?: string; avatar?: unknown }; 186 + }; 187 + 178 188 export function buildAttendee( 179 189 did: string, 180 190 status: 'going' | 'interested', 181 - profiles?: EventProfiles | RsvpProfileEntry[] 191 + profiles?: AttendeeProfileEntry[] 182 192 ): AttendeeInfo { 183 193 const profile = profiles?.find((entry) => entry.did === did); 184 194 const handle = profile?.handle;
+78 -25
src/routes/(app)/+page.server.ts
··· 84 84 limit: 20 85 85 }); 86 86 87 - const recentActivityPromise = (async (): Promise<ActivityCluster[]> => { 88 - const response = await publicClient.get('rsvp.atmo.rsvp.listRecords', { 89 - params: { 90 - hydrateEvent: true, 91 - profiles: true, 92 - sort: 'createdAt', 93 - order: 'desc', 94 - limit: ACTIVITY_FETCH_LIMIT 95 - } 96 - }); 97 - if (!response.ok) return []; 87 + // listRecords and getFeed return structurally identical rsvp records, but TS 88 + // sees them as nominally-distinct lex types. Cast inputs to the structural 89 + // minimum we actually use. 90 + type ActivityRsvp = { 91 + did: string; 92 + collection: string; 93 + uri: string; 94 + value?: { status?: string; createdAt?: string }; 95 + event?: Parameters<typeof flattenEventRecord>[0]; 96 + }; 97 + type ActivityProfile = Parameters<typeof buildAttendee>[2] extends Array<infer P> | undefined 98 + ? P 99 + : never; 98 100 99 - const records = response.data.records ?? []; 100 - const profiles = response.data.profiles ?? []; 101 + function clusterRsvps( 102 + records: ActivityRsvp[], 103 + profiles: ActivityProfile[] 104 + ): ActivityCluster[] { 101 105 const nowMs = Date.now(); 102 106 const clusters = new Map<string, ActivityCluster>(); 103 - 104 107 for (const r of records) { 105 108 const status = r.value?.status; 106 109 const isGoing = status?.endsWith('#going'); 107 110 const isInterested = status?.endsWith('#interested'); 108 111 if (!isGoing && !isInterested) continue; 109 - 110 112 if (!r.event) continue; 111 113 const flatEvent = flattenEventRecord(r.event); 112 114 if (!flatEvent) continue; 113 - 114 115 const eventEndMs = new Date(flatEvent.endsAt || flatEvent.startsAt).getTime(); 115 116 if (eventEndMs < nowMs) continue; 116 117 117 118 const attendee = buildAttendee(r.did, isGoing ? 'going' : 'interested', profiles); 118 - // `createdAt` is an atproto convention all RSVPs carry, even though the 119 - // community.lexicon.calendar.rsvp schema doesn't formally declare it. 120 - const createdAt = (r.value as { createdAt?: string } | undefined)?.createdAt; 121 - const createdAtMs = createdAt ? new Date(createdAt).getTime() : 0; 122 - 119 + const createdAtMs = r.value?.createdAt ? new Date(r.value.createdAt).getTime() : 0; 123 120 let cluster = clusters.get(flatEvent.uri); 124 121 if (!cluster) { 125 122 cluster = { event: flatEvent, attendees: [], latestCreatedAtMs: createdAtMs }; ··· 128 125 cluster.attendees.push(attendee); 129 126 if (createdAtMs > cluster.latestCreatedAtMs) cluster.latestCreatedAtMs = createdAtMs; 130 127 } 131 - 132 128 return Array.from(clusters.values()) 133 129 .sort((a, b) => b.latestCreatedAtMs - a.latestCreatedAtMs) 134 130 .slice(0, ACTIVITY_DISPLAY_LIMIT); 131 + } 132 + 133 + async function fetchGlobalActivity(): Promise<ActivityCluster[]> { 134 + const response = await publicClient.get('rsvp.atmo.rsvp.listRecords', { 135 + params: { 136 + hydrateEvent: true, 137 + profiles: true, 138 + sort: 'createdAt', 139 + order: 'desc', 140 + limit: ACTIVITY_FETCH_LIMIT 141 + } 142 + }); 143 + if (!response.ok) return []; 144 + return clusterRsvps( 145 + (response.data.records ?? []) as ActivityRsvp[], 146 + (response.data.profiles ?? []) as ActivityProfile[] 147 + ); 148 + } 149 + 150 + const recentActivityPromise = (async (): Promise<{ 151 + activity: ActivityCluster[]; 152 + isPersonalized: boolean; 153 + }> => { 154 + // Logged-in: try the personalized "from people you follow" feed first. 155 + // If empty (cold start, follows haven't RSVP'd to upcoming events), 156 + // fall back to the global recent-RSVP feed so the section isn't dead. 157 + // Anon: skip straight to global. 158 + if (locals.did) { 159 + const response = await publicClient.get('rsvp.atmo.getFeed', { 160 + params: { 161 + feed: 'network', 162 + actor: locals.did, 163 + // NOTE: contrail's getFeed runtime checks `collection` against 164 + // the SHORT names from `feedConfig.targets` (e.g. 'rsvp'), but 165 + // the regenerated lex schema documents the full NSID as the 166 + // valid enum. Pass the short name; full NSID returns 400. 167 + collection: 'rsvp', 168 + hydrateEvent: true, 169 + profiles: true, 170 + sort: 'createdAt', 171 + order: 'desc', 172 + limit: ACTIVITY_FETCH_LIMIT 173 + } 174 + }); 175 + if (response.ok) { 176 + const personalized = clusterRsvps( 177 + (response.data.records ?? []) as ActivityRsvp[], 178 + (response.data.profiles ?? []) as ActivityProfile[] 179 + ); 180 + if (personalized.length > 0) return { activity: personalized, isPersonalized: true }; 181 + } 182 + } 183 + return { activity: await fetchGlobalActivity(), isPersonalized: false }; 135 184 })(); 136 185 137 - const [myEvents, response, recentActivity] = await Promise.all([ 186 + const [myEvents, response, recentActivityResult] = await Promise.all([ 138 187 myEventsPromise, 139 188 globalPromise, 140 189 recentActivityPromise 141 190 ]); 191 + const { activity: recentActivity, isPersonalized: recentActivityIsPersonalized } = 192 + recentActivityResult; 142 193 143 194 if (!response) { 144 195 return { ··· 146 197 handles: {}, 147 198 myUpcoming: myEvents.upcoming, 148 199 myPast: myEvents.past, 149 - recentActivity 200 + recentActivity, 201 + recentActivityIsPersonalized 150 202 }; 151 203 } 152 204 ··· 160 212 handles, 161 213 myUpcoming: myEvents.upcoming, 162 214 myPast: myEvents.past, 163 - recentActivity 215 + recentActivity, 216 + recentActivityIsPersonalized 164 217 }; 165 218 };
+8 -1
src/routes/(app)/+page.svelte
··· 99 99 100 100 {#if data.recentActivity.length > 0} 101 101 <section class="mt-16"> 102 - <h2 class="text-base-900 dark:text-base-50 mb-4 text-xl font-bold">Recent activity</h2> 102 + <h2 class="text-base-900 dark:text-base-50 text-xl font-bold {data.recentActivityIsPersonalized ? 'mb-1' : 'mb-4'}"> 103 + {data.recentActivityIsPersonalized ? 'From people you follow' : 'Recent activity'} 104 + </h2> 105 + {#if data.recentActivityIsPersonalized} 106 + <p class="text-base-500 dark:text-base-400 mb-4 text-sm"> 107 + Events your Bluesky follows are RSVPing to. 108 + </p> 109 + {/if} 103 110 <RecentActivity activities={data.recentActivity} /> 104 111 </section> 105 112 {/if}
+27
src/routes/(oauth)/oauth/callback/+server.ts
··· 2 2 import { createOAuthClient } from '$lib/atproto/server/oauth'; 3 3 import { setSignedCookie } from '$lib/atproto/server/signed-cookie'; 4 4 import { scopes } from '$lib/atproto/settings'; 5 + import { getServerClient } from '$lib/contrail'; 5 6 import { dev } from '$app/environment'; 6 7 import type { RequestHandler } from './$types'; 7 8 ··· 10 11 11 12 // oauth.callback() validates the state parameter (CSRF protection) and 12 13 // exchanges the authorization code for tokens via the token endpoint. 14 + let did: string | null = null; 13 15 try { 14 16 const { session } = await oauth.callback(url.searchParams); 17 + did = session.did; 15 18 16 19 const cookieOpts = { 17 20 path: '/', ··· 26 29 } catch (e) { 27 30 console.error('OAuth callback failed:', e); 28 31 redirect(303, '/?error=auth_failed'); 32 + } 33 + 34 + // Pre-warm the personalized feed: fire-and-forget a getFeed call so contrail 35 + // kicks off the on-demand follow-backfill (which now runs out-of-band via 36 + // waitUntil) while the user is being redirected. By the time they land on 37 + // the home page, feed_items should be populated. We don't await — the call 38 + // itself returns immediately; the bootstrap continues in the background. 39 + if (did && platform?.env.DB) { 40 + const warm = (async () => { 41 + try { 42 + const client = getServerClient(platform.env.DB); 43 + await client.get('rsvp.atmo.getFeed', { 44 + params: { 45 + feed: 'network', 46 + actor: did as `did:${string}:${string}`, 47 + collection: 'rsvp', 48 + limit: 1 49 + } 50 + }); 51 + } catch (e) { 52 + console.warn('[oauth/callback] feed pre-warm failed:', e); 53 + } 54 + })(); 55 + platform.ctx?.waitUntil(warm); 29 56 } 30 57 31 58 const returnTo = cookies.get('oauth_return_to');