@misofm/musicos 0.2.0 → 0.3.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.
Files changed (50) hide show
  1. package/README.md +90 -13
  2. package/dist/client.d.ts +27 -26
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +58 -51
  5. package/dist/client.js.map +1 -1
  6. package/dist/deployments.d.ts +9 -0
  7. package/dist/deployments.d.ts.map +1 -1
  8. package/dist/deployments.js +14 -0
  9. package/dist/deployments.js.map +1 -1
  10. package/dist/errors.d.ts +10 -0
  11. package/dist/errors.d.ts.map +1 -0
  12. package/dist/errors.js +17 -0
  13. package/dist/errors.js.map +1 -0
  14. package/dist/events.d.ts +6 -1
  15. package/dist/events.d.ts.map +1 -1
  16. package/dist/events.js +0 -5
  17. package/dist/events.js.map +1 -1
  18. package/dist/execute.d.ts +1 -48
  19. package/dist/execute.d.ts.map +1 -1
  20. package/dist/execute.js +5 -106
  21. package/dist/execute.js.map +1 -1
  22. package/dist/internal.d.ts +45 -6
  23. package/dist/internal.d.ts.map +1 -1
  24. package/dist/internal.js.map +1 -1
  25. package/dist/queries.d.ts +38 -62
  26. package/dist/queries.d.ts.map +1 -1
  27. package/dist/queries.js +240 -266
  28. package/dist/queries.js.map +1 -1
  29. package/dist/transactions.d.ts +2 -1
  30. package/dist/transactions.d.ts.map +1 -1
  31. package/dist/transactions.js.map +1 -1
  32. package/dist/types.d.ts +129 -79
  33. package/dist/types.d.ts.map +1 -1
  34. package/dist/types.js +169 -1
  35. package/dist/types.js.map +1 -1
  36. package/dist/view.d.ts +3 -2
  37. package/dist/view.d.ts.map +1 -1
  38. package/dist/view.js +19 -6
  39. package/dist/view.js.map +1 -1
  40. package/package.json +8 -1
  41. package/src/client.ts +78 -98
  42. package/src/deployments.ts +15 -0
  43. package/src/errors.ts +30 -0
  44. package/src/events.ts +1 -1
  45. package/src/execute.ts +5 -133
  46. package/src/internal.ts +17 -23
  47. package/src/queries.ts +386 -424
  48. package/src/transactions.ts +2 -1
  49. package/src/types.ts +59 -48
  50. package/src/view.ts +23 -9
package/dist/queries.js CHANGED
@@ -1,11 +1,27 @@
1
1
  // Copyright (c) Miso Labs, Inc.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
+ // Object reads. Single-object fetches use the Core API with `include: content`
4
+ // and parse the BCS contents through the codegen-generated structs (so parsing
5
+ // tracks the on-chain ABI). Generic-type discovery (by share type / by owner)
6
+ // uses GraphQL to find object addresses, then reads them through the Core path.
7
+ //
8
+ // Every read requires the `SuiClient` service (and `SuiGraphQL` for the
9
+ // type-discovery reads) instead of taking a client parameter — see
10
+ // `@misofm/effect`. Not-found is a typed `ObjectNotFoundError`; a BCS decode
11
+ // failure (including a wrong on-chain type, which fails to parse against the
12
+ // expected ABI) is a typed `BcsDecodeError`. Extension dynamic-field readers
13
+ // return `Option.none()` for "not attached" — extension data is optional by
14
+ // design, and absence is a normal, expected state, not a failure.
15
+ import { ConflictingWorkKindError } from "./errors.js";
16
+ import { Effect, Option, Schema } from "effect";
3
17
  import { graphql } from "@mysten/sui/graphql/schema";
4
18
  import { deriveObjectID, normalizeSuiAddress } from "@mysten/sui/utils";
19
+ import { BcsDecodeError, decodeBcs, getObjectContent, getObjectsContent, ObjectNotFoundError, SuiClient, SuiGraphQL, SuiRpcError, } from "@misofm/effect";
5
20
  import { Composition as CompositionBcs } from "./contracts/musicos/composition.js";
6
21
  import { Recording as RecordingBcs } from "./contracts/musicos/recording.js";
7
22
  import { Release as ReleaseBcs, ReleaseRegistry as ReleaseRegistryBcs, } from "./contracts/musicos/release.js";
8
- import { mapBps, mapComposition, mapRecording, mapRelease, } from "./internal.js";
23
+ import { mapComposition, mapRecording, mapRelease, } from "./internal.js";
24
+ import { Composition, CompositionAdminCap, Recording, RecordingAdminCap, Release, ReleaseAdminCap, ReleaseRegistry, } from "./types.js";
9
25
  // ============================================================================
10
26
  // Helpers
11
27
  // ============================================================================
@@ -35,33 +51,26 @@ export function extractTypeParams2(objectType) {
35
51
  }
36
52
  throw new Error(`Expected two type parameters in: ${objectType}`);
37
53
  }
