# API / Bibliothèque

> @colis/core : createBucket, l’API fichiers, les envois en morceaux, les enregistrements JSON, les codes et le gestionnaire de routes.

Canonical: https://colis-docs.vercel.app/docs/api · Markdown: https://colis-docs.vercel.app/docs/api.md

`@colis/core` est le seul paquet qui détient les identifiants du stockage. La page de livraison est
construite dessus ; vous pouvez l’utiliser pour construire votre propre flux. Il tourne côté serveur
uniquement.

## `createBucket(config?)`

Renvoie un `Bucket`. L’appel est léger : le `S3Client` sous-jacent n’est construit qu’à la première
requête, donc on peut l’appeler au niveau du module. Chaque option est dans
[Configuration](https://colis-docs.vercel.app/docs/configuration#la-bibliothèque).

```ts
import { createBucket } from '@colis/core'

const store = createBucket({ bucket: 'livraisons', prefix: 'drop' })
```

`new Bucket(config)` est équivalent, et exporté pour typer ou étendre.

## L’API fichiers

### `upload(body, options?)`

Envoie un corps en un seul `PutObject`. Écrire sur une clé existante la remplace.

`body` accepte `string`, `Buffer`, `Uint8Array`, `ArrayBuffer`, tableau typé, `Blob`, `File`,
`Readable` de Node ou `ReadableStream` web.

| Option                                           | Défaut                                                                            |
| ------------------------------------------------ | --------------------------------------------------------------------------------- |
| `key`                                            | générée : `<uuid>-<filename>`                                                     |
| `prefix`                                         | le préfixe du bucket                                                              |
| `filename`                                       | le nom du `File`, s’il y en a un                                                  |
| `contentType`                                    | le type du `File`, sinon deviné par l’extension, sinon `application/octet-stream` |
| `contentLength`                                  | la taille du corps ; **obligatoire pour un flux**                                 |
| `cacheControl`, `contentDisposition`, `metadata` | —                                                                                 |
| `acl`                                            | — (la plupart des buckets refusent les ACL)                                       |
| `ifMatch`                                        | — n’écrire que si l’ETag stocké correspond encore                                 |
| `ifAbsent`                                       | — n’écrire que si rien n’est encore stocké                                        |
| `signal`                                         | —                                                                                 |

Renvoie `{ bucket, key, path, contentType, size?, etag?, url? }`. `key` est la poignée à repasser aux
autres méthodes, `path` la clé réelle dans le bucket, préfixe compris ; `url` n’existe que si
`publicUrl` est configuré.

### `put(id, body, options?)`

`upload(body, { key: id })`, avec l’identifiant en premier. Mêmes options sauf `key`, même résultat.

### `get(key, options?)`

Relit un fichier, ou `null` si la clé n’existe pas. Le résultat porte `contentType`, `filename`,
`size`, `etag`, `lastModified`, `metadata`, et le corps : `body` (un `Readable`), `bytes()` ou
`text()`, à lire une seule fois.

### `head(key, options?)`

Ce que S3 sait d’un objet sans le télécharger, ou `null`. Les mêmes champs que `get()`, sans le corps.

### `getUrl(key, options?)`

L’adresse publique si `publicUrl` est configuré, une URL `GET` présignée sinon. La signature est un
calcul local : ni aller-retour réseau, ni permission supplémentaire.

| Option      | Défaut                                                                                |
| ----------- | ------------------------------------------------------------------------------------- |
| `expiresIn` | `3600` secondes, au plus `604800`                                                     |
| `signed`    | `true` sauf si `publicUrl` est configuré                                              |
| `download`  | — `Content-Disposition: attachment`, avec un nom de fichier si vous passez une chaîne |

### `delete(key)`

Supprime une clé ou un tableau de clés (par lots de 1 000). Supprimer une clé absente n’est pas une
erreur ; un échec par clé signalé par S3 lève `DELETE_FAILED` en les nommant.

## Les envois en morceaux

Pour envoyer un gros fichier du navigateur directement au bucket, comme le fait la page de livraison
(voir [Gros fichiers](https://colis-docs.vercel.app/docs/gros-fichiers)) :

```ts
const { uploadId } = await store.createMultipartUpload(key, { filename, contentType })

// Pour chaque morceau : une URL PUT signée avec sa longueur exacte.
const url = await store.presignUploadPart(key, uploadId, partNumber, { contentLength, expiresIn: 3600 })

// Pour reprendre : les morceaux que le bucket a déjà, ou null si l’envoi n’existe plus.
const parts = await store.listUploadedParts(key, uploadId)

// Le client renvoie l’ETag de chaque morceau.
await store.completeMultipartUpload(key, uploadId, [{ partNumber: 1, etag }], { ifAbsent: true })

// Ou l’abandonner, ce qui libère les morceaux déjà stockés.
await store.abortMultipartUpload(key, uploadId)
```

`maxSize` n’est pas vérifié ici : la taille n’est connue qu’une fois les morceaux arrivés. Relisez-la
avec `head()` après l’assemblage. `ifAbsent` envoie `If-None-Match: *` avec l’assemblage ; AWS S3 le
respecte, vérifiez que votre fournisseur aussi. Le bucket a besoin d’une règle CORS qui autorise `PUT`
et expose `ETag`.

`transferFileObject(code, { filename, contentType?, expiresAt? })` donne la clé, le type et les
métadonnées qui font d’un objet un colis que les routes du protocole reconnaissent : à passer à
`createMultipartUpload()` pour qu’un fichier arrivé en morceaux ne se distingue pas d’un autre.

## Les enregistrements JSON

`putSnapshot()` et `getSnapshot()` stockent de petits objets JSON, compressés en gzip, avec une
expiration vérifiée à la lecture. La page de livraison s’en sert pour l’accusé de réception et les
options d’un colis ; la CLI s’en sert pour la sonde de `colis verifier --remote`.

```ts
await store.putSnapshot(
  `meta/${code}.verdict`,
  { decision: 'approved', at: new Date().toISOString() },
  {
    expiresIn: 7 * 86_400,
    ifAbsent: true, // la première écriture gagne, les suivantes lèvent PRECONDITION_FAILED
  },
)

const record = await store.getSnapshot<{ decision: string }>(`meta/${code}.verdict`)
record?.data.decision // null si absent ou expiré
```

| Option de `putSnapshot` | Effet                                                                                              |
| ----------------------- | -------------------------------------------------------------------------------------------------- |
| `app`, `device`         | Des étiquettes libres, rendues à la lecture.                                                       |
| `version`               | Un numéro de schéma ; `getSnapshot(…, { maxVersion })` refuse plus récent avec `SNAPSHOT_TOO_NEW`. |
| `expiresIn`             | Secondes. Passé ce délai, `getSnapshot()` renvoie `null`.                                          |
| `compress`              | `true` par défaut.                                                                                 |
| `ifMatch`, `ifAbsent`   | Écritures conditionnelles, comme pour `upload()`.                                                  |
| `prefix`, `signal`      | Comme partout.                                                                                     |

`getSnapshot()` renvoie `{ data, app, device, version, createdAt, expiresAt, etag, size, bucket, key, path }`,
ou `null` pour une clé absente comme pour une clé expirée.

## Les codes

`store.codes` crée et relit les codes, dans la forme configurée par `syncCode` :

```ts
store.codes.create() // "K7QP2M4X"
store.codes.normalize('k7qp-2m4x') // "K7QP2M4X"
```

| Membre               | Rôle                                                                                             |
| -------------------- | ------------------------------------------------------------------------------------------------ |
| `create()`           | Un nouveau code.                                                                                 |
| `normalize(input)`   | Ce qu’on a tapé, remis en forme. Lève `INVALID_SYNC_CODE` si un caractère ne peut pas y figurer. |
| `alphabet`, `length` | La forme configurée.                                                                             |
| `entropyBits`        | Ce que vaut un code face à quelqu’un qui devine : 40 par défaut.                                 |

`createSyncCodes(options?)`, `createSyncCode()`, `normalizeSyncCode()` et `syncCodeAlphabets` font la
même chose hors d’un bucket. Voir [Configuration](https://colis-docs.vercel.app/docs/configuration#la-forme-des-codes).

## Le gestionnaire de routes

`createTransferHandler({ bucket, … })` sert les quatre routes du protocole sur n’importe quoi qui
parle `Request` et `Response`. Ses options (`basePath`, `expiresIn`, `authorize`, `raw`, `app`,
`maxVersion`) sont décrites dans [Protocole](https://colis-docs.vercel.app/docs/protocole#monter-les-routes).

## Le reste

- **`store.client`** : le `S3Client` sous-jacent, pour tout ce que colis ne couvre pas (lister,
  administrer).
- **`store.destroy()`** : libère les connexions du client créé par colis. Un client passé par
  `config.client` reste à vous.
- **`ColisError` et `isColisError(error)`** : voir [Erreurs](https://colis-docs.vercel.app/docs/erreurs).
