@misofm/platform 0.25.0 → 0.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +144 -45
- package/dist/auth.d.ts +8 -13
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +78 -59
- package/dist/auth.js.map +1 -1
- package/dist/catalog.d.ts +5 -5
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +33 -21
- package/dist/catalog.js.map +1 -1
- package/dist/client.d.ts +17 -30
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +53 -75
- package/dist/client.js.map +1 -1
- package/dist/cover.d.ts +4 -3
- package/dist/cover.d.ts.map +1 -1
- package/dist/cover.js +28 -17
- package/dist/cover.js.map +1 -1
- package/dist/credits.d.ts +12 -10
- package/dist/credits.d.ts.map +1 -1
- package/dist/credits.js +60 -33
- package/dist/credits.js.map +1 -1
- package/dist/deployments.d.ts +21 -13
- package/dist/deployments.d.ts.map +1 -1
- package/dist/deployments.js +84 -37
- package/dist/deployments.js.map +1 -1
- package/dist/errors.d.ts +91 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +111 -0
- package/dist/errors.js.map +1 -0
- package/dist/execute.d.ts +3 -1
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +9 -4
- package/dist/execute.js.map +1 -1
- package/dist/genre.d.ts +25 -41
- package/dist/genre.d.ts.map +1 -1
- package/dist/genre.js +71 -20
- package/dist/genre.js.map +1 -1
- package/dist/party/client.d.ts +16 -13
- package/dist/party/client.d.ts.map +1 -1
- package/dist/party/client.js +33 -22
- package/dist/party/client.js.map +1 -1
- package/dist/party/extensions/links.d.ts +2 -1
- package/dist/party/extensions/links.d.ts.map +1 -1
- package/dist/party/extensions/links.js +2 -1
- package/dist/party/extensions/links.js.map +1 -1
- package/dist/party/queries.d.ts +12 -11
- package/dist/party/queries.d.ts.map +1 -1
- package/dist/party/queries.js +63 -57
- package/dist/party/queries.js.map +1 -1
- package/dist/party/types.d.ts +31 -20
- package/dist/party/types.d.ts.map +1 -1
- package/dist/party/types.js +82 -1
- package/dist/party/types.js.map +1 -1
- package/dist/pressing.d.ts +55 -35
- package/dist/pressing.d.ts.map +1 -1
- package/dist/pressing.js +99 -76
- package/dist/pressing.js.map +1 -1
- package/dist/queries.d.ts +1 -1
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +5 -4
- package/dist/queries.js.map +1 -1
- package/dist/read/artist.d.ts +6 -4
- package/dist/read/artist.d.ts.map +1 -1
- package/dist/read/artist.js +29 -27
- package/dist/read/artist.js.map +1 -1
- package/dist/read/catalog.d.ts +20 -17
- package/dist/read/catalog.d.ts.map +1 -1
- package/dist/read/catalog.js +120 -141
- package/dist/read/catalog.js.map +1 -1
- package/dist/read/client.d.ts +10 -9
- package/dist/read/client.d.ts.map +1 -1
- package/dist/read/client.js.map +1 -1
- package/dist/read/genres.d.ts +5 -3
- package/dist/read/genres.d.ts.map +1 -1
- package/dist/read/genres.js +18 -15
- package/dist/read/genres.js.map +1 -1
- package/dist/read/index.d.ts.map +1 -1
- package/dist/read/index.js +7 -1
- package/dist/read/index.js.map +1 -1
- package/dist/read/receipts.d.ts +15 -6
- package/dist/read/receipts.d.ts.map +1 -1
- package/dist/read/receipts.js +73 -60
- package/dist/read/receipts.js.map +1 -1
- package/dist/read/wallet.d.ts +11 -9
- package/dist/read/wallet.d.ts.map +1 -1
- package/dist/read/wallet.js +117 -132
- package/dist/read/wallet.js.map +1 -1
- package/dist/read/works.d.ts +9 -16
- package/dist/read/works.d.ts.map +1 -1
- package/dist/read/works.js +48 -117
- package/dist/read/works.js.map +1 -1
- package/dist/recording-extensions.d.ts +8 -7
- package/dist/recording-extensions.d.ts.map +1 -1
- package/dist/recording-extensions.js +30 -63
- package/dist/recording-extensions.js.map +1 -1
- package/dist/release-extensions.d.ts +3 -2
- package/dist/release-extensions.d.ts.map +1 -1
- package/dist/release-extensions.js +9 -10
- package/dist/release-extensions.js.map +1 -1
- package/dist/royalty.d.ts +51 -51
- package/dist/royalty.d.ts.map +1 -1
- package/dist/royalty.js +93 -11
- package/dist/royalty.js.map +1 -1
- package/dist/share.d.ts +7 -6
- package/dist/share.d.ts.map +1 -1
- package/dist/share.js +37 -30
- package/dist/share.js.map +1 -1
- package/dist/vault.d.ts +20 -8
- package/dist/vault.d.ts.map +1 -1
- package/dist/vault.js +31 -15
- package/dist/vault.js.map +1 -1
- package/package.json +11 -4
- package/src/auth.ts +89 -70
- package/src/catalog.ts +33 -49
- package/src/client.ts +117 -97
- package/src/cover.ts +17 -26
- package/src/credits.ts +43 -75
- package/src/deployments.ts +103 -69
- package/src/errors.ts +156 -0
- package/src/execute.ts +10 -6
- package/src/genre.ts +38 -23
- package/src/party/client.ts +35 -23
- package/src/party/extensions/links.ts +3 -2
- package/src/party/queries.ts +77 -65
- package/src/party/types.ts +52 -48
- package/src/pressing.ts +158 -161
- package/src/queries.ts +5 -5
- package/src/read/artist.ts +47 -42
- package/src/read/catalog.ts +200 -223
- package/src/read/client.ts +12 -11
- package/src/read/genres.ts +24 -16
- package/src/read/index.ts +7 -1
- package/src/read/receipts.ts +128 -67
- package/src/read/wallet.ts +194 -162
- package/src/read/works.ts +75 -194
- package/src/recording-extensions.ts +38 -92
- package/src/release-extensions.ts +7 -12
- package/src/royalty.ts +117 -20
- package/src/share.ts +44 -29
- package/src/vault.ts +47 -24
package/src/genre.ts
CHANGED
|
@@ -40,12 +40,14 @@
|
|
|
40
40
|
* `genres()[0]`, computed client-side the same way Move computes them.
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
|
-
import type { ClientWithCoreApi } from "@mysten/sui/client";
|
|
44
43
|
import { bcs } from "@mysten/sui/bcs";
|
|
45
44
|
import { deriveDynamicFieldID, deriveObjectID, normalizeSuiObjectId } from "@mysten/sui/utils";
|
|
46
45
|
import type { Transaction, TransactionObjectArgument } from "@mysten/sui/transactions";
|
|
46
|
+
import { Effect, Schema } from "effect";
|
|
47
|
+
import { getOptionalObjectContent, type SuiClient, type SuiRpcError } from "@misofm/effect";
|
|
47
48
|
import type { TxThunk } from "./transactions.ts";
|
|
48
49
|
import { directAdminCap, invokeWithAdminCap, type AdminCapAuthority, type ObjectInput } from "./vault.ts";
|
|
50
|
+
import { PlatformDeployment } from "./deployments.ts";
|
|
49
51
|
import type { ReleaseExtensionTarget } from "./release-extensions.ts";
|
|
50
52
|
import type { RecordingExtensionTarget } from "./recording-extensions.ts";
|
|
51
53
|
import * as genre from "./contracts/genre/genre.ts";
|
|
@@ -82,6 +84,31 @@ export function deriveGenreAddress(
|
|
|
82
84
|
);
|
|
83
85
|
}
|
|
84
86
|
|
|
87
|
+
/**
|
|
88
|
+
* A vocabulary entry from the shared `genre` registry, identified by its
|
|
89
|
+
* canonical name (e.g. `"HIP_HOP"`). Constructing one needs the deployment's
|
|
90
|
+
* genre registry object id and genre package id — see {@link Genre.derive}.
|
|
91
|
+
*/
|
|
92
|
+
export class Genre extends Schema.Class<Genre>("@misofm/platform/Genre")({
|
|
93
|
+
id: Schema.String,
|
|
94
|
+
name: Schema.String,
|
|
95
|
+
}) {
|
|
96
|
+
/**
|
|
97
|
+
* Derives a `Genre`'s immutable object address from the platform
|
|
98
|
+
* deployment's genre registry + package id. This is a pure derivation (no
|
|
99
|
+
* chain read) but is the pilot for a value object whose construction
|
|
100
|
+
* genuinely needs deployment context, so it reaches that context through
|
|
101
|
+
* the `PlatformDeployment` service rather than an explicit parameter.
|
|
102
|
+
*/
|
|
103
|
+
static readonly derive = Effect.fn("Genre.derive")(function* (
|
|
104
|
+
canonicalName: string,
|
|
105
|
+
): Effect.fn.Return<Genre, never, PlatformDeployment> {
|
|
106
|
+
const deployment = yield* PlatformDeployment;
|
|
107
|
+
const id = deriveGenreAddress(deployment.objects.genreRegistry, deployment.packages.genre, canonicalName);
|
|
108
|
+
return new Genre({ id, name: canonicalName });
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
85
112
|
// ── Shared validation ────────────────────────────────────────────────────────
|
|
86
113
|
|
|
87
114
|
/** The maximum number of genres `release_genre` / `recording_genre` will accept. */
|
|
@@ -334,19 +361,13 @@ export function recordingGenresFieldId(
|
|
|
334
361
|
* are attached. The primary is `list[0]`; there is no separate primary reader
|
|
335
362
|
* because the Move `genres` view already returns `[]` for the absent case.
|
|
336
363
|
*/
|
|
337
|
-
export
|
|
338
|
-
client: ClientWithCoreApi,
|
|
364
|
+
export const getReleaseGenres = Effect.fn("getReleaseGenres")(function* (
|
|
339
365
|
releaseId: string,
|
|
340
366
|
releaseGenrePackageId: string,
|
|
341
|
-
):
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
});
|
|
346
|
-
const object = objects[0];
|
|
347
|
-
if (!object || object instanceof Error || !object.content) return [];
|
|
348
|
-
return parseReleaseGenresContent(object.content);
|
|
349
|
-
}
|
|
367
|
+
): Effect.fn.Return<string[], SuiRpcError, SuiClient> {
|
|
368
|
+
const found = yield* getOptionalObjectContent(releaseGenresFieldId(releaseId, releaseGenrePackageId));
|
|
369
|
+
return found._tag === "None" ? [] : parseReleaseGenresContent(found.value.content);
|
|
370
|
+
});
|
|
350
371
|
|
|
351
372
|
/**
|
|
352
373
|
* Read a Recording's genre ids in order (primary first), or `[]` when no
|
|
@@ -354,16 +375,10 @@ export async function getReleaseGenres(
|
|
|
354
375
|
* reader because the Move `genres` view already returns `[]` for the absent
|
|
355
376
|
* case.
|
|
356
377
|
*/
|
|
357
|
-
export
|
|
358
|
-
client: ClientWithCoreApi,
|
|
378
|
+
export const getRecordingGenres = Effect.fn("getRecordingGenres")(function* (
|
|
359
379
|
recordingId: string,
|
|
360
380
|
recordingGenrePackageId: string,
|
|
361
|
-
):
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
});
|
|
366
|
-
const object = objects[0];
|
|
367
|
-
if (!object || object instanceof Error || !object.content) return [];
|
|
368
|
-
return parseRecordingGenresContent(object.content);
|
|
369
|
-
}
|
|
381
|
+
): Effect.fn.Return<string[], SuiRpcError, SuiClient> {
|
|
382
|
+
const found = yield* getOptionalObjectContent(recordingGenresFieldId(recordingId, recordingGenrePackageId));
|
|
383
|
+
return found._tag === "None" ? [] : parseRecordingGenresContent(found.value.content);
|
|
384
|
+
});
|
package/src/party/client.ts
CHANGED
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
|
|
4
4
|
// Party EXTENSIONS bound to one `PartyExtensionsDeployment`, wrapping a
|
|
5
5
|
// `PartyosClient` for the Party core. Consumers reach this through
|
|
6
|
-
// `client.miso.party`.
|
|
6
|
+
// `client.miso.party`. Every read returns `Effect<A, E>` (`R = never`) — the
|
|
7
|
+
// `SuiClient` service is provided once by `PartyosClient`/`MisoPlatformClient`
|
|
8
|
+
// and threaded through here, never re-provided per method.
|
|
7
9
|
|
|
10
|
+
import { Effect } from "effect";
|
|
11
|
+
import type { Option } from "effect";
|
|
8
12
|
import type { ClientWithCoreApi } from "@mysten/sui/client";
|
|
13
|
+
import { SuiClient, type BcsDecodeError, type ObjectNotFoundError, type ObjectTypeMismatchError, type SuiRpcError } from "@misofm/effect";
|
|
9
14
|
import type { Party, PartyosClient } from "@misofm/partyos";
|
|
10
15
|
import type { PartyExtensionsDeployment } from "../deployments.ts";
|
|
11
16
|
import * as queries from "./queries.ts";
|
|
@@ -61,8 +66,8 @@ function bindModulePackage<M extends object, K extends readonly (keyof M)[]>(
|
|
|
61
66
|
* CTAs, platform links) this package owns.
|
|
62
67
|
*/
|
|
63
68
|
export class PartyPlatformClient {
|
|
64
|
-
#client: ClientWithCoreApi;
|
|
65
69
|
#core: PartyosClient;
|
|
70
|
+
#layer: ReturnType<typeof SuiClient.layer>;
|
|
66
71
|
#profilePkg: string;
|
|
67
72
|
#countryCodePkg: string;
|
|
68
73
|
#languageCodePkg: string;
|
|
@@ -77,8 +82,8 @@ export class PartyPlatformClient {
|
|
|
77
82
|
#proLinkPkg: string;
|
|
78
83
|
|
|
79
84
|
constructor(client: ClientWithCoreApi, core: PartyosClient, extensions: PartyExtensionsDeployment) {
|
|
80
|
-
this.#client = client;
|
|
81
85
|
this.#core = core;
|
|
86
|
+
this.#layer = SuiClient.layer(client);
|
|
82
87
|
this.#profilePkg = extensions.partyProfile;
|
|
83
88
|
this.#countryCodePkg = extensions.countryCode;
|
|
84
89
|
this.#languageCodePkg = extensions.languageCode;
|
|
@@ -98,61 +103,68 @@ export class PartyPlatformClient {
|
|
|
98
103
|
return this.#core;
|
|
99
104
|
}
|
|
100
105
|
|
|
106
|
+
/** Provides the bound `SuiClient` layer, dropping `R` to `never`. */
|
|
107
|
+
#provide<A, E>(effect: Effect.Effect<A, E, SuiClient>): Effect.Effect<A, E> {
|
|
108
|
+
return effect.pipe(Effect.provide(this.#layer));
|
|
109
|
+
}
|
|
110
|
+
|
|
101
111
|
// === Core (delegated to `PartyosClient`) ===
|
|
102
112
|
|
|
103
|
-
|
|
113
|
+
getPartyById(partyId: string): Effect.Effect<Party, ObjectNotFoundError | ObjectTypeMismatchError | BcsDecodeError | SuiRpcError> {
|
|
104
114
|
return this.#core.getPartyById(partyId);
|
|
105
115
|
}
|
|
106
|
-
|
|
116
|
+
getPartiesByIds(
|
|
117
|
+
partyIds: readonly string[],
|
|
118
|
+
): Effect.Effect<Partial<Record<string, Party>>, ObjectTypeMismatchError | BcsDecodeError | SuiRpcError> {
|
|
107
119
|
return this.#core.getPartiesByIds(partyIds);
|
|
108
120
|
}
|
|
109
121
|
derivePartyAdminCapId(partyId: string): string {
|
|
110
122
|
return this.#core.derivePartyAdminCapId(partyId);
|
|
111
123
|
}
|
|
112
124
|
/** Group ids a party belongs to (member-side membership records). */
|
|
113
|
-
|
|
125
|
+
getMemberships(partyId: string): Effect.Effect<string[], SuiRpcError> {
|
|
114
126
|
return this.#core.getMemberships(partyId);
|
|
115
127
|
}
|
|
116
128
|
/** Member ids invited to a group but not yet accepted. */
|
|
117
|
-
|
|
129
|
+
getPendingInvites(groupId: string): Effect.Effect<string[], SuiRpcError> {
|
|
118
130
|
return this.#core.getPendingInvites(groupId);
|
|
119
131
|
}
|
|
120
132
|
/** Group ids that have invited this party but are awaiting its response. */
|
|
121
|
-
|
|
133
|
+
getPendingMemberships(partyId: string): Effect.Effect<string[], SuiRpcError> {
|
|
122
134
|
return this.#core.getPendingMemberships(partyId);
|
|
123
135
|
}
|
|
124
136
|
/** Whether a party is a member of a group. */
|
|
125
|
-
|
|
137
|
+
isMember(memberId: string, groupId: string): Effect.Effect<boolean, SuiRpcError> {
|
|
126
138
|
return this.#core.isMember(memberId, groupId);
|
|
127
139
|
}
|
|
128
140
|
|
|
129
141
|
// === Extension queries ===
|
|
130
142
|
|
|
131
|
-
|
|
132
|
-
return queries.getProfile(
|
|
143
|
+
getProfile(partyId: string): Effect.Effect<Option.Option<Profile>, BcsDecodeError | SuiRpcError> {
|
|
144
|
+
return this.#provide(queries.getProfile(partyId, this.#profilePkg));
|
|
133
145
|
}
|
|
134
|
-
|
|
135
|
-
return queries.getMedia(
|
|
146
|
+
getMedia(partyId: string): Effect.Effect<Option.Option<Media>, BcsDecodeError | SuiRpcError> {
|
|
147
|
+
return this.#provide(queries.getMedia(partyId, this.#mediaPkg));
|
|
136
148
|
}
|
|
137
149
|
/** The party's artist-type roles (display names). */
|
|
138
|
-
|
|
139
|
-
return queries.getRoles(
|
|
150
|
+
getRoles(partyId: string): Effect.Effect<string[], SuiRpcError> {
|
|
151
|
+
return this.#provide(queries.getRoles(partyId, this.#rolesPkg));
|
|
140
152
|
}
|
|
141
153
|
/** The party's free-form tags. */
|
|
142
|
-
|
|
143
|
-
return queries.getTags(
|
|
154
|
+
getTags(partyId: string): Effect.Effect<string[], SuiRpcError> {
|
|
155
|
+
return this.#provide(queries.getTags(partyId, this.#tagsPkg));
|
|
144
156
|
}
|
|
145
157
|
/** The party's genre object ids. */
|
|
146
|
-
|
|
147
|
-
return queries.getGenres(
|
|
158
|
+
getGenres(partyId: string): Effect.Effect<string[], SuiRpcError> {
|
|
159
|
+
return this.#provide(queries.getGenres(partyId, this.#partyGenrePkg));
|
|
148
160
|
}
|
|
149
161
|
/** The party's ordered CTA list (position is priority). */
|
|
150
|
-
|
|
151
|
-
return queries.getCtas(
|
|
162
|
+
getCtas(partyId: string): Effect.Effect<Cta[], SuiRpcError> {
|
|
163
|
+
return this.#provide(queries.getCtas(partyId, this.#ctaPkg));
|
|
152
164
|
}
|
|
153
165
|
/** All external-platform links attached to the party (social, music, professional). */
|
|
154
|
-
|
|
155
|
-
return queries.getLinks(
|
|
166
|
+
getLinks(partyId: string): Effect.Effect<PlatformLink[], SuiRpcError> {
|
|
167
|
+
return this.#provide(queries.getLinks(partyId));
|
|
156
168
|
}
|
|
157
169
|
|
|
158
170
|
// === Transaction builders (thunks; package ids bound from the client) ===
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
import * as platformLinkMod from "../../contracts/party_platform_link/party_platform_link.ts";
|
|
14
14
|
import type { TxThunk } from "@misofm/partyos";
|
|
15
|
-
import
|
|
15
|
+
import { PlatformLink } from "../types.ts";
|
|
16
|
+
import type { PlatformKey } from "../types.ts";
|
|
16
17
|
|
|
17
18
|
/** Which payload package a platform's `Data` type + constructor live in. */
|
|
18
19
|
type PayloadPkgKind = "social" | "music" | "pro";
|
|
@@ -160,7 +161,7 @@ export function platformForDataType(dataType: string): PlatformKey | null {
|
|
|
160
161
|
|
|
161
162
|
/** Builds a public `PlatformLink` from a platform key and its stored native identifier. */
|
|
162
163
|
export function buildLink(platform: PlatformKey, value: string): PlatformLink {
|
|
163
|
-
return { platform, value, url: PLATFORMS[platform].url(value) };
|
|
164
|
+
return new PlatformLink({ platform, value, url: PLATFORMS[platform].url(value) });
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
// === Bound builders (the client binds package ids, then exposes per-platform methods) ===
|
package/src/party/queries.ts
CHANGED
|
@@ -7,10 +7,17 @@
|
|
|
7
7
|
// in `@misofm/partyos` — this file only covers profile/media/roles/tags/genres/
|
|
8
8
|
// ctas/links.
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import { Effect, Option, Stream } from "effect";
|
|
11
11
|
import { deriveDynamicFieldID } from "@mysten/sui/utils";
|
|
12
12
|
import { bcs } from "@mysten/sui/bcs";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
decodeBcs,
|
|
15
|
+
getOptionalObjectContent,
|
|
16
|
+
listDynamicFields,
|
|
17
|
+
SuiClient,
|
|
18
|
+
type BcsDecodeError,
|
|
19
|
+
type SuiRpcError,
|
|
20
|
+
} from "@misofm/effect";
|
|
14
21
|
import { Profile as ProfileBcs, ProfileKey as ProfileKeyBcs } from "../contracts/party_profile/party_profile.ts";
|
|
15
22
|
import { Media as MediaBcs, MediaKey as MediaKeyBcs } from "../contracts/party_media/party_media.ts";
|
|
16
23
|
import { ArtistRole as ArtistRoleBcs, RolesKey as RolesKeyBcs } from "../contracts/party_roles/party_roles.ts";
|
|
@@ -19,115 +26,122 @@ import { GenresKey as GenresKeyBcs } from "../contracts/party_genre/party_genre.
|
|
|
19
26
|
import { Cta as CtaBcs, CtasKey as CtasKeyBcs } from "../contracts/party_cta/party_cta.ts";
|
|
20
27
|
import { mapCtas, mapGenres, mapMedia, mapProfile, mapRoles, mapTags } from "./internal.ts";
|
|
21
28
|
import { buildLink, platformForDataType } from "./extensions/links.ts";
|
|
22
|
-
import
|
|
29
|
+
import { Cta, Media, PlatformLink, Profile } from "./types.ts";
|
|
23
30
|
|
|
24
31
|
// The profile is a dynamic field on the party UID under an empty `ProfileKey()`
|
|
25
32
|
// (Move's implicit `dummy_field: bool` → one zero byte).
|
|
26
33
|
const ProfileField = bcs.struct("Field", { id: bcs.Address, name: ProfileKeyBcs, value: ProfileBcs });
|
|
27
34
|
const PROFILE_KEY_BYTES = ProfileKeyBcs.serialize([false]).toBytes();
|
|
28
35
|
|
|
29
|
-
/** Fetches a party's profile
|
|
30
|
-
export
|
|
31
|
-
client: ClientWithCoreApi,
|
|
36
|
+
/** Fetches a party's profile; `Option.none()` if none is set. */
|
|
37
|
+
export const getProfile = Effect.fn("getProfile")(function* (
|
|
32
38
|
partyId: string,
|
|
33
39
|
partyProfilePackageId: string,
|
|
34
|
-
):
|
|
40
|
+
): Effect.fn.Return<Option.Option<Profile>, BcsDecodeError | SuiRpcError, SuiClient> {
|
|
35
41
|
const fieldId = deriveDynamicFieldID(
|
|
36
42
|
partyId,
|
|
37
43
|
`${partyProfilePackageId}::party_profile::ProfileKey`,
|
|
38
44
|
PROFILE_KEY_BYTES,
|
|
39
45
|
);
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
}
|
|
46
|
+
const contentOpt = yield* getOptionalObjectContent(fieldId);
|
|
47
|
+
if (Option.isNone(contentOpt)) return Option.none();
|
|
48
|
+
const profile = yield* decodeBcs(
|
|
49
|
+
{ parse: (bytes: Uint8Array) => mapProfile(partyId, ProfileField.parse(bytes).value) },
|
|
50
|
+
Profile,
|
|
51
|
+
contentOpt.value.content,
|
|
52
|
+
{ type: "Profile", objectId: fieldId },
|
|
53
|
+
);
|
|
54
|
+
return Option.some(profile);
|
|
55
|
+
});
|
|
44
56
|
|
|
45
57
|
// Media is a dynamic field on the party UID under an empty `MediaKey()` (same
|
|
46
58
|
// implicit `dummy_field: bool` → one zero byte as the profile key).
|
|
47
59
|
const MediaField = bcs.struct("Field", { id: bcs.Address, name: MediaKeyBcs, value: MediaBcs });
|
|
48
60
|
const MEDIA_KEY_BYTES = MediaKeyBcs.serialize([false]).toBytes();
|
|
49
61
|
|
|
50
|
-
/** Fetches a party's media (avatar + header)
|
|
51
|
-
export
|
|
52
|
-
client: ClientWithCoreApi,
|
|
62
|
+
/** Fetches a party's media (avatar + header); `Option.none()` if none is set. */
|
|
63
|
+
export const getMedia = Effect.fn("getMedia")(function* (
|
|
53
64
|
partyId: string,
|
|
54
65
|
partyMediaPackageId: string,
|
|
55
|
-
):
|
|
66
|
+
): Effect.fn.Return<Option.Option<Media>, BcsDecodeError | SuiRpcError, SuiClient> {
|
|
56
67
|
const fieldId = deriveDynamicFieldID(
|
|
57
68
|
partyId,
|
|
58
69
|
`${partyMediaPackageId}::party_media::MediaKey`,
|
|
59
70
|
MEDIA_KEY_BYTES,
|
|
60
71
|
);
|
|
61
|
-
const
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
}
|
|
72
|
+
const contentOpt = yield* getOptionalObjectContent(fieldId);
|
|
73
|
+
if (Option.isNone(contentOpt)) return Option.none();
|
|
74
|
+
const media = yield* decodeBcs(
|
|
75
|
+
{ parse: (bytes: Uint8Array) => mapMedia(partyId, MediaField.parse(bytes).value) },
|
|
76
|
+
Media,
|
|
77
|
+
contentOpt.value.content,
|
|
78
|
+
{ type: "Media", objectId: fieldId },
|
|
79
|
+
);
|
|
80
|
+
return Option.some(media);
|
|
81
|
+
});
|
|
65
82
|
|
|
66
83
|
// === Collection extension reads (roles / tags / genres / ctas) ===
|
|
67
84
|
//
|
|
68
85
|
// Each is a single dynamic field on the party UID under an empty positional key
|
|
69
86
|
// (Move's implicit `dummy_field: bool` → one zero byte, same as the profile key).
|
|
87
|
+
// Absence keeps the pre-migration policy: an empty collection, not a failure.
|
|
70
88
|
|
|
71
89
|
const ROLES_KEY_BYTES = RolesKeyBcs.serialize([false]).toBytes();
|
|
72
90
|
const RolesField = bcs.struct("Field", { id: bcs.Address, name: RolesKeyBcs, value: bcs.vector(ArtistRoleBcs) });
|
|
73
91
|
|
|
74
92
|
/** The party's artist-type roles (display names), or `[]` if none are set. */
|
|
75
|
-
export
|
|
76
|
-
client: ClientWithCoreApi,
|
|
93
|
+
export const getRoles = Effect.fn("getRoles")(function* (
|
|
77
94
|
partyId: string,
|
|
78
95
|
partyRolesPackageId: string,
|
|
79
|
-
):
|
|
96
|
+
): Effect.fn.Return<string[], SuiRpcError, SuiClient> {
|
|
80
97
|
const fieldId = deriveDynamicFieldID(partyId, `${partyRolesPackageId}::party_roles::RolesKey`, ROLES_KEY_BYTES);
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
83
|
-
return mapRoles(RolesField.parse(content).value);
|
|
84
|
-
}
|
|
98
|
+
const contentOpt = yield* getOptionalObjectContent(fieldId);
|
|
99
|
+
if (Option.isNone(contentOpt)) return [];
|
|
100
|
+
return mapRoles(RolesField.parse(contentOpt.value.content).value);
|
|
101
|
+
});
|
|
85
102
|
|
|
86
103
|
const TAGS_KEY_BYTES = TagsKeyBcs.serialize([false]).toBytes();
|
|
87
104
|
const TagsField = bcs.struct("Field", { id: bcs.Address, name: TagsKeyBcs, value: bcs.vector(bcs.string()) });
|
|
88
105
|
|
|
89
106
|
/** The party's free-form tags, or `[]` if none are set. */
|
|
90
|
-
export
|
|
91
|
-
client: ClientWithCoreApi,
|
|
107
|
+
export const getTags = Effect.fn("getTags")(function* (
|
|
92
108
|
partyId: string,
|
|
93
109
|
partyTagsPackageId: string,
|
|
94
|
-
):
|
|
110
|
+
): Effect.fn.Return<string[], SuiRpcError, SuiClient> {
|
|
95
111
|
const fieldId = deriveDynamicFieldID(partyId, `${partyTagsPackageId}::party_tags::TagsKey`, TAGS_KEY_BYTES);
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
98
|
-
return mapTags(TagsField.parse(content).value);
|
|
99
|
-
}
|
|
112
|
+
const contentOpt = yield* getOptionalObjectContent(fieldId);
|
|
113
|
+
if (Option.isNone(contentOpt)) return [];
|
|
114
|
+
return mapTags(TagsField.parse(contentOpt.value.content).value);
|
|
115
|
+
});
|
|
100
116
|
|
|
101
117
|
const GENRES_KEY_BYTES = GenresKeyBcs.serialize([false]).toBytes();
|
|
102
118
|
const GenresField = bcs.struct("Field", { id: bcs.Address, name: GenresKeyBcs, value: bcs.vector(bcs.Address) });
|
|
103
119
|
|
|
104
120
|
/** The party's genre object ids, or `[]` if none are set. */
|
|
105
|
-
export
|
|
106
|
-
client: ClientWithCoreApi,
|
|
121
|
+
export const getGenres = Effect.fn("getGenres")(function* (
|
|
107
122
|
partyId: string,
|
|
108
123
|
partyGenrePackageId: string,
|
|
109
|
-
):
|
|
124
|
+
): Effect.fn.Return<string[], SuiRpcError, SuiClient> {
|
|
110
125
|
const fieldId = deriveDynamicFieldID(partyId, `${partyGenrePackageId}::party_genre::GenresKey`, GENRES_KEY_BYTES);
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
return mapGenres(GenresField.parse(content).value);
|
|
114
|
-
}
|
|
126
|
+
const contentOpt = yield* getOptionalObjectContent(fieldId);
|
|
127
|
+
if (Option.isNone(contentOpt)) return [];
|
|
128
|
+
return mapGenres(GenresField.parse(contentOpt.value.content).value);
|
|
129
|
+
});
|
|
115
130
|
|
|
116
131
|
const CTAS_KEY_BYTES = CtasKeyBcs.serialize([false]).toBytes();
|
|
117
132
|
// The CTA field's value is a bare `vector<Cta>` (not a wrapper struct).
|
|
118
133
|
const CtasField = bcs.struct("Field", { id: bcs.Address, name: CtasKeyBcs, value: bcs.vector(CtaBcs) });
|
|
119
134
|
|
|
120
135
|
/** The party's ordered CTA list (position is priority), or `[]` if none is set. */
|
|
121
|
-
export
|
|
122
|
-
client: ClientWithCoreApi,
|
|
136
|
+
export const getCtas = Effect.fn("getCtas")(function* (
|
|
123
137
|
partyId: string,
|
|
124
138
|
partyCtaPackageId: string,
|
|
125
|
-
):
|
|
139
|
+
): Effect.fn.Return<Cta[], SuiRpcError, SuiClient> {
|
|
126
140
|
const fieldId = deriveDynamicFieldID(partyId, `${partyCtaPackageId}::party_cta::CtasKey`, CTAS_KEY_BYTES);
|
|
127
|
-
const
|
|
128
|
-
if (
|
|
129
|
-
return mapCtas(CtasField.parse(content).value);
|
|
130
|
-
}
|
|
141
|
+
const contentOpt = yield* getOptionalObjectContent(fieldId);
|
|
142
|
+
if (Option.isNone(contentOpt)) return [];
|
|
143
|
+
return mapCtas(CtasField.parse(contentOpt.value.content).value).map((c) => new Cta(c));
|
|
144
|
+
});
|
|
131
145
|
|
|
132
146
|
// === Platform-link reads ===
|
|
133
147
|
//
|
|
@@ -141,23 +155,21 @@ const PlatformLinkField = bcs.struct("Field", { id: bcs.Address, name: bcs.bool(
|
|
|
141
155
|
const PLATFORM_LINK_KEY_RE = /::platform_link::PlatformLinkKey<(.+)>$/;
|
|
142
156
|
|
|
143
157
|
/** All external-platform links attached to a party (social, music, professional). */
|
|
144
|
-
export
|
|
158
|
+
export const getLinks = Effect.fn("getLinks")(function* (
|
|
159
|
+
partyId: string,
|
|
160
|
+
): Effect.fn.Return<PlatformLink[], SuiRpcError, SuiClient> {
|
|
161
|
+
const fields = Array.from(yield* Stream.runCollect(listDynamicFields(partyId)));
|
|
145
162
|
const links: PlatformLink[] = [];
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (!content) continue;
|
|
158
|
-
links.push(buildLink(platform, PlatformLinkField.parse(content).value));
|
|
159
|
-
}
|
|
160
|
-
cursor = page.hasNextPage ? page.cursor : null;
|
|
161
|
-
} while (cursor);
|
|
163
|
+
for (const f of fields) {
|
|
164
|
+
if (typeof f.name?.type !== "string") continue;
|
|
165
|
+
const match = f.name.type.match(PLATFORM_LINK_KEY_RE);
|
|
166
|
+
const dataType = match?.[1];
|
|
167
|
+
if (!dataType) continue;
|
|
168
|
+
const platform = platformForDataType(dataType);
|
|
169
|
+
if (!platform) continue;
|
|
170
|
+
const contentOpt = yield* getOptionalObjectContent(f.fieldId);
|
|
171
|
+
if (Option.isNone(contentOpt)) continue;
|
|
172
|
+
links.push(buildLink(platform, PlatformLinkField.parse(contentOpt.value.content).value));
|
|
173
|
+
}
|
|
162
174
|
return links;
|
|
163
|
-
}
|
|
175
|
+
});
|
package/src/party/types.ts
CHANGED
|
@@ -6,79 +6,83 @@
|
|
|
6
6
|
// these. The Party object model itself (`Party`, `PartyKind`) is owned by
|
|
7
7
|
// `@misofm/partyos` — import it from there.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
import { Schema } from "effect";
|
|
10
|
+
|
|
11
|
+
export class Profile extends Schema.Class<Profile>("@misofm/platform/Profile")({
|
|
12
|
+
partyId: Schema.String,
|
|
13
|
+
bioShort: Schema.String,
|
|
14
|
+
bioLong: Schema.optional(Schema.String),
|
|
13
15
|
/** ISO 3166-1 alpha-2 country code, e.g. "GB". */
|
|
14
|
-
country
|
|
16
|
+
country: Schema.optional(Schema.String),
|
|
15
17
|
/** ISO 639-1 language codes, e.g. ["en"]. */
|
|
16
|
-
languages:
|
|
17
|
-
}
|
|
18
|
+
languages: Schema.Array(Schema.String),
|
|
19
|
+
}) {}
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* A party's imagery, stored on-chain as a single Walrus quilt. Individual images
|
|
21
23
|
* (avatar, header, …) are quilt patches addressed by identifier off-chain — see
|
|
22
24
|
* `mediaUrls` / `MEDIA_IDENTIFIERS` in `extensions/media`.
|
|
23
25
|
*/
|
|
24
|
-
export
|
|
25
|
-
partyId:
|
|
26
|
+
export class Media extends Schema.Class<Media>("@misofm/platform/Media")({
|
|
27
|
+
partyId: Schema.String,
|
|
26
28
|
/** Walrus quilt blob id (base64url) holding the party's images. */
|
|
27
|
-
quiltId:
|
|
28
|
-
}
|
|
29
|
+
quiltId: Schema.String,
|
|
30
|
+
}) {}
|
|
29
31
|
|
|
30
32
|
/** A single call-to-action on a party: a labeled external link. Position is priority. */
|
|
31
|
-
export
|
|
32
|
-
label:
|
|
33
|
-
url:
|
|
34
|
-
}
|
|
33
|
+
export class Cta extends Schema.Class<Cta>("@misofm/platform/Cta")({
|
|
34
|
+
label: Schema.String,
|
|
35
|
+
url: Schema.String,
|
|
36
|
+
}) {}
|
|
35
37
|
|
|
36
38
|
/**
|
|
37
39
|
* A platform this SDK knows how to build a link for. One key per external
|
|
38
40
|
* platform, spanning social (`x`, `instagram`, …), music (`spotify`, `bandcamp`,
|
|
39
41
|
* …), and professional/industry (`website`, `patreon`, …) payloads.
|
|
40
42
|
*/
|
|
41
|
-
export
|
|
43
|
+
export const PlatformKey = Schema.Literals([
|
|
42
44
|
// Social (party_social)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
"x",
|
|
46
|
+
"instagram",
|
|
47
|
+
"threads",
|
|
48
|
+
"tiktok",
|
|
49
|
+
"youtube",
|
|
50
|
+
"discord",
|
|
51
|
+
"telegram",
|
|
52
|
+
"reddit",
|
|
53
|
+
"twitch",
|
|
54
|
+
"facebook",
|
|
53
55
|
// Music (party_music)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
"spotify",
|
|
57
|
+
"bandcamp",
|
|
58
|
+
"soundcloud",
|
|
59
|
+
"appleMusic",
|
|
60
|
+
"deezer",
|
|
61
|
+
"tidal",
|
|
62
|
+
"amazonMusic",
|
|
63
|
+
"audiomack",
|
|
62
64
|
// Professional / industry (party_pro_link)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
"website",
|
|
66
|
+
"bookingPage",
|
|
67
|
+
"managementPage",
|
|
68
|
+
"publisherPage",
|
|
69
|
+
"labelPage",
|
|
70
|
+
"epk",
|
|
71
|
+
"patreon",
|
|
72
|
+
"substack",
|
|
73
|
+
"kofi",
|
|
74
|
+
]);
|
|
75
|
+
export type PlatformKey = typeof PlatformKey.Type;
|
|
72
76
|
|
|
73
77
|
/**
|
|
74
78
|
* One external-platform link attached to a party. Only the native identifier
|
|
75
79
|
* (`value` — a handle, artist id, subdomain, or full URL) is stored on-chain; the
|
|
76
80
|
* public `url` is rebuilt client-side from it.
|
|
77
81
|
*/
|
|
78
|
-
export
|
|
79
|
-
platform: PlatformKey
|
|
82
|
+
export class PlatformLink extends Schema.Class<PlatformLink>("@misofm/platform/PlatformLink")({
|
|
83
|
+
platform: PlatformKey,
|
|
80
84
|
/** The platform-native identifier stored on-chain (handle / id / subdomain / URL). */
|
|
81
|
-
value:
|
|
85
|
+
value: Schema.String,
|
|
82
86
|
/** The public profile URL, rebuilt from `value`. */
|
|
83
|
-
url:
|
|
84
|
-
}
|
|
87
|
+
url: Schema.String,
|
|
88
|
+
}) {}
|