@misofm/platform 0.25.0 → 0.26.0
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/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
|
|
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
|
-
|
|
636
|
-
client: ClientWithCoreApi,
|
|
636
|
+
const fetchCreditFields = Effect.fn("fetchCreditFields")(function* (
|
|
637
637
|
targets: readonly CreditFieldTarget[],
|
|
638
|
-
):
|
|
638
|
+
): Effect.fn.Return<Map<string, Uint8Array>, SuiRpcError, SuiClient> {
|
|
639
639
|
if (targets.length === 0) return new Map();
|
|
640
|
-
const
|
|
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
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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
|
-
|
|
787
|
-
|
|
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
|
-
):
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
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
|
|
804
|
-
client: ClientWithCoreApi,
|
|
792
|
+
export const getCompositionCreditsByIds = Effect.fn("getCompositionCreditsByIds")(function* (
|
|
805
793
|
compositionIdsInput: readonly string[],
|
|
806
794
|
compositionCreditsPackageId: string,
|
|
807
|
-
):
|
|
795
|
+
): Effect.fn.Return<Partial<Record<string, CreditView[]>>, SuiRpcError, SuiClient> {
|
|
808
796
|
const compositionIds = [...new Set(compositionIdsInput)];
|
|
809
|
-
const targets = compositionCreditTargets(
|
|
810
|
-
|
|
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)
|
|
814
|
+
* `recording_credits` extension).
|
|
830
815
|
*/
|
|
831
|
-
export
|
|
832
|
-
client: ClientWithCoreApi,
|
|
816
|
+
export const getRecordingCredits = Effect.fn("getRecordingCredits")(function* (
|
|
833
817
|
recordingId: string,
|
|
834
818
|
recordingCreditsPackageId: string,
|
|
835
|
-
):
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
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
|
|
849
|
-
client: ClientWithCoreApi,
|
|
825
|
+
export const getRecordingCreditsByIds = Effect.fn("getRecordingCreditsByIds")(function* (
|
|
850
826
|
recordingIdsInput: readonly string[],
|
|
851
827
|
recordingCreditsPackageId: string,
|
|
852
|
-
):
|
|
828
|
+
): Effect.fn.Return<Partial<Record<string, RecordingCreditsView>>, SuiRpcError, SuiClient> {
|
|
853
829
|
const recordingIds = [...new Set(recordingIdsInput)];
|
|
854
|
-
const targets = recordingCreditTargets(
|
|
855
|
-
|
|
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)
|
|
875
|
-
*
|
|
847
|
+
* Reads a release's top-line billing credits (the `release_credits` extension).
|
|
848
|
+
* Each credit has exactly one role.
|
|
876
849
|
*/
|
|
877
|
-
export
|
|
878
|
-
client: ClientWithCoreApi,
|
|
850
|
+
export const getReleaseCredits = Effect.fn("getReleaseCredits")(function* (
|
|
879
851
|
releaseId: string,
|
|
880
852
|
releaseCreditsPackageId: string,
|
|
881
|
-
):
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
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
|
|
891
|
-
client: ClientWithCoreApi,
|
|
859
|
+
export const getReleaseCreditsByIds = Effect.fn("getReleaseCreditsByIds")(function* (
|
|
892
860
|
releaseIdsInput: readonly string[],
|
|
893
861
|
releaseCreditsPackageId: string,
|
|
894
|
-
):
|
|
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 =
|
|
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
|
+
});
|
package/src/deployments.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
44
|
+
export const executeViaExecutor = Effect.fn("executeViaExecutor")(function* (
|
|
43
45
|
executor: ParallelTransactionExecutor,
|
|
44
46
|
...thunks: TxThunk[]
|
|
45
|
-
):
|
|
46
|
-
const tx =
|
|
47
|
-
const res: SuiClientTypes.TransactionResult<PlatformFullInclude> =
|
|
48
|
-
|
|
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
|
+
});
|