Atelier

Storage

S3-compatible object storage with Row Level Security, ownership-aware access, and on-the-fly image transforms.

Model

Each project has its own storage namespace. File metadata lives in a system table (_atelier_files) with RLS, so the same authz model that protects your database protects your files.

Upload

import { atelier } from '@atelier/sdk';
 
const { fileId, url } = await atelier.storage.upload(file, {
  bucket: 'avatars',
  visibility: 'private', // or 'public'
});

Read

Public files: a stable URL is returned at upload. Private files: request a short-lived signed URL when needed.

// Public
<img src={atelier.storage.url(fileId)} />
 
// Private — short-lived signed URL
const url = await atelier.storage.sign(fileId, { ttlSec: 300 });

RLS for files

Each file row has an owner_id column populated from the OIDC sub. Default policies let owners read and write their own files; override these with custom policies the same way as for any other table.

-- Example: team-shared files
CREATE POLICY files_team_read ON _atelier_files
  FOR SELECT
  USING (
    owner_id = current_setting('atelier.user_id')::uuid
    OR EXISTS (
      SELECT 1 FROM team_members
      WHERE team_id = _atelier_files.team_id
        AND user_id = current_setting('atelier.user_id')::uuid
    )
  );

Image transforms

Request transforms inline; Base resizes, re-encodes, and caches at the edge:

atelier.storage.url(fileId, { w: 400, h: 400, format: 'webp', quality: 82 });
atelier.storage.url(fileId, { w: 1200, format: 'avif' });
atelier.storage.url(fileId, { fit: 'cover', w: 600, h: 400 });
  • Supported: w, h, fit (cover/contain), format (webp, avif, jpeg, png), quality (1–100)
  • Transforms cached per variant. First request transforms; subsequent reads are CDN-served.

Logical buckets

Group files by bucket for clarity and per-bucket policies (max size, allowed mimetypes, public/private default). Buckets are namespaces, not separate stores.

// atelier.config.ts
export default {
  storage: {
    buckets: {
      avatars: { maxSizeMb: 5, mimeTypes: ['image/*'], default: 'public' },
      reports: { maxSizeMb: 100, default: 'private' },
    },
  },
};
Behind the scenes

Storage is S3-compatible (S3 in production, MinIO in dev). Bring your own bucket if you need raw S3 access — including Cloudflare R2 for zero-egress.