# Dateispeicher SDK

# Dateispeicher SDK

**`platform.files`** ist das virtuelle Dateisystem der Plattform: der zentrale Dateispeicher, den Nutzer:innen unter `/files` sehen. Dein Tool schreibt seine Ergebnisse dorthin, damit sie auffindbar, teilbar und durchsuchbar sind. Du fasst nie rohes R2 an und rechnest nie selbst mit Quota.

## Ausgaben sichtbar ablegen

Tool-Ergebnisse gehören unter `Tools/<Anzeigename deines Tools>/`. Genau ein Aufruf erledigt Byte-Write, Quota-Prüfung und den sichtbaren Node:

```typescript
import { saveToolFile } from "@/lib/files/tool-files";

const ref = await saveToolFile(ctx, {
  moduleId: "homestaging",     // der Anzeigename kommt aus der Modul-Registry
  name: "Wohnzimmer · Skandinavisch.jpg",
  bytes,                       // Uint8Array
  contentType: "image/jpeg",
  subfolder: "Entwürfe",       // optional, max. eine Ebene
  propertyId,                  // optional: zusätzlich mit einem Objekt verknüpfen
  dedupeKey: `gen-${runId}`,   // optional, aber empfohlen (siehe unten)
});
// ref: { nodeId, key, name, contentType, sizeBytes, url }
```

- **Quota:** `saveToolFile` bucht die Nutzung und erzwingt das Plan-Limit. Ist der Speicher voll, wirft es `StorageQuotaExceededError`. Fang das in Massen-Writern ab, stoppe sauber und benachrichtige die Inhaber:in, statt blind weiterzuladen.
- **Idempotenz:** Gleicher `dedupeKey` liefert bei einem Retry denselben Node statt einer Dublette. Nutze eine stabile ID aus deinem Lauf.
- **Verboten:** roher `r2Put` oder `storageService().put` in Modul-Code. Der Reuse-Ratchet (`bun validate:reuse`) bricht CI, wenn ein Modul daran vorbei schreibt.

Wenn deine Bytes aus Serving-Gründen ihren eigenen Key behalten müssen (z. B. eine öffentlich ausgelieferte Datei), registriere das bestehende Objekt als Node, ohne es zu kopieren:

```typescript
import { registerToolFile } from "@/lib/files/tool-files";
await registerToolFile(ctx, { moduleId: "website", key, name, bytes, contentType, propertyId });
```

## Dateien vom Nutzer wählen lassen

Braucht dein Tool eine Datei aus dem Speicher als Eingabe, öffne den zentralen Datei-Picker statt eines eigenen Uploads:

```typescript
import { useFilePicker } from "@/lib/files/picker";

const pick = useFilePicker();
const files = await pick({ accept: ["image/*"], multiple: false });
// files[0]: { id, name, contentType, sizeBytes, ... }
```

Der Picker zeigt nur, was die fragende Person sehen darf (Ordner-ACLs und private Bereiche werden respektiert). Du bekommst Metadaten und eine Zugriffs-URL, nie rohe Keys.

## Nach außen teilen

Einen zeitlich begrenzten, optional passwortgeschützten Link erzeugst du über `platform.share`. Der rohe Token erscheint genau einmal (gespeichert wird nur sein Hash):

```typescript
const link = await platform.share.create(ctx, {
  resourceType: "file_node",
  resourceId: node.id,
  expiresInDays: 30,           // 1..90
  passwordHash,                // optional (scrypt), nie Klartext
  allowDownload: true,
});
// link.token → nur hier verfügbar; Pfad: /share/<token>
```

Widerrufen ist sofort wirksam (`platform.share.revoke(ctx, link.id)`). Die öffentliche Seite ist nicht von Suchmaschinen indexierbar und zeigt bei ungültigen Links immer dieselbe neutrale Meldung.

## Regeln

- **Nie roher Storage.** Schreiben über `saveToolFile`/`registerToolFile`, Quota nie selbst rechnen.
- **`dedupeKey` setzen.** Retries dürfen keine Dubletten erzeugen.
- **Quota-Fehler behandeln.** `StorageQuotaExceededError` sauber abfangen, Owner informieren.
- **ACLs vertrauen.** Picker und Suche filtern serverseitig; nie an ihnen vorbei auf Nodes zugreifen.
- **Keine PII in Logs.** Datei-Inhalte und Namen gehören nicht nach Sentry/PostHog.
