@mappa-ai/mappa-node 0.1.2 → 1.1.1

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/README.md CHANGED
@@ -1,6 +1,16 @@
1
- # Mappa Node SDK
1
+ # Mappa Node SDK [WIP - DO NOT USE YET]
2
2
 
3
- A lightweight JavaScript/TypeScript client for the Mappa API.
3
+ [![npm version](https://img.shields.io/npm/v/@mappa-ai/mappa-node.svg)](https://www.npmjs.com/package/@mappa-ai/mappa-node)
4
+ ![license](https://img.shields.io/npm/l/@mappa-ai/mappa-node.svg)
5
+
6
+ Official JavaScript/TypeScript SDK for the **Mappa API**.
7
+
8
+ - Works in **Node.js 18+** (and modern runtimes with `fetch`, `crypto`, `AbortController`).
9
+ - **Typed** end-to-end (requests, replies, errors).
10
+ - Built-in **retries**, **idempotency**, and **job helpers** (polling + streaming).
11
+ - Simple **webhook signature verification**.
12
+
13
+ ---
4
14
 
5
15
  ## Installation
6
16
 
@@ -9,77 +19,456 @@ npm install @mappa-ai/mappa-node
9
19
  # or
10
20
  yarn add @mappa-ai/mappa-node
11
21
  # or
12
- pnpm install @mappa-ai/mappa-node
22
+ pnpm add @mappa-ai/mappa-node
13
23
  # or
14
24
  bun add @mappa-ai/mappa-node
15
25
  ```
16
26
 
17
- ## Requirements
27
+ ---
28
+
29
+ ## Quickstart
30
+
31
+ Create a report from a remote media URL and wait for completion:
32
+
33
+ ```ts
34
+ import { Mappa } from "@mappa-ai/mappa-node";
35
+
36
+ const mappa = new Mappa({
37
+ apiKey: process.env.MAPPA_API_KEY!,
38
+ });
39
+
40
+ // One-liner for remote URLs: download -> upload -> create job -> wait -> fetch report
41
+ const report = await mappa.reports.generateFromUrl({
42
+ url: "https://example.com/media.mp3",
43
+ output: { type: "markdown", template: "general_report" },
44
+ });
45
+
46
+
47
+ if (report.output.type === "markdown") {
48
+ console.log(report.markdown);
49
+ }
50
+ ```
18
51
 
19
- - A valid Mappa API key.
52
+ ---
20
53
 
21
54
  ## Authentication
22
55
 
23
- Set the `MAPPA_API_KEY` environment variable or pass the key directly:
56
+ Pass the API key when constructing the client:
57
+
58
+ ```ts
59
+ import { Mappa } from "@mappa-ai/mappa-node";
60
+
61
+ const mappa = new Mappa({ apiKey: "YOUR_MAPPA_API_KEY" });
62
+ ```
63
+
64
+ Recommended: load the key from environment variables:
24
65
 
25
66
  ```bash
26
67
  export MAPPA_API_KEY="your-api-key"
27
68
  ```
28
69
 
70
+ ```ts
71
+ const mappa = new Mappa({ apiKey: process.env.MAPPA_API_KEY! });
72
+ ```
73
+
74
+ ---
75
+
76
+ ## Configuration
77
+
78
+ The client supports per-instance configuration:
79
+
29
80
  ```ts
30
81
  import { Mappa } from "@mappa-ai/mappa-node";
31
82
 
32
- const client = new Mappa("your-api-key");
83
+ const mappa = new Mappa({
84
+ apiKey: process.env.MAPPA_API_KEY!,
85
+ baseUrl: "https://api.mappa.ai", // default
86
+ timeoutMs: 30_000, // default; per HTTP attempt
87
+ maxRetries: 2, // default
88
+ defaultHeaders: {
89
+ "X-My-App": "my-service",
90
+ },
91
+ });
33
92
  ```
34
93
 
35
- ## Usage
94
+ ### Request tracing
36
95
 
37
- ### Instantiate the client
96
+ The SDK sets `X-Request-Id` on every request. You can supply your own `requestId`
97
+ per call to correlate logs across your system and Mappa support.
98
+
99
+ ### Idempotency
100
+
101
+ Most write APIs accept `idempotencyKey`. If you do not provide one, the SDK
102
+ generates a best-effort key per request. For long-running workflows, it is best
103
+ practice to supply a stable key per logical operation.
104
+
105
+ Example:
38
106
 
39
107
  ```ts
40
- import { Mappa } from "@mappa-ai/mappa-node";
108
+ await mappa.reports.createJob({
109
+ media: { mediaId: "media_..." },
110
+ output: { type: "markdown", template: "general_report" },
111
+ idempotencyKey: "report:customer_123:2026-01-14",
112
+ requestId: "req_customer_123",
113
+ });
114
+ ```
115
+
116
+ ### Retries
117
+
118
+ Retries are enabled for retryable requests (GETs and idempotent writes). Use
119
+ `maxRetries: 0` to disable retries globally, or pass your own `idempotencyKey`
120
+ to make POST retries safe.
121
+
122
+ Create a derived client with overrides:
41
123
 
42
- const mappa = new Mappa(); // Reads MAPPA_API_KEY by default
124
+ ```ts
125
+ const mappaNoRetries = mappa.withOptions({ maxRetries: 0 });
43
126
  ```
44
127
 
45
- ### Generate a behavior profile from a remote URL
128
+ ### Timeouts vs long-running jobs
129
+
130
+ `timeoutMs` is a **per-request** timeout (including each retry attempt).
131
+ For long-running work, create a job and use `jobs.wait(...)` or `reports.makeHandle(jobId)`.
132
+
133
+ ### Cancelling waits
134
+
135
+ Use `AbortController` to cancel polling or streaming when your app shuts down
136
+ or the user navigates away.
46
137
 
47
138
  ```ts
48
- await mappa.generateTextReport({
49
- inputMedia: {
50
- kind: "url",
51
- url: "https://example.com/media.mp3",
52
- },
53
- targetSpeaker: { strategy: "dominant" },
139
+ const controller = new AbortController();
140
+
141
+ setTimeout(() => controller.abort(), 10_000);
142
+
143
+ const receipt = await mappa.reports.createJob({
144
+ media: { mediaId: "media_..." },
145
+ output: { type: "markdown" },
54
146
  });
147
+
148
+ try {
149
+ const report = await receipt.handle!.wait({
150
+ signal: controller.signal,
151
+ });
152
+ console.log(report.id);
153
+ } catch (err) {
154
+ if (err instanceof Error && err.name === "AbortError") {
155
+ console.log("wait canceled");
156
+ }
157
+ }
55
158
  ```
56
159
 
57
- ### Generate a behavior profile from an uploaded file
160
+ Streaming with cancellation:
161
+
162
+ ```ts
163
+ const controller = new AbortController();
164
+
165
+ const handle = mappa.reports.makeHandle("job_...");
166
+
167
+ setTimeout(() => controller.abort(), 5_000);
168
+
169
+ for await (const event of handle.stream({ signal: controller.signal })) {
170
+ if (event.type === "terminal") break;
171
+ }
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Core concepts
177
+
178
+ ### Reports are asynchronous
179
+
180
+ In the underlying architecture we do a series of transformations and
181
+ ML inference, which can take time.
182
+ To accommodate this, creating a report returns a **job receipt**. You can:
183
+
184
+ - **Wait** (poll) until completion.
185
+ - **Stream** job events.
186
+ - **Use webhooks** so your server is notified when work is done.
187
+
188
+ ### Media input
189
+
190
+ Report job creation accepts **already-uploaded** media references:
191
+
192
+ - `{ mediaId: string }`
193
+
194
+ If you want a one-liner starting from something else:
195
+
196
+ - Remote URLs: `reports.createJobFromUrl()` / `reports.generateFromUrl()` (download client-side → upload → create job)
197
+ - Local bytes: `reports.createJobFromFile()` / `reports.generateFromFile()` (upload → create job)
198
+
199
+ ---
200
+
201
+ ## Creating reports
202
+
203
+ ### 1) Create a job (recommended for production)
204
+
205
+ If you already have an uploaded `mediaId`, use `createJob()`:
58
206
 
59
207
  ```ts
60
- const file = new File([buffer], "sample.wav", { type: "audio/wav" });
208
+ const receipt = await mappa.reports.createJob({
209
+ media: { mediaId: "media_..." },
210
+ output: { type: "markdown", template: "general_report" },
211
+ subject: {
212
+ externalRef: "customer_123",
213
+ metadata: { plan: "pro" },
214
+ },
215
+ options: {
216
+ language: "en",
217
+ timezone: "UTC",
218
+ },
219
+ // Optional: provide your own idempotency key for safe retries.
220
+ idempotencyKey: "report:customer_123:2026-01-14",
221
+ });
61
222
 
62
- await mappa.generateTextReport({
63
- inputMedia: {
64
- kind: "file",
65
- file,
223
+ console.log(receipt.jobId);
224
+ ```
225
+
226
+ If you’re starting from a remote URL, use `createJobFromUrl()`:
227
+
228
+ ```ts
229
+ const receiptFromUrl = await mappa.reports.createJobFromUrl({
230
+ url: "https://example.com/media.mp3",
231
+ output: { type: "markdown", template: "general_report" },
232
+ subject: {
233
+ externalRef: "customer_123",
234
+ metadata: { plan: "pro" },
66
235
  },
67
- targetSpeaker: {
68
- strategy: "magic_hint",
69
- hint: "the person being interviewed",
236
+ options: {
237
+ language: "en",
238
+ timezone: "UTC",
70
239
  },
240
+ idempotencyKey: "report:customer_123:2026-01-14",
71
241
  });
242
+
243
+ console.log(receiptFromUrl.jobId);
244
+ ```
245
+
246
+ ### 2) Wait for completion (polling)
247
+
248
+ ```ts
249
+ const report = await receipt.handle!.wait({
250
+ timeoutMs: 10 * 60_000,
251
+ onEvent: (e) => {
252
+ if (e.type === "stage") {
253
+ console.log("stage", e.stage, e.progress);
254
+ }
255
+ },
256
+ });
257
+ ```
258
+
259
+ ### 3) Stream job events
260
+
261
+ `jobs.stream(jobId)` yields state transitions.
262
+
263
+ ```ts
264
+ if (report.output.type === "json") {
265
+ for (const section of report.sections) {
266
+ console.log(section.section_title, section.section_content);
267
+ }
268
+ }
72
269
  ```
73
270
 
74
- The method returns:
271
+ Type narrowing helpers:
75
272
 
76
273
  ```ts
77
- {
78
- behaviorProfile: string;
79
- entityId: string;
274
+ if (report.output.type === "markdown") {
275
+ console.log(report.markdown);
80
276
  }
81
277
  ```
82
278
 
279
+ ---
280
+
281
+ ## Feedback
282
+
283
+ Use `mappa.feedback.create()` to share ratings or corrections. Provide exactly
284
+ one of `reportId` or `jobId`.
285
+
286
+ ```ts
287
+ await mappa.feedback.create({
288
+ reportId: "report_...",
289
+ rating: "thumbs_up",
290
+ tags: ["quality"],
291
+ comment: "Accurate summary",
292
+ });
293
+ ```
294
+
295
+ ---
296
+
297
+ ## Errors
298
+
299
+
300
+ The SDK throws typed errors:
301
+
302
+ - `ApiError` for non-2xx responses
303
+ - `AuthError` for 401/403
304
+ - `ValidationError` for 422
305
+ - `RateLimitError` for 429 (may include `retryAfterMs`)
306
+ - `JobFailedError` / `JobCanceledError` from polling helpers
307
+ - `MappaError` for client-side validation or runtime constraints
308
+
309
+ ```ts
310
+ import {
311
+ ApiError,
312
+ AuthError,
313
+ MappaError,
314
+ RateLimitError,
315
+ ValidationError,
316
+ } from "@mappa-ai/mappa-node";
317
+
318
+ try {
319
+ await mappa.reports.generateFromUrl({
320
+ url: "https://example.com/media.mp3",
321
+ output: { type: "markdown", template: "general_report" },
322
+ });
323
+
324
+ await mappa.reports.generateFromUrl({
325
+ url: "https://example.com/interview.mp3",
326
+ output: {
327
+ type: "markdown",
328
+ template: "hiring_report",
329
+ templateParams: {
330
+ roleTitle: "Customer Success Manager",
331
+ roleDescription: "Own onboarding and renewal conversations.",
332
+ companyCulture: "Curious, candid, customer-obsessed.",
333
+ },
334
+ },
335
+ });
336
+ } catch (err) {
337
+ if (err instanceof AuthError) {
338
+ console.error("Auth error", err.requestId);
339
+ throw err;
340
+ }
341
+
342
+ if (err instanceof RateLimitError) {
343
+ console.error("Rate limited; retry after", err.retryAfterMs);
344
+ throw err;
345
+ }
346
+
347
+ if (err instanceof ValidationError) {
348
+ console.error("Invalid request", err.details);
349
+ throw err;
350
+ }
351
+
352
+ if (err instanceof ApiError) {
353
+ console.error("API error", err.status, err.code, err.requestId);
354
+ throw err;
355
+ }
356
+
357
+ if (err instanceof MappaError) {
358
+ console.error("Client error", err.message);
359
+ throw err;
360
+ }
361
+
362
+ throw err;
363
+ }
364
+ ```
365
+
366
+ ---
367
+
368
+ ## Webhooks
369
+
370
+ Use `mappa.webhooks.verifySignature()` to verify incoming webhook events.
371
+
372
+ Tips for raw body handling:
373
+ - Express: `express.text({ type: "*/*" })`
374
+ - Fastify: use `rawBody` (enable `bodyLimit` and `rawBody`)
375
+ - Next.js (App Router): read `await req.text()` before parsing
376
+ - Node 18+ or modern runtimes are required for WebCrypto
377
+
378
+ The SDK expects a header shaped like:
379
+
380
+ ```http
381
+ mappa-signature: t=1700000000,v1=<hex>
382
+ ```
383
+
384
+ And verifies the HMAC-SHA256 signature of:
385
+
386
+ ```ts
387
+ ${t}.${rawBody}
388
+ ```
389
+
390
+ Example (Express):
391
+
392
+ ```ts
393
+ import express from "express";
394
+ import { Mappa } from "@mappa-ai/mappa-node";
395
+
396
+ const app = express();
397
+
398
+ // IMPORTANT: you must keep the raw body for signature verification.
399
+ app.post(
400
+ "/webhooks/mappa",
401
+ express.text({ type: "*/*" }),
402
+ async (req, res) => {
403
+ const mappa = new Mappa({ apiKey: process.env.MAPPA_API_KEY! });
404
+
405
+ await mappa.webhooks.verifySignature({
406
+ payload: req.body,
407
+ headers: req.headers as Record<string, string | string[] | undefined>,
408
+ secret: process.env.MAPPA_WEBHOOK_SECRET!,
409
+ toleranceSec: 300,
410
+ });
411
+
412
+ const event = mappa.webhooks.parseEvent(req.body);
413
+
414
+ // Example: handle event types.
415
+ switch (event.type) {
416
+ case "report.completed":
417
+ // event.data contains the payload from Mappa.
418
+ break;
419
+ default:
420
+ break;
421
+ }
422
+
423
+ res.status(200).send("ok");
424
+ },
425
+ );
426
+ ```
427
+
428
+ ---
429
+
430
+ ## Telemetry hooks
431
+
432
+ You can hook into request/response/error events:
433
+
434
+ ```ts
435
+ import { Mappa } from "@mappa-ai/mappa-node";
436
+
437
+ const mappa = new Mappa({
438
+ apiKey: process.env.MAPPA_API_KEY!,
439
+ telemetry: {
440
+ onRequest: ({ method, url, requestId }) => {
441
+ console.log("request", method, url, requestId);
442
+ },
443
+ onResponse: ({ status, url, durationMs, requestId }) => {
444
+ console.log("response", status, url, durationMs, requestId);
445
+ },
446
+ onError: ({ url, requestId, error }) => {
447
+ console.log("error", url, requestId, error);
448
+ },
449
+ },
450
+ });
451
+ ```
452
+
453
+ ---
454
+
455
+ ## TypeScript
456
+
457
+ Everything is typed. Common types are exported and `Report` is a discriminated
458
+ union on `report.output.type`.
459
+
460
+ ```ts
461
+ import type {
462
+ Report,
463
+ Job,
464
+ ReportCreateJobRequest,
465
+ ReportOutput,
466
+ WaitOptions,
467
+ } from "@mappa-ai/mappa-node";
468
+ ```
469
+
470
+ ---
471
+
83
472
  ## License
84
473
 
85
474
  MIT
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- let e=require(`zod`);var t=class{async generateReport(){return``}};async function n(e,t=3){let n;for(let r=0;r<t;r++)try{return await e()}catch(e){console.warn(`Attempt ${r+1} failed. Retrying...`),n=e}throw n}let r;(function(t){t.availablePresetsSchema=e.z.enum([`base`])})(r||={});let i;(function(t){t.generateReportInputSchema=e.z.object({inputMedia:e.z.discriminatedUnion(`kind`,[e.z.object({kind:e.z.literal(`file`),file:e.z.file().mime([`video/*`,`audio/*`],{message:`Invalid file type, only audio and video files are allowed`}).describe(`The audio file to be processed`)}),e.z.object({kind:e.z.literal(`url`),url:e.z.url().describe(`The URL of the audio or video file to be processed`)})]),targetSpeaker:e.z.discriminatedUnion(`strategy`,[e.z.object({strategy:e.z.literal(`dominant`).describe(`Select the speaker who speaks the most in the audio`)}),e.z.object({strategy:e.z.literal(`magic_hint`).describe(`Use a hint to identify the target speaker`),hint:e.z.string().describe(`A hint to help identify the target speaker, e.g., 'the interviewer', 'the CEO', 'the person being interviewed`)})]).default({strategy:`dominant`}).describe(`Strategy for target extraction, this is useful when there are multiple speakers in the audio`),template:r.availablePresetsSchema}),t.generateReportOutputSchema=e.z.object({outputs:e.z.object({entity_id:e.z.string().describe(`The unique identifier for the analyzed entity`),behavior_profile:e.z.string().describe(`The generated behavior profile report`)}).array().transform(e=>e[0]??null)})})(i||={});var a=class{apiKey;apiBaseUrl=`https://api.mappa.ai`;apiVersion=`v1`;presets=new t;constructor(e){if(!e&&typeof process<`u`&&process.env&&process.env.MAPPA_API_KEY?.trim()!==``&&(this.apiKey=process.env.MAPPA_API_KEY||``),this.apiKey=e||``,this.apiKey.trim()===``)throw Error(`Mappa API key is required`)}async generateTextReport(e){let t;if(e.inputMedia.kind===`file`){let r=new FormData;r.append(`file`,e.inputMedia.file),r.append(`metadata`,JSON.stringify({mode:`sync`,output:`behavior_profile`,target:e.targetSpeaker})),t=await n(()=>fetch(`${this.apiBaseUrl}/${this.apiVersion}/analyze/file`,{method:`POST`,headers:{"Mappa-Api-Key":this.apiKey},body:r}))}else{let r=new URL(e.inputMedia.url);t=await n(()=>fetch(`${this.apiBaseUrl}/${this.apiVersion}/analyze/url`,{method:`POST`,headers:{"Content-Type":`application/json`,"Mappa-Api-Key":this.apiKey},body:JSON.stringify({url:r.href,mode:`sync`,output:`behavior_profile`,target:e.targetSpeaker})}))}if(!t.ok){let e=`Mappa API request failed with status ${t.status}: ${await t.text().catch(()=>`Unable to retrieve error message`)}`;throw console.error(e),Error(e)}let r=await t.json(),a=i.generateReportOutputSchema.parse(r).outputs;if(!a)throw Error(`No outputs found in the response, error occurred during analysis.`);return{behaviorProfile:a.behavior_profile,entityId:a.entity_id}}};exports.Mappa=a;
1
+ let e=require(`@paralleldrive/cuid2`);var t=class extends Error{name=`MappaError`;requestId;code;constructor(e,t){super(e),this.requestId=t?.requestId,this.code=t?.code,this.cause=t?.cause}},n=class extends t{name=`ApiError`;status;details;constructor(e,t){super(e,{requestId:t.requestId,code:t.code}),this.status=t.status,this.details=t.details}},r=class extends n{name=`RateLimitError`;retryAfterMs},i=class extends n{name=`AuthError`},a=class extends n{name=`ValidationError`},o=class extends t{name=`JobFailedError`;jobId;constructor(e,t,n){super(t,n),this.jobId=e}},s=class extends t{name=`JobCanceledError`;jobId;constructor(e,t,n){super(t,n),this.jobId=e}},c=class{constructor(e){this.transport=e}async getBalance(e){return(await this.transport.request({method:`GET`,path:`/v1/credits/balance`,requestId:e?.requestId,signal:e?.signal,retryable:!0})).data}async listTransactions(e){let t={};return e?.limit!==void 0&&(t.limit=String(e.limit)),e?.offset!==void 0&&(t.offset=String(e.offset)),(await this.transport.request({method:`GET`,path:`/v1/credits/transactions`,query:t,requestId:e?.requestId,signal:e?.signal,retryable:!0})).data}async*listAllTransactions(e){let t=0,n=e?.limit??50;for(;;){let r=await this.listTransactions({...e,limit:n,offset:t});for(let e of r.transactions)yield e;if(t+=r.transactions.length,t>=r.pagination.total)break}}async getJobUsage(e,n){if(!e)throw new t(`jobId is required`);return(await this.transport.request({method:`GET`,path:`/v1/credits/usage/${encodeURIComponent(e)}`,requestId:n?.requestId,signal:n?.signal,retryable:!0})).data}async hasEnough(e,t){return(await this.getBalance(t)).available>=e}async getAvailable(e){return(await this.getBalance(e)).available}};const l=/^[a-zA-Z0-9_-]{1,64}$/,u=10;function d(e){if(typeof e!=`string`)throw new t(`Tags must be strings`);if(!l.test(e))throw new t(`Invalid tag "${e}": must be 1-64 characters, alphanumeric with underscores and hyphens only`)}function f(e){if(!Array.isArray(e))throw new t(`tags must be an array`);if(e.length>10)throw new t(`Too many tags: maximum 10 per request`);for(let t of e)d(t)}var p=class{constructor(e){this.transport=e}async get(e,n){if(!e||typeof e!=`string`)throw new t(`entityId must be a non-empty string`);return(await this.transport.request({method:`GET`,path:`/v1/entities/${encodeURIComponent(e)}`,requestId:n?.requestId,signal:n?.signal,retryable:!0})).data}async list(e){let t={};return e?.tags&&(f(e.tags),t.tags=e.tags.join(`,`)),e?.cursor&&(t.cursor=e.cursor),e?.limit!==void 0&&(t.limit=String(e.limit)),(await this.transport.request({method:`GET`,path:`/v1/entities`,query:t,requestId:e?.requestId,signal:e?.signal,retryable:!0})).data}async*listAll(e){let t,n=!0;for(;n;){let r=await this.list({...e,cursor:t});for(let e of r.entities)yield e;t=r.cursor,n=r.hasMore}}async getByTag(e,t){return d(e),this.list({...t,tags:[e]})}async addTags(e,n,r){if(!e||typeof e!=`string`)throw new t(`entityId must be a non-empty string`);if(n.length===0)throw new t(`At least one tag is required`);return f(n),(await this.transport.request({method:`POST`,path:`/v1/entities/${encodeURIComponent(e)}/tags`,body:{tags:n},requestId:r?.requestId,signal:r?.signal,retryable:!0})).data}async removeTags(e,n,r){if(!e||typeof e!=`string`)throw new t(`entityId must be a non-empty string`);if(n.length===0)throw new t(`At least one tag is required`);return f(n),(await this.transport.request({method:`DELETE`,path:`/v1/entities/${encodeURIComponent(e)}/tags`,body:{tags:n},requestId:r?.requestId,signal:r?.signal,retryable:!0})).data}async setTags(e,n,r){if(!e||typeof e!=`string`)throw new t(`entityId must be a non-empty string`);return f(n),(await this.transport.request({method:`PUT`,path:`/v1/entities/${encodeURIComponent(e)}/tags`,body:{tags:n},requestId:r?.requestId,signal:r?.signal,retryable:!0})).data}},m=class{constructor(e){this.transport=e}async create(e){if(!!e.reportId==!!e.jobId)throw new t(`Provide exactly one of reportId or jobId`);return(await this.transport.request({method:`POST`,path:`/v1/feedback`,body:e,idempotencyKey:e.idempotencyKey,requestId:e.requestId,signal:e.signal,retryable:!0})).data}},h=class{constructor(e){this.transport=e}async upload(e){if(typeof FormData>`u`)throw new t(`FormData is not available in this runtime; cannot perform multipart upload`);let n=g(e.file,e.filename),r=e.contentType??n;if(!r)throw new t(`contentType is required when it cannot be inferred from file.type or filename`);let i=e.filename??_(e.file)??`upload`,a=await y(e.file,r),o=new FormData;return o.append(`file`,a,i),o.append(`contentType`,r),e.filename&&o.append(`filename`,e.filename),(await this.transport.request({method:`POST`,path:`/v1/files`,body:o,idempotencyKey:e.idempotencyKey,requestId:e.requestId,signal:e.signal,retryable:!0})).data}async get(e,n){if(!e)throw new t(`mediaId is required`);return(await this.transport.request({method:`GET`,path:`/v1/files/${encodeURIComponent(e)}`,requestId:n?.requestId,signal:n?.signal,retryable:!0})).data}async list(e){let t={};return e?.limit!==void 0&&(t.limit=String(e.limit)),e?.cursor&&(t.cursor=e.cursor),e?.includeDeleted!==void 0&&(t.includeDeleted=String(e.includeDeleted)),(await this.transport.request({method:`GET`,path:`/v1/files`,query:t,requestId:e?.requestId,signal:e?.signal,retryable:!0})).data}async*listAll(e){let t,n=!0;for(;n;){let r=await this.list({...e,cursor:t});for(let e of r.files)yield e;t=r.cursor,n=r.hasMore}}async setRetentionLock(e,n,r){if(!e)throw new t(`mediaId is required`);return(await this.transport.request({method:`PATCH`,path:`/v1/files/${encodeURIComponent(e)}/retention`,body:{lock:n},requestId:r?.requestId,signal:r?.signal,retryable:!0})).data}async delete(e,n){if(!e)throw new t(`mediaId is required`);return(await this.transport.request({method:`DELETE`,path:`/v1/files/${encodeURIComponent(e)}`,idempotencyKey:n?.idempotencyKey,requestId:n?.requestId,signal:n?.signal,retryable:!0})).data}};function g(e,t){if(typeof Blob<`u`&&e instanceof Blob&&e.type)return e.type;if(t)return v(t)}function _(e){if(typeof Blob<`u`&&e instanceof Blob){let t=e;if(typeof t.name==`string`&&t.name)return t.name}}function v(e){let t=e.lastIndexOf(`.`);if(!(t<0))switch(e.slice(t+1).toLowerCase()){case`mp4`:return`video/mp4`;case`mov`:return`video/quicktime`;case`webm`:return`video/webm`;case`mp3`:return`audio/mpeg`;case`wav`:return`audio/wav`;case`m4a`:return`audio/mp4`;case`png`:return`image/png`;case`jpg`:case`jpeg`:return`image/jpeg`;case`gif`:return`image/gif`;case`webp`:return`image/webp`;case`pdf`:return`application/pdf`;case`json`:return`application/json`;case`txt`:return`text/plain`;default:return}}async function y(e,n){if(typeof Blob<`u`&&e instanceof Blob){if(e.type===n)return e;let t=e;return typeof t.slice==`function`?t.slice(0,e.size,n):e}if(e instanceof ArrayBuffer||e instanceof Uint8Array)return new Blob([e],{type:n});if(typeof ReadableStream<`u`&&e instanceof ReadableStream){if(typeof Response>`u`)throw new t(`ReadableStream upload requires Response to convert stream to Blob`);let r=await new Response(e).blob();return r.slice(0,r.size,n)}throw new t(`Unsupported file type for upload()`)}var b=class{constructor(e){this.transport=e}async ping(){return(await this.transport.request({method:`GET`,path:`/v1/health/ping`,retryable:!0})).data}};const x=(0,e.init)({length:32});function S(e,t){let n=e.get(t);return n===null?void 0:n}function C(e){let t=.8+Math.random()*.4;return Math.floor(e*t)}function w(e,t,n){let r=t*2**Math.max(0,e-1);return Math.min(r,n)}function T(){return Date.now()}function E(e){return!!e&&typeof e.aborted==`boolean`}function D(){let e=Error(`The operation was aborted`);return e.name=`AbortError`,e}function O(e=`req`){return`${e}_${x()}`}var k=class{constructor(e){this.transport=e}async get(e,t){return(await this.transport.request({method:`GET`,path:`/v1/jobs/${encodeURIComponent(e)}`,requestId:t?.requestId,signal:t?.signal,retryable:!0})).data}async cancel(e,t){return(await this.transport.request({method:`POST`,path:`/v1/jobs/${encodeURIComponent(e)}/cancel`,idempotencyKey:t?.idempotencyKey,requestId:t?.requestId,signal:t?.signal,retryable:!0})).data}async wait(e,t){let n=t?.timeoutMs??5*6e4,r=t?.pollIntervalMs??1e3,i=t?.maxPollIntervalMs??1e4,a=T(),c=0,l,u;for(;;){if(t?.signal?.aborted)throw D();let d=await this.get(e,{signal:t?.signal});if(d.status!==u&&(u=d.status,t?.onEvent?.({type:`status`,job:d})),d.stage&&d.stage!==l&&(l=d.stage,t?.onEvent?.({type:`stage`,stage:d.stage,progress:d.progress,job:d})),d.status===`succeeded`)return t?.onEvent?.({type:`terminal`,job:d}),d;if(d.status===`failed`)throw t?.onEvent?.({type:`terminal`,job:d}),new o(e,d.error?.message??`Job failed`,{requestId:d.requestId,code:d.error?.code,cause:d.error});if(d.status===`canceled`)throw t?.onEvent?.({type:`terminal`,job:d}),new s(e,`Job canceled`,{requestId:d.requestId,cause:d.error});if(T()-a>n)throw new o(e,`Timed out waiting for job ${e} after ${n}ms`,{cause:{jobId:e,timeoutMs:n}});c+=1;let f=C(w(c,r,i));await new Promise(e=>setTimeout(e,f))}}async*stream(e,t){let n,r;for(;;){if(t?.signal?.aborted)return;let i=await this.get(e,{signal:t?.signal});if(i.status!==r){r=i.status;let e={type:`status`,job:i};t?.onEvent?.(e),yield e}if(i.stage&&i.stage!==n){n=i.stage;let e={type:`stage`,stage:i.stage,progress:i.progress,job:i};t?.onEvent?.(e),yield e}if(i.status===`succeeded`||i.status===`failed`||i.status===`canceled`){let e={type:`terminal`,job:i};t?.onEvent?.(e),yield e;return}await new Promise(e=>setTimeout(e,1e3))}}};function A(e){let n=e;if(!(e=>typeof e==`object`&&!!e)(n))throw new t(`media must be an object`);if(n.url!==void 0)throw new t(`media.url is not supported; pass { mediaId } or use createJobFromUrl()`);let r=n.mediaId;if(typeof r!=`string`||!r)throw new t(`media.mediaId must be a non-empty string`)}var j=class{constructor(e,t,n,r){this.transport=e,this.jobs=t,this.files=n,this.fetchImpl=r}async createJob(e){A(e.media);let t=e.idempotencyKey??this.defaultIdempotencyKey(e),n=await this.transport.request({method:`POST`,path:`/v1/reports/jobs`,body:this.normalizeJobRequest(e),idempotencyKey:t,requestId:e.requestId,retryable:!0}),r={...n.data,requestId:n.requestId??n.data.requestId};return r.handle=this.makeHandle(r.jobId),r}async createJobFromFile(e){let{file:t,contentType:n,filename:r,idempotencyKey:i,requestId:a,signal:o,...s}=e,c=await this.files.upload({file:t,contentType:n,filename:r,idempotencyKey:i,requestId:a,signal:o});return this.createJob({...s,media:{mediaId:c.mediaId},idempotencyKey:i,requestId:a})}async createJobFromUrl(e){let{url:n,contentType:r,filename:i,idempotencyKey:a,requestId:o,signal:s,...c}=e,l;try{l=new URL(n)}catch{throw new t(`url must be a valid URL`)}if(l.protocol!==`http:`&&l.protocol!==`https:`)throw new t(`url must use http: or https:`);let u=await this.fetchImpl(l.toString(),{signal:s});if(!u.ok)throw new t(`Failed to download url (status ${u.status})`);let d=u.headers.get(`content-type`)??void 0,f=r??d;if(!f)throw new t(`contentType is required when it cannot be inferred from the download response`);if(typeof Blob>`u`)throw new t(`Blob is not available in this runtime; cannot download and upload from url`);let p=await u.blob(),m=await this.files.upload({file:p,contentType:f,filename:i,idempotencyKey:a,requestId:o,signal:s});return this.createJob({...c,media:{mediaId:m.mediaId},idempotencyKey:a,requestId:o})}async get(e,t){return(await this.transport.request({method:`GET`,path:`/v1/reports/${encodeURIComponent(e)}`,requestId:t?.requestId,signal:t?.signal,retryable:!0})).data}async getByJob(e,t){return(await this.transport.request({method:`GET`,path:`/v1/reports/by-job/${encodeURIComponent(e)}`,requestId:t?.requestId,signal:t?.signal,retryable:!0})).data}async generate(e,n){let r=await this.createJob(e);if(!r.handle)throw new t(`Job receipt is missing handle`);return r.handle.wait(n?.wait)}async generateFromFile(e,n){let r=await this.createJobFromFile(e);if(!r.handle)throw new t(`Job receipt is missing handle`);return r.handle.wait(n?.wait)}async generateFromUrl(e,n){let r=await this.createJobFromUrl(e);if(!r.handle)throw new t(`Job receipt is missing handle`);return r.handle.wait(n?.wait)}makeHandle(e){let n=this;return{jobId:e,stream:t=>n.jobs.stream(e,t),async wait(r){let i=await n.jobs.wait(e,r);if(!i.reportId)throw new t(`Job ${e} succeeded but no reportId was returned`);return n.get(i.reportId)},cancel:()=>n.jobs.cancel(e),job:()=>n.jobs.get(e),report:()=>n.getByJob(e)}}defaultIdempotencyKey(e){return O(`idem`)}normalizeJobRequest(e){let t=e.target;if(!t)return e;let n={strategy:t.strategy};switch(t.onMiss&&(n.on_miss=t.onMiss),t.tags&&t.tags.length>0&&(n.tags=t.tags),t.excludeTags&&t.excludeTags.length>0&&(n.exclude_tags=t.excludeTags),t.strategy){case`dominant`:return{...e,target:n};case`timerange`:{let r=t.timeRange??{};return{...e,target:{...n,timerange:{start_seconds:r.startSeconds??null,end_seconds:r.endSeconds??null}}}}case`entity_id`:return{...e,target:{...n,entity_id:t.entityId}};case`magic_hint`:return{...e,target:{...n,hint:t.hint}};default:return e}}};function M(e,t,n){let r=new URL(t.replace(/^\//,``),e.endsWith(`/`)?e:`${e}/`);if(n)for(let[e,t]of Object.entries(n))t!==void 0&&r.searchParams.set(e,String(t));return r.toString()}async function N(e){let t=await e.text();if(!t)return{parsed:null,text:``};try{return{parsed:JSON.parse(t),text:t}}catch{return{parsed:t,text:t}}}function P(e,t){let o=e.headers.get(`x-request-id`)??void 0,s,c=`Request failed with status ${e.status}`,l=t;if(typeof t==`string`)c=t;else if(t&&typeof t==`object`){let e=t,n=e.error??e;if(n&&typeof n==`object`){let e=n;typeof e.message==`string`&&(c=e.message),typeof e.code==`string`&&(s=e.code),`details`in e&&(l=e.details)}}if(e.status===401||e.status===403)return new i(c,{status:e.status,requestId:o,code:s,details:l});if(e.status===422)return new a(c,{status:e.status,requestId:o,code:s,details:l});if(e.status===429){let t=new r(c,{status:e.status,requestId:o,code:s,details:l}),n=e.headers.get(`retry-after`);if(n){let e=Number(n);Number.isFinite(e)&&e>=0&&(t.retryAfterMs=e*1e3)}return t}return new n(c,{status:e.status,requestId:o,code:s,details:l})}function F(e,t){return e.retryable?t instanceof r?{retry:!0,retryAfterMs:t.retryAfterMs}:t instanceof n?{retry:t.status>=500&&t.status<=599}:t instanceof TypeError?{retry:!0}:{retry:!1}:{retry:!1}}var I=class{fetchImpl;constructor(e){this.opts=e,this.fetchImpl=e.fetch??fetch}async request(e){let t=M(this.opts.baseUrl,e.path,e.query),n=e.requestId??O(`req`),r={"Mappa-Api-Key":this.opts.apiKey,"X-Request-Id":n,...this.opts.userAgent?{"User-Agent":this.opts.userAgent}:{},...this.opts.defaultHeaders??{}};if(e.idempotencyKey&&(r[`Idempotency-Key`]=e.idempotencyKey),e.headers)for(let[t,n]of Object.entries(e.headers))n!==void 0&&(r[t]=n);let i=typeof FormData<`u`&&e.body instanceof FormData;e.body!==void 0&&!i&&(r[`Content-Type`]=`application/json`);let a=e.body===void 0?void 0:i?e.body:JSON.stringify(e.body),o=Math.max(0,this.opts.maxRetries),s=Date.now();for(let i=1;i<=1+o;i++){let c=new AbortController,l=setTimeout(()=>c.abort(D()),this.opts.timeoutMs);if(E(e.signal)){let t=e.signal;if(!t)throw clearTimeout(l),Error(`Unexpected: abort signal missing`);if(t.aborted)throw clearTimeout(l),D();t.addEventListener(`abort`,()=>c.abort(D()),{once:!0})}this.opts.telemetry?.onRequest?.({method:e.method,url:t,requestId:n});try{let l=await this.fetchImpl(t,{method:e.method,headers:r,body:a,signal:c.signal}),u=Date.now()-s,d=S(l.headers,`x-request-id`)??n;if(!l.ok){let{parsed:n}=await N(l),r=P(l,n);this.opts.telemetry?.onError?.({url:t,requestId:d,error:r});let a=F(e,r);if(i<=o+1&&a.retry&&i<=o){let e=a.retryAfterMs??C(w(i,500,4e3));await new Promise(t=>setTimeout(t,e));continue}throw r}let f=l.headers.get(`content-type`)??``,p;return p=f.includes(`application/json`)?await l.json():await l.text(),this.opts.telemetry?.onResponse?.({status:l.status,url:t,requestId:d,durationMs:u}),{data:p,status:l.status,requestId:d,headers:l.headers}}catch(r){this.opts.telemetry?.onError?.({url:t,requestId:n,error:r});let a=F(e,r);if(i<=o&&a.retry){let e=a.retryAfterMs??C(w(i,500,4e3));await new Promise(t=>setTimeout(t,e));continue}throw r}finally{clearTimeout(l)}}throw Error(`Unexpected transport exit`)}};function L(e){return typeof e==`object`&&!!e}var R=class{async verifySignature(e){let t=e.toleranceSec??300,n=z(e.headers,`mappa-signature`);if(!n)throw Error(`Missing mappa-signature header`);let r=B(n),i=Number(r.t);if(!Number.isFinite(i))throw Error(`Invalid signature timestamp`);let a=Math.floor(Date.now()/1e3);if(Math.abs(a-i)>t)throw Error(`Signature timestamp outside tolerance`);let o=`${r.t}.${e.payload}`;if(!U(await V(e.secret,o),r.v1))throw Error(`Invalid signature`);return{ok:!0}}parseEvent(e){let t=JSON.parse(e);if(!L(t))throw Error(`Invalid webhook payload: not an object`);let n=t,r=n.id,i=n.type,a=n.createdAt;if(typeof r!=`string`)throw Error(`Invalid webhook payload: id must be a string`);if(typeof i!=`string`)throw Error(`Invalid webhook payload: type must be a string`);if(typeof a!=`string`)throw Error(`Invalid webhook payload: createdAt must be a string`);return{id:r,type:i,createdAt:a,data:`data`in n?n.data:void 0}}};function z(e,t){let n=Object.keys(e).find(e=>e.toLowerCase()===t.toLowerCase()),r=n?e[n]:void 0;if(r)return Array.isArray(r)?r[0]:r}function B(e){let t={};for(let n of e.split(`,`)){let[e,r]=n.split(`=`);e&&r&&(t[e.trim()]=r.trim())}if(!t.t||!t.v1)throw Error(`Invalid signature format`);return{t:t.t,v1:t.v1}}async function V(e,t){let n=new TextEncoder,r=await crypto.subtle.importKey(`raw`,n.encode(e),{name:`HMAC`,hash:`SHA-256`},!1,[`sign`]);return H(await crypto.subtle.sign(`HMAC`,r,n.encode(t)))}function H(e){let t=new Uint8Array(e),n=``;for(let e of t)n+=e.toString(16).padStart(2,`0`);return n}function U(e,t){if(e.length!==t.length)return!1;let n=0;for(let r=0;r<e.length;r++)n|=e.charCodeAt(r)^t.charCodeAt(r);return n===0}var W=class e{files;jobs;reports;feedback;credits;entities;webhooks;health;transport;opts;constructor(e){if(!e.apiKey)throw new t(`apiKey is required`);let n=e.baseUrl??`https://api.mappa.ai`,r=e.timeoutMs??3e4,i=e.maxRetries??2;this.opts={...e,apiKey:e.apiKey,baseUrl:n,timeoutMs:r,maxRetries:i},this.transport=new I({apiKey:e.apiKey,baseUrl:n,timeoutMs:r,maxRetries:i,defaultHeaders:e.defaultHeaders,fetch:e.fetch,telemetry:e.telemetry,userAgent:e.userAgent}),this.files=new h(this.transport),this.jobs=new k(this.transport),this.reports=new j(this.transport,this.jobs,this.files,this.opts.fetch??fetch),this.feedback=new m(this.transport),this.credits=new c(this.transport),this.entities=new p(this.transport),this.webhooks=new R,this.health=new b(this.transport)}withOptions(t){return new e({...this.opts,...t,apiKey:t.apiKey??this.opts.apiKey})}close(){}};function G(e){return e.output.type===`markdown`}function K(e){return e.output.type===`json`}function q(e){return e.output.type===`pdf`}function J(e){return e.output.type===`url`}function Y(e){return e.entity!==void 0&&e.entity!==null}function X(e){return e instanceof t}exports.ApiError=n,exports.AuthError=i,exports.JobCanceledError=s,exports.JobFailedError=o,exports.Mappa=W,exports.MappaError=t,exports.RateLimitError=r,exports.ValidationError=a,exports.hasEntity=Y,exports.isJsonReport=K,exports.isMappaError=X,exports.isMarkdownReport=G,exports.isPdfReport=q,exports.isUrlReport=J;