dimah-s3v1.5.11
Database

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: "avatar/photo.jpg" };

const objects = await s3.db.objects.listByScope({
  scope,
  status: "active",
  route: "avatar",
  limit: 50,
  cursor: previous.nextCursor ?? undefined,
});

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,
});

const written = await s3.db.objects.upsertPending({
  scope,
  bucket: ref.bucket,
  key: ref.key,
  route: "avatar",
  declaredSize: 1024000,
});

const active = await s3.db.objects.markActive({
  bucket: ref.bucket,
  key: ref.key,
  size: 1024000,
});

StorageObjectStore

import type { StorageObjectStore } from "@dimah-s3/db";

Prop

Type

To remove a file, use api.delete — not store helpers. See delete behavior.

GET /db/objects (browser listObjects) defaults to status: active and limit: 50, and rejects a limit above 100. It returns nextCursor when another page exists. GET /db/object (getObject) loads one row by key. Pass limit on listByScope yourself for server listings.

Browser

Register dbClient() once. In a client component, s3Client.useApi() gives you api.db.listObjects. Call it and render the list yourself — there is no list hook or list component.

lib/s3-client.ts
"use client";

import { createS3Client } from "@dimah-s3/react";
import { dbClient } from "@dimah-s3/db/client"; 

export const s3Client = createS3Client({
  basePath: "/api/s3",
  plugins: [dbClient()], 
});
components/file-list.tsx
"use client";

import { useCallback, useEffect, useState } from "react";
import type { DbClientObject } from "@dimah-s3/db/client";
import { s3Client } from "@/lib/s3-client";

export function Files() {
  const api = s3Client.useApi();
  const [objects, setObjects] = useState<DbClientObject[]>([]);
  const [nextCursor, setNextCursor] = useState<string | null>(null);

  const load = useCallback(
    async (cursor?: string) => {
      const page = await api.db.listObjects({
        route: "avatar",
        ...(cursor ? { cursor } : {}),
      });
      setObjects((prev) =>
        cursor ? [...prev, ...page.objects] : page.objects,
      );
      setNextCursor(page.nextCursor ?? null);
    },
    [api],
  );

  useEffect(() => {
    void load();
  }, [load]);

  return (
    <>
      <ul>
        {objects.map((file) => (
          <li key={file.id}>{file.filename ?? file.key}</li>
        ))}
      </ul>
      {nextCursor ? (
        <button type="button" onClick={() => void load(nextCursor)}>
          More
        </button>
      ) : null}
    </>
  );
}

Scope is resolved on the server

listObjects is the browser wrapper for GET /db/objects. It does not take a scope. The plugin uses resolveScope on the request. To list another scope, call listByScope on the server.

Quota guards

For the normal db() integration, pass app-owned limits through quota. The plugin runs the check after its ownership guard:

plugins: [
  db({
    client: dimahS3Db,
    resolveScope,
    quota: { maxBytes: 100 * 1024 * 1024, maxFiles: 50 }, 
  }),
],

The guard projects the scope's current DB usage plus the incoming file's declared size. It rejects a missing scope with UNAUTHORIZED and an exceeded byte or file-count limit with QUOTA_EXCEEDED (HTTP 413).

createQuotaGuard

Use createQuotaGuard when you need the same quota check in a route guard you compose yourself. Its signature is createQuotaGuard(options): (context: QuotaGuardContext) => Promise<void>.

import { createQuotaGuard } from "@dimah-s3/db";

const quotaGuard = createQuotaGuard({
  client: dimahS3Db,
  resolveScope,
  maxBytes: 100 * 1024 * 1024, 
  maxFiles: 50, 
});

avatar: route({
  upload: {
    guard: quotaGuard,
  },
});

When db() is also registered, its plugin guard runs before this route guard. Use chainHooks(quotaGuard, anotherGuard) from @dimah-s3/server when the route already has another upload guard.

Prop

Type

Object access guards

db() already installs object access guards for its routes. Use createObjectAccessGuard as an escape hatch when you manually compose a custom route policy. Its signature is createObjectAccessGuard(options): (context: ObjectAccessGuardContext) => Promise<void>.

The guard resolves the caller's scope, loads the row by bucket and key, checks its status and ownership, and optionally verifies a multipart uploadId. It throws UNAUTHORIZED, OBJECT_NOT_FOUND, or FORBIDDEN when a check fails.

import { createObjectAccessGuard } from "@dimah-s3/db";

const activeObjectGuard = createObjectAccessGuard({
  client: dimahS3Db,
  resolveScope,
  // requireStatus defaults to "active"
});

documents: route({
  // This route owns its guard composition; matching DB rows must already exist.
  plugins: { db: false }, 
  download: { guard: activeObjectGuard }, 
  delete: { guard: activeObjectGuard }, 
});

The default "active" status fits download and delete. For a new upload's manual confirm guard use requireStatus: "pending" (an overwrite can use "any"); for upload.multipart.guard, also set requireUploadId: true. requireStatus: "any" accepts pending or active rows, never deleted rows.

authorize replaces the default object.scope === scope ownership test. A custom callback must therefore enforce every ownership or sharing rule your application requires.

Prop

Type

On this page