# A file's history — supersede, and what each version keeps

> Replacing a stored file's content while keeping what it said before. The FILE is the identity — a record pointing at it never re-points, and its grants keep covering the whole history — while each content it has had is a version you can list, show and download. Superseding copies nothing.

<!-- id: storage-file-versions · area: storage · stability: preview · html: https://osysharp.com/reference/storage/file-versions/ -->

## Summary        {#summary}

A `FileAsset` is a file's **identity**; a `FileVersion` is one **content** it has had. The asset points at the version
that is current, so:

- a business record's FK never re-points — `doc.Pdf` is the same row before and after a revision
- the file's grants cover its whole history, because a version has no authorization of its own
- superseding **copies nothing** — a new content is a new row and a moved pointer

```osy syntax
var pdf = File.Create("decision.pdf", "application/pdf", bytes);   // the file, and its version 1
File.Supersede(pdf, corrected);                                    // version 2; version 1 is untouched
```

## Signature      {#signature}

```osy syntax
FileAsset File.Create(string name, string mimeType, byte[] bytes)
void      File.Supersede(FileAsset asset, byte[] bytes)
```

## Description    {#description}

### Why a file is two things    {#two-things}

Because the two change on different clocks. A document's *identity* — what a case links to, who may see it, which
folder it is filed in — outlives any particular content, and a revision must not disturb it. Its *content* is what
gets replaced, and what has to be kept.

Writing content on the file itself would mean copying the old bytes somewhere on every revision. Writing a chain of
files instead would mean every record pointing at one had to follow the chain to find out what "current" is. Neither
is what an app wants to write.

### What each version keeps    {#what-a-version-keeps}

| | |
|---|---|
| `Version` | 1 for the first content, counting up |
| `CreatedBy` / `CreatedAt` | **who** replaced it, and **when** — the platform's own audit stamps |
| `Name` / `MimeType` | what the file was called and what type it was **at the time**, so a rename is history too |
| `Size` / `ContentHash` | derived from the bytes by the platform, never declarable by an app |
| the content | so any version can be shown or downloaded, not just the current one |

⚠ **A version's `CreatedAt` is when that content STOPPED being current**, not when it started — the row is written at
the moment it is replaced. A history that wants "in force from X to Y" takes Y from the version and X from the
previous one (or from the file's own `CreatedAt`, for version 1).

### Showing every version a file has had    {#listing}

`asset.Versions` is every content the file has had, current one included — so a history list is one loop with no
special last entry.

```osy syntax
foreach (var v in doc.Pdf.Versions.OrderBy(v => v.Version)) {
  Row(gap: Space.Gutter) {
    Text("v" + v.Version);
    Text(v.Name);
    Text(Text.ByteSize(v.Size));
    if (v.Id == doc.Pdf.CurrentVersion.Id) { Badge("current", tone: Tone.Success); }
  }
}
```

### Downloading one version    {#downloading}

A signed URL names **one content**. Ask for a version and the link fetches that version and nothing else — not the
next one, not the current one. Whoever may read the file may read all of it, but a link handed to a browser is a
bearer capability, and the one beside "version 2" has no business also fetching version 7.

### Erasing a file    {#erasing}

Deleting a `FileAsset` deletes its versions **and their bytes**. That is not a convenience: a history that outlived
its file would be a retention problem wearing an archive costume, and rows removed while their content stayed in the
store would make "erase this person's files" true only on paper.

⚠ **The consequence to accept: the only way to remove one version is to remove the file.** For an archive that is
correct — selective deletion of history is what an archive exists to prevent.

## Traps that cost real time   {#traps}

**A file's size lives on its content.** `asset.Size` does not exist; `asset.CurrentVersion.Size` is what it says now,
and `v.Size` is what a given version weighs. Every one of them counts against the app's storage quota — a document
superseded fifty times is fifty stored contents.

**Superseding does not rename.** `Supersede` replaces bytes; `asset.Name` is an ordinary field you set yourself. The
version records the name that was in force, so a rename after the fact is not retroactive.

**A file can exist with no content.** `new FileAsset { Name = … }` is legal and gives you an identity with no
`CurrentVersion` — useful for a placeholder, and the reason a viewer should handle "nothing to show" rather than
assume bytes.

## Examples       {#examples}

```osy title="a case document, revised — and the link to it never moves" test app=storage-file-versions
app CaseFiles { use Osysharp.Storage; }

using Osysharp.Storage;

entity CaseDocument {
  [MaxLength(200)] string Title;
  FileAsset Pdf;
}

// A new document: `File.Create` writes the file AND its first content, along with the size, the hash and the
// inline-vs-external routing — none of which an app declares.
CaseDocument NewDocument(string title, UploadedFile f) {
  var doc = new CaseDocument { Title = title, Pdf = File.Create(f.FileName, f.ContentType, File.ReadAllBytes(f.Path)) };
  UnitOfWork.Commit();
  return doc;
}

// A revision. `doc.Pdf` is the SAME row afterwards, so nothing that points at this document has to be told —
// and what the document used to say is still there, under its own version number.
void ReplaceContent(CaseDocument doc, UploadedFile f) {
  File.Supersede(doc.Pdf, File.ReadAllBytes(f.Path));
  UnitOfWork.Commit();
}
```

## See also       {#see-also}

- [File.SignedUrl](https://osysharp.com/reference/storage/file-signed-url/) — how a private file reaches a browser at all
- [The PDF kit — a document viewer and a page thumbnail](https://osysharp.com/reference/ui/pdf-kit/) — showing a document, and one page of it as a picture
- [use](https://osysharp.com/reference/types/use/) — `use Osysharp.Storage;`, which puts all of this in scope
