The server The route
The route
createDriveHandler takes a Request and gives a Response. Every route of the drive lives under one path, and none carries the bytes but on a disk: the browser is given addresses to send them to and read them from.On this page
In Next.js
app/api/files/[...path]/route.ts
import { byRoles, createDriveHandler, s3OptionsFrom, s3Storage, sqliteMetadata } from '@rebofiles/server';
import { session } from '@/lib/auth'; // your own
const handle = createDriveHandler({
storage: s3Storage(s3OptionsFrom(process.env)),
metadata: await sqliteMetadata('drive.sqlite'),
identify: async (request) => {
const user = await session(request);
return user ? { id: user.id, admin: user.isAdmin } : null;
},
authorize: byRoles,
secret: process.env.REBOFILES_SECRET!,
base: '/api/files',
});
export { handle as GET, handle as POST, handle as PATCH, handle as PUT, handle as DELETE };On node:http
nodeHandler makes it a listener, for node:http, Express and what is built on it.
server.ts
import { createServer } from 'node:http';
import { createDriveHandler, diskStorage, everyone, memoryMetadata, nodeHandler } from '@rebofiles/server';
const handle = createDriveHandler({
storage: diskStorage({ folder: './storage', secret: process.env.REBOFILES_SECRET! }),
metadata: memoryMetadata(),
...everyone(), // only on your own machine: no sign-in, everyone may do everything
secret: process.env.REBOFILES_SECRET!,
base: '/api/files',
});
createServer(nodeHandler(handle)).listen(3000);Every option
| Option | Type | What it does |
|---|---|---|
| storage | Storage | Where the bytes are: s3Storage(s3OptionsFrom(process.env)), gcsStorage(…), azureStorage(…) or diskStorage({ folder, secret }). |
| metadata | MetadataStore | Where the entries are: memoryMetadata(), sqliteMetadata(path) or postgresMetadata(pool). |
| identify | (request) => User | null | Who is asking, from your session; null answers 401. { id, admin: true } may configure the drive with byRoles. |
| authorize | (user, action, entry, drive) => boolean | Asked on every route; false answers 403. byRoles, or rules of yours around it. |
| secret | string | What tokens are signed with: long, random, the same in every process. |
| base | string | The path the routes are under: /api/files. |
| top | 'own' | 'everyone' | What My files shows at the top: each person’s own (the default), or everything one may read, for one drive of a whole team. |
| maxSize | number | The largest file taken, in bytes; 5 GiB unless said. |
| settings | Partial<DriveSettings> | The defaults until someone changes them: language, theme, brand colour, pictures, quota, trashDays, and company, the name the page of a request for files shows. |
| cors | { origins: string[] } | Pages of other origins that may use the routes: the desktop window, a page of another domain. |
| onShare | (event) => void | Each share kept, with who, by whom and whether it is the first: to tell the person. |
| beforeUpload | (upload, drive) => boolean | void | Asked before an upload is given addresses, with its name, type, size and folder: throw a Refused, or return false, to refuse it. |
| inspect | (upload, drive) => boolean | void | Asked once the bytes arrived, before the file is listed, with a read of them: checkType(), clamav(), or yours. A refusal takes the bytes away. |
| live | false | { poll } | Changes told at /events as they happen; those of another process on the same store within poll ms (2000). false for none. |
| derive | Derive | What the server makes of a file once kept, for what the page did not: pictures, a placeholder, its text (of a PDF, a Word document, a workbook; of a picture with ocr()). deriveAll() of @rebofiles/server/derive. |
| onChange | ({ activity, entry }) => void | Each thing done to the drive, as its activity says it: for webhooks, an audit, another index, counting space. |
| onComment | ({ comment, entry, mentions, by }) => void | Each comment made, with whom it names with an @: to tell them. |
| onReceived | ({ entry, from, item, request, got }) => void | Each file that came through a request for files, with who sent it, for which document, and what came so far: to tell the owner. |
| onReview | ({ entry, status, reason, from, by }) => void | Each file accepted or returned: to tell whoever sent it. |
| reminders | { send(reminder) } | Whom a request was sent to, reminded every remind days of what is missing, and at once: mailReminders({ mail: resend(…), url }), or yours. |
| check | false | ({ entry, item, text }) => … | What is written in what came, against the words and find of its document: checkWritten unless yours; false for none. |
| expiring | false | { field, days, onExpiring } | The owner of a file whose field expires comes within days (15) told in the bell, and onExpiring; false for none. |
| onRead | ({ action, user, link, entries, path }) => void | Each thing opened, seen or taken: a folder listed, a file seen or downloaded, an archive; by someone or through a link. For an audit. |
| people | { search(query, user), names?(ids, user) } | Who is who, from your directory: the people and groups that match what is typed where something is shared, and their names, shown in place of ids. |
| logger | Logger | Where what went wrong and was not answered for is written: pino(), console unless given. |
| onRequest | (done) => void | After every request, its route, status, time and who: for metrics, as openTelemetry(meter). |
| limits | Limits | false | How many uploads, archives, text searches, wrong passwords of a link and requests each may make; a 429 past them. |
| limiter | Limiter | Where the counts of limits are kept: in the process unless given; Redis for more than one. |
| clientIp | (request) => string | The address of who asks, to count wrong passwords of a link by it too. |
| now | () => Date | The clock, for tests. |
| newId | () => string | The ids of new entries; random UUIDs unless given. |
Held in memory
The drive is read once and held, and read again only when the store says it changed, by this process or another: a listing of 20,000 entries went from 36 ms to under 1. Fine for a team and well beyond; not for millions of entries.
Every route
HTTP routes lists them, as the code keeps them.