@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misofm/api-client",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Typed client and response contract for the Miso API read layer. The one definition of what a Miso read returns.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
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 live pressing. The only field that moves between reads is the
24
- // copies-sold counter and a pressing is UNCAPPED (one open run,
25
- // no editions, no supply cap, no sold-out state), so that counter
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
  //
@@ -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", "featured"],
74
+ include: ["roles", "tags"],
75
75
  });
76
- expect(calls[0]).toContain("include=roles%2Ctags%2Cfeatured");
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("a superseded pressing is null, not an error", async () => {
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
- DropPreview,
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
- getDropPreview: (dropId: string): Promise<DropPreview | null> =>
207
+ getPressingPreview: (pressingId: string): Promise<PressingPreview | null> =>
208
208
  request(
209
- s.dropPreviewSchema,
210
- `/drops/${dropId}/preview`,
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" | "featured")[] } = {},
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
- getDropPreview: "sale",
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
- edition: z.number().int().min(0),
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
- quantitySold: u64Schema,
138
- /** Null = uncapped (an open edition). */
139
- maxSupply: u64Schema.nullable(),
140
- startTimestampMs: z.number().int(),
141
- /** Null = evergreen. */
142
- endTimestampMs: z.number().int().nullable(),
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
- pressing: pressingViewSchema,
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 dropPreviewSchema = z.object({
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
- price: priceSchema,
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
- dropId: z.string(),
356
- releaseId: z.string(),
357
- edition: z.number().int().min(0),
363
+ listingId: suiIdSchema,
364
+ pressingId: suiIdSchema,
365
+ releaseId: suiIdSchema,
358
366
  recordId: suiIdSchema,
359
367
  number: u64Schema,
360
368
  paid: u64Schema,
361
- buyer: z.string(),
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 drop's list price — differs from `sale.paid` on a floor-price overpay. */
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 DropPreview = z.infer<typeof s.dropPreviewSchema>;
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>;