musicbrainz-api 1.0.0 → 1.1.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
@@ -90,7 +90,7 @@ const config = {
90
90
  // Required: Application details
91
91
  appName: 'my-app',
92
92
  appVersion: '0.1.0',
93
- appMail: 'user@mail.org',
93
+ appContactInfo: 'user@mail.org',
94
94
 
95
95
  // Optional: Proxy settings (default: no proxy server)
96
96
  proxy: {
@@ -1,5 +1,5 @@
1
1
  import Debug from "debug";
2
- const debug = Debug('musicbrainz-api-node');
2
+ const debug = Debug('musicbrainz-api:http-client');
3
3
  function isConnectionReset(err) {
4
4
  // Undici puts the OS error on .cause, with .code like 'ECONNRESET'
5
5
  const code = err?.cause?.code ?? err?.code;
@@ -18,7 +18,11 @@ export class HttpClient {
18
18
  }
19
19
  postForm(path, formData, options) {
20
20
  const encodedFormData = new URLSearchParams(formData).toString();
21
- return this._fetch('post', path, { ...options, body: encodedFormData, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });
21
+ return this._fetch('post', path, {
22
+ ...options,
23
+ body: encodedFormData,
24
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
25
+ });
22
26
  }
23
27
  postJson(path, json, options) {
24
28
  const encodedJson = JSON.stringify(json);
@@ -63,6 +67,7 @@ export class HttpClient {
63
67
  continue;
64
68
  }
65
69
  }
70
+ debug(`Received status=${response.status}`);
66
71
  await this.registerCookies(response);
67
72
  return response;
68
73
  }
@@ -87,6 +87,18 @@ export interface ISessionInformation {
87
87
  csrf: ICsrfSession;
88
88
  loggedIn?: boolean;
89
89
  }
90
+ export declare class MusicBrainzApiError extends Error {
91
+ readonly status: number;
92
+ readonly statusText: string;
93
+ readonly body?: unknown;
94
+ readonly url?: string;
95
+ constructor(message: string, opts: {
96
+ status: number;
97
+ statusText: string;
98
+ body?: unknown;
99
+ url?: string;
100
+ });
101
+ }
90
102
  export declare class MusicBrainzApi {
91
103
  readonly config: IInternalConfig;
92
104
  protected rateLimiter: RateLimitThreshold;
@@ -10,6 +10,25 @@ export { XmlIsrcList } from './xml/xml-isrc-list.js';
10
10
  export { XmlRecording } from './xml/xml-recording.js';
11
11
  export * from './musicbrainz.types.js';
12
12
  const debug = Debug('musicbrainz-api');
13
+ async function safeJson(res) {
14
+ // Some responses might not be JSON, or the client may have already consumed the body.
15
+ try {
16
+ return await res.json();
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ }
22
+ export class MusicBrainzApiError extends Error {
23
+ constructor(message, opts) {
24
+ super(message);
25
+ this.name = "MusicBrainzApiError";
26
+ this.status = opts.status;
27
+ this.statusText = opts.statusText;
28
+ this.body = opts.body;
29
+ this.url = opts.url;
30
+ }
31
+ }
13
32
  export class MusicBrainzApi {
14
33
  static fetchCsrf(html) {
15
34
  return {
@@ -53,6 +72,20 @@ export class MusicBrainzApi {
53
72
  query,
54
73
  retryLimit: 10
55
74
  });
75
+ if (response.status === 400) {
76
+ const body = await safeJson(response);
77
+ const mb = (body ?? {});
78
+ // Prefer the API-provided message when available.
79
+ const message = mb.error
80
+ ? `MusicBrainz request failed (${response.status}): ${mb.error}`
81
+ : `MusicBrainz request failed (${response.status}): ${response.statusText}`;
82
+ throw new MusicBrainzApiError(message, {
83
+ status: response.status,
84
+ statusText: response.statusText,
85
+ body,
86
+ url: response.url,
87
+ });
88
+ }
56
89
  return response.json();
57
90
  }
58
91
  lookup(entity, mbid, inc = []) {
@@ -83,7 +116,7 @@ export class MusicBrainzApi {
83
116
  }
84
117
  return this.restGet(`/${entity}`, query);
85
118
  }
86
- search(entity, query) {
119
+ async search(entity, query) {
87
120
  const urlQuery = { ...query };
88
121
  if (typeof query.query === 'object') {
89
122
  urlQuery.query = makeAndQueryString(query.query);
@@ -91,7 +124,20 @@ export class MusicBrainzApi {
91
124
  if (Array.isArray(query.inc)) {
92
125
  urlQuery.inc = urlQuery.inc.join(' ');
93
126
  }
94
- return this.restGet(`/${entity}/`, urlQuery);
127
+ const result = await this.restGet(`/${entity}/`, urlQuery);
128
+ // Temporary workaround for https://tickets.metabrainz.org/browse/SEARCH-444
129
+ // Should be resolved by https://github.com/metabrainz/mb-solr/pull/69
130
+ if (entity === 'url') {
131
+ // @ts-expect-error
132
+ result.urls.forEach(url => {
133
+ // @ts-expect-error
134
+ if (!url.relations && url['relation-list']) {
135
+ // @ts-expect-error
136
+ url.relations = url['relation-list'];
137
+ }
138
+ });
139
+ }
140
+ return result;
95
141
  }
96
142
  // ---------------------------------------------------------------------------
97
143
  async postRecording(xmlMetadata) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musicbrainz-api",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "MusicBrainz API client for reading and submitting metadata",
5
5
  "exports": {
6
6
  "node": {
@@ -61,7 +61,7 @@
61
61
  "uuid": "^13.0.0"
62
62
  },
63
63
  "devDependencies": {
64
- "@biomejs/biome": "2.3.11",
64
+ "@biomejs/biome": "2.3.14",
65
65
  "@types/chai": "^5.0.0",
66
66
  "@types/jsontoxml": "^1.0.5",
67
67
  "@types/mocha": "^10.0.4",