# Storage

Every Scribase environment includes an S3-compatible object storage service
backed by `storage-api`. Buckets, access policies, and object lifecycle are all
managed within the environment.

## Concepts

| Term | Description |
|---|---|
| **Bucket** | A named container for objects. Each environment can have many buckets. |
| **Object** | A file stored in a bucket, identified by a key (path). |
| **Bucket policy** | Public or private; controls anonymous access to the bucket. |
| **RLS policy** | Postgres-enforced per-object access control for authenticated users. |

## HTTP API

The storage API is accessible at `/storage/v1/`:

| Endpoint | Description |
|---|---|
| `POST /storage/v1/bucket` | Create a bucket |
| `GET /storage/v1/bucket` | List buckets |
| `GET /storage/v1/bucket/{id}` | Get bucket details |
| `DELETE /storage/v1/bucket/{id}` | Delete bucket |
| `POST /storage/v1/object/{bucket}/{path}` | Upload an object |
| `GET /storage/v1/object/{bucket}/{path}` | Download an object |
| `DELETE /storage/v1/object/{bucket}/{path}` | Delete an object |
| `GET /storage/v1/object/list/{bucket}` | List objects |
| `POST /storage/v1/object/sign/{bucket}/{path}` | Generate a signed URL |

### Creating a bucket

```sh
curl -X POST 'https://<your-project-url>/storage/v1/bucket' \
  -H 'Authorization: Bearer <service-role-key>' \
  -H 'Content-Type: application/json' \
  -d '{"id": "avatars", "name": "avatars", "public": false}'
```

### Uploading a file

```sh
curl -X POST 'https://<your-project-url>/storage/v1/object/avatars/user-123.png' \
  -H 'Authorization: Bearer <user-jwt>' \
  -H 'Content-Type: image/png' \
  --data-binary @avatar.png
```

## RLS policies for storage

Storage objects are backed by the `storage.objects` table. Protect them with
RLS policies:

```sql
-- Allow a user to access their own objects
CREATE POLICY "owner can upload"
  ON storage.objects FOR INSERT
  WITH CHECK (bucket_id = 'avatars' AND (storage.foldername(name))[1] = auth.uid()::text);

CREATE POLICY "owner can read"
  ON storage.objects FOR SELECT
  USING (bucket_id = 'avatars' AND (storage.foldername(name))[1] = auth.uid()::text);
```

## Signed URLs

Generate time-limited signed URLs for objects without requiring authentication
on the client:

```sh
curl -X POST 'https://<your-project-url>/storage/v1/object/sign/avatars/user-123.png' \
  -H 'Authorization: Bearer <service-role-key>' \
  -H 'Content-Type: application/json' \
  -d '{"expiresIn": 3600}'
```

## SDK

```ts
// List buckets
const { buckets } = await scribase.storage.listBuckets(ref);

// List objects in a bucket
const objects = await scribase.storage.listObjects(ref, 'avatars', {
  prefix: 'user-123/',
  limit: 100,
});
```

## Local development

`scribase dev` starts the storage service automatically. Files are stored
locally under `.scribase/storage/`. No S3 credentials are required for local
development.
