@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.
Files changed (140) hide show
  1. package/README.md +144 -45
  2. package/dist/auth.d.ts +8 -13
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js +78 -59
  5. package/dist/auth.js.map +1 -1
  6. package/dist/catalog.d.ts +5 -5
  7. package/dist/catalog.d.ts.map +1 -1
  8. package/dist/catalog.js +33 -21
  9. package/dist/catalog.js.map +1 -1
  10. package/dist/client.d.ts +17 -30
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +53 -75
  13. package/dist/client.js.map +1 -1
  14. package/dist/cover.d.ts +4 -3
  15. package/dist/cover.d.ts.map +1 -1
  16. package/dist/cover.js +28 -17
  17. package/dist/cover.js.map +1 -1
  18. package/dist/credits.d.ts +12 -10
  19. package/dist/credits.d.ts.map +1 -1
  20. package/dist/credits.js +60 -33
  21. package/dist/credits.js.map +1 -1
  22. package/dist/deployments.d.ts +21 -13
  23. package/dist/deployments.d.ts.map +1 -1
  24. package/dist/deployments.js +84 -37
  25. package/dist/deployments.js.map +1 -1
  26. package/dist/errors.d.ts +91 -0
  27. package/dist/errors.d.ts.map +1 -0
  28. package/dist/errors.js +111 -0
  29. package/dist/errors.js.map +1 -0
  30. package/dist/execute.d.ts +3 -1
  31. package/dist/execute.d.ts.map +1 -1
  32. package/dist/execute.js +9 -4
  33. package/dist/execute.js.map +1 -1
  34. package/dist/genre.d.ts +25 -41
  35. package/dist/genre.d.ts.map +1 -1
  36. package/dist/genre.js +71 -20
  37. package/dist/genre.js.map +1 -1
  38. package/dist/party/client.d.ts +16 -13
  39. package/dist/party/client.d.ts.map +1 -1
  40. package/dist/party/client.js +33 -22
  41. package/dist/party/client.js.map +1 -1
  42. package/dist/party/extensions/links.d.ts +2 -1
  43. package/dist/party/extensions/links.d.ts.map +1 -1
  44. package/dist/party/extensions/links.js +2 -1
  45. package/dist/party/extensions/links.js.map +1 -1
  46. package/dist/party/queries.d.ts +12 -11
  47. package/dist/party/queries.d.ts.map +1 -1
  48. package/dist/party/queries.js +63 -57
  49. package/dist/party/queries.js.map +1 -1
  50. package/dist/party/types.d.ts +31 -20
  51. package/dist/party/types.d.ts.map +1 -1
  52. package/dist/party/types.js +82 -1
  53. package/dist/party/types.js.map +1 -1
  54. package/dist/pressing.d.ts +55 -35
  55. package/dist/pressing.d.ts.map +1 -1
  56. package/dist/pressing.js +99 -76
  57. package/dist/pressing.js.map +1 -1
  58. package/dist/queries.d.ts +1 -1
  59. package/dist/queries.d.ts.map +1 -1
  60. package/dist/queries.js +5 -4
  61. package/dist/queries.js.map +1 -1
  62. package/dist/read/artist.d.ts +6 -4
  63. package/dist/read/artist.d.ts.map +1 -1
  64. package/dist/read/artist.js +29 -27
  65. package/dist/read/artist.js.map +1 -1
  66. package/dist/read/catalog.d.ts +20 -17
  67. package/dist/read/catalog.d.ts.map +1 -1
  68. package/dist/read/catalog.js +120 -141
  69. package/dist/read/catalog.js.map +1 -1
  70. package/dist/read/client.d.ts +10 -9
  71. package/dist/read/client.d.ts.map +1 -1
  72. package/dist/read/client.js.map +1 -1
  73. package/dist/read/genres.d.ts +5 -3
  74. package/dist/read/genres.d.ts.map +1 -1
  75. package/dist/read/genres.js +18 -15
  76. package/dist/read/genres.js.map +1 -1
  77. package/dist/read/index.d.ts.map +1 -1
  78. package/dist/read/index.js +7 -1
  79. package/dist/read/index.js.map +1 -1
  80. package/dist/read/receipts.d.ts +15 -6
  81. package/dist/read/receipts.d.ts.map +1 -1
  82. package/dist/read/receipts.js +73 -60
  83. package/dist/read/receipts.js.map +1 -1
  84. package/dist/read/wallet.d.ts +11 -9
  85. package/dist/read/wallet.d.ts.map +1 -1
  86. package/dist/read/wallet.js +117 -132
  87. package/dist/read/wallet.js.map +1 -1
  88. package/dist/read/works.d.ts +9 -16
  89. package/dist/read/works.d.ts.map +1 -1
  90. package/dist/read/works.js +48 -117
  91. package/dist/read/works.js.map +1 -1
  92. package/dist/recording-extensions.d.ts +8 -7
  93. package/dist/recording-extensions.d.ts.map +1 -1
  94. package/dist/recording-extensions.js +30 -63
  95. package/dist/recording-extensions.js.map +1 -1
  96. package/dist/release-extensions.d.ts +3 -2
  97. package/dist/release-extensions.d.ts.map +1 -1
  98. package/dist/release-extensions.js +9 -10
  99. package/dist/release-extensions.js.map +1 -1
  100. package/dist/royalty.d.ts +51 -51
  101. package/dist/royalty.d.ts.map +1 -1
  102. package/dist/royalty.js +93 -11
  103. package/dist/royalty.js.map +1 -1
  104. package/dist/share.d.ts +7 -6
  105. package/dist/share.d.ts.map +1 -1
  106. package/dist/share.js +37 -30
  107. package/dist/share.js.map +1 -1
  108. package/dist/vault.d.ts +20 -8
  109. package/dist/vault.d.ts.map +1 -1
  110. package/dist/vault.js +31 -15
  111. package/dist/vault.js.map +1 -1
  112. package/package.json +11 -4
  113. package/src/auth.ts +89 -70
  114. package/src/catalog.ts +33 -49
  115. package/src/client.ts +117 -97
  116. package/src/cover.ts +17 -26
  117. package/src/credits.ts +43 -75
  118. package/src/deployments.ts +103 -69
  119. package/src/errors.ts +156 -0
  120. package/src/execute.ts +10 -6
  121. package/src/genre.ts +38 -23
  122. package/src/party/client.ts +35 -23
  123. package/src/party/extensions/links.ts +3 -2
  124. package/src/party/queries.ts +77 -65
  125. package/src/party/types.ts +52 -48
  126. package/src/pressing.ts +158 -161
  127. package/src/queries.ts +5 -5
  128. package/src/read/artist.ts +47 -42
  129. package/src/read/catalog.ts +200 -223
  130. package/src/read/client.ts +12 -11
  131. package/src/read/genres.ts +24 -16
  132. package/src/read/index.ts +7 -1
  133. package/src/read/receipts.ts +128 -67
  134. package/src/read/wallet.ts +194 -162
  135. package/src/read/works.ts +75 -194
  136. package/src/recording-extensions.ts +38 -92
  137. package/src/release-extensions.ts +7 -12
  138. package/src/royalty.ts +117 -20
  139. package/src/share.ts +44 -29
  140. package/src/vault.ts +47 -24
