@misofm/api-client 0.3.0 → 0.4.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/package.json +1 -1
- package/src/cache.ts +3 -6
- package/src/client.test.ts +9 -3
- package/src/client.ts +6 -6
- package/src/schemas.ts +36 -27
- package/src/types.ts +4 -2
package/package.json
CHANGED
package/src/cache.ts
CHANGED
|
@@ -20,12 +20,9 @@
|
|
|
20
20
|
// draft the same work before publication: actively being edited in
|
|
21
21
|
// studio, so the editor must see their own change land.
|
|
22
22
|
// artist a profile page. Owner edits should surface within a minute.
|
|
23
|
-
// sale a
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
// gates nothing. It is a display number, not a stock level, which
|
|
27
|
-
// is why a minute of staleness is cosmetic rather than a
|
|
28
|
-
// correctness problem.
|
|
23
|
+
// sale a Pressing or Listing. Their state and the uncapped run's supply
|
|
24
|
+
// can move, so clients refresh them quickly. The chain remains the
|
|
25
|
+
// purchase gate when an edge response is briefly stale.
|
|
29
26
|
// private address-scoped. Never enters a shared cache — the failure mode is
|
|
30
27
|
// serving one user's library to another.
|
|
31
28
|
//
|
package/src/client.test.ts
CHANGED
|
@@ -71,9 +71,9 @@ describe("URL construction", () => {
|
|
|
71
71
|
test("joins the artist include list into one param", async () => {
|
|
72
72
|
const { fetch, calls } = stubFetch({ status: 404 });
|
|
73
73
|
await createMisoApiClient({ baseUrl: BASE, fetch }).getArtist("0xp", {
|
|
74
|
-
include: ["roles", "tags"
|
|
74
|
+
include: ["roles", "tags"],
|
|
75
75
|
});
|
|
76
|
-
expect(calls[0]).toContain("include=roles%2Ctags
|
|
76
|
+
expect(calls[0]).toContain("include=roles%2Ctags");
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
test("joins catalog relationship expansions into one param", async () => {
|
|
@@ -89,6 +89,12 @@ describe("URL construction", () => {
|
|
|
89
89
|
expect(calls[2]).toContain("include=release%2CtrackCredits");
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
+
test("uses the permanent pressing preview route", async () => {
|
|
93
|
+
const { fetch, calls } = stubFetch({ status: 404 });
|
|
94
|
+
await createMisoApiClient({ baseUrl: BASE, fetch }).getPressingPreview("0xp");
|
|
95
|
+
expect(calls[0]).toBe(`${BASE}/read/v1/pressings/0xp/preview`);
|
|
96
|
+
});
|
|
97
|
+
|
|
92
98
|
test("a custom prefix is honored (self-hosted / direct-to-service)", async () => {
|
|
93
99
|
const { fetch, calls } = stubFetch({ body: balance });
|
|
94
100
|
await createMisoApiClient({
|
|
@@ -101,7 +107,7 @@ describe("URL construction", () => {
|
|
|
101
107
|
});
|
|
102
108
|
|
|
103
109
|
describe("not-found handling", () => {
|
|
104
|
-
test("
|
|
110
|
+
test("an unknown pressing is null, not an error", async () => {
|
|
105
111
|
const { fetch } = stubFetch({
|
|
106
112
|
status: 404,
|
|
107
113
|
body: { error: { code: "not_found", message: "gone" } },
|
package/src/client.ts
CHANGED
|
@@ -22,7 +22,7 @@ import type {
|
|
|
22
22
|
ArtistProfile,
|
|
23
23
|
Balance,
|
|
24
24
|
DiscoverShelf,
|
|
25
|
-
|
|
25
|
+
PressingPreview,
|
|
26
26
|
OwnedParty,
|
|
27
27
|
PendingMembership,
|
|
28
28
|
OwnedRecord,
|
|
@@ -204,10 +204,10 @@ export function createMisoApiClient(options: MisoApiClientOptions) {
|
|
|
204
204
|
),
|
|
205
205
|
|
|
206
206
|
/** Confirmation preview for a pasted pressing id. `null` when it isn't a pressing. */
|
|
207
|
-
|
|
207
|
+
getPressingPreview: (pressingId: string): Promise<PressingPreview | null> =>
|
|
208
208
|
request(
|
|
209
|
-
s.
|
|
210
|
-
`/
|
|
209
|
+
s.pressingPreviewSchema,
|
|
210
|
+
`/pressings/${pressingId}/preview`,
|
|
211
211
|
{},
|
|
212
212
|
{ nullOn404: true },
|
|
213
213
|
),
|
|
@@ -216,7 +216,7 @@ export function createMisoApiClient(options: MisoApiClientOptions) {
|
|
|
216
216
|
/** An artist plus optional relationship expansions. */
|
|
217
217
|
getArtist: (
|
|
218
218
|
partyId: string,
|
|
219
|
-
opts: { include?: readonly ("roles" | "tags"
|
|
219
|
+
opts: { include?: readonly ("roles" | "tags")[] } = {},
|
|
220
220
|
): Promise<ArtistProfile | null> =>
|
|
221
221
|
request(
|
|
222
222
|
s.artistProfileSchema,
|
|
@@ -313,7 +313,7 @@ export function cacheBuster(nowMs: number = Date.now()): string {
|
|
|
313
313
|
export const READ_CACHE_CLASS = {
|
|
314
314
|
getDiscover: "sale",
|
|
315
315
|
getPressing: "sale",
|
|
316
|
-
|
|
316
|
+
getPressingPreview: "sale",
|
|
317
317
|
getRecordAlbum: "immutable",
|
|
318
318
|
getReceipt: "immutable",
|
|
319
319
|
getArtist: "artist",
|
package/src/schemas.ts
CHANGED
|
@@ -58,6 +58,8 @@ export const trackViewSchema = z.object({
|
|
|
58
58
|
no: z.string(),
|
|
59
59
|
title: z.string(),
|
|
60
60
|
recordingId: suiIdSchema,
|
|
61
|
+
/** The composition underlying this track's recording. */
|
|
62
|
+
compositionId: suiIdSchema,
|
|
61
63
|
/** This track's share of the release's revenue, in basis points. */
|
|
62
64
|
splitBps: z.number().int().min(0).max(10_000),
|
|
63
65
|
disc: z.number().int().min(1),
|
|
@@ -128,19 +130,35 @@ export const currencySchema = z.object({
|
|
|
128
130
|
decimals: z.number().int().min(0).max(18),
|
|
129
131
|
});
|
|
130
132
|
|
|
133
|
+
export const pressingStateSchema = z.union([
|
|
134
|
+
z.object({
|
|
135
|
+
kind: z.literal("scheduled"),
|
|
136
|
+
startTimestampMs: z.number().int(),
|
|
137
|
+
}),
|
|
138
|
+
z.object({ kind: z.literal("active") }),
|
|
139
|
+
z.object({ kind: z.literal("paused") }),
|
|
140
|
+
]);
|
|
141
|
+
|
|
131
142
|
export const pressingViewSchema = z.object({
|
|
132
143
|
id: suiIdSchema,
|
|
133
144
|
releaseId: suiIdSchema,
|
|
134
|
-
|
|
145
|
+
state: pressingStateSchema,
|
|
146
|
+
/** Records pressed so far. The permanent run is intentionally uncapped. */
|
|
147
|
+
supply: u64Schema,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
export const listingViewSchema = z.object({
|
|
151
|
+
id: suiIdSchema,
|
|
152
|
+
pressingId: suiIdSchema,
|
|
153
|
+
releaseId: suiIdSchema,
|
|
135
154
|
price: priceSchema,
|
|
136
155
|
currency: currencySchema,
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
soldOut: z.boolean(),
|
|
156
|
+
state: z.enum(["enabled", "disabled"]),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
export const saleViewSchema = z.object({
|
|
160
|
+
pressing: pressingViewSchema,
|
|
161
|
+
listing: listingViewSchema,
|
|
144
162
|
});
|
|
145
163
|
|
|
146
164
|
export const pressingDetailSchema = z.object({
|
|
@@ -154,7 +172,7 @@ export const pressingDetailWireSchema = z.object({
|
|
|
154
172
|
});
|
|
155
173
|
|
|
156
174
|
export const discoverItemSchema = z.object({
|
|
157
|
-
|
|
175
|
+
sale: saleViewSchema,
|
|
158
176
|
releaseId: suiIdSchema,
|
|
159
177
|
title: z.string(),
|
|
160
178
|
/** Primary artists joined for display. Empty when no credits are set. */
|
|
@@ -177,13 +195,13 @@ export const recordAlbumWireSchema = z.object({
|
|
|
177
195
|
release: releaseDetailWireSchema.nullable().optional(),
|
|
178
196
|
});
|
|
179
197
|
|
|
180
|
-
export const
|
|
198
|
+
export const pressingPreviewSchema = z.object({
|
|
181
199
|
pressingId: suiIdSchema,
|
|
182
|
-
currency: currencySchema,
|
|
183
200
|
title: z.string(),
|
|
184
201
|
subtitle: z.string().nullable(),
|
|
185
202
|
coverUrl: z.string().url().nullable(),
|
|
186
|
-
|
|
203
|
+
state: pressingStateSchema,
|
|
204
|
+
supply: u64Schema,
|
|
187
205
|
trackCount: z.number().int().min(0),
|
|
188
206
|
});
|
|
189
207
|
|
|
@@ -243,14 +261,6 @@ export const partyLinkSchema = z.object({
|
|
|
243
261
|
|
|
244
262
|
export const partyCtaSchema = z.object({ label: z.string(), url: z.string() });
|
|
245
263
|
|
|
246
|
-
export const featuredReleaseSchema = z.object({
|
|
247
|
-
pressingId: suiIdSchema,
|
|
248
|
-
releaseId: suiIdSchema,
|
|
249
|
-
title: z.string(),
|
|
250
|
-
artist: z.string(),
|
|
251
|
-
coverUrl: z.string().url().nullable(),
|
|
252
|
-
});
|
|
253
|
-
|
|
254
264
|
export const artistProfileSchema = z.object({
|
|
255
265
|
id: suiIdSchema,
|
|
256
266
|
kind: z.enum(["individual", "group"]),
|
|
@@ -268,8 +278,6 @@ export const artistProfileSchema = z.object({
|
|
|
268
278
|
/** Present only when requested via `include` — the owner-editor fields. */
|
|
269
279
|
roles: z.array(z.string()).optional(),
|
|
270
280
|
tags: z.array(z.string()).optional(),
|
|
271
|
-
/** Present only when requested; null means the artist has no pinned release. */
|
|
272
|
-
featured: featuredReleaseSchema.nullable().optional(),
|
|
273
281
|
avatarUrl: z.string().url(),
|
|
274
282
|
});
|
|
275
283
|
|
|
@@ -352,13 +360,14 @@ export const ownershipSchema = z.object({
|
|
|
352
360
|
// ── Receipts ─────────────────────────────────────────────────────────────────
|
|
353
361
|
|
|
354
362
|
export const recordSaleSchema = z.object({
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
363
|
+
listingId: suiIdSchema,
|
|
364
|
+
pressingId: suiIdSchema,
|
|
365
|
+
releaseId: suiIdSchema,
|
|
358
366
|
recordId: suiIdSchema,
|
|
359
367
|
number: u64Schema,
|
|
360
368
|
paid: u64Schema,
|
|
361
|
-
|
|
369
|
+
price: priceSchema,
|
|
370
|
+
buyer: suiIdSchema,
|
|
362
371
|
});
|
|
363
372
|
|
|
364
373
|
export const trackRoyaltySchema = z.object({
|
|
@@ -375,7 +384,7 @@ export const trackRoyaltySchema = z.object({
|
|
|
375
384
|
export const purchaseReceiptSchema = z.object({
|
|
376
385
|
sale: recordSaleSchema,
|
|
377
386
|
detail: pressingDetailSchema,
|
|
378
|
-
/** The
|
|
387
|
+
/** The Listing price — differs from `sale.paid` on a floor-price overpay. */
|
|
379
388
|
price: u64Schema,
|
|
380
389
|
tracks: z.array(trackRoyaltySchema),
|
|
381
390
|
});
|
package/src/types.ts
CHANGED
|
@@ -17,19 +17,21 @@ export type ReleaseDetail = z.infer<typeof s.releaseDetailSchema>;
|
|
|
17
17
|
export type TrackCredits = z.infer<typeof s.trackCreditsSchema>;
|
|
18
18
|
export type Price = z.infer<typeof s.priceSchema>;
|
|
19
19
|
export type Currency = z.infer<typeof s.currencySchema>;
|
|
20
|
+
export type PressingState = z.infer<typeof s.pressingStateSchema>;
|
|
20
21
|
export type PressingView = z.infer<typeof s.pressingViewSchema>;
|
|
22
|
+
export type ListingView = z.infer<typeof s.listingViewSchema>;
|
|
23
|
+
export type SaleView = z.infer<typeof s.saleViewSchema>;
|
|
21
24
|
export type PressingDetail = z.infer<typeof s.pressingDetailSchema>;
|
|
22
25
|
export type DiscoverItem = z.infer<typeof s.discoverItemSchema>;
|
|
23
26
|
export type DiscoverShelf = z.infer<typeof s.discoverShelfSchema>;
|
|
24
27
|
export type RecordAlbum = z.infer<typeof s.recordAlbumSchema>;
|
|
25
|
-
export type
|
|
28
|
+
export type PressingPreview = z.infer<typeof s.pressingPreviewSchema>;
|
|
26
29
|
|
|
27
30
|
export type PartyMember = z.infer<typeof s.partyMemberSchema>;
|
|
28
31
|
export type PlatformKey = z.infer<typeof s.platformKeySchema>;
|
|
29
32
|
export type PartyLink = z.infer<typeof s.partyLinkSchema>;
|
|
30
33
|
export type PartyCta = z.infer<typeof s.partyCtaSchema>;
|
|
31
34
|
export type ArtistProfile = z.infer<typeof s.artistProfileSchema>;
|
|
32
|
-
export type FeaturedRelease = z.infer<typeof s.featuredReleaseSchema>;
|
|
33
35
|
export type PartySummary = z.infer<typeof s.partySummarySchema>;
|
|
34
36
|
|
|
35
37
|
export type OwnedRecord = z.infer<typeof s.ownedRecordSchema>;
|