@reqvet-sdk/sdk 2.2.1 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.2.1
4
+
5
+ - **Feat** : ajout de `getSignedUploadUrl(fileName, contentType)` — obtenir une URL presignée Supabase pour uploader l'audio directement, sans passer par `/api/v1/upload` (Vercel Serverless Function, limite ~4.5 MB). Recommandé pour les proxies serveur (Next.js, Express…) gérant des fichiers > 4 MB.
6
+ - **Types** : ajout de `SignedUploadResult` dans `index.d.ts`.
7
+ - **Docs** : `uploadAudio()` annotée avec l'avertissement Vercel dans `index.js`, `index.d.ts`, `SDK_REFERENCE.md` et `README.md`.
8
+ - **Examples** : `nextjs/route-generate.ts` et `nextjs/route-generate.mjs` mis à jour pour utiliser `getSignedUploadUrl()`.
9
+ - **Security** : exemple proxy dans `SECURITY.md` mis à jour.
10
+
3
11
  ## 2.2.0
4
12
 
5
13
  - **Feat** : ajout de `listJobs(options?)` — liste les jobs avec pagination (`limit`, `offset`) et filtres (`status`, `sort`, `order`). Aligne le SDK sur `GET /api/v1/jobs`.
package/README.md CHANGED
@@ -51,6 +51,8 @@ const templateId = system[0].id;
51
51
 
52
52
  ### Flux webhook (recommandé)
53
53
 
54
+ Pour les intégrations serveur (Next.js, Express…), utilisez `getSignedUploadUrl()` qui uploade le fichier directement dans Supabase sans passer par une Vercel Serverless Function — **pas de limite de taille**.
55
+
54
56
  ```ts
55
57
  import ReqVet from '@reqvet-sdk/sdk';
56
58
 
@@ -58,20 +60,36 @@ const reqvet = new ReqVet(process.env.REQVET_API_KEY!, {
58
60
  baseUrl: process.env.REQVET_BASE_URL,
59
61
  });
60
62
 
61
- // 1. Uploader l'audio
62
- const { path } = await reqvet.uploadAudio(audioBuffer, 'consultation.webm');
63
+ // 1. Obtenir une URL signée Supabase (requête JSON légère, pas de fichier)
64
+ const { uploadUrl, path } = await reqvet.getSignedUploadUrl(
65
+ 'consultation.webm',
66
+ 'audio/webm',
67
+ );
68
+
69
+ // 2. Uploader directement vers Supabase (contourne Vercel, pas de limite de taille)
70
+ await fetch(uploadUrl, {
71
+ method: 'PUT',
72
+ headers: { 'Content-Type': 'audio/webm' },
73
+ body: audioBuffer, // Buffer | Blob | File
74
+ });
63
75
 
64
- // 2. Créer un job — ReqVet POSTera le résultat sur votre webhook quand il sera prêt
76
+ // 3. Créer un job — ReqVet POSTera le résultat sur votre webhook quand il sera prêt
65
77
  const job = await reqvet.createJob({
66
78
  audioFile: path,
67
79
  animalName: 'Rex',
68
80
  templateId: 'your-template-uuid',
69
81
  callbackUrl: 'https://your-app.com/api/reqvet/webhook',
70
- metadata: { consultationId: 'abc123' }, // transmis tel quel à votre webhook
82
+ metadata: { consultationId: 'abc123' },
71
83
  });
72
84
  // { job_id: '...', status: 'pending' }
73
85
  ```
74
86
 
87
+ > **`uploadAudio()` vs `getSignedUploadUrl()`**
88
+ >
89
+ > `uploadAudio()` est pratique pour des fichiers légers (< 4 MB) ou des contextes navigateur.
90
+ > Pour les proxies serveur, préférez `getSignedUploadUrl()` : le fichier va directement dans Supabase,
91
+ > sans passer par `/api/v1/upload` (Vercel Serverless Function, limite ~4.5 MB).
92
+
75
93
  Votre webhook reçoit un événement `job.completed` :
76
94
 
