songbrain 0.1.2 → 0.3.0

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
@@ -46,13 +46,16 @@ Every method resolves to the JSON body of the response. Field names are exactly
46
46
 
47
47
  | Method | API call | Key |
48
48
  |---|---|---|
49
- | `analyze({ file?, audioUrl?, title?, artist?, webhookUrl?, externalRef?, filename?, wait = true, pollIntervalMs = 5000, timeoutMs = 300000 })` | `POST /songs`, then polls | yes |
49
+ | `analyze({ file?, audioUrl?, title?, artist?, webhookUrl?, externalRef?, filename?, test?, idempotencyKey?, wait = true, pollIntervalMs = 5000, timeoutMs = 300000 })` | `POST /songs`, then polls | yes |
50
50
  | `waitFor(id, { pollIntervalMs?, timeoutMs? })` | polls `GET /songs/{id}` | yes |
51
51
  | `getSong(id, { view?, include? })` | `GET /songs/{id}` | yes |
52
52
  | `shotPlan(id)` | `GET /songs/{id}/shot-plan` | yes |
53
- | `listSongs({ limit = 20 })` | `GET /songs` | yes |
53
+ | `listSongs({ limit = 20, startingAfter? })` | `GET /songs` (one page) | yes |
54
+ | `iterSongs({ pageSize = 100, startingAfter? })` | `GET /songs`, all pages (async iterator) | yes |
54
55
  | `deleteSong(id)` | `DELETE /songs/{id}` | yes |
55
56
  | `account()` | `GET /account` | yes |
57
+ | `testWebhook(url)` | `POST /webhooks/test` | yes |
58
+ | `webhookDeliveries({ limit = 20 })` | `GET /webhooks/deliveries` | yes |
56
59
  | `pricing()` | `GET /pricing` | no |
57
60
  | `examples()` | `GET /examples` | no |
58
61
  | `example(id, { view?, include? })` | `GET /examples/{id}` | no |
@@ -68,11 +71,47 @@ new Songbrain({ apiKey: "sb_live_…", timeoutMs: 60_000, maxRetries: 3 });
68
71
 
69
72
  Keep the key on your server. Never ship it in a browser or app bundle.
70
73
 
74
+ ## Test mode
75
+
76
+ `analyze({ test: true })` is free, needs no audio and is done right away. It returns the Sugar Rush example analysis with your `title` and `externalRef`, `livemode: false` and `billing.type === "test"`. A `webhookUrl` still gets a signed `song.done`. Use it in CI:
77
+
78
+ ```ts
79
+ const song = await sb.analyze({ test: true, title: "CI smoke test", externalRef: "build-123" });
80
+ assert(song.status === "done" && song.livemode === false);
81
+ ```
82
+
83
+ ## Idempotency
84
+
85
+ Every `analyze()` sends an `Idempotency-Key` (a random UUID) and reuses it when the client retries. So network errors, 5xx and 429 are retried for uploads too, and a retry never creates or charges a second song. Pass your own key to make retries across processes safe (same key within 24 h = same song):
86
+
87
+ ```ts
88
+ await sb.analyze({ audioUrl: url, idempotencyKey: `order-${orderId}`, wait: false });
89
+ ```
90
+
91
+ The same key with a different request throws a 409 `idempotency_key_reused`.
92
+
93
+ ## Pagination
94
+
95
+ ```ts
96
+ let page = await sb.listSongs({ limit: 50 }); // { data, has_more, next_cursor }
97
+ page = await sb.listSongs({ limit: 50, startingAfter: page.next_cursor ?? undefined });
98
+
99
+ for await (const item of sb.iterSongs()) console.log(item.id, item.status, item.livemode);
100
+ ```
101
+
102
+ ## Rate limits and request ids
103
+
104
+ ```ts
105
+ await sb.account();
106
+ console.log(sb.lastRateLimit); // { limit: 120, remaining: 119, reset: 0 }
107
+ console.log(sb.lastRequestId); // "req_…"
108
+ ```
109
+
71
110
  ## Errors
72
111
 
73
- API errors throw `SongbrainError` with `status`, `code` and `message`. Subclasses: `AuthenticationError` (401), `InsufficientCredits` (402), `NotFound` (404), `RateLimited` (429, with `retryAfter` in seconds), `AnalysisFailed` (the song failed; credits are refunded) and `WaitTimeout` (the song keeps processing).
112
+ API errors throw `SongbrainError` with `status`, `code`, `message` and `requestId` (`req_…`; the message ends with `(request_id: req_…)`, quote it when you write to support). Subclasses: `AuthenticationError` (401), `InsufficientCredits` (402), `NotFound` (404), `RateLimited` (429, with `retryAfter` in seconds), `AnalysisFailed` (the song failed; credits are refunded) and `WaitTimeout` (the song keeps processing).
74
113
 
75
- The client retries 429 and 5xx responses up to 3 times with backoff and honours `Retry-After` (up to 60 s). Uploads are only retried when nothing can have been created (429, 502, 503).
114
+ The client retries network errors, 429 and 5xx responses up to 3 times with backoff and honours `Retry-After` (up to 60 s). Song creation is retried too, because it carries an `Idempotency-Key`.
76
115
 
77
116
  ```ts
78
117
  import { Songbrain, InsufficientCredits, RateLimited } from "songbrain";
@@ -99,13 +138,20 @@ app.post("/songbrain", express.raw({ type: "application/json" }), (req, res) =>
99
138
  return res.sendStatus(400);
100
139
  }
101
140
  const event = JSON.parse(req.body.toString("utf8"));
102
- // event.type: "song.done" | "song.failed" | "account.low_balance"
141
+ // event.id: "evt_…" (same on every retry), event.type: "song.done" | "song.failed" | "account.low_balance" | "ping"
103
142
  res.sendStatus(200);
104
143
  });
105
144
  ```
106
145
 
107
146
  `verifyWebhook(rawBody, header, secret, toleranceSec = 300)` returns a boolean. `constructWebhookEvent()` verifies and parses in one step.
108
147
 
148
+ Songbrain retries a failed delivery up to 10 times over about 3 days, with the same `event.id` every time. Store the ids you have handled and skip repeats. Check your receiver with a signed `ping`:
149
+
150
+ ```ts
151
+ await sb.testWebhook("https://example.com/songbrain"); // { delivered, status_code, latency_ms, event_id }
152
+ await sb.webhookDeliveries({ limit: 20 }); // the last delivery attempts
153
+ ```
154
+
109
155
  ## Pricing
110
156
 