54
+ /** Key bytes for Move unit structs (single `0x00` for `dummy_field: bool = false`). */
55
+ const UNIT_STRUCT_KEY_BYTES = new Uint8Array([0x00]);
38
56
  /**
39
- * True when `e` is a "this object does not exist" error from any of the Sui
40
- * client transports, so null-returning readers can distinguish absence from
41
- * transport failure. Matches, in order of preference:
57
+ * True when `e` is a "this object/dynamic field does not exist" error from any
58
+ * of the Sui client transports. This mirrors `@misofm/effect`'s internal
59
+ * classifier (not exported, since `getObjectContent` already covers the common
60
+ * case) — needed here for the reads that don't go through it: type-only object
61
+ * reads (no `content`) and dynamic-field reads. Matches, in order of
62
+ * preference:
42
63
  *
43
64
  * 1. Structured `ObjectError.code` values thrown by the JSON-RPC core client
44
65
  * (`notExists`, `deleted`, `dynamicFieldNotFound`) and the GraphQL core
45
- * client (`notFound`). The class itself is not exported by `@mysten/sui`,
46
- * so we duck-type on `code`.
66
+ * client (`notFound`).
47
67
  * 2. The message shapes those clients (and the gRPC core client, which wraps
48
- * the server's per-object status message in a plain `Error`) produce:
49
- * "Object 0x… does not exist" / "Object 0x… not found" / "Object 0x… has
50
- * been deleted" / "Dynamic field not found for object 0x…" / "No object
51
- * found for id 0x…".
52
- *
53
- * Transport/protocol errors must NOT match: every message pattern requires
54
- * object-ish context ("object" / "dynamic field"), so e.g. a JSON-RPC
55
- * "Method not found" or a gRPC "peer not found" is never treated as a missing
56
- * object and propagates to the caller.
68
+ * the server's per-object status message in a plain `Error`) produce.
57
69
  */