77
95
  ```json
@@ -125,7 +143,8 @@ export async function POST(req: NextRequest) {
125
143
 
126
144
  | Méthode | Description |
127
145
  |---------|-------------|
128
- | `uploadAudio(audio, fileName?)` | Uploader un fichier audio |
146
+ | `getSignedUploadUrl(fileName, contentType)` | URL signée Supabase pour upload direct (recommandé serveur) |
147
+ | `uploadAudio(audio, fileName?)` | Uploader un fichier audio via ReqVet (limite Vercel ~4.5 MB) |
129
148
  | `generateReport(params)` | Upload + création de job (helper tout-en-un) |
130
149
  | `createJob(params)` | Créer un job de génération |
131
150
  | `listJobs(options?)` | Lister les jobs avec pagination et filtre par statut |
package/SDK_REFERENCE.md CHANGED
@@ -11,8 +11,8 @@ import ReqVet from '@reqvet-sdk/sdk';
11
11
 
12
12
  const reqvet = new ReqVet(process.env.REQVET_API_KEY!, {
13
13
  baseUrl: process.env.REQVET_BASE_URL ?? 'https://api.reqvet.com',
14
- pollInterval: 5000, // intervalle de polling en ms (défaut : 5000)
15
- timeout: 5 * 60 * 1000, // attente maximale en polling en ms (défaut : 300 000 = 5 min)
14
+ pollInterval: 5000, // intervalle de polling en ms (défaut : 5000)
15
+ timeout: 5 * 60 * 1000, // attente maximale en polling en ms (défaut : 300 000 = 5 min)
16
16
  });
17
17
  ```
18
18
 
@@ -25,6 +25,7 @@ La clé API doit commencer par `rqv_`. Une `Error` est levée immédiatement dan
25
25
  ### Obtenir vos identifiants
26
26
 
27
27
  Votre responsable de compte ReqVet vous fournira :
28
+
28
29
  - `REQVET_API_KEY` — votre clé API d'organisation (`rqv_live_...`)
29
30
  - `REQVET_BASE_URL` — l'URL de base de l'API
30
31
  - `REQVET_WEBHOOK_SECRET` — votre secret de signature webhook (si vous utilisez les webhooks)
