Skip to content

Drive

Available since v0.48.0

Drive is the app’s file storage. Like the KV store, it belongs to the app and everyone the app reaches sees the same files. Use KV for structured data and Drive for bytes.

interface MaypopDriveFile {
id: string;
cid: string;
name: string;
mimeType: string | null;
size: number | null;
uploadedBy: string;
createdAt: string;
}
interface MaypopDrive {
readonly maxUploadBytes: number;
list(opts?: { prefix?: string }): Promise<MaypopDriveFile[]>;
stat(name: string): Promise<MaypopDriveFile | null>;
read(name: string): Promise<string>;
readBlob(name: string): Promise<Blob>;
write(
name: string,
content: string | Blob | ArrayBuffer | ArrayBufferView,
): Promise<MaypopDriveFile>;
upload(file: Blob, opts?: { name?: string }): Promise<MaypopDriveFile>;
url(cid: string): Promise<string>;
imageUrl(cid: string): Promise<string>;
delete(cid: string): Promise<void>;
remove(name: string): Promise<void>;
}
await maypop.ready();
input.onchange = async () => {
const file = await maypop.drive.upload(input.files![0]);
// Keep the file's cid in KV, not a URL: URLs expire.
await maypop.kv.set(`photo/${file.id}`, { cid: file.cid, name: file.name });
};
maypop.kv.subscribe("photo/", async (entries) => {
for (const { value } of entries) {
const { cid } = value as { cid: string };
addImage(await maypop.drive.imageUrl(cid));
}
});
  • Names don’t overwrite. write to a name that exists fails with maypop/conflict. Write a new name, or remove the old file first.
  • cid identifies the content. Store it in KV to refer to a file.
  • url(cid) is short-lived. Fetch a fresh one when you need it; never save it.
  • imageUrl(cid) is cached for fast display in this page only.
  • delete(cid) removes one file; remove(name) removes every file with that name.
  • Names are /-separated paths such as avatars/alice.png: up to 512 bytes, no empty, . or .. segments, no backslashes.
  • Uploads are limited to maxUploadBytes (50 MiB); a bigger file fails with maypop/file-too-large before anything is sent.
  • Readers can list and download; writing needs edit or use access.

By default, everyone with access can list and download every file, writers can add files, and only the person who uploaded a file can delete it. Add .maypop/drive-policy.json to your app’s source to change that; it takes effect with the next published version.

It uses the same presets, roles and actions as the KV policy, with a name pattern instead of a key, and two extra conditions on uploads:

  • contentTypes: exact types such as image/png (no wildcards).
  • maxBytes: a size limit, at most 50 MiB.

Each person manages their own avatar; everyone can see all avatars:

{
"schemaVersion": 1,
"mode": "advanced",
"rules": [
{
"actions": ["read", "create", "delete"],
"roles": ["user", "admin", "owner"],
"name": "avatars/{memberId}/**",
"owner": null,
"contentTypes": ["image/png", "image/jpeg", "image/webp"],
"maxBytes": 5242880
},
{
"actions": ["read"],
"roles": ["guest", "user", "admin", "owner"],
"name": "avatars/**",
"owner": null
}
]
}

Files a person can’t read are left out of their list. A policy can narrow what someone’s role allows, never widen it: a reader can’t be given write access. In Drive policies owner can only be { "source": "entryAuthor" }, the uploader.