feat: F5.5 large binary files as blobs on server F

From 16 KB a picture's or binary file's entry names its bytes by their
SHA-256 instead of holding them; the bytes are on F, content-addressed
(GET/POST /blobs with the session's grant, from the editor's origin, at
most 16 MiB). A document's blob side says where they are: the editor's
fetches and uploads, the room thread's asks F's main thread, and a start
waits for the ones asked for; without a side everything stays in the
document. Text stays Y.Text. A blob whose bytes are not there is never
written back empty. A blob F has not got yet is asked for again a few
times, and a running room reads late bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 16:46:50 +00:00
co-authored by Claude Opus 5.5
parent 47beda6b85
commit 4b33e36580
29 changed files with 1185 additions and 29 deletions
+14 -2
View File
@@ -4,10 +4,16 @@
// rooms, each with a replica of its project's document: the document's
// updates go both ways, a person's messages go in, the room's come out,
// each for one person (a room tells its people apart; the main thread
// numbers them).
// numbers them). A room's large binary files (blobs) are F's: the thread
// asks for the ones it has not got, and gives F the ones the simulation
// makes before the document's change that names them.
import type { HostMessage } from '@mdg-filius/protocol';
/** How often, and how long apart, a thread asks again for a blob F has not got (yet). */
export const BLOB_ASKS = 5;
export const BLOB_ASK_AGAIN_MILLISECONDS = 1_000;
export type ToRoomThread =
/** A room for a project, its document as it is now. */
| { readonly type: 'open'; readonly room: string; readonly state: Uint8Array }
@@ -19,10 +25,16 @@ export type ToRoomThread =
/** The simulation stopped (what it left written back first); answered by `stopped`. */
| { readonly type: 'stop'; readonly room: string }
/** The room ended — stopped first with `writeBack`, else as it is (its project is gone); answered by `closed`. */
| { readonly type: 'close'; readonly room: string; readonly writeBack: boolean };
| { readonly type: 'close'; readonly room: string; readonly writeBack: boolean }
/** The blobs asked for: those F has, and the names of those it has not. */
| { readonly type: 'blobs'; readonly room: string; readonly blobs: readonly { readonly sha: string; readonly bytes: Uint8Array }[]; readonly missing: readonly string[] };
export type FromRoomThread =
| { readonly type: 'doc'; readonly room: string; readonly update: Uint8Array }
| { readonly type: 'host'; readonly room: string; readonly client: number; readonly message: HostMessage }
| { readonly type: 'stopped'; readonly room: string }
/** Blobs the document names that the thread has not got. */
| { readonly type: 'needBlobs'; readonly room: string; readonly shas: readonly string[] }
/** A blob the simulation made (before the change that names it). */
| { readonly type: 'blob'; readonly room: string; readonly bytes: Uint8Array }
| { readonly type: 'closed'; readonly room: string };