package/src/catalog.ts CHANGED
@@ -11,16 +11,16 @@
11
11
  // to fetch, and chooses a concurrency/rate-limit tradeoff. Those are platform
12
12
  // calls, not protocol facts.
13
13
 
14
- import type { ClientWithCoreApi } from "@mysten/sui/client";
15
- import type { SuiGraphQLClient } from "@mysten/sui/graphql";
14
+ import { Effect } from "effect";
16
15
  import {
17
16
  extractTypeParams2,
18
17
  getOwnedRecordingAdminCaps,
19
18
  getRecordingsByIds,
20
19
  getReleaseById,
20
+ getWorkAddressesByShareTypes,
21
21
  type Recording,
22
22
  } from "@misofm/musicos";
23
- import { getWorkAddressesByShareTypes } from "./read/works.ts";
23
+ import { SuiClient, SuiGraphQL, SuiRpcError, type BcsDecodeError, type ObjectNotFoundError } from "@misofm/effect";
24
24
  import {
25
25
  getCompositionCreditsByIds,
26
26
  getRecordingCreditsByIds,
@@ -58,42 +58,40 @@ const EMPTY_RECORDING_CREDITS: RecordingCreditsView = {
58
58
  * are then resolved concurrently, deduplicated, and all composition credit
59
59
  * fields are fetched concurrently as well.
60
60
  */
61
- export async function getReleaseTrackCredits(
62
- client: ClientWithCoreApi,
63
- graphqlClient: SuiGraphQLClient,
61
+ export const getReleaseTrackCredits = Effect.fn("getReleaseTrackCredits")(function* (
64
62
  releaseId: string,
65
63
  options: GetReleaseTrackCreditsOptions,
66
- ): Promise<Record<string, ReleaseTrackCredits>> {
67
- const release = await getReleaseById(client, releaseId);
68
- return getTrackCreditsByRecordingIds(
69
- client,
70
- graphqlClient,
64
+ ): Effect.fn.Return<
65
+ Record<string, ReleaseTrackCredits>,
66
+ ObjectNotFoundError | SuiRpcError | BcsDecodeError,
67
+ SuiClient | SuiGraphQL
68
+ > {
69
+ const release = yield* getReleaseById(releaseId);
70
+ return yield* getTrackCreditsByRecordingIds(
71
71
  release.tracks.map((track) => track.recordingId),
72
72
  options,
73
73
  );
74
- }
74
+ });
75
75
 
76
76
  /**
77
77
  * Composition and recording credits for the supplied recordings, keyed by
78
78
  * recording id. This form is intended for consumers that already loaded a
79
79
  * release and do not need to fetch it again.
80
80
  */
81
- export async function getTrackCreditsByRecordingIds(
82
- client: ClientWithCoreApi,
83
- graphqlClient: SuiGraphQLClient,
81
+ export const getTrackCreditsByRecordingIds = Effect.fn("getTrackCreditsByRecordingIds")(function* (
84
82
  recordingIdsInput: readonly string[],
85
83
  options: GetReleaseTrackCreditsOptions,
86
- ): Promise<Record<string, ReleaseTrackCredits>> {
84
+ ): Effect.fn.Return<Record<string, ReleaseTrackCredits>, SuiRpcError | BcsDecodeError, SuiClient | SuiGraphQL> {
87
85
  const recordingIds = [...new Set(recordingIdsInput)];
88
86
  if (recordingIds.length === 0) return {};
89
87
 
90
- const [recordingCreditsById, recordingObjects] = await Promise.all([
91
- getRecordingCreditsByIds(
92
- client,
93
- recordingIds,
94
- options.recordingCreditsPackageId,
95
- ),
96
- client.core.getObjects({ objectIds: recordingIds }),
88
+ const client = yield* SuiClient;
89
+ const [recordingCreditsById, recordingObjects] = yield* Effect.all([
90
+ getRecordingCreditsByIds(recordingIds, options.recordingCreditsPackageId),
91
+ Effect.tryPromise({
92
+ try: (signal) => client.core.getObjects({ objectIds: recordingIds, signal }),
93
+ catch: (cause) => new SuiRpcError({ operation: "getObjects", cause }),
94
+ }),
97
95
  ]);
98
96
  const recordingReads = recordingObjects.objects.map((object, index) => {
99
97
  const recordingId = recordingIds[index]!;
@@ -106,11 +104,8 @@ export async function getTrackCreditsByRecordingIds(
106
104
  };
107
105
  });
108
106
 
109
- const compositionShareTypes = [
110
- ...new Set(recordingReads.map((read) => read.compositionShareType)),
111
- ];
112
- const addresses = await getWorkAddressesByShareTypes(
113
- graphqlClient,
107
+ const compositionShareTypes = [...new Set(recordingReads.map((read) => read.compositionShareType))];
108
+ const addresses = yield* getWorkAddressesByShareTypes(
114
109
  { compositions: compositionShareTypes, recordings: [] },
115
110
  options.misoPackageId,
116
111
  );
@@ -120,14 +115,9 @@ export async function getTrackCreditsByRecordingIds(
120
115
  }
121
116
  }
122
117
  const compositionIds = [
123
- ...new Set(
124
- Object.values(addresses.compositions).filter(
125
- (id): id is string => id !== undefined,
126
- ),
127
- ),
118
+ ...new Set(Object.values(addresses.compositions).filter((id): id is string => id !== undefined)),
128
119
  ];
129
- const compositionCreditsById = await getCompositionCreditsByIds(
130
- client,
120
+ const compositionCreditsById = yield* getCompositionCreditsByIds(
131
121
  compositionIds,
132
122
  options.compositionCreditsPackageId,
133
123
  );
@@ -144,7 +134,7 @@ export async function getTrackCreditsByRecordingIds(
144
134
  ];
145
135
  }),
146
136
  );
147
- }
137
+ });
148
138
 
149
139
  export interface GetAdministeredRecordingsOptions {
150
140
  /**
@@ -160,30 +150,24 @@ export interface GetAdministeredRecordingsOptions {
160
150
  * Three bounded stages: list the owner's `RecordingAdminCap`s, resolve every
161
151
  * share type in one aliased GraphQL query, then batch-fetch the recordings.
162
152
  */
163
- export async function getAdministeredRecordings(
164
- client: ClientWithCoreApi,
165
- graphqlClient: SuiGraphQLClient,
153
+ export const getAdministeredRecordings = Effect.fn("getAdministeredRecordings")(function* (
166
154
  owner: string,
167
155
  misoPackageId: string,
168
156
  options: GetAdministeredRecordingsOptions = {},
169
- ): Promise<Recording[]> {
157
+ ): Effect.fn.Return<Recording[], SuiRpcError | BcsDecodeError, SuiClient | SuiGraphQL> {
170
158
  void options;
171
- const caps = await getOwnedRecordingAdminCaps(client, owner, misoPackageId);
159
+ const caps = yield* getOwnedRecordingAdminCaps(owner, misoPackageId);
172
160
  if (caps.length === 0) return [];
173
- const addresses = await getWorkAddressesByShareTypes(
174
- graphqlClient,
161
+ const addresses = yield* getWorkAddressesByShareTypes(
175
162
  { compositions: [], recordings: caps.map((cap) => cap.shareType) },
176
163
  misoPackageId,
177
164
  );
178
- const byId = await getRecordingsByIds(
179
- client,
180
- Object.values(addresses.recordings).filter(
181
- (id): id is string => id !== undefined,
182
- ),
165
+ const byId = yield* getRecordingsByIds(
166
+ Object.values(addresses.recordings).filter((id): id is string => id !== undefined),
183
167
  );
184
168
  return caps.flatMap((cap) => {
185
169
  const id = addresses.recordings[cap.shareType];
186
170
  const recording = id ? byId[id] : undefined;
187
171
  return recording ? [recording] : [];
188
172
  });
189
- }
173
+ });
package/src/client.ts CHANGED
@@ -90,13 +90,13 @@ import {
90
90
  } from "./pressing.ts";
91
91
  import type {
92
92
  GetSaleParams,
93
- ListingView,
93
+ Listing,
94
94
  OpenListingParams,
95
95
  OpenPressingParams,
96
+ Pressing,
96
97
  PressingAdministrationParams,
97
- PressingView,
98
+ PressingRecord,
98
99
  PurchaseRecordParams,
99
- RecordView,
100
100
  SetListingPriceParams,
101
101
  SetListingStateParams,
102
102
  } from "./pressing.ts";
@@ -165,6 +165,25 @@ import {
165
165
  executeViaExecutor as executePlatformViaExecutor,
166
166
  type PlatformExecResult,
167
167
  } from "./execute.ts";
168
+ import { Effect } from "effect";
169
+ import {
170
+ SuiClient,
171
+ SuiRpcError,
172
+ type BcsDecodeError,
173
+ type ObjectTypeMismatchError,
174
+ type TransactionFailedError,
175
+ } from "@misofm/effect";
176
+ import {
177
+ MisoChainIdentifierMismatchError,
178
+ MisoClientNotReadyError,
179
+ MisoNetworkMismatchError,
180
+ } from "./errors.ts";
181
+
182
+ export {
183
+ MisoChainIdentifierMismatchError,
184
+ MisoClientNotReadyError,
185
+ MisoNetworkMismatchError,
186
+ } from "./errors.ts";
168
187
 
169
188
  type BoundMoveFunction<F> = F extends (options: infer Options) => infer Result
170
189
  ? Options extends { package?: unknown }
@@ -243,39 +262,6 @@ export interface MisoOptions<Name extends string = "miso"> {
243
262
  graphqlClient?: SuiGraphQLClient;
244
263
  }
245
264
 
246
- export class MisoNetworkMismatchError extends Error {
247
- override readonly name = "MisoNetworkMismatchError";
248
- constructor(
249
- readonly clientNetwork: string,
250
- readonly deploymentNetwork: string,
251
- ) {
252
- super(
253
- `@misofm/platform: client network "${clientNetwork}" does not match deployment network "${deploymentNetwork}"`,
254
- );
255
- }
256
- }
257
-
258
- export class MisoChainIdentifierMismatchError extends Error {
259
- override readonly name = "MisoChainIdentifierMismatchError";
260
- constructor(
261
- readonly actual: string,
262
- readonly expected: string,
263
- ) {
264
- super(
265
- `@misofm/platform: endpoint chain identifier "${actual}" does not match deployment chain identifier "${expected}"`,
266
- );
267
- }
268
- }
269
-
270
- export class MisoClientNotReadyError extends Error {
271
- override readonly name = "MisoClientNotReadyError";
272
- constructor(readonly operation: string) {
273
- super(
274
- `@misofm/platform: ${operation} requires an exact-chain validation lifecycle; call and await client.miso.ready() first`,
275
- );
276
- }
277
- }
278
-
279
265
  export interface MisoPlatformConfig {
280
266
  /** Exact client network label for deprecated `misoPlatform()` registration. */
281
267
  network?: string;
@@ -411,7 +397,10 @@ export class MisoPlatformClient {
411
397
  readonly deployment?: MisoPlatformDeployment;
412
398
  #readyState: "unvalidated" | "validating" | "ready" | "failed" =
413
399
  "unvalidated";
414
- #readyPromise?: Promise<void>;
400
+ /** Set once in the constructor via `Effect.cached`, so every caller of
401
+ * `ready()` shares the same in-flight/completed chain read. */
402
+ readonly #readyEffect: Effect.Effect<void, MisoChainIdentifierMismatchError | SuiRpcError>;
403
+ readonly #layer: ReturnType<typeof SuiClient.layer>;
415
404
 
416
405
  constructor(
417
406
  client: ClientWithCoreApi,
@@ -451,6 +440,38 @@ export class MisoPlatformClient {
451
440
  : undefined;
452
441
  this.#chainIdentifier =
453
442
  deploymentSnapshot?.chainIdentifier ?? configSnapshot.chainIdentifier;
443
+ this.#layer = SuiClient.layer(client);
444
+ // Built once; `Effect.cached` shares the same in-flight/completed chain
445
+ // read across every caller of `ready()` — the outer `Effect.cached(...)`
446
+ // itself only sets up the memoization ref, so running it synchronously
447
+ // here does not perform the underlying Core API read.
448
+ this.#readyEffect = Effect.runSync(
449
+ Effect.cached(
450
+ Effect.suspend(() => {
451
+ this.#readyState = "validating";
452
+ if (!this.#chainIdentifier) {
453
+ return Effect.die(
454
+ new Error(
455
+ "misoPlatform: chain readiness requires a complete deployment or an explicit `chainIdentifier`.",
456
+ ),
457
+ );
458
+ }
459
+ const expected = this.#chainIdentifier;
460
+ return Effect.tryPromise({
461
+ try: (signal) => this.#client.core.getChainIdentifier({ signal }),
462
+ catch: (cause) => new SuiRpcError({ operation: "getChainIdentifier", cause }),
463
+ }).pipe(
464
+ Effect.flatMap(({ chainIdentifier }) =>
465
+ chainIdentifier === expected
466
+ ? Effect.void
467
+ : new MisoChainIdentifierMismatchError({ actual: chainIdentifier, expected }),
468
+ ),
469
+ Effect.tap(() => Effect.sync(() => { this.#readyState = "ready"; })),
470
+ Effect.tapError(() => Effect.sync(() => { this.#readyState = "failed"; })),
471
+ );
472
+ }),
473
+ ),
474
+ );
454
475
  }
455
476
 
456
477
  /** Permissionless protocol APIs bound to the same validated ledger. */
@@ -465,38 +486,13 @@ export class MisoPlatformClient {
465
486
  * synchronous network-label mismatch; this Core API read protects every
466
487
  * client-bound write surface from a mislabeled or custom endpoint.
467
488
  */
468
- ready(): Promise<void> {
469
- if (!this.#readyPromise) {
470
- this.#readyState = "validating";
471
- this.#readyPromise = (async () => {
472
- try {
473
- if (!this.#chainIdentifier) {
474
- throw new Error(
475
- "misoPlatform: chain readiness requires a complete deployment or an explicit `chainIdentifier`.",
476
- );
477
- }
478
- const { chainIdentifier } =
479
- await this.#client.core.getChainIdentifier();
480
- if (chainIdentifier !== this.#chainIdentifier) {
481
- throw new MisoChainIdentifierMismatchError(
482
- chainIdentifier,
483
- this.#chainIdentifier,
484
- );
485
- }
486
- this.#readyState = "ready";
487
- } catch (error) {
488
- this.#readyState = "failed";
489
- throw error;
490
- }
491
- })();
492
- }
493
- return this.#readyPromise;
489
+ ready(): Effect.Effect<void, MisoChainIdentifierMismatchError | SuiRpcError> {
490
+ return this.#readyEffect;
494
491
  }
495
492
 
496
- /** Compatibility hook; prefer `await client.miso.ready()`. */
497
- async validateChainIdentifier(): Promise<string> {
498
- await this.ready();
499
- return this.#chainIdentifier!;
493
+ /** Compatibility hook; prefer `client.miso.ready()`. */
494
+ validateChainIdentifier(): Effect.Effect<string, MisoChainIdentifierMismatchError | SuiRpcError> {
495
+ return this.ready().pipe(Effect.map(() => this.#chainIdentifier!));
500
496
  }
501
497
 
502
498
  #requireReady(operation: string): void {
@@ -523,10 +519,23 @@ export class MisoPlatformClient {
523
519
  this.#operations();
524
520
  }
525
521
  if (this.#readyState !== "ready") {
526
- throw new MisoClientNotReadyError(operation);
522
+ throw new MisoClientNotReadyError({ operation });
527
523
  }
528
524
  }
529
525
 
526
+ /**
527
+ * Runs the exact-ledger readiness gate, then provides `SuiClient` from this
528
+ * client's `ClientWithCoreApi`, so a read method's program has `R = never`.
529
+ */
530
+ #run<A, E>(
531
+ effect: Effect.Effect<A, E, SuiClient>,
532
+ ): Effect.Effect<A, E | MisoChainIdentifierMismatchError | SuiRpcError> {
533
+ return this.ready().pipe(
534
+ Effect.flatMap(() => effect),
535
+ Effect.provide(this.#layer),
536
+ );
537
+ }
538
+
530
539
  /** Party identity and profile APIs, backed by the network SDK. */
531
540
  get party(): PartyPlatformClient {
532
541
  if (!this.#party) {
@@ -611,34 +620,47 @@ export class MisoPlatformClient {
611
620
  // ── Reads ─────────────────────────────────────────────────────────────────
612
621
 
613
622
  /** The run itself, or `null` if this release has never opened one. */
614
- async getPressing(pressingId: string): Promise<PressingView | null> {
615
- await this.ready();
616
- return getPressing(this.#client, pressingId, this.recordPackageId);
623
+ getPressing(
624
+ pressingId: string,
625
+ ): Effect.Effect<
626
+ Pressing | null,
627
+ ObjectTypeMismatchError | BcsDecodeError | SuiRpcError | MisoChainIdentifierMismatchError
628
+ > {
629
+ return this.#run(getPressing(pressingId, this.recordPackageId));
617
630
  }
618
631
 
619
632
  /** One currency's offer, or `null` if the run does not sell in it. */
620
- async getListing(listingId: string): Promise<ListingView | null> {
621
- await this.ready();
622
- return getListing(this.#client, listingId, this.recordShopPackageId);
633
+ getListing(
634
+ listingId: string,
635
+ ): Effect.Effect<
636
+ Listing | null,
637
+ ObjectTypeMismatchError | BcsDecodeError | SuiRpcError | MisoChainIdentifierMismatchError
638
+ > {
639
+ return this.#run(getListing(listingId, this.recordShopPackageId));
623
640
  }
624
641
 
625
642
  /** One concrete purchased Record, including immutable purchase provenance. */
626
- async getRecord(recordId: string): Promise<RecordView | null> {
627
- await this.ready();
628
- return getRecord(this.#client, recordId, this.recordPackageId);
643
+ getRecord(
644
+ recordId: string,
645
+ ): Effect.Effect<
646
+ PressingRecord | null,
647
+ ObjectTypeMismatchError | BcsDecodeError | SuiRpcError | MisoChainIdentifierMismatchError
648
+ > {
649
+ return this.#run(getRecord(recordId, this.recordPackageId));
629
650
  }
630
651
 
631
652
  /** Run + one currency's offer in a single round trip, by address math. */
632
- async getSale(p: Configured<GetSaleParams>): Promise<{
633
- pressing: PressingView | null;
634
- listing: ListingView | null;
635
- }> {
636
- await this.ready();
637
- return getSale(this.#client, {
638
- ...p,
639
- recordPackageId: this.recordPackageId,
640
- recordShopPackageId: this.recordShopPackageId,
641
- });
653
+ getSale(p: Configured<GetSaleParams>): Effect.Effect<
654
+ { pressing: Pressing | null; listing: Listing | null },
655
+ ObjectTypeMismatchError | BcsDecodeError | SuiRpcError | MisoChainIdentifierMismatchError
656
+ > {
657
+ return this.#run(
658
+ getSale({
659
+ ...p,
660
+ recordPackageId: this.recordPackageId,
661
+ recordShopPackageId: this.recordShopPackageId,
662
+ }),
663
+ );
642
664
  }
643
665
 
644
666
  // ── Address math ──────────────────────────────────────────────────────────
@@ -898,21 +920,19 @@ export class MisoPlatformClient {
898
920
  // ── Share currency provisioning (executes; Signer pattern) ─────────────────
899
921
 
900
922
  /** Publishes + initializes a fresh share currency (two txs). */
901
- async createShareCurrency(
923
+ createShareCurrency(
902
924
  signer: Signer,
903
925
  params: share.CreateShareCurrencyParams,
904
- ): Promise<share.ShareCurrency> {
905
- await this.ready();
906
- return share.createShareCurrency(this.#client, signer, params);
926
+ ): Effect.Effect<share.ShareCurrency, SuiRpcError | TransactionFailedError | MisoChainIdentifierMismatchError> {
927
+ return this.#run(share.createShareCurrency(signer, params));
907
928
  }
908
929
 
909
930
  /** Execute a composed platform PTB only after exact-ledger validation. */
910
- async executeViaExecutor(
931
+ executeViaExecutor(
911
932
  executor: ParallelTransactionExecutor,
912
933
  ...thunks: TxThunk[]
913
- ): Promise<PlatformExecResult> {
914
- await this.ready();
915
- return executePlatformViaExecutor(executor, ...thunks);
934
+ ): Effect.Effect<PlatformExecResult, SuiRpcError | MisoChainIdentifierMismatchError> {
935
+ return this.ready().pipe(Effect.flatMap(() => executePlatformViaExecutor(executor, ...thunks)));
916
936
  }
917
937
 
918
938
  // ── Generated layer ───────────────────────────────────────────────────────
@@ -1304,7 +1324,7 @@ export function miso<const Name extends string = "miso">(
1304
1324
  options.deployment ?? getMisoPlatformDeployment(client.network),
1305
1325
  );
1306
1326
  if (deployment.network !== client.network) {
1307
- throw new MisoNetworkMismatchError(client.network, deployment.network);
1327
+ throw new MisoNetworkMismatchError({ clientNetwork: client.network, deploymentNetwork: deployment.network });
1308
1328
  }
1309
1329
  const protocol = protocolMiso({
1310
1330
  deployment: deployment.protocol,
@@ -1356,7 +1376,7 @@ export function misoPlatform(config: MisoPlatformConfig) {
1356
1376
  register: (client: ClientWithCoreApi) => {
1357
1377
  const configSnapshot = immutableSnapshot(config);
1358
1378
  if (configSnapshot.network && configSnapshot.network !== client.network) {
1359
- throw new MisoNetworkMismatchError(client.network, configSnapshot.network);
1379
+ throw new MisoNetworkMismatchError({ clientNetwork: client.network, deploymentNetwork: configSnapshot.network });
1360
1380
  }
1361
1381
  const protocol = configSnapshot.misoPackageId
1362
1382
  ? protocolMiso({
package/src/cover.ts CHANGED
@@ -14,10 +14,11 @@
14
14
  // opinion attached to it through the release's cap-gated `uid_mut` hook, which is
15
15
  // why this module ships from `@misofm/platform` rather than the protocol SDK.
16
16
 
17
- import type { ClientWithCoreApi } from "@mysten/sui/client";
18
17
  import { bcs } from "@mysten/sui/bcs";
19
18
  import { deriveDynamicFieldID } from "@mysten/sui/utils";
20
19
  import type { Transaction, TransactionObjectArgument } from "@mysten/sui/transactions";
20
+ import { Effect } from "effect";
21
+ import { getObjectsContent, type SuiClient, type SuiRpcError } from "@misofm/effect";
21
22
  import type { TxThunk } from "./transactions.ts";
22
23
  import { asU64, directAdminCap, invokeWithAdminCap, type AdminCapAuthority, type ObjectInput, type U64Input } from "./vault.ts";
23
24
  import { OPTION_NONE, OPTION_SOME, unencryptedWalrusBlob } from "./internal.ts";
@@ -149,17 +150,13 @@ function toCoverImageRef(blob: ParsedWalrusBlob): CoverImageRef {
149
150
  * release, parses the `ReleaseCoverArt`, and returns the still (+ optional
150
151
  * animation) as normalized Walrus refs for release displays.
151
152
  */
152
- export async function getReleaseCover(
153
- client: ClientWithCoreApi,
153
+ export const getReleaseCover = Effect.fn("getReleaseCover")(function* (
154
154
  releaseId: string,
155
155
  releaseCoverArtPackageId: string,
156
- ): Promise<ReleaseCoverView | null> {
157
- return (
158
- (
159
- await getReleaseCoversByIds(client, [releaseId], releaseCoverArtPackageId)
160
- )[releaseId] ?? null
161
- );
162
- }
156
+ ): Effect.fn.Return<ReleaseCoverView | null, SuiRpcError, SuiClient> {
157
+ const found = yield* getReleaseCoversByIds([releaseId], releaseCoverArtPackageId);
158
+ return found[releaseId] ?? null;
159
+ });
163
160
 
164
161
  export function parseReleaseCoverContent(
165
162
  content: Uint8Array,
@@ -191,11 +188,10 @@ export function releaseCoverFieldId(
191
188
  /**
192
189
  * Read covers for many releases from the configured package in one Core request.
193
190
  */
194
- export async function getReleaseCoversByIds(
195
- client: ClientWithCoreApi,
191
+ export const getReleaseCoversByIds = Effect.fn("getReleaseCoversByIds")(function* (
196
192
  releaseIdsInput: readonly string[],
197
193
  releaseCoverArtPackageId: string,
198
- ): Promise<Partial<Record<string, ReleaseCoverView>>> {
194
+ ): Effect.fn.Return<Partial<Record<string, ReleaseCoverView>>, SuiRpcError, SuiClient> {
199
195
  const releaseIds = [...new Set(releaseIdsInput)];
200
196
  const targets = releaseIds.map((releaseId) => ({
201
197
  releaseId,
@@ -203,18 +199,13 @@ export async function getReleaseCoversByIds(
203
199
  }));
204
200
  if (targets.length === 0) return {};
205
201
 
206
- const { objects } = await client.core.getObjects({
207
- objectIds: targets.map((target) => target.fieldId),
208
- include: { content: true },
209
- });
202
+ const contentById = yield* getObjectsContent(targets.map((target) => target.fieldId));
210
203
  const out: Partial<Record<string, ReleaseCoverView>> = {};
211
- objects.forEach((object, index) => {
212
- const target = targets[index];
213
- if (!target || object instanceof Error || !object.content) return;
214
- const cover = parseReleaseCoverContent(object.content);
215
- if (cover) {
216
- out[target.releaseId] = cover;
217
- }
218
- });
204
+ for (const target of targets) {
205
+ const found = contentById.get(target.fieldId);
206
+ if (!found) continue;
207
+ const cover = parseReleaseCoverContent(found.content);
208
+ if (cover) out[target.releaseId] = cover;
209
+ }
219
210
  return out;
220
- }
211
+ });