@mappa-ai/mappa-node 1.2.4 → 2.0.8

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,474 +1,239 @@
1
- # Mappa Node SDK [WIP - DO NOT USE YET]
1
+ # @mappa-ai/mappa-node
2
2
 
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)
3
+ Official TypeScript SDK for the Mappa API.
5
4
 
6
- Official JavaScript/TypeScript SDK for the **Mappa API**.
5
+ Built for server and worker runtimes. Primary targets are Node.js and Bun.
7
6
 
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**.
7
+ ## Agent + Dev Quickstart
12
8
 
13
- ---
9
+ - Install package: `npm install @mappa-ai/mappa-node`
10
+ - Use server-side only with `MAPPA_API_KEY`
11
+ - Validate package changes: `bun run check && bun run type-check && bun test`
12
+ - Build distribution artifacts: `bun run build`
13
+ - Follow package-specific agent rules: `packages/sdk-ts/AGENTS.md`
14
14
 
15
- ## Installation
15
+ ## Runtime support
16
16
 
17
- ```bash
18
- npm install @mappa-ai/mappa-node
19
- # or
20
- yarn add @mappa-ai/mappa-node
21
- # or
22
- pnpm add @mappa-ai/mappa-node
23
- # or
24
- bun add @mappa-ai/mappa-node
25
- ```
26
-
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";
17
+ - Primary: Node.js, Bun
18
+ - Compatible: Deno, Cloudflare Workers, other fetch-compatible server/edge runtimes
19
+ - Browser: blocked by default to protect secret API keys
35
20
 
36
- const mappa = new Mappa({
37
- apiKey: process.env.MAPPA_API_KEY!,
38
- });
21
+ ## Security model
39
22
 
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
- });
23
+ This SDK is designed for secret API keys.
45
24
 
25
+ - Do not call Mappa directly from browser apps with a private API key
26
+ - Put your key in a server or edge function and proxy requests
27
+ - The client throws in browser-like runtimes unless you set `dangerouslyAllowBrowser: true`
46
28
 
47
- if (report.output.type === "markdown") {
48
- console.log(report.markdown);
49
- }
50
- ```
29
+ ## Server proxy pattern
51
30
 
52
- ---
31
+ Use your backend as a trust boundary:
53
32
 
54
- ## Authentication
33
+ - Browser uploads/calls your endpoint
34
+ - Your endpoint uses this SDK with `MAPPA_API_KEY`
35
+ - Your endpoint returns only safe response fields
55
36
 
56
- Pass the API key when constructing the client:
37
+ ### Next.js App Router example
57
38
 
58
39
  ```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:
65
-
66
- ```bash
67
- export MAPPA_API_KEY="your-api-key"
68
- ```
40
+ // app/api/mappa/report/route.ts
41
+ import { NextResponse } from "next/server";
42
+ import { Mappa, isMappaError } from "@mappa-ai/mappa-node";
69
43
 
70
- ```ts
71
44
  const mappa = new Mappa({ apiKey: process.env.MAPPA_API_KEY! });
72
- ```
73
45
 
74
- ---
46
+ export async function POST(req: Request) {
47
+ try {
48
+ const form = await req.formData();
49
+ const file = form.get("file");
50
+
51
+ if (!(file instanceof File)) {
52
+ return NextResponse.json({ error: "file is required" }, { status: 400 });
53
+ }
54
+
55
+ const report = await mappa.reports.generateFromFile({
56
+ file,
57
+ output: { template: "general_report" },
58
+ target: { strategy: "dominant" },
59
+ });
60
+
61
+ return NextResponse.json({
62
+ id: report.id,
63
+ template: report.output.template,
64
+ summary: report.summary ?? null,
65
+ });
66
+ } catch (err) {
67
+ if (isMappaError(err)) {
68
+ return NextResponse.json(
69
+ { error: err.message, requestId: err.requestId ?? null },
70
+ { status: 502 },
71
+ );
72
+ }
73
+ return NextResponse.json({ error: "Unexpected error" }, { status: 500 });
74
+ }
75
+ }
76
+ ```
75
77
 
76
- ## Configuration
78
+ For advanced flow control, prefer `createJob` + `handle.wait()` or `handle.stream()`.
77
79
 
78
- The client supports per-instance configuration:
80
+ ### Framework-agnostic pattern
79
81
 
80
82
  ```ts
