@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/credits.ts CHANGED
@@ -28,9 +28,10 @@
28
28
  // about any of it — it only provides the cap-gated `uid_mut` hook these attach
29
29
  // through.
30
30
 
31
- import type { ClientWithCoreApi } from "@mysten/sui/client";
31
+ import { Effect, Option } from "effect";
32
32
  import { bcs } from "@mysten/sui/bcs";
33
33
  import { deriveDynamicFieldID } from "@mysten/sui/utils";
34
+ import { getObjectsContent, SuiClient, type SuiRpcError } from "@misofm/effect";
34
35
  import type {
35
36
  Transaction,
36
37
  TransactionArgument,
@@ -632,23 +633,18 @@ interface CreditFieldTarget {
632
633
  }
633
634
 
634
635
  /** Fetch many derived credit fields through one Core bulk request. */
635
- async function fetchCreditFields(
636
- client: ClientWithCoreApi,
636
+ const fetchCreditFields = Effect.fn("fetchCreditFields")(function* (
637
637
  targets: readonly CreditFieldTarget[],
638
- ): Promise<Map<string, Uint8Array>> {
638
+ ): Effect.fn.Return<Map<string, Uint8Array>, SuiRpcError, SuiClient> {
639
639
  if (targets.length === 0) return new Map();
640
- const { objects } = await client.core.getObjects({
641
- objectIds: targets.map((target) => target.fieldId),
642
- include: { content: true },
643
- });
640
+ const objects = yield* getObjectsContent(targets.map((target) => target.fieldId));
644
641
  const contents = new Map<string, Uint8Array>();
645
- objects.forEach((object, index) => {
646
- if (object instanceof Error || !object.content) return;
647
- const target = targets[index];
648
- if (target) contents.set(target.fieldId, object.content);
649
- });
642
+ for (const target of targets) {
643
+ const object = objects.get(target.fieldId);
644
+ if (object) contents.set(target.fieldId, object.content);
645
+ }
650
646
  return contents;
651
- }
647
+ });
652
648
 
653
649
  function compositionCreditTargets(
654
650
  compositionIds: readonly string[],
@@ -783,34 +779,23 @@ function recordingRoleLabel(role: ParsedEnum): string {
783
779
  * Reads a composition's writing credits (the `composition_credits` extension), or
784
780
  * `null` if no credits field is attached.
785
781
  */
786
- export async function getCompositionCredits(
787
- client: ClientWithCoreApi,
782
+ /** Reads a composition's writing credits (the `composition_credits` extension). */
783
+ export const getCompositionCredits = Effect.fn("getCompositionCredits")(function* (
788
784
  compositionId: string,
789
785
  compositionCreditsPackageId: string,
790
- ): Promise<CreditView[] | null> {
791
- return (
792
- (
793
- await getCompositionCreditsByIds(
794
- client,
795
- [compositionId],
796
- compositionCreditsPackageId,
797
- )
798
- )[compositionId] ?? null
799
- );
800
- }
786
+ ): Effect.fn.Return<Option.Option<CreditView[]>, SuiRpcError, SuiClient> {
787
+ const byId = yield* getCompositionCreditsByIds([compositionId], compositionCreditsPackageId);
788
+ return Option.fromNullishOr(byId[compositionId]);
789
+ });
801
790
 
802
791
  /** Composition credits for many works in one Core bulk request. */
803
- export async function getCompositionCreditsByIds(
804
- client: ClientWithCoreApi,
792
+ export const getCompositionCreditsByIds = Effect.fn("getCompositionCreditsByIds")(function* (
805
793
  compositionIdsInput: readonly string[],
806
794
  compositionCreditsPackageId: string,
807
- ): Promise<Partial<Record<string, CreditView[]>>> {
795
+ ): Effect.fn.Return<Partial<Record<string, CreditView[]>>, SuiRpcError, SuiClient> {
808
796
  const compositionIds = [...new Set(compositionIdsInput)];
809
- const targets = compositionCreditTargets(
810
- compositionIds,
811
- compositionCreditsPackageId,
812
- );
813
- const contents = await fetchCreditFields(client, targets);
797
+ const targets = compositionCreditTargets(compositionIds, compositionCreditsPackageId);
798
+ const contents = yield* fetchCreditFields(targets);
814
799
  const out: Partial<Record<string, CreditView[]>> = {};
815
800
  for (const target of targets) {
816
801
  const content = contents.get(target.fieldId);
@@ -822,40 +807,28 @@ export async function getCompositionCreditsByIds(
822
807
  }
823
808
  }
824
809
  return out;
825
- }
810
+ });
826
811
 
827
812
  /**
828
813
  * Reads a recording's credits plus its primary/featured artist party ids (the
829
- * `recording_credits` extension), or `null` if no credits field is attached.
814
+ * `recording_credits` extension).
830
815
  */
831
- export async function getRecordingCredits(
832
- client: ClientWithCoreApi,
816
+ export const getRecordingCredits = Effect.fn("getRecordingCredits")(function* (
833
817
  recordingId: string,
834
818
  recordingCreditsPackageId: string,
835
- ): Promise<RecordingCreditsView | null> {
836
- return (
837
- (
838
- await getRecordingCreditsByIds(
839
- client,
840
- [recordingId],
841
- recordingCreditsPackageId,
842
- )
843
- )[recordingId] ?? null
844
- );
845
- }
819
+ ): Effect.fn.Return<Option.Option<RecordingCreditsView>, SuiRpcError, SuiClient> {
820
+ const byId = yield* getRecordingCreditsByIds([recordingId], recordingCreditsPackageId);
821
+ return Option.fromNullishOr(byId[recordingId]);
822
+ });
846
823
 
847
824
  /** Recording credits for many works in one Core bulk request. */
848
- export async function getRecordingCreditsByIds(
849
- client: ClientWithCoreApi,
825
+ export const getRecordingCreditsByIds = Effect.fn("getRecordingCreditsByIds")(function* (
850
826
  recordingIdsInput: readonly string[],
851
827
  recordingCreditsPackageId: string,
852
- ): Promise<Partial<Record<string, RecordingCreditsView>>> {
828
+ ): Effect.fn.Return<Partial<Record<string, RecordingCreditsView>>, SuiRpcError, SuiClient> {
853
829
  const recordingIds = [...new Set(recordingIdsInput)];
854
- const targets = recordingCreditTargets(
855
- recordingIds,
856
- recordingCreditsPackageId,
857
- );
858
- const contents = await fetchCreditFields(client, targets);
830
+ const targets = recordingCreditTargets(recordingIds, recordingCreditsPackageId);
831
+ const contents = yield* fetchCreditFields(targets);
859
832
  const out: Partial<Record<string, RecordingCreditsView>> = {};
860
833
  for (const target of targets) {
861
834
  const content = contents.get(target.fieldId);
@@ -868,33 +841,28 @@ export async function getRecordingCreditsByIds(
868
841
  };
869
842
  }
870
843
  return out;
871
- }
844
+ });
872
845
 
873
846
  /**
874
- * Reads a release's top-line billing credits (the `release_credits` extension), or
875
- * `null` if no credits field is attached. Each credit has exactly one role.
847
+ * Reads a release's top-line billing credits (the `release_credits` extension).
848
+ * Each credit has exactly one role.
876
849
  */
877
- export async function getReleaseCredits(
878
- client: ClientWithCoreApi,
850
+ export const getReleaseCredits = Effect.fn("getReleaseCredits")(function* (
879
851
  releaseId: string,
880
852
  releaseCreditsPackageId: string,
881
- ): Promise<CreditView[] | null> {
882
- return (
883
- (
884
- await getReleaseCreditsByIds(client, [releaseId], releaseCreditsPackageId)
885
- )[releaseId] ?? null
886
- );
887
- }
853
+ ): Effect.fn.Return<Option.Option<CreditView[]>, SuiRpcError, SuiClient> {
854
+ const byId = yield* getReleaseCreditsByIds([releaseId], releaseCreditsPackageId);
855
+ return Option.fromNullishOr(byId[releaseId]);
856
+ });
888
857
 
889
858
  /** Release billing credits for many releases in one Core bulk request. */
890
- export async function getReleaseCreditsByIds(
891
- client: ClientWithCoreApi,
859
+ export const getReleaseCreditsByIds = Effect.fn("getReleaseCreditsByIds")(function* (
892
860
  releaseIdsInput: readonly string[],
893
861
  releaseCreditsPackageId: string,
894
- ): Promise<Partial<Record<string, CreditView[]>>> {
862
+ ): Effect.fn.Return<Partial<Record<string, CreditView[]>>, SuiRpcError, SuiClient> {
895
863
  const releaseIds = [...new Set(releaseIdsInput)];
896
864
  const targets = releaseCreditTargets(releaseIds, releaseCreditsPackageId);
897
- const contents = await fetchCreditFields(client, targets);
865
+ const contents = yield* fetchCreditFields(targets);
898
866
  const out: Partial<Record<string, CreditView[]>> = {};
899
867
  for (const target of targets) {
900
868
  const content = contents.get(target.fieldId);
@@ -903,4 +871,4 @@ export async function getReleaseCreditsByIds(
903
871
  }
904
872
  }
905
873
  return out;
906
- }
874
+ });
@@ -6,7 +6,20 @@ import { MISO_DEPLOYMENTS } from "@misofm/musicos/deployments";
6
6
  import type { PartyDeployment } from "@misofm/partyos/deployments";
7
7
  import { PARTYOS_DEPLOYMENTS } from "@misofm/partyos/deployments";
8
8
  import { normalizeSuiAddress, normalizeSuiObjectId } from "@mysten/sui/utils";
9
+ import { Context, Effect, Layer } from "effect";
10
+ import { DeploymentError } from "@misofm/effect/errors";
9
11
  import { immutableSnapshot } from "./internal.ts";
12
+ import {
13
+ MisoPlatformDeploymentInvalidError,
14
+ OperationsUnavailableError,
15
+ RecordSalesUnavailableError,
16
+ } from "./errors.ts";
17
+
18
+ export {
19
+ MisoPlatformDeploymentInvalidError,
20
+ OperationsUnavailableError,
21
+ RecordSalesUnavailableError,
22
+ } from "./errors.ts";
10
23
 
11
24
  /** Every package address a platform deployment must supply, for canonical-id
12
25
  * and pairwise-distinctness validation in {@link assertMisoPlatformDeployment}. */
@@ -66,20 +79,13 @@ export type RecordSalesDeployment =
66
79
  readonly recordShopPackageId: string;
67
80
  };
68
81
 
69
- export class RecordSalesUnavailableError extends Error {
70
- override readonly name = "RecordSalesUnavailableError";
71
- constructor(readonly reason: string) {
72
- super(`@misofm/platform: Record sales are unavailable: ${reason}`);
73
- }
74
- }
75
-
76
82
  export function requireRecordSalesDeployment(
77
83
  deployment: RecordSalesDeployment | undefined,
78
84
  ): Extract<RecordSalesDeployment, { status: "available" }> {
79
85
  if (!deployment || deployment.status === "unavailable") {
80
- throw new RecordSalesUnavailableError(
81
- deployment?.reason ?? "no Record sales deployment was configured",
82
- );
86
+ throw new RecordSalesUnavailableError({
87
+ reason: deployment?.reason ?? "no Record sales deployment was configured",
88
+ });
83
89
  }
84
90
  const canonicalPackageId = (value: string): boolean => {
85
91
  if (!/^0x[0-9a-f]{64}$/.test(value)) return false;
@@ -93,14 +99,14 @@ export function requireRecordSalesDeployment(
93
99
  !canonicalPackageId(deployment.recordPackageId) ||
94
100
  !canonicalPackageId(deployment.recordShopPackageId)
95
101
  ) {
96
- throw new RecordSalesUnavailableError(
97
- "Record and Record Shop package IDs must be canonical 32-byte Sui object IDs",
98
- );
102
+ throw new RecordSalesUnavailableError({
103
+ reason: "Record and Record Shop package IDs must be canonical 32-byte Sui object IDs",
104
+ });
99
105
  }
100
106
  if (deployment.recordPackageId === deployment.recordShopPackageId) {
101
- throw new RecordSalesUnavailableError(
102
- "Record and Record Shop package IDs must be distinct",
103
- );
107
+ throw new RecordSalesUnavailableError({
108
+ reason: "Record and Record Shop package IDs must be distinct",
109
+ });
104
110
  }
105
111
  return deployment;
106
112
  }
@@ -137,13 +143,6 @@ export type OperationsDeployment =
137
143
  };
138
144
  };
139
145
 
140
- export class OperationsUnavailableError extends Error {
141
- override readonly name = "OperationsUnavailableError";
142
- constructor(readonly reason: string) {
143
- super(`@misofm/platform: Vault operations are unavailable: ${reason}`);
144
- }
145
- }
146
-
147
146
  export type AvailableOperationsDeployment = Extract<
148
147
  OperationsDeployment,
149
148
  { status: "available" }
@@ -172,9 +171,9 @@ export function requireOperationsDeployment(
172
171
  deployment: OperationsDeployment | undefined,
173
172
  ): AvailableOperationsDeployment {
174
173
  if (!deployment || deployment.status !== "available") {
175
- throw new OperationsUnavailableError(
176
- deployment?.reason ?? "no Vault operations deployment was configured",
177
- );
174
+ throw new OperationsUnavailableError({
175
+ reason: deployment?.reason ?? "no Vault operations deployment was configured",
176
+ });
178
177
  }
179
178
 
180
179
  const candidate = deployment as Partial<AvailableOperationsDeployment>;
@@ -201,15 +200,15 @@ export function requireOperationsDeployment(
201
200
 
202
201
  for (const [field, id] of packageEntries) {
203
202
  if (!canonicalObjectId(id)) {
204
- throw new OperationsUnavailableError(
205
- `${field} must be a canonical 32-byte Sui package ID`,
206
- );
203
+ throw new OperationsUnavailableError({
204
+ reason: `${field} must be a canonical 32-byte Sui package ID`,
205
+ });
207
206
  }
208
207
  }
209
208
  if (!canonicalObjectId(vault?.registryId)) {
210
- throw new OperationsUnavailableError(
211
- "vault.registryId must be a canonical 32-byte Sui object ID",
212
- );
209
+ throw new OperationsUnavailableError({
210
+ reason: "vault.registryId must be a canonical 32-byte Sui object ID",
211
+ });
213
212
  }
214
213
 
215
214
  const identities = [
@@ -217,9 +216,9 @@ export function requireOperationsDeployment(
217
216
  vault.registryId,
218
217
  ];
219
218
  if (new Set(identities).size !== identities.length) {
220
- throw new OperationsUnavailableError(
221
- `the Vault registry object and all ${packageEntries.length} package IDs must be distinct`,
222
- );
219
+ throw new OperationsUnavailableError({
220
+ reason: `the Vault registry object and all ${packageEntries.length} package IDs must be distinct`,
221
+ });
223
222
  }
224
223
  return deployment;
225
224
  }
@@ -316,10 +315,6 @@ export interface MisoPlatformDeployment {
316
315
  };
317
316
  }
318
317
 
319
- export class MisoPlatformDeploymentInvalidError extends Error {
320
- override readonly name = "MisoPlatformDeploymentInvalidError";
321
- }
322
-
323
318
  /**
324
319
  * Validates the `packages` and `party` package-id sets before any Move target
325
320
  * is constructed from them: every required id must be a canonical 32-byte Sui
@@ -331,28 +326,28 @@ export function assertMisoPlatformDeployment(
331
326
  deployment: unknown,
332
327
  ): asserts deployment is MisoPlatformDeployment {
333
328
  if (!deployment || typeof deployment !== "object" || Array.isArray(deployment)) {
334
- throw new MisoPlatformDeploymentInvalidError(
335
- "@misofm/platform: deployment must be a complete MisoPlatformDeployment object.",
336
- );
329
+ throw new MisoPlatformDeploymentInvalidError({
330
+ message: "@misofm/platform: deployment must be a complete MisoPlatformDeployment object.",
331
+ });
337
332
  }
338
333
  const candidate = deployment as Partial<MisoPlatformDeployment>;
339
334
  const packages = candidate.packages as Partial<MisoPlatformDeployment["packages"]> | undefined;
340
335
  const party = candidate.party as Partial<PartyExtensionsDeployment> | undefined;
341
336
  const partyos = candidate.partyos as Partial<PartyDeployment> | undefined;
342
337
  if (!packages || typeof packages !== "object") {
343
- throw new MisoPlatformDeploymentInvalidError(
344
- "@misofm/platform: deployment is missing its `packages` section.",
345
- );
338
+ throw new MisoPlatformDeploymentInvalidError({
339
+ message: "@misofm/platform: deployment is missing its `packages` section.",
340
+ });
346
341
  }
347
342
  if (!party || typeof party !== "object") {
348
- throw new MisoPlatformDeploymentInvalidError(
349
- "@misofm/platform: deployment is missing its `party` section.",
350
- );
343
+ throw new MisoPlatformDeploymentInvalidError({
344
+ message: "@misofm/platform: deployment is missing its `party` section.",
345
+ });
351
346
  }
352
347
  if (!partyos || typeof partyos !== "object") {
353
- throw new MisoPlatformDeploymentInvalidError(
354
- "@misofm/platform: deployment is missing its `partyos` section.",
355
- );
348
+ throw new MisoPlatformDeploymentInvalidError({
349
+ message: "@misofm/platform: deployment is missing its `partyos` section.",
350
+ });
356
351
  }
357
352
 
358
353
  const seenPackageIds = new Set<string>();
@@ -363,29 +358,30 @@ export function assertMisoPlatformDeployment(
363
358
  * manifest. */
364
359
  const checkCanonical = (field: string, packageId: unknown, dedupeOk = false) => {
365
360
  if (typeof packageId !== "string") {
366
- throw new MisoPlatformDeploymentInvalidError(
367
- `@misofm/platform: deployment is missing package ID for "${field}".`,
368
- );
361
+ throw new MisoPlatformDeploymentInvalidError({
362
+ message: `@misofm/platform: deployment is missing package ID for "${field}".`,
363
+ });
369
364
  }
370
365
  let normalized: string;
371
366
  try {
372
367
  normalized = normalizeSuiAddress(packageId);
373
368
  } catch {
374
- throw new MisoPlatformDeploymentInvalidError(
375
- `@misofm/platform: deployment package ID for "${field}" is not a valid Sui address.`,
376
- );
369
+ throw new MisoPlatformDeploymentInvalidError({
370
+ message: `@misofm/platform: deployment package ID for "${field}" is not a valid Sui address.`,
371
+ });
377
372
  }
378
373
  if (packageId !== normalized) {
379
- throw new MisoPlatformDeploymentInvalidError(
380
- `@misofm/platform: deployment package ID for "${field}" must be normalized (${normalized}).`,
381
- );
374
+ throw new MisoPlatformDeploymentInvalidError({
375
+ message: `@misofm/platform: deployment package ID for "${field}" must be normalized (${normalized}).`,
376
+ });
382
377
  }
383
378
  if (!dedupeOk) {
384
379
  if (seenPackageIds.has(packageId)) {
385
- throw new MisoPlatformDeploymentInvalidError(
386
- `@misofm/platform: deployment package ID for "${field}" duplicates another package. ` +
380
+ throw new MisoPlatformDeploymentInvalidError({
381
+ message:
382
+ `@misofm/platform: deployment package ID for "${field}" duplicates another package. ` +
387
383
  "Every package in a deployment manifest must have its own address.",
388
- );
384
+ });
389
385
  }
390
386
  seenPackageIds.add(packageId);
391
387
  }
@@ -401,14 +397,16 @@ export function assertMisoPlatformDeployment(
401
397
  checkCanonical(`party.${key}`, party[key], key === "countryCode" || key === "languageCode");
402
398
  }
403
399
  if (party.countryCode !== packages.countryCode) {
404
- throw new MisoPlatformDeploymentInvalidError(
405
- "@misofm/platform: party.countryCode must match packages.countryCode (the same shared dependency package).",
406
- );
400
+ throw new MisoPlatformDeploymentInvalidError({
401
+ message:
402
+ "@misofm/platform: party.countryCode must match packages.countryCode (the same shared dependency package).",
403
+ });
407
404
  }
408
405
  if (party.languageCode !== packages.languageCode) {
409
- throw new MisoPlatformDeploymentInvalidError(
410
- "@misofm/platform: party.languageCode must match packages.languageCode (the same shared dependency package).",
411
- );
406
+ throw new MisoPlatformDeploymentInvalidError({
407
+ message:
408
+ "@misofm/platform: party.languageCode must match packages.languageCode (the same shared dependency package).",
409
+ });
412
410
  }
413
411
  }
414
412
 
@@ -572,3 +570,39 @@ export function getMisoPlatformDeployment(
572
570
  }
573
571
  return deployment;
574
572
  }
573
+
574
+ /** Effect-returning `assertMisoPlatformDeployment`: validation failure becomes a typed `DeploymentError`. */
575
+ export function validateMisoPlatformDeployment(
576
+ deployment: unknown,
577
+ ): Effect.Effect<MisoPlatformDeployment, DeploymentError> {
578
+ return Effect.try({
579
+ try: () => normalizeMisoPlatformDeployment(deployment),
580
+ catch: (cause) =>
581
+ new DeploymentError({
582
+ message: cause instanceof Error ? cause.message : String(cause),
583
+ }),
584
+ });
585
+ }
586
+
587
+ /**
588
+ * The platform deployment manifest as a service, so the deployment-aware
589
+ * platform reads and PTB builders (notably {@link Genre.derive}, which needs
590
+ * the genre registry + package ids) can declare it in `R` instead of taking it
591
+ * as an explicit parameter. `MisoPlatformClient`/`misoPlatform()` provide this
592
+ * layer internally; a bare read composed from `queries.ts` functions must
593
+ * provide it explicitly, e.g.
594
+ * `program.pipe(Effect.provide(PlatformDeployment.layer(deployment)))`.
595
+ */
596
+ export class PlatformDeployment extends Context.Service<PlatformDeployment, MisoPlatformDeployment>()(
597
+ "@misofm/platform/PlatformDeployment",
598
+ ) {
599
+ /** Provides a concrete, already-validated `MisoPlatformDeployment` as the service. */
600
+ static layer(deployment: MisoPlatformDeployment): Layer.Layer<PlatformDeployment> {
601
+ return Layer.succeed(PlatformDeployment, deployment);
602
+ }
603
+
604
+ /** Resolves a bundled deployment for `network` and provides it as the service; fails closed for unknown networks. */
605
+ static fromNetwork(network: string): Layer.Layer<PlatformDeployment> {
606
+ return PlatformDeployment.layer(getMisoPlatformDeployment(network));
607
+ }
608
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,156 @@
1
+ // Copyright (c) Miso Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ // The platform-specific error vocabulary, built on the shared foundation
5
+ // (`@misofm/effect/errors`). Every failure a caller might need to act on is a
6
+ // `Schema.TaggedError` carrying the fields needed to act; `isNotFound`-style
7
+ // message sniffing is replaced by `Effect.catchTag` against these tags.
8
+
9
+ import { Schema } from "effect";
10
+
11
+ // Re-export the foundation vocabulary so a platform consumer never needs a
12
+ // second import for the errors platform reads/executes surface directly.
13
+ export {
14
+ BcsDecodeError,
15
+ DeploymentError,
16
+ GraphQLUnavailableError,
17
+ ObjectNotFoundError,
18
+ ObjectTypeMismatchError,
19
+ SuiRpcError,
20
+ TransactionFailedError,
21
+ type SuiReadError,
22
+ } from "@misofm/effect/errors";
23
+
24
+ /** Record sales (Record + Record Shop) are unavailable on this deployment. */
25
+ export class RecordSalesUnavailableError extends Schema.TaggedError<RecordSalesUnavailableError>()(
26
+ "RecordSalesUnavailableError",
27
+ {
28
+ reason: Schema.String,
29
+ },
30
+ ) {
31
+ override get message(): string {
32
+ return `@misofm/platform: Record sales are unavailable: ${this.reason}`;
33
+ }
34
+ }
35
+
36
+ /** Vault/Action/plugin operations are unavailable on this deployment. */
37
+ export class OperationsUnavailableError extends Schema.TaggedError<OperationsUnavailableError>()(
38
+ "OperationsUnavailableError",
39
+ {
40
+ reason: Schema.String,
41
+ },
42
+ ) {
43
+ override get message(): string {
44
+ return `@misofm/platform: Vault operations are unavailable: ${this.reason}`;
45
+ }
46
+ }
47
+
48
+ /** A caller-supplied `MisoPlatformDeployment` manifest failed structural validation. */
49
+ export class MisoPlatformDeploymentInvalidError extends Schema.TaggedError<MisoPlatformDeploymentInvalidError>()(
50
+ "MisoPlatformDeploymentInvalidError",
51
+ {
52
+ message: Schema.String,
53
+ },
54
+ ) {}
55
+
56
+ /** The connected client's network does not match the configured deployment's network. */
57
+ export class MisoNetworkMismatchError extends Schema.TaggedError<MisoNetworkMismatchError>()(
58
+ "MisoNetworkMismatchError",
59
+ {
60
+ clientNetwork: Schema.String,
61
+ deploymentNetwork: Schema.String,
62
+ },
63
+ ) {
64
+ override get message(): string {
65
+ return `@misofm/platform: client network "${this.clientNetwork}" does not match deployment network "${this.deploymentNetwork}"`;
66
+ }
67
+ }
68
+
69
+ /** The connected endpoint's chain identifier does not match the configured deployment's. */
70
+ export class MisoChainIdentifierMismatchError extends Schema.TaggedError<MisoChainIdentifierMismatchError>()(
71
+ "MisoChainIdentifierMismatchError",
72
+ {
73
+ actual: Schema.String,
74
+ expected: Schema.String,
75
+ },
76
+ ) {
77
+ override get message(): string {
78
+ return `@misofm/platform: endpoint chain identifier "${this.actual}" does not match deployment chain identifier "${this.expected}"`;
79
+ }
80
+ }
81
+
82
+ /** A method that requires the exact-chain validation lifecycle was called before `ready()`. */
83
+ export class MisoClientNotReadyError extends Schema.TaggedError<MisoClientNotReadyError>()(
84
+ "MisoClientNotReadyError",
85
+ {
86
+ operation: Schema.String,
87
+ },
88
+ ) {
89
+ override get message(): string {
90
+ return `@misofm/platform: ${this.operation} requires an exact-chain validation lifecycle; call and await client.miso.ready() first`;
91
+ }
92
+ }
93
+
94
+ /** An on-chain `RecordSoldEvent` could not be decoded into a `RecordSale`. */
95
+ export class MalformedRecordSoldEventError extends Schema.TaggedError<MalformedRecordSoldEventError>()(
96
+ "MalformedRecordSoldEventError",
97
+ {
98
+ digest: Schema.optional(Schema.String),
99
+ reason: Schema.optional(Schema.String),
100
+ },
101
+ ) {}
102
+
103
+ /** Authenticated-fetch / API-authorization signing or verification failure. */
104
+ export const MisoAuthErrorCode = Schema.Literals([
105
+ "invalid_target",
106
+ "challenge_rejected",
107
+ "invalid_challenge",
108
+ "signing_failed",
109
+ ]);
110
+ export type MisoAuthErrorCode = typeof MisoAuthErrorCode.Type;
111
+
112
+ export class MisoAuthError extends Schema.TaggedError<MisoAuthError>()("MisoAuthError", {
113
+ code: MisoAuthErrorCode,
114
+ reason: Schema.String,
115
+ status: Schema.optional(Schema.Number),
116
+ cause: Schema.optional(Schema.Defect()),
117
+ }) {
118
+ override get message(): string {
119
+ return this.reason;
120
+ }
121
+ }
122
+
123
+ // ── Domain not-found wrappers ───────────────────────────────────────────────
124
+ //
125
+ // Added where a caller benefits from a typed tag instead of message-sniffing —
126
+ // concretely, `misofm/api`'s read service hand-rolls exactly this bridge today
127
+ // (`services/miso-read-service/src/routes.ts`: `isMissingRelease`,
128
+ // `isMissingReceipt`). These let that bridge use `Effect.catchTag` instead.
129
+
130
+ /** No Release exists at the requested id (or it is not a Release from the configured deployment). */
131
+ export class ReleaseNotFoundError extends Schema.TaggedError<ReleaseNotFoundError>()("ReleaseNotFoundError", {
132
+ releaseId: Schema.String,
133
+ }) {}
134
+
135
+ /** The fullnode and the indexer both have no record of this transaction digest. */
136
+ export class ReceiptNotFoundError extends Schema.TaggedError<ReceiptNotFoundError>()("ReceiptNotFoundError", {
137
+ digest: Schema.String,
138
+ }) {}
139
+
140
+ /** The transaction exists, but its effects contain no `record_shop::listing::RecordSoldEvent`. */
141
+ export class RecordPurchaseNotFoundError extends Schema.TaggedError<RecordPurchaseNotFoundError>()(
142
+ "RecordPurchaseNotFoundError",
143
+ {
144
+ digest: Schema.String,
145
+ },
146
+ ) {}
147
+
148
+ /** An object exists at the requested Pressing id, but is not a Pressing from the configured Record package. */
149
+ export class ForeignPressingError extends Schema.TaggedError<ForeignPressingError>()("ForeignPressingError", {
150
+ pressingId: Schema.String,
151
+ actualType: Schema.optional(Schema.String),
152
+ }) {
153
+ override get message(): string {
154
+ return `@misofm/platform: object ${this.pressingId} is not a Pressing from the configured Record package.`;
155
+ }
156
+ }
package/src/execute.ts CHANGED
@@ -10,7 +10,9 @@
10
10
 
11
11
  import type { ParallelTransactionExecutor } from "@mysten/sui/transactions";
12
12
  import type { SuiClientTypes } from "@mysten/sui/client";
13
+ import { Effect } from "effect";
13
14
  import { buildTx, toExecResult, FULL_INCLUDE, type ExecResult } from "@misofm/musicos";
15
+ import { SuiRpcError } from "@misofm/effect/errors";
14
16
  import type { TxThunk } from "./transactions.ts";
15
17
 
16
18
  // Consumers of the platform SDK should not need a second direct dependency on
@@ -39,14 +41,16 @@ export interface PlatformExecResult extends ExecResult {
39
41
  * job (e.g. a resumable checkpoint that reconciles against on-chain state). A Move
40
42
  * abort RESOLVES as a `FailedTransaction`, so `toExecResult` surfaces it too.
41
43
  */
42
- export async function executeViaExecutor(
44
+ export const executeViaExecutor = Effect.fn("executeViaExecutor")(function* (
43
45
  executor: ParallelTransactionExecutor,
44
46
  ...thunks: TxThunk[]
45
- ): Promise<PlatformExecResult> {
46
- const tx = await buildTx(...thunks);
47
- const res: SuiClientTypes.TransactionResult<PlatformFullInclude> =
48
- await executor.executeTransaction(tx, PLATFORM_FULL_INCLUDE);
47
+ ): Effect.fn.Return<PlatformExecResult, SuiRpcError> {
48
+ const tx = yield* buildTx(...thunks);
49
+ const res: SuiClientTypes.TransactionResult<PlatformFullInclude> = yield* Effect.tryPromise({
50
+ try: () => executor.executeTransaction(tx, PLATFORM_FULL_INCLUDE),
51
+ catch: (cause) => new SuiRpcError({ operation: "executeTransaction", cause }),
52
+ });
49
53
  const base = toExecResult(res);
50
54
  if (res.$kind !== "Transaction") throw new Error("unreachable: toExecResult accepted a failed transaction");
51
55
  return { ...base, events: res.Transaction.events ?? [] };
52
- }
56
+ });