API
List objects and measure usage on the server and in the browser.
Server
With the plugin registered, s3.db.objects is available in routes, quota checks, and jobs:
const scope = "user:123";
const ref = { bucket: "my-bucket", key: "uploads/photo.jpg" };
const objects = await s3.db.objects.listByScope({
scope,
status: "active",
limit: 50,
offset: 0,
});
const count = await s3.db.objects.countByScope(scope, "active");
const usage = await s3.db.objects.getScopeUsage(scope);
const object = await s3.db.objects.find({ bucket: ref.bucket, key: ref.key });
const active = await s3.db.objects.findActive({
bucket: ref.bucket,
key: ref.key,
});
const pending = await s3.db.objects.findPendingMultipart({
bucket: ref.bucket,
key: ref.key,
fileSize: 1024000,
});| Method | Purpose |
|---|---|
listByScope | List for a scope — skips deleted by default |
countByScope | Count with the same filters as listByScope |
getScopeUsage | Total bytes and count — quota checks |
find | One row by bucket + key (any status) |
findActive | One row — only active |
findPendingMultipart | Multipart resume lookup |
To remove a file, use api.delete — not store helpers (Setup).
Browser (client-side)
Register dbClient() once. In a client component, useApi() gives you api.db.listObjects:
import { createS3Client } from "@dimah-s3/react";
import { dbClient } from "@dimah-s3/db/client";
export const { api, S3Provider, useApi } = createS3Client({
basePath: "/api/s3",
plugins: [dbClient()],
});"use client";
import { useEffect, useState } from "react";
import { useApi } from "@/lib/s3-client";
export function FileList() {
const api = useApi();
const [files, setFiles] = useState([]);
useEffect(() => {
api.db
.listObjects({ status: "active", limit: 50, offset: 0 })
.then((result) => setFiles(result.objects));
}, [api]);
return (
<ul>
{files.map((file) => (
<li key={file.id}>{file.filename ?? file.key}</li>
))}
</ul>
);
}listObjects is browser-only. (scope is a server argument and in
listObjects automatically resolved by the plugin in the server).
Quota and extra guards
Quotas are app-owned. The db plugin enforces ownership first; your guards run after (Composition).
import { dimahS3, chainHooks } from "@dimah-s3/server";
import { db } from "@dimah-s3/db";
export const s3 = dimahS3({
plugins: [db({ client: dimahS3Db, resolveScope })],
upload: {
enabled: true,
presignGuard: quotaGuard,
// or stack several: chainHooks(quotaGuard, rateLimitGuard),
},
});async function quotaGuard({ request, fileSize }) {
const scope = await resolveScope(request);
const { totalBytes } = await s3.db.objects.getScopeUsage(scope);
if (totalBytes + (fileSize ?? 0) > MAX_BYTES)
throw forbidden("Quota exceeded");
}getScopeUsage scans every row for the scope on each call — fine for
demos and light use. For frequent presign checks or large scopes, keep a
separate quota table (or counter) updated in your hooks instead of recounting
each time.
Use chainHooks when you need more than one user guard on the same hook. Ownership stays on the plugin — you only add policy on top.
Custom access
For rules beyond same-scope ownership, add a guard on the feature config (runs after the plugin) or use createObjectAccessGuard on your own routes:
download: {
enabled: true,
presignGuard: async ({ request, bucket, key }) => {
// ownership already checked by the db plugin
},
},createObjectAccessGuard({ db: dimahS3Db, resolveScope, authorize: myRule });authorize replaces the default object.scope === scope check — use it where you own the full access policy (custom routes, or a guard you wire yourself).