@misofm/sdk 0.3.0 → 0.5.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 +31 -0
- package/dist/auth.d.ts +73 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +193 -0
- package/dist/auth.js.map +1 -0
- package/dist/catalog.d.ts +4 -17
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +46 -58
- package/dist/catalog.js.map +1 -1
- package/dist/cover.d.ts +10 -0
- package/dist/cover.d.ts.map +1 -1
- package/dist/cover.js +66 -19
- package/dist/cover.js.map +1 -1
- package/dist/credits.d.ts +10 -0
- package/dist/credits.d.ts.map +1 -1
- package/dist/credits.js +149 -39
- package/dist/credits.js.map +1 -1
- package/dist/drop.d.ts +7 -0
- package/dist/drop.d.ts.map +1 -1
- package/dist/drop.js +84 -34
- package/dist/drop.js.map +1 -1
- package/dist/pressing.d.ts.map +1 -1
- package/dist/pressing.js +59 -16
- package/dist/pressing.js.map +1 -1
- package/dist/read/artist.d.ts +1 -6
- package/dist/read/artist.d.ts.map +1 -1
- package/dist/read/artist.js +39 -22
- package/dist/read/artist.js.map +1 -1
- package/dist/read/catalog.d.ts +24 -3
- package/dist/read/catalog.d.ts.map +1 -1
- package/dist/read/catalog.js +190 -89
- package/dist/read/catalog.js.map +1 -1
- package/dist/read/index.d.ts +5 -3
- package/dist/read/index.d.ts.map +1 -1
- package/dist/read/index.js +2 -2
- package/dist/read/index.js.map +1 -1
- package/dist/read/types.d.ts +12 -1
- package/dist/read/types.d.ts.map +1 -1
- package/dist/read/works.d.ts +3 -2
- package/dist/read/works.d.ts.map +1 -1
- package/dist/read/works.js +158 -48
- package/dist/read/works.js.map +1 -1
- package/package.json +5 -1
- package/src/auth.ts +275 -0
- package/src/catalog.ts +71 -77
- package/src/cover.ts +107 -26
- package/src/credits.ts +353 -89
- package/src/drop.ts +150 -40
- package/src/pressing.ts +146 -36
- package/src/read/artist.ts +73 -34
- package/src/read/catalog.ts +324 -89
- package/src/read/index.ts +23 -2
- package/src/read/types.ts +16 -2
- package/src/read/works.ts +224 -59
package/src/read/catalog.ts
CHANGED
|
@@ -10,13 +10,25 @@
|
|
|
10
10
|
// credits → recordings); the same work happens once here, inside one datacenter,
|
|
11
11
|
// and every subsequent visitor is served from cache.
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
import { getCurrentDrops, getDrop, type DropView } from "../drop.ts";
|
|
14
|
+
import {
|
|
15
|
+
getReleaseCoversByIds,
|
|
16
|
+
parseReleaseCoverContent,
|
|
17
|
+
releaseCoverFieldId,
|
|
18
|
+
type ReleaseCoverView,
|
|
19
|
+
type CoverImageRef,
|
|
20
|
+
} from "../cover.ts";
|
|
21
|
+
import {
|
|
22
|
+
getReleaseCreditsByIds,
|
|
23
|
+
parseReleaseCreditsContent,
|
|
24
|
+
releaseCreditsFieldId,
|
|
25
|
+
type CreditView,
|
|
26
|
+
} from "../credits.ts";
|
|
27
|
+
import { getTrackCreditsByRecordingIds } from "../catalog.ts";
|
|
28
|
+
import { getReleaseById, getReleasesByIds, isNotFound } from "@misonetwork/sdk";
|
|
17
29
|
import type { Release } from "@misonetwork/sdk";
|
|
18
30
|
import type { MisoClient } from "./client.ts";
|
|
19
|
-
import { getRecordingTitles } from "./works.ts";
|
|
31
|
+
import { getRecordingTitles, parseReleaseObject } from "./works.ts";
|
|
20
32
|
import { gte, int, ms, msOrNull, u64, u64OrNull } from "./internal/scalars.ts";
|
|
21
33
|
import { quiltPatchId, u256ToB64Url } from "./internal/walrus.ts";
|
|
22
34
|
import type {
|
|
@@ -41,15 +53,30 @@ import type {
|
|
|
41
53
|
/** Aggregator URL for a cover image ref, whichever Walrus variant it is. */
|
|
42
54
|
function imageUrl(aggregator: string, ref: CoverImageRef): string {
|
|
43
55
|
const base = aggregator.replace(/\/$/, "");
|
|
44
|
-
if (ref.kind === "blob")
|
|
45
|
-
|
|
56
|
+
if (ref.kind === "blob")
|
|
57
|
+
return `${base}/v1/blobs/${u256ToB64Url(ref.blobId)}`;
|
|
58
|
+
const patch = quiltPatchId(
|
|
59
|
+
ref.quiltId,
|
|
60
|
+
ref.version,
|
|
61
|
+
ref.startIndex,
|
|
62
|
+
ref.endIndex,
|
|
63
|
+
);
|
|
46
64
|
return `${base}/v1/blobs/by-quilt-patch-id/${patch}`;
|
|
47
65
|
}
|
|
48
66
|
|
|
49
|
-
function toCover(
|
|
67
|
+
function toCover(
|
|
68
|
+
aggregator: string,
|
|
69
|
+
view: ReleaseCoverView | null,
|
|
70
|
+
): Cover | null {
|
|
50
71
|
if (!view) return null;
|
|
51
|
-
const image = (ref: CoverImageRef): CoverImage => ({
|
|
52
|
-
|
|
72
|
+
const image = (ref: CoverImageRef): CoverImage => ({
|
|
73
|
+
kind: ref.kind,
|
|
74
|
+
url: imageUrl(aggregator, ref),
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
still: image(view.still),
|
|
78
|
+
animated: view.animated ? image(view.animated) : null,
|
|
79
|
+
};
|
|
53
80
|
}
|
|
54
81
|
|
|
55
82
|
// ── Currency ─────────────────────────────────────────────────────────────────
|
|
@@ -61,9 +88,14 @@ function toCover(aggregator: string, view: ReleaseCoverView | null): Cover | nul
|
|
|
61
88
|
*/
|
|
62
89
|
export function currencyInfo(type: string | null): Currency {
|
|
63
90
|
if (!type) return { type: null, symbol: "COIN", decimals: 9 };
|
|
64
|
-
if (/::fakeusd::/i.test(type))
|
|
91
|
+
if (/::fakeusd::/i.test(type))
|
|
92
|
+
return { type, symbol: "FAKEUSD", decimals: 6 };
|
|
65
93
|
if (type === "0x2::sui::SUI") return { type, symbol: "SUI", decimals: 9 };
|
|
66
|
-
return {
|
|
94
|
+
return {
|
|
95
|
+
type,
|
|
96
|
+
symbol: (type.split("::").pop() ?? "COIN").toUpperCase(),
|
|
97
|
+
decimals: 9,
|
|
98
|
+
};
|
|
67
99
|
}
|
|
68
100
|
|
|
69
101
|
// ── Projections ──────────────────────────────────────────────────────────────
|
|
@@ -71,7 +103,10 @@ export function currencyInfo(type: string | null): Currency {
|
|
|
71
103
|
function toPressingView(drop: DropView): PressingView {
|
|
72
104
|
const quantitySold = u64(drop.quantitySold);
|
|
73
105
|
const maxSupply = u64OrNull(drop.maxSupply);
|
|
74
|
-
const price: Price = {
|
|
106
|
+
const price: Price = {
|
|
107
|
+
kind: drop.price.kind,
|
|
108
|
+
amount: u64(drop.price.amount),
|
|
109
|
+
};
|
|
75
110
|
return {
|
|
76
111
|
id: drop.id,
|
|
77
112
|
releaseId: drop.releaseId,
|
|
@@ -87,29 +122,40 @@ function toPressingView(drop: DropView): PressingView {
|
|
|
87
122
|
}
|
|
88
123
|
|
|
89
124
|
function toWorkState(state: Release["state"]): WorkState {
|
|
90
|
-
return state.type === "Published"
|
|
125
|
+
return state.type === "Published"
|
|
126
|
+
? { type: "Published", timestampMs: state.timestampMs }
|
|
127
|
+
: { type: "Initialized" };
|
|
91
128
|
}
|
|
92
129
|
|
|
93
130
|
function toCredits(credits: CreditView[] | null): Credit[] {
|
|
94
|
-
return (credits ?? []).map((c) => ({
|
|
131
|
+
return (credits ?? []).map((c) => ({
|
|
132
|
+
partyId: c.partyId,
|
|
133
|
+
displayName: c.displayName,
|
|
134
|
+
roles: [...c.roles],
|
|
135
|
+
}));
|
|
95
136
|
}
|
|
96
137
|
|
|
97
138
|
/** The release's PRIMARY credits, in chain order — a release's artist line. */
|
|
98
139
|
export function primaryArtistNames(credits: Credit[]): string[] {
|
|
99
|
-
return credits
|
|
140
|
+
return credits
|
|
141
|
+
.filter((c) => c.roles.includes("Primary"))
|
|
142
|
+
.map((c) => c.displayName);
|
|
100
143
|
}
|
|
101
144
|
|
|
102
145
|
/**
|
|
103
146
|
* Number the protocol's flat tracklist. Display grouping such as discs belongs
|
|
104
147
|
* to metadata extensions and can be layered onto this projection later.
|
|
105
148
|
*/
|
|
106
|
-
function toTracks(
|
|
149
|
+
function toTracks(
|
|
150
|
+
release: Release,
|
|
151
|
+
titles: Record<string, string>,
|
|
152
|
+
): TrackView[] {
|
|
107
153
|
return release.tracks.map((track, index) => ({
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
154
|
+
no: `${index + 1}`,
|
|
155
|
+
title: titles[track.recordingId] ?? "Untitled",
|
|
156
|
+
recordingId: track.recordingId,
|
|
157
|
+
splitBps: int(track.splitBps.value),
|
|
158
|
+
disc: 1,
|
|
113
159
|
}));
|
|
114
160
|
}
|
|
115
161
|
|
|
@@ -124,13 +170,105 @@ function toTracks(release: Release, titles: Record<string, string>): TrackView[]
|
|
|
124
170
|
* simply misses them rather than failing. Ported verbatim from miso-app's
|
|
125
171
|
* `lib/pressing.ts:readReleaseCover`.
|
|
126
172
|
*/
|
|
127
|
-
export async function readReleaseCover(
|
|
173
|
+
export async function readReleaseCover(
|
|
174
|
+
client: MisoClient,
|
|
175
|
+
releaseId: string,
|
|
176
|
+
): Promise<Cover | null> {
|
|
128
177
|
const { releaseCoverArt, legacyReleaseCoverArt } = client.config.protocol;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
178
|
+
const covers = await getReleaseCoversByIds(
|
|
179
|
+
client.protocol,
|
|
180
|
+
[releaseId],
|
|
181
|
+
[releaseCoverArt, ...legacyReleaseCoverArt],
|
|
182
|
+
).catch(() => ({}) as Partial<Record<string, ReleaseCoverView>>);
|
|
183
|
+
return toCover(client.config.walrusAggregatorUrl, covers[releaseId] ?? null);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export type ReleaseResourceInclude = "cover" | "credits";
|
|
187
|
+
|
|
188
|
+
export interface ReleaseResources {
|
|
189
|
+
release: Release;
|
|
190
|
+
cover?: Cover | null;
|
|
191
|
+
credits?: Credit[];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* A release plus selected derived resources in one heterogeneous Core batch.
|
|
196
|
+
* Every extension id is deterministic, so separate object calls only add
|
|
197
|
+
* network round trips without discovering anything new.
|
|
198
|
+
*/
|
|
199
|
+
export async function getReleaseResources(
|
|
200
|
+
client: MisoClient,
|
|
201
|
+
releaseId: string,
|
|
202
|
+
include: readonly ReleaseResourceInclude[] = [],
|
|
203
|
+
): Promise<ReleaseResources> {
|
|
204
|
+
const wantsCover = include.includes("cover");
|
|
205
|
+
const wantsCredits = include.includes("credits");
|
|
206
|
+
const coverPackages = wantsCover
|
|
207
|
+
? [
|
|
208
|
+
client.config.protocol.releaseCoverArt,
|
|
209
|
+
...client.config.protocol.legacyReleaseCoverArt,
|
|
210
|
+
]
|
|
211
|
+
: [];
|
|
212
|
+
const coverFieldIds = coverPackages.map((packageId) =>
|
|
213
|
+
releaseCoverFieldId(releaseId, packageId),
|
|
214
|
+
);
|
|
215
|
+
const creditsFieldId = wantsCredits
|
|
216
|
+
? releaseCreditsFieldId(releaseId, client.config.protocol.releaseCredits)
|
|
217
|
+
: null;
|
|
218
|
+
const objectIds = [
|
|
219
|
+
releaseId,
|
|
220
|
+
...coverFieldIds,
|
|
221
|
+
...(creditsFieldId ? [creditsFieldId] : []),
|
|
222
|
+
];
|
|
223
|
+
const { objects } = await client.protocol.core.getObjects({
|
|
224
|
+
objectIds,
|
|
225
|
+
include: { content: true, json: true },
|
|
226
|
+
});
|
|
227
|
+
const releaseObject = objects[0];
|
|
228
|
+
if (!releaseObject) throw new Error(`Release not found: ${releaseId}`);
|
|
229
|
+
if (releaseObject instanceof Error) throw releaseObject;
|
|
230
|
+
if (!releaseObject.content)
|
|
231
|
+
throw new Error(`Release has no content: ${releaseId}`);
|
|
232
|
+
const release = parseReleaseObject(
|
|
233
|
+
releaseObject.objectId,
|
|
234
|
+
releaseObject.content,
|
|
235
|
+
releaseObject.json,
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
let cover: Cover | null | undefined;
|
|
239
|
+
if (wantsCover) {
|
|
240
|
+
let view: ReleaseCoverView | null = null;
|
|
241
|
+
for (let index = 0; index < coverFieldIds.length; index += 1) {
|
|
242
|
+
const object = objects[index + 1];
|
|
243
|
+
if (!object || object instanceof Error || !object.content) continue;
|
|
244
|
+
try {
|
|
245
|
+
view = parseReleaseCoverContent(object.content);
|
|
246
|
+
} catch {
|
|
247
|
+
view = null;
|
|
248
|
+
}
|
|
249
|
+
if (view) break;
|
|
250
|
+
}
|
|
251
|
+
cover = toCover(client.config.walrusAggregatorUrl, view);
|
|
132
252
|
}
|
|
133
|
-
|
|
253
|
+
|
|
254
|
+
let credits: Credit[] | undefined;
|
|
255
|
+
if (wantsCredits) {
|
|
256
|
+
const object = objects[1 + coverFieldIds.length];
|
|
257
|
+
let view: CreditView[] = [];
|
|
258
|
+
if (object && !(object instanceof Error) && object.content) {
|
|
259
|
+
try {
|
|
260
|
+
view = parseReleaseCreditsContent(object.content);
|
|
261
|
+
} catch {
|
|
262
|
+
view = [];
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
credits = toCredits(view);
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
release,
|
|
269
|
+
...(wantsCover ? { cover: cover ?? null } : {}),
|
|
270
|
+
...(wantsCredits ? { credits: credits ?? [] } : {}),
|
|
271
|
+
};
|
|
134
272
|
}
|
|
135
273
|
|
|
136
274
|
// ── Release ──────────────────────────────────────────────────────────────────
|
|
@@ -144,64 +282,108 @@ export async function readReleaseCover(client: MisoClient, releaseId: string): P
|
|
|
144
282
|
* split is inherited from `lib/drops.ts` and is what keeps a half-configured
|
|
145
283
|
* release renderable.
|
|
146
284
|
*/
|
|
147
|
-
export
|
|
148
|
-
const { releaseCredits } = client.config.protocol;
|
|
285
|
+
export type ReleaseInclude = "trackCredits";
|
|
149
286
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
287
|
+
export interface GetReleaseOptions {
|
|
288
|
+
include?: readonly ReleaseInclude[];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export async function getReleaseDetail(
|
|
292
|
+
client: MisoClient,
|
|
293
|
+
releaseId: string,
|
|
294
|
+
options: GetReleaseOptions = {},
|
|
295
|
+
): Promise<ReleaseDetail> {
|
|
296
|
+
const { release, cover, credits } = await getReleaseResources(
|
|
297
|
+
client,
|
|
298
|
+
releaseId,
|
|
299
|
+
["cover", "credits"],
|
|
300
|
+
);
|
|
155
301
|
|
|
156
302
|
const recordingIds = release.tracks.map((track) => track.recordingId);
|
|
157
|
-
const titles = await
|
|
303
|
+
const [titles, trackCredits] = await Promise.all([
|
|
304
|
+
getRecordingTitles(
|
|
305
|
+
client.protocol,
|
|
306
|
+
client.graphql,
|
|
307
|
+
recordingIds,
|
|
308
|
+
client.config.protocol.miso,
|
|
309
|
+
),
|
|
310
|
+
options.include?.includes("trackCredits")
|
|
311
|
+
? getTrackCreditsForRecordingIds(client, recordingIds)
|
|
312
|
+
: Promise.resolve(undefined),
|
|
313
|
+
]);
|
|
158
314
|
|
|
159
|
-
const creditViews =
|
|
315
|
+
const creditViews = credits ?? [];
|
|
160
316
|
return {
|
|
161
317
|
id: release.id,
|
|
162
318
|
title: release.title,
|
|
163
319
|
subtitle: null,
|
|
164
320
|
state: toWorkState(release.state),
|
|
165
|
-
publishedAtMs:
|
|
166
|
-
|
|
321
|
+
publishedAtMs:
|
|
322
|
+
release.state.type === "Published" ? release.state.timestampMs : null,
|
|
323
|
+
cover: cover ?? null,
|
|
167
324
|
credits: creditViews,
|
|
168
325
|
primaryArtists: primaryArtistNames(creditViews),
|
|
169
326
|
discCount: release.tracks.length > 0 ? 1 : 0,
|
|
170
327
|
tracks: toTracks(release, titles),
|
|
328
|
+
...(trackCredits !== undefined ? { trackCredits } : {}),
|
|
171
329
|
};
|
|
172
330
|
}
|
|
173
331
|
|
|
332
|
+
async function getTrackCreditsForRecordingIds(
|
|
333
|
+
client: MisoClient,
|
|
334
|
+
recordingIds: readonly string[],
|
|
335
|
+
): Promise<Record<string, TrackCredits>> {
|
|
336
|
+
const { compositionCredits, recordingCredits, miso } = client.config.protocol;
|
|
337
|
+
const tracks = await getTrackCreditsByRecordingIds(
|
|
338
|
+
client.protocol,
|
|
339
|
+
client.graphql,
|
|
340
|
+
recordingIds,
|
|
341
|
+
{
|
|
342
|
+
misoPackageId: miso,
|
|
343
|
+
compositionCreditsPackageId: compositionCredits,
|
|
344
|
+
recordingCreditsPackageId: recordingCredits,
|
|
345
|
+
},
|
|
346
|
+
);
|
|
347
|
+
return Object.fromEntries(
|
|
348
|
+
Object.entries(tracks).map(([id, track]) => [
|
|
349
|
+
id,
|
|
350
|
+
{
|
|
351
|
+
compositionCredits: toCredits(track.compositionCredits),
|
|
352
|
+
recordingCredits: {
|
|
353
|
+
credits: toCredits(track.recordingCredits.credits),
|
|
354
|
+
primaryArtistIds: [...track.recordingCredits.primaryArtistIds],
|
|
355
|
+
featuredArtistIds: [...track.recordingCredits.featuredArtistIds],
|
|
356
|
+
},
|
|
357
|
+
} satisfies TrackCredits,
|
|
358
|
+
]),
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
174
362
|
/**
|
|
175
363
|
* Per-track credits for a release, keyed by recording id. A track with no credits
|
|
176
364
|
* set maps to an empty entry rather than being absent, so a caller can tell
|
|
177
365
|
* "nothing credited" from "no such track".
|
|
178
366
|
*/
|
|
179
|
-
export async function getTrackCredits(
|
|
180
|
-
|
|
367
|
+
export async function getTrackCredits(
|
|
368
|
+
client: MisoClient,
|
|
369
|
+
releaseId: string,
|
|
370
|
+
): Promise<Record<string, TrackCredits>> {
|
|
181
371
|
const release = await getReleaseById(client.protocol, releaseId);
|
|
182
372
|
const recordingIds = release.tracks.map((track) => track.recordingId);
|
|
183
|
-
|
|
184
|
-
const entries = await Promise.all(
|
|
185
|
-
recordingIds.map(async (id) => {
|
|
186
|
-
const view = await getRecordingCredits(client.protocol, id, recordingCredits).catch(() => null);
|
|
187
|
-
const credits: TrackCredits = {
|
|
188
|
-
credits: toCredits(view?.credits ?? null),
|
|
189
|
-
primaryArtistIds: view?.primaryArtistIds ?? [],
|
|
190
|
-
featuredArtistIds: view?.featuredArtistIds ?? [],
|
|
191
|
-
};
|
|
192
|
-
return [id, credits] as const;
|
|
193
|
-
}),
|
|
194
|
-
);
|
|
195
|
-
return Object.fromEntries(entries);
|
|
373
|
+
return getTrackCreditsForRecordingIds(client, recordingIds);
|
|
196
374
|
}
|
|
197
375
|
|
|
198
376
|
// ── Pressing ─────────────────────────────────────────────────────────────────
|
|
199
377
|
|
|
200
378
|
/** Everything the pressing buy page renders. `null` when no such pressing exists. */
|
|
201
|
-
export async function getPressingDetail(
|
|
379
|
+
export async function getPressingDetail(
|
|
380
|
+
client: MisoClient,
|
|
381
|
+
pressingId: string,
|
|
382
|
+
options: GetReleaseOptions = {},
|
|
383
|
+
): Promise<PressingDetail | null> {
|
|
202
384
|
const pressing = await getDrop(client.protocol, pressingId);
|
|
203
385
|
if (!pressing) return null;
|
|
204
|
-
const release = await getReleaseDetail(client, pressing.releaseId);
|
|
386
|
+
const release = await getReleaseDetail(client, pressing.releaseId, options);
|
|
205
387
|
return { pressing: toPressingView(pressing), release };
|
|
206
388
|
}
|
|
207
389
|
|
|
@@ -210,17 +392,18 @@ export async function getPressingDetail(client: MisoClient, pressingId: string):
|
|
|
210
392
|
* object really is a `Drop` before spending reads on it — a release id or a
|
|
211
393
|
* record id pasted by mistake must come back as `null`, not as a half-built card.
|
|
212
394
|
*/
|
|
213
|
-
export async function getDropPreview(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
395
|
+
export async function getDropPreview(
|
|
396
|
+
client: MisoClient,
|
|
397
|
+
dropId: string,
|
|
398
|
+
): Promise<DropPreview | null> {
|
|
217
399
|
const pressing = await getDrop(client.protocol, dropId);
|
|
218
400
|
if (!pressing?.currencyType) return null;
|
|
219
401
|
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
402
|
+
const { release, cover } = await getReleaseResources(
|
|
403
|
+
client,
|
|
404
|
+
pressing.releaseId,
|
|
405
|
+
["cover"],
|
|
406
|
+
);
|
|
224
407
|
|
|
225
408
|
return {
|
|
226
409
|
pressingId: dropId,
|
|
@@ -243,31 +426,51 @@ export async function getDropPreview(client: MisoClient, dropId: string): Promis
|
|
|
243
426
|
* Following the release's `CurrentDropKey` pointer keeps this list pointing at the
|
|
244
427
|
* live edition without anyone touching the config.
|
|
245
428
|
*/
|
|
246
|
-
export async function getDiscoverShelf(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (
|
|
257
|
-
|
|
258
|
-
const [
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
429
|
+
export async function getDiscoverShelf(
|
|
430
|
+
client: MisoClient,
|
|
431
|
+
): Promise<DiscoverItem[]> {
|
|
432
|
+
const configuredIds = [...client.config.discoverReleaseIds];
|
|
433
|
+
const drops = await getCurrentDrops(
|
|
434
|
+
client.protocol,
|
|
435
|
+
configuredIds,
|
|
436
|
+
client.config.protocol.drop,
|
|
437
|
+
);
|
|
438
|
+
const releaseIds = configuredIds.filter((releaseId) => drops[releaseId]);
|
|
439
|
+
if (releaseIds.length === 0) return [];
|
|
440
|
+
|
|
441
|
+
const [releases, coverViews, credits] = await Promise.all([
|
|
442
|
+
getReleasesByIds(client.protocol, releaseIds),
|
|
443
|
+
getReleaseCoversByIds(client.protocol, releaseIds, [
|
|
444
|
+
client.config.protocol.releaseCoverArt,
|
|
445
|
+
...client.config.protocol.legacyReleaseCoverArt,
|
|
446
|
+
]).catch(() => ({}) as Partial<Record<string, ReleaseCoverView>>),
|
|
447
|
+
getReleaseCreditsByIds(
|
|
448
|
+
client.protocol,
|
|
449
|
+
releaseIds,
|
|
450
|
+
client.config.protocol.releaseCredits,
|
|
451
|
+
).catch(() => ({}) as Partial<Record<string, CreditView[]>>),
|
|
262
452
|
]);
|
|
263
453
|
|
|
264
|
-
return {
|
|
265
|
-
|
|
266
|
-
releaseId
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
454
|
+
return releaseIds.flatMap((releaseId) => {
|
|
455
|
+
const drop = drops[releaseId];
|
|
456
|
+
const release = releases[releaseId];
|
|
457
|
+
if (!drop || !release) return [];
|
|
458
|
+
const cover = toCover(
|
|
459
|
+
client.config.walrusAggregatorUrl,
|
|
460
|
+
coverViews[releaseId] ?? null,
|
|
461
|
+
);
|
|
462
|
+
return [
|
|
463
|
+
{
|
|
464
|
+
pressing: toPressingView(drop),
|
|
465
|
+
releaseId,
|
|
466
|
+
title: release.title,
|
|
467
|
+
artist: primaryArtistNames(toCredits(credits[releaseId] ?? null)).join(
|
|
468
|
+
", ",
|
|
469
|
+
),
|
|
470
|
+
coverUrl: cover?.still.url ?? null,
|
|
471
|
+
},
|
|
472
|
+
];
|
|
473
|
+
});
|
|
271
474
|
}
|
|
272
475
|
|
|
273
476
|
// ── Record → release ─────────────────────────────────────────────────────────
|
|
@@ -280,12 +483,44 @@ async function resolveDiscoverItem(client: MisoClient, releaseId: string): Promi
|
|
|
280
483
|
* This answer is IMMUTABLE for the life of the record, which is what lets the
|
|
281
484
|
* endpoint in front of it cache for a year.
|
|
282
485
|
*/
|
|
283
|
-
export
|
|
486
|
+
export type RecordAlbumInclude = "release" | "trackCredits";
|
|
487
|
+
|
|
488
|
+
export interface GetRecordAlbumOptions {
|
|
489
|
+
include?: readonly RecordAlbumInclude[];
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export async function getRecordAlbum(
|
|
493
|
+
client: MisoClient,
|
|
494
|
+
recordId: string,
|
|
495
|
+
options: GetRecordAlbumOptions = {},
|
|
496
|
+
): Promise<RecordAlbum | null> {
|
|
284
497
|
try {
|
|
285
|
-
const { object } = await client.protocol.core.getObject({
|
|
498
|
+
const { object } = await client.protocol.core.getObject({
|
|
499
|
+
objectId: recordId,
|
|
500
|
+
include: { json: true },
|
|
501
|
+
});
|
|
286
502
|
const json = (object?.json ?? {}) as Record<string, unknown>;
|
|
287
503
|
const raw = json.release_id ?? json.releaseId;
|
|
288
|
-
|
|
504
|
+
const releaseId = typeof raw === "string" && raw ? raw : null;
|
|
505
|
+
const includeRelease =
|
|
506
|
+
options.include?.some(
|
|
507
|
+
(part) => part === "release" || part === "trackCredits",
|
|
508
|
+
) ?? false;
|
|
509
|
+
const release =
|
|
510
|
+
includeRelease && releaseId
|
|
511
|
+
? await getReleaseDetail(client, releaseId, {
|
|
512
|
+
include: options.include?.includes("trackCredits")
|
|
513
|
+
? ["trackCredits"]
|
|
514
|
+
: [],
|
|
515
|
+
})
|
|
516
|
+
: includeRelease
|
|
517
|
+
? null
|
|
518
|
+
: undefined;
|
|
519
|
+
return {
|
|
520
|
+
recordId,
|
|
521
|
+
releaseId,
|
|
522
|
+
...(includeRelease ? { release } : {}),
|
|
523
|
+
};
|
|
289
524
|
} catch (e) {
|
|
290
525
|
if (isNotFound(e)) return null;
|
|
291
526
|
throw e;
|
package/src/read/index.ts
CHANGED
|
@@ -18,7 +18,14 @@ export { createMisoClient } from "./client.ts";
|
|
|
18
18
|
export type { MisoClient, CreateMisoClientOptions } from "./client.ts";
|
|
19
19
|
|
|
20
20
|
export { misoConfig, networkFrom } from "./config.ts";
|
|
21
|
-
export type {
|
|
21
|
+
export type {
|
|
22
|
+
MisoConfig,
|
|
23
|
+
MisoConfigOverrides,
|
|
24
|
+
MoneyIds,
|
|
25
|
+
Network,
|
|
26
|
+
PartyIds,
|
|
27
|
+
ProtocolIds,
|
|
28
|
+
} from "./config.ts";
|
|
22
29
|
|
|
23
30
|
export {
|
|
24
31
|
currencyInfo,
|
|
@@ -27,12 +34,26 @@ export {
|
|
|
27
34
|
getPressingDetail,
|
|
28
35
|
getRecordAlbum,
|
|
29
36
|
getReleaseDetail,
|
|
37
|
+
getReleaseResources,
|
|
30
38
|
getTrackCredits,
|
|
31
39
|
primaryArtistNames,
|
|
32
40
|
readReleaseCover,
|
|
33
41
|
} from "./catalog.ts";
|
|
42
|
+
export type {
|
|
43
|
+
GetRecordAlbumOptions,
|
|
44
|
+
GetReleaseOptions,
|
|
45
|
+
RecordAlbumInclude,
|
|
46
|
+
ReleaseInclude,
|
|
47
|
+
ReleaseResourceInclude,
|
|
48
|
+
} from "./catalog.ts";
|
|
49
|
+
export type { ReleaseResources } from "./catalog.ts";
|
|
34
50
|
|
|
35
|
-
export {
|
|
51
|
+
export {
|
|
52
|
+
getArtistProfile,
|
|
53
|
+
getFeaturedRelease,
|
|
54
|
+
getPartySummaries,
|
|
55
|
+
partyAvatarUrl,
|
|
56
|
+
} from "./artist.ts";
|
|
36
57
|
export type { ArtistInclude, GetArtistOptions } from "./artist.ts";
|
|
37
58
|
|
|
38
59
|
export { resolveGenreNames } from "./genres.ts";
|
package/src/read/types.ts
CHANGED
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
// ── Catalog ──────────────────────────────────────────────────────────────────
|
|
22
22
|
|
|
23
23
|
/** Lifecycle state shared by compositions, recordings, and releases. */
|
|
24
|
-
export type WorkState =
|
|
24
|
+
export type WorkState =
|
|
25
|
+
| { type: "Initialized" }
|
|
26
|
+
| { type: "Published"; timestampMs: number };
|
|
25
27
|
|
|
26
28
|
/** A cover image's location on Walrus, plus the URL to fetch it from. */
|
|
27
29
|
export interface CoverImage {
|
|
@@ -49,12 +51,18 @@ export interface Credit {
|
|
|
49
51
|
* party, not roles the party performed — a producer can be a featured artist. UI
|
|
50
52
|
* badges the names by intersecting the two.
|
|
51
53
|
*/
|
|
52
|
-
export interface
|
|
54
|
+
export interface RecordingCredits {
|
|
53
55
|
credits: Credit[];
|
|
54
56
|
primaryArtistIds: string[];
|
|
55
57
|
featuredArtistIds: string[];
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
/** Composition writing credits plus recording performance/production credits. */
|
|
61
|
+
export interface TrackCredits {
|
|
62
|
+
compositionCredits: Credit[];
|
|
63
|
+
recordingCredits: RecordingCredits;
|
|
64
|
+
}
|
|
65
|
+
|
|
58
66
|
/** One track as a tracklist renders it. */
|
|
59
67
|
export interface TrackView {
|
|
60
68
|
/** Display number — `"1"`, or `"1.2"` (disc.track) on a multi-disc set. */
|
|
@@ -81,6 +89,8 @@ export interface ReleaseDetail {
|
|
|
81
89
|
primaryArtists: string[];
|
|
82
90
|
discCount: number;
|
|
83
91
|
tracks: TrackView[];
|
|
92
|
+
/** Present only when requested via `include`, keyed by recording id. */
|
|
93
|
+
trackCredits?: Record<string, TrackCredits>;
|
|
84
94
|
}
|
|
85
95
|
|
|
86
96
|
/** Pricing policy: pay exactly `amount` (fixed), or at least `amount` (floor). */
|
|
@@ -137,6 +147,8 @@ export interface RecordAlbum {
|
|
|
137
147
|
recordId: string;
|
|
138
148
|
/** Null when the record object carries no release id. */
|
|
139
149
|
releaseId: string | null;
|
|
150
|
+
/** Present only when requested via `include`; null when no release is linked. */
|
|
151
|
+
release?: ReleaseDetail | null;
|
|
140
152
|
}
|
|
141
153
|
|
|
142
154
|
/** The confirmation preview shown when an owner pastes a pressing id to pin. */
|
|
@@ -229,6 +241,8 @@ export interface ArtistProfile {
|
|
|
229
241
|
/** Present only when requested via `include` — the owner-editor fields. */
|
|
230
242
|
roles?: string[];
|
|
231
243
|
tags?: string[];
|
|
244
|
+
/** Present only when requested via `include`; null means nothing is pinned. */
|
|
245
|
+
featured?: FeaturedRelease | null;
|
|
232
246
|
/** Public avatar URL (miso-api R2 lane; may 404 when unset). */
|
|
233
247
|
avatarUrl: string;
|
|
234
248
|
}
|