@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misofm/api-client",
3
- "version": "0.3.0",
3
+ "version": "0.4.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": {
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,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("a superseded pressing is null, not an error", async () => {
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
- DropPreview,
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
- getDropPreview: (dropId: string): Promise<DropPreview | null> =>
221
+ getPressingPreview: (pressingId: string): Promise<PressingPreview | null> =>
208
222
  request(
209
- s.dropPreviewSchema,
210
- `/drops/${dropId}/preview`,
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" | "featured")[] } = {},
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
- getDropPreview: "sale",
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
- 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({
@@ -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
- pressing: pressingViewSchema,
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 dropPreviewSchema = z.object({
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
- price: priceSchema,
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
- dropId: z.string(),
356
- releaseId: z.string(),
357
- edition: z.number().int().min(0),
373
+ listingId: suiIdSchema,
374
+ pressingId: suiIdSchema,
375
+ releaseId: suiIdSchema,
358
376
  recordId: suiIdSchema,
359
377
  number: u64Schema,
360
378
  paid: u64Schema,
361
- buyer: z.string(),
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 drop's list price — differs from `sale.paid` on a floor-price overpay. */
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 DropPreview = z.infer<typeof s.dropPreviewSchema>;
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>;