Dateien, Presence und Broadcast
Dateien
Abschnitt betitelt „Dateien“const file = await db.$files.upload(blob, { path: "avatars/me.png" });const bytes = await db.$files.download("avatars/me.png");const all = await db.$files.list({ prefix: "avatars/" });await db.$files.delete("avatars/me.png");Dateibytes liegen im FILES-R2-Bucket. Dateien jeder Größe lassen sich hochladen: Über 16 MiB sendet upload die Datei als R2-Multipart-Upload in Teilen von 16 MiB und bricht den Upload ab, wenn ein Teil fehlschlägt. Die Datei erscheint in der Liste, sobald der letzte Teil angekommen ist. Der Index, samt wer jede Datei hochgeladen hat, wird über das Transaktionslog geschrieben, sodass eine Wiederherstellung ihn behält.
Dateizugriff
Abschnitt betitelt „Dateizugriff“Wer jede Datei lesen, schreiben und löschen darf, legst du im Schema fest. Ohne Regeln: Lesen member, Schreiben und Löschen editor. Jede Regel ist ein role(), ein fileCheck() über den Aufrufer und die Datei ({ path, size, contentType, uploadedBy }, beim Schreiben zusätzlich die ersetzte Datei als existing) oder attachedTo(model, path => id): Damit folgt eine Datei ihrer Zeile – lesbar für alle, die die Zeile lesen dürfen, schreib- und löschbar für alle, die sie aktualisieren dürfen.
export default schema({ task, project }, { files: { read: attachedTo("task", (path) => path.split("/")[1]), // tasks/<id>/... write: attachedTo("task", (path) => path.split("/")[1]), delete: fileCheck(({ auth, file }) => file.uploadedBy === auth.sub), },});Der Workspace entscheidet jede Operation, bevor ein Byte bewegt wird, und list() liefert nur die Dateien, die der Aufrufer lesen darf. Eine Datei, die der Aufrufer nicht lesen darf, antwortet mit 404, wie eine, die nicht existiert. Eine Regel, die einen Fehler wirft, verweigert, und die Rolle eines Editors setzt keine Regel außer Kraft.
Presence
Abschnitt betitelt „Presence“Presence ist ephemer und fährt auf dem Workspace-Socket mit:
const room = db.$presence("board:" + boardId);room.set({ name, color });room.cursor({ x, y });const stop = room.subscribe((peers) => render(peers));
// Beim Verlassen des Raums:room.leave();Ein Presence-Room bietet außerdem drag() und clearDrag() für Drag-State.
Broadcast
Abschnitt betitelt „Broadcast“Broadcast ist eine Fire-and-forget-Nachricht zwischen den Clients eines Workspace, auf demselben Socket:
const board = db.$broadcast<{ kind: "ping"; at: number }>("board:" + boardId);const stop = board.subscribe((message, from) => flash(from.userId, message));board.send({ kind: "ping", at: Date.now() }); // false (verworfen), während du offline bistEin abgelehntes Topic-Abonnement meldet sich über den optionalen onError-Callback von subscribe. Eine Nachricht erreicht jeden anderen Client, der auf dem Topic lauscht, mit Client-ID und User-ID des Senders; deine eigenen Nachrichten werden nicht zurückgespiegelt. Nichts wird gespeichert oder erneut abgespielt, und ein Send bei getrennter Verbindung wird verworfen statt später zugestellt. Lauschende Clients treten ihren Topics nach einem Reconnect wieder bei. Nachrichten jeder Größe kommen an: Eine große reist in Teilen, die der Workspace weiterreicht, sobald sie eintreffen, und der empfangende Client setzt sie zusammen.
Standardmäßig darf jedes Mitglied des Workspace lauschen und senden. Entscheide pro Topic im Worker:
import { defineTapeDB, WORKSPACE_ROLE_LEVELS } from "tapedb/worker";import schema from "./tapedb.schema";
defineTapeDB({ schema, topics: { authorize: ({ auth, topic, action }) => !topic.startsWith("admin:") || auth.role >= WORKSPACE_ROLE_LEVELS.ADMIN, },});React-Bindings: usePresence und useBroadcast in React.