@misofm/api-client 0.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misofm/api-client",
3
- "version": "0.4.0",
3
+ "version": "0.5.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": {
@@ -79,20 +79,23 @@ describe("URL construction", () => {
79
79
  test("joins catalog relationship expansions into one param", async () => {
80
80
  const { fetch, calls } = stubFetch({ status: 404 });
81
81
  const client = createMisoApiClient({ baseUrl: BASE, fetch });
82
- await client.getPressing("0xp", { include: ["trackCredits"] });
83
82
  await client.getRelease("0xl", { include: ["trackCredits"] });
84
83
  await client.getRecordAlbum("0xr", {
85
84
  include: ["release", "trackCredits"],
86
85
  });
87
86
  expect(calls[0]).toContain("include=trackCredits");
88
- expect(calls[1]).toContain("include=trackCredits");
89
- expect(calls[2]).toContain("include=release%2CtrackCredits");
87
+ expect(calls[1]).toContain("include=release%2CtrackCredits");
90
88
  });
91
89
 
92
- test("uses the permanent pressing preview route", async () => {
90
+ test("passes the currency to the modular Listing route", async () => {
93
91
  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`);
92
+ await createMisoApiClient({ baseUrl: BASE, fetch }).getListing(
93
+ "0xp",
94
+ "0x2::sui::SUI",
95
+ );
96
+ expect(calls[0]).toBe(
97
+ `${BASE}/read/v1/pressings/0xp/listing?currencyType=0x2%3A%3Asui%3A%3ASUI`,
98
+ );
96
99
  });
97
100
 
98
101
  test("a custom prefix is honored (self-hosted / direct-to-service)", async () => {
@@ -143,9 +146,9 @@ describe("error handling", () => {
143
146
  body: { error: { code: "rate-limited", message: "Too many requests." } },
144
147
  });
145
148
  const api = createMisoApiClient({ baseUrl: BASE, fetch });
146
- await expect(api.getDiscover()).rejects.toThrow(MisoApiError);
149
+ await expect(api.getPressing("0xp")).rejects.toThrow(MisoApiError);
147
150
  try {
148
- await api.getDiscover();
151
+ await api.getPressing("0xp");
149
152
  } catch (e) {
150
153
  expect(e).toBeInstanceOf(MisoApiError);
151
154
  expect((e as MisoApiError).status).toBe(429);
@@ -157,7 +160,7 @@ describe("error handling", () => {
157
160
  test("an error body in an unexpected shape still throws a usable error", async () => {
158
161
  const { fetch } = stubFetch({ status: 500, body: { oops: true } });
159
162
  try {
160
- await createMisoApiClient({ baseUrl: BASE, fetch }).getDiscover();
163
+ await createMisoApiClient({ baseUrl: BASE, fetch }).getPressing("0xp");
161
164
  throw new Error("should have thrown");
162
165
  } catch (e) {
163
166
  expect(e).toBeInstanceOf(MisoApiError);
@@ -237,8 +240,8 @@ describe("READ_CACHE_CLASS", () => {
237
240
  });
238
241
 
239
242
  test("live sale reads are the sale class", () => {
240
- expect(READ_CACHE_CLASS.getDiscover).toBe("sale");
241
243
  expect(READ_CACHE_CLASS.getPressing).toBe("sale");
244
+ expect(READ_CACHE_CLASS.getListing).toBe("sale");
242
245
  });
243
246
  });
244
247
 
package/src/client.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  // The typed client for the Miso read API. One of these per app.
5
5
  //
6
6
  // const api = createMisoApiClient({ baseUrl: "https://api.testnet.miso.fm" })
7
- // const shelf = await api.getDiscover()
7
+ // const pressing = await api.getPressing("0x…")
8
8
  //
9
9
  // Every method returns a PARSED, validated body. Validation on the client is not
10
10
  // paranoia about our own server — it is what turns a silent contract break into a
@@ -21,15 +21,14 @@ import { queryPolicy, type CacheClass } from "./cache.ts";
21
21
  import type {
22
22
  ArtistProfile,
23
23
  Balance,
24
- DiscoverShelf,
25
- PressingPreview,
24
+ ListingView,
26
25
  OwnedParty,
27
26
  PendingMembership,
28
27
  OwnedRecord,
29
28
  OwnedWork,
30
29
  Ownership,
31
30
  PartySummary,
32
- PressingDetail,
31
+ PressingView,
33
32
  PurchaseReceipt,
34
33
  RecordAlbum,
35
34
  ReleaseDetail,
@@ -163,19 +162,24 @@ export function createMisoApiClient(options: MisoApiClientOptions) {
163
162
 
164
163
  return {
165
164
  // ── Catalog ─────────────────────────────────────────────────────────────
166
- /** The records currently on sale. */
167
- getDiscover: (): Promise<DiscoverShelf> =>
168
- required(s.discoverShelfSchema, "/discover"),
165
+ /** One permanent pressing resource. */
166
+ getPressing: (pressingId: string): Promise<PressingView | null> =>
167
+ request(
168
+ s.pressingViewSchema,
169
+ `/pressings/${pressingId}`,
170
+ {},
171
+ { nullOn404: true },
172
+ ),
169
173
 
170
- /** A pressing and everything its buy page renders. `null` when there is no such pressing. */
171
- getPressing: (
174
+ /** One currency-specific Listing derived from a permanent Pressing. */
175
+ getListing: (
172
176
  pressingId: string,
173
- opts: { include?: readonly "trackCredits"[] } = {},
174
- ): Promise<PressingDetail | null> =>
177
+ currencyType: string,
178
+ ): Promise<ListingView | null> =>
175
179
  request(
176
- s.pressingDetailSchema,
177
- `/pressings/${pressingId}`,
178
- { include: opts.include?.join(",") },
180
+ s.listingViewSchema,
181
+ `/pressings/${pressingId}/listing`,
182
+ { currencyType },
179
183
  { nullOn404: true },
180
184
  ),
181
185
 
@@ -203,15 +207,6 @@ export function createMisoApiClient(options: MisoApiClientOptions) {
203
207
  { nullOn404: true },
204
208
  ),
205
209
 
206
- /** Confirmation preview for a pasted pressing id. `null` when it isn't a pressing. */
207
- getPressingPreview: (pressingId: string): Promise<PressingPreview | null> =>
208
- request(
209
- s.pressingPreviewSchema,
210
- `/pressings/${pressingId}/preview`,
211
- {},
212
- { nullOn404: true },
213
- ),
214
-
215
210
  // ── Artist ──────────────────────────────────────────────────────────────
216
211
  /** An artist plus optional relationship expansions. */
217
212
  getArtist: (
@@ -311,9 +306,8 @@ export function cacheBuster(nowMs: number = Date.now()): string {
311
306
  * it with `workCacheClass(release.state)`.
312
307
  */
313
308
  export const READ_CACHE_CLASS = {
314
- getDiscover: "sale",
315
309
  getPressing: "sale",
316
- getPressingPreview: "sale",
310
+ getListing: "sale",
317
311
  getRecordAlbum: "immutable",
318
312
  getReceipt: "immutable",
319
313
  getArtist: "artist",
package/src/schemas.ts CHANGED
@@ -156,11 +156,6 @@ export const listingViewSchema = z.object({
156
156
  state: z.enum(["enabled", "disabled"]),
157
157
  });
158
158
 
159
- export const saleViewSchema = z.object({
160
- pressing: pressingViewSchema,
161
- listing: listingViewSchema,
162
- });
163
-
164
159
  export const pressingDetailSchema = z.object({
165
160
  pressing: pressingViewSchema,
166
161
  release: releaseDetailSchema,
@@ -171,17 +166,6 @@ export const pressingDetailWireSchema = z.object({
171
166
  release: releaseDetailWireSchema,
172
167
  });
173
168
 
174
- export const discoverItemSchema = z.object({
175
- sale: saleViewSchema,
176
- releaseId: suiIdSchema,
177
- title: z.string(),
178
- /** Primary artists joined for display. Empty when no credits are set. */
179
- artist: z.string(),
180
- coverUrl: z.string().url().nullable(),
181
- });
182
-
183
- export const discoverShelfSchema = z.array(discoverItemSchema);
184
-
185
169
  export const recordAlbumSchema = z.object({
186
170
  recordId: suiIdSchema,
187
171
  releaseId: suiIdSchema.nullable(),
@@ -195,16 +179,6 @@ export const recordAlbumWireSchema = z.object({
195
179
  release: releaseDetailWireSchema.nullable().optional(),
196
180
  });
197
181
 
198
- export const pressingPreviewSchema = z.object({
199
- pressingId: suiIdSchema,
200
- title: z.string(),
201
- subtitle: z.string().nullable(),
202
- coverUrl: z.string().url().nullable(),
203
- state: pressingStateSchema,
204
- supply: u64Schema,
205
- trackCount: z.number().int().min(0),
206
- });
207
-
208
182
  // ── Artist ───────────────────────────────────────────────────────────────────
209
183
 
210
184
  export const partyMemberSchema = z.object({
@@ -367,6 +341,7 @@ export const recordSaleSchema = z.object({
367
341
  number: u64Schema,
368
342
  paid: u64Schema,
369
343
  price: priceSchema,
344
+ currencyType: z.string(),
370
345
  buyer: suiIdSchema,
371
346
  });
372
347
 
package/src/types.ts CHANGED
@@ -20,12 +20,8 @@ export type Currency = z.infer<typeof s.currencySchema>;
20
20
  export type PressingState = z.infer<typeof s.pressingStateSchema>;
21
21
  export type PressingView = z.infer<typeof s.pressingViewSchema>;
22
22
  export type ListingView = z.infer<typeof s.listingViewSchema>;
23
- export type SaleView = z.infer<typeof s.saleViewSchema>;
24
23
  export type PressingDetail = z.infer<typeof s.pressingDetailSchema>;
25
- export type DiscoverItem = z.infer<typeof s.discoverItemSchema>;
26
- export type DiscoverShelf = z.infer<typeof s.discoverShelfSchema>;
27
24
  export type RecordAlbum = z.infer<typeof s.recordAlbumSchema>;
28
- export type PressingPreview = z.infer<typeof s.pressingPreviewSchema>;
29
25
 
30
26
  export type PartyMember = z.infer<typeof s.partyMemberSchema>;
31
27
  export type PlatformKey = z.infer<typeof s.platformKeySchema>;