@misofm/api-client 0.3.0 → 0.4.1
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 +20 -3
- package/src/client.ts +21 -6
- package/src/schemas.ts +47 -27
- package/src/types.ts +5 -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,23 @@ 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
|
+
|
|
98
|
+
test("passes the Listing currency to the pressing sale route", async () => {
|
|
99
|
+
const { fetch, calls } = stubFetch({ status: 404 });
|
|
100
|
+
await createMisoApiClient({ baseUrl: BASE, fetch }).getPressingSale(
|
|
101
|
+
"0xp",
|
|
102
|
+
"0x2::sui::SUI",
|
|
103
|
+
);
|
|
104
|
+
expect(calls[0]).toBe(
|
|
105
|
+
`${BASE}/read/v1/pressings/0xp/sale?currencyType=0x2%3A%3Asui%3A%3ASUI`,
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
92
109
|
test("a custom prefix is honored (self-hosted / direct-to-service)", async () => {
|
|
93
110
|
const { fetch, calls } = stubFetch({ body: balance });
|
|
94
111
|
await createMisoApiClient({
|
|
@@ -101,7 +118,7 @@ describe("URL construction", () => {
|
|
|
101
118
|
});
|
|
102
119
|
|
|
103
120
|
describe("not-found handling", () => {
|
|
104
|
-
test("
|
|
121
|
+
test("an unknown pressing is null, not an error", async () => {
|
|
105
122
|
const { fetch } = stubFetch({
|
|
106
123
|
status: 404,
|
|
107
124
|
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,
|
|
@@ -30,6 +30,7 @@ import type {
|
|
|
30
30
|
Ownership,
|
|
31
31
|
PartySummary,
|
|
32
32
|
PressingDetail,
|
|
33
|
+
SaleDetail,
|
|
33
34
|
PurchaseReceipt,
|
|
34
35
|
RecordAlbum,
|
|
35
36
|
ReleaseDetail,
|
|
@@ -179,6 +180,19 @@ export function createMisoApiClient(options: MisoApiClientOptions) {
|
|
|
179
180
|
{ nullOn404: true },
|
|
180
181
|
),
|
|
181
182
|
|
|
183
|
+
/** One Pressing and its currency-specific Listing. */
|
|
184
|
+
getPressingSale: (
|
|
185
|
+
pressingId: string,
|
|
186
|
+
currencyType: string,
|
|
187
|
+
opts: { include?: readonly "trackCredits"[] } = {},
|
|
188
|
+
): Promise<SaleDetail | null> =>
|
|
189
|
+
request(
|
|
190
|
+
s.saleDetailSchema,
|
|
191
|
+
`/pressings/${pressingId}/sale`,
|
|
192
|
+
{ currencyType, include: opts.include?.join(",") },
|
|
193
|
+
{ nullOn404: true },
|
|
194
|
+
),
|
|
195
|
+
|
|
182
196
|
/** A release with its cover, credits, and resolved tracklist. */
|
|
183
197
|
getRelease: (
|
|
184
198
|
releaseId: string,
|
|
@@ -204,10 +218,10 @@ export function createMisoApiClient(options: MisoApiClientOptions) {
|
|
|
204
218
|
),
|
|
205
219
|
|
|
206
220
|
/** Confirmation preview for a pasted pressing id. `null` when it isn't a pressing. */
|
|
207
|
-
|
|
221
|
+
getPressingPreview: (pressingId: string): Promise<PressingPreview | null> =>
|
|
208
222
|
request(
|
|
209
|
-
s.
|
|
210
|
-
`/
|
|
223
|
+
s.pressingPreviewSchema,
|
|
224
|
+
`/pressings/${pressingId}/preview`,
|
|
211
225
|
{},
|
|
212
226
|
{ nullOn404: true },
|
|
213
227
|
),
|
|
@@ -216,7 +230,7 @@ export function createMisoApiClient(options: MisoApiClientOptions) {
|
|
|
216
230
|
/** An artist plus optional relationship expansions. */
|
|
217
231
|
getArtist: (
|
|
218
232
|
partyId: string,
|
|
219
|
-
opts: { include?: readonly ("roles" | "tags"
|
|
233
|
+
opts: { include?: readonly ("roles" | "tags")[] } = {},
|
|
220
234
|
): Promise<ArtistProfile | null> =>
|
|
221
235
|
request(
|
|
222
236
|
s.artistProfileSchema,
|
|
@@ -313,7 +327,8 @@ export function cacheBuster(nowMs: number = Date.now()): string {
|
|
|
313
327
|
export const READ_CACHE_CLASS = {
|
|
314
328
|
getDiscover: "sale",
|
|
315
329
|
getPressing: "sale",
|
|
316
|
-
|
|
330
|
+
getPressingSale: "sale",
|
|
331
|
+
getPressingPreview: "sale",
|
|
317
332
|
getRecordAlbum: "immutable",
|
|
318
333
|
getReceipt: "immutable",
|
|
319
334
|
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({
|
|
@@ -153,8 +171,18 @@ export const pressingDetailWireSchema = z.object({
|
|
|
153
171
|
release: releaseDetailWireSchema,
|
|
154
172
|
});
|
|
155
173
|
|
|
174
|
+
export const saleDetailSchema = z.object({
|
|
175
|
+
sale: saleViewSchema,
|
|
176
|
+
release: releaseDetailSchema,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
export const saleDetailWireSchema = z.object({
|
|
180
|
+
sale: saleViewSchema,
|
|
181
|
+
release: releaseDetailWireSchema,
|
|
182
|
+
});
|
|
183
|
+
|
|
156
184
|
export const discoverItemSchema = z.object({
|
|
157
|
-
|
|
185
|
+
sale: saleViewSchema,
|
|
158
186
|
releaseId: suiIdSchema,
|
|
159
187
|
title: z.string(),
|
|
160
188
|
/** Primary artists joined for display. Empty when no credits are set. */
|
|
@@ -177,13 +205,13 @@ export const recordAlbumWireSchema = z.object({
|
|
|
177
205
|
release: releaseDetailWireSchema.nullable().optional(),
|
|
178
206
|
});
|
|
179
207
|
|
|
180
|
-
export const
|
|
208
|
+
export const pressingPreviewSchema = z.object({
|
|
181
209
|
pressingId: suiIdSchema,
|
|
182
|
-
currency: currencySchema,
|
|
183
210
|
title: z.string(),
|
|
184
211
|
subtitle: z.string().nullable(),
|
|
185
212
|
coverUrl: z.string().url().nullable(),
|
|
186
|
-
|
|
213
|
+
state: pressingStateSchema,
|
|
214
|
+
supply: u64Schema,
|
|
187
215
|
trackCount: z.number().int().min(0),
|
|
188
216
|
});
|
|
189
217
|
|
|
@@ -243,14 +271,6 @@ export const partyLinkSchema = z.object({
|
|
|
243
271
|
|
|
244
272
|
export const partyCtaSchema = z.object({ label: z.string(), url: z.string() });
|
|
245
273
|
|
|
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
274
|
export const artistProfileSchema = z.object({
|
|
255
275
|
id: suiIdSchema,
|
|
256
276
|
kind: z.enum(["individual", "group"]),
|
|
@@ -268,8 +288,6 @@ export const artistProfileSchema = z.object({
|
|
|
268
288
|
/** Present only when requested via `include` — the owner-editor fields. */
|
|
269
289
|
roles: z.array(z.string()).optional(),
|
|
270
290
|
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
291
|
avatarUrl: z.string().url(),
|
|
274
292
|
});
|
|
275
293
|
|
|
@@ -352,13 +370,15 @@ export const ownershipSchema = z.object({
|
|
|
352
370
|
// ── Receipts ─────────────────────────────────────────────────────────────────
|
|
353
371
|
|
|
354
372
|
export const recordSaleSchema = z.object({
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
373
|
+
listingId: suiIdSchema,
|
|
374
|
+
pressingId: suiIdSchema,
|
|
375
|
+
releaseId: suiIdSchema,
|
|
358
376
|
recordId: suiIdSchema,
|
|
359
377
|
number: u64Schema,
|
|
360
378
|
paid: u64Schema,
|
|
361
|
-
|
|
379
|
+
price: priceSchema,
|
|
380
|
+
currencyType: z.string(),
|
|
381
|
+
buyer: suiIdSchema,
|
|
362
382
|
});
|
|
363
383
|
|
|
364
384
|
export const trackRoyaltySchema = z.object({
|
|
@@ -375,7 +395,7 @@ export const trackRoyaltySchema = z.object({
|
|
|
375
395
|
export const purchaseReceiptSchema = z.object({
|
|
376
396
|
sale: recordSaleSchema,
|
|
377
397
|
detail: pressingDetailSchema,
|
|
378
|
-
/** The
|
|
398
|
+
/** The Listing price — differs from `sale.paid` on a floor-price overpay. */
|
|
379
399
|
price: u64Schema,
|
|
380
400
|
tracks: z.array(trackRoyaltySchema),
|
|
381
401
|
});
|
package/src/types.ts
CHANGED
|
@@ -17,19 +17,22 @@ 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>;
|
|
25
|
+
export type SaleDetail = z.infer<typeof s.saleDetailSchema>;
|
|
22
26
|
export type DiscoverItem = z.infer<typeof s.discoverItemSchema>;
|
|
23
27
|
export type DiscoverShelf = z.infer<typeof s.discoverShelfSchema>;
|
|
24
28
|
export type RecordAlbum = z.infer<typeof s.recordAlbumSchema>;
|
|
25
|
-
export type
|
|
29
|
+
export type PressingPreview = z.infer<typeof s.pressingPreviewSchema>;
|
|
26
30
|
|
|
27
31
|
export type PartyMember = z.infer<typeof s.partyMemberSchema>;
|
|
28
32
|
export type PlatformKey = z.infer<typeof s.platformKeySchema>;
|
|
29
33
|
export type PartyLink = z.infer<typeof s.partyLinkSchema>;
|
|
30
34
|
export type PartyCta = z.infer<typeof s.partyCtaSchema>;
|
|
31
35
|
export type ArtistProfile = z.infer<typeof s.artistProfileSchema>;
|
|
32
|
-
export type FeaturedRelease = z.infer<typeof s.featuredReleaseSchema>;
|
|
33
36
|
export type PartySummary = z.infer<typeof s.partySummarySchema>;
|
|
34
37
|
|
|
35
38
|
export type OwnedRecord = z.infer<typeof s.ownedRecordSchema>;
|