@@ -68,9 +69,53 @@ const report = await reqvet.generateReport({ audio, animalName, templateId, wait
68
69
 
69
70
  ## 4) Méthodes
70
71
 
72
+ ### `getSignedUploadUrl(fileName, contentType)` ⭐ recommandé serveur
73
+
74
+ Obtenir une URL signée Supabase pour uploader le fichier audio directement, sans passer par une Vercel Serverless Function.
75
+
76
+ **Quand l'utiliser :** intégrations serveur (Next.js proxy, Express, etc.), fichiers > 4 MB.
77
+
78
+ **Flow :**
79
+ ```
80
+ getSignedUploadUrl() → PUT uploadUrl (Supabase direct) → createJob({ audioFile: path })
81
+ ```
82
+
83
+ **Paramètres :**
84
+ | Nom | Type | Requis | Description |
85
+ |-----|------|--------|-------------|
86
+ | `fileName` | `string` | ✅ | Nom du fichier (ex. `consultation.webm`) |
87
+ | `contentType` | `string` | ✅ | Type MIME (ex. `audio/webm`) |
88
+
89
+ **Réponse :**
90
+ ```ts
91
+ {
92
+ uploadUrl: string; // URL presignée Supabase — à utiliser avec PUT
93
+ path: string; // chemin de stockage — à passer à createJob()
94
+ }
95
+ ```
96
+
97
+ **Exemple :**
98
+ ```ts
99
+ const { uploadUrl, path } = await reqvet.getSignedUploadUrl('consultation.webm', 'audio/webm');
100
+
101
+ await fetch(uploadUrl, {
102
+ method: 'PUT',
103
+ headers: { 'Content-Type': 'audio/webm' },
104
+ body: audioBuffer,
105
+ });
106
+
107
+ const job = await reqvet.createJob({ audioFile: path, animalName, templateId });
108
+ ```
109
+
110
+ ---
111
+
71
112
  ### `uploadAudio(audio, fileName?)`
72
113
 
73
- Uploader un fichier audio vers le stockage ReqVet.
114
+ Uploader un fichier audio vers le stockage ReqVet via `/api/v1/upload`.
115
+
116
+ > ⚠️ **Limite serveur** : `/api/v1/upload` est une Vercel Serverless Function avec une limite de payload de ~4.5 MB. Pour les proxies serveur gérant des fichiers > 4 MB, utilisez [`getSignedUploadUrl()`](#getsigneduploadurlfilename-contenttype--recommandé-serveur) à la place.
117
+ >
118
+ > `uploadAudio()` reste adapté pour les contextes navigateur (Blob/File natif, pas de limite Vercel) ou les fichiers légers.
74
119
 
75
120
  **Paramètres :**
76
121
  | Nom | Type | Requis | Description |
@@ -79,10 +124,11 @@ Uploader un fichier audio vers le stockage ReqVet.
79
124
  | `fileName` | `string` | — | Nom du fichier, utilisé pour inférer le type MIME (défaut : `audio.webm`) |
80
125
 
81
126
  **Réponse :**
127
+
82
128
  ```ts
83
129
  {
84
- audio_file: string; // chemin de stockage canonique — à passer à createJob()
85
- path: string; // alias de audio_file
130
+ audio_file: string; // chemin de stockage canonique — à passer à createJob()
131
+ path: string; // alias de audio_file
86
132
  size_bytes: number;
87
133
  content_type: string;
88
134
  }
@@ -110,6 +156,7 @@ Wrapper pratique : `uploadAudio → createJob`. Attend optionnellement la fin du
110
156
  | `onStatus` | `(status: string) => void` | — | Appelé à chaque poll (uniquement si `waitForResult: true`) |
111
157
 
112
158
  **Réponse :**
159
+
113
160
  - `waitForResult: false` (défaut) : `{ job_id: string, status: 'pending' }`
114
161
  - `waitForResult: true` : `ReqVetReport` (voir `waitForJob`)
115
162
 
@@ -130,8 +177,12 @@ Démarrer un pipeline de transcription + génération de compte rendu.
130
177
  | `extraInstructions` | `string` | — | Instructions de génération supplémentaires (max 5 000 caractères) |
131
178
 
132
179
  **Réponse :**
180
+
133
181
  ```ts
134
- { job_id: string; status: 'pending' }
182
+ {
183
+ job_id: string;
184
+ status: 'pending';
185
+ }
135
186
  ```
136
187
 
137
188
  > **Limite de débit** : 10 000 requêtes/minute par organisation.
@@ -152,6 +203,7 @@ Lister les jobs de l'organisation authentifiée, avec pagination et filtrage.
152
203
  | `order` | `string` | `desc` | Direction : `asc` ou `desc` |
153
204
 
154
205
  **Réponse :**
206
+
155
207
  ```ts
156
208
  {
157
209
  jobs: JobSummary[];
@@ -167,21 +219,22 @@ Obtenir l'état actuel et le résultat d'un job.
167
219
 
168
220
  **Champs de réponse par statut :**
169
221
 
170
- | Champ | `pending` | `transcribing` | `generating` | `completed` | `failed` |
171
- |-------|:---------:|:--------------:|:------------:|:-----------:|:--------:|
172
- | `job_id` | | | | | |
173
- | `status` | | | | | |
174
- | `animal_name` | | | | | |
175
- | `metadata` | | | | | |
176
- | `transcription` | | | | | |
177
- | `result.html` | | | | | |
178
- | `result.fields` | | | | ✅* | |
179
- | `cost` | | | | | |
180
- | `error` | | | | | |
222
+ | Champ | `pending` | `transcribing` | `generating` | `completed` | `failed` |
223
+ | --------------- | :-------: | :------------: | :----------: | :---------: | :------: |
224
+ | `job_id` | | | | | |
225
+ | `status` | | | | | |
226
+ | `animal_name` | | | | | |
227
+ | `metadata` | | | | | |
228
+ | `transcription` | | | | | |
229
+ | `result.html` | | | | | |
230
+ | `result.fields` | | | | ✅\* | |
231
+ | `cost` | | | | | |
232
+ | `error` | | | | | |
181
233
 
182
- *`result.fields` n'est présent que si votre organisation a un `field_schema` configuré (extraction de données structurées). Vaut `null` sinon. Voir [Schéma de champs](#5-schéma-de-champs) ci-dessous.
234
+ \*`result.fields` n'est présent que si votre organisation a un `field_schema` configuré (extraction de données structurées). Vaut `null` sinon. Voir [Schéma de champs](#5-schéma-de-champs) ci-dessous.
183
235
 
184
236
  **Structure du coût (jobs terminés) :**
237
+
185
238
  ```ts
186
239
  cost: {
187
240
  transcription_usd: number;
@@ -199,14 +252,19 @@ cost: {
199
252
  Poller jusqu'à ce qu'un job atteigne `completed` ou `failed`. Respecte `pollInterval` et `timeout`.
200
253
 
201
254
  **Réponse (`ReqVetReport`) :**
255
+
202
256
  ```ts
203
257
  {
204
258
  jobId: string;
205
- html: string; // HTML du compte rendu généré
206
- fields: ExtractedFields | null; // null si aucun field_schema configuré
259
+ html: string; // HTML du compte rendu généré
260
+ fields: ExtractedFields | null; // null si aucun field_schema configuré
207
261
  transcription: string;
208
262
  animalName: string;
209
- cost: { transcription_usd: number; generation_usd: number; total_usd: number };
263
+ cost: {
264
+ transcription_usd: number;
265
+ generation_usd: number;
266
+ total_usd: number;
267
+ }
210
268
  metadata: Record<string, unknown>;
211
269
  }
212
270
  ```
@@ -226,6 +284,7 @@ Régénérer le compte rendu d'un job terminé — par exemple avec des instruct
226
284
  | `templateId` | `string` | Basculer vers un autre template |
227
285
 
228
286
  **Réponse :**
287
+
229
288
  ```ts
230
289
  { job_id: string; status: 'completed'; result: { html: string; fields?: ExtractedFields } }
231
290
  ```
@@ -247,8 +306,14 @@ Ajouter un audio complémentaire à un job terminé. Le nouvel audio est transcr
247
306
  | `templateId` | `string` | — | Basculer vers un autre template |
248
307
 
249
308
  **Réponse :**
309
+
250
310
  ```ts
251
- { job_id: string; status: 'amending'; amendment_number: number; message: string }
311
+ {
312
+ job_id: string;
313
+ status: 'amending';
314
+ amendment_number: number;
315
+ message: string;
316
+ }
252
317
  ```
253
318
 
254
319
  Le job repasse à `completed` quand l'amendement est terminé. Utilisez `waitForJob()` ou écoutez l'événement webhook `job.amended`. Plusieurs amendements sont supportés — chacun est ajouté à la transcription complète.
@@ -275,6 +340,7 @@ Générer une version alternative d'un compte rendu terminé pour une audience s
275
340
  | `custom` | Défini par `customInstructions` |
276
341
 
277
342
  **Réponse (`ReqVetReformulation`) :**
343
+
278
344
  ```ts
279
345
  {
280
346
  id: string;
@@ -308,12 +374,12 @@ Générer une version alternative d'un compte rendu terminé pour une audience s
308
374
 
309
375
  #### `createTemplate(params)` → `Template`
310
376
 
311
- | Nom | Type | Requis |
312
- |-----|------|--------|
313
- | `name` | `string` | ✅ |
314
- | `content` | `string` | ✅ |
315
- | `description` | `string` | — |
316
- | `is_default` | `boolean` | — |
377
+ | Nom | Type | Requis |
378
+ | ------------- | --------- | ------ |
379
+ | `name` | `string` | ✅ |
380
+ | `content` | `string` | ✅ |
381
+ | `description` | `string` | — |
382
+ | `is_default` | `boolean` | — |
317
383
 
318
384
  #### `updateTemplate(templateId, updates)` → `Template`
319
385
 
@@ -462,10 +528,10 @@ import { verifyWebhookSignature } from '@reqvet-sdk/sdk/webhooks';
462
528
 
463
529
  const { ok, reason } = verifyWebhookSignature({
464
530
  secret: process.env.REQVET_WEBHOOK_SECRET!,
465
- rawBody, // corps brut de la requête — à lire AVANT JSON.parse
466
- signature, // valeur de l'en-tête X-ReqVet-Signature
467
- timestamp, // valeur de l'en-tête X-ReqVet-Timestamp
468
- maxSkewMs: 5 * 60 * 1000, // rejeter les événements de plus de 5 min (défaut)
531
+ rawBody, // corps brut de la requête — à lire AVANT JSON.parse
532
+ signature, // valeur de l'en-tête X-ReqVet-Signature
533
+ timestamp, // valeur de l'en-tête X-ReqVet-Timestamp
534
+ maxSkewMs: 5 * 60 * 1000, // rejeter les événements de plus de 5 min (défaut)
469
535
  });
470
536
  ```
471
537
 
@@ -486,21 +552,21 @@ try {
486
552
  const report = await reqvet.waitForJob(jobId);
487
553
  } catch (err) {
488
554
  if (err instanceof ReqVetError) {
489
- console.error(err.message); // message lisible par un humain
490
- console.error(err.status); // statut HTTP (0 pour les erreurs réseau/timeout)
491
- console.error(err.body); // corps brut de la réponse
555
+ console.error(err.message); // message lisible par un humain
556
+ console.error(err.status); // statut HTTP (0 pour les erreurs réseau/timeout)
557
+ console.error(err.body); // corps brut de la réponse
492
558
  }
493
559
  }
494
560
  ```
495
561
 
496
- | Statut | Signification |
497
- |--------|---------------|
498
- | `400` | Erreur de validation — vérifiez `err.body.issues` |
499
- | `401` | Clé API invalide ou manquante |
500
- | `403` | Quota mensuel dépassé |
501
- | `404` | Job ou template introuvable |
502
- | `429` | Limite de débit dépassée — attendez et réessayez |
503
- | `500` | Erreur interne ReqVet |
562
+ | Statut | Signification |
563
+ | ------ | ------------------------------------------------- |
564
+ | `400` | Erreur de validation — vérifiez `err.body.issues` |
565
+ | `401` | Clé API invalide ou manquante |
566
+ | `403` | Quota mensuel dépassé |
567
+ | `404` | Job ou template introuvable |
568
+ | `429` | Limite de débit dépassée — attendez et réessayez |
569
+ | `500` | Erreur interne ReqVet |
504
570
 
505
571
  ---
506
572
 
package/SECURITY.md CHANGED
@@ -29,7 +29,7 @@ Example proxy route (Next.js App Router):
29
29
  ```ts
30
30
  // app/api/reqvet/generate/route.ts
31
31
  import { NextRequest, NextResponse } from 'next/server';
32
- import ReqVet from '@reqvet/sdk';
32
+ import ReqVet from '@reqvet-sdk/sdk';
33
33
 
34
34
  const reqvet = new ReqVet(process.env.REQVET_API_KEY!);
35
35
 
@@ -37,7 +37,21 @@ export async function POST(req: NextRequest) {
37
37
  const form = await req.formData();
38
38
  const audio = form.get('audio') as File;
39
39
 
40
- const { path } = await reqvet.uploadAudio(audio, audio.name);
40
+ // Use getSignedUploadUrl() instead of uploadAudio() for server-side proxies.
41
+ // uploadAudio() posts to /api/v1/upload (Vercel Serverless Function, ~4.5 MB limit).
42
+ // getSignedUploadUrl() uploads directly to Supabase — no size limit.
43
+ const { uploadUrl, path } = await reqvet.getSignedUploadUrl(
44
+ audio.name || 'consultation.webm',
45
+ audio.type || 'audio/webm',
46
+ );
47
+
48
+ const audioBuffer = Buffer.from(await audio.arrayBuffer());
49
+ await fetch(uploadUrl, {
50
+ method: 'PUT',
51
+ headers: { 'Content-Type': audio.type || 'audio/webm' },
52
+ body: audioBuffer,
53
+ });
54
+
41
55
  const job = await reqvet.createJob({
42
56
  audioFile: path,
43
57
  animalName: form.get('animalName') as string,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reqvet-sdk/sdk",
3
- "version": "2.2.1",
3
+ "version": "2.2.2",
4
4
  "description": "Official JavaScript SDK for the ReqVet veterinary report generation API.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -18,7 +18,13 @@
18
18
  },
19
19
  "./package.json": "./package.json"
20
20
  },
21
- "files": ["src/", "README.md", "SDK_REFERENCE.md", "CHANGELOG.md", "SECURITY.md"],
21
+ "files": [
22
+ "src/",
23
+ "README.md",
24
+ "SDK_REFERENCE.md",
25
+ "CHANGELOG.md",
26
+ "SECURITY.md"
27
+ ],
22
28
  "keywords": [
23
29
  "reqvet",
24
30
  "veterinary",
package/src/index.d.ts CHANGED
@@ -15,6 +15,13 @@ export interface UploadResult {
15
15
  content_type: string;
16
16
  }
17
17
 
18
+ export interface SignedUploadResult {
19
+ /** Presigned URL for direct PUT upload to Supabase storage */
20
+ uploadUrl: string;
21
+ /** Storage path to pass to createJob({ audioFile: path }) */
22
+ path: string;
23
+ }
24
+
18
25
  export interface JobResult {
19
26
  job_id: string;
20
27
  status: 'pending' | 'transcribing' | 'generating' | 'completed' | 'failed' | 'amending';
@@ -201,7 +208,18 @@ export declare class ReqVet {
201
208
  generateReport(params: GenerateReportParams & { waitForResult?: false }): Promise<JobResult>;
202
209
  generateReport(params: GenerateReportParams): Promise<JobResult | ReqVetReport>;
203
210
 
204
- /** Upload an audio file */
211
+ /**
212
+ * Get a presigned URL for direct upload to Supabase storage.
213
+ * Recommended for server-side proxies — bypasses Vercel's ~4.5 MB payload limit.
214
+ * PUT the audio buffer to uploadUrl, then pass path to createJob().
215
+ */
216
+ getSignedUploadUrl(fileName: string, contentType: string): Promise<SignedUploadResult>;
217
+
218
+ /**
219
+ * Upload an audio file via ReqVet's /api/v1/upload endpoint.
220
+ * ⚠️ Subject to Vercel Serverless Function payload limit (~4.5 MB).
221
+ * For files > 4 MB in server-side contexts, use getSignedUploadUrl() instead.
222
+ */
205
223
  uploadAudio(audio: Blob | Buffer | File, fileName?: string): Promise<UploadResult>;
206
224
 
207
225
  /** Create a generation job */
package/src/index.js CHANGED
@@ -108,9 +108,37 @@ class ReqVet {
108
108
 
109
109
  // ─── Upload ────────────────────────────────────────────────
110
110
 
111
+ /**
112
+ * Get a presigned upload URL for direct upload to ReqVet storage (Supabase).
113
+ *
114
+ * Recommended for server-side integrations (e.g. Next.js proxy routes).
115
+ * The file goes directly to Supabase — it never passes through a Vercel
116
+ * Serverless Function, so there is no ~4.5 MB payload limit.
117
+ *
118
+ * Flow:
119
+ * 1. getSignedUploadUrl(fileName, contentType) — tiny JSON request, no file.
120
+ * 2. PUT the audio buffer to uploadUrl (direct to Supabase).
121
+ * 3. Pass path to createJob({ audioFile: path }).
122
+ *
123
+ * @param {string} fileName - File name (e.g. 'consultation.webm')
124
+ * @param {string} contentType - MIME type (e.g. 'audio/webm')
125
+ * @returns {Promise<{uploadUrl: string, path: string}>}
126
+ */
127
+ async getSignedUploadUrl(fileName, contentType) {
128
+ return this._fetch('POST', '/api/v1/storage/signed-upload', {
129
+ file_name: fileName,
130
+ content_type: contentType,
131
+ });
132
+ }
133
+
111
134
  /**
112
135
  * Upload an audio file to ReqVet storage.
113
136
  *
137
+ * ⚠️ This method POSTs the file to /api/v1/upload, which runs as a
138
+ * Vercel Serverless Function (~4.5 MB request limit). For server-side
139
+ * proxies (Next.js, Express…) handling files > 4 MB, prefer
140
+ * getSignedUploadUrl() + a direct PUT to avoid this limit.
141
+ *
114
142
  * @param {Blob|Buffer|File} audio - The audio file
115
143
  * @param {string} [fileName] - File name
116
144
  * @returns {Promise<{audio_file: string, size_bytes: number}>}