@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.
- package/README.md +90 -13
- package/dist/client.d.ts +27 -26
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +58 -51
- package/dist/client.js.map +1 -1
- package/dist/deployments.d.ts +9 -0
- package/dist/deployments.d.ts.map +1 -1
- package/dist/deployments.js +14 -0
- package/dist/deployments.js.map +1 -1
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +17 -0
- package/dist/errors.js.map +1 -0
- package/dist/events.d.ts +6 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +0 -5
- package/dist/events.js.map +1 -1
- package/dist/execute.d.ts +1 -48
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +5 -106
- package/dist/execute.js.map +1 -1
- package/dist/internal.d.ts +45 -6
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js.map +1 -1
- package/dist/queries.d.ts +38 -62
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +240 -266
- package/dist/queries.js.map +1 -1
- package/dist/transactions.d.ts +2 -1
- package/dist/transactions.d.ts.map +1 -1
- package/dist/transactions.js.map +1 -1
- package/dist/types.d.ts +129 -79
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +169 -1
- package/dist/types.js.map +1 -1
- package/dist/view.d.ts +3 -2
- package/dist/view.d.ts.map +1 -1
- package/dist/view.js +19 -6
- package/dist/view.js.map +1 -1
- package/package.json +8 -1
- package/src/client.ts +78 -98
- package/src/deployments.ts +15 -0
- package/src/errors.ts +30 -0
- package/src/events.ts +1 -1
- package/src/execute.ts +5 -133
- package/src/internal.ts +17 -23
- package/src/queries.ts +386 -424
- package/src/transactions.ts +2 -1
- package/src/types.ts +59 -48
- 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 {
|
|
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
|
|
40
|
-
*
|
|
41
|
-
*
|
|
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`).
|
|
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
|
-
|
|
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
|
-
/**
|
|
74
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
100
|
-
*
|
|
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
|
|
103
|
-
const content =
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
135
|
+
* boolean byte. Absence resolves to `Option.none()`; transport errors still fail.
|
|
112
136
|
*/
|
|
113
|
-
export
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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(
|
|
152
|
-
return getReleaseDspField(
|
|
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(
|
|
156
|
-
return getReleaseDspField(
|
|
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
|
|
163
|
-
return getObjectByBcs(
|
|
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
|
|
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 =
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
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
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
307
|
-
|
|
308
|
-
|
|
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[
|
|
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[
|
|
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[
|
|
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
|
|
337
|
+
export const getCompositionsByIds = Effect.fn("getCompositionsByIds")(function* (compositionIds) {
|
|
331
338
|
if (compositionIds.length === 0)
|
|
332
339
|
return {};
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
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
|
|
347
|
-
|
|
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
|
|
354
|
-
const
|
|
355
|
-
return extractTypeParam(
|
|
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
|
|
359
|
-
const address =
|
|
360
|
-
if (
|
|
361
|
-
|
|
362
|
-
|
|
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
|
|
368
|
+
export function getCompositionAddressByShareType(shareType, misoPackageId) {
|
|
372
369
|
const type = `${misoPackageId}::composition::Composition<${shareType}>`;
|
|
373
|
-
return firstAddressOfType(
|
|
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
|
|
383
|
+
export const getOwnedCompositionAdminCaps = Effect.fn("getOwnedCompositionAdminCaps")(function* (owner, misoPackageId) {
|
|
387
384
|
const capType = `${misoPackageId}::composition::CompositionAdminCap`;
|
|
388
|
-
const objects =
|
|
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
|
|
400
|
+
export const getRecordingsByIds = Effect.fn("getRecordingsByIds")(function* (recordingIds) {
|
|
404
401
|
if (recordingIds.length === 0)
|
|
405
402
|
return {};
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
|
431
|
-
const [recordingShareType] =
|
|
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
|
|
441
|
-
const
|
|
442
|
-
return extractTypeParams2(
|
|
443
|
-
}
|
|
444
|
-
export
|
|
445
|
-
const address =
|
|
446
|
-
if (
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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
|
|
449
|
+
export const getOwnedRecordingAdminCaps = Effect.fn("getOwnedRecordingAdminCaps")(function* (owner, misoPackageId) {
|
|
463
450
|
const capType = `${misoPackageId}::recording::RecordingAdminCap`;
|
|
464
|
-
const objects =
|
|
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
|
|
466
|
+
export const getReleasesByIds = Effect.fn("getReleasesByIds")(function* (releaseIds) {
|
|
480
467
|
if (releaseIds.length === 0)
|
|
481
468
|
return {};
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
492
|
-
|
|
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
|
-
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
|
|
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 =
|
|
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
|
|
536
|
-
const
|
|
537
|
-
return extractTypeParam(
|
|
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 =
|
|
551
|
-
* const capId =
|
|
521
|
+
* const shareType = yield* getShareCurrencyType(shareCurrencyId);
|
|
522
|
+
* const capId = yield* getShareCurrencyTreasuryCap(shareType, owner);
|
|
552
523
|
* ```
|
|
553
524
|
*/
|
|
554
|
-
export
|
|
555
|
-
const objects =
|
|
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
|
|
568
|
-
|
|
569
|
-
const
|
|
570
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
556
|
+
const addressOfRecordingWithShareType = Effect.fn("addressOfRecordingWithShareType")(function* (misoPackageId, shareType) {
|
|
557
|
+
const client = yield* SuiGraphQL;
|
|
587
558
|
let cursor;
|
|
588
559
|
do {
|
|
589
|
-
const result =
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
|
|
610
|
-
}
|
|
583
|
+
return Option.none();
|
|
584
|
+
});
|
|
611
585
|
//# sourceMappingURL=queries.js.map
|