58
- export function isNotFound(e) {
70
+ function isMissingObjectError(e) {
59
71
  if (typeof e === "object" && e !== null && "code" in e) {
60
72
  const code = e.code;
61
- if (code === "notExists" ||
62
- code === "deleted" ||
63
- code === "dynamicFieldNotFound" ||
64
- code === "notFound") {
73
+ if (code === "notExists" || code === "deleted" || code === "dynamicFieldNotFound" || code === "notFound") {
65
74
  return true;
66
75
  }
67
76
  }
@@ -70,14 +79,38 @@ export function isNotFound(e) {
70
79
  /\bdynamic field\b[\s\S]*\bnot\s?found\b/i.test(msg) ||
71
80
  /\bno object\b/i.test(msg));
72
81
  }
73
- /** Key bytes for Move unit structs (single `0x00` for `dummy_field: bool = false`). */
74
- const UNIT_STRUCT_KEY_BYTES = new Uint8Array([0x00]);
82
+ /** Classifies a Core API rejection against `objectId`: missing -> `ObjectNotFoundError`, else `SuiRpcError`. */
83
+ function classifyMissing(objectId, operation, cause) {
84
+ return isMissingObjectError(cause) ? new ObjectNotFoundError({ objectId }) : new SuiRpcError({ operation, cause });
85
+ }
86
+ /** Fetches just an object's on-chain `type` (no content) through the Core API. */
87
+ const getObjectType = Effect.fn("getObjectType")(function* (objectId) {
88
+ const client = yield* SuiClient;
89
+ const { object } = yield* Effect.tryPromise({
90
+ try: (signal) => client.core.getObject({ objectId, signal }),
91
+ catch: (cause) => classifyMissing(objectId, "getObject", cause),
92
+ });
93
+ return object.type;
94
+ });
95
+ /** Fetches one dynamic field's raw BCS value bytes; not-found becomes `ObjectNotFoundError`. */
96
+ const getDynamicFieldValue = Effect.fn("getDynamicFieldValue")(function* (parentId, name) {
97
+ const client = yield* SuiClient;
98
+ const { dynamicField } = yield* Effect.tryPromise({
99
+ try: (signal) => client.core.getDynamicField({ parentId, name, signal }),
100
+ catch: (cause) => classifyMissing(parentId, "getDynamicField", cause),
101
+ });
102
+ return dynamicField.value.bcs;
103
+ });
75
104
  /** Exhaust every Core owned-object page; cap discovery must not truncate. */
76
- async function listAllOwnedObjects(client, input) {
105
+ const listAllOwnedObjects = Effect.fn("listAllOwnedObjects")(function* (input) {
106
+ const client = yield* SuiClient;
77
107
  const objects = [];
78
108
  let cursor;
79
109
  do {
80
- const page = await client.core.listOwnedObjects(cursor ? { ...input, cursor } : input);
110
+ const page = yield* Effect.tryPromise({
111
+ try: (signal) => client.core.listOwnedObjects(cursor ? { ...input, cursor, signal } : { ...input, signal }),
112
+ catch: (cause) => new SuiRpcError({ operation: "listOwnedObjects", cause }),
113
+ });
81
114
  objects.push(...page.objects);
82
115
  cursor = page.pageInfo?.hasNextPage
83
116
  ? page.pageInfo.endCursor
@@ -86,81 +119,50 @@ async function listAllOwnedObjects(client, input) {
86
119
  : null;
87
120
  } while (cursor);
88
121
  return objects;
89
- }
90
- /** Fetches one object's BCS content bytes (or null if absent). */
91
- async function getContent(client, objectId) {
92
- const { object } = await client.core.getObject({
93
- objectId,
94
- include: { content: true },
95
- });
96
- return object.content ?? null;
97
- }
122
+ });
98
123
  /**
99
- * Fetch and parse an object through the transport-neutral Core API. Object
100
- * content is BCS; never pass the full `objectBcs` envelope to a Move codec.
124
+ * Fetch and parse an object through the transport-neutral Core API, validating
125
+ * the parsed result against a domain `Schema`. Object content is BCS; never
126
+ * pass the full `objectBcs` envelope to a Move codec.
101
127
  */
102
- export async function getObjectByBcs(client, objectId, codec) {
103
- const content = await getContent(client, objectId);
104
- if (!content)
105
- throw new Error(`Object not found: ${objectId}`);
106
- return codec.parse(content);
107
- }
128
+ export const getObjectByBcs = Effect.fn("getObjectByBcs")(function* (objectId, codec, schema, typeName) {
129
+ const { content } = yield* getObjectContent(objectId);
130
+ return yield* decodeBcs(codec, schema, content, { type: typeName, objectId });
131
+ });
108
132
  /**
109
133
  * Read an optional first-party extension field from a core object's UID. Every
110
134
  * current extension uses a fieldless `ExtensionKey`, whose BCS is one false
111
- * boolean byte. Absence returns `null`; transport errors still propagate.
135
+ * boolean byte. Absence resolves to `Option.none()`; transport errors still fail.
112
136
  */
113
- export async function getExtensionField(client, parentId, params) {
114
- try {
115
- const { dynamicField } = await client.core.getDynamicField({
116
- parentId,
117
- name: {
118
- type: `${params.packageId}::${params.module}::ExtensionKey`,
119
- bcs: UNIT_STRUCT_KEY_BYTES,
120
- },
121
- });
122
- return params.codec.parse(dynamicField.value.bcs);
123
- }
124
- catch (error) {
125
- if (isNotFound(error))
126
- return null;
127
- throw error;
128
- }
129
- }
130
- async function getReleaseDspField(client, releaseId, key, params) {
137
+ export const getExtensionField = Effect.fn("getExtensionField")(function* (parentId, params) {
138
+ return yield* getDynamicFieldValue(parentId, {
139
+ type: `${params.packageId}::${params.module}::ExtensionKey`,
140
+ bcs: UNIT_STRUCT_KEY_BYTES,
141
+ }).pipe(Effect.map((bytes) => Option.some(params.codec.parse(bytes))), Effect.catchTag("ObjectNotFoundError", () => Effect.succeed(Option.none())));
142
+ });
143
+ const getReleaseDspField = Effect.fn("getReleaseDspField")(function* (releaseId, key, params) {
131
144
  if (!Number.isInteger(params.platform) || params.platform < 0 || params.platform > 255) {
132
145
  throw new Error("DSP platform must be a u8 discriminator");
133
146
  }
134
- try {
135
- const { dynamicField } = await client.core.getDynamicField({
136
- parentId: releaseId,
137
- name: {
138
- type: `${params.packageId}::release_dsp_link::${key}`,
139
- bcs: Uint8Array.of(params.platform),
140
- },
141
- });
142
- return params.codec.parse(dynamicField.value.bcs);
143
- }
144
- catch (error) {
145
- if (isNotFound(error))
146
- return null;
147
- throw error;
148
- }
149
- }
147
+ return yield* getDynamicFieldValue(releaseId, {
148
+ type: `${params.packageId}::release_dsp_link::${key}`,
149
+ bcs: Uint8Array.of(params.platform),
150
+ }).pipe(Effect.map((bytes) => Option.some(params.codec.parse(bytes))), Effect.catchTag("ObjectNotFoundError", () => Effect.succeed(Option.none())));
151
+ });
150
152
  /** Read a release-level DSP link stored under `ReleaseLinkKey(platform)`. */
151
- export function getReleaseDspLink(client, releaseId, params) {
152
- return getReleaseDspField(client, releaseId, "ReleaseLinkKey", params);
153
+ export function getReleaseDspLink(releaseId, params) {
154
+ return getReleaseDspField(releaseId, "ReleaseLinkKey", params);
153
155
  }
154
156
  /** Read the per-track DSP-link array stored under `TrackLinksKey(platform)`. */
155
- export function getTrackDspLinks(client, releaseId, params) {
156
- return getReleaseDspField(client, releaseId, "TrackLinksKey", params);
157
+ export function getTrackDspLinks(releaseId, params) {
158
+ return getReleaseDspField(releaseId, "TrackLinksKey", params);
157
159
  }
158
160
  // ============================================================================
159
161
  // Core registry and generic primitive reads
160
162
  // ============================================================================
161
163
  /** Parse the shared canonical core `miso::release::ReleaseRegistry` by ID. */
162
- export async function getReleaseRegistryById(client, registryId) {
163
- return getObjectByBcs(client, registryId, ReleaseRegistryBcs);
164
+ export function getReleaseRegistryById(registryId) {
165
+ return getObjectByBcs(registryId, ReleaseRegistryBcs, ReleaseRegistry, "ReleaseRegistry");
164
166
  }
165
167
  // ============================================================================
166
168
  // GraphQL discovery queries
@@ -208,12 +210,13 @@ const AddressesAndTypesByTypeQuery = graphql(`
208
210
  * exposes the first, so one bare Recording scan is shared by every requested
209
211
  * recording type and filtered client-side.
210
212
  */
211
- export async function getWorkAddressesByShareTypes(client, shareTypes, misoPackageId) {
213
+ export const getWorkAddressesByShareTypes = Effect.fn("getWorkAddressesByShareTypes")(function* (shareTypes, misoPackageId) {
212
214
  const compositions = [...new Set(shareTypes.compositions)];
213
215
  const recordings = new Set(shareTypes.recordings);
214
216
  const out = { compositions: {}, recordings: {} };
215
217
  if (compositions.length === 0 && recordings.size === 0)
216
218
  return out;
219
+ const client = yield* SuiGraphQL;
217
220
  const declarations = [];
218
221
  const selections = [];
219
222
  const variables = {};
@@ -221,8 +224,7 @@ export async function getWorkAddressesByShareTypes(client, shareTypes, misoPacka
221
224
  const variable = `compositionType${index}`;
222
225
  declarations.push(`$${variable}: String!`);
223
226
  selections.push(`composition${index}: objects(first: 1, filter: { type: $${variable} }) { nodes { address } }`);
224
- variables[variable] =
225
- `${misoPackageId}::composition::Composition<${shareType}>`;
227
+ variables[variable] = `${misoPackageId}::composition::Composition<${shareType}>`;
226
228
  });
227
229
  if (recordings.size > 0) {
228
230
  declarations.push("$recordingType: String!");
@@ -232,14 +234,20 @@ export async function getWorkAddressesByShareTypes(client, shareTypes, misoPacka
232
234
  }`);
233
235
  variables.recordingType = `${misoPackageId}::recording::Recording`;
234
236
  }
235
- const result = await client.query({
236
- query: `query WorkAddressesByShareTypes(${declarations.join(", ")}) {
237
- ${selections.join("\n")}
238
- }`,
239
- variables,
237
+ const result = yield* Effect.tryPromise({
238
+ try: () => client.query({
239
+ query: `query WorkAddressesByShareTypes(${declarations.join(", ")}) {
240
+ ${selections.join("\n")}
241
+ }`,
242
+ variables,
243
+ }),
244
+ catch: (cause) => new SuiRpcError({ operation: "workAddressesByShareTypes", cause }),
240
245
  });
241
246
  if (result.errors?.length) {
242
- throw new AggregateError(result.errors.map((error) => new Error(error.message)), "Work type discovery failed");
247
+ return yield* new SuiRpcError({
248
+ operation: "workAddressesByShareTypes",
249
+ cause: new AggregateError(result.errors.map((error) => new Error(error.message)), "Work type discovery failed"),
250
+ });
243
251
  }
244
252
  compositions.forEach((shareType, index) => {
245
253
  const address = result.data?.[`composition${index}`]?.nodes[0]?.address;
@@ -267,35 +275,39 @@ export async function getWorkAddressesByShareTypes(client, shareTypes, misoPacka
267
275
  while (recordingPage?.pageInfo?.hasNextPage &&
268
276
  recordingPage.pageInfo.endCursor &&
269
277
  Object.keys(out.recordings).length < recordings.size) {
270
- const next = await client.query({
271
- query: `query RecordingWorkAddresses($recordingType: String!, $cursor: String!) {
272
- recordings: objects(first: 50, after: $cursor, filter: { type: $recordingType }) {
273
- pageInfo { hasNextPage endCursor }
274
- nodes { address asMoveObject { contents { type { repr } } } }
275
- }
276
- }`,
277
- variables: {
278
- recordingType: variables.recordingType,
279
- cursor: recordingPage.pageInfo.endCursor,
280
- },
278
+ const cursor = recordingPage.pageInfo.endCursor;
279
+ const next = yield* Effect.tryPromise({
280
+ try: () => client.query({
281
+ query: `query RecordingWorkAddresses($recordingType: String!, $cursor: String!) {
282
+ recordings: objects(first: 50, after: $cursor, filter: { type: $recordingType }) {
283
+ pageInfo { hasNextPage endCursor }
284
+ nodes { address asMoveObject { contents { type { repr } } } }
285
+ }
286
+ }`,
287
+ variables: { recordingType: variables.recordingType, cursor },
288
+ }),
289
+ catch: (cause) => new SuiRpcError({ operation: "recordingWorkAddresses", cause }),
281
290
  });
282
291
  if (next.errors?.length) {
283
- throw new AggregateError(next.errors.map((error) => new Error(error.message)), "Recording type discovery failed");
292
+ return yield* new SuiRpcError({
293
+ operation: "recordingWorkAddresses",
294
+ cause: new AggregateError(next.errors.map((error) => new Error(error.message)), "Recording type discovery failed"),
295
+ });
284
296
  }
285
297
  recordingPage = next.data?.recordings;
286
298
  readRecordingPage(recordingPage);
287
299
  }
288
300
  return out;
289
- }
301
+ });
290
302
  /** Fetch and parse heterogeneous work objects through one Core bulk request. */
291
- export async function getWorksByIds(client, ids) {
303
+ export const getWorksByIds = Effect.fn("getWorksByIds")(function* (ids) {
292
304
  const kinds = new Map();
293
305
  for (const [kind, objectIds] of Object.entries(ids)) {
294
306
  for (const objectId of objectIds) {
295
307
  const normalized = normalizeSuiAddress(objectId);
296
308
  const previous = kinds.get(normalized);
297
309
  if (previous && previous !== kind) {
298
- throw new Error(`Work ${normalized} was requested as both ${previous} and ${kind}`);
310
+ return yield* new ConflictingWorkKindError({ objectId: normalized, kinds: [previous, kind] });
299
311
  }
300
312
  kinds.set(normalized, kind);
301
313
  }
@@ -303,64 +315,49 @@ export async function getWorksByIds(client, ids) {
303
315
  const out = { compositions: {}, recordings: {}, releases: {} };
304
316
  if (kinds.size === 0)
305
317
  return out;
306
- const { objects } = await client.core.getObjects({
307
- objectIds: [...kinds.keys()],
308
- include: { content: true },
309
- });
310
- for (const obj of objects) {
311
- if (obj instanceof Error || !obj.content)
312
- continue;
313
- const kind = kinds.get(normalizeSuiAddress(obj.objectId));
318
+ const contents = yield* getObjectsContent([...kinds.keys()]);
319
+ yield* Effect.forEach([...contents.entries()], ([objectId, { content }]) => Effect.gen(function* () {
320
+ const kind = kinds.get(normalizeSuiAddress(objectId));
314
321
  if (kind === "compositions") {
315
- out.compositions[obj.objectId] = mapComposition(obj.objectId, CompositionBcs.parse(obj.content));
322
+ out.compositions[objectId] = yield* decodeBcs({ parse: (bytes) => mapComposition(objectId, CompositionBcs.parse(bytes)) }, Composition, content, { type: "Composition", objectId });
316
323
  }
317
324
  else if (kind === "recordings") {
318
- out.recordings[obj.objectId] = mapRecording(obj.objectId, RecordingBcs.parse(obj.content));
325
+ out.recordings[objectId] = yield* decodeBcs({ parse: (bytes) => mapRecording(objectId, RecordingBcs.parse(bytes)) }, Recording, content, { type: "Recording", objectId });
319
326
  }
320
327
  else if (kind === "releases") {
321
- out.releases[obj.objectId] = mapRelease(obj.objectId, ReleaseBcs.parse(obj.content));
328
+ out.releases[objectId] = yield* decodeBcs({ parse: (bytes) => mapRelease(objectId, ReleaseBcs.parse(bytes)) }, Release, content, { type: "Release", objectId });
322
329
  }
323
- }
330
+ }), { concurrency: "unbounded" });
324
331
  return out;
325
- }
332
+ });
326
333
  // ============================================================================
327
334
  // Composition
328
335
  // ============================================================================
329
336
  /** Fetches multiple compositions by ID in one Core request. */
330
- export async function getCompositionsByIds(client, compositionIds) {
337
+ export const getCompositionsByIds = Effect.fn("getCompositionsByIds")(function* (compositionIds) {
331
338
  if (compositionIds.length === 0)
332
339
  return {};
333
- const { objects } = await client.core.getObjects({
334
- objectIds: compositionIds,
335
- include: { content: true },
336
- });
337
- const out = {};
338
- for (const obj of objects) {
339
- if (obj instanceof Error || !obj.content)
340
- continue;
341
- out[obj.objectId] = mapComposition(obj.objectId, CompositionBcs.parse(obj.content));
342
- }
343
- return out;
344
- }
340
+ const contents = yield* getObjectsContent(compositionIds);
341
+ const decoded = yield* Effect.forEach([...contents.entries()], ([objectId, { content }]) => decodeBcs({ parse: (bytes) => mapComposition(objectId, CompositionBcs.parse(bytes)) }, Composition, content, { type: "Composition", objectId }).pipe(Effect.map((composition) => [objectId, composition])), { concurrency: "unbounded" });
342
+ return Object.fromEntries(decoded);
343
+ });
345
344
  /** Fetches a composition by its object ID. */
346
- export async function getCompositionById(client, compositionId) {
347
- const content = await getContent(client, compositionId);
348
- if (!content)
349
- throw new Error(`Composition not found: ${compositionId}`);
350
- return mapComposition(compositionId, CompositionBcs.parse(content));
345
+ export function getCompositionById(compositionId) {
346
+ return getObjectByBcs(compositionId, { parse: (bytes) => mapComposition(compositionId, CompositionBcs.parse(bytes)) }, Composition, "Composition");
351
347
  }
352
348
  /** Extracts the share type `T` from a `Composition<T>` object. */
353
- export async function getCompositionShareType(client, compositionId) {
354
- const { object } = await client.core.getObject({ objectId: compositionId });
355
- return extractTypeParam(object.type);
356
- }
349
+ export const getCompositionShareType = Effect.fn("getCompositionShareType")(function* (compositionId) {
350
+ const type = yield* getObjectType(compositionId);
351
+ return extractTypeParam(type);
352
+ });
357
353
  /** Fetches a composition by its share type (GraphQL discovery + Core read). */
358
- export async function getCompositionByShareType(client, graphqlClient, shareType, misoPackageId) {
359
- const address = await getCompositionAddressByShareType(graphqlClient, shareType, misoPackageId);
360
- if (!address)
361
- throw new Error(`Composition not found for share type: ${shareType}`);
362
- return getCompositionById(client, address);
363
- }
354
+ export const getCompositionByShareType = Effect.fn("getCompositionByShareType")(function* (shareType, misoPackageId) {
355
+ const address = yield* getCompositionAddressByShareType(shareType, misoPackageId);
356
+ if (Option.isNone(address)) {
357
+ return yield* new ObjectNotFoundError({ objectId: `composition::Composition<${shareType}>` });
358
+ }
359
+ return yield* getCompositionById(address.value);
360
+ });
364
361
  /**
365
362
  * Resolves a composition share type to its object address.
366
363
  *
@@ -368,14 +365,14 @@ export async function getCompositionByShareType(client, graphqlClient, shareType
368
365
  * composition's identity but will read extension fields rather than the core
369
366
  * Composition contents.
370
367
  */
371
- export async function getCompositionAddressByShareType(graphqlClient, shareType, misoPackageId) {
368
+ export function getCompositionAddressByShareType(shareType, misoPackageId) {
372
369
  const type = `${misoPackageId}::composition::Composition<${shareType}>`;
373
- return firstAddressOfType(graphqlClient, type);
374
- }
375
- export async function getCompositionAdminCapById(client, adminCapId) {
376
- const { object } = await client.core.getObject({ objectId: adminCapId });
377
- return { id: adminCapId, shareType: extractTypeParam(object.type) };
370
+ return firstAddressOfType(type);
378
371
  }
372
+ export const getCompositionAdminCapById = Effect.fn("getCompositionAdminCapById")(function* (adminCapId) {
373
+ const type = yield* getObjectType(adminCapId);
374
+ return new CompositionAdminCap({ id: adminCapId, shareType: extractTypeParam(type) });
375
+ });
379
376
  /**
380
377
  * Composition admin caps owned by `owner`.
381
378
  *
@@ -383,43 +380,32 @@ export async function getCompositionAdminCapById(client, adminCapId) {
383
380
  * object's instantiated `type`, so the share type is read straight off
384
381
  * `CompositionAdminCap<CompositionShare>` with no second round-trip.
385
382
  */
386
- export async function getOwnedCompositionAdminCaps(client, owner, misoPackageId) {
383
+ export const getOwnedCompositionAdminCaps = Effect.fn("getOwnedCompositionAdminCaps")(function* (owner, misoPackageId) {
387
384
  const capType = `${misoPackageId}::composition::CompositionAdminCap`;
388
- const objects = await listAllOwnedObjects(client, { owner, type: capType });
385
+ const objects = yield* listAllOwnedObjects({ owner, type: capType });
389
386
  const caps = [];
390
387
  for (const obj of objects) {
391
388
  const match = obj.type?.match(/<(.+)>$/);
392
389
  if (match?.[1])
393
- caps.push({ id: obj.objectId, shareType: match[1] });
390
+ caps.push(new CompositionAdminCap({ id: obj.objectId, shareType: match[1] }));
394
391
  }
395
392
  return caps;
396
- }
393
+ });
397
394
  export function deriveCompositionAdminCapId(compositionId, misoPackageId) {
398
395
  return deriveObjectID(compositionId, `${misoPackageId}::composition::CompositionAdminCapKey`, UNIT_STRUCT_KEY_BYTES);
399
396
  }
400
397
  // ============================================================================
401
398
  // Recording
402
399
  // ============================================================================
403
- export async function getRecordingsByIds(client, recordingIds) {
400
+ export const getRecordingsByIds = Effect.fn("getRecordingsByIds")(function* (recordingIds) {
404
401
  if (recordingIds.length === 0)
405
402
  return {};
406
- const { objects } = await client.core.getObjects({
407
- objectIds: recordingIds,
408
- include: { content: true },
409
- });
410
- const out = {};
411
- for (const obj of objects) {
412
- if (obj instanceof Error || !obj.content)
413
- continue;
414
- out[obj.objectId] = mapRecording(obj.objectId, RecordingBcs.parse(obj.content));
415
- }
416
- return out;
417
- }
418
- export async function getRecordingById(client, recordingId) {
419
- const content = await getContent(client, recordingId);
420
- if (!content)
421
- throw new Error(`Recording not found: ${recordingId}`);
422
- return mapRecording(recordingId, RecordingBcs.parse(content));
403
+ const contents = yield* getObjectsContent(recordingIds);
404
+ const decoded = yield* Effect.forEach([...contents.entries()], ([objectId, { content }]) => decodeBcs({ parse: (bytes) => mapRecording(objectId, RecordingBcs.parse(bytes)) }, Recording, content, { type: "Recording", objectId }).pipe(Effect.map((recording) => [objectId, recording])), { concurrency: "unbounded" });
405
+ return Object.fromEntries(decoded);
406
+ });
407
+ export function getRecordingById(recordingId) {
408
+ return getObjectByBcs(recordingId, { parse: (bytes) => mapRecording(recordingId, RecordingBcs.parse(bytes)) }, Recording, "Recording");
423
409
  }
424
410
  /**
425
411
  * The recording's OWN share type (`RecordingShare`). `Recording` is generic over
@@ -427,30 +413,31 @@ export async function getRecordingById(client, recordingId) {
427
413
  * them and returns the first; use {@link getRecordingShareTypes} when the
428
414
  * parent composition's share type is needed too.
429
415
  */
430
- export async function getRecordingShareType(client, recordingId) {
431
- const [recordingShareType] = await getRecordingShareTypes(client, recordingId);
416
+ export const getRecordingShareType = Effect.fn("getRecordingShareType")(function* (recordingId) {
417
+ const [recordingShareType] = yield* getRecordingShareTypes(recordingId);
432
418
  return recordingShareType;
433
- }
419
+ });
434
420
  /**
435
421
  * Both of a recording's share types, as `[RecordingShare, CompositionShare]`.
436
422
  * Most builders need the pair — `track::new`, `recording::publish`
437
423
  * and the recording credit/pool extensions are all generic over both, in this
438
424
  * order.
439
425
  */
440
- export async function getRecordingShareTypes(client, recordingId) {
441
- const { object } = await client.core.getObject({ objectId: recordingId });
442
- return extractTypeParams2(object.type);
443
- }
444
- export async function getRecordingByShareType(client, graphqlClient, shareType, misoPackageId) {
445
- const address = await addressOfRecordingWithShareType(graphqlClient, misoPackageId, shareType);
446
- if (!address)
447
- throw new Error(`Recording not found for share type: ${shareType}`);
448
- return getRecordingById(client, address);
449
- }
450
- export async function getRecordingAdminCapById(client, adminCapId) {
451
- const { object } = await client.core.getObject({ objectId: adminCapId });
452
- return { id: adminCapId, shareType: extractTypeParam(object.type) };
453
- }
426
+ export const getRecordingShareTypes = Effect.fn("getRecordingShareTypes")(function* (recordingId) {
427
+ const type = yield* getObjectType(recordingId);
428
+ return extractTypeParams2(type);
429
+ });
430
+ export const getRecordingByShareType = Effect.fn("getRecordingByShareType")(function* (shareType, misoPackageId) {
431
+ const address = yield* addressOfRecordingWithShareType(misoPackageId, shareType);
432
+ if (Option.isNone(address)) {
433
+ return yield* new ObjectNotFoundError({ objectId: `recording::Recording<${shareType}, ...>` });
434
+ }
435
+ return yield* getRecordingById(address.value);
436
+ });
437
+ export const getRecordingAdminCapById = Effect.fn("getRecordingAdminCapById")(function* (adminCapId) {
438
+ const type = yield* getObjectType(adminCapId);
439
+ return new RecordingAdminCap({ id: adminCapId, shareType: extractTypeParam(type) });
440
+ });
454
441
  /**
455
442
  * Recording admin caps owned by `owner`.
456
443
  *
@@ -459,72 +446,56 @@ export async function getRecordingAdminCapById(client, adminCapId) {
459
446
  * so this yields only the recording's own share type — its parent composition's
460
447
  * share type is not recoverable from the cap alone.
461
448
  */
462
- export async function getOwnedRecordingAdminCaps(client, owner, misoPackageId) {
449
+ export const getOwnedRecordingAdminCaps = Effect.fn("getOwnedRecordingAdminCaps")(function* (owner, misoPackageId) {
463
450
  const capType = `${misoPackageId}::recording::RecordingAdminCap`;
464
- const objects = await listAllOwnedObjects(client, { owner, type: capType });
451
+ const objects = yield* listAllOwnedObjects({ owner, type: capType });
465
452
  const caps = [];
466
453
  for (const obj of objects) {
467
454
  const match = obj.type?.match(/<(.+)>$/);
468
455
  if (match?.[1])
469
- caps.push({ id: obj.objectId, shareType: match[1] });
456
+ caps.push(new RecordingAdminCap({ id: obj.objectId, shareType: match[1] }));
470
457
  }
471
458
  return caps;
472
- }
459
+ });
473
460
  export function deriveRecordingAdminCapId(recordingId, misoPackageId) {
474
461
  return deriveObjectID(recordingId, `${misoPackageId}::recording::RecordingAdminCapKey`, UNIT_STRUCT_KEY_BYTES);
475
462
  }
476
463
  // ============================================================================
477
464
  // Release
478
465
  // ============================================================================
479
- export async function getReleasesByIds(client, releaseIds) {
466
+ export const getReleasesByIds = Effect.fn("getReleasesByIds")(function* (releaseIds) {
480
467
  if (releaseIds.length === 0)
481
468
  return {};
482
- const { objects } = await client.core.getObjects({
483
- objectIds: releaseIds,
484
- include: { content: true },
485
- });
486
- const out = {};
487
- for (const obj of objects) {
488
- if (obj instanceof Error || !obj.content)
489
- continue;
490
- out[obj.objectId] = mapRelease(obj.objectId, ReleaseBcs.parse(obj.content));
491
- }
492
- return out;
493
- }
494
- export async function getReleaseById(client, releaseId) {
495
- const { object } = await client.core.getObject({
496
- objectId: releaseId,
497
- include: { content: true },
498
- });
499
- if (!object.content)
500
- throw new Error(`Release not found: ${releaseId}`);
501
- return mapRelease(releaseId, ReleaseBcs.parse(object.content));
502
- }
503
- export async function getReleaseAdminCapById(client, adminCapId) {
504
- const { object } = await client.core.getObject({
505
- objectId: adminCapId,
506
- include: { json: true },
469
+ const contents = yield* getObjectsContent(releaseIds);
470
+ const decoded = yield* Effect.forEach([...contents.entries()], ([objectId, { content }]) => decodeBcs({ parse: (bytes) => mapRelease(objectId, ReleaseBcs.parse(bytes)) }, Release, content, { type: "Release", objectId }).pipe(Effect.map((release) => [objectId, release])), { concurrency: "unbounded" });
471
+ return Object.fromEntries(decoded);
472
+ });
473
+ export function getReleaseById(releaseId) {
474
+ return getObjectByBcs(releaseId, { parse: (bytes) => mapRelease(releaseId, ReleaseBcs.parse(bytes)) }, Release, "Release");
475
+ }
476
+ export const getReleaseAdminCapById = Effect.fn("getReleaseAdminCapById")(function* (adminCapId) {
477
+ const client = yield* SuiClient;
478
+ const { object } = yield* Effect.tryPromise({
479
+ try: (signal) => client.core.getObject({ objectId: adminCapId, include: { json: true }, signal }),
480
+ catch: (cause) => classifyMissing(adminCapId, "getObject", cause),
507
481
  });
508
482
  const json = object.json;
509
- if (!json?.release_id)
510
- throw new Error(`ReleaseAdminCap not found: ${adminCapId}`);
511
- return { id: adminCapId, releaseId: json.release_id };
512
- }
513
- export async function getOwnedReleaseAdminCaps(client, owner, misoPackageId) {
483
+ if (!json?.release_id) {
484
+ return yield* new ObjectNotFoundError({ objectId: adminCapId });
485
+ }
486
+ return new ReleaseAdminCap({ id: adminCapId, releaseId: json.release_id });
487
+ });
488
+ export const getOwnedReleaseAdminCaps = Effect.fn("getOwnedReleaseAdminCaps")(function* (owner, misoPackageId) {
514
489
  const capType = `${misoPackageId}::release::ReleaseAdminCap`;
515
- const objects = await listAllOwnedObjects(client, {
516
- owner,
517
- type: capType,
518
- include: { json: true },
519
- });
490
+ const objects = yield* listAllOwnedObjects({ owner, type: capType, include: { json: true } });
520
491
  const caps = [];
521
492
  for (const obj of objects) {
522
493
  const json = obj.json;
523
494
  if (json?.release_id)
524
- caps.push({ id: obj.objectId, releaseId: json.release_id });
495
+ caps.push(new ReleaseAdminCap({ id: obj.objectId, releaseId: json.release_id }));
525
496
  }
526
497
  return caps;
527
- }
498
+ });
528
499
  export function deriveReleaseAdminCapId(releaseId, misoPackageId) {
529
500
  return deriveObjectID(releaseId, `${misoPackageId}::release::ReleaseAdminCapKey`, UNIT_STRUCT_KEY_BYTES);
530
501
  }
@@ -532,10 +503,10 @@ export function deriveReleaseAdminCapId(releaseId, misoPackageId) {
532
503
  // Share Currency
533
504
  // ============================================================================
534
505
  /** Extracts the share type `T` from a `Currency<T>` object. */
535
- export async function getShareCurrencyType(client, shareCurrencyId) {
536
- const { object } = await client.core.getObject({ objectId: shareCurrencyId });
537
- return extractTypeParam(object.type);
538
- }
506
+ export const getShareCurrencyType = Effect.fn("getShareCurrencyType")(function* (shareCurrencyId) {
507
+ const type = yield* getObjectType(shareCurrencyId);
508
+ return extractTypeParam(type);
509
+ });
539
510
  /**
540
511
  * Finds the `TreasuryCap<ShareType>` owned by `owner`. One Core API call.
541
512
  *
@@ -547,33 +518,32 @@ export async function getShareCurrencyType(client, shareCurrencyId) {
547
518
  * compose the two:
548
519
  *
549
520
  * ```ts
550
- * const shareType = await getShareCurrencyType(client, shareCurrencyId);
551
- * const capId = await getShareCurrencyTreasuryCap(client, shareType, owner);
521
+ * const shareType = yield* getShareCurrencyType(shareCurrencyId);
522
+ * const capId = yield* getShareCurrencyTreasuryCap(shareType, owner);
552
523
  * ```
553
524
  */
554
- export async function getShareCurrencyTreasuryCap(client, shareType, owner) {
555
- const objects = await listAllOwnedObjects(client, {
556
- owner,
557
- type: `0x2::coin::TreasuryCap<${shareType}>`,
558
- });
525
+ export const getShareCurrencyTreasuryCap = Effect.fn("getShareCurrencyTreasuryCap")(function* (shareType, owner) {
526
+ const objects = yield* listAllOwnedObjects({ owner, type: `0x2::coin::TreasuryCap<${shareType}>` });
559
527
  if (objects.length === 0) {
560
528
  throw new Error(`No TreasuryCap found for ${shareType} owned by ${owner}`);
561
529
  }
562
530
  return objects[0].objectId;
563
- }
531
+ });
564
532
  // ============================================================================
565
533
  // Private
566
534
  // ============================================================================
567
- /** Returns the first object address of a fully-qualified type, or null. */
568
- async function firstAddressOfType(client, type) {
569
- const result = await client.query({
570
- query: AddressesByTypeQuery,
571
- variables: { type },
535
+ /** Returns the first object address of a fully-qualified type, or `Option.none()`. */
536
+ const firstAddressOfType = Effect.fn("firstAddressOfType")(function* (type) {
537
+ const client = yield* SuiGraphQL;
538
+ const result = yield* Effect.tryPromise({
539
+ try: () => client.query({ query: AddressesByTypeQuery, variables: { type } }),
540
+ catch: (cause) => new SuiRpcError({ operation: "addressesByType", cause }),
572
541
  });
573
- return result.data?.objects?.nodes?.[0]?.address ?? null;
574
- }
542
+ return Option.fromNullishOr(result.data?.objects?.nodes?.[0]?.address);
543
+ });
575
544
  /**
576
- * Address of the `Recording` whose FIRST type parameter is `shareType`, or null.
545
+ * Address of the `Recording` whose FIRST type parameter is `shareType`, or
546
+ * `Option.none()`.
577
547
  *
578
548
  * `Recording<RecordingShare, CompositionShare>` takes two parameters and a type
579
549
  * filter must supply all of them or none, so filtering by
@@ -583,17 +553,21 @@ async function firstAddressOfType(client, type) {
583
553
  * first parameter client-side. A recording's share currency is unique to it, so
584
554
  * the match is unambiguous.
585
555
  */
586
- async function addressOfRecordingWithShareType(client, misoPackageId, shareType) {
556
+ const addressOfRecordingWithShareType = Effect.fn("addressOfRecordingWithShareType")(function* (misoPackageId, shareType) {
557
+ const client = yield* SuiGraphQL;
587
558
  let cursor;
588
559
  do {
589
- const result = await client.query({
590
- query: `query RecordingAddress($type: String!, $cursor: String) {
591
- objects(first: 50, after: $cursor, filter: { type: $type }) {
592
- pageInfo { hasNextPage endCursor }
593
- nodes { address asMoveObject { contents { type { repr } } } }
594
- }
595
- }`,
596
- variables: { type: `${misoPackageId}::recording::Recording`, cursor },
560
+ const result = yield* Effect.tryPromise({
561
+ try: () => client.query({
562
+ query: `query RecordingAddress($type: String!, $cursor: String) {
563
+ objects(first: 50, after: $cursor, filter: { type: $type }) {
564
+ pageInfo { hasNextPage endCursor }
565
+ nodes { address asMoveObject { contents { type { repr } } } }
566
+ }
567
+ }`,
568
+ variables: { type: `${misoPackageId}::recording::Recording`, cursor },
569
+ }),
570
+ catch: (cause) => new SuiRpcError({ operation: "recordingAddress", cause }),
597
571
  });
598
572
  const page = result.data?.objects;
599
573
  for (const node of page?.nodes ?? []) {
@@ -602,10 +576,10 @@ async function addressOfRecordingWithShareType(client, misoPackageId, shareType)
602
576
  continue;
603
577
  const [recordingShareType] = extractTypeParams2(repr);
604
578
  if (recordingShareType === shareType)
605
- return node.address;
579
+ return Option.some(node.address);
606
580
  }
607
581
  cursor = page?.pageInfo?.hasNextPage ? page.pageInfo.endCursor : null;
608
582
  } while (cursor);
609
- return null;
610
- }
583
+ return Option.none();
584
+ });
611
585
  //# sourceMappingURL=queries.js.map