81
- import { Mappa } from "@mappa-ai/mappa-node";
82
-
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
- });
83
+ // POST /api/report
84
+ // 1) Validate auth and input
85
+ // 2) Call Mappa on the server
86
+ // 3) Return sanitized output
92
87
  ```
93
88
 
94
- ### Request tracing
95
-
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:
106
-
107
89
  ```ts
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",
90
+ const mappa = new Mappa({ apiKey: process.env.MAPPA_API_KEY! });
91
+ const report = await mappa.reports.generateFromFile({
92
+ file,
93
+ output: { template: "general_report" },
94
+ target: { strategy: "dominant" },
113
95
  });
96
+ return { id: report.id, summary: report.summary };
114
97
  ```
115
98
 
116
- ### Retries
99
+ ### Security checklist
117
100
 
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.
101
+ - Keep `MAPPA_API_KEY` server-only
102
+ - Require auth for proxy endpoints
103
+ - Validate input size and type before SDK calls
104
+ - Add rate limits on proxy endpoints
105
+ - Log `requestId` values from SDK errors for traceability
121
106
 
122
- Create a derived client with overrides:
107
+ ## Install
123
108
 
124
- ```ts
125
- const mappaNoRetries = mappa.withOptions({ maxRetries: 0 });
109
+ ```bash
110
+ npm install @mappa-ai/mappa-node
126
111
  ```
127
112
 
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.
113
+ ## Quickstart
137
114
 
138
115
  ```ts
139
- const controller = new AbortController();
140
-
141
- setTimeout(() => controller.abort(), 10_000);
116
+ import { Mappa } from "@mappa-ai/mappa-node";
142
117
 
143
- const receipt = await mappa.reports.createJob({
144
- media: { mediaId: "media_..." },
145
- output: { type: "markdown" },
118
+ const mappa = new Mappa({
119
+ apiKey: process.env.MAPPA_API_KEY!,
146
120
  });
147
121
 
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
- }
158
- ```
159
-
160
- Streaming with cancellation:
161
-
162
- ```ts
163
- const controller = new AbortController();
164
-
165
- const handle = mappa.reports.makeHandle("job_...");
122
+ const media = await mappa.files.upload({
123
+ file: new Blob(["...binary..."]),
124
+ });
166
125
 
167
- setTimeout(() => controller.abort(), 5_000);
126
+ const receipt = await mappa.reports.createJob({
127
+ media: { mediaId: media.mediaId },
128
+ output: { template: "general_report" },
129
+ target: { strategy: "dominant" },
130
+ });
168
131
 
169
- for await (const event of handle.stream({ signal: controller.signal })) {
170
- if (event.type === "terminal") break;
171
- }
132
+ const report = await receipt.handle?.wait();
133
+ console.log(report?.id);
172
134
  ```
173
135
 
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
136
+ ## Node filesystem helpers
189
137
 
190
- Report job creation accepts **already-uploaded** media references:
138
+ For Node/Bun file-path ergonomics, use the Node adapter subpath.
191
139
 
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)
140
+ ```ts
141
+ import { Mappa } from "@mappa-ai/mappa-node";
142
+ import { uploadFromPath, generateReportFromPath } from "@mappa-ai/mappa-node/node";
204
143
 
205
- If you already have an uploaded `mediaId`, use `createJob()`:
144
+ const mappa = new Mappa({ apiKey: process.env.MAPPA_API_KEY! });
206
145
 
207
- ```ts
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",
146
+ const media = await uploadFromPath(mappa, {
147
+ path: "./recording.wav",
221
148
  });
222
149
 
