@mappa-ai/mappa-node 1.2.0 → 1.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/dist/index.cjs CHANGED
@@ -1,5 +1,1732 @@
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(e){return!!e&&typeof e.aborted==`boolean`}function E(){let e=Error(`The operation was aborted`);return e.name=`AbortError`,e}function D(e=`req`){return`${e}_${x()}`}var O=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=new AbortController,i=setTimeout(()=>r.abort(),n);if(t?.signal){if(t.signal.aborted)throw clearTimeout(i),E();t.signal.addEventListener(`abort`,()=>r.abort(),{once:!0})}try{for await(let n of this.stream(e,{signal:r.signal,onEvent:t?.onEvent}))if(n.type===`terminal`){let t=n.job;if(t.status===`succeeded`)return t;if(t.status===`failed`)throw new o(e,t.error?.message??`Job failed`,{requestId:t.requestId,code:t.error?.code,cause:t.error});if(t.status===`canceled`)throw new s(e,`Job canceled`,{requestId:t.requestId,cause:t.error})}throw new o(e,`Timed out waiting for job ${e} after ${n}ms`,{cause:{jobId:e,timeoutMs:n}})}finally{clearTimeout(i)}}async*stream(e,n){let r,i=0;for(;i<3;)try{let a=this.transport.streamSSE(`/v1/jobs/${encodeURIComponent(e)}/stream`,{signal:n?.signal,lastEventId:r});for await(let e of a){if(r=e.id,e.event===`error`){let n=e.data;throw new t(n.error?.message??`Unknown SSE error`,{code:n.error?.code})}if(e.event===`heartbeat`)continue;let a=this.mapSSEToJobEvent(e);if(a&&(n?.onEvent?.(a),yield a,e.event===`terminal`))return;i=0}i++,i<3&&await this.backoff(i)}catch(e){if(n?.signal?.aborted||(i++,i>=3))throw e;await this.backoff(i)}throw new t(`Failed to get status for job ${e} after 3 retries`)}mapSSEToJobEvent(e){let t=e.data;switch(e.event){case`status`:return{type:`status`,job:t.job};case`stage`:return{type:`stage`,stage:t.stage,progress:t.progress,job:t.job};case`terminal`:return{type:`terminal`,job:t.job};default:return{type:`status`,job:t.job}}}async backoff(e){let t=Math.min(1e3*2**e,1e4),n=t*.5*Math.random();await new Promise(e=>setTimeout(e,t+n))}};function k(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 A=class{constructor(e,t,n,r){this.transport=e,this.jobs=t,this.files=n,this.fetchImpl=r}async createJob(e){k(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 D(`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 j(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 M(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 N(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 P(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 F=class{fetchImpl;constructor(e){this.opts=e,this.fetchImpl=e.fetch??fetch}async*streamSSE(e,n){let r=j(this.opts.baseUrl,e),i=D(`req`),a={Accept:`text/event-stream`,"Cache-Control":`no-cache`,"Mappa-Api-Key":this.opts.apiKey,"X-Request-Id":i,...this.opts.userAgent?{"User-Agent":this.opts.userAgent}:{},...this.opts.defaultHeaders??{}};n?.lastEventId&&(a[`Last-Event-ID`]=n.lastEventId);let o=new AbortController,s=setTimeout(()=>o.abort(E()),this.opts.timeoutMs);if(T(n?.signal)){let e=n?.signal;if(e?.aborted)throw clearTimeout(s),E();e?.addEventListener(`abort`,()=>o.abort(E()),{once:!0})}this.opts.telemetry?.onRequest?.({method:`GET`,url:r,requestId:i});let c;try{c=await this.fetchImpl(r,{method:`GET`,headers:a,signal:o.signal})}catch(e){throw clearTimeout(s),this.opts.telemetry?.onError?.({url:r,requestId:i,error:e}),e}if(!c.ok){clearTimeout(s);let{parsed:e}=await M(c),t=N(c,e);throw this.opts.telemetry?.onError?.({url:r,requestId:i,error:t}),t}if(!c.body)throw clearTimeout(s),new t(`SSE response has no body`);try{yield*this.parseSSEStream(c.body)}finally{clearTimeout(s)}}async*parseSSEStream(e){let t=new TextDecoder,n=e.getReader(),r=``;try{for(;;){let{done:e,value:i}=await n.read();if(e)break;r+=t.decode(i,{stream:!0});let a=r.split(`
1
+ let _paralleldrive_cuid2 = require("@paralleldrive/cuid2");
2
2
 
3
- `);r=a.pop()??``;for(let e of a){if(!e.trim())continue;let t=this.parseSSEEvent(e);t&&(yield t)}}if(r.trim()){let e=this.parseSSEEvent(r);e&&(yield e)}}finally{n.releaseLock()}}parseSSEEvent(e){let t=e.split(`
4
- `),n,r=`message`,i=``;for(let e of t)e.startsWith(`id:`)?n=e.slice(3).trim():e.startsWith(`event:`)?r=e.slice(6).trim():e.startsWith(`data:`)&&(i&&(i+=`
5
- `),i+=e.slice(5).trim());if(!i)return null;let a;try{a=JSON.parse(i)}catch{a=i}return{id:n,event:r,data:a}}async request(e){let t=j(this.opts.baseUrl,e.path,e.query),n=e.requestId??D(`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(E()),this.opts.timeoutMs);if(T(e.signal)){let t=e.signal;if(!t)throw clearTimeout(l),Error(`Unexpected: abort signal missing`);if(t.aborted)throw clearTimeout(l),E();t.addEventListener(`abort`,()=>c.abort(E()),{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 M(l),r=N(l,n);this.opts.telemetry?.onError?.({url:t,requestId:d,error:r});let a=P(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=P(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 I(e){return typeof e==`object`&&!!e}var L=class{async verifySignature(e){let t=e.toleranceSec??300,n=R(e.headers,`mappa-signature`);if(!n)throw Error(`Missing mappa-signature header`);let r=z(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(!H(await B(e.secret,o),r.v1))throw Error(`Invalid signature`);return{ok:!0}}parseEvent(e){let t=JSON.parse(e);if(!I(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 R(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 z(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 B(e,t){let n=new TextEncoder,r=await crypto.subtle.importKey(`raw`,n.encode(e),{name:`HMAC`,hash:`SHA-256`},!1,[`sign`]);return V(await crypto.subtle.sign(`HMAC`,r,n.encode(t)))}function V(e){let t=new Uint8Array(e),n=``;for(let e of t)n+=e.toString(16).padStart(2,`0`);return n}function H(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 U=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 F({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 O(this.transport),this.reports=new A(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 L,this.health=new b(this.transport)}withOptions(t){return new e({...this.opts,...t,apiKey:t.apiKey??this.opts.apiKey})}close(){}};function W(e){return e.output.type===`markdown`}function G(e){return e.output.type===`json`}function K(e){return e.output.type===`pdf`}function q(e){return e.output.type===`url`}function J(e){return e.entity!==void 0&&e.entity!==null}function Y(e){return e instanceof t}exports.ApiError=n,exports.AuthError=i,exports.JobCanceledError=s,exports.JobFailedError=o,exports.Mappa=U,exports.MappaError=t,exports.RateLimitError=r,exports.ValidationError=a,exports.hasEntity=J,exports.isJsonReport=G,exports.isMappaError=Y,exports.isMarkdownReport=W,exports.isPdfReport=K,exports.isUrlReport=q;
3
+ //#region src/errors.ts
4
+ /**
5
+ * Symbol for custom Node.js inspect formatting.
6
+ * Ensures errors display nicely in console.log, REPL, and debuggers.
7
+ */
8
+ const customInspect = Symbol.for("nodejs.util.inspect.custom");
9
+ /**
10
+ * Formats error details for display.
11
+ */
12
+ function formatDetails(details, indent = " ") {
13
+ if (details === void 0 || details === null) return "";
14
+ try {
15
+ return JSON.stringify(details, null, 2).split("\n").join(`\n${indent}`);
16
+ } catch {
17
+ return String(details);
18
+ }
19
+ }
20
+ /**
21
+ * Base error type for all SDK-raised errors.
22
+ *
23
+ * When available, {@link MappaError.requestId} can be used to correlate a failure
24
+ * with server logs/support.
25
+ */
26
+ var MappaError = class extends Error {
27
+ name = "MappaError";
28
+ requestId;
29
+ code;
30
+ constructor(message, opts) {
31
+ super(message);
32
+ this.requestId = opts?.requestId;
33
+ this.code = opts?.code;
34
+ this.cause = opts?.cause;
35
+ }
36
+ toString() {
37
+ const lines = [`${this.name}: ${this.message}`];
38
+ if (this.code) lines.push(` Code: ${this.code}`);
39
+ if (this.requestId) lines.push(` Request ID: ${this.requestId}`);
40
+ return lines.join("\n");
41
+ }
42
+ [customInspect]() {
43
+ return this.toString();
44
+ }
45
+ };
46
+ /**
47
+ * Error returned when the API responds with a non-2xx status.
48
+ */
49
+ var ApiError = class extends MappaError {
50
+ name = "ApiError";
51
+ status;
52
+ details;
53
+ constructor(message, opts) {
54
+ super(message, {
55
+ requestId: opts.requestId,
56
+ code: opts.code
57
+ });
58
+ this.status = opts.status;
59
+ this.details = opts.details;
60
+ }
61
+ toString() {
62
+ const lines = [`${this.name}: ${this.message}`];
63
+ lines.push(` Status: ${this.status}`);
64
+ if (this.code) lines.push(` Code: ${this.code}`);
65
+ if (this.requestId) lines.push(` Request ID: ${this.requestId}`);
66
+ if (this.details !== void 0 && this.details !== null) lines.push(` Details: ${formatDetails(this.details)}`);
67
+ return lines.join("\n");
68
+ }
69
+ };
70
+ /**
71
+ * Error returned for HTTP 429 responses.
72
+ *
73
+ * If provided by the server, {@link RateLimitError.retryAfterMs} indicates when
74
+ * it is safe to retry.
75
+ */
76
+ var RateLimitError = class extends ApiError {
77
+ name = "RateLimitError";
78
+ retryAfterMs;
79
+ toString() {
80
+ const lines = [`${this.name}: ${this.message}`];
81
+ lines.push(` Status: ${this.status}`);
82
+ if (this.retryAfterMs !== void 0) lines.push(` Retry After: ${this.retryAfterMs}ms`);
83
+ if (this.code) lines.push(` Code: ${this.code}`);
84
+ if (this.requestId) lines.push(` Request ID: ${this.requestId}`);
85
+ return lines.join("\n");
86
+ }
87
+ };
88
+ /**
89
+ * Error returned for authentication/authorization failures (typically 401/403).
90
+ */
91
+ var AuthError = class extends ApiError {
92
+ name = "AuthError";
93
+ };
94
+ /**
95
+ * Error returned when the server rejects a request as invalid (typically 422).
96
+ */
97
+ var ValidationError = class extends ApiError {
98
+ name = "ValidationError";
99
+ };
100
+ /**
101
+ * Error returned when the account lacks sufficient credits (HTTP 402).
102
+ *
103
+ * Use {@link InsufficientCreditsError.required} and {@link InsufficientCreditsError.available}
104
+ * to inform users how many credits are needed.
105
+ *
106
+ * @example
107
+ * ```typescript
108
+ * try {
109
+ * await mappa.reports.createJob({ ... });
110
+ * } catch (err) {
111
+ * if (err instanceof InsufficientCreditsError) {
112
+ * console.log(`Need ${err.required} credits, have ${err.available}`);
113
+ * }
114
+ * }
115
+ * ```
116
+ */
117
+ var InsufficientCreditsError = class extends ApiError {
118
+ name = "InsufficientCreditsError";
119
+ /** Credits required for the operation */
120
+ required;
121
+ /** Credits currently available */
122
+ available;
123
+ constructor(message, opts) {
124
+ super(message, opts);
125
+ this.required = opts.details?.required ?? 0;
126
+ this.available = opts.details?.available ?? 0;
127
+ }
128
+ toString() {
129
+ const lines = [`${this.name}: ${this.message}`];
130
+ lines.push(` Status: ${this.status}`);
131
+ lines.push(` Required: ${this.required} credits`);
132
+ lines.push(` Available: ${this.available} credits`);
133
+ if (this.code) lines.push(` Code: ${this.code}`);
134
+ if (this.requestId) lines.push(` Request ID: ${this.requestId}`);
135
+ return lines.join("\n");
136
+ }
137
+ };
138
+ /**
139
+ * Error thrown by polling helpers when a job reaches the "failed" terminal state.
140
+ */
141
+ var JobFailedError = class extends MappaError {
142
+ name = "JobFailedError";
143
+ jobId;
144
+ constructor(jobId, message, opts) {
145
+ super(message, opts);
146
+ this.jobId = jobId;
147
+ }
148
+ toString() {
149
+ const lines = [`${this.name}: ${this.message}`];
150
+ lines.push(` Job ID: ${this.jobId}`);
151
+ if (this.code) lines.push(` Code: ${this.code}`);
152
+ if (this.requestId) lines.push(` Request ID: ${this.requestId}`);
153
+ return lines.join("\n");
154
+ }
155
+ };
156
+ /**
157
+ * Error thrown by polling helpers when a job reaches the "canceled" terminal state.
158
+ */
159
+ var JobCanceledError = class extends MappaError {
160
+ name = "JobCanceledError";
161
+ jobId;
162
+ constructor(jobId, message, opts) {
163
+ super(message, opts);
164
+ this.jobId = jobId;
165
+ }
166
+ toString() {
167
+ const lines = [`${this.name}: ${this.message}`];
168
+ lines.push(` Job ID: ${this.jobId}`);
169
+ if (this.requestId) lines.push(` Request ID: ${this.requestId}`);
170
+ return lines.join("\n");
171
+ }
172
+ };
173
+
174
+ //#endregion
175
+ //#region src/resources/credits.ts
176
+ /**
177
+ * Credits API resource.
178
+ *
179
+ * Provides methods to manage and query credit balances, transaction history,
180
+ * and job-specific credit usage.
181
+ */
182
+ var CreditsResource = class {
183
+ constructor(transport) {
184
+ this.transport = transport;
185
+ }
186
+ /**
187
+ * Get the current credit balance for your team.
188
+ *
189
+ * @example
190
+ * const balance = await mappa.credits.getBalance();
191
+ * console.log(`Available: ${balance.available} credits`);
192
+ */
193
+ async getBalance(opts) {
194
+ return (await this.transport.request({
195
+ method: "GET",
196
+ path: "/v1/credits/balance",
197
+ requestId: opts?.requestId,
198
+ signal: opts?.signal,
199
+ retryable: true
200
+ })).data;
201
+ }
202
+ /**
203
+ * List credit transactions with offset pagination.
204
+ *
205
+ * @example
206
+ * const { transactions, pagination } = await mappa.credits.listTransactions({ limit: 25 });
207
+ * console.log(`Showing ${transactions.length} of ${pagination.total}`);
208
+ */
209
+ async listTransactions(opts) {
210
+ const query = {};
211
+ if (opts?.limit !== void 0) query.limit = String(opts.limit);
212
+ if (opts?.offset !== void 0) query.offset = String(opts.offset);
213
+ return (await this.transport.request({
214
+ method: "GET",
215
+ path: "/v1/credits/transactions",
216
+ query,
217
+ requestId: opts?.requestId,
218
+ signal: opts?.signal,
219
+ retryable: true
220
+ })).data;
221
+ }
222
+ /**
223
+ * Iterate over all transactions, automatically handling pagination.
224
+ *
225
+ * @example
226
+ * for await (const tx of mappa.credits.listAllTransactions()) {
227
+ * console.log(`${tx.type}: ${tx.amount}`);
228
+ * }
229
+ */
230
+ async *listAllTransactions(opts) {
231
+ let offset = 0;
232
+ const limit = opts?.limit ?? 50;
233
+ while (true) {
234
+ const page = await this.listTransactions({
235
+ ...opts,
236
+ limit,
237
+ offset
238
+ });
239
+ for (const tx of page.transactions) yield tx;
240
+ offset += page.transactions.length;
241
+ if (offset >= page.pagination.total) break;
242
+ }
243
+ }
244
+ /**
245
+ * Get credit usage details for a completed job.
246
+ *
247
+ * @example
248
+ * const usage = await mappa.credits.getJobUsage("job_xyz");
249
+ * console.log(`Net credits used: ${usage.creditsNetUsed}`);
250
+ */
251
+ async getJobUsage(jobId, opts) {
252
+ if (!jobId) throw new MappaError("jobId is required");
253
+ return (await this.transport.request({
254
+ method: "GET",
255
+ path: `/v1/credits/usage/${encodeURIComponent(jobId)}`,
256
+ requestId: opts?.requestId,
257
+ signal: opts?.signal,
258
+ retryable: true
259
+ })).data;
260
+ }
261
+ /**
262
+ * Check if the team has enough available credits for an operation.
263
+ *
264
+ * @example
265
+ * if (await mappa.credits.hasEnough(100)) {
266
+ * await mappa.reports.createJob(...);
267
+ * }
268
+ */
269
+ async hasEnough(credits, opts) {
270
+ return (await this.getBalance(opts)).available >= credits;
271
+ }
272
+ /**
273
+ * Get the number of available credits (balance - reserved).
274
+ *
275
+ * @example
276
+ * const available = await mappa.credits.getAvailable();
277
+ * console.log(`You can spend ${available} credits`);
278
+ */
279
+ async getAvailable(opts) {
280
+ return (await this.getBalance(opts)).available;
281
+ }
282
+ };
283
+
284
+ //#endregion
285
+ //#region src/resources/entities.ts
286
+ /**
287
+ * Tag validation regex: 1-64 chars, alphanumeric with underscores and hyphens.
288
+ */
289
+ const TAG_REGEX = /^[a-zA-Z0-9_-]{1,64}$/;
290
+ /**
291
+ * Maximum number of tags per request.
292
+ */
293
+ const MAX_TAGS_PER_REQUEST = 10;
294
+ /**
295
+ * Validates a single tag.
296
+ * @throws {MappaError} if validation fails
297
+ */
298
+ function validateTag(tag) {
299
+ if (typeof tag !== "string") throw new MappaError("Tags must be strings");
300
+ if (!TAG_REGEX.test(tag)) throw new MappaError(`Invalid tag "${tag}": must be 1-64 characters, alphanumeric with underscores and hyphens only`);
301
+ }
302
+ /**
303
+ * Validates an array of tags.
304
+ * @throws {MappaError} if validation fails
305
+ */
306
+ function validateTags(tags) {
307
+ if (!Array.isArray(tags)) throw new MappaError("tags must be an array");
308
+ if (tags.length > MAX_TAGS_PER_REQUEST) throw new MappaError(`Too many tags: maximum ${MAX_TAGS_PER_REQUEST} per request`);
309
+ for (const tag of tags) validateTag(tag);
310
+ }
311
+ /**
312
+ * Entities API resource.
313
+ *
314
+ * Responsibilities:
315
+ * - List entities with optional tag filtering (`GET /v1/entities`)
316
+ * - Get single entity details (`GET /v1/entities/:entityId`)
317
+ * - Add tags to entities (`POST /v1/entities/:entityId/tags`)
318
+ * - Remove tags from entities (`DELETE /v1/entities/:entityId/tags`)
319
+ * - Replace all entity tags (`PUT /v1/entities/:entityId/tags`)
320
+ *
321
+ * Entities represent analyzed speakers identified by voice fingerprints.
322
+ * Tags allow you to label entities (e.g., "interviewer", "sales-rep") for easier
323
+ * filtering and identification across multiple reports.
324
+ */
325
+ var EntitiesResource = class {
326
+ constructor(transport) {
327
+ this.transport = transport;
328
+ }
329
+ /**
330
+ * Get a single entity by ID.
331
+ *
332
+ * Returns entity metadata including tags, creation time, and usage statistics.
333
+ *
334
+ * @param entityId - The entity ID to retrieve
335
+ * @param opts - Optional request options (requestId, signal)
336
+ * @returns Entity details with tags and metadata
337
+ *
338
+ * @example
339
+ * ```typescript
340
+ * const entity = await mappa.entities.get("entity_abc123");
341
+ * console.log(entity.tags); // ["interviewer", "john"]
342
+ * ```
343
+ */
344
+ async get(entityId, opts) {
345
+ if (!entityId || typeof entityId !== "string") throw new MappaError("entityId must be a non-empty string");
346
+ return (await this.transport.request({
347
+ method: "GET",
348
+ path: `/v1/entities/${encodeURIComponent(entityId)}`,
349
+ requestId: opts?.requestId,
350
+ signal: opts?.signal,
351
+ retryable: true
352
+ })).data;
353
+ }
354
+ /**
355
+ * List entities with optional tag filtering.
356
+ *
357
+ * Supports cursor-based pagination. Use the returned `cursor` for fetching
358
+ * subsequent pages, or use {@link listAll} for automatic pagination.
359
+ *
360
+ * When `tags` is provided, only entities with ALL specified tags are returned (AND logic).
361
+ *
362
+ * @param opts - List options: tags filter, cursor, limit
363
+ * @returns Paginated list of entities
364
+ *
365
+ * @example
366
+ * ```typescript
367
+ * // List all entities
368
+ * const page1 = await mappa.entities.list({ limit: 20 });
369
+ *
370
+ * // Filter by tags (must have both "interviewer" AND "sales")
371
+ * const filtered = await mappa.entities.list({
372
+ * tags: ["interviewer", "sales"],
373
+ * limit: 50
374
+ * });
375
+ *
376
+ * // Pagination
377
+ * const page2 = await mappa.entities.list({
378
+ * cursor: page1.cursor,
379
+ * limit: 20
380
+ * });
381
+ * ```
382
+ */
383
+ async list(opts) {
384
+ const query = {};
385
+ if (opts?.tags) {
386
+ validateTags(opts.tags);
387
+ query.tags = opts.tags.join(",");
388
+ }
389
+ if (opts?.cursor) query.cursor = opts.cursor;
390
+ if (opts?.limit !== void 0) query.limit = String(opts.limit);
391
+ return (await this.transport.request({
392
+ method: "GET",
393
+ path: "/v1/entities",
394
+ query,
395
+ requestId: opts?.requestId,
396
+ signal: opts?.signal,
397
+ retryable: true
398
+ })).data;
399
+ }
400
+ /**
401
+ * Async iterator that automatically paginates through all entities.
402
+ *
403
+ * Useful for processing large entity sets without manual pagination management.
404
+ *
405
+ * @param opts - List options: tags filter, limit per page
406
+ * @yields Individual entities
407
+ *
408
+ * @example
409
+ * ```typescript
410
+ * // Process all entities with "interviewer" tag
411
+ * for await (const entity of mappa.entities.listAll({ tags: ["interviewer"] })) {
412
+ * console.log(`${entity.id}: ${entity.tags.join(", ")}`);
413
+ * }
414
+ * ```
415
+ */
416
+ async *listAll(opts) {
417
+ let cursor;
418
+ let hasMore = true;
419
+ while (hasMore) {
420
+ const page = await this.list({
421
+ ...opts,
422
+ cursor
423
+ });
424
+ for (const entity of page.entities) yield entity;
425
+ cursor = page.cursor;
426
+ hasMore = page.hasMore;
427
+ }
428
+ }
429
+ /**
430
+ * Get all entities with a specific tag.
431
+ *
432
+ * Convenience wrapper around {@link list} for single-tag filtering.
433
+ *
434
+ * @param tag - The tag to filter by
435
+ * @param opts - Optional pagination and request options
436
+ * @returns Paginated list of entities with the specified tag
437
+ *
438
+ * @example
439
+ * ```typescript
440
+ * const interviewers = await mappa.entities.getByTag("interviewer");
441
+ * ```
442
+ */
443
+ async getByTag(tag, opts) {
444
+ validateTag(tag);
445
+ return this.list({
446
+ ...opts,
447
+ tags: [tag]
448
+ });
449
+ }
450
+ /**
451
+ * Add tags to an entity.
452
+ *
453
+ * Idempotent: existing tags are preserved, duplicates are ignored.
454
+ *
455
+ * @param entityId - The entity ID to tag
456
+ * @param tags - Array of tags to add (1-10 tags, each 1-64 chars)
457
+ * @param opts - Optional request options
458
+ * @returns Updated tags for the entity
459
+ *
460
+ * @example
461
+ * ```typescript
462
+ * await mappa.entities.addTags("entity_abc123", ["interviewer", "john"]);
463
+ * ```
464
+ */
465
+ async addTags(entityId, tags, opts) {
466
+ if (!entityId || typeof entityId !== "string") throw new MappaError("entityId must be a non-empty string");
467
+ if (tags.length === 0) throw new MappaError("At least one tag is required");
468
+ validateTags(tags);
469
+ return (await this.transport.request({
470
+ method: "POST",
471
+ path: `/v1/entities/${encodeURIComponent(entityId)}/tags`,
472
+ body: { tags },
473
+ requestId: opts?.requestId,
474
+ signal: opts?.signal,
475
+ retryable: true
476
+ })).data;
477
+ }
478
+ /**
479
+ * Remove tags from an entity.
480
+ *
481
+ * Idempotent: missing tags are silently ignored.
482
+ *
483
+ * @param entityId - The entity ID to update
484
+ * @param tags - Array of tags to remove
485
+ * @param opts - Optional request options
486
+ * @returns Updated tags for the entity
487
+ *
488
+ * @example
489
+ * ```typescript
490
+ * await mappa.entities.removeTags("entity_abc123", ["interviewer"]);
491
+ * ```
492
+ */
493
+ async removeTags(entityId, tags, opts) {
494
+ if (!entityId || typeof entityId !== "string") throw new MappaError("entityId must be a non-empty string");
495
+ if (tags.length === 0) throw new MappaError("At least one tag is required");
496
+ validateTags(tags);
497
+ return (await this.transport.request({
498
+ method: "DELETE",
499
+ path: `/v1/entities/${encodeURIComponent(entityId)}/tags`,
500
+ body: { tags },
501
+ requestId: opts?.requestId,
502
+ signal: opts?.signal,
503
+ retryable: true
504
+ })).data;
505
+ }
506
+ /**
507
+ * Replace all tags on an entity.
508
+ *
509
+ * Sets the complete tag list, removing any tags not in the provided array.
510
+ * Pass an empty array to remove all tags.
511
+ *
512
+ * @param entityId - The entity ID to update
513
+ * @param tags - New complete tag list (0-10 tags)
514
+ * @param opts - Optional request options
515
+ * @returns Updated tags for the entity
516
+ *
517
+ * @example
518
+ * ```typescript
519
+ * // Replace all tags
520
+ * await mappa.entities.setTags("entity_abc123", ["sales-rep", "john"]);
521
+ *
522
+ * // Remove all tags
523
+ * await mappa.entities.setTags("entity_abc123", []);
524
+ * ```
525
+ */
526
+ async setTags(entityId, tags, opts) {
527
+ if (!entityId || typeof entityId !== "string") throw new MappaError("entityId must be a non-empty string");
528
+ validateTags(tags);
529
+ return (await this.transport.request({
530
+ method: "PUT",
531
+ path: `/v1/entities/${encodeURIComponent(entityId)}/tags`,
532
+ body: { tags },
533
+ requestId: opts?.requestId,
534
+ signal: opts?.signal,
535
+ retryable: true
536
+ })).data;
537
+ }
538
+ };
539
+
540
+ //#endregion
541
+ //#region src/resources/feedback.ts
542
+ var FeedbackResource = class {
543
+ constructor(transport) {
544
+ this.transport = transport;
545
+ }
546
+ /**
547
+ * Create feedback for a report or job. Provide exactly one of `reportId` or `jobId`.
548
+ */
549
+ async create(req) {
550
+ if (!!req.reportId === !!req.jobId) throw new MappaError("Provide exactly one of reportId or jobId");
551
+ return (await this.transport.request({
552
+ method: "POST",
553
+ path: "/v1/feedback",
554
+ body: req,
555
+ idempotencyKey: req.idempotencyKey,
556
+ requestId: req.requestId,
557
+ signal: req.signal,
558
+ retryable: true
559
+ })).data;
560
+ }
561
+ };
562
+
563
+ //#endregion
564
+ //#region src/resources/files.ts
565
+ /**
566
+ * Uses multipart/form-data for uploads.
567
+ *
568
+ * If you need resumable uploads, add a dedicated resumable protocol.
569
+ */
570
+ var FilesResource = class {
571
+ constructor(transport) {
572
+ this.transport = transport;
573
+ }
574
+ async upload(req) {
575
+ if (typeof FormData === "undefined") throw new MappaError("FormData is not available in this runtime; cannot perform multipart upload");
576
+ const derivedContentType = inferContentType(req.file, req.filename);
577
+ const contentType = req.contentType ?? derivedContentType;
578
+ if (!contentType) throw new MappaError("contentType is required when it cannot be inferred from file.type or filename");
579
+ const filename = req.filename ?? inferFilename(req.file) ?? "upload";
580
+ const filePart = await toFormDataPart(req.file, contentType);
581
+ const form = new FormData();
582
+ form.append("file", filePart, filename);
583
+ form.append("contentType", contentType);
584
+ if (req.filename) form.append("filename", req.filename);
585
+ return (await this.transport.request({
586
+ method: "POST",
587
+ path: "/v1/files",
588
+ body: form,
589
+ idempotencyKey: req.idempotencyKey,
590
+ requestId: req.requestId,
591
+ signal: req.signal,
592
+ retryable: true
593
+ })).data;
594
+ }
595
+ /**
596
+ * Retrieve metadata for a single uploaded file.
597
+ *
598
+ * @example
599
+ * const file = await mappa.files.get("media_abc123");
600
+ * console.log(file.processingStatus); // "COMPLETED"
601
+ */
602
+ async get(mediaId, opts) {
603
+ if (!mediaId) throw new MappaError("mediaId is required");
604
+ return (await this.transport.request({
605
+ method: "GET",
606
+ path: `/v1/files/${encodeURIComponent(mediaId)}`,
607
+ requestId: opts?.requestId,
608
+ signal: opts?.signal,
609
+ retryable: true
610
+ })).data;
611
+ }
612
+ /**
613
+ * List uploaded files with cursor pagination.
614
+ *
615
+ * @example
616
+ * const page1 = await mappa.files.list({ limit: 10 });
617
+ * if (page1.hasMore) {
618
+ * const page2 = await mappa.files.list({ limit: 10, cursor: page1.cursor });
619
+ * }
620
+ */
621
+ async list(opts) {
622
+ const query = {};
623
+ if (opts?.limit !== void 0) query.limit = String(opts.limit);
624
+ if (opts?.cursor) query.cursor = opts.cursor;
625
+ if (opts?.includeDeleted !== void 0) query.includeDeleted = String(opts.includeDeleted);
626
+ return (await this.transport.request({
627
+ method: "GET",
628
+ path: "/v1/files",
629
+ query,
630
+ requestId: opts?.requestId,
631
+ signal: opts?.signal,
632
+ retryable: true
633
+ })).data;
634
+ }
635
+ /**
636
+ * Iterate over all files, automatically handling pagination.
637
+ *
638
+ * @example
639
+ * for await (const file of mappa.files.listAll()) {
640
+ * console.log(file.mediaId);
641
+ * }
642
+ *
643
+ * // Or collect all
644
+ * const allFiles = [];
645
+ * for await (const file of mappa.files.listAll({ limit: 50 })) {
646
+ * allFiles.push(file);
647
+ * }
648
+ */
649
+ async *listAll(opts) {
650
+ let cursor;
651
+ let hasMore = true;
652
+ while (hasMore) {
653
+ const page = await this.list({
654
+ ...opts,
655
+ cursor
656
+ });
657
+ for (const file of page.files) yield file;
658
+ cursor = page.cursor;
659
+ hasMore = page.hasMore;
660
+ }
661
+ }
662
+ /**
663
+ * Lock or unlock a file's retention to prevent/allow automatic deletion.
664
+ *
665
+ * @example
666
+ * // Prevent automatic deletion
667
+ * await mappa.files.setRetentionLock("media_abc", true);
668
+ *
669
+ * // Allow automatic deletion
670
+ * await mappa.files.setRetentionLock("media_abc", false);
671
+ */
672
+ async setRetentionLock(mediaId, locked, opts) {
673
+ if (!mediaId) throw new MappaError("mediaId is required");
674
+ return (await this.transport.request({
675
+ method: "PATCH",
676
+ path: `/v1/files/${encodeURIComponent(mediaId)}/retention`,
677
+ body: { lock: locked },
678
+ requestId: opts?.requestId,
679
+ signal: opts?.signal,
680
+ retryable: true
681
+ })).data;
682
+ }
683
+ async delete(mediaId, opts) {
684
+ if (!mediaId) throw new MappaError("mediaId is required");
685
+ return (await this.transport.request({
686
+ method: "DELETE",
687
+ path: `/v1/files/${encodeURIComponent(mediaId)}`,
688
+ idempotencyKey: opts?.idempotencyKey,
689
+ requestId: opts?.requestId,
690
+ signal: opts?.signal,
691
+ retryable: true
692
+ })).data;
693
+ }
694
+ };
695
+ function inferContentType(file, filename) {
696
+ if (typeof Blob !== "undefined" && file instanceof Blob) {
697
+ if (file.type) return file.type;
698
+ }
699
+ if (filename) return contentTypeFromFilename(filename);
700
+ }
701
+ function inferFilename(file) {
702
+ if (typeof Blob !== "undefined" && file instanceof Blob) {
703
+ const anyBlob = file;
704
+ if (typeof anyBlob.name === "string" && anyBlob.name) return anyBlob.name;
705
+ }
706
+ }
707
+ function contentTypeFromFilename(filename) {
708
+ const i = filename.lastIndexOf(".");
709
+ if (i < 0) return void 0;
710
+ switch (filename.slice(i + 1).toLowerCase()) {
711
+ case "mp4": return "video/mp4";
712
+ case "mov": return "video/quicktime";
713
+ case "webm": return "video/webm";
714
+ case "mp3": return "audio/mpeg";
715
+ case "wav": return "audio/wav";
716
+ case "m4a": return "audio/mp4";
717
+ case "png": return "image/png";
718
+ case "jpg":
719
+ case "jpeg": return "image/jpeg";
720
+ case "gif": return "image/gif";
721
+ case "webp": return "image/webp";
722
+ case "pdf": return "application/pdf";
723
+ case "json": return "application/json";
724
+ case "txt": return "text/plain";
725
+ default: return;
726
+ }
727
+ }
728
+ async function toFormDataPart(file, contentType) {
729
+ if (typeof Blob !== "undefined" && file instanceof Blob) {
730
+ if (file.type === contentType) return file;
731
+ const slicer = file;
732
+ if (typeof slicer.slice === "function") return slicer.slice(0, file.size, contentType);
733
+ return file;
734
+ }
735
+ if (file instanceof ArrayBuffer) return new Blob([file], { type: contentType });
736
+ if (file instanceof Uint8Array) return new Blob([file], { type: contentType });
737
+ if (typeof ReadableStream !== "undefined" && file instanceof ReadableStream) {
738
+ if (typeof Response === "undefined") throw new MappaError("ReadableStream upload requires Response to convert stream to Blob");
739
+ const blob = await new Response(file).blob();
740
+ return blob.slice(0, blob.size, contentType);
741
+ }
742
+ throw new MappaError("Unsupported file type for upload()");
743
+ }
744
+
745
+ //#endregion
746
+ //#region src/resources/health.ts
747
+ var HealthResource = class {
748
+ constructor(transport) {
749
+ this.transport = transport;
750
+ }
751
+ async ping() {
752
+ return (await this.transport.request({
753
+ method: "GET",
754
+ path: "/v1/health/ping",
755
+ retryable: true
756
+ })).data;
757
+ }
758
+ };
759
+
760
+ //#endregion
761
+ //#region src/utils/index.ts
762
+ const createId = (0, _paralleldrive_cuid2.init)({ length: 32 });
763
+ function getHeader(headers, name) {
764
+ const v = headers.get(name);
765
+ return v === null ? void 0 : v;
766
+ }
767
+ function jitter(ms) {
768
+ const r = .8 + Math.random() * .4;
769
+ return Math.floor(ms * r);
770
+ }
771
+ function backoffMs(attempt, baseMs, maxMs) {
772
+ const ms = baseMs * 2 ** Math.max(0, attempt - 1);
773
+ return Math.min(ms, maxMs);
774
+ }
775
+ function hasAbortSignal(signal) {
776
+ return !!signal && typeof signal.aborted === "boolean";
777
+ }
778
+ function makeAbortError() {
779
+ const e = /* @__PURE__ */ new Error("The operation was aborted");
780
+ e.name = "AbortError";
781
+ return e;
782
+ }
783
+ function randomId(prefix = "req") {
784
+ return `${prefix}_${createId()}`;
785
+ }
786
+
787
+ //#endregion
788
+ //#region src/resources/jobs.ts
789
+ var JobsResource = class {
790
+ constructor(transport) {
791
+ this.transport = transport;
792
+ }
793
+ async get(jobId, opts) {
794
+ return (await this.transport.request({
795
+ method: "GET",
796
+ path: `/v1/jobs/${encodeURIComponent(jobId)}`,
797
+ requestId: opts?.requestId,
798
+ signal: opts?.signal,
799
+ retryable: true
800
+ })).data;
801
+ }
802
+ async cancel(jobId, opts) {
803
+ return (await this.transport.request({
804
+ method: "POST",
805
+ path: `/v1/jobs/${encodeURIComponent(jobId)}/cancel`,
806
+ idempotencyKey: opts?.idempotencyKey,
807
+ requestId: opts?.requestId,
808
+ signal: opts?.signal,
809
+ retryable: true
810
+ })).data;
811
+ }
812
+ /**
813
+ * Wait for a job to reach a terminal state.
814
+ *
815
+ * Uses SSE streaming internally for efficient real-time updates.
816
+ */
817
+ async wait(jobId, opts) {
818
+ const timeoutMs = opts?.timeoutMs ?? 5 * 6e4;
819
+ const controller = new AbortController();
820
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
821
+ if (opts?.signal) {
822
+ if (opts.signal.aborted) {
823
+ clearTimeout(timeoutId);
824
+ throw makeAbortError();
825
+ }
826
+ opts.signal.addEventListener("abort", () => controller.abort(), { once: true });
827
+ }
828
+ try {
829
+ for await (const event of this.stream(jobId, {
830
+ signal: controller.signal,
831
+ onEvent: opts?.onEvent
832
+ })) if (event.type === "terminal") {
833
+ const job = event.job;
834
+ if (job.status === "succeeded") return job;
835
+ if (job.status === "failed") throw new JobFailedError(jobId, job.error?.message ?? "Job failed", {
836
+ requestId: job.requestId,
837
+ code: job.error?.code,
838
+ cause: job.error
839
+ });
840
+ if (job.status === "canceled") throw new JobCanceledError(jobId, "Job canceled", {
841
+ requestId: job.requestId,
842
+ cause: job.error
843
+ });
844
+ }
845
+ throw new JobFailedError(jobId, `Timed out waiting for job ${jobId} after ${timeoutMs}ms`, { cause: {
846
+ jobId,
847
+ timeoutMs
848
+ } });
849
+ } finally {
850
+ clearTimeout(timeoutId);
851
+ }
852
+ }
853
+ /**
854
+ * Stream job events via SSE.
855
+ *
856
+ * Yields events as they arrive from the server. Use `AbortSignal` to cancel streaming.
857
+ * Automatically handles reconnection with `Last-Event-ID` for up to 3 retries.
858
+ */
859
+ async *stream(jobId, opts) {
860
+ const maxRetries = 3;
861
+ let lastEventId;
862
+ let retries = 0;
863
+ while (retries < maxRetries) try {
864
+ const sseStream = this.transport.streamSSE(`/v1/jobs/${encodeURIComponent(jobId)}/stream`, {
865
+ signal: opts?.signal,
866
+ lastEventId
867
+ });
868
+ for await (const sseEvent of sseStream) {
869
+ lastEventId = sseEvent.id;
870
+ if (sseEvent.event === "error") {
871
+ const errorData = sseEvent.data;
872
+ throw new MappaError(errorData.error?.message ?? "Unknown SSE error", { code: errorData.error?.code });
873
+ }
874
+ if (sseEvent.event === "heartbeat") continue;
875
+ const jobEvent = this.mapSSEToJobEvent(sseEvent);
876
+ if (jobEvent) {
877
+ opts?.onEvent?.(jobEvent);
878
+ yield jobEvent;
879
+ if (sseEvent.event === "terminal") return;
880
+ }
881
+ retries = 0;
882
+ }
883
+ retries++;
884
+ if (retries < maxRetries) await this.backoff(retries);
885
+ } catch (error) {
886
+ if (opts?.signal?.aborted) throw error;
887
+ retries++;
888
+ if (retries >= maxRetries) throw error;
889
+ await this.backoff(retries);
890
+ }
891
+ throw new MappaError(`Failed to get status for job ${jobId} after ${maxRetries} retries`);
892
+ }
893
+ /**
894
+ * Map an SSE event to a JobEvent.
895
+ */
896
+ mapSSEToJobEvent(sseEvent) {
897
+ const data = sseEvent.data;
898
+ switch (sseEvent.event) {
899
+ case "status": return {
900
+ type: "status",
901
+ job: data.job
902
+ };
903
+ case "stage": return {
904
+ type: "stage",
905
+ stage: data.stage,
906
+ progress: data.progress,
907
+ job: data.job
908
+ };
909
+ case "terminal": return {
910
+ type: "terminal",
911
+ job: data.job
912
+ };
913
+ default: return {
914
+ type: "status",
915
+ job: data.job
916
+ };
917
+ }
918
+ }
919
+ /**
920
+ * Exponential backoff with jitter for reconnection.
921
+ */
922
+ async backoff(attempt) {
923
+ const delay = Math.min(1e3 * 2 ** attempt, 1e4);
924
+ const jitter$1 = delay * .5 * Math.random();
925
+ await new Promise((r) => setTimeout(r, delay + jitter$1));
926
+ }
927
+ };
928
+
929
+ //#endregion
930
+ //#region src/resources/reports.ts
931
+ /**
932
+ * Runtime validation for the internal `MediaIdRef` requirement.
933
+ *
934
+ * The public API server expects a `mediaId` when creating a report job.
935
+ * Use helpers like `createJobFromFile` / `createJobFromUrl` to start from bytes or a URL.
936
+ */
937
+ function validateMedia(media) {
938
+ const m = media;
939
+ const isObj = (v) => v !== null && typeof v === "object";
940
+ if (!isObj(m)) throw new MappaError("media must be an object");
941
+ if (m.url !== void 0) throw new MappaError("media.url is not supported; pass { mediaId } or use createJobFromUrl()");
942
+ const mediaId = m.mediaId;
943
+ if (typeof mediaId !== "string" || !mediaId) throw new MappaError("media.mediaId must be a non-empty string");
944
+ }
945
+ /**
946
+ * Reports API resource.
947
+ *
948
+ * Responsibilities:
949
+ * - Create report jobs (`POST /v1/reports/jobs`).
950
+ * - Fetch reports by report ID (`GET /v1/reports/:reportId`).
951
+ * - Fetch reports by job ID (`GET /v1/reports/by-job/:jobId`).
952
+ *
953
+ * Convenience helpers:
954
+ * - {@link ReportsResource.createJobFromFile} orchestrates `files.upload()` + {@link ReportsResource.createJob}.
955
+ * - {@link ReportsResource.createJobFromUrl} downloads a remote URL, uploads it, then calls {@link ReportsResource.createJob}.
956
+ * - {@link ReportsResource.generate} / {@link ReportsResource.generateFromFile} are script-friendly wrappers that
957
+ * create a job, wait for completion, and then fetch the final report.
958
+ *
959
+ * For production systems, prefer `createJob*()` plus webhooks or streaming job events rather than blocking waits.
960
+ */
961
+ var ReportsResource = class {
962
+ constructor(transport, jobs, files, fetchImpl) {
963
+ this.transport = transport;
964
+ this.jobs = jobs;
965
+ this.files = files;
966
+ this.fetchImpl = fetchImpl;
967
+ }
968
+ /**
969
+ * Create a new report job.
970
+ *
971
+ * Behavior:
972
+ * - Validates {@link MediaIdRef} at runtime (must provide `{ mediaId }`).
973
+ * - Defaults to `{ strategy: "dominant" }` when `target` is omitted.
974
+ * - Applies an idempotency key: uses `req.idempotencyKey` when provided; otherwise generates a best-effort default.
975
+ * - Forwards `req.requestId` to the transport for end-to-end correlation.
976
+ *
977
+ * The returned receipt includes a {@link ReportRunHandle} (`receipt.handle`) which can be used to:
978
+ * - stream job events
979
+ * - wait for completion and fetch the final report
980
+ * - cancel the job, or fetch job/report metadata
981
+ */
982
+ async createJob(req) {
983
+ validateMedia(req.media);
984
+ const idempotencyKey = req.idempotencyKey ?? this.defaultIdempotencyKey(req);
985
+ const res = await this.transport.request({
986
+ method: "POST",
987
+ path: "/v1/reports/jobs",
988
+ body: this.normalizeJobRequest(req),
989
+ idempotencyKey,
990
+ requestId: req.requestId,
991
+ retryable: true
992
+ });
993
+ const receipt = {
994
+ ...res.data,
995
+ requestId: res.requestId ?? res.data.requestId
996
+ };
997
+ receipt.handle = this.makeHandle(receipt.jobId);
998
+ return receipt;
999
+ }
1000
+ /**
1001
+ * Upload a file and create a report job in one call.
1002
+ *
1003
+ * Keeps `createJob()` strict about `media: { mediaId }` while offering a
1004
+ * convenient helper when you start from raw bytes.
1005
+ */
1006
+ async createJobFromFile(req) {
1007
+ const { file, contentType, filename, idempotencyKey, requestId, signal, ...rest } = req;
1008
+ const upload = await this.files.upload({
1009
+ file,
1010
+ contentType,
1011
+ filename,
1012
+ idempotencyKey,
1013
+ requestId,
1014
+ signal
1015
+ });
1016
+ return this.createJob({
1017
+ ...rest,
1018
+ media: { mediaId: upload.mediaId },
1019
+ idempotencyKey,
1020
+ requestId
1021
+ });
1022
+ }
1023
+ /**
1024
+ * Download a file from a URL, upload it, and create a report job.
1025
+ *
1026
+ * Recommended when starting from a remote URL because report job creation
1027
+ * only accepts `media: { mediaId }`.
1028
+ *
1029
+ * Workflow:
1030
+ * 1) `fetch(url)`
1031
+ * 2) Validate the response (2xx) and derive `contentType`
1032
+ * 3) `files.upload({ file: Blob, ... })`
1033
+ * 4) `createJob({ media: { mediaId }, ... })`
1034
+ *
1035
+ * Verification / safety:
1036
+ * - Only allows `http:` and `https:` URLs.
1037
+ * - Requires a resolvable `contentType` (from `req.contentType` or response header).
1038
+ */
1039
+ async createJobFromUrl(req) {
1040
+ const { url, contentType: contentTypeOverride, filename, idempotencyKey, requestId, signal, ...rest } = req;
1041
+ let parsed;
1042
+ try {
1043
+ parsed = new URL(url);
1044
+ } catch {
1045
+ throw new MappaError("url must be a valid URL");
1046
+ }
1047
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new MappaError("url must use http: or https:");
1048
+ const res = await this.fetchImpl(parsed.toString(), { signal });
1049
+ if (!res.ok) throw new MappaError(`Failed to download url (status ${res.status})`);
1050
+ const derivedContentType = res.headers.get("content-type") ?? void 0;
1051
+ const contentType = contentTypeOverride ?? derivedContentType;
1052
+ if (!contentType) throw new MappaError("contentType is required when it cannot be inferred from the download response");
1053
+ if (typeof Blob === "undefined") throw new MappaError("Blob is not available in this runtime; cannot download and upload from url");
1054
+ const blob = await res.blob();
1055
+ const upload = await this.files.upload({
1056
+ file: blob,
1057
+ contentType,
1058
+ filename,
1059
+ idempotencyKey,
1060
+ requestId,
1061
+ signal
1062
+ });
1063
+ return this.createJob({
1064
+ ...rest,
1065
+ media: { mediaId: upload.mediaId },
1066
+ idempotencyKey,
1067
+ requestId
1068
+ });
1069
+ }
1070
+ async get(reportId, opts) {
1071
+ return (await this.transport.request({
1072
+ method: "GET",
1073
+ path: `/v1/reports/${encodeURIComponent(reportId)}`,
1074
+ requestId: opts?.requestId,
1075
+ signal: opts?.signal,
1076
+ retryable: true
1077
+ })).data;
1078
+ }
1079
+ async getByJob(jobId, opts) {
1080
+ return (await this.transport.request({
1081
+ method: "GET",
1082
+ path: `/v1/reports/by-job/${encodeURIComponent(jobId)}`,
1083
+ requestId: opts?.requestId,
1084
+ signal: opts?.signal,
1085
+ retryable: true
1086
+ })).data;
1087
+ }
1088
+ /**
1089
+ * Convenience wrapper: createJob + wait + get
1090
+ * Use for scripts; for production prefer createJob + webhooks/stream.
1091
+ */
1092
+ async generate(req, opts) {
1093
+ const receipt = await this.createJob(req);
1094
+ if (!receipt.handle) throw new MappaError("Job receipt is missing handle");
1095
+ return receipt.handle.wait(opts?.wait);
1096
+ }
1097
+ /**
1098
+ * Convenience wrapper: createJobFromFile + wait + get.
1099
+ * Use for scripts; for production prefer createJobFromFile + webhooks/stream.
1100
+ */
1101
+ async generateFromFile(req, opts) {
1102
+ const receipt = await this.createJobFromFile(req);
1103
+ if (!receipt.handle) throw new MappaError("Job receipt is missing handle");
1104
+ return receipt.handle.wait(opts?.wait);
1105
+ }
1106
+ /**
1107
+ * Convenience wrapper: createJobFromUrl + wait + get.
1108
+ * Use for scripts; for production prefer createJobFromUrl + webhooks/stream.
1109
+ */
1110
+ async generateFromUrl(req, opts) {
1111
+ const receipt = await this.createJobFromUrl(req);
1112
+ if (!receipt.handle) throw new MappaError("Job receipt is missing handle");
1113
+ return receipt.handle.wait(opts?.wait);
1114
+ }
1115
+ makeHandle(jobId) {
1116
+ const self = this;
1117
+ return {
1118
+ jobId,
1119
+ stream: (opts) => self.jobs.stream(jobId, opts),
1120
+ async wait(opts) {
1121
+ const terminal = await self.jobs.wait(jobId, opts);
1122
+ if (!terminal.reportId) throw new MappaError(`Job ${jobId} succeeded but no reportId was returned`);
1123
+ return self.get(terminal.reportId);
1124
+ },
1125
+ cancel: () => self.jobs.cancel(jobId),
1126
+ job: () => self.jobs.get(jobId),
1127
+ report: () => self.getByJob(jobId)
1128
+ };
1129
+ }
1130
+ defaultIdempotencyKey(_req) {
1131
+ return randomId("idem");
1132
+ }
1133
+ normalizeJobRequest(req) {
1134
+ const target = req.target;
1135
+ if (!target) return {
1136
+ ...req,
1137
+ target: { strategy: "dominant" }
1138
+ };
1139
+ const baseTarget = { strategy: target.strategy };
1140
+ if (target.onMiss) baseTarget.on_miss = target.onMiss;
1141
+ if (target.tags && target.tags.length > 0) baseTarget.tags = target.tags;
1142
+ if (target.excludeTags && target.excludeTags.length > 0) baseTarget.exclude_tags = target.excludeTags;
1143
+ switch (target.strategy) {
1144
+ case "dominant": return {
1145
+ ...req,
1146
+ target: baseTarget
1147
+ };
1148
+ case "timerange": {
1149
+ const timeRange = target.timeRange ?? {};
1150
+ return {
1151
+ ...req,
1152
+ target: {
1153
+ ...baseTarget,
1154
+ timerange: {
1155
+ start_seconds: timeRange.startSeconds ?? null,
1156
+ end_seconds: timeRange.endSeconds ?? null
1157
+ }
1158
+ }
1159
+ };
1160
+ }
1161
+ case "entity_id": return {
1162
+ ...req,
1163
+ target: {
1164
+ ...baseTarget,
1165
+ entity_id: target.entityId
1166
+ }
1167
+ };
1168
+ case "magic_hint": return {
1169
+ ...req,
1170
+ target: {
1171
+ ...baseTarget,
1172
+ hint: target.hint
1173
+ }
1174
+ };
1175
+ default: return req;
1176
+ }
1177
+ }
1178
+ };
1179
+
1180
+ //#endregion
1181
+ //#region src/resources/transport.ts
1182
+ function buildUrl(baseUrl, path, query) {
1183
+ const u = new URL(path.replace(/^\//, ""), baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
1184
+ if (query) for (const [k, v] of Object.entries(query)) {
1185
+ if (v === void 0) continue;
1186
+ u.searchParams.set(k, String(v));
1187
+ }
1188
+ return u.toString();
1189
+ }
1190
+ async function readBody(res) {
1191
+ const text = await res.text();
1192
+ if (!text) return {
1193
+ parsed: null,
1194
+ text: ""
1195
+ };
1196
+ try {
1197
+ return {
1198
+ parsed: JSON.parse(text),
1199
+ text
1200
+ };
1201
+ } catch {
1202
+ return {
1203
+ parsed: text,
1204
+ text
1205
+ };
1206
+ }
1207
+ }
1208
+ function coerceApiError(res, parsed) {
1209
+ const requestId = res.headers.get("x-request-id") ?? void 0;
1210
+ let code;
1211
+ let message = `Request failed with status ${res.status}`;
1212
+ let details = parsed;
1213
+ if (typeof parsed === "string") message = parsed;
1214
+ else if (parsed && typeof parsed === "object") {
1215
+ const p = parsed;
1216
+ const err = p.error ?? p;
1217
+ if (err && typeof err === "object") {
1218
+ const e = err;
1219
+ if (typeof e.message === "string") message = e.message;
1220
+ if (typeof e.code === "string") code = e.code;
1221
+ if ("details" in e) details = e.details;
1222
+ }
1223
+ }
1224
+ if (res.status === 401 || res.status === 403) return new AuthError(message, {
1225
+ status: res.status,
1226
+ requestId,
1227
+ code,
1228
+ details
1229
+ });
1230
+ if (res.status === 422) return new ValidationError(message, {
1231
+ status: res.status,
1232
+ requestId,
1233
+ code,
1234
+ details
1235
+ });
1236
+ if (res.status === 402 && code === "insufficient_credits") return new InsufficientCreditsError(message, {
1237
+ status: res.status,
1238
+ requestId,
1239
+ code,
1240
+ details
1241
+ });
1242
+ if (res.status === 429) {
1243
+ const e = new RateLimitError(message, {
1244
+ status: res.status,
1245
+ requestId,
1246
+ code,
1247
+ details
1248
+ });
1249
+ const ra = res.headers.get("retry-after");
1250
+ if (ra) {
1251
+ const sec = Number(ra);
1252
+ if (Number.isFinite(sec) && sec >= 0) e.retryAfterMs = sec * 1e3;
1253
+ }
1254
+ return e;
1255
+ }
1256
+ return new ApiError(message, {
1257
+ status: res.status,
1258
+ requestId,
1259
+ code,
1260
+ details
1261
+ });
1262
+ }
1263
+ function shouldRetry(opts, err) {
1264
+ if (!opts.retryable) return { retry: false };
1265
+ if (err instanceof RateLimitError) return {
1266
+ retry: true,
1267
+ retryAfterMs: err.retryAfterMs
1268
+ };
1269
+ if (err instanceof ApiError) return { retry: err.status >= 500 && err.status <= 599 };
1270
+ if (err instanceof TypeError) return { retry: true };
1271
+ return { retry: false };
1272
+ }
1273
+ var Transport = class {
1274
+ fetchImpl;
1275
+ constructor(opts) {
1276
+ this.opts = opts;
1277
+ this.fetchImpl = opts.fetch ?? fetch;
1278
+ }
1279
+ /**
1280
+ * Stream SSE events from a given path.
1281
+ *
1282
+ * Uses native `fetch` with streaming response body (not browser-only `EventSource`).
1283
+ * Parses SSE format manually from the `ReadableStream`.
1284
+ */
1285
+ async *streamSSE(path, opts) {
1286
+ const url = buildUrl(this.opts.baseUrl, path);
1287
+ const requestId = randomId("req");
1288
+ const headers = {
1289
+ Accept: "text/event-stream",
1290
+ "Cache-Control": "no-cache",
1291
+ "Mappa-Api-Key": this.opts.apiKey,
1292
+ "X-Request-Id": requestId,
1293
+ ...this.opts.userAgent ? { "User-Agent": this.opts.userAgent } : {},
1294
+ ...this.opts.defaultHeaders ?? {}
1295
+ };
1296
+ if (opts?.lastEventId) headers["Last-Event-ID"] = opts.lastEventId;
1297
+ const controller = new AbortController();
1298
+ const timeout = setTimeout(() => controller.abort(makeAbortError()), this.opts.timeoutMs);
1299
+ if (hasAbortSignal(opts?.signal)) {
1300
+ const signal = opts?.signal;
1301
+ if (signal?.aborted) {
1302
+ clearTimeout(timeout);
1303
+ throw makeAbortError();
1304
+ }
1305
+ signal?.addEventListener("abort", () => controller.abort(makeAbortError()), { once: true });
1306
+ }
1307
+ this.opts.telemetry?.onRequest?.({
1308
+ method: "GET",
1309
+ url,
1310
+ requestId
1311
+ });
1312
+ let res;
1313
+ try {
1314
+ res = await this.fetchImpl(url, {
1315
+ method: "GET",
1316
+ headers,
1317
+ signal: controller.signal
1318
+ });
1319
+ } catch (err) {
1320
+ clearTimeout(timeout);
1321
+ this.opts.telemetry?.onError?.({
1322
+ url,
1323
+ requestId,
1324
+ error: err
1325
+ });
1326
+ throw err;
1327
+ }
1328
+ if (!res.ok) {
1329
+ clearTimeout(timeout);
1330
+ const { parsed } = await readBody(res);
1331
+ const apiErr = coerceApiError(res, parsed);
1332
+ this.opts.telemetry?.onError?.({
1333
+ url,
1334
+ requestId,
1335
+ error: apiErr
1336
+ });
1337
+ throw apiErr;
1338
+ }
1339
+ if (!res.body) {
1340
+ clearTimeout(timeout);
1341
+ throw new MappaError("SSE response has no body");
1342
+ }
1343
+ try {
1344
+ yield* this.parseSSEStream(res.body);
1345
+ } finally {
1346
+ clearTimeout(timeout);
1347
+ }
1348
+ }
1349
+ /**
1350
+ * Parse SSE events from a ReadableStream.
1351
+ *
1352
+ * SSE format:
1353
+ * ```
1354
+ * id: <id>
1355
+ * event: <type>
1356
+ * data: <json>
1357
+ *
1358
+ * ```
1359
+ * Each event is terminated by an empty line.
1360
+ */
1361
+ async *parseSSEStream(body) {
1362
+ const decoder = new TextDecoder();
1363
+ const reader = body.getReader();
1364
+ let buffer = "";
1365
+ try {
1366
+ while (true) {
1367
+ const { done, value } = await reader.read();
1368
+ if (done) break;
1369
+ buffer += decoder.decode(value, { stream: true });
1370
+ const events = buffer.split("\n\n");
1371
+ buffer = events.pop() ?? "";
1372
+ for (const eventText of events) {
1373
+ if (!eventText.trim()) continue;
1374
+ const event = this.parseSSEEvent(eventText);
1375
+ if (event) yield event;
1376
+ }
1377
+ }
1378
+ if (buffer.trim()) {
1379
+ const event = this.parseSSEEvent(buffer);
1380
+ if (event) yield event;
1381
+ }
1382
+ } finally {
1383
+ reader.releaseLock();
1384
+ }
1385
+ }
1386
+ /**
1387
+ * Parse a single SSE event from text.
1388
+ */
1389
+ parseSSEEvent(text) {
1390
+ const lines = text.split("\n");
1391
+ let id;
1392
+ let event = "message";
1393
+ let data = "";
1394
+ for (const line of lines) if (line.startsWith("id:")) id = line.slice(3).trim();
1395
+ else if (line.startsWith("event:")) event = line.slice(6).trim();
1396
+ else if (line.startsWith("data:")) {
1397
+ if (data) data += "\n";
1398
+ data += line.slice(5).trim();
1399
+ }
1400
+ if (!data) return null;
1401
+ let parsedData;
1402
+ try {
1403
+ parsedData = JSON.parse(data);
1404
+ } catch {
1405
+ parsedData = data;
1406
+ }
1407
+ return {
1408
+ id,
1409
+ event,
1410
+ data: parsedData
1411
+ };
1412
+ }
1413
+ async request(req) {
1414
+ const url = buildUrl(this.opts.baseUrl, req.path, req.query);
1415
+ const requestId = req.requestId ?? randomId("req");
1416
+ const headers = {
1417
+ "Mappa-Api-Key": this.opts.apiKey,
1418
+ "X-Request-Id": requestId,
1419
+ ...this.opts.userAgent ? { "User-Agent": this.opts.userAgent } : {},
1420
+ ...this.opts.defaultHeaders ?? {}
1421
+ };
1422
+ if (req.idempotencyKey) headers["Idempotency-Key"] = req.idempotencyKey;
1423
+ if (req.headers) {
1424
+ for (const [k, v] of Object.entries(req.headers)) if (v !== void 0) headers[k] = v;
1425
+ }
1426
+ const isFormData = typeof FormData !== "undefined" && req.body instanceof FormData;
1427
+ if (req.body !== void 0 && !isFormData) headers["Content-Type"] = "application/json";
1428
+ const body = req.body === void 0 ? void 0 : isFormData ? req.body : JSON.stringify(req.body);
1429
+ const maxRetries = Math.max(0, this.opts.maxRetries);
1430
+ const startedAt = Date.now();
1431
+ for (let attempt = 1; attempt <= 1 + maxRetries; attempt++) {
1432
+ const controller = new AbortController();
1433
+ const timeout = setTimeout(() => controller.abort(makeAbortError()), this.opts.timeoutMs);
1434
+ if (hasAbortSignal(req.signal)) {
1435
+ const signal = req.signal;
1436
+ if (!signal) {
1437
+ clearTimeout(timeout);
1438
+ throw new Error("Unexpected: abort signal missing");
1439
+ }
1440
+ if (signal.aborted) {
1441
+ clearTimeout(timeout);
1442
+ throw makeAbortError();
1443
+ }
1444
+ signal.addEventListener("abort", () => controller.abort(makeAbortError()), { once: true });
1445
+ }
1446
+ this.opts.telemetry?.onRequest?.({
1447
+ method: req.method,
1448
+ url,
1449
+ requestId
1450
+ });
1451
+ try {
1452
+ const res = await this.fetchImpl(url, {
1453
+ method: req.method,
1454
+ headers,
1455
+ body,
1456
+ signal: controller.signal
1457
+ });
1458
+ const durationMs = Date.now() - startedAt;
1459
+ const serverRequestId = getHeader(res.headers, "x-request-id") ?? requestId;
1460
+ if (!res.ok) {
1461
+ const { parsed } = await readBody(res);
1462
+ const apiErr = coerceApiError(res, parsed);
1463
+ this.opts.telemetry?.onError?.({
1464
+ url,
1465
+ requestId: serverRequestId,
1466
+ error: apiErr
1467
+ });
1468
+ const decision = shouldRetry(req, apiErr);
1469
+ if (attempt <= maxRetries + 1 && decision.retry && attempt <= maxRetries) {
1470
+ const base = decision.retryAfterMs ?? jitter(backoffMs(attempt, 500, 4e3));
1471
+ await new Promise((r) => setTimeout(r, base));
1472
+ continue;
1473
+ }
1474
+ throw apiErr;
1475
+ }
1476
+ const ct = res.headers.get("content-type") ?? "";
1477
+ let data;
1478
+ if (ct.includes("application/json")) data = await res.json();
1479
+ else data = await res.text();
1480
+ this.opts.telemetry?.onResponse?.({
1481
+ status: res.status,
1482
+ url,
1483
+ requestId: serverRequestId,
1484
+ durationMs
1485
+ });
1486
+ return {
1487
+ data,
1488
+ status: res.status,
1489
+ requestId: serverRequestId,
1490
+ headers: res.headers
1491
+ };
1492
+ } catch (err) {
1493
+ this.opts.telemetry?.onError?.({
1494
+ url,
1495
+ requestId,
1496
+ error: err
1497
+ });
1498
+ const decision = shouldRetry(req, err);
1499
+ if (attempt <= maxRetries && decision.retry) {
1500
+ const sleep = decision.retryAfterMs ?? jitter(backoffMs(attempt, 500, 4e3));
1501
+ await new Promise((r) => setTimeout(r, sleep));
1502
+ continue;
1503
+ }
1504
+ throw err;
1505
+ } finally {
1506
+ clearTimeout(timeout);
1507
+ }
1508
+ }
1509
+ throw new Error("Unexpected transport exit");
1510
+ }
1511
+ };
1512
+
1513
+ //#endregion
1514
+ //#region src/resources/webhooks.ts
1515
+ function isObject(v) {
1516
+ return v !== null && typeof v === "object";
1517
+ }
1518
+ /**
1519
+ * Async signature verification using WebCrypto (works in modern Node and browsers).
1520
+ * Signature scheme placeholder:
1521
+ * headers["mappa-signature"] = "t=1700000000,v1=<hex_hmac_sha256>"
1522
+ * Signed payload: `${t}.${rawBody}`
1523
+ */
1524
+ var WebhooksResource = class {
1525
+ async verifySignature(params) {
1526
+ const tolerance = params.toleranceSec ?? 300;
1527
+ const sigHeader = headerValue(params.headers, "mappa-signature");
1528
+ if (!sigHeader) throw new Error("Missing mappa-signature header");
1529
+ const parts = parseSig(sigHeader);
1530
+ const ts = Number(parts.t);
1531
+ if (!Number.isFinite(ts)) throw new Error("Invalid signature timestamp");
1532
+ const nowSec = Math.floor(Date.now() / 1e3);
1533
+ if (Math.abs(nowSec - ts) > tolerance) throw new Error("Signature timestamp outside tolerance");
1534
+ const signed = `${parts.t}.${params.payload}`;
1535
+ if (!timingSafeEqualHex(await hmacHex(params.secret, signed), parts.v1)) throw new Error("Invalid signature");
1536
+ return { ok: true };
1537
+ }
1538
+ parseEvent(payload) {
1539
+ const raw = JSON.parse(payload);
1540
+ if (!isObject(raw)) throw new Error("Invalid webhook payload: not an object");
1541
+ const obj = raw;
1542
+ const id = obj.id;
1543
+ const type = obj.type;
1544
+ const createdAt = obj.createdAt;
1545
+ if (typeof id !== "string") throw new Error("Invalid webhook payload: id must be a string");
1546
+ if (typeof type !== "string") throw new Error("Invalid webhook payload: type must be a string");
1547
+ if (typeof createdAt !== "string") throw new Error("Invalid webhook payload: createdAt must be a string");
1548
+ return {
1549
+ id,
1550
+ type,
1551
+ createdAt,
1552
+ data: "data" in obj ? obj.data : void 0
1553
+ };
1554
+ }
1555
+ };
1556
+ function headerValue(headers, name) {
1557
+ const key = Object.keys(headers).find((k) => k.toLowerCase() === name.toLowerCase());
1558
+ const v = key ? headers[key] : void 0;
1559
+ if (!v) return void 0;
1560
+ return Array.isArray(v) ? v[0] : v;
1561
+ }
1562
+ function parseSig(h) {
1563
+ const out = {};
1564
+ for (const part of h.split(",")) {
1565
+ const [k, v] = part.split("=");
1566
+ if (k && v) out[k.trim()] = v.trim();
1567
+ }
1568
+ if (!out.t || !out.v1) throw new Error("Invalid signature format");
1569
+ return {
1570
+ t: out.t,
1571
+ v1: out.v1
1572
+ };
1573
+ }
1574
+ async function hmacHex(secret, message) {
1575
+ const enc = new TextEncoder();
1576
+ const key = await crypto.subtle.importKey("raw", enc.encode(secret), {
1577
+ name: "HMAC",
1578
+ hash: "SHA-256"
1579
+ }, false, ["sign"]);
1580
+ return bufToHex(await crypto.subtle.sign("HMAC", key, enc.encode(message)));
1581
+ }
1582
+ function bufToHex(buf) {
1583
+ const b = new Uint8Array(buf);
1584
+ let s = "";
1585
+ for (const x of b) s += x.toString(16).padStart(2, "0");
1586
+ return s;
1587
+ }
1588
+ function timingSafeEqualHex(a, b) {
1589
+ if (a.length !== b.length) return false;
1590
+ let r = 0;
1591
+ for (let i = 0; i < a.length; i++) r |= a.charCodeAt(i) ^ b.charCodeAt(i);
1592
+ return r === 0;
1593
+ }
1594
+
1595
+ //#endregion
1596
+ //#region src/Mappa.ts
1597
+ /**
1598
+ * Main SDK client.
1599
+ *
1600
+ * Exposes resource namespaces ({@link Mappa.files}, {@link Mappa.reports}, etc.)
1601
+ * and configures a shared HTTP transport.
1602
+ */
1603
+ var Mappa = class Mappa {
1604
+ files;
1605
+ jobs;
1606
+ reports;
1607
+ feedback;
1608
+ credits;
1609
+ entities;
1610
+ webhooks;
1611
+ health;
1612
+ transport;
1613
+ opts;
1614
+ constructor(options) {
1615
+ if (!options.apiKey) throw new MappaError("apiKey is required");
1616
+ const baseUrl = options.baseUrl ?? "https://api.mappa.ai";
1617
+ const timeoutMs = options.timeoutMs ?? 3e4;
1618
+ const maxRetries = options.maxRetries ?? 2;
1619
+ this.opts = {
1620
+ ...options,
1621
+ apiKey: options.apiKey,
1622
+ baseUrl,
1623
+ timeoutMs,
1624
+ maxRetries
1625
+ };
1626
+ this.transport = new Transport({
1627
+ apiKey: options.apiKey,
1628
+ baseUrl,
1629
+ timeoutMs,
1630
+ maxRetries,
1631
+ defaultHeaders: options.defaultHeaders,
1632
+ fetch: options.fetch,
1633
+ telemetry: options.telemetry,
1634
+ userAgent: options.userAgent
1635
+ });
1636
+ this.files = new FilesResource(this.transport);
1637
+ this.jobs = new JobsResource(this.transport);
1638
+ this.reports = new ReportsResource(this.transport, this.jobs, this.files, this.opts.fetch ?? fetch);
1639
+ this.feedback = new FeedbackResource(this.transport);
1640
+ this.credits = new CreditsResource(this.transport);
1641
+ this.entities = new EntitiesResource(this.transport);
1642
+ this.webhooks = new WebhooksResource();
1643
+ this.health = new HealthResource(this.transport);
1644
+ }
1645
+ withOptions(overrides) {
1646
+ return new Mappa({
1647
+ ...this.opts,
1648
+ ...overrides,
1649
+ apiKey: overrides.apiKey ?? this.opts.apiKey
1650
+ });
1651
+ }
1652
+ close() {}
1653
+ };
1654
+
1655
+ //#endregion
1656
+ //#region src/types.ts
1657
+ /**
1658
+ * Type guard for MarkdownReport.
1659
+ */
1660
+ function isMarkdownReport(report) {
1661
+ return report.output.type === "markdown";
1662
+ }
1663
+ /**
1664
+ * Type guard for JsonReport.
1665
+ */
1666
+ function isJsonReport(report) {
1667
+ return report.output.type === "json";
1668
+ }
1669
+ /**
1670
+ * Type guard for PdfReport.
1671
+ */
1672
+ function isPdfReport(report) {
1673
+ return report.output.type === "pdf";
1674
+ }
1675
+ /**
1676
+ * Type guard for UrlReport.
1677
+ */
1678
+ function isUrlReport(report) {
1679
+ return report.output.type === "url";
1680
+ }
1681
+ /**
1682
+ * Type guard to check if a report has entity information.
1683
+ * Always returns true since entity is always present in reports.
1684
+ */
1685
+ function hasEntity(report) {
1686
+ return report.entity !== void 0 && report.entity !== null;
1687
+ }
1688
+
1689
+ //#endregion
1690
+ //#region src/index.ts
1691
+ /**
1692
+ * Type guard for catching SDK errors.
1693
+ */
1694
+ function isMappaError(err) {
1695
+ return err instanceof MappaError;
1696
+ }
1697
+ /**
1698
+ * Type guard for insufficient credits errors.
1699
+ *
1700
+ * @example
1701
+ * ```typescript
1702
+ * try {
1703
+ * await mappa.reports.createJob({ ... });
1704
+ * } catch (err) {
1705
+ * if (isInsufficientCreditsError(err)) {
1706
+ * console.log(`Need ${err.required} credits, have ${err.available}`);
1707
+ * }
1708
+ * }
1709
+ * ```
1710
+ */
1711
+ function isInsufficientCreditsError(err) {
1712
+ return err instanceof InsufficientCreditsError;
1713
+ }
1714
+
1715
+ //#endregion
1716
+ exports.ApiError = ApiError;
1717
+ exports.AuthError = AuthError;
1718
+ exports.InsufficientCreditsError = InsufficientCreditsError;
1719
+ exports.JobCanceledError = JobCanceledError;
1720
+ exports.JobFailedError = JobFailedError;
1721
+ exports.Mappa = Mappa;
1722
+ exports.MappaError = MappaError;
1723
+ exports.RateLimitError = RateLimitError;
1724
+ exports.ValidationError = ValidationError;
1725
+ exports.hasEntity = hasEntity;
1726
+ exports.isInsufficientCreditsError = isInsufficientCreditsError;
1727
+ exports.isJsonReport = isJsonReport;
1728
+ exports.isMappaError = isMappaError;
1729
+ exports.isMarkdownReport = isMarkdownReport;
1730
+ exports.isPdfReport = isPdfReport;
1731
+ exports.isUrlReport = isUrlReport;
1732
+ //# sourceMappingURL=index.cjs.map