Auf dem Testserver lief die App beim Scrollen langer Timelines in ein 429: nach jeder Scroll-Pause ging sofort ein PUT /settings raus, und jedes sichtbare Vorschaubild zählte auf dasselbe Limit von 300/min. Danach blieben Bilder leer und "Einstellungen konnten nicht gespeichert werden" erschien. Backend: Medien-Reads bekommen einen eigenen Limiter (media, 1200/min), POST /client-logs nimmt Warnungen und Fehler der App an (client-logs, 30/min) und schreibt sie in den täglichen Kanal `client`; Exceptions tragen user_id, path und method im Kontext. Frontend: Einstellungen gehen frühestens 1,5 s nach der letzten Änderung und nur bei geändertem Inhalt zum Server, 429/Netzfehler werden still mit Backoff wiederholt, nur eine Ablehnung wird gemeldet. Bilder laufen durch ein Gate (6 parallel, Retry-After, 3 Versuche) und fallen auf einen Platzhalter statt auf einen kaputten Pfad zurück. Neuer clientLog-Service mit IndexedDB-Puffer, Dedupe und Batch-Versand, globaler Fehlerfang im Boot, Protokoll im Entwickler-Tab. Drei pulsierende Punkte statt weißer Fläche in Timeline-Punkt, Key Image und Medienkacheln. Dokumentation in documentation/logging.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
58 lines
2.1 KiB
JavaScript
58 lines
2.1 KiB
JavaScript
import Dexie from 'dexie'
|
|
|
|
export const db = new Dexie('thatsMeDB')
|
|
|
|
db.version(1).stores({
|
|
// Events: indexed by id (PK), date for sorted queries, syncStatus for dirty tracking
|
|
events: 'id, date, updatedAt, syncStatus',
|
|
|
|
// Sync queue: outbound mutations waiting to be pushed to server
|
|
syncQueue: '++queueId, eventId, action, createdAt',
|
|
|
|
// Image cache: offline blob storage for thumbnails
|
|
imageCache: 'url, eventId, type, cachedAt',
|
|
|
|
// Metadata: key-value pairs (lastSyncCursor, userId, etc.)
|
|
meta: 'key'
|
|
})
|
|
|
|
db.version(2).stores({
|
|
// Events are locally namespaced by userId until the backend owns persistence.
|
|
events: 'id, userId, [userId+date], updatedAt, syncStatus',
|
|
syncQueue: '++queueId, userId, eventId, action, createdAt',
|
|
imageCache: 'url, eventId, type, cachedAt',
|
|
meta: 'key'
|
|
})
|
|
|
|
db.version(3).stores({
|
|
events: 'id, userId, [userId+date], updatedAt, syncStatus',
|
|
syncQueue: '++queueId, userId, eventId, action, createdAt',
|
|
imageCache: 'url, eventId, type, cachedAt',
|
|
eventMedia: 'id, eventId, userId, createdAt',
|
|
meta: 'key'
|
|
})
|
|
|
|
db.version(4).stores({
|
|
events: 'id, userId, [userId+date], updatedAt, syncStatus',
|
|
syncQueue: '++queueId, userId, eventId, action, createdAt',
|
|
// The image cache was keyed by URL alone, so a cached image stayed
|
|
// readable after a user switch and survived logout. The key now carries
|
|
// the API origin, the owning user and the variant. The primary key
|
|
// changes, so the old table is dropped — it is a pure cache and simply
|
|
// refills on the next load.
|
|
imageCache: null,
|
|
scopedImageCache: 'key, userId, eventId, type, cachedAt',
|
|
eventMedia: 'id, eventId, userId, createdAt',
|
|
meta: 'key'
|
|
})
|
|
|
|
db.version(5).stores({
|
|
events: 'id, userId, [userId+date], updatedAt, syncStatus',
|
|
syncQueue: '++queueId, userId, eventId, action, createdAt',
|
|
scopedImageCache: 'key, userId, eventId, type, cachedAt',
|
|
eventMedia: 'id, eventId, userId, createdAt',
|
|
// Warnings and errors from the browser, kept until they were shipped to
|
|
// the server and for the developer view (see services/clientLog.js).
|
|
clientLogs: 'id, occurredAt, shipped',
|
|
meta: 'key'
|
|
})
|