@misofm/platform 0.25.0 → 0.26.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/README.md +144 -45
- package/dist/auth.d.ts +8 -13
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +78 -59
- package/dist/auth.js.map +1 -1
- package/dist/catalog.d.ts +5 -5
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +33 -21
- package/dist/catalog.js.map +1 -1
- package/dist/client.d.ts +17 -30
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +53 -75
- package/dist/client.js.map +1 -1
- package/dist/cover.d.ts +4 -3
- package/dist/cover.d.ts.map +1 -1
- package/dist/cover.js +28 -17
- package/dist/cover.js.map +1 -1
- package/dist/credits.d.ts +12 -10
- package/dist/credits.d.ts.map +1 -1
- package/dist/credits.js +60 -33
- package/dist/credits.js.map +1 -1
- package/dist/deployments.d.ts +21 -13
- package/dist/deployments.d.ts.map +1 -1
- package/dist/deployments.js +84 -37
- package/dist/deployments.js.map +1 -1
- package/dist/errors.d.ts +91 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +111 -0
- package/dist/errors.js.map +1 -0
- package/dist/execute.d.ts +3 -1
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +9 -4
- package/dist/execute.js.map +1 -1
- package/dist/genre.d.ts +25 -41
- package/dist/genre.d.ts.map +1 -1
- package/dist/genre.js +71 -20
- package/dist/genre.js.map +1 -1
- package/dist/party/client.d.ts +16 -13
- package/dist/party/client.d.ts.map +1 -1
- package/dist/party/client.js +33 -22
- package/dist/party/client.js.map +1 -1
- package/dist/party/extensions/links.d.ts +2 -1
- package/dist/party/extensions/links.d.ts.map +1 -1
- package/dist/party/extensions/links.js +2 -1
- package/dist/party/extensions/links.js.map +1 -1
- package/dist/party/queries.d.ts +12 -11
- package/dist/party/queries.d.ts.map +1 -1
- package/dist/party/queries.js +63 -57
- package/dist/party/queries.js.map +1 -1
- package/dist/party/types.d.ts +31 -20
- package/dist/party/types.d.ts.map +1 -1
- package/dist/party/types.js +82 -1
- package/dist/party/types.js.map +1 -1
- package/dist/pressing.d.ts +55 -35
- package/dist/pressing.d.ts.map +1 -1
- package/dist/pressing.js +99 -76
- package/dist/pressing.js.map +1 -1
- package/dist/queries.d.ts +1 -1
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +5 -4
- package/dist/queries.js.map +1 -1
- package/dist/read/artist.d.ts +6 -4
- package/dist/read/artist.d.ts.map +1 -1
- package/dist/read/artist.js +29 -27
- package/dist/read/artist.js.map +1 -1
- package/dist/read/catalog.d.ts +20 -17
- package/dist/read/catalog.d.ts.map +1 -1
- package/dist/read/catalog.js +120 -141
- package/dist/read/catalog.js.map +1 -1
- package/dist/read/client.d.ts +10 -9
- package/dist/read/client.d.ts.map +1 -1
- package/dist/read/client.js.map +1 -1
- package/dist/read/genres.d.ts +5 -3
- package/dist/read/genres.d.ts.map +1 -1
- package/dist/read/genres.js +18 -15
- package/dist/read/genres.js.map +1 -1
- package/dist/read/index.d.ts.map +1 -1
- package/dist/read/index.js +7 -1
- package/dist/read/index.js.map +1 -1
- package/dist/read/receipts.d.ts +15 -6
- package/dist/read/receipts.d.ts.map +1 -1
- package/dist/read/receipts.js +73 -60
- package/dist/read/receipts.js.map +1 -1
- package/dist/read/wallet.d.ts +11 -9
- package/dist/read/wallet.d.ts.map +1 -1
- package/dist/read/wallet.js +117 -132
- package/dist/read/wallet.js.map +1 -1
- package/dist/read/works.d.ts +9 -16
- package/dist/read/works.d.ts.map +1 -1
- package/dist/read/works.js +48 -117
- package/dist/read/works.js.map +1 -1
- package/dist/recording-extensions.d.ts +8 -7
- package/dist/recording-extensions.d.ts.map +1 -1
- package/dist/recording-extensions.js +30 -63
- package/dist/recording-extensions.js.map +1 -1
- package/dist/release-extensions.d.ts +3 -2
- package/dist/release-extensions.d.ts.map +1 -1
- package/dist/release-extensions.js +9 -10
- package/dist/release-extensions.js.map +1 -1
- package/dist/royalty.d.ts +51 -51
- package/dist/royalty.d.ts.map +1 -1
- package/dist/royalty.js +93 -11
- package/dist/royalty.js.map +1 -1
- package/dist/share.d.ts +7 -6
- package/dist/share.d.ts.map +1 -1
- package/dist/share.js +37 -30
- package/dist/share.js.map +1 -1
- package/dist/vault.d.ts +20 -8
- package/dist/vault.d.ts.map +1 -1
- package/dist/vault.js +31 -15
- package/dist/vault.js.map +1 -1
- package/package.json +11 -4
- package/src/auth.ts +89 -70
- package/src/catalog.ts +33 -49
- package/src/client.ts +117 -97
- package/src/cover.ts +17 -26
- package/src/credits.ts +43 -75
- package/src/deployments.ts +103 -69
- package/src/errors.ts +156 -0
- package/src/execute.ts +10 -6
- package/src/genre.ts +38 -23
- package/src/party/client.ts +35 -23
- package/src/party/extensions/links.ts +3 -2
- package/src/party/queries.ts +77 -65
- package/src/party/types.ts +52 -48
- package/src/pressing.ts +158 -161
- package/src/queries.ts +5 -5
- package/src/read/artist.ts +47 -42
- package/src/read/catalog.ts +200 -223
- package/src/read/client.ts +12 -11
- package/src/read/genres.ts +24 -16
- package/src/read/index.ts +7 -1
- package/src/read/receipts.ts +128 -67
- package/src/read/wallet.ts +194 -162
- package/src/read/works.ts +75 -194
- package/src/recording-extensions.ts +38 -92
- package/src/release-extensions.ts +7 -12
- package/src/royalty.ts +117 -20
- package/src/share.ts +44 -29
- package/src/vault.ts +47 -24
package/src/read/catalog.ts
CHANGED
|
@@ -10,14 +10,15 @@
|
|
|
10
10
|
// credits → recordings); the same work happens once here, inside one datacenter,
|
|
11
11
|
// and every subsequent visitor is served from cache.
|
|
12
12
|
|
|
13
|
+
import { Effect } from "effect";
|
|
13
14
|
import {
|
|
14
15
|
deriveListingId,
|
|
15
16
|
getListing,
|
|
16
17
|
getPressing,
|
|
17
18
|
getRecord,
|
|
18
19
|
getSale,
|
|
19
|
-
type
|
|
20
|
-
type
|
|
20
|
+
type Listing as ContractListing,
|
|
21
|
+
type Pressing as ContractPressing,
|
|
21
22
|
} from "../pressing.ts";
|
|
22
23
|
import { requireRecordSalesDeployment } from "../deployments.ts";
|
|
23
24
|
import {
|
|
@@ -44,9 +45,19 @@ import {
|
|
|
44
45
|
type RecordingEngineSessionView,
|
|
45
46
|
} from "../recording-extensions.ts";
|
|
46
47
|
import { getTrackCreditsByRecordingIds } from "../catalog.ts";
|
|
47
|
-
import { getReleaseById, getReleasesByIds
|
|
48
|
+
import { getReleaseById, getReleasesByIds } from "@misofm/musicos";
|
|
48
49
|
import type { Release } from "@misofm/musicos";
|
|
49
|
-
import
|
|
50
|
+
import {
|
|
51
|
+
getObjectsContent,
|
|
52
|
+
ObjectTypeMismatchError,
|
|
53
|
+
type BcsDecodeError,
|
|
54
|
+
type ObjectNotFoundError,
|
|
55
|
+
type SuiClient,
|
|
56
|
+
type SuiGraphQL,
|
|
57
|
+
type SuiRpcError,
|
|
58
|
+
} from "@misofm/effect";
|
|
59
|
+
import { ReleaseNotFoundError } from "../errors.ts";
|
|
60
|
+
import type { MisoConfig } from "./config.ts";
|
|
50
61
|
import { getRecordingTitles, parseReleaseObject } from "./works.ts";
|
|
51
62
|
import { int } from "./internal/scalars.ts";
|
|
52
63
|
import {
|
|
@@ -117,7 +128,7 @@ export function currencyInfo(type: string | null): Currency {
|
|
|
117
128
|
|
|
118
129
|
// ── Projections ──────────────────────────────────────────────────────────────
|
|
119
130
|
|
|
120
|
-
function toPressingView(pressing:
|
|
131
|
+
function toPressingView(pressing: ContractPressing): PressingView {
|
|
121
132
|
return {
|
|
122
133
|
id: pressing.id,
|
|
123
134
|
releaseId: pressing.releaseId,
|
|
@@ -128,7 +139,7 @@ function toPressingView(pressing: ContractPressingView): PressingView {
|
|
|
128
139
|
};
|
|
129
140
|
}
|
|
130
141
|
|
|
131
|
-
function toListingView(listing:
|
|
142
|
+
function toListingView(listing: ContractListing): ListingView {
|
|
132
143
|
const price: Price = {
|
|
133
144
|
kind: listing.pricing.kind,
|
|
134
145
|
amount: listing.pricing.amount,
|
|
@@ -144,37 +155,33 @@ function toListingView(listing: ContractListingView): ListingView {
|
|
|
144
155
|
}
|
|
145
156
|
|
|
146
157
|
function toSaleView(
|
|
147
|
-
pressing:
|
|
148
|
-
listing:
|
|
158
|
+
pressing: ContractPressing,
|
|
159
|
+
listing: ContractListing,
|
|
149
160
|
): SaleView {
|
|
150
161
|
return { pressing: toPressingView(pressing), listing: toListingView(listing) };
|
|
151
162
|
}
|
|
152
163
|
|
|
153
164
|
/** One permanent pressing, projected to the JSON-safe read boundary. */
|
|
154
|
-
export
|
|
155
|
-
client: MisoClient,
|
|
165
|
+
export const getPressingView = Effect.fn("getPressingView")(function* (
|
|
156
166
|
pressingId: string,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
pressingId,
|
|
162
|
-
sales.recordPackageId,
|
|
163
|
-
);
|
|
167
|
+
config: MisoConfig,
|
|
168
|
+
): Effect.fn.Return<PressingView | null, ObjectTypeMismatchError | BcsDecodeError | SuiRpcError, SuiClient> {
|
|
169
|
+
const sales = requireRecordSalesDeployment(config.recordSales);
|
|
170
|
+
const pressing = yield* getPressing(pressingId, sales.recordPackageId);
|
|
164
171
|
return pressing ? toPressingView(pressing) : null;
|
|
165
|
-
}
|
|
172
|
+
});
|
|
166
173
|
|
|
167
174
|
/** One currency-specific listing derived from its permanent pressing. */
|
|
168
|
-
export
|
|
169
|
-
client: MisoClient,
|
|
175
|
+
export const getListingView = Effect.fn("getListingView")(function* (
|
|
170
176
|
pressingId: string,
|
|
171
177
|
currencyType: string,
|
|
172
|
-
|
|
173
|
-
|
|
178
|
+
config: MisoConfig,
|
|
179
|
+
): Effect.fn.Return<ListingView | null, ObjectTypeMismatchError | BcsDecodeError | SuiRpcError, SuiClient> {
|
|
180
|
+
const sales = requireRecordSalesDeployment(config.recordSales);
|
|
174
181
|
const listingId = deriveListingId(pressingId, currencyType, sales.recordShopPackageId);
|
|
175
|
-
const listing =
|
|
182
|
+
const listing = yield* getListing(listingId, sales.recordShopPackageId);
|
|
176
183
|
return listing ? toListingView(listing) : null;
|
|
177
|
-
}
|
|
184
|
+
});
|
|
178
185
|
|
|
179
186
|
function toWorkState(state: Release["state"]): WorkState {
|
|
180
187
|
return state.type === "Published"
|
|
@@ -182,7 +189,7 @@ function toWorkState(state: Release["state"]): WorkState {
|
|
|
182
189
|
: { type: "Initialized" };
|
|
183
190
|
}
|
|
184
191
|
|
|
185
|
-
function toCredits(credits: CreditView[] | null): Credit[] {
|
|
192
|
+
function toCredits(credits: readonly CreditView[] | null): Credit[] {
|
|
186
193
|
return (credits ?? []).map((c) => ({
|
|
187
194
|
partyId: c.partyId,
|
|
188
195
|
displayName: c.displayName,
|
|
@@ -197,10 +204,6 @@ export function primaryArtistNames(credits: Credit[]): string[] {
|
|
|
197
204
|
.map((c) => c.displayName);
|
|
198
205
|
}
|
|
199
206
|
|
|
200
|
-
/**
|
|
201
|
-
* Number the protocol's flat tracklist. Display grouping such as discs belongs
|
|
202
|
-
* to metadata extensions and can be layered onto this projection later.
|
|
203
|
-
*/
|
|
204
207
|
/** Per-recording audio attachments, every id already base64url. */
|
|
205
208
|
export interface TrackAudio {
|
|
206
209
|
masterBlobIds?: Partial<Record<string, string>>;
|
|
@@ -208,6 +211,10 @@ export interface TrackAudio {
|
|
|
208
211
|
engineSessions?: Partial<Record<string, TrackEngineSession>>;
|
|
209
212
|
}
|
|
210
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Number the protocol's flat tracklist. Display grouping such as discs belongs
|
|
216
|
+
* to metadata extensions and can be layered onto this projection later.
|
|
217
|
+
*/
|
|
211
218
|
export function toTracks(
|
|
212
219
|
release: Release,
|
|
213
220
|
titles: Record<string, string>,
|
|
@@ -252,18 +259,16 @@ function toTrackEngineSession(view: RecordingEngineSessionView): TrackEngineSess
|
|
|
252
259
|
// ── Cover ────────────────────────────────────────────────────────────────────
|
|
253
260
|
|
|
254
261
|
/** A release's cover from the configured `release_cover_art` package. */
|
|
255
|
-
export
|
|
256
|
-
client: MisoClient,
|
|
262
|
+
export const readReleaseCover = Effect.fn("readReleaseCover")(function* (
|
|
257
263
|
releaseId: string,
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
264
|
+
config: MisoConfig,
|
|
265
|
+
): Effect.fn.Return<Cover | null, never, SuiClient> {
|
|
266
|
+
const { releaseCoverArt } = config.protocol;
|
|
267
|
+
const covers = yield* getReleaseCoversByIds([releaseId], releaseCoverArt).pipe(
|
|
268
|
+
Effect.catch(() => Effect.succeed({} as Partial<Record<string, ReleaseCoverView>>)),
|
|
269
|
+
);
|
|
270
|
+
return toCover(config.walrusAggregatorUrl, covers[releaseId] ?? null);
|
|
271
|
+
});
|
|
267
272
|
|
|
268
273
|
export type ReleaseResourceInclude = "cover" | "credits" | "kind";
|
|
269
274
|
|
|
@@ -279,66 +284,50 @@ export interface ReleaseResources {
|
|
|
279
284
|
* Every extension id is deterministic, so separate object calls only add
|
|
280
285
|
* network round trips without discovering anything new.
|
|
281
286
|
*/
|
|
282
|
-
export
|
|
283
|
-
client: MisoClient,
|
|
287
|
+
export const getReleaseResources = Effect.fn("getReleaseResources")(function* (
|
|
284
288
|
releaseId: string,
|
|
289
|
+
config: MisoConfig,
|
|
285
290
|
include: readonly ReleaseResourceInclude[] = [],
|
|
286
|
-
):
|
|
291
|
+
): Effect.fn.Return<ReleaseResources, ReleaseNotFoundError | BcsDecodeError | SuiRpcError, SuiClient> {
|
|
287
292
|
const wantsCover = include.includes("cover");
|
|
288
293
|
const wantsCredits = include.includes("credits");
|
|
289
294
|
const wantsKind = include.includes("kind");
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const creditsFieldId = wantsCredits
|
|
294
|
-
? releaseCreditsFieldId(releaseId, client.config.protocol.releaseCredits)
|
|
295
|
-
: null;
|
|
296
|
-
const kindFieldId = wantsKind
|
|
297
|
-
? releaseKindFieldId(releaseId, client.config.protocol.releaseKind)
|
|
298
|
-
: null;
|
|
295
|
+
const coverFieldId = wantsCover ? releaseCoverFieldId(releaseId, config.protocol.releaseCoverArt) : null;
|
|
296
|
+
const creditsFieldId = wantsCredits ? releaseCreditsFieldId(releaseId, config.protocol.releaseCredits) : null;
|
|
297
|
+
const kindFieldId = wantsKind ? releaseKindFieldId(releaseId, config.protocol.releaseKind) : null;
|
|
299
298
|
const objectIds = [
|
|
300
299
|
releaseId,
|
|
301
|
-
...
|
|
300
|
+
...(coverFieldId ? [coverFieldId] : []),
|
|
302
301
|
...(creditsFieldId ? [creditsFieldId] : []),
|
|
303
302
|
...(kindFieldId ? [kindFieldId] : []),
|
|
304
303
|
];
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
});
|
|
309
|
-
const
|
|
310
|
-
if (!releaseObject) throw new Error(`Release not found: ${releaseId}`);
|
|
311
|
-
if (releaseObject instanceof Error) throw releaseObject;
|
|
312
|
-
if (!releaseObject.content)
|
|
313
|
-
throw new Error(`Release has no content: ${releaseId}`);
|
|
314
|
-
const release = parseReleaseObject(
|
|
315
|
-
releaseObject.objectId,
|
|
316
|
-
releaseObject.content,
|
|
317
|
-
);
|
|
304
|
+
const contentById = yield* getObjectsContent(objectIds);
|
|
305
|
+
|
|
306
|
+
const releaseFound = contentById.get(releaseId);
|
|
307
|
+
if (!releaseFound) return yield* new ReleaseNotFoundError({ releaseId });
|
|
308
|
+
const release = yield* parseReleaseObject(releaseId, releaseFound.content);
|
|
318
309
|
|
|
319
310
|
let cover: Cover | null | undefined;
|
|
320
311
|
if (wantsCover) {
|
|
312
|
+
const found = coverFieldId ? contentById.get(coverFieldId) : undefined;
|
|
321
313
|
let view: ReleaseCoverView | null = null;
|
|
322
|
-
|
|
323
|
-
const object = objects[index + 1];
|
|
324
|
-
if (!object || object instanceof Error || !object.content) continue;
|
|
314
|
+
if (found) {
|
|
325
315
|
try {
|
|
326
|
-
view = parseReleaseCoverContent(
|
|
316
|
+
view = parseReleaseCoverContent(found.content);
|
|
327
317
|
} catch {
|
|
328
318
|
view = null;
|
|
329
319
|
}
|
|
330
|
-
if (view) break;
|
|
331
320
|
}
|
|
332
|
-
cover = toCover(
|
|
321
|
+
cover = toCover(config.walrusAggregatorUrl, view);
|
|
333
322
|
}
|
|
334
323
|
|
|
335
324
|
let credits: Credit[] | undefined;
|
|
336
325
|
if (wantsCredits) {
|
|
337
|
-
const
|
|
326
|
+
const found = creditsFieldId ? contentById.get(creditsFieldId) : undefined;
|
|
338
327
|
let view: CreditView[] = [];
|
|
339
|
-
if (
|
|
328
|
+
if (found) {
|
|
340
329
|
try {
|
|
341
|
-
view = parseReleaseCreditsContent(
|
|
330
|
+
view = parseReleaseCreditsContent(found.content);
|
|
342
331
|
} catch {
|
|
343
332
|
view = [];
|
|
344
333
|
}
|
|
@@ -347,13 +336,8 @@ export async function getReleaseResources(
|
|
|
347
336
|
}
|
|
348
337
|
let kind: string | null | undefined;
|
|
349
338
|
if (wantsKind) {
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
];
|
|
353
|
-
kind =
|
|
354
|
-
object && !(object instanceof Error) && object.content
|
|
355
|
-
? parseReleaseKindContent(object.content)
|
|
356
|
-
: null;
|
|
339
|
+
const found = kindFieldId ? contentById.get(kindFieldId) : undefined;
|
|
340
|
+
kind = found ? parseReleaseKindContent(found.content) : null;
|
|
357
341
|
}
|
|
358
342
|
return {
|
|
359
343
|
release,
|
|
@@ -361,7 +345,7 @@ export async function getReleaseResources(
|
|
|
361
345
|
...(wantsCredits ? { credits: credits ?? [] } : {}),
|
|
362
346
|
...(wantsKind ? { kind: kind ?? null } : {}),
|
|
363
347
|
};
|
|
364
|
-
}
|
|
348
|
+
});
|
|
365
349
|
|
|
366
350
|
// ── Release ──────────────────────────────────────────────────────────────────
|
|
367
351
|
|
|
@@ -380,42 +364,39 @@ export interface GetReleaseOptions {
|
|
|
380
364
|
include?: readonly ReleaseInclude[];
|
|
381
365
|
}
|
|
382
366
|
|
|
383
|
-
export
|
|
384
|
-
client: MisoClient,
|
|
367
|
+
export const getReleaseDetail = Effect.fn("getReleaseDetail")(function* (
|
|
385
368
|
releaseId: string,
|
|
369
|
+
config: MisoConfig,
|
|
386
370
|
options: GetReleaseOptions = {},
|
|
387
|
-
):
|
|
388
|
-
const { release, cover, credits, kind } =
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
);
|
|
371
|
+
): Effect.fn.Return<ReleaseDetail, ReleaseNotFoundError | BcsDecodeError | SuiRpcError, SuiClient | SuiGraphQL> {
|
|
372
|
+
const { release, cover, credits, kind } = yield* getReleaseResources(releaseId, config, [
|
|
373
|
+
"cover",
|
|
374
|
+
"credits",
|
|
375
|
+
"kind",
|
|
376
|
+
]);
|
|
393
377
|
|
|
394
378
|
const recordingIds = release.tracks.map((track) => track.recordingId);
|
|
395
|
-
const { recordingStreamingTranscode, recordingEngineSession } =
|
|
396
|
-
const [titles, masterReferences, transcodes, engineSessions, trackCredits] =
|
|
397
|
-
getRecordingTitles(
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
379
|
+
const { recordingStreamingTranscode, recordingEngineSession } = config.protocol;
|
|
380
|
+
const [titles, masterReferences, transcodes, engineSessions, trackCredits] = yield* Effect.all([
|
|
381
|
+
getRecordingTitles(recordingIds, config.deployment.musicos).pipe(
|
|
382
|
+
Effect.catch(() => Effect.succeed({} as Record<string, string>)),
|
|
383
|
+
),
|
|
384
|
+
getRecordingMasterReferencesByIds(recordingIds, config.protocol.recordingMasterReference).pipe(
|
|
385
|
+
Effect.catch(() => Effect.succeed({} as Partial<Record<string, string>>)),
|
|
402
386
|
),
|
|
403
|
-
getRecordingMasterReferencesByIds(
|
|
404
|
-
client.protocol,
|
|
405
|
-
recordingIds,
|
|
406
|
-
client.config.protocol.recordingMasterReference,
|
|
407
|
-
).catch(() => ({})),
|
|
408
387
|
recordingStreamingTranscode
|
|
409
|
-
? getRecordingStreamingTranscodesByIds(
|
|
410
|
-
|
|
388
|
+
? getRecordingStreamingTranscodesByIds(recordingIds, recordingStreamingTranscode).pipe(
|
|
389
|
+
Effect.catch(() => Effect.succeed({} as Partial<Record<string, string>>)),
|
|
390
|
+
)
|
|
391
|
+
: Effect.succeed({} as Partial<Record<string, string>>),
|
|
411
392
|
recordingEngineSession
|
|
412
|
-
? getRecordingEngineSessionsByIds(
|
|
413
|
-
()
|
|
393
|
+
? getRecordingEngineSessionsByIds(recordingIds, recordingEngineSession).pipe(
|
|
394
|
+
Effect.catch(() => Effect.succeed({} as Partial<Record<string, RecordingEngineSessionView>>)),
|
|
414
395
|
)
|
|
415
|
-
:
|
|
396
|
+
: Effect.succeed({} as Partial<Record<string, RecordingEngineSessionView>>),
|
|
416
397
|
options.include?.includes("trackCredits")
|
|
417
|
-
? getTrackCreditsForRecordingIds(
|
|
418
|
-
:
|
|
398
|
+
? getTrackCreditsForRecordingIds(recordingIds, config)
|
|
399
|
+
: Effect.succeed(undefined),
|
|
419
400
|
]);
|
|
420
401
|
const audio: TrackAudio = {
|
|
421
402
|
masterBlobIds: b64UrlByRecording(masterReferences),
|
|
@@ -443,23 +424,18 @@ export async function getReleaseDetail(
|
|
|
443
424
|
tracks: toTracks(release, titles, audio),
|
|
444
425
|
...(trackCredits !== undefined ? { trackCredits } : {}),
|
|
445
426
|
};
|
|
446
|
-
}
|
|
427
|
+
});
|
|
447
428
|
|
|
448
|
-
|
|
449
|
-
client: MisoClient,
|
|
429
|
+
const getTrackCreditsForRecordingIds = Effect.fn("getTrackCreditsForRecordingIds")(function* (
|
|
450
430
|
recordingIds: readonly string[],
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
compositionCreditsPackageId: compositionCredits,
|
|
460
|
-
recordingCreditsPackageId: recordingCredits,
|
|
461
|
-
},
|
|
462
|
-
);
|
|
431
|
+
config: MisoConfig,
|
|
432
|
+
): Effect.fn.Return<Record<string, TrackCredits>, SuiRpcError | BcsDecodeError, SuiClient | SuiGraphQL> {
|
|
433
|
+
const { compositionCredits, recordingCredits } = config.protocol;
|
|
434
|
+
const tracks = yield* getTrackCreditsByRecordingIds(recordingIds, {
|
|
435
|
+
misoPackageId: config.deployment.musicos,
|
|
436
|
+
compositionCreditsPackageId: compositionCredits,
|
|
437
|
+
recordingCreditsPackageId: recordingCredits,
|
|
438
|
+
});
|
|
463
439
|
return Object.fromEntries(
|
|
464
440
|
Object.entries(tracks).map(([id, track]) => [
|
|
465
441
|
id,
|
|
@@ -473,75 +449,83 @@ async function getTrackCreditsForRecordingIds(
|
|
|
473
449
|
} satisfies TrackCredits,
|
|
474
450
|
]),
|
|
475
451
|
);
|
|
476
|
-
}
|
|
452
|
+
});
|
|
477
453
|
|
|
478
454
|
/**
|
|
479
455
|
* Per-track credits for a release, keyed by recording id. A track with no credits
|
|
480
456
|
* set maps to an empty entry rather than being absent, so a caller can tell
|
|
481
457
|
* "nothing credited" from "no such track".
|
|
482
458
|
*/
|
|
483
|
-
export
|
|
484
|
-
client: MisoClient,
|
|
459
|
+
export const getTrackCredits = Effect.fn("getTrackCredits")(function* (
|
|
485
460
|
releaseId: string,
|
|
486
|
-
|
|
487
|
-
|
|
461
|
+
config: MisoConfig,
|
|
462
|
+
): Effect.fn.Return<Record<string, TrackCredits>, ObjectNotFoundError | SuiRpcError | BcsDecodeError, SuiClient | SuiGraphQL> {
|
|
463
|
+
const release = yield* getReleaseById(releaseId);
|
|
488
464
|
const recordingIds = release.tracks.map((track) => track.recordingId);
|
|
489
|
-
return getTrackCreditsForRecordingIds(
|
|
490
|
-
}
|
|
465
|
+
return yield* getTrackCreditsForRecordingIds(recordingIds, config);
|
|
466
|
+
});
|
|
491
467
|
|
|
492
468
|
// ── Pressing ─────────────────────────────────────────────────────────────────
|
|
493
469
|
|
|
494
470
|
/** Everything a Pressing page renders. `null` when no such Pressing exists. */
|
|
495
|
-
export
|
|
496
|
-
client: MisoClient,
|
|
471
|
+
export const getPressingDetail = Effect.fn("getPressingDetail")(function* (
|
|
497
472
|
pressingId: string,
|
|
473
|
+
config: MisoConfig,
|
|
498
474
|
options: GetReleaseOptions = {},
|
|
499
|
-
):
|
|
500
|
-
|
|
475
|
+
): Effect.fn.Return<
|
|
476
|
+
PressingDetail | null,
|
|
477
|
+
ObjectTypeMismatchError | ReleaseNotFoundError | BcsDecodeError | SuiRpcError,
|
|
478
|
+
SuiClient | SuiGraphQL
|
|
479
|
+
> {
|
|
480
|
+
const pressing = yield* getPressingView(pressingId, config);
|
|
501
481
|
if (!pressing) return null;
|
|
502
|
-
const release =
|
|
482
|
+
const release = yield* getReleaseDetail(pressing.releaseId, config, options);
|
|
503
483
|
return { pressing, release };
|
|
504
|
-
}
|
|
484
|
+
});
|
|
505
485
|
|
|
506
486
|
/**
|
|
507
487
|
* A Pressing page plus one currency-specific Listing. Unlike `getSaleDetail`,
|
|
508
488
|
* this starts from a Pressing id, which is the durable route and Party-feature
|
|
509
489
|
* reference exposed to users.
|
|
510
490
|
*/
|
|
511
|
-
export
|
|
512
|
-
client: MisoClient,
|
|
491
|
+
export const getPressingSaleDetail = Effect.fn("getPressingSaleDetail")(function* (
|
|
513
492
|
pressingId: string,
|
|
514
493
|
currencyType: string,
|
|
494
|
+
config: MisoConfig,
|
|
515
495
|
options: GetReleaseOptions = {},
|
|
516
|
-
):
|
|
517
|
-
|
|
496
|
+
): Effect.fn.Return<
|
|
497
|
+
SaleDetail | null,
|
|
498
|
+
ObjectTypeMismatchError | ReleaseNotFoundError | BcsDecodeError | SuiRpcError,
|
|
499
|
+
SuiClient | SuiGraphQL
|
|
500
|
+
> {
|
|
501
|
+
const pressing = yield* getPressingView(pressingId, config);
|
|
518
502
|
if (!pressing) return null;
|
|
519
503
|
|
|
520
|
-
const [listing, release] =
|
|
521
|
-
getListingView(
|
|
522
|
-
getReleaseDetail(
|
|
504
|
+
const [listing, release] = yield* Effect.all([
|
|
505
|
+
getListingView(pressing.id, currencyType, config),
|
|
506
|
+
getReleaseDetail(pressing.releaseId, config, options),
|
|
523
507
|
]);
|
|
524
508
|
if (!listing) return null;
|
|
525
509
|
return { sale: { pressing, listing }, release };
|
|
526
|
-
}
|
|
510
|
+
});
|
|
527
511
|
|
|
528
512
|
/**
|
|
529
513
|
* The confirmation preview behind "paste a pressing id to pin it". Verifies the
|
|
530
514
|
* object really is a `Pressing` before spending reads on it — a release id or a
|
|
531
515
|
* record id pasted by mistake must come back as `null`, not as a half-built card.
|
|
532
516
|
*/
|
|
533
|
-
export
|
|
534
|
-
client: MisoClient,
|
|
517
|
+
export const getPressingPreview = Effect.fn("getPressingPreview")(function* (
|
|
535
518
|
pressingId: string,
|
|
536
|
-
|
|
537
|
-
|
|
519
|
+
config: MisoConfig,
|
|
520
|
+
): Effect.fn.Return<
|
|
521
|
+
PressingPreview | null,
|
|
522
|
+
ObjectTypeMismatchError | ReleaseNotFoundError | BcsDecodeError | SuiRpcError,
|
|
523
|
+
SuiClient
|
|
524
|
+
> {
|
|
525
|
+
const pressing = yield* getPressingView(pressingId, config);
|
|
538
526
|
if (!pressing) return null;
|
|
539
527
|
|
|
540
|
-
const { release, cover } =
|
|
541
|
-
client,
|
|
542
|
-
pressing.releaseId,
|
|
543
|
-
["cover"],
|
|
544
|
-
);
|
|
528
|
+
const { release, cover } = yield* getReleaseResources(pressing.releaseId, config, ["cover"]);
|
|
545
529
|
|
|
546
530
|
return {
|
|
547
531
|
pressingId,
|
|
@@ -553,22 +537,26 @@ export async function getPressingPreview(
|
|
|
553
537
|
maxSupply: pressing.maxSupply,
|
|
554
538
|
trackCount: release.tracks.length,
|
|
555
539
|
};
|
|
556
|
-
}
|
|
540
|
+
});
|
|
557
541
|
|
|
558
542
|
/**
|
|
559
543
|
* Everything a currency-specific buy page renders. The Listing is derived from
|
|
560
544
|
* the release's Pressing and the requested currency, never found through mutable
|
|
561
545
|
* lookup state.
|
|
562
546
|
*/
|
|
563
|
-
export
|
|
564
|
-
client: MisoClient,
|
|
547
|
+
export const getSaleDetail = Effect.fn("getSaleDetail")(function* (
|
|
565
548
|
releaseId: string,
|
|
566
549
|
edition: number,
|
|
567
550
|
currencyType: string,
|
|
551
|
+
config: MisoConfig,
|
|
568
552
|
options: GetReleaseOptions = {},
|
|
569
|
-
):
|
|
570
|
-
|
|
571
|
-
|
|
553
|
+
): Effect.fn.Return<
|
|
554
|
+
SaleDetail | null,
|
|
555
|
+
ObjectTypeMismatchError | ReleaseNotFoundError | BcsDecodeError | SuiRpcError,
|
|
556
|
+
SuiClient | SuiGraphQL
|
|
557
|
+
> {
|
|
558
|
+
const sales = requireRecordSalesDeployment(config.recordSales);
|
|
559
|
+
const sale = yield* getSale({
|
|
572
560
|
releaseId,
|
|
573
561
|
edition,
|
|
574
562
|
currencyType,
|
|
@@ -576,9 +564,9 @@ export async function getSaleDetail(
|
|
|
576
564
|
recordShopPackageId: sales.recordShopPackageId,
|
|
577
565
|
});
|
|
578
566
|
if (!sale.pressing || !sale.listing) return null;
|
|
579
|
-
const release =
|
|
567
|
+
const release = yield* getReleaseDetail(sale.pressing.releaseId, config, options);
|
|
580
568
|
return { sale: toSaleView(sale.pressing, sale.listing), release };
|
|
581
|
-
}
|
|
569
|
+
});
|
|
582
570
|
|
|
583
571
|
// ── Discover ─────────────────────────────────────────────────────────────────
|
|
584
572
|
|
|
@@ -587,60 +575,55 @@ export async function getSaleDetail(
|
|
|
587
575
|
*
|
|
588
576
|
* Configured by release + edition + currency: both addresses are deterministic.
|
|
589
577
|
*/
|
|
590
|
-
export
|
|
591
|
-
|
|
592
|
-
):
|
|
593
|
-
const configuredSales = [...
|
|
594
|
-
const sales = requireRecordSalesDeployment(
|
|
595
|
-
const settled =
|
|
596
|
-
configuredSales
|
|
597
|
-
|
|
598
|
-
|
|
578
|
+
export const getDiscoverShelf = Effect.fn("getDiscoverShelf")(function* (
|
|
579
|
+
config: MisoConfig,
|
|
580
|
+
): Effect.fn.Return<DiscoverItem[], ObjectTypeMismatchError | BcsDecodeError | SuiRpcError, SuiClient> {
|
|
581
|
+
const configuredSales = [...config.discoverSales];
|
|
582
|
+
const sales = requireRecordSalesDeployment(config.recordSales);
|
|
583
|
+
const settled = yield* Effect.forEach(
|
|
584
|
+
configuredSales,
|
|
585
|
+
(configured) =>
|
|
586
|
+
getSale({
|
|
599
587
|
...configured,
|
|
600
588
|
recordPackageId: sales.recordPackageId,
|
|
601
589
|
recordShopPackageId: sales.recordShopPackageId,
|
|
602
|
-
}),
|
|
603
|
-
}
|
|
590
|
+
}).pipe(Effect.map((result) => ({ configured, result }))),
|
|
591
|
+
{ concurrency: "unbounded" },
|
|
604
592
|
);
|
|
605
593
|
const available = settled.filter(
|
|
606
594
|
(item): item is typeof item & {
|
|
607
|
-
result: { pressing:
|
|
595
|
+
result: { pressing: ContractPressing; listing: ContractListing };
|
|
608
596
|
} => item.result.pressing !== null && item.result.listing !== null,
|
|
609
597
|
);
|
|
610
598
|
const releaseIds = [...new Set(available.map((item) => item.result.pressing.releaseId))];
|
|
611
599
|
if (releaseIds.length === 0) return [];
|
|
612
600
|
|
|
613
|
-
const [releases, coverViews, credits] =
|
|
614
|
-
getReleasesByIds(
|
|
615
|
-
getReleaseCoversByIds(
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
)
|
|
601
|
+
const [releases, coverViews, credits] = yield* Effect.all([
|
|
602
|
+
getReleasesByIds(releaseIds),
|
|
603
|
+
getReleaseCoversByIds(releaseIds, config.protocol.releaseCoverArt).pipe(
|
|
604
|
+
Effect.catch(() => Effect.succeed({} as Partial<Record<string, ReleaseCoverView>>)),
|
|
605
|
+
),
|
|
606
|
+
getReleaseCreditsByIds(releaseIds, config.protocol.releaseCredits).pipe(
|
|
607
|
+
Effect.catch(() => Effect.succeed({} as Partial<Record<string, CreditView[]>>)),
|
|
608
|
+
),
|
|
621
609
|
]);
|
|
622
610
|
|
|
623
611
|
return available.flatMap(({ result }) => {
|
|
624
612
|
const releaseId = result.pressing.releaseId;
|
|
625
613
|
const release = releases[releaseId];
|
|
626
614
|
if (!release) return [];
|
|
627
|
-
const cover = toCover(
|
|
628
|
-
client.config.walrusAggregatorUrl,
|
|
629
|
-
coverViews[releaseId] ?? null,
|
|
630
|
-
);
|
|
615
|
+
const cover = toCover(config.walrusAggregatorUrl, coverViews[releaseId] ?? null);
|
|
631
616
|
return [
|
|
632
617
|
{
|
|
633
618
|
sale: toSaleView(result.pressing, result.listing),
|
|
634
619
|
releaseId,
|
|
635
620
|
title: release.title,
|
|
636
|
-
artist: primaryArtistNames(toCredits(credits[releaseId] ?? null)).join(
|
|
637
|
-
", ",
|
|
638
|
-
),
|
|
621
|
+
artist: primaryArtistNames(toCredits(credits[releaseId] ?? null)).join(", "),
|
|
639
622
|
coverUrl: cover?.still.url ?? null,
|
|
640
623
|
},
|
|
641
624
|
];
|
|
642
625
|
});
|
|
643
|
-
}
|
|
626
|
+
});
|
|
644
627
|
|
|
645
628
|
// ── Record → release ─────────────────────────────────────────────────────────
|
|
646
629
|
|
|
@@ -656,37 +639,31 @@ export interface GetRecordAlbumOptions {
|
|
|
656
639
|
include?: readonly RecordAlbumInclude[];
|
|
657
640
|
}
|
|
658
641
|
|
|
659
|
-
export
|
|
660
|
-
client: MisoClient,
|
|
642
|
+
export const getRecordAlbum = Effect.fn("getRecordAlbum")(function* (
|
|
661
643
|
recordId: string,
|
|
644
|
+
config: MisoConfig,
|
|
662
645
|
options: GetRecordAlbumOptions = {},
|
|
663
|
-
):
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
:
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
};
|
|
688
|
-
} catch (e) {
|
|
689
|
-
if (isNotFound(e)) return null;
|
|
690
|
-
throw e;
|
|
691
|
-
}
|
|
692
|
-
}
|
|
646
|
+
): Effect.fn.Return<
|
|
647
|
+
RecordAlbum | null,
|
|
648
|
+
ObjectTypeMismatchError | ReleaseNotFoundError | BcsDecodeError | SuiRpcError,
|
|
649
|
+
SuiClient | SuiGraphQL
|
|
650
|
+
> {
|
|
651
|
+
const sales = requireRecordSalesDeployment(config.recordSales);
|
|
652
|
+
const record = yield* getRecord(recordId, sales.recordPackageId);
|
|
653
|
+
if (!record) return null;
|
|
654
|
+
const releaseId = record.releaseId;
|
|
655
|
+
const includeRelease = options.include?.some((part) => part === "release" || part === "trackCredits") ?? false;
|
|
656
|
+
const release =
|
|
657
|
+
includeRelease && releaseId
|
|
658
|
+
? yield* getReleaseDetail(releaseId, config, {
|
|
659
|
+
include: options.include?.includes("trackCredits") ? ["trackCredits"] : [],
|
|
660
|
+
})
|
|
661
|
+
: includeRelease
|
|
662
|
+
? null
|
|
663
|
+
: undefined;
|
|
664
|
+
return {
|
|
665
|
+
recordId,
|
|
666
|
+
releaseId,
|
|
667
|
+
...(includeRelease ? { release } : {}),
|
|
668
|
+
};
|
|
669
|
+
});
|