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
+9 -1
View File
@@ -16,7 +16,7 @@ import { expect } from 'vitest';
import WebSocket from 'ws';
import * as Y from 'yjs';
import { emptyProject, writeProject } from '@mdg-filius/doc';
import { startServer, type DocStore, type FiliusServer, type Log, type ServerOptions, type SessionStore } from '../src/index.js';
import { PostgresBlobs, startServer, type DocStore, type PostgresStore, type FiliusServer, type Log, type ServerOptions, type SessionStore } from '../src/index.js';
import { signTicket } from './tickets.js';
export const TICKET_SECRET = 'test-ticket-secret';
@@ -38,6 +38,13 @@ export function memoryLog(): MemoryLog {
export type TestServer = FiliusServer & { readonly log: MemoryLog; readonly base: string };
/** F's blob store in the test database (the table made, once a file). */
async function testBlobs(store: DocStore): Promise<PostgresBlobs> {
const blobs = new PostgresBlobs((store as PostgresStore).pool);
await blobs.migrate();
return blobs;
}
let roomThread: Promise<URL> | undefined;
/** src/simWorker.ts bundled into a directory of its own, once a test file. */
@@ -63,6 +70,7 @@ export async function startTestServer(store: DocStore, sessions: SessionStore, o
origins: [EDITOR_ORIGIN],
secureCookie: false,
workerUrl: await bundledRoomThread(),
blobs: await testBlobs(store),
simThreads: 1,
...options,
});