111
157
  5 free songs per account per month. Then 25 credits per song (about $0.50). No subscription. Everything is included: analysis, story and shot plan. Results are yours, also in a paid or white-label product. See [Terms §17](https://www.songbrain.ai/terms#api).
package/dist/index.cjs CHANGED
@@ -10,12 +10,15 @@ var SongbrainError = class extends Error {
10
10
  code;
11
11
  /** The parsed response body, when there was one. */
12
12
  body;
13
- constructor(status, code, message, body) {
14
- super(message);
13
+ /** The `Songbrain-Request-Id` of the failed request (`req_…`), or null. */
14
+ requestId;
15
+ constructor(status, code, message, body, requestId = null) {
16
+ super(requestId ? `${message} (request_id: ${requestId})` : message);
15
17
  this.name = new.target.name;
16
18
  this.status = status;
17
19
  this.code = code;
18
20
  this.body = body;
21
+ this.requestId = requestId;
19
22
  Object.setPrototypeOf(this, new.target.prototype);
20
23
  }
21
24
  };
@@ -28,8 +31,8 @@ var NotFound = class extends SongbrainError {
28
31
  var RateLimited = class extends SongbrainError {
29
32
  /** Seconds to wait, from the Retry-After header (null if absent). */
30
33
  retryAfter;
31
- constructor(status, code, message, body, retryAfter = null) {
32
- super(status, code, message, body);
34
+ constructor(status, code, message, body, retryAfter = null, requestId = null) {
35
+ super(status, code, message, body, requestId);
33
36
  this.retryAfter = retryAfter;
34
37
  }
35
38
  };
@@ -53,10 +56,12 @@ var WaitTimeout = class extends SongbrainError {
53
56
  };
54
57
 
55
58
  // src/version.ts
56
- var VERSION = "0.1.2";
59
+ var VERSION = "0.3.0";
57
60
 
58
61
  // src/client.ts
59
62
  var DEFAULT_BASE_URL = "https://api.songbrain.ai/v1";
63
+ var REQUEST_ID_HEADER = "Songbrain-Request-Id";
64
+ var IDEMPOTENCY_HEADER = "Idempotency-Key";
60
65
  var MAX_RETRY_WAIT_SEC = 60;
61
66
  var MIME = {
62
67
  mp3: "audio/mpeg",
@@ -101,12 +106,47 @@ function retryAfterSeconds(value) {
101
106
  return Number.isNaN(when) ? null : Math.max(0, (when - Date.now()) / 1e3);
102
107
  }
103
108
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
109
+ function intHeader(value) {
110
+ if (value === null || value === "") return null;
111
+ const n = Number(value);
112
+ return Number.isFinite(n) ? Math.trunc(n) : null;
113
+ }
114
+ function rateLimitFrom(headers) {
115
+ const limit = intHeader(headers.get("X-RateLimit-Limit"));
116
+ if (limit === null) return null;
117
+ return {
118
+ limit,
119
+ remaining: intHeader(headers.get("X-RateLimit-Remaining")),
120
+ reset: intHeader(headers.get("X-RateLimit-Reset"))
121
+ };
122
+ }
123
+ async function newIdempotencyKey() {
124
+ const c = globalThis.crypto;
125
+ if (typeof c?.randomUUID === "function") return c.randomUUID();
126
+ try {
127
+ const { randomUUID } = await import('crypto');
128
+ return randomUUID();
129
+ } catch {
130
+ const hex = Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16));
131
+ hex[12] = "4";
132
+ hex[16] = (parseInt(hex[16], 16) & 3 | 8).toString(16);
133
+ const h = hex.join("");
134
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
135
+ }
136
+ }
104
137
  var Songbrain = class {
105
138
  apiKey;
106
139
  baseUrl;
107
140
  timeoutMs;
108
141
  maxRetries;
109
142
  fetchImpl;
143
+ /**
144
+ * `{ limit, remaining, reset }` from the `X-RateLimit-*` headers of the last response
145
+ * that had them (null before that). `reset` is seconds until the window has room.
146
+ */
147
+ lastRateLimit = null;
148
+ /** The `Songbrain-Request-Id` of the last response (null before the first one). */
149
+ lastRequestId = null;
110
150
  /** @internal Overridable in tests. */
111
151
  _sleep = sleep;
112
152
  constructor(options = {}) {
@@ -119,21 +159,30 @@ var Songbrain = class {
119
159
  this.fetchImpl = f;
120
160
  }
121
161
  async analyze(options) {
122
- const { file, audioUrl } = options;
123
- if (file === void 0 === (audioUrl === void 0)) {
162
+ const { file, audioUrl, test } = options;
163
+ if (file !== void 0 && audioUrl !== void 0) {
124
164
  throw new TypeError("Pass exactly one of `file` or `audioUrl`.");
125
165
  }
166
+ if (file === void 0 && audioUrl === void 0 && !test) {
167
+ throw new TypeError("Pass exactly one of `file` or `audioUrl` (or `test: true`).");
168
+ }
126
169
  const fields = {};
127
170
  if (options.title !== void 0) fields.title = options.title;
128
171
  if (options.artist !== void 0) fields.artist = options.artist;
129
172
  if (options.webhookUrl !== void 0) fields.webhook_url = options.webhookUrl;
130
173
  if (options.externalRef !== void 0) fields.external_ref = options.externalRef;
174
+ if (options.lyrics !== void 0) fields.lyrics = options.lyrics;
175
+ const headers = { [IDEMPOTENCY_HEADER]: options.idempotencyKey || await newIdempotencyKey() };
131
176
  let created;
132
- if (audioUrl !== void 0) {
133
- created = await this.request("POST", "/songs", { json: { audio_url: audioUrl, ...fields } });
177
+ if (file === void 0) {
178
+ const json = audioUrl !== void 0 ? { audio_url: audioUrl, ...fields } : { ...fields };
179
+ if (test) json.test = true;
180
+ created = await this.request("POST", "/songs", { json, headers });
134
181
  } else {
182
+ if (test) fields.test = "true";
135
183
  const { blob, name } = await toUpload(file, options.filename);
136
184
  created = await this.request("POST", "/songs", {
185
+ headers,
137
186
  form: () => {
138
187
  const form = new FormData();
139
188
  form.append("file", blob, name);
@@ -177,9 +226,28 @@ var Songbrain = class {
177
226
  shotPlan(songId) {
178
227
  return this.request("GET", `/songs/${encodeURIComponent(songId)}/shot-plan`);
179
228
  }
180
- /** Your songs, newest first. `limit` is 1-100 (default 20). */
229
+ /** One page of your songs, newest first: `{ object: "list", data, has_more, next_cursor }`. */
181
230
  listSongs(options = {}) {
182
- return this.request("GET", "/songs", { query: { limit: String(options.limit ?? 20) } });
231
+ return this.request("GET", "/songs", {
232
+ query: { limit: String(options.limit ?? 20), starting_after: options.startingAfter }
233
+ });
234
+ }
235
+ /**
236
+ * All your songs, newest first, fetching pages as needed.
237
+ *
238
+ * ```ts
239
+ * for await (const item of sb.iterSongs()) console.log(item.id, item.status);
240
+ * ```
241
+ */
242
+ async *iterSongs(options = {}) {
243
+ let cursor = options.startingAfter;
244
+ for (; ; ) {
245
+ const page = await this.listSongs({ limit: options.pageSize ?? 100, startingAfter: cursor });
246
+ const data = page.data ?? [];
247
+ yield* data;
248
+ cursor = page.next_cursor ?? data[data.length - 1]?.id;
249
+ if (!page.has_more || !cursor) return;
250
+ }
183
251
  }
184
252
  /** Delete a song's audio and analysis now. 409 `still_processing` while it runs. */
185
253
  deleteSong(songId) {
@@ -194,6 +262,20 @@ var Songbrain = class {
194
262
  pricing() {
195
263
  return this.request("GET", "/pricing");
196
264
  }
265
+ // ── webhooks ─────────────────────────────────────────────────────────────
266
+ /**
267
+ * Send a signed `ping` event to `url` right now:
268
+ * `{ delivered, status_code, latency_ms, event_id }`. Checks your receiver before real songs.
269
+ */
270
+ testWebhook(url) {
271
+ return this.request("POST", "/webhooks/test", { json: { url } });
272
+ }
273
+ /** The last webhook delivery attempts for your account, newest first. */
274
+ webhookDeliveries(options = {}) {
275
+ return this.request("GET", "/webhooks/deliveries", {
276
+ query: { limit: String(options.limit ?? 20) }
277
+ });
278
+ }
197
279
  // ── examples (no key) ────────────────────────────────────────────────────
198
280
  /** Example analyses of Songbrain's own songs. No key needed. */
199
281
  examples() {
@@ -214,13 +296,15 @@ var Songbrain = class {
214
296
  async request(method, path, opts = {}) {
215
297
  const url = new URL(this.baseUrl + path);
216
298
  for (const [k, v] of Object.entries(opts.query ?? {})) if (v !== void 0) url.searchParams.set(k, v);
217
- const idempotent = method !== "POST";
299
+ const hasKey = Boolean(opts.headers?.[IDEMPOTENCY_HEADER]);
300
+ const idempotent = method !== "POST" || hasKey;
218
301
  for (let attempt = 0; ; attempt++) {
219
302
  const headers = {
220
303
  Accept: "application/json",
221
304
  "User-Agent": `songbrain-js/${VERSION}`
222
305
  };
223
306
  if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
307
+ Object.assign(headers, opts.headers);
224
308
  let body;
225
309
  if (opts.json) {
226
310
  headers["Content-Type"] = "application/json";
@@ -239,6 +323,9 @@ var Songbrain = class {
239
323
  const message = err instanceof Error ? err.message : String(err);
240
324
  throw new SongbrainError(0, "connection_error", message);
241
325
  }
326
+ this.lastRequestId = res.headers.get(REQUEST_ID_HEADER) || null;
327
+ const rateLimit = rateLimitFrom(res.headers);
328
+ if (rateLimit) this.lastRateLimit = rateLimit;
242
329
  if (res.ok) {
243
330
  const text = await res.text();
244
331
  if (!text) return {};
@@ -249,10 +336,12 @@ var Songbrain = class {
249
336
  }
250
337
  }
251
338
  const error = await errorFrom(res);
252
- const retryable = res.status === 429 || res.status >= 500 && (idempotent || res.status === 502 || res.status === 503);
339
+ const inProgress = hasKey && res.status === 409 && error.code === "idempotency_in_progress";
340
+ const retryable = res.status === 429 || inProgress || res.status >= 500 && (idempotent || res.status === 502 || res.status === 503);
253
341
  if (retryable && attempt < this.maxRetries) {
254
342
  const after = error instanceof RateLimited ? error.retryAfter : null;
255
- const delayMs = after !== null ? after * 1e3 : this.backoffMs(attempt);
343
+ let delayMs = after !== null ? after * 1e3 : this.backoffMs(attempt);
344
+ if (inProgress) delayMs = Math.max(1e3, delayMs);
256
345
  if (delayMs <= MAX_RETRY_WAIT_SEC * 1e3) {
257
346
  await this._sleep(delayMs);
258
347
  continue;
@@ -271,6 +360,7 @@ async function errorFrom(res) {
271
360
  let body;
272
361
  let code = `http_${res.status}`;
273
362
  let message = text.trim().slice(0, 500) || res.statusText || `HTTP ${res.status}`;
363
+ let requestId = res.headers.get(REQUEST_ID_HEADER) || null;
274
364
  try {
275
365
  body = JSON.parse(text);
276
366
  } catch {
@@ -282,6 +372,7 @@ async function errorFrom(res) {
282
372
  const e = err;
283
373
  if (e.code) code = String(e.code);
284
374
  if (e.message) message = String(e.message);
375
+ if (e.request_id) requestId = String(e.request_id);
285
376
  } else if ("detail" in body) {
286
377
  code = "validation_error";
287
378
  message = JSON.stringify(body.detail);
@@ -289,15 +380,15 @@ async function errorFrom(res) {
289
380
  }
290
381
  switch (res.status) {
291
382
  case 401:
292
- return new AuthenticationError(401, code, message, body);
383
+ return new AuthenticationError(401, code, message, body, requestId);
293
384
  case 402:
294
- return new InsufficientCredits(402, code, message, body);
385
+ return new InsufficientCredits(402, code, message, body, requestId);
295
386
  case 404:
296
- return new NotFound(404, code, message, body);
387
+ return new NotFound(404, code, message, body, requestId);
297
388
  case 429:
298
- return new RateLimited(429, code, message, body, retryAfterSeconds(res.headers.get("Retry-After")));
389
+ return new RateLimited(429, code, message, body, retryAfterSeconds(res.headers.get("Retry-After")), requestId);
299
390
  default:
300
- return new SongbrainError(res.status, code, message, body);
391
+ return new SongbrainError(res.status, code, message, body, requestId);
301
392
  }
302
393
  }
303
394
  async function toUpload(file, filename) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/version.ts","../src/client.ts","../src/webhooks.ts"],"names":["createHmac","timingSafeEqual"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Every API error has the shape `{ error: { code, message } }`. The client\n * turns it into a `SongbrainError` (or a subclass) with `status`, `code` and\n * `message`.\n */\nexport class SongbrainError extends Error {\n /** HTTP status, or 0 when the error did not come from an HTTP response. */\n readonly status: number;\n /** Machine-readable code, e.g. \"invalid_url\" or \"rate_limited\". */\n readonly code: string;\n /** The parsed response body, when there was one. */\n readonly body: unknown;\n\n constructor(status: number, code: string, message: string, body?: unknown) {\n super(message);\n this.name = new.target.name;\n this.status = status;\n this.code = code;\n this.body = body;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** 401: the API key is missing, invalid or revoked. */\nexport class AuthenticationError extends SongbrainError {}\n\n/** 402: the free songs for this month are used up and the balance is below one song. */\nexport class InsufficientCredits extends SongbrainError {}\n\n/** 404: unknown id, or the song belongs to another account. */\nexport class NotFound extends SongbrainError {}\n\n/** 429: too many requests, too many songs in flight, daily cap or busy pipeline. */\nexport class RateLimited extends SongbrainError {\n /** Seconds to wait, from the Retry-After header (null if absent). */\n readonly retryAfter: number | null;\n\n constructor(status: number, code: string, message: string, body?: unknown, retryAfter: number | null = null) {\n super(status, code, message, body);\n this.retryAfter = retryAfter;\n }\n}\n\n/** The song was accepted but its analysis failed. Paid credits are refunded automatically. */\nexport class AnalysisFailed extends SongbrainError {\n readonly songId: string;\n\n constructor(songId: string, code: string, message: string, body?: unknown) {\n super(0, code, message, body);\n this.songId = songId;\n }\n}\n\n/** `analyze({ wait: true })` gave up before the song was done. It keeps processing on the server. */\nexport class WaitTimeout extends SongbrainError {\n readonly songId: string;\n\n constructor(songId: string, timeoutMs: number) {\n super(\n 0,\n \"wait_timeout\",\n `Song ${songId} was not done after ${Math.round(timeoutMs / 1000)} s. It is still processing; fetch it later with getSong(\"${songId}\").`,\n );\n this.songId = songId;\n }\n}\n","export const VERSION = \"0.1.2\";\n","import {\n AnalysisFailed,\n AuthenticationError,\n InsufficientCredits,\n NotFound,\n RateLimited,\n SongbrainError,\n WaitTimeout,\n} from \"./errors.js\";\nimport type {\n Account,\n DeleteResult,\n ExampleList,\n Pricing,\n SectionName,\n Song,\n SongCreated,\n SongList,\n SongShotPlan,\n} from \"./types.js\";\nimport { VERSION } from \"./version.js\";\n\nexport const DEFAULT_BASE_URL = \"https://api.songbrain.ai/v1\";\n\n/** A Blob/File, a Buffer or bytes, or a local file path (Node). */\nexport type FileInput = Blob | Uint8Array | ArrayBuffer | string;\n\nexport interface SongbrainOptions {\n /** Your key (`sb_live_…`). Defaults to `process.env.SONGBRAIN_API_KEY`. The example endpoints work without a key. */\n apiKey?: string;\n /** Defaults to `process.env.SONGBRAIN_BASE_URL` or https://api.songbrain.ai/v1. */\n baseUrl?: string;\n /** Timeout for each HTTP request in ms (uploads included). Default 60 000. */\n timeoutMs?: number;\n /** How often to retry 429 and 5xx responses, with backoff. Default 3. */\n maxRetries?: number;\n /** A custom fetch implementation. Defaults to the global fetch. */\n fetch?: typeof fetch;\n}\n\nexport interface AnalyzeOptions {\n /** A Blob/File, a Buffer or bytes, or a local file path. Pass this or `audioUrl`. */\n file?: FileInput;\n /** A public http(s) URL of the audio file. Pass this or `file`. */\n audioUrl?: string;\n title?: string;\n artist?: string;\n /** Receives `song.done` / `song.failed`. */\n webhookUrl?: string;\n /** Your own id, echoed back in responses and webhooks. */\n externalRef?: string;\n /** File name for an upload. Its extension must match the audio format. Taken from the path or File name, or guessed from the bytes. */\n filename?: string;\n /** Wait until the song is done and return the full document (default true). */\n wait?: boolean;\n /** Time between status checks while waiting. Default 5 000. */\n pollIntervalMs?: number;\n /** Total time to wait before throwing `WaitTimeout`. Default 300 000. */\n timeoutMs?: number;\n}\n\nexport interface WaitOptions {\n pollIntervalMs?: number;\n timeoutMs?: number;\n}\n\nexport interface GetSongOptions {\n /** \"summary\" drops word timings and beat arrays (about 3x smaller). */\n view?: \"full\" | \"summary\";\n /** Only these sections, e.g. [\"song_dna\", \"shot_plan\"]. */\n include?: SectionName[] | string;\n}\n\ntype Json = Record<string, unknown>;\n\nconst MAX_RETRY_WAIT_SEC = 60;\nconst MIME: Record<string, string> = {\n mp3: \"audio/mpeg\",\n wav: \"audio/wav\",\n flac: \"audio/flac\",\n ogg: \"audio/ogg\",\n m4a: \"audio/mp4\",\n aac: \"audio/aac\",\n aiff: \"audio/aiff\",\n aif: \"audio/aiff\",\n};\n\nfunction env(name: string): string | undefined {\n const p = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process;\n return p?.env?.[name] || undefined;\n}\n\n/** Guess the audio format from the first bytes (the API checks the extension against the contents). */\nexport function sniffExtension(head: Uint8Array): string | null {\n const ascii = (from: number, to: number) => String.fromCharCode(...head.subarray(from, to));\n if (ascii(0, 3) === \"ID3\") return \"mp3\";\n if (ascii(0, 4) === \"RIFF\" && ascii(8, 12) === \"WAVE\") return \"wav\";\n if (ascii(0, 4) === \"fLaC\") return \"flac\";\n if (ascii(0, 4) === \"OggS\") return \"ogg\";\n if (ascii(0, 4) === \"FORM\" && [\"AIFF\", \"AIFC\"].includes(ascii(8, 12))) return \"aiff\";\n if (ascii(4, 8) === \"ftyp\") return \"m4a\";\n const b0 = head[0];\n const b1 = head[1];\n if (b0 === 0xff && b1 !== undefined && (b1 & 0xe0) === 0xe0) return (b1 & 0x06) === 0 ? \"aac\" : \"mp3\";\n return null;\n}\n\nfunction extOf(name: string): string {\n const base = name.split(/[\\\\/]/).pop() ?? \"\";\n const i = base.lastIndexOf(\".\");\n return i > 0 ? base.slice(i + 1).toLowerCase() : \"\";\n}\n\nfunction baseName(name: string): string {\n return name.split(/[\\\\/]/).pop() ?? name;\n}\n\nfunction retryAfterSeconds(value: string | null): number | null {\n if (!value) return null;\n const n = Number(value);\n if (Number.isFinite(n)) return Math.max(0, n);\n const when = Date.parse(value);\n return Number.isNaN(when) ? null : Math.max(0, (when - Date.now()) / 1000);\n}\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\n/**\n * Client for the Songbrain API. Every method resolves to the JSON body of\n * the response.\n *\n * ```ts\n * const sb = new Songbrain(); // reads SONGBRAIN_API_KEY\n * const song = await sb.analyze({ file: \"song.mp3\" });\n * for (const s of song.shot_plan?.clip?.scenes ?? []) console.log(s.start_sec, s.prompt);\n * ```\n */\nexport class Songbrain {\n readonly apiKey: string | undefined;\n readonly baseUrl: string;\n readonly timeoutMs: number;\n readonly maxRetries: number;\n private readonly fetchImpl: typeof fetch;\n /** @internal Overridable in tests. */\n _sleep: (ms: number) => Promise<void> = sleep;\n\n constructor(options: SongbrainOptions = {}) {\n this.apiKey = options.apiKey !== undefined ? options.apiKey || undefined : env(\"SONGBRAIN_API_KEY\");\n this.baseUrl = (options.baseUrl ?? env(\"SONGBRAIN_BASE_URL\") ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.timeoutMs = options.timeoutMs ?? 60_000;\n this.maxRetries = Math.max(0, options.maxRetries ?? 3);\n const f = options.fetch ?? (globalThis.fetch as typeof fetch | undefined);\n if (!f) throw new Error(\"No global fetch found. Use Node 18+ or pass `fetch` in the options.\");\n this.fetchImpl = f;\n }\n\n // ── songs ────────────────────────────────────────────────────────────────\n\n /**\n * Analyse a song from a file or a public URL. Pass exactly one of `file` or `audioUrl`.\n * Typically 60-90 s. With `wait: true` (default) resolves to the full document;\n * with `wait: false` to the 202 body `{ id, status: \"processing\", eta_sec, billing }`.\n */\n analyze(options: AnalyzeOptions & { wait: false }): Promise<SongCreated>;\n analyze(options: AnalyzeOptions & { wait?: true }): Promise<Song>;\n analyze(options: AnalyzeOptions): Promise<Song | SongCreated>;\n async analyze(options: AnalyzeOptions): Promise<Song | SongCreated> {\n const { file, audioUrl } = options;\n if ((file === undefined) === (audioUrl === undefined)) {\n throw new TypeError(\"Pass exactly one of `file` or `audioUrl`.\");\n }\n const fields: Record<string, string> = {};\n if (options.title !== undefined) fields.title = options.title;\n if (options.artist !== undefined) fields.artist = options.artist;\n if (options.webhookUrl !== undefined) fields.webhook_url = options.webhookUrl;\n if (options.externalRef !== undefined) fields.external_ref = options.externalRef;\n\n let created: SongCreated;\n if (audioUrl !== undefined) {\n created = await this.request<SongCreated>(\"POST\", \"/songs\", { json: { audio_url: audioUrl, ...fields } });\n } else {\n const { blob, name } = await toUpload(file as FileInput, options.filename);\n created = await this.request<SongCreated>(\"POST\", \"/songs\", {\n form: () => {\n const form = new FormData();\n form.append(\"file\", blob, name);\n for (const [k, v] of Object.entries(fields)) form.append(k, v);\n return form;\n },\n });\n }\n if (options.wait === false) return created;\n return this.waitFor(created.id, {\n pollIntervalMs: options.pollIntervalMs,\n timeoutMs: options.timeoutMs,\n });\n }\n\n /** Poll GET /songs/{id} until the song is done. Throws `AnalysisFailed` or `WaitTimeout`. */\n async waitFor(songId: string, options: WaitOptions = {}): Promise<Song> {\n const timeoutMs = options.timeoutMs ?? 300_000;\n const interval = Math.max(500, options.pollIntervalMs ?? 5_000);\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n const doc = await this.getSong(songId);\n if (doc.status === \"done\") return doc;\n if (doc.status === \"failed\") {\n throw new AnalysisFailed(\n songId,\n doc.error?.code ?? \"analysis_failed\",\n doc.error?.message ?? \"The analysis failed. Paid credits were refunded automatically.\",\n doc,\n );\n }\n const remaining = deadline - Date.now();\n if (remaining <= 0) throw new WaitTimeout(songId, timeoutMs);\n await this._sleep(Math.min(interval, remaining));\n }\n }\n\n /** Status while processing, then the full document. */\n getSong(songId: string, options: GetSongOptions = {}): Promise<Song> {\n return this.request<Song>(\"GET\", `/songs/${encodeURIComponent(songId)}`, { query: docQuery(options) });\n }\n\n /** Only the story and the shot plan: `{ id, status, song, story, shot_plan }`. */\n shotPlan(songId: string): Promise<SongShotPlan> {\n return this.request<SongShotPlan>(\"GET\", `/songs/${encodeURIComponent(songId)}/shot-plan`);\n }\n\n /** Your songs, newest first. `limit` is 1-100 (default 20). */\n listSongs(options: { limit?: number } = {}): Promise<SongList> {\n return this.request<SongList>(\"GET\", \"/songs\", { query: { limit: String(options.limit ?? 20) } });\n }\n\n /** Delete a song's audio and analysis now. 409 `still_processing` while it runs. */\n deleteSong(songId: string): Promise<DeleteResult> {\n return this.request<DeleteResult>(\"DELETE\", `/songs/${encodeURIComponent(songId)}`);\n }\n\n // ── account ──────────────────────────────────────────────────────────────\n\n /** Free songs left this month, credit balance and price per song. */\n account(): Promise<Account> {\n return this.request<Account>(\"GET\", \"/account\");\n }\n\n /** Prices and limits. No key needed. */\n pricing(): Promise<Pricing> {\n return this.request<Pricing>(\"GET\", \"/pricing\");\n }\n\n // ── examples (no key) ────────────────────────────────────────────────────\n\n /** Example analyses of Songbrain's own songs. No key needed. */\n examples(): Promise<ExampleList> {\n return this.request<ExampleList>(\"GET\", \"/examples\");\n }\n\n /** A full example document, in the same format as `getSong`. No key needed. */\n example(exampleId: string, options: GetSongOptions = {}): Promise<Song> {\n return this.request<Song>(\"GET\", `/examples/${encodeURIComponent(exampleId)}`, { query: docQuery(options) });\n }\n\n /** An example story and shot plan, in the same format as `shotPlan`. No key needed. */\n exampleShotPlan(exampleId: string): Promise<SongShotPlan> {\n return this.request<SongShotPlan>(\"GET\", `/examples/${encodeURIComponent(exampleId)}/shot-plan`);\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private backoffMs(attempt: number): number {\n return Math.min(500 * 2 ** attempt, 8_000) + Math.random() * 250;\n }\n\n private async request<T>(\n method: \"GET\" | \"POST\" | \"DELETE\",\n path: string,\n opts: { query?: Record<string, string | undefined>; json?: Json; form?: () => FormData } = {},\n ): Promise<T> {\n const url = new URL(this.baseUrl + path);\n for (const [k, v] of Object.entries(opts.query ?? {})) if (v !== undefined) url.searchParams.set(k, v);\n const idempotent = method !== \"POST\";\n\n for (let attempt = 0; ; attempt++) {\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"User-Agent\": `songbrain-js/${VERSION}`,\n };\n if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;\n let body: BodyInit | undefined;\n if (opts.json) {\n headers[\"Content-Type\"] = \"application/json\";\n body = JSON.stringify(opts.json);\n } else if (opts.form) {\n body = opts.form(); // fresh FormData per attempt; fetch sets the boundary\n }\n\n let res: Response;\n try {\n res = await this.fetchImpl(url, { method, headers, body, signal: AbortSignal.timeout(this.timeoutMs) });\n } catch (err) {\n // Network errors and timeouts: only resend if nothing can have been created.\n if (idempotent && attempt < this.maxRetries) {\n await this._sleep(this.backoffMs(attempt));\n continue;\n }\n const message = err instanceof Error ? err.message : String(err);\n throw new SongbrainError(0, \"connection_error\", message);\n }\n\n if (res.ok) {\n const text = await res.text();\n if (!text) return {} as T;\n try {\n return JSON.parse(text) as T;\n } catch {\n throw new SongbrainError(res.status, \"invalid_response\", \"Response is not JSON.\");\n }\n }\n\n const error = await errorFrom(res);\n // 429 never started any work. For POST only retry gateway errors (502/503).\n const retryable =\n res.status === 429 || (res.status >= 500 && (idempotent || res.status === 502 || res.status === 503));\n if (retryable && attempt < this.maxRetries) {\n const after = error instanceof RateLimited ? error.retryAfter : null;\n const delayMs = after !== null ? after * 1000 : this.backoffMs(attempt);\n if (delayMs <= MAX_RETRY_WAIT_SEC * 1000) {\n await this._sleep(delayMs);\n continue;\n }\n }\n throw error;\n }\n }\n}\n\nfunction docQuery(options: GetSongOptions): Record<string, string | undefined> {\n const include = Array.isArray(options.include) ? options.include.join(\",\") : options.include;\n return { view: options.view, include };\n}\n\nasync function errorFrom(res: Response): Promise<SongbrainError> {\n const text = await res.text().catch(() => \"\");\n let body: unknown;\n let code = `http_${res.status}`;\n let message = text.trim().slice(0, 500) || res.statusText || `HTTP ${res.status}`;\n try {\n body = JSON.parse(text);\n } catch {\n body = undefined;\n }\n if (body && typeof body === \"object\") {\n const err = (body as { error?: unknown }).error;\n if (err && typeof err === \"object\") {\n const e = err as { code?: unknown; message?: unknown };\n if (e.code) code = String(e.code);\n if (e.message) message = String(e.message);\n } else if (\"detail\" in (body as object)) {\n code = \"validation_error\";\n message = JSON.stringify((body as { detail: unknown }).detail);\n }\n }\n switch (res.status) {\n case 401:\n return new AuthenticationError(401, code, message, body);\n case 402:\n return new InsufficientCredits(402, code, message, body);\n case 404:\n return new NotFound(404, code, message, body);\n case 429:\n return new RateLimited(429, code, message, body, retryAfterSeconds(res.headers.get(\"Retry-After\")));\n default:\n return new SongbrainError(res.status, code, message, body);\n }\n}\n\nasync function toUpload(file: FileInput, filename?: string): Promise<{ blob: Blob; name: string }> {\n let blob: Blob;\n let sourceName: string | undefined;\n if (typeof file === \"string\") {\n const { readFile } = await import(\"node:fs/promises\");\n const data = await readFile(file);\n blob = new Blob([data as BlobPart]);\n sourceName = baseName(file);\n } else if (file instanceof ArrayBuffer) {\n blob = new Blob([file]);\n } else if (file instanceof Uint8Array) {\n blob = new Blob([file as BlobPart]);\n } else if (typeof Blob !== \"undefined\" && file instanceof Blob) {\n blob = file;\n const n = (file as Blob & { name?: unknown }).name;\n if (typeof n === \"string\" && n) sourceName = baseName(n);\n } else {\n throw new TypeError(\"`file` must be a Blob, a Buffer/Uint8Array, an ArrayBuffer or a file path.\");\n }\n\n let name = filename ?? sourceName;\n if (!name || !extOf(name)) {\n const head = new Uint8Array(await blob.slice(0, 16).arrayBuffer());\n const ext = sniffExtension(head);\n if (!ext) {\n throw new TypeError('Could not tell the audio format. Pass `filename` with an extension, e.g. filename: \"song.mp3\".');\n }\n name = `${(name ?? \"song\").replace(/\\.$/, \"\")}.${ext}`;\n }\n const type = MIME[extOf(name)];\n if (type && !blob.type) blob = new Blob([blob], { type });\n return { blob, name };\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport type { WebhookEvent } from \"./types.js\";\n\n/** The header Songbrain signs webhooks with. */\nexport const SIGNATURE_HEADER = \"Songbrain-Signature\";\n\ntype RawBody = string | Uint8Array | ArrayBuffer;\n\nfunction toBuffer(body: RawBody): Buffer {\n if (typeof body === \"string\") return Buffer.from(body, \"utf8\");\n if (body instanceof ArrayBuffer) return Buffer.from(new Uint8Array(body));\n return Buffer.from(body.buffer, body.byteOffset, body.byteLength);\n}\n\nfunction parseHeader(header: string): { t: number | null; v1: string[] } {\n let t: number | null = null;\n const v1: string[] = [];\n for (const part of header.split(\",\")) {\n const i = part.indexOf(\"=\");\n if (i < 0) continue;\n const key = part.slice(0, i).trim();\n const value = part.slice(i + 1).trim();\n if (key === \"t\") {\n if (!/^\\d+$/.test(value)) return { t: null, v1: [] };\n t = Number(value);\n } else if (key === \"v1\" && value) {\n v1.push(value.toLowerCase());\n }\n }\n return { t, v1 };\n}\n\nfunction mac(body: Buffer, t: number, secret: string): string {\n return createHmac(\"sha256\", secret).update(`${t}.`).update(body).digest(\"hex\");\n}\n\n/**\n * Check a `Songbrain-Signature: t=<unix>,v1=<hex>` header, where v1 is the\n * HMAC-SHA256 of `\"<t>.<raw body>\"` with your key's webhook secret.\n *\n * Pass the raw request body exactly as received, before any JSON parsing.\n * Returns false (never throws) for a missing, malformed, wrong or old signature.\n *\n * @param toleranceSec Maximum age in seconds (default 300). 0 disables the check.\n */\nexport function verifyWebhook(\n rawBody: RawBody,\n header: string | null | undefined,\n secret: string,\n toleranceSec = 300,\n): boolean {\n if (!header || !secret) return false;\n const { t, v1 } = parseHeader(header);\n if (t === null || v1.length === 0) return false;\n if (toleranceSec > 0 && Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;\n const expected = Buffer.from(mac(toBuffer(rawBody), t, secret), \"utf8\");\n return v1.some((sig) => {\n const given = Buffer.from(sig, \"utf8\");\n return given.length === expected.length && timingSafeEqual(given, expected);\n });\n}\n\n/**\n * Verify the signature and return the parsed event:\n * `{ type: \"song.done\" | \"song.failed\" | \"account.low_balance\", created, data }`.\n * Throws if the signature is not valid.\n */\nexport function constructWebhookEvent<T = Record<string, unknown>>(\n rawBody: RawBody,\n header: string | null | undefined,\n secret: string,\n toleranceSec = 300,\n): WebhookEvent<T> {\n if (!verifyWebhook(rawBody, header, secret, toleranceSec)) {\n throw new Error(\"Invalid Songbrain-Signature\");\n }\n return JSON.parse(toBuffer(rawBody).toString(\"utf8\")) as WebhookEvent<T>;\n}\n\n/** Build a signature header value. Useful to test your webhook handler. */\nexport function signWebhook(rawBody: RawBody, secret: string, timestampSec = Math.floor(Date.now() / 1000)): string {\n return `t=${timestampSec},v1=${mac(toBuffer(rawBody), timestampSec, secret)}`;\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/version.ts","../src/client.ts","../src/webhooks.ts"],"names":["createHmac","timingSafeEqual"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Every API error has the shape `{ error: { code, message, request_id } }`.\n * The client turns it into a `SongbrainError` (or a subclass) with `status`,\n * `code`, `message` and `requestId`. The message ends with\n * ` (request_id: req_…)` when there is one; quote it when you contact support.\n */\nexport class SongbrainError extends Error {\n /** HTTP status, or 0 when the error did not come from an HTTP response. */\n readonly status: number;\n /** Machine-readable code, e.g. \"invalid_url\" or \"rate_limited\". */\n readonly code: string;\n /** The parsed response body, when there was one. */\n readonly body: unknown;\n /** The `Songbrain-Request-Id` of the failed request (`req_…`), or null. */\n readonly requestId: string | null;\n\n constructor(status: number, code: string, message: string, body?: unknown, requestId: string | null = null) {\n super(requestId ? `${message} (request_id: ${requestId})` : message);\n this.name = new.target.name;\n this.status = status;\n this.code = code;\n this.body = body;\n this.requestId = requestId;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** 401: the API key is missing, invalid or revoked. */\nexport class AuthenticationError extends SongbrainError {}\n\n/** 402: the free songs for this month are used up and the balance is below one song. */\nexport class InsufficientCredits extends SongbrainError {}\n\n/** 404: unknown id, or the song belongs to another account. */\nexport class NotFound extends SongbrainError {}\n\n/** 429: too many requests, too many songs in flight, daily cap or busy pipeline. */\nexport class RateLimited extends SongbrainError {\n /** Seconds to wait, from the Retry-After header (null if absent). */\n readonly retryAfter: number | null;\n\n constructor(\n status: number,\n code: string,\n message: string,\n body?: unknown,\n retryAfter: number | null = null,\n requestId: string | null = null,\n ) {\n super(status, code, message, body, requestId);\n this.retryAfter = retryAfter;\n }\n}\n\n/** The song was accepted but its analysis failed. Paid credits are refunded automatically. */\nexport class AnalysisFailed extends SongbrainError {\n readonly songId: string;\n\n constructor(songId: string, code: string, message: string, body?: unknown) {\n super(0, code, message, body);\n this.songId = songId;\n }\n}\n\n/** `analyze({ wait: true })` gave up before the song was done. It keeps processing on the server. */\nexport class WaitTimeout extends SongbrainError {\n readonly songId: string;\n\n constructor(songId: string, timeoutMs: number) {\n super(\n 0,\n \"wait_timeout\",\n `Song ${songId} was not done after ${Math.round(timeoutMs / 1000)} s. It is still processing; fetch it later with getSong(\"${songId}\").`,\n );\n this.songId = songId;\n }\n}\n","export const VERSION = \"0.3.0\";\n","import {\n AnalysisFailed,\n AuthenticationError,\n InsufficientCredits,\n NotFound,\n RateLimited,\n SongbrainError,\n WaitTimeout,\n} from \"./errors.js\";\nimport type {\n Account,\n DeleteResult,\n ExampleList,\n Pricing,\n RateLimitInfo,\n SectionName,\n Song,\n SongCreated,\n SongList,\n SongListItem,\n SongShotPlan,\n WebhookDeliveryList,\n WebhookTestResult,\n} from \"./types.js\";\nimport { VERSION } from \"./version.js\";\n\nexport const DEFAULT_BASE_URL = \"https://api.songbrain.ai/v1\";\nconst REQUEST_ID_HEADER = \"Songbrain-Request-Id\";\nconst IDEMPOTENCY_HEADER = \"Idempotency-Key\";\n\n/** A Blob/File, a Buffer or bytes, or a local file path (Node). */\nexport type FileInput = Blob | Uint8Array | ArrayBuffer | string;\n\nexport interface SongbrainOptions {\n /** Your key (`sb_live_…`). Defaults to `process.env.SONGBRAIN_API_KEY`. The example endpoints work without a key. */\n apiKey?: string;\n /** Defaults to `process.env.SONGBRAIN_BASE_URL` or https://api.songbrain.ai/v1. */\n baseUrl?: string;\n /** Timeout for each HTTP request in ms (uploads included). Default 60 000. */\n timeoutMs?: number;\n /**\n * How often to retry network errors, 429 and 5xx responses, with backoff. Default 3.\n * Song creation is retried too, because every create sends an `Idempotency-Key`.\n */\n maxRetries?: number;\n /** A custom fetch implementation. Defaults to the global fetch. */\n fetch?: typeof fetch;\n}\n\nexport interface AnalyzeOptions {\n /** A Blob/File, a Buffer or bytes, or a local file path. Pass this or `audioUrl`. */\n file?: FileInput;\n /** A public http(s) URL of the audio file. Pass this or `file`. */\n audioUrl?: string;\n title?: string;\n artist?: string;\n /** Receives `song.done` / `song.failed`. */\n webhookUrl?: string;\n /** Your own id, echoed back in responses and webhooks. */\n externalRef?: string;\n /** File name for an upload. Its extension must match the audio format. Taken from the path or File name, or guessed from the bytes. */\n filename?: string;\n /**\n * The song's lyrics as plain text (up to 20,000 characters), e.g. from Suno. The\n * analysis then uses your exact words on the transcription's timing\n * (`lyrics.source` = \"provided_lyrics\"); words the singer can't be heard on are\n * left out, never guessed.\n */\n lyrics?: string;\n /**\n * Test mode: free, never charged, no audio needed. The song is done right away and\n * returns the Sugar Rush example analysis with `livemode: false`. `song.done` is\n * still sent to `webhookUrl`. Made for CI and integration tests.\n */\n test?: boolean;\n /**\n * Sent as `Idempotency-Key`. By default a random UUID per call, reused across the\n * client's own retries, so a retried create never makes a second song. Pass your\n * own (e.g. your job id) to make retries across processes safe too (24 h window).\n */\n idempotencyKey?: string;\n /** Wait until the song is done and return the full document (default true). */\n wait?: boolean;\n /** Time between status checks while waiting. Default 5 000. */\n pollIntervalMs?: number;\n /** Total time to wait before throwing `WaitTimeout`. Default 300 000. */\n timeoutMs?: number;\n}\n\nexport interface WaitOptions {\n pollIntervalMs?: number;\n timeoutMs?: number;\n}\n\nexport interface ListSongsOptions {\n /** Page size, 1-100. Default 20. */\n limit?: number;\n /** A song id, usually the previous page's `next_cursor`. Returns the songs after it. */\n startingAfter?: string;\n}\n\nexport interface IterSongsOptions {\n /** Songs per request, 1-100. Default 100. */\n pageSize?: number;\n /** Start after this song id. */\n startingAfter?: string;\n}\n\nexport interface GetSongOptions {\n /** \"summary\" drops word timings and beat arrays (about 3x smaller). */\n view?: \"full\" | \"summary\";\n /** Only these sections, e.g. [\"song_dna\", \"shot_plan\"]. */\n include?: SectionName[] | string;\n}\n\ntype Json = Record<string, unknown>;\n\nconst MAX_RETRY_WAIT_SEC = 60;\nconst MIME: Record<string, string> = {\n mp3: \"audio/mpeg\",\n wav: \"audio/wav\",\n flac: \"audio/flac\",\n ogg: \"audio/ogg\",\n m4a: \"audio/mp4\",\n aac: \"audio/aac\",\n aiff: \"audio/aiff\",\n aif: \"audio/aiff\",\n};\n\nfunction env(name: string): string | undefined {\n const p = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process;\n return p?.env?.[name] || undefined;\n}\n\n/** Guess the audio format from the first bytes (the API checks the extension against the contents). */\nexport function sniffExtension(head: Uint8Array): string | null {\n const ascii = (from: number, to: number) => String.fromCharCode(...head.subarray(from, to));\n if (ascii(0, 3) === \"ID3\") return \"mp3\";\n if (ascii(0, 4) === \"RIFF\" && ascii(8, 12) === \"WAVE\") return \"wav\";\n if (ascii(0, 4) === \"fLaC\") return \"flac\";\n if (ascii(0, 4) === \"OggS\") return \"ogg\";\n if (ascii(0, 4) === \"FORM\" && [\"AIFF\", \"AIFC\"].includes(ascii(8, 12))) return \"aiff\";\n if (ascii(4, 8) === \"ftyp\") return \"m4a\";\n const b0 = head[0];\n const b1 = head[1];\n if (b0 === 0xff && b1 !== undefined && (b1 & 0xe0) === 0xe0) return (b1 & 0x06) === 0 ? \"aac\" : \"mp3\";\n return null;\n}\n\nfunction extOf(name: string): string {\n const base = name.split(/[\\\\/]/).pop() ?? \"\";\n const i = base.lastIndexOf(\".\");\n return i > 0 ? base.slice(i + 1).toLowerCase() : \"\";\n}\n\nfunction baseName(name: string): string {\n return name.split(/[\\\\/]/).pop() ?? name;\n}\n\nfunction retryAfterSeconds(value: string | null): number | null {\n if (!value) return null;\n const n = Number(value);\n if (Number.isFinite(n)) return Math.max(0, n);\n const when = Date.parse(value);\n return Number.isNaN(when) ? null : Math.max(0, (when - Date.now()) / 1000);\n}\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\nfunction intHeader(value: string | null): number | null {\n if (value === null || value === \"\") return null;\n const n = Number(value);\n return Number.isFinite(n) ? Math.trunc(n) : null;\n}\n\nfunction rateLimitFrom(headers: Headers): RateLimitInfo | null {\n const limit = intHeader(headers.get(\"X-RateLimit-Limit\"));\n if (limit === null) return null;\n return {\n limit,\n remaining: intHeader(headers.get(\"X-RateLimit-Remaining\")),\n reset: intHeader(headers.get(\"X-RateLimit-Reset\")),\n };\n}\n\n/** A random UUID v4: Web Crypto where available (browsers, Node 19+), node:crypto on Node 18. */\nasync function newIdempotencyKey(): Promise<string> {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (typeof c?.randomUUID === \"function\") return c.randomUUID();\n try {\n const { randomUUID } = await import(\"node:crypto\");\n return randomUUID();\n } catch {\n const hex = Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16));\n hex[12] = \"4\";\n hex[16] = ((parseInt(hex[16] as string, 16) & 0x3) | 0x8).toString(16);\n const h = hex.join(\"\");\n return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;\n }\n}\n\n/**\n * Client for the Songbrain API. Every method resolves to the JSON body of\n * the response.\n *\n * ```ts\n * const sb = new Songbrain(); // reads SONGBRAIN_API_KEY\n * const song = await sb.analyze({ file: \"song.mp3\" });\n * for (const s of song.shot_plan?.clip?.scenes ?? []) console.log(s.start_sec, s.prompt);\n * ```\n */\nexport class Songbrain {\n readonly apiKey: string | undefined;\n readonly baseUrl: string;\n readonly timeoutMs: number;\n readonly maxRetries: number;\n private readonly fetchImpl: typeof fetch;\n /**\n * `{ limit, remaining, reset }` from the `X-RateLimit-*` headers of the last response\n * that had them (null before that). `reset` is seconds until the window has room.\n */\n lastRateLimit: RateLimitInfo | null = null;\n /** The `Songbrain-Request-Id` of the last response (null before the first one). */\n lastRequestId: string | null = null;\n /** @internal Overridable in tests. */\n _sleep: (ms: number) => Promise<void> = sleep;\n\n constructor(options: SongbrainOptions = {}) {\n this.apiKey = options.apiKey !== undefined ? options.apiKey || undefined : env(\"SONGBRAIN_API_KEY\");\n this.baseUrl = (options.baseUrl ?? env(\"SONGBRAIN_BASE_URL\") ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.timeoutMs = options.timeoutMs ?? 60_000;\n this.maxRetries = Math.max(0, options.maxRetries ?? 3);\n const f = options.fetch ?? (globalThis.fetch as typeof fetch | undefined);\n if (!f) throw new Error(\"No global fetch found. Use Node 18+ or pass `fetch` in the options.\");\n this.fetchImpl = f;\n }\n\n // ── songs ────────────────────────────────────────────────────────────────\n\n /**\n * Analyse a song from a file or a public URL. Pass exactly one of `file` or `audioUrl`\n * (or neither with `test: true`).\n * Typically 60-90 s. With `wait: true` (default) resolves to the full document;\n * with `wait: false` to the 202 body `{ id, status: \"processing\", eta_sec, billing }`.\n */\n analyze(options: AnalyzeOptions & { wait: false }): Promise<SongCreated>;\n analyze(options: AnalyzeOptions & { wait?: true }): Promise<Song>;\n analyze(options: AnalyzeOptions): Promise<Song | SongCreated>;\n async analyze(options: AnalyzeOptions): Promise<Song | SongCreated> {\n const { file, audioUrl, test } = options;\n if (file !== undefined && audioUrl !== undefined) {\n throw new TypeError(\"Pass exactly one of `file` or `audioUrl`.\");\n }\n if (file === undefined && audioUrl === undefined && !test) {\n throw new TypeError(\"Pass exactly one of `file` or `audioUrl` (or `test: true`).\");\n }\n const fields: Record<string, string> = {};\n if (options.title !== undefined) fields.title = options.title;\n if (options.artist !== undefined) fields.artist = options.artist;\n if (options.webhookUrl !== undefined) fields.webhook_url = options.webhookUrl;\n if (options.externalRef !== undefined) fields.external_ref = options.externalRef;\n if (options.lyrics !== undefined) fields.lyrics = options.lyrics;\n const headers = { [IDEMPOTENCY_HEADER]: options.idempotencyKey || (await newIdempotencyKey()) };\n\n let created: SongCreated;\n if (file === undefined) {\n const json: Json = audioUrl !== undefined ? { audio_url: audioUrl, ...fields } : { ...fields };\n if (test) json.test = true;\n created = await this.request<SongCreated>(\"POST\", \"/songs\", { json, headers });\n } else {\n if (test) fields.test = \"true\";\n const { blob, name } = await toUpload(file, options.filename);\n created = await this.request<SongCreated>(\"POST\", \"/songs\", {\n headers,\n form: () => {\n const form = new FormData();\n form.append(\"file\", blob, name);\n for (const [k, v] of Object.entries(fields)) form.append(k, v);\n return form;\n },\n });\n }\n if (options.wait === false) return created;\n return this.waitFor(created.id, {\n pollIntervalMs: options.pollIntervalMs,\n timeoutMs: options.timeoutMs,\n });\n }\n\n /** Poll GET /songs/{id} until the song is done. Throws `AnalysisFailed` or `WaitTimeout`. */\n async waitFor(songId: string, options: WaitOptions = {}): Promise<Song> {\n const timeoutMs = options.timeoutMs ?? 300_000;\n const interval = Math.max(500, options.pollIntervalMs ?? 5_000);\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n const doc = await this.getSong(songId);\n if (doc.status === \"done\") return doc;\n if (doc.status === \"failed\") {\n throw new AnalysisFailed(\n songId,\n doc.error?.code ?? \"analysis_failed\",\n doc.error?.message ?? \"The analysis failed. Paid credits were refunded automatically.\",\n doc,\n );\n }\n const remaining = deadline - Date.now();\n if (remaining <= 0) throw new WaitTimeout(songId, timeoutMs);\n await this._sleep(Math.min(interval, remaining));\n }\n }\n\n /** Status while processing, then the full document. */\n getSong(songId: string, options: GetSongOptions = {}): Promise<Song> {\n return this.request<Song>(\"GET\", `/songs/${encodeURIComponent(songId)}`, { query: docQuery(options) });\n }\n\n /** Only the story and the shot plan: `{ id, status, song, story, shot_plan }`. */\n shotPlan(songId: string): Promise<SongShotPlan> {\n return this.request<SongShotPlan>(\"GET\", `/songs/${encodeURIComponent(songId)}/shot-plan`);\n }\n\n /** One page of your songs, newest first: `{ object: \"list\", data, has_more, next_cursor }`. */\n listSongs(options: ListSongsOptions = {}): Promise<SongList> {\n return this.request<SongList>(\"GET\", \"/songs\", {\n query: { limit: String(options.limit ?? 20), starting_after: options.startingAfter },\n });\n }\n\n /**\n * All your songs, newest first, fetching pages as needed.\n *\n * ```ts\n * for await (const item of sb.iterSongs()) console.log(item.id, item.status);\n * ```\n */\n async *iterSongs(options: IterSongsOptions = {}): AsyncGenerator<SongListItem, void, undefined> {\n let cursor = options.startingAfter;\n for (;;) {\n const page = await this.listSongs({ limit: options.pageSize ?? 100, startingAfter: cursor });\n const data = page.data ?? [];\n yield* data;\n cursor = page.next_cursor ?? data[data.length - 1]?.id;\n if (!page.has_more || !cursor) return;\n }\n }\n\n /** Delete a song's audio and analysis now. 409 `still_processing` while it runs. */\n deleteSong(songId: string): Promise<DeleteResult> {\n return this.request<DeleteResult>(\"DELETE\", `/songs/${encodeURIComponent(songId)}`);\n }\n\n // ── account ──────────────────────────────────────────────────────────────\n\n /** Free songs left this month, credit balance and price per song. */\n account(): Promise<Account> {\n return this.request<Account>(\"GET\", \"/account\");\n }\n\n /** Prices and limits. No key needed. */\n pricing(): Promise<Pricing> {\n return this.request<Pricing>(\"GET\", \"/pricing\");\n }\n\n // ── webhooks ─────────────────────────────────────────────────────────────\n\n /**\n * Send a signed `ping` event to `url` right now:\n * `{ delivered, status_code, latency_ms, event_id }`. Checks your receiver before real songs.\n */\n testWebhook(url: string): Promise<WebhookTestResult> {\n return this.request<WebhookTestResult>(\"POST\", \"/webhooks/test\", { json: { url } });\n }\n\n /** The last webhook delivery attempts for your account, newest first. */\n webhookDeliveries(options: { limit?: number } = {}): Promise<WebhookDeliveryList> {\n return this.request<WebhookDeliveryList>(\"GET\", \"/webhooks/deliveries\", {\n query: { limit: String(options.limit ?? 20) },\n });\n }\n\n // ── examples (no key) ────────────────────────────────────────────────────\n\n /** Example analyses of Songbrain's own songs. No key needed. */\n examples(): Promise<ExampleList> {\n return this.request<ExampleList>(\"GET\", \"/examples\");\n }\n\n /** A full example document, in the same format as `getSong`. No key needed. */\n example(exampleId: string, options: GetSongOptions = {}): Promise<Song> {\n return this.request<Song>(\"GET\", `/examples/${encodeURIComponent(exampleId)}`, { query: docQuery(options) });\n }\n\n /** An example story and shot plan, in the same format as `shotPlan`. No key needed. */\n exampleShotPlan(exampleId: string): Promise<SongShotPlan> {\n return this.request<SongShotPlan>(\"GET\", `/examples/${encodeURIComponent(exampleId)}/shot-plan`);\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private backoffMs(attempt: number): number {\n return Math.min(500 * 2 ** attempt, 8_000) + Math.random() * 250;\n }\n\n private async request<T>(\n method: \"GET\" | \"POST\" | \"DELETE\",\n path: string,\n opts: {\n query?: Record<string, string | undefined>;\n json?: Json;\n form?: () => FormData;\n headers?: Record<string, string>;\n } = {},\n ): Promise<T> {\n const url = new URL(this.baseUrl + path);\n for (const [k, v] of Object.entries(opts.query ?? {})) if (v !== undefined) url.searchParams.set(k, v);\n const hasKey = Boolean(opts.headers?.[IDEMPOTENCY_HEADER]);\n // A POST with an Idempotency-Key is safe to resend: the API returns the first answer again.\n const idempotent = method !== \"POST\" || hasKey;\n\n for (let attempt = 0; ; attempt++) {\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"User-Agent\": `songbrain-js/${VERSION}`,\n };\n if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;\n Object.assign(headers, opts.headers);\n let body: BodyInit | undefined;\n if (opts.json) {\n headers[\"Content-Type\"] = \"application/json\";\n body = JSON.stringify(opts.json);\n } else if (opts.form) {\n body = opts.form(); // fresh FormData per attempt; fetch sets the boundary\n }\n\n let res: Response;\n try {\n res = await this.fetchImpl(url, { method, headers, body, signal: AbortSignal.timeout(this.timeoutMs) });\n } catch (err) {\n // Network errors and timeouts: only resend if nothing can have been created\n // or the request carries an Idempotency-Key.\n if (idempotent && attempt < this.maxRetries) {\n await this._sleep(this.backoffMs(attempt));\n continue;\n }\n const message = err instanceof Error ? err.message : String(err);\n throw new SongbrainError(0, \"connection_error\", message);\n }\n\n this.lastRequestId = res.headers.get(REQUEST_ID_HEADER) || null;\n const rateLimit = rateLimitFrom(res.headers);\n if (rateLimit) this.lastRateLimit = rateLimit;\n\n if (res.ok) {\n const text = await res.text();\n if (!text) return {} as T;\n try {\n return JSON.parse(text) as T;\n } catch {\n throw new SongbrainError(res.status, \"invalid_response\", \"Response is not JSON.\");\n }\n }\n\n const error = await errorFrom(res);\n // 429 never started any work. For a POST without Idempotency-Key only retry\n // gateway errors (502/503).\n const inProgress = hasKey && res.status === 409 && error.code === \"idempotency_in_progress\";\n const retryable =\n res.status === 429 ||\n inProgress ||\n (res.status >= 500 && (idempotent || res.status === 502 || res.status === 503));\n if (retryable && attempt < this.maxRetries) {\n const after = error instanceof RateLimited ? error.retryAfter : null;\n let delayMs = after !== null ? after * 1000 : this.backoffMs(attempt);\n if (inProgress) delayMs = Math.max(1000, delayMs);\n if (delayMs <= MAX_RETRY_WAIT_SEC * 1000) {\n await this._sleep(delayMs);\n continue;\n }\n }\n throw error;\n }\n }\n}\n\nfunction docQuery(options: GetSongOptions): Record<string, string | undefined> {\n const include = Array.isArray(options.include) ? options.include.join(\",\") : options.include;\n return { view: options.view, include };\n}\n\nasync function errorFrom(res: Response): Promise<SongbrainError> {\n const text = await res.text().catch(() => \"\");\n let body: unknown;\n let code = `http_${res.status}`;\n let message = text.trim().slice(0, 500) || res.statusText || `HTTP ${res.status}`;\n let requestId = res.headers.get(REQUEST_ID_HEADER) || null;\n try {\n body = JSON.parse(text);\n } catch {\n body = undefined;\n }\n if (body && typeof body === \"object\") {\n const err = (body as { error?: unknown }).error;\n if (err && typeof err === \"object\") {\n const e = err as { code?: unknown; message?: unknown; request_id?: unknown };\n if (e.code) code = String(e.code);\n if (e.message) message = String(e.message);\n if (e.request_id) requestId = String(e.request_id);\n } else if (\"detail\" in (body as object)) {\n // request validation, older API versions\n code = \"validation_error\";\n message = JSON.stringify((body as { detail: unknown }).detail);\n }\n }\n switch (res.status) {\n case 401:\n return new AuthenticationError(401, code, message, body, requestId);\n case 402:\n return new InsufficientCredits(402, code, message, body, requestId);\n case 404:\n return new NotFound(404, code, message, body, requestId);\n case 429:\n return new RateLimited(429, code, message, body, retryAfterSeconds(res.headers.get(\"Retry-After\")), requestId);\n default:\n return new SongbrainError(res.status, code, message, body, requestId);\n }\n}\n\nasync function toUpload(file: FileInput, filename?: string): Promise<{ blob: Blob; name: string }> {\n let blob: Blob;\n let sourceName: string | undefined;\n if (typeof file === \"string\") {\n const { readFile } = await import(\"node:fs/promises\");\n const data = await readFile(file);\n blob = new Blob([data as BlobPart]);\n sourceName = baseName(file);\n } else if (file instanceof ArrayBuffer) {\n blob = new Blob([file]);\n } else if (file instanceof Uint8Array) {\n blob = new Blob([file as BlobPart]);\n } else if (typeof Blob !== \"undefined\" && file instanceof Blob) {\n blob = file;\n const n = (file as Blob & { name?: unknown }).name;\n if (typeof n === \"string\" && n) sourceName = baseName(n);\n } else {\n throw new TypeError(\"`file` must be a Blob, a Buffer/Uint8Array, an ArrayBuffer or a file path.\");\n }\n\n let name = filename ?? sourceName;\n if (!name || !extOf(name)) {\n const head = new Uint8Array(await blob.slice(0, 16).arrayBuffer());\n const ext = sniffExtension(head);\n if (!ext) {\n throw new TypeError('Could not tell the audio format. Pass `filename` with an extension, e.g. filename: \"song.mp3\".');\n }\n name = `${(name ?? \"song\").replace(/\\.$/, \"\")}.${ext}`;\n }\n const type = MIME[extOf(name)];\n if (type && !blob.type) blob = new Blob([blob], { type });\n return { blob, name };\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport type { WebhookEvent } from \"./types.js\";\n\n/** The header Songbrain signs webhooks with. */\nexport const SIGNATURE_HEADER = \"Songbrain-Signature\";\n\ntype RawBody = string | Uint8Array | ArrayBuffer;\n\nfunction toBuffer(body: RawBody): Buffer {\n if (typeof body === \"string\") return Buffer.from(body, \"utf8\");\n if (body instanceof ArrayBuffer) return Buffer.from(new Uint8Array(body));\n return Buffer.from(body.buffer, body.byteOffset, body.byteLength);\n}\n\nfunction parseHeader(header: string): { t: number | null; v1: string[] } {\n let t: number | null = null;\n const v1: string[] = [];\n for (const part of header.split(\",\")) {\n const i = part.indexOf(\"=\");\n if (i < 0) continue;\n const key = part.slice(0, i).trim();\n const value = part.slice(i + 1).trim();\n if (key === \"t\") {\n if (!/^\\d+$/.test(value)) return { t: null, v1: [] };\n t = Number(value);\n } else if (key === \"v1\" && value) {\n v1.push(value.toLowerCase());\n }\n }\n return { t, v1 };\n}\n\nfunction mac(body: Buffer, t: number, secret: string): string {\n return createHmac(\"sha256\", secret).update(`${t}.`).update(body).digest(\"hex\");\n}\n\n/**\n * Check a `Songbrain-Signature: t=<unix>,v1=<hex>` header, where v1 is the\n * HMAC-SHA256 of `\"<t>.<raw body>\"` with your key's webhook secret.\n *\n * Pass the raw request body exactly as received, before any JSON parsing.\n * Returns false (never throws) for a missing, malformed, wrong or old signature.\n *\n * @param toleranceSec Maximum age in seconds (default 300). 0 disables the check.\n */\nexport function verifyWebhook(\n rawBody: RawBody,\n header: string | null | undefined,\n secret: string,\n toleranceSec = 300,\n): boolean {\n if (!header || !secret) return false;\n const { t, v1 } = parseHeader(header);\n if (t === null || v1.length === 0) return false;\n if (toleranceSec > 0 && Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;\n const expected = Buffer.from(mac(toBuffer(rawBody), t, secret), \"utf8\");\n return v1.some((sig) => {\n const given = Buffer.from(sig, \"utf8\");\n return given.length === expected.length && timingSafeEqual(given, expected);\n });\n}\n\n/**\n * Verify the signature and return the parsed event:\n * `{ id, type: \"song.done\" | \"song.failed\" | \"account.low_balance\" | \"ping\", created, livemode, data }`.\n * Throws if the signature is not valid.\n *\n * `id` (`evt_…`, also the `Songbrain-Event-Id` header) stays the same when\n * Songbrain retries an event (up to 10 attempts over about 3 days): store the\n * ids you have handled and skip repeats.\n */\nexport function constructWebhookEvent<T = Record<string, unknown>>(\n rawBody: RawBody,\n header: string | null | undefined,\n secret: string,\n toleranceSec = 300,\n): WebhookEvent<T> {\n if (!verifyWebhook(rawBody, header, secret, toleranceSec)) {\n throw new Error(\"Invalid Songbrain-Signature\");\n }\n return JSON.parse(toBuffer(rawBody).toString(\"utf8\")) as WebhookEvent<T>;\n}\n\n/** Build a signature header value. Useful to test your webhook handler. */\nexport function signWebhook(rawBody: RawBody, secret: string, timestampSec = Math.floor(Date.now() / 1000)): string {\n return `t=${timestampSec},v1=${mac(toBuffer(rawBody), timestampSec, secret)}`;\n}\n"]}