Osy#betaa language · its runtime Osyrin · a hosted platform
Why Osy#Built for agentsAgents as declarationsWorkflows that waitRuns exactly onceSecure by defaultNothing to mockThe editor is the compilerUI in the languageDocuments are dataOne program

Reference / Storage

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

File.Supersede(doc.Pdf, bytes) // under `using Osysharp.Storage;`

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.

preview1 example compiled by CIstoragefileshistoryaudit

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
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#

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

Description#

Why a file is 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#

Version1 for the first content, counting up
CreatedBy / CreatedAtwho replaced it, and when — the platform's own audit stamps
Name / MimeTypewhat the file was called and what type it was at the time, so a rename is history too
Size / ContentHashderived from the bytes by the platform, never declarable by an app
the contentso 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#

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

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#

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#

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#

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#

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#

Related

File.SignedUrl

Mints a temporary, signed URL that lets an authorized browser fetch a PRIVATE app file — one stored outside `public/` —…

File.Url

Turns an app-relative file path into the public URL a browser can fetch it from — `File.Url("public/x.png")` returns…

The PDF kit — a document viewer and a page thumbnail

Two controls over a stored PDF: a reader with fit, zoom and a real text layer, and a thumbnail that draws one page as a…

use

Declares a capability your app depends on, written inside the `app { }` manifest block. It provisions the capability…