@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/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 async function getReleaseGenres(
338
- client: ClientWithCoreApi,
364
+ export const getReleaseGenres = Effect.fn("getReleaseGenres")(function* (
339
365
  releaseId: string,
340
366
  releaseGenrePackageId: string,
341
- ): Promise<string[]> {
342
- const { objects } = await client.core.getObjects({
343
- objectIds: [releaseGenresFieldId(releaseId, releaseGenrePackageId)],
344
- include: { content: true },
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 async function getRecordingGenres(
358
- client: ClientWithCoreApi,
378
+ export const getRecordingGenres = Effect.fn("getRecordingGenres")(function* (
359
379
  recordingId: string,
360
380
  recordingGenrePackageId: string,
361
- ): Promise<string[]> {
362
- const { objects } = await client.core.getObjects({
363
- objectIds: [recordingGenresFieldId(recordingId, recordingGenrePackageId)],
364
- include: { content: true },
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
+ });
@@ -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
- async getPartyById(partyId: string): Promise<Party> {
113
+ getPartyById(partyId: string): Effect.Effect<Party, ObjectNotFoundError | ObjectTypeMismatchError | BcsDecodeError | SuiRpcError> {
104
114
  return this.#core.getPartyById(partyId);
105
115
  }
106
- async getPartiesByIds(partyIds: readonly string[]): Promise<Partial<Record<string, Party>>> {
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
- async getMemberships(partyId: string): Promise<string[]> {
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
- async getPendingInvites(groupId: string): Promise<string[]> {
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
- async getPendingMemberships(partyId: string): Promise<string[]> {
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
- async isMember(memberId: string, groupId: string): Promise<boolean> {
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
- async getProfile(partyId: string): Promise<Profile | null> {
132
- return queries.getProfile(this.#client, partyId, this.#profilePkg);
143
+ getProfile(partyId: string): Effect.Effect<Option.Option<Profile>, BcsDecodeError | SuiRpcError> {
144
+ return this.#provide(queries.getProfile(partyId, this.#profilePkg));
133
145
  }
134
- async getMedia(partyId: string): Promise<Media | null> {
135
- return queries.getMedia(this.#client, partyId, this.#mediaPkg);
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
- async getRoles(partyId: string): Promise<string[]> {
139
- return queries.getRoles(this.#client, partyId, this.#rolesPkg);
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
- async getTags(partyId: string): Promise<string[]> {
143
- return queries.getTags(this.#client, partyId, this.#tagsPkg);
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
- async getGenres(partyId: string): Promise<string[]> {
147
- return queries.getGenres(this.#client, partyId, this.#partyGenrePkg);
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
- async getCtas(partyId: string): Promise<Cta[]> {
151
- return queries.getCtas(this.#client, partyId, this.#ctaPkg);
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
- async getLinks(partyId: string): Promise<PlatformLink[]> {
155
- return queries.getLinks(this.#client, partyId);
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 type { PlatformKey, PlatformLink } from "../types.ts";
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) ===
@@ -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 type { ClientWithCoreApi } from "@mysten/sui/client";
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 { getObjectContent } from "@misofm/partyos";
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 type { Cta, Media, PlatformLink, Profile } from "./types.ts";
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, or null if none is set. */
30
- export async function getProfile(
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
- ): Promise<Profile | null> {
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 content = await getObjectContent(client, fieldId);
41
- if (!content) return null;
42
- return mapProfile(partyId, ProfileField.parse(content).value);
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), or null if none is set. */
51
- export async function getMedia(
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
- ): Promise<Media | null> {
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 content = await getObjectContent(client, fieldId);
62
- if (!content) return null;
63
- return mapMedia(partyId, MediaField.parse(content).value);
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 async function getRoles(
76
- client: ClientWithCoreApi,
93
+ export const getRoles = Effect.fn("getRoles")(function* (
77
94
  partyId: string,
78
95
  partyRolesPackageId: string,
79
- ): Promise<string[]> {
96
+ ): Effect.fn.Return<string[], SuiRpcError, SuiClient> {
80
97
  const fieldId = deriveDynamicFieldID(partyId, `${partyRolesPackageId}::party_roles::RolesKey`, ROLES_KEY_BYTES);
81
- const content = await getObjectContent(client, fieldId);
82
- if (!content) return [];
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 async function getTags(
91
- client: ClientWithCoreApi,
107
+ export const getTags = Effect.fn("getTags")(function* (
92
108
  partyId: string,
93
109
  partyTagsPackageId: string,
94
- ): Promise<string[]> {
110
+ ): Effect.fn.Return<string[], SuiRpcError, SuiClient> {
95
111
  const fieldId = deriveDynamicFieldID(partyId, `${partyTagsPackageId}::party_tags::TagsKey`, TAGS_KEY_BYTES);
96
- const content = await getObjectContent(client, fieldId);
97
- if (!content) return [];
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 async function getGenres(
106
- client: ClientWithCoreApi,
121
+ export const getGenres = Effect.fn("getGenres")(function* (
107
122
  partyId: string,
108
123
  partyGenrePackageId: string,
109
- ): Promise<string[]> {
124
+ ): Effect.fn.Return<string[], SuiRpcError, SuiClient> {
110
125
  const fieldId = deriveDynamicFieldID(partyId, `${partyGenrePackageId}::party_genre::GenresKey`, GENRES_KEY_BYTES);
111
- const content = await getObjectContent(client, fieldId);
112
- if (!content) return [];
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 async function getCtas(
122
- client: ClientWithCoreApi,
136
+ export const getCtas = Effect.fn("getCtas")(function* (
123
137
  partyId: string,
124
138
  partyCtaPackageId: string,
125
- ): Promise<Cta[]> {
139
+ ): Effect.fn.Return<Cta[], SuiRpcError, SuiClient> {
126
140
  const fieldId = deriveDynamicFieldID(partyId, `${partyCtaPackageId}::party_cta::CtasKey`, CTAS_KEY_BYTES);
127
- const content = await getObjectContent(client, fieldId);
128
- if (!content) return [];
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 async function getLinks(client: ClientWithCoreApi, partyId: string): Promise<PlatformLink[]> {
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
- let cursor: string | null | undefined;
147
- do {
148
- const page = await client.core.listDynamicFields({ parentId: partyId, cursor: cursor ?? undefined });
149
- for (const f of page.dynamicFields) {
150
- if (typeof f.name?.type !== "string") continue;
151
- const match = f.name.type.match(PLATFORM_LINK_KEY_RE);
152
- const dataType = match?.[1];
153
- if (!dataType) continue;
154
- const platform = platformForDataType(dataType);
155
- if (!platform) continue;
156
- const content = await getObjectContent(client, f.fieldId);
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
+ });
@@ -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
- export interface Profile {
10
- partyId: string;
11
- bioShort: string;
12
- bioLong?: string;
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?: string;
16
+ country: Schema.optional(Schema.String),
15
17
  /** ISO 639-1 language codes, e.g. ["en"]. */
16
- languages: string[];
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 interface Media {
25
- partyId: string;
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: string;
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 interface Cta {
32
- label: string;
33
- url: string;
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 type PlatformKey =
43
+ export const PlatformKey = Schema.Literals([
42
44
  // Social (party_social)
43
- | "x"
44
- | "instagram"
45
- | "threads"
46
- | "tiktok"
47
- | "youtube"
48
- | "discord"
49
- | "telegram"
50
- | "reddit"
51
- | "twitch"
52
- | "facebook"
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
- | "spotify"
55
- | "bandcamp"
56
- | "soundcloud"
57
- | "appleMusic"
58
- | "deezer"
59
- | "tidal"
60
- | "amazonMusic"
61
- | "audiomack"
56
+ "spotify",
57
+ "bandcamp",
58
+ "soundcloud",
59
+ "appleMusic",
60
+ "deezer",
61
+ "tidal",
62
+ "amazonMusic",
63
+ "audiomack",
62
64
  // Professional / industry (party_pro_link)
63
- | "website"
64
- | "bookingPage"
65
- | "managementPage"
66
- | "publisherPage"
67
- | "labelPage"
68
- | "epk"
69
- | "patreon"
70
- | "substack"
71
- | "kofi";
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 interface PlatformLink {
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: string;
85
+ value: Schema.String,
82
86
  /** The public profile URL, rebuilt from `value`. */
83
- url: string;
84
- }
87
+ url: Schema.String,
88
+ }) {}