Skip to content
52 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Docs: a workspace knowledge base people and agents write together1/**
2 * Where files put in pages are kept: behind this small interface, so a
3 * self-hosted g1t can keep them on disk or in any S3-compatible store.
4 * The managed service uses R2 (`r2FileStore`).
5 */
6
7export type StoredFile = { body: ReadableStream; content_type: string; bytes: number; etag: string };
8
9export interface FileStore {
10 put(key: string, body: ReadableStream | ArrayBuffer | Uint8Array, contentType: string): Promise<void>;
11 get(key: string): Promise<StoredFile | null>;
12 delete(key: string): Promise<void>;
13}
14
15export function r2FileStore(bucket: R2Bucket): FileStore {
16 return {
17 async put(key, body, contentType) {
18 await bucket.put(key, body, { httpMetadata: { contentType } });
19 },
20 async get(key) {
21 const object = await bucket.get(key);
22 if (!object) return null;
23 return {
24 body: object.body,
25 content_type: object.httpMetadata?.contentType ?? "application/octet-stream",
26 bytes: object.size,
27 etag: object.httpEtag,
28 };
29 },
30 async delete(key) {
31 await bucket.delete(key);
32 },
33 };
34}
35
36/** Types a page's file is served as; anything else is served as a download. */
37const INLINE = new Set(["image/png", "image/jpeg", "image/gif", "image/webp", "image/avif", "video/mp4", "video/webm", "audio/mpeg", "audio/ogg", "audio/wav", "application/pdf"]);
38
39/** The type a file is kept and served as: images and media as themselves, SVG and everything else as bytes to download (nothing served can run script). */
40export function servedType(contentType: string): string {
41 const type = String(contentType ?? "").split(";")[0]!.trim().toLowerCase();
42 return INLINE.has(type) ? type : "application/octet-stream";
43}
44
45/** A file name safe to keep and to put in a header. */
46export function safeName(name: string): string {
47 const clean = String(name ?? "")
48 .replace(/[\\/:*?"<>|\u0000-\u001f]+/g, "_")
49 .trim()
50 .slice(0, 180);
51 return clean || "file";
52}