# @uploadflow/core

Framework-agnostic engine for the **uploadflow** file-upload pipeline. Zero framework dependencies —
use it directly in Express, Fastify, workers, or plain Node, or via `@uploadflow/nestjs` /
`@uploadflow/express`.

```bash
npm i @uploadflow/core
# storage driver (optional peer) — install the one you use:
npm i @azure/storage-blob
# or: npm i @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner
# local disk driver needs nothing
```

## What's inside

- `StorageService` + adapters: `AzureBlobAdapter` (real SAS), `S3Adapter`, `LocalAdapter`, or your own.
- `FileValidatorService` — magic-byte content detection + size + allowlist.
- `PresignService` — presigned PUT + `confirm()` with staging→permanent promotion and orphan handling.
- `processDirectUpload()` / `runLinkedUpload()` — the pipeline used by the framework adapters.
- `InMemoryAttachmentStore` + the `AttachmentStore` / `StorageAdapter` interfaces.

## Direct use (any framework)

```ts
import { StorageService, FileValidatorService, processDirectUpload } from '@uploadflow/core';

const storage = new StorageService({
  storage: { driver: 's3', s3: { region, bucket, accessKeyId, secretAccessKey } },
  urlMode: 'signed',
});
const validator = new FileValidatorService({ allow: ['image/*'], maxFileSize: '5mb' });

// `file` is { buffer, originalName, mimeType, size }
const stored = await processDirectUpload(file, storage, validator);
// stored = { key, url, size, mime, originalName }
```

## Presigned flow

```ts
import { PresignService } from '@uploadflow/core';
const presign = new PresignService(storage, validator);

const { uploadUrl, key } = await presign.getUploadUrl({ filename: 'a.png', contentType: 'image/png' });
// client PUTs the bytes to uploadUrl (staging/), then:
const stored = await presign.confirm(key); // validates content, promotes staging → permanent
```

## Configuration reference

```ts
interface UploadOptions {
  storage: {
    driver: 'azure' | 's3' | 'local' | StorageAdapter;
    azure?: { account; accountKey; container; endpoint? };
    s3?:    { region; bucket; accessKeyId; secretAccessKey; endpoint?; forcePathStyle?; publicBaseUrl? };
    local?: { basePath; publicBaseUrl?; signingSecret? };
  };
  validation?: {
    maxFileSize?: number | string;   // '5mb' | bytes
    allow?: string[];                // ['image/*','video/mp4']; omit = any
    magicBytes?: boolean;            // default true
    signaturelessTypes?: string[];   // text types accepted without a signature
  };
  presign?: { expiresIn?: number };  // seconds, default 3600
  urlMode?: 'signed' | 'public';     // default 'signed'
  stagingPrefix?: string;            // default 'staging/'
  stagingTtlHours?: number;          // default 24 (lifecycle TTL you configure on the bucket)
  keyStrategy?: (i) => string;       // default `${uuid}.${ext}`
}
```

## Custom storage adapter

Implement `StorageAdapter` (`upload`, `download`, `delete`, `exists`, `getSignedUploadUrl`,
`getSignedDownloadUrl`, `copy`, `publicUrl`, `readRange`) and pass the instance as `storage.driver`.

## Errors

`UploadflowError` carries `code` + `statusCode`: `INVALID_FILE_TYPE`/`CONTENT_UNVERIFIED` (400),
`FILE_TOO_LARGE` (413), `NOT_FOUND` (404), `ADAPTER_MISSING`/`CONFIG` (500).

Full docs: **https://github.com/OWNER/uploadflow#readme**

MIT