223
- console.log(receipt.jobId);
150
+ const report = await generateReportFromPath(mappa, {
151
+ path: "./recording.wav",
152
+ output: { template: "general_report" },
153
+ target: { strategy: "dominant" },
154
+ });
224
155
  ```
225
156
 
226
- If you’re starting from a remote URL, use `createJobFromUrl()`:
157
+ ## Matching analysis
227
158
 
228
159
  ```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" },
235
- },
236
- options: {
237
- language: "en",
238
- timezone: "UTC",
239
- },
240
- idempotencyKey: "report:customer_123:2026-01-14",
160
+ const job = await mappa.matchingAnalysis.createJob({
161
+ entityA: { type: "entity_id", entityId: "entity_a" },
162
+ entityB: { type: "entity_id", entityId: "entity_b" },
163
+ context: "Compare communication style",
164
+ output: { template: "matching_analysis" },
241
165
  });
242
166
 
243
- console.log(receiptFromUrl.jobId);
167
+ const analysis = await job.handle?.wait();
168
+ console.log(analysis?.id);
244
169
  ```
245
170
 
246
- ### 2) Wait for completion (polling)
171
+ ## Observability and retries
247
172
 
248
173
  ```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
- },
174
+ const mappa = new Mappa({
175
+ apiKey: process.env.MAPPA_API_KEY!,
176
+ timeoutMs: 30000,
177
+ maxRetries: 2,
178
+ telemetry: {
179
+ onRequest: (ctx) => console.log("req", ctx.method, ctx.url, ctx.requestId),
180
+ onResponse: (ctx) => console.log("res", ctx.status, ctx.durationMs),
181
+ onError: (ctx) => console.error("err", ctx.requestId, ctx.error),
182
+ },
256
183
  });
257
184
  ```
258
185
 
259
- ### 3) Stream job events
260
-
261
- `jobs.stream(jobId)` yields state transitions.
186
+ ## Webhooks
262
187
 
263
188
  ```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
- }
269
- ```
189
+ const payload = rawBodyString;
270
190
 
271
- Type narrowing helpers:
191
+ await mappa.webhooks.verifySignature({
192
+ payload,
193
+ headers: req.headers,
194
+ secret: process.env.MAPPA_WEBHOOK_SECRET!,
195
+ });
272
196
 
273
- ```ts
274
- if (report.output.type === "markdown") {
275
- console.log(report.markdown);
197
+ const event = mappa.webhooks.parseEvent(payload);
198
+ if (event.type === "report.completed") {
199
+ console.log(event.data.reportId);
276
200
  }
277
201
  ```
278
202
 
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
203
+ ## Error handling
308
204
 
309
205
  ```ts
310
206
  import {
311
- ApiError,
312
- AuthError,
313
- MappaError,
314
- RateLimitError,
315
- ValidationError,
207
+ isInsufficientCreditsError,
208
+ isMappaError,
209
+ isStreamError,
316
210
  } from "@mappa-ai/mappa-node";
317
211
 
318
212
  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
- });
213
+ // ...
336
214
  } 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;
215
+ if (isInsufficientCreditsError(err)) {
216
+ console.error(err.required, err.available);
217
+ } else if (isStreamError(err)) {
218
+ console.error(err.jobId, err.retryCount);
219
+ } else if (isMappaError(err)) {
220
+ console.error(err.requestId, err.code, err.message);
221
+ }
363
222
  }
364
223
  ```
365
224
 
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
- ---
225
+ ## Scope
429
226
 
430
- ## Telemetry hooks
227
+ - API-key workflows only
228
+ - Includes: health, files, jobs, reports, matching-analysis, feedback, entities, webhooks
229
+ - Excludes: billing and user-session console endpoints
431
230
 
432
- You can hook into request/response/error events:
231
+ ## Development
433
232
 
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";
233
+ ```bash
234
+ bun run build
235
+ bun run check
236
+ bun run type-check
237
+ bun test
468
238
  ```
469
239
 
470
- ---
471
-
472
- ## License
473
-
474
- MIT