musicbrainz-api 0.25.1 → 0.27.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 +4 -0
- package/lib/http-client.js +24 -7
- package/lib/musicbrainz-api.d.ts +12 -1
- package/lib/musicbrainz-api.js +8 -1
- package/lib/musicbrainz.types.d.ts +22 -11
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -100,6 +100,10 @@ const config = {
|
|
|
100
100
|
|
|
101
101
|
// Optional: Disable rate limiting (default: false)
|
|
102
102
|
disableRateLimiting: false,
|
|
103
|
+
|
|
104
|
+
// Optional: Set max number of request with X seconds
|
|
105
|
+
// (default: 15 requests every 18 seconds)
|
|
106
|
+
rateLimit: [15, 18]
|
|
103
107
|
};
|
|
104
108
|
|
|
105
109
|
const mbApi = new MusicBrainzApi(config);
|
package/lib/http-client.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import Debug from "debug";
|
|
2
2
|
const debug = Debug('musicbrainz-api-node');
|
|
3
|
+
function isConnectionReset(err) {
|
|
4
|
+
// Undici puts the OS error on .cause, with .code like 'ECONNRESET'
|
|
5
|
+
const code = err?.cause?.code ?? err?.code;
|
|
6
|
+
// Add other transient codes you consider safe to retry:
|
|
7
|
+
return typeof code === "string" && code === 'ECONNRESET';
|
|
8
|
+
}
|
|
3
9
|
export class HttpClient {
|
|
4
10
|
constructor(httpOptions) {
|
|
5
11
|
this.httpOptions = httpOptions;
|
|
@@ -31,13 +37,24 @@ export class HttpClient {
|
|
|
31
37
|
headers.set('Cookie', cookies);
|
|
32
38
|
}
|
|
33
39
|
while (retryLimit > 0) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
let response;
|
|
41
|
+
try {
|
|
42
|
+
response = await fetch(url, {
|
|
43
|
+
method,
|
|
44
|
+
...options,
|
|
45
|
+
headers,
|
|
46
|
+
body: options.body,
|
|
47
|
+
redirect: options.followRedirects === false ? 'manual' : 'follow'
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
if (isConnectionReset(err)) {
|
|
52
|
+
// Retry on TCP connection resets
|
|
53
|
+
await this._delay(retryTimeout); // wait 200ms before retry
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
41
58
|
if (response.status === 429 || response.status === 503) {
|
|
42
59
|
debug(`Received status=${response.status}, assume reached rate limit, retry in ${retryTimeout} ms`);
|
|
43
60
|
retryLimit--;
|
package/lib/musicbrainz-api.d.ts
CHANGED
|
@@ -39,7 +39,7 @@ export type InstrumentIncludes = MiscIncludes | RelationsIncludes;
|
|
|
39
39
|
export type LabelIncludes = MiscIncludes | RelationsIncludes | 'releases';
|
|
40
40
|
export type PlaceIncludes = MiscIncludes | RelationsIncludes;
|
|
41
41
|
export type RecordingIncludes = MiscIncludes | RelationsIncludes | SubQueryIncludes | 'artists' | 'releases' | 'isrcs';
|
|
42
|
-
export type ReleaseIncludes = MiscIncludes | SubQueryIncludes | RelationsIncludes | 'artists' | 'collections' | 'labels' | 'recordings' | 'release-groups';
|
|
42
|
+
export type ReleaseIncludes = MiscIncludes | SubQueryIncludes | RelationsIncludes | 'artists' | 'collections' | 'labels' | 'recordings' | 'release-groups' | 'recording-level-rels';
|
|
43
43
|
export type ReleaseGroupIncludes = MiscIncludes | SubQueryIncludes | RelationsIncludes | 'artists' | 'releases';
|
|
44
44
|
export type SeriesIncludes = MiscIncludes | RelationsIncludes;
|
|
45
45
|
export type WorkIncludes = MiscIncludes | RelationsIncludes;
|
|
@@ -64,6 +64,17 @@ export interface IMusicBrainzConfig {
|
|
|
64
64
|
*/
|
|
65
65
|
appContactInfo?: string;
|
|
66
66
|
disableRateLimiting?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Optional rate limit configuration.
|
|
69
|
+
*
|
|
70
|
+
* [maxRequests, periodSeconds]
|
|
71
|
+
*
|
|
72
|
+
* maxRequests The maximum number of allowed requests within the period
|
|
73
|
+
* periodSeconds The time window in seconds during which requests are counted
|
|
74
|
+
*
|
|
75
|
+
* Default is [15, 18], which allows up to 15 requests every 18 seconds
|
|
76
|
+
*/
|
|
77
|
+
rateLimit?: [number, number];
|
|
67
78
|
}
|
|
68
79
|
interface IInternalConfig extends IMusicBrainzConfig {
|
|
69
80
|
baseUrl: string;
|
package/lib/musicbrainz-api.js
CHANGED
|
@@ -36,7 +36,8 @@ export class MusicBrainzApi {
|
|
|
36
36
|
..._config
|
|
37
37
|
};
|
|
38
38
|
this.httpClient = this.initHttpClient();
|
|
39
|
-
this.
|
|
39
|
+
const limits = this.config.rateLimit ?? [15, 18];
|
|
40
|
+
this.rateLimiter = new RateLimitThreshold(limits[0], limits[1]);
|
|
40
41
|
}
|
|
41
42
|
initHttpClient() {
|
|
42
43
|
return new HttpClient({
|
|
@@ -74,6 +75,12 @@ export class MusicBrainzApi {
|
|
|
74
75
|
// Serialize include parameter
|
|
75
76
|
query.inc = inc.join(' ');
|
|
76
77
|
}
|
|
78
|
+
for (const pipedFilter of ['type', 'status']) {
|
|
79
|
+
if (query[pipedFilter]) {
|
|
80
|
+
// Serialize type parameter
|
|
81
|
+
query[pipedFilter] = query[pipedFilter].join('|');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
77
84
|
return this.restGet(`/${entity}`, query);
|
|
78
85
|
}
|
|
79
86
|
search(entity, query) {
|
|
@@ -46,7 +46,7 @@ export interface IMatch {
|
|
|
46
46
|
score: number;
|
|
47
47
|
}
|
|
48
48
|
export type Gender = 'male' | 'female' | 'other' | 'not applicable';
|
|
49
|
-
export interface IArtist extends ITypedEntity {
|
|
49
|
+
export interface IArtist extends ITypedEntity, IMayHaveRelations {
|
|
50
50
|
name: string;
|
|
51
51
|
disambiguation: string;
|
|
52
52
|
'sort-name': string;
|
|
@@ -60,7 +60,6 @@ export interface IArtist extends ITypedEntity {
|
|
|
60
60
|
area?: IArea;
|
|
61
61
|
begin_area?: IArea;
|
|
62
62
|
end_area?: IArea;
|
|
63
|
-
relations?: IRelation[];
|
|
64
63
|
/**
|
|
65
64
|
* Only defined if 'releases' are includes
|
|
66
65
|
*/
|
|
@@ -104,7 +103,7 @@ export interface IInstrument extends ITypedEntity {
|
|
|
104
103
|
export type ReleaseQuality = 'normal' | 'high';
|
|
105
104
|
export type ReleaseStatus = 'Official' | 'Promotion' | 'Bootleg' | 'Pseudo-release' | 'Withdrawn' | 'Expunged' | 'Cancelled';
|
|
106
105
|
export type ReleasePackaging = 'Book' | 'Box' | 'Cardboard/Paper Sleeve' | 'Cassette Case' | 'Clamshell Case' | 'Digibook' | 'Digifile' | 'Digipak' | 'Discbox Slider' | 'Fatbox' | 'Gatefold Cover' | 'Jewel case' | 'Keep Case' | 'Longbox' | 'Metal Tin' | 'Plastic sleeve' | 'Slidepack' | 'Slim Jewel Case' | 'Snap Case' | 'SnapPack' | 'Super Jewel Box' | 'Other' | 'None';
|
|
107
|
-
export interface IRelease extends IEntity {
|
|
106
|
+
export interface IRelease extends IEntity, IMayHaveRelations {
|
|
108
107
|
title: string;
|
|
109
108
|
'text-representation': {
|
|
110
109
|
'language': string;
|
|
@@ -123,7 +122,6 @@ export interface IRelease extends IEntity {
|
|
|
123
122
|
country: string;
|
|
124
123
|
quality: ReleaseQuality;
|
|
125
124
|
barcode: string;
|
|
126
|
-
relations?: IRelation[];
|
|
127
125
|
'artist-credit'?: IArtistCredit[];
|
|
128
126
|
'release-group'?: IReleaseGroup;
|
|
129
127
|
collections?: ICollection[];
|
|
@@ -135,14 +133,13 @@ export interface IReleaseEvent {
|
|
|
135
133
|
date?: string;
|
|
136
134
|
}
|
|
137
135
|
export type MediaFormatType = 'Digital Media';
|
|
138
|
-
export interface IRecording extends IEntity {
|
|
136
|
+
export interface IRecording extends IEntity, IMayHaveRelations {
|
|
139
137
|
video: boolean;
|
|
140
138
|
length: number;
|
|
141
139
|
title: string;
|
|
142
140
|
disambiguation: string;
|
|
143
141
|
isrcs?: string[];
|
|
144
142
|
releases?: IRelease[];
|
|
145
|
-
relations?: IRelation[];
|
|
146
143
|
'artist-credit'?: IArtistCredit[];
|
|
147
144
|
aliases?: IAlias[];
|
|
148
145
|
'first-release-date': string;
|
|
@@ -273,7 +270,7 @@ export interface IRelation {
|
|
|
273
270
|
url?: IUrl;
|
|
274
271
|
release?: IRelease;
|
|
275
272
|
}
|
|
276
|
-
export interface
|
|
273
|
+
export interface IMayHaveRelations {
|
|
277
274
|
relations: IRelation[];
|
|
278
275
|
}
|
|
279
276
|
export interface IWork extends IEntity {
|
|
@@ -301,10 +298,9 @@ export interface ISeries extends ITypedEntity {
|
|
|
301
298
|
export interface ITag {
|
|
302
299
|
name: string;
|
|
303
300
|
}
|
|
304
|
-
export interface IUrl extends IEntity {
|
|
301
|
+
export interface IUrl extends IEntity, IMayHaveRelations {
|
|
305
302
|
id: string;
|
|
306
303
|
resource: string;
|
|
307
|
-
relations?: IRelationList[];
|
|
308
304
|
}
|
|
309
305
|
export interface IExernalIds {
|
|
310
306
|
[type: string]: string;
|
|
@@ -321,6 +317,14 @@ export type OtherEntityTypes = 'annotation' | 'cdstub' | 'tag';
|
|
|
321
317
|
*/
|
|
322
318
|
export type EntityType = 'annotation' | 'area' | 'artist' | 'collection' | 'event' | 'instrument' | 'label' | 'place' | 'recording' | 'release' | 'release-group' | 'series' | 'work' | 'url';
|
|
323
319
|
export type Relationships = 'area-rels' | 'artist-rels' | 'event-rels' | 'instrument-rels' | 'label-rels' | 'place-rels' | 'recording-rels' | 'release-rels' | 'release-group-rels' | 'series-rels' | 'url-rels' | 'work-rels';
|
|
320
|
+
/**
|
|
321
|
+
* Ref: https://musicbrainz.org/doc/MusicBrainz_API#Release_.28Group.29_Type_and_Status
|
|
322
|
+
*/
|
|
323
|
+
export type ReleaseStatusQuery = 'official' | 'promotion' | 'bootleg' | 'pseudo-release' | 'withdrawn' | 'cancelled';
|
|
324
|
+
/**
|
|
325
|
+
* Ref: https://musicbrainz.org/doc/MusicBrainz_API#Release_.28Group.29_Type_and_Status
|
|
326
|
+
*/
|
|
327
|
+
export type ReleaseTypeQuery = 'album' | 'single' | 'ep' | 'broadcast' | 'other' | 'audiobook' | 'compilation' | 'demo' | 'dj-mix' | 'field recording' | 'interview' | 'live' | 'mixtape/street' | 'remix' | 'soundtrack' | 'spokenword';
|
|
324
328
|
export declare enum LinkType {
|
|
325
329
|
license = 302,
|
|
326
330
|
production = 256,
|
|
@@ -346,6 +350,13 @@ export interface IPagination {
|
|
|
346
350
|
*/
|
|
347
351
|
limit?: number;
|
|
348
352
|
}
|
|
353
|
+
/**
|
|
354
|
+
* Release and release-group types
|
|
355
|
+
*/
|
|
356
|
+
export interface IReleaseTypeAndStatus {
|
|
357
|
+
status?: ReleaseStatusQuery[];
|
|
358
|
+
type?: ReleaseTypeQuery[];
|
|
359
|
+
}
|
|
349
360
|
/**
|
|
350
361
|
* https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2/Search#Artist
|
|
351
362
|
*/
|
|
@@ -510,7 +521,7 @@ interface BrowseReleasesEntityParams {
|
|
|
510
521
|
track_artist: string;
|
|
511
522
|
work: string;
|
|
512
523
|
}
|
|
513
|
-
export type IBrowseReleasesQuery = IPagination & OneOf<BrowseReleasesEntityParams>;
|
|
524
|
+
export type IBrowseReleasesQuery = IPagination & IReleaseTypeAndStatus & OneOf<BrowseReleasesEntityParams>;
|
|
514
525
|
/**
|
|
515
526
|
* List of entity names allowed for browsing artists by a single MBID.
|
|
516
527
|
* Used as a key set for constructing exclusive query types.
|
|
@@ -591,7 +602,7 @@ interface BrowseReleaseGroupsEntityParams {
|
|
|
591
602
|
collection: string;
|
|
592
603
|
release: string;
|
|
593
604
|
}
|
|
594
|
-
export type IBrowseReleaseGroupsQuery = IPagination & OneOf<BrowseReleaseGroupsEntityParams>;
|
|
605
|
+
export type IBrowseReleaseGroupsQuery = IPagination & IReleaseTypeAndStatus & OneOf<BrowseReleaseGroupsEntityParams>;
|
|
595
606
|
/**
|
|
596
607
|
* List of entity names allowed for browsing works by a single MBID.
|
|
597
608
|
* Used as a key set for constructing exclusive query types.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "musicbrainz-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "MusicBrainz API client for reading and submitting metadata",
|
|
5
5
|
"exports": {
|
|
6
6
|
"node": {
|
|
@@ -58,22 +58,22 @@
|
|
|
58
58
|
"rate-limit-threshold": "^0.2.0",
|
|
59
59
|
"spark-md5": "^3.0.2",
|
|
60
60
|
"tough-cookie": "^5.0.0",
|
|
61
|
-
"uuid": "^
|
|
61
|
+
"uuid": "^13.0.0"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
-
"@biomejs/biome": "2.
|
|
64
|
+
"@biomejs/biome": "2.3.8",
|
|
65
65
|
"@types/chai": "^5.0.0",
|
|
66
66
|
"@types/jsontoxml": "^1.0.5",
|
|
67
67
|
"@types/mocha": "^10.0.4",
|
|
68
68
|
"@types/node": "^24.0.3",
|
|
69
|
-
"@types/sinon": "^
|
|
69
|
+
"@types/sinon": "^21.0.0",
|
|
70
70
|
"@types/source-map-support": "^0",
|
|
71
71
|
"@types/spark-md5": "^3",
|
|
72
72
|
"@types/tough-cookie": "^4.0.5",
|
|
73
|
-
"@types/uuid": "^
|
|
73
|
+
"@types/uuid": "^11.0.0",
|
|
74
74
|
"c8": "^10.1.2",
|
|
75
|
-
"chai": "^
|
|
76
|
-
"del-cli": "^
|
|
75
|
+
"chai": "^6.2.0",
|
|
76
|
+
"del-cli": "^7.0.0",
|
|
77
77
|
"mocha": "^11.0.1",
|
|
78
78
|
"remark-cli": "^12.0.0",
|
|
79
79
|
"remark-preset-lint-recommended": "^7.0.0",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"compile": "yarn run compile-lib && yarn run compile-test",
|
|
90
90
|
"lint:md": "remark -u preset-lint-recommended .",
|
|
91
91
|
"lint:ts": "biome check",
|
|
92
|
-
"lint:fix": "biome check --
|
|
92
|
+
"lint:fix": "biome check --write",
|
|
93
93
|
"lint": "yarn run lint:md && yarn run lint:ts",
|
|
94
94
|
"test": "mocha",
|
|
95
95
|
"build": "yarn run clean && yarn run compile",
|