songbrain 0.1.1 → 0.2.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/dist/index.js CHANGED
@@ -8,12 +8,15 @@ var SongbrainError = class extends Error {
8
8
  code;
9
9
  /** The parsed response body, when there was one. */
10
10
  body;
11
- constructor(status, code, message, body) {
12
- super(message);
11
+ /** The `Songbrain-Request-Id` of the failed request (`req_…`), or null. */
12
+ requestId;
13
+ constructor(status, code, message, body, requestId = null) {
14
+ super(requestId ? `${message} (request_id: ${requestId})` : message);
13
15
  this.name = new.target.name;
14
16
  this.status = status;
15
17
  this.code = code;
16
18
  this.body = body;
19
+ this.requestId = requestId;
17
20
  Object.setPrototypeOf(this, new.target.prototype);
18
21
  }
19
22
  };
@@ -26,8 +29,8 @@ var NotFound = class extends SongbrainError {
26
29
  var RateLimited = class extends SongbrainError {
27
30
  /** Seconds to wait, from the Retry-After header (null if absent). */
28
31
  retryAfter;
29
- constructor(status, code, message, body, retryAfter = null) {
30
- super(status, code, message, body);
32
+ constructor(status, code, message, body, retryAfter = null, requestId = null) {
33
+ super(status, code, message, body, requestId);
31
34
  this.retryAfter = retryAfter;
32
35
  }
33
36
  };
@@ -51,10 +54,12 @@ var WaitTimeout = class extends SongbrainError {
51
54
  };
52
55
 
53
56
  // src/version.ts
54
- var VERSION = "0.1.1";
57
+ var VERSION = "0.2.0";
55
58
 
56
59
  // src/client.ts
57
60
  var DEFAULT_BASE_URL = "https://api.songbrain.ai/v1";
61
+ var REQUEST_ID_HEADER = "Songbrain-Request-Id";
62
+ var IDEMPOTENCY_HEADER = "Idempotency-Key";
58
63
  var MAX_RETRY_WAIT_SEC = 60;
59
64
  var MIME = {
60
65
  mp3: "audio/mpeg",
@@ -99,12 +104,47 @@ function retryAfterSeconds(value) {
99
104
  return Number.isNaN(when) ? null : Math.max(0, (when - Date.now()) / 1e3);
100
105
  }
101
106
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
107
+ function intHeader(value) {
108
+ if (value === null || value === "") return null;
109
+ const n = Number(value);
110
+ return Number.isFinite(n) ? Math.trunc(n) : null;
111
+ }
112
+ function rateLimitFrom(headers) {
113
+ const limit = intHeader(headers.get("X-RateLimit-Limit"));
114
+ if (limit === null) return null;
115
+ return {
116
+ limit,
117
+ remaining: intHeader(headers.get("X-RateLimit-Remaining")),
118
+ reset: intHeader(headers.get("X-RateLimit-Reset"))
119
+ };
120
+ }
121
+ async function newIdempotencyKey() {
122
+ const c = globalThis.crypto;
123
+ if (typeof c?.randomUUID === "function") return c.randomUUID();
124
+ try {
125
+ const { randomUUID } = await import('crypto');
126
+ return randomUUID();
127
+ } catch {
128
+ const hex = Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16));
129
+ hex[12] = "4";
130
+ hex[16] = (parseInt(hex[16], 16) & 3 | 8).toString(16);
131
+ const h = hex.join("");
132
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
133
+ }
134
+ }
102
135
  var Songbrain = class {
103
136
  apiKey;
104
137
  baseUrl;
105
138
  timeoutMs;
106
139
  maxRetries;
107
140
  fetchImpl;
141
+ /**
142
+ * `{ limit, remaining, reset }` from the `X-RateLimit-*` headers of the last response
143
+ * that had them (null before that). `reset` is seconds until the window has room.
144
+ */
145
+ lastRateLimit = null;
146
+ /** The `Songbrain-Request-Id` of the last response (null before the first one). */
147
+ lastRequestId = null;
108
148
  /** @internal Overridable in tests. */
109
149
  _sleep = sleep;
110
150
  constructor(options = {}) {
@@ -117,21 +157,29 @@ var Songbrain = class {
117
157
  this.fetchImpl = f;
118
158
  }
119
159
  async analyze(options) {
120
- const { file, audioUrl } = options;
121
- if (file === void 0 === (audioUrl === void 0)) {
160
+ const { file, audioUrl, test } = options;
161
+ if (file !== void 0 && audioUrl !== void 0) {
122
162
  throw new TypeError("Pass exactly one of `file` or `audioUrl`.");
123
163
  }
164
+ if (file === void 0 && audioUrl === void 0 && !test) {
165
+ throw new TypeError("Pass exactly one of `file` or `audioUrl` (or `test: true`).");
166
+ }
124
167
  const fields = {};
125
168
  if (options.title !== void 0) fields.title = options.title;
126
169
  if (options.artist !== void 0) fields.artist = options.artist;
127
170
  if (options.webhookUrl !== void 0) fields.webhook_url = options.webhookUrl;
128
171
  if (options.externalRef !== void 0) fields.external_ref = options.externalRef;
172
+ const headers = { [IDEMPOTENCY_HEADER]: options.idempotencyKey || await newIdempotencyKey() };
129
173
  let created;
130
- if (audioUrl !== void 0) {
131
- created = await this.request("POST", "/songs", { json: { audio_url: audioUrl, ...fields } });
174
+ if (file === void 0) {
175
+ const json = audioUrl !== void 0 ? { audio_url: audioUrl, ...fields } : { ...fields };
176
+ if (test) json.test = true;
177
+ created = await this.request("POST", "/songs", { json, headers });
132
178
  } else {
179
+ if (test) fields.test = "true";
133
180
  const { blob, name } = await toUpload(file, options.filename);
134
181
  created = await this.request("POST", "/songs", {
182
+ headers,
135
183
  form: () => {
136
184
  const form = new FormData();
137
185
  form.append("file", blob, name);
@@ -175,9 +223,28 @@ var Songbrain = class {
175
223
  shotPlan(songId) {
176
224
  return this.request("GET", `/songs/${encodeURIComponent(songId)}/shot-plan`);
177
225
  }
178
- /** Your songs, newest first. `limit` is 1-100 (default 20). */
226
+ /** One page of your songs, newest first: `{ object: "list", data, has_more, next_cursor }`. */
179
227
  listSongs(options = {}) {
180
- return this.request("GET", "/songs", { query: { limit: String(options.limit ?? 20) } });
228
+ return this.request("GET", "/songs", {
229
+ query: { limit: String(options.limit ?? 20), starting_after: options.startingAfter }
230
+ });
231
+ }
232
+ /**
233
+ * All your songs, newest first, fetching pages as needed.
234
+ *
235
+ * ```ts
236
+ * for await (const item of sb.iterSongs()) console.log(item.id, item.status);
237
+ * ```
238
+ */
239
+ async *iterSongs(options = {}) {
240
+ let cursor = options.startingAfter;
241
+ for (; ; ) {
242
+ const page = await this.listSongs({ limit: options.pageSize ?? 100, startingAfter: cursor });
243
+ const data = page.data ?? [];
244
+ yield* data;
245
+ cursor = page.next_cursor ?? data[data.length - 1]?.id;
246
+ if (!page.has_more || !cursor) return;
247
+ }
181
248
  }
182
249
  /** Delete a song's audio and analysis now. 409 `still_processing` while it runs. */
183
250
  deleteSong(songId) {
@@ -192,6 +259,20 @@ var Songbrain = class {
192
259
  pricing() {
193
260
  return this.request("GET", "/pricing");
194
261
  }
262
+ // ── webhooks ─────────────────────────────────────────────────────────────
263
+ /**
264
+ * Send a signed `ping` event to `url` right now:
265
+ * `{ delivered, status_code, latency_ms, event_id }`. Checks your receiver before real songs.
266
+ */
267
+ testWebhook(url) {
268
+ return this.request("POST", "/webhooks/test", { json: { url } });
269
+ }
270
+ /** The last webhook delivery attempts for your account, newest first. */
271
+ webhookDeliveries(options = {}) {
272
+ return this.request("GET", "/webhooks/deliveries", {
273
+ query: { limit: String(options.limit ?? 20) }
274
+ });
275
+ }
195
276
  // ── examples (no key) ────────────────────────────────────────────────────
196
277
  /** Example analyses of Songbrain's own songs. No key needed. */
197
278
  examples() {
@@ -212,13 +293,15 @@ var Songbrain = class {
212
293
  async request(method, path, opts = {}) {
213
294
  const url = new URL(this.baseUrl + path);
214
295
  for (const [k, v] of Object.entries(opts.query ?? {})) if (v !== void 0) url.searchParams.set(k, v);
215
- const idempotent = method !== "POST";
296
+ const hasKey = Boolean(opts.headers?.[IDEMPOTENCY_HEADER]);
297
+ const idempotent = method !== "POST" || hasKey;
216
298
  for (let attempt = 0; ; attempt++) {
217
299
  const headers = {
218
300
  Accept: "application/json",
219
301
  "User-Agent": `songbrain-js/${VERSION}`
220
302
  };
221
303
  if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
304
+ Object.assign(headers, opts.headers);
222
305
  let body;
223
306
  if (opts.json) {
224
307
  headers["Content-Type"] = "application/json";
@@ -237,6 +320,9 @@ var Songbrain = class {
237
320
  const message = err instanceof Error ? err.message : String(err);
238
321
  throw new SongbrainError(0, "connection_error", message);
239
322
  }
323
+ this.lastRequestId = res.headers.get(REQUEST_ID_HEADER) || null;
324
+ const rateLimit = rateLimitFrom(res.headers);
325
+ if (rateLimit) this.lastRateLimit = rateLimit;
240
326
  if (res.ok) {
241
327
  const text = await res.text();
242
328
  if (!text) return {};
@@ -247,10 +333,12 @@ var Songbrain = class {
247
333
  }
248
334
  }
249
335
  const error = await errorFrom(res);
250
- const retryable = res.status === 429 || res.status >= 500 && (idempotent || res.status === 502 || res.status === 503);
336
+ const inProgress = hasKey && res.status === 409 && error.code === "idempotency_in_progress";
337
+ const retryable = res.status === 429 || inProgress || res.status >= 500 && (idempotent || res.status === 502 || res.status === 503);
251
338
  if (retryable && attempt < this.maxRetries) {
252
339
  const after = error instanceof RateLimited ? error.retryAfter : null;
253
- const delayMs = after !== null ? after * 1e3 : this.backoffMs(attempt);
340
+ let delayMs = after !== null ? after * 1e3 : this.backoffMs(attempt);
341
+ if (inProgress) delayMs = Math.max(1e3, delayMs);
254
342
  if (delayMs <= MAX_RETRY_WAIT_SEC * 1e3) {
255
343
  await this._sleep(delayMs);
256
344
  continue;
@@ -269,6 +357,7 @@ async function errorFrom(res) {
269
357
  let body;
270
358
  let code = `http_${res.status}`;
271
359
  let message = text.trim().slice(0, 500) || res.statusText || `HTTP ${res.status}`;
360
+ let requestId = res.headers.get(REQUEST_ID_HEADER) || null;
272
361
  try {
273
362
  body = JSON.parse(text);
274
363
  } catch {
@@ -280,6 +369,7 @@ async function errorFrom(res) {
280
369
  const e = err;
281
370
  if (e.code) code = String(e.code);
282
371
  if (e.message) message = String(e.message);
372
+ if (e.request_id) requestId = String(e.request_id);
283
373
  } else if ("detail" in body) {
284
374
  code = "validation_error";
285
375
  message = JSON.stringify(body.detail);
@@ -287,15 +377,15 @@ async function errorFrom(res) {
287
377
  }
288
378
  switch (res.status) {
289
379
  case 401:
290
- return new AuthenticationError(401, code, message, body);
380
+ return new AuthenticationError(401, code, message, body, requestId);
291
381
  case 402:
292
- return new InsufficientCredits(402, code, message, body);
382
+ return new InsufficientCredits(402, code, message, body, requestId);
293
383
  case 404:
294
- return new NotFound(404, code, message, body);
384
+ return new NotFound(404, code, message, body, requestId);
295
385
  case 429:
296
- return new RateLimited(429, code, message, body, retryAfterSeconds(res.headers.get("Retry-After")));
386
+ return new RateLimited(429, code, message, body, retryAfterSeconds(res.headers.get("Retry-After")), requestId);
297
387
  default:
298
- return new SongbrainError(res.status, code, message, body);
388
+ return new SongbrainError(res.status, code, message, body, requestId);
299
389
  }
300
390
  }
301
391
  async function toUpload(file, filename) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/version.ts","../src/client.ts","../src/webhooks.ts"],"names":[],"mappings":"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","file":"index.js","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.1\";\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":[],"mappings":"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","file":"index.js","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.2.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 * 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 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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "songbrain",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Official TypeScript/JavaScript client for the Songbrain API: song in, video plan out. Song DNA, beat grid, best moments, timed lyrics, story and a beat-synced shot plan.",
5
5
  "license": "MIT",
6
6
  "author": "Songbrain <support@songbrain.ai> (https://www.songbrain.ai)",