@misofm/api-client 0.4.1 → 0.5.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misofm/api-client",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
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,30 +79,22 @@ 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`);
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(
92
+ await createMisoApiClient({ baseUrl: BASE, fetch }).getListing(
101
93
  "0xp",
102
94
  "0x2::sui::SUI",
103
95
  );
104
96
  expect(calls[0]).toBe(
105
- `${BASE}/read/v1/pressings/0xp/sale?currencyType=0x2%3A%3Asui%3A%3ASUI`,
97
+ `${BASE}/read/v1/pressings/0xp/listing?currencyType=0x2%3A%3Asui%3A%3ASUI`,
106
98
  );
107
99
  });
108
100
 
@@ -154,9 +146,9 @@ describe("error handling", () => {
154
146
  body: { error: { code: "rate-limited", message: "Too many requests." } },
155
147
  });
156
148
  const api = createMisoApiClient({ baseUrl: BASE, fetch });
157
- await expect(api.getDiscover()).rejects.toThrow(MisoApiError);
149
+ await expect(api.getPressing("0xp")).rejects.toThrow(MisoApiError);
158
150
  try {
159
- await api.getDiscover();
151
+ await api.getPressing("0xp");
160
152
  } catch (e) {
161
153
  expect(e).toBeInstanceOf(MisoApiError);
162
154
  expect((e as MisoApiError).status).toBe(429);
@@ -168,7 +160,7 @@ describe("error handling", () => {
168
160
  test("an error body in an unexpected shape still throws a usable error", async () => {
169
161
  const { fetch } = stubFetch({ status: 500, body: { oops: true } });
170
162
  try {
171
- await createMisoApiClient({ baseUrl: BASE, fetch }).getDiscover();
163
+ await createMisoApiClient({ baseUrl: BASE, fetch }).getPressing("0xp");
172
164
  throw new Error("should have thrown");
173
165
  } catch (e) {
174
166
  expect(e).toBeInstanceOf(MisoApiError);
@@ -248,8 +240,8 @@ describe("READ_CACHE_CLASS", () => {
248
240
  });
249
241
 
250
242
  test("live sale reads are the sale class", () => {
251
- expect(READ_CACHE_CLASS.getDiscover).toBe("sale");
252
243
  expect(READ_CACHE_CLASS.getPressing).toBe("sale");
244
+ expect(READ_CACHE_CLASS.getListing).toBe("sale");
253
245
  });
254
246
  });
255
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,16 +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,
33
- SaleDetail,
31
+ PressingView,
34
32
  PurchaseReceipt,
35
33
  RecordAlbum,
36
34
  ReleaseDetail,
@@ -164,32 +162,24 @@ export function createMisoApiClient(options: MisoApiClientOptions) {
164
162
 
165
163
  return {
166
164
  // ── Catalog ─────────────────────────────────────────────────────────────
167
- /** The records currently on sale. */
168
- getDiscover: (): Promise<DiscoverShelf> =>
169
- required(s.discoverShelfSchema, "/discover"),
170
-
171
- /** A pressing and everything its buy page renders. `null` when there is no such pressing. */
172
- getPressing: (
173
- pressingId: string,
174
- opts: { include?: readonly "trackCredits"[] } = {},
175
- ): Promise<PressingDetail | null> =>
165
+ /** One permanent pressing resource. */
166
+ getPressing: (pressingId: string): Promise<PressingView | null> =>
176
167
  request(
177
- s.pressingDetailSchema,
168
+ s.pressingViewSchema,
178
169
  `/pressings/${pressingId}`,
179
- { include: opts.include?.join(",") },
170
+ {},
180
171
  { nullOn404: true },
181
172
  ),
182
173
 
183
- /** One Pressing and its currency-specific Listing. */
184
- getPressingSale: (
174
+ /** One currency-specific Listing derived from a permanent Pressing. */
175
+ getListing: (
185
176
  pressingId: string,
186
177
  currencyType: string,
187
- opts: { include?: readonly "trackCredits"[] } = {},
188
- ): Promise<SaleDetail | null> =>
178
+ ): Promise<ListingView | null> =>
189
179
  request(
190
- s.saleDetailSchema,
191
- `/pressings/${pressingId}/sale`,
192
- { currencyType, include: opts.include?.join(",") },
180
+ s.listingViewSchema,
181
+ `/pressings/${pressingId}/listing`,
182
+ { currencyType },
193
183
  { nullOn404: true },
194
184
  ),
195
185
 
@@ -217,15 +207,6 @@ export function createMisoApiClient(options: MisoApiClientOptions) {
217
207
  { nullOn404: true },
218
208
  ),
219
209
 
220
- /** Confirmation preview for a pasted pressing id. `null` when it isn't a pressing. */
221
- getPressingPreview: (pressingId: string): Promise<PressingPreview | null> =>
222
- request(
223
- s.pressingPreviewSchema,
224
- `/pressings/${pressingId}/preview`,
225
- {},
226
- { nullOn404: true },
227
- ),
228
-
229
210
  // ── Artist ──────────────────────────────────────────────────────────────
230
211
  /** An artist plus optional relationship expansions. */
231
212
  getArtist: (
@@ -325,10 +306,8 @@ export function cacheBuster(nowMs: number = Date.now()): string {
325
306
  * it with `workCacheClass(release.state)`.
326
307
  */
327
308
  export const READ_CACHE_CLASS = {
328
- getDiscover: "sale",
329
309
  getPressing: "sale",
330
- getPressingSale: "sale",
331
- getPressingPreview: "sale",
310
+ getListing: "sale",
332
311
  getRecordAlbum: "immutable",
333
312
  getReceipt: "immutable",
334
313
  getArtist: "artist",
package/src/schemas.ts CHANGED
@@ -98,6 +98,8 @@ const releaseDetailBaseSchema = z.object({
98
98
  id: suiIdSchema,
99
99
  title: z.string(),
100
100
  subtitle: z.string().nullable(),
101
+ /** Self-declared `release_kind`, or null when the extension is absent. */
102
+ kind: z.string().nullable(),
101
103
  state: workStateSchema,
102
104
  publishedAtMs: z.number().int().nullable(),
103
105
  cover: coverSchema.nullable(),
@@ -156,11 +158,6 @@ export const listingViewSchema = z.object({
156
158
  state: z.enum(["enabled", "disabled"]),
157
159
  });
158
160
 
159
- export const saleViewSchema = z.object({
160
- pressing: pressingViewSchema,
161
- listing: listingViewSchema,
162
- });
163
-
164
161
  export const pressingDetailSchema = z.object({
165
162
  pressing: pressingViewSchema,
166
163
  release: releaseDetailSchema,
@@ -171,27 +168,6 @@ export const pressingDetailWireSchema = z.object({
171
168
  release: releaseDetailWireSchema,
172
169
  });
173
170
 
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
-
184
- export const discoverItemSchema = z.object({
185
- sale: saleViewSchema,
186
- releaseId: suiIdSchema,
187
- title: z.string(),
188
- /** Primary artists joined for display. Empty when no credits are set. */
189
- artist: z.string(),
190
- coverUrl: z.string().url().nullable(),
191
- });
192
-
193
- export const discoverShelfSchema = z.array(discoverItemSchema);
194
-
195
171
  export const recordAlbumSchema = z.object({
196
172
  recordId: suiIdSchema,
197
173
  releaseId: suiIdSchema.nullable(),
@@ -205,16 +181,6 @@ export const recordAlbumWireSchema = z.object({
205
181
  release: releaseDetailWireSchema.nullable().optional(),
206
182
  });
207
183
 
208
- export const pressingPreviewSchema = z.object({
209
- pressingId: suiIdSchema,
210
- title: z.string(),
211
- subtitle: z.string().nullable(),
212
- coverUrl: z.string().url().nullable(),
213
- state: pressingStateSchema,
214
- supply: u64Schema,
215
- trackCount: z.number().int().min(0),
216
- });
217
-
218
184
  // ── Artist ───────────────────────────────────────────────────────────────────
219
185
 
220
186
  export const partyMemberSchema = z.object({
package/src/types.ts CHANGED
@@ -20,13 +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 SaleDetail = z.infer<typeof s.saleDetailSchema>;
26
- export type DiscoverItem = z.infer<typeof s.discoverItemSchema>;
27
- export type DiscoverShelf = z.infer<typeof s.discoverShelfSchema>;
28
24
  export type RecordAlbum = z.infer<typeof s.recordAlbumSchema>;
29
- export type PressingPreview = z.infer<typeof s.pressingPreviewSchema>;
30
25
 
31
26
  export type PartyMember = z.infer<typeof s.partyMemberSchema>;
32
27
  export type PlatformKey = z.infer<typeof s.platformKeySchema>;