Ga naar inhoud

Bestanden, presence en broadcast

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");

De bytes van bestanden staan in de FILES R2-bucket. Bestanden van elke grootte kunnen worden geüpload: boven 16 MiB verstuurt upload het bestand als R2-multipart-upload in delen van 16 MiB, en breekt de upload af als een deel mislukt. Het bestand verschijnt in de lijst zodra het laatste deel binnen is. De index, met wie elk bestand heeft geüpload, wordt via het transactielog geschreven, dus een restore behoudt hem.

Wie elk bestand mag lezen, schrijven en verwijderen, leg je vast in het schema. Zonder regels: lezen member, schrijven en verwijderen editor. Elke regel is een role(), een fileCheck() over de aanroeper en het bestand ({ path, size, contentType, uploadedBy }, en bij een schrijfactie het bestand dat wordt vervangen als existing), of attachedTo(model, path => id), waarmee een bestand de regels van zijn rij volgt: leesbaar voor wie de rij mag lezen, schrijfbaar en verwijderbaar voor wie de rij mag bijwerken.

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),
},
});

De workspace beslist over elke handeling voordat er een byte wordt verplaatst, en list() geeft alleen de bestanden terug die de aanroeper mag lezen. Een bestand dat de aanroeper niet mag lezen, geeft 404, net als een bestand dat niet bestaat. Een regel die een fout gooit, weigert, en de rol van een editor gaat niet boven een regel.

Presence is efemeer en rijdt mee op de workspace-socket:

const room = db.$presence("board:" + boardId);
room.set({ name, color });
room.cursor({ x, y });
const stop = room.subscribe((peers) => render(peers));
// Bij het verlaten van de room:
room.leave();

Een presence-room heeft ook drag() en clearDrag() voor drag-state.

Broadcast is een fire-and-forget-bericht tussen de clients van een workspace, op dezelfde 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 (vervallen) terwijl je offline bent

Een geweigerde topic-subscription meldt zich via de optionele onError-callback van subscribe. Een bericht bereikt elke andere client die op het topic luistert, met het client-id en user-id van de afzender; je eigen berichten worden niet teruggekaatst. Niets wordt opgeslagen of opnieuw afgespeeld, en een send terwijl de socket down is, vervalt in plaats van later te worden bezorgd. Luisterende clients voegen zich na een reconnect weer bij hun topics. Berichten van elke grootte komen aan: een groot bericht reist in delen die de workspace doorgeeft zodra ze binnenkomen, en de ontvangende client voegt ze samen.

Standaard mag elk lid van de workspace luisteren en sturen. Bepaal dit per topic op de 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 en useBroadcast in React.