···2323- [x] Reuse the existing normalization and upsert path for on-demand indexing jobs
2424- [x] Trigger indexing jobs from repo, issue, PR, profile, and similar fetch handlers
2525- [x] Add dedupe, retries, and observability for indexing jobs
2626-- [ ] Add a JetStream cache consumer with a persisted timestamp cursor
2727-- [ ] Seed the JetStream cursor to `now - 24h` on first boot and rewind slightly on reconnect
2828-- [ ] Store and serve bounded recent activity from the local cache
2626+- [x] Add a JetStream cache consumer with a persisted timestamp cursor
2727+- [x] Seed the JetStream cursor to `now - 24h` on first boot and rewind slightly on reconnect
2828+- [x] Store and serve bounded recent activity from the local cache
2929- [ ] Keep Tap as the authoritative indexing and bulk backfill path
3030- [ ] Define a controlled backfill and repo-resync playbook for recovery (`docs/references/resync.md`)
3131
-51
packages/api/internal/api/actors.go
···178178 }
179179}
180180181181-// isError is a type-safe errors.As replacement for pointer receiver targets.
182182-func isError[T error](err error, target *T) bool {
183183- if err == nil {
184184- return false
185185- }
186186-187187- type unwrapper interface{ Unwrap() error }
188188- for e := err; e != nil; {
189189- if t, ok := e.(T); ok {
190190- *target = t
191191- return true
192192- }
193193- if u, ok := e.(unwrapper); ok {
194194- e = u.Unwrap()
195195- } else {
196196- break
197197- }
198198- }
199199- return false
200200-}
201201-202181// handleGetActor returns the actor's Tangled profile + optional Bluesky info.
203182// GET /actors/{handle}
204183func (s *Server) handleGetActor(w http.ResponseWriter, r *http.Request) {
···872851 return pulls, statusMap, nil
873852}
874853875875-func resolveIssueState(stateMap map[string]string, issueURI string) string {
876876- raw := stateMap[issueURI]
877877- if strings.HasSuffix(raw, ".closed") {
878878- return "closed"
879879- }
880880- return "open"
881881-}
882882-883883-func resolvePullStatus(statusMap map[string]string, pullURI string) string {
884884- raw := statusMap[pullURI]
885885- switch {
886886- case strings.HasSuffix(raw, ".merged"):
887887- return "merged"
888888- case strings.HasSuffix(raw, ".closed"):
889889- return "closed"
890890- default:
891891- return "open"
892892- }
893893-}
894894-895854type bskyProfileResponse struct {
896855 DisplayName string `json:"displayName,omitempty"`
897856 Avatar string `json:"avatar,omitempty"`
···910869 }
911870 return &p
912871}
913913-914914-// parseATURI splits an AT URI (at://did/collection/rkey) into its components.
915915-func parseATURI(uri string) (did, collection, rkey string, err error) {
916916- trimmed := strings.TrimPrefix(uri, "at://")
917917- parts := strings.SplitN(trimmed, "/", 3)
918918- if len(parts) != 3 {
919919- return "", "", "", fmt.Errorf("invalid AT URI: %q", uri)
920920- }
921921- return parts[0], parts[1], parts[2], nil
922922-}
···11+CREATE TABLE IF NOT EXISTS jetstream_events (
22+ id INTEGER PRIMARY KEY AUTOINCREMENT,
33+ time_us INTEGER NOT NULL,
44+ did TEXT NOT NULL,
55+ kind TEXT NOT NULL,
66+ collection TEXT,
77+ rkey TEXT,
88+ operation TEXT,
99+ payload TEXT NOT NULL,
1010+ received_at TEXT NOT NULL
1111+);
1212+1313+CREATE INDEX IF NOT EXISTS idx_jetstream_events_time_us
1414+ ON jetstream_events(time_us DESC);