@audd/sdk 1.5.6 → 1.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -96,14 +96,14 @@ only what you need — each provider you ask for adds latency:
96
96
 
97
97
  ```ts
98
98
  const song = await audd.recognize("https://audd.tech/example.mp3", {
99
- return: ["apple_music", "spotify"],
99
+ returnMetadata: ["apple_music", "spotify"],
100
100
  });
101
101
  console.log(song?.appleMusic?.url); // direct Apple Music link
102
102
  console.log(song?.spotify?.uri); // spotify:track:...
103
103
  console.log(song?.previewUrl()); // first preview across requested providers, or null
104
104
  ```
105
105
 
106
- Valid `return` values: `apple_music`, `spotify`, `deezer`, `napster`,
106
+ Valid `returnMetadata` values: `apple_music`, `spotify`, `deezer`, `napster`,
107
107
  `musicbrainz`. Blocks are `undefined` when not requested.
108
108
 
109
109
  `streamingUrl(provider)` prefers the direct provider URL when you
@@ -121,6 +121,17 @@ console.log(song.extras); // any non-typed top-level fields
121
121
  console.log(song.rawResponse); // the whole result object as the server returned it
122
122
  ```
123
123
 
124
+ For the **request** side, every call accepts an `extraParameters` map for additional form fields the typed options don't cover — undocumented parameters or beta features:
125
+
126
+ ```ts
127
+ await audd.recognize(url, {
128
+ returnMetadata: "apple_music",
129
+ extraParameters: { some_beta_flag: "true" },
130
+ });
131
+ ```
132
+
133
+ The same `extraParameters` field is on `RecognizeEnterpriseOptions`, `SetCallbackUrlOptions`, and `AddStreamOptions`. Typed options win on collision.
134
+
124
135
  ## Long files (enterprise)
125
136
 
126
137
  `recognizeEnterprise` accepts files up to several hours and returns a
@@ -273,6 +284,58 @@ If you already have the body bytes (queue consumer, replay tool), call
273
284
  `parseCallback(body)` directly — it accepts a parsed JSON object or a
274
285
  JSON string and returns the same `{ match, notification }` shape.
275
286
 
287
+ #### Per-framework wiring
288
+
289
+ The same `handleCallback(req)` works across Node web frameworks — register
290
+ a POST route and pass the request object in.
291
+
292
+ `Fastify`:
293
+
294
+ ```ts
295
+ import Fastify from "fastify";
296
+ import { handleCallback } from "@audd/sdk";
297
+
298
+ const app = Fastify();
299
+ app.post("/audd-callback", async (req, reply) => {
300
+ const { match } = await handleCallback(req);
301
+ if (match) console.log(`${match.song.artist} — ${match.song.title}`);
302
+ return { ok: true };
303
+ });
304
+ ```
305
+
306
+ `Koa`:
307
+
308
+ ```ts
309
+ import Koa from "koa";
310
+ import Router from "@koa/router";
311
+ import bodyParser from "koa-bodyparser";
312
+ import { handleCallback } from "@audd/sdk";
313
+
314
+ const app = new Koa();
315
+ const router = new Router();
316
+
317
+ app.use(bodyParser());
318
+ router.post("/audd-callback", async (ctx) => {
319
+ const { match } = await handleCallback(ctx.request);
320
+ if (match) console.log(`${match.song.artist} — ${match.song.title}`);
321
+ ctx.body = { ok: true };
322
+ });
323
+ app.use(router.routes());
324
+ ```
325
+
326
+ `Next.js` (App Router, `app/api/audd-callback/route.ts`):
327
+
328
+ ```ts
329
+ import { NextRequest, NextResponse } from "next/server";
330
+ import { handleCallback } from "@audd/sdk";
331
+
332
+ export async function POST(req: NextRequest) {
333
+ const { match } = await handleCallback(req);
334
+ if (match) console.log(`${match.song.artist} — ${match.song.title}`);
335
+ return NextResponse.json({ ok: true });
336
+ }
337
+ ```
338
+
276
339
  ### Receiving events without a callback URL (longpoll)
277
340
 
278
341
  Useful when you can't expose a public HTTPS receiver. The poll handle
@@ -286,9 +349,9 @@ callback URL set, and the preflight surfaces that as an actionable
286
349
  error. Pass `skipCallbackCheck: true` to bypass.
287
350
 
288
351
  ```ts
289
- const category = audd.streams.deriveLongpollCategory(12345); // pure, no network call
290
- const poll = await audd.streams.longpoll(category, { timeout: 30 });
352
+ const radioId = 1; // any integer you choose — your handle for this stream
291
353
 
354
+ const poll = await audd.streams.longpoll({ radioId, timeout: 30 });
292
355
  for await (const m of poll.matches) {
293
356
  console.log(m.song.artist, m.song.title);
294
357
  }
package/dist/index.cjs CHANGED
@@ -25,7 +25,7 @@ function _interopNamespace(e) {
25
25
  var path__namespace = /*#__PURE__*/_interopNamespace(path);
26
26
 
27
27
  // src/version.ts
28
- var VERSION = "1.4.6";
28
+ var VERSION = "1.5.8";
29
29
 
30
30
  // src/errors.ts
31
31
  var AudDError = class extends Error {
@@ -684,6 +684,8 @@ function shouldRetryResponse(resp, retryClass) {
684
684
  return s >= HTTP_SERVER_ERROR_FLOOR;
685
685
  case "mutating":
686
686
  return false;
687
+ case "none":
688
+ return false;
687
689
  }
688
690
  }
689
691
  function shouldRetryError(err, retryClass) {
@@ -694,6 +696,8 @@ function shouldRetryError(err, retryClass) {
694
696
  return isPreUploadConnectionError(err);
695
697
  case "mutating":
696
698
  return isPreUploadConnectionError(err);
699
+ case "none":
700
+ return false;
697
701
  }
698
702
  }
699
703
  function sleep(ms) {
@@ -1123,7 +1127,9 @@ var Streams = class {
1123
1127
  */
1124
1128
  async setCallbackUrl(url, opts = {}) {
1125
1129
  const finalUrl = addReturnToUrl(url, opts.returnMetadata);
1126
- await this.post("setCallbackUrl", { url: finalUrl }, this.mutatingPolicy);
1130
+ const fields = opts.extraParameters ? { ...opts.extraParameters } : {};
1131
+ fields["url"] = finalUrl;
1132
+ await this.post("setCallbackUrl", fields, this.mutatingPolicy);
1127
1133
  }
1128
1134
  /** Get the currently registered callback URL. */
1129
1135
  async getCallbackUrl() {
@@ -1132,10 +1138,9 @@ var Streams = class {
1132
1138
  }
1133
1139
  /** Register a new stream for real-time recognition. */
1134
1140
  async add(opts) {
1135
- const fields = {
1136
- url: opts.url,
1137
- radio_id: String(opts.radioId)
1138
- };
1141
+ const fields = opts.extraParameters ? { ...opts.extraParameters } : {};
1142
+ fields["url"] = opts.url;
1143
+ fields["radio_id"] = String(opts.radioId);
1139
1144
  if (opts.callbacks !== void 0) fields["callbacks"] = opts.callbacks;
1140
1145
  await this.post("addStream", fields, this.mutatingPolicy);
1141
1146
  }
@@ -1266,12 +1271,12 @@ async function runRetried2(fn, policy) {
1266
1271
  }
1267
1272
  }
1268
1273
  var CustomCatalog = class {
1269
- constructor(http, mutatingPolicy) {
1274
+ constructor(http, noRetryPolicy) {
1270
1275
  this.http = http;
1271
- this.mutatingPolicy = mutatingPolicy;
1276
+ this.noRetryPolicy = noRetryPolicy;
1272
1277
  }
1273
1278
  http;
1274
- mutatingPolicy;
1279
+ noRetryPolicy;
1275
1280
  /**
1276
1281
  * **This is NOT how you submit audio for music recognition.** For
1277
1282
  * recognition, use `audd.recognize()` (or `audd.recognizeEnterprise()` for
@@ -1283,6 +1288,11 @@ var CustomCatalog = class {
1283
1288
  * Calling this again with the same `audioId` re-fingerprints that slot.
1284
1289
  * There is no public list/delete endpoint; track `audioId` ↔ song
1285
1290
  * mappings on your side.
1291
+ *
1292
+ * **No automatic retry.** Custom-catalog upload is metered, and a transport
1293
+ * failure could otherwise cause a silent re-upload that double-charges. Any
1294
+ * 5xx or pre-upload connection error surfaces as a clean exception — the
1295
+ * caller decides whether to retry.
1286
1296
  */
1287
1297
  async add(opts) {
1288
1298
  const reopen = prepareSource(opts.source);
@@ -1291,7 +1301,7 @@ var CustomCatalog = class {
1291
1301
  const prepared = await reopen();
1292
1302
  const fields = { ...prepared.fields, audio_id: audioId };
1293
1303
  return this.http.postForm(UPLOAD_URL, fields);
1294
- }, this.mutatingPolicy);
1304
+ }, this.noRetryPolicy);
1295
1305
  decodeSuccess2(resp.jsonBody, resp.httpStatus, resp.requestId);
1296
1306
  }
1297
1307
  };
@@ -1384,8 +1394,8 @@ function formatReturn(value) {
1384
1394
  return Array.isArray(value) ? value.join(",") : value;
1385
1395
  }
1386
1396
  function buildEnterpriseFields(opts) {
1387
- const fields = {};
1388
- const ret = formatReturn(opts.return);
1397
+ const fields = opts.extraParameters ? { ...opts.extraParameters } : {};
1398
+ const ret = formatReturn(opts.returnMetadata);
1389
1399
  if (ret !== void 0) fields["return"] = ret;
1390
1400
  if (opts.skip !== void 0) fields["skip"] = String(opts.skip);
1391
1401
  if (opts.every !== void 0) fields["every"] = String(opts.every);
@@ -1535,7 +1545,7 @@ var AudD = class {
1535
1545
  /** Sub-namespace for the private fingerprint catalog. NOT for recognition. */
1536
1546
  get customCatalog() {
1537
1547
  if (this._customCatalog === void 0) {
1538
- this._customCatalog = new CustomCatalog(this._http, this.policyFor("mutating"));
1548
+ this._customCatalog = new CustomCatalog(this._http, this.policyFor("none"));
1539
1549
  }
1540
1550
  return this._customCatalog;
1541
1551
  }
@@ -1554,7 +1564,7 @@ var AudD = class {
1554
1564
  */
1555
1565
  async recognize(source, opts = {}) {
1556
1566
  const reopen = prepareSource(source);
1557
- const ret = formatReturn(opts.return);
1567
+ const ret = formatReturn(opts.returnMetadata);
1558
1568
  const market = opts.market;
1559
1569
  const policy = this.policyFor("recognition");
1560
1570
  const url = `${API_BASE3}/`;
@@ -1574,6 +1584,7 @@ var AudD = class {
1574
1584
  resp = await runRetried4(async () => {
1575
1585
  const prepared = await reopen();
1576
1586
  const fields = { ...prepared.fields };
1587
+ if (opts.extraParameters) Object.assign(fields, opts.extraParameters);
1577
1588
  if (ret !== void 0) fields["return"] = ret;
1578
1589
  if (market !== void 0) fields["market"] = market;
1579
1590
  return this._http.postForm(url, fields, {