@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/src/queries.ts
CHANGED
|
@@ -6,22 +6,31 @@
|
|
|
6
6
|
// tracks the on-chain ABI). Generic-type discovery (by share type / by owner)
|
|
7
7
|
// uses GraphQL to find object addresses, then reads them through the Core path.
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
import type { ClientWithCoreApi } from "@mysten/sui/client";
|
|
21
|
-
import type { SuiGraphQLClient } from "@mysten/sui/graphql";
|
|
9
|
+
// Every read requires the `SuiClient` service (and `SuiGraphQL` for the
|
|
10
|
+
// type-discovery reads) instead of taking a client parameter — see
|
|
11
|
+
// `@misofm/effect`. Not-found is a typed `ObjectNotFoundError`; a BCS decode
|
|
12
|
+
// failure (including a wrong on-chain type, which fails to parse against the
|
|
13
|
+
// expected ABI) is a typed `BcsDecodeError`. Extension dynamic-field readers
|
|
14
|
+
// return `Option.none()` for "not attached" — extension data is optional by
|
|
15
|
+
// design, and absence is a normal, expected state, not a failure.
|
|
16
|
+
|
|
17
|
+
import { ConflictingWorkKindError } from "./errors.ts";
|
|
18
|
+
import { Effect, Option, Schema } from "effect";
|
|
22
19
|
import { graphql } from "@mysten/sui/graphql/schema";
|
|
23
20
|
import { deriveObjectID, normalizeSuiAddress } from "@mysten/sui/utils";
|
|
24
21
|
|
|
22
|
+
import {
|
|
23
|
+
type BcsParser,
|
|
24
|
+
BcsDecodeError,
|
|
25
|
+
decodeBcs,
|
|
26
|
+
getObjectContent,
|
|
27
|
+
getObjectsContent,
|
|
28
|
+
ObjectNotFoundError,
|
|
29
|
+
SuiClient,
|
|
30
|
+
SuiGraphQL,
|
|
31
|
+
SuiRpcError,
|
|
32
|
+
} from "@misofm/effect";
|
|
33
|
+
|
|
25
34
|
import { Composition as CompositionBcs } from "./contracts/musicos/composition.ts";
|
|
26
35
|
import { Recording as RecordingBcs } from "./contracts/musicos/recording.ts";
|
|
27
36
|
import {
|
|
@@ -29,20 +38,22 @@ import {
|
|
|
29
38
|
ReleaseRegistry as ReleaseRegistryBcs,
|
|
30
39
|
} from "./contracts/musicos/release.ts";
|
|
31
40
|
import {
|
|
32
|
-
mapBps,
|
|
33
41
|
mapComposition,
|
|
34
42
|
mapRecording,
|
|
35
43
|
mapRelease,
|
|
36
44
|
} from "./internal.ts";
|
|
37
|
-
import
|
|
45
|
+
import {
|
|
38
46
|
Composition,
|
|
39
47
|
CompositionAdminCap,
|
|
40
48
|
Recording,
|
|
41
49
|
RecordingAdminCap,
|
|
42
50
|
Release,
|
|
43
51
|
ReleaseAdminCap,
|
|
52
|
+
ReleaseRegistry,
|
|
44
53
|
} from "./types.ts";
|
|
45
54
|
|
|
55
|
+
export type { BcsParser };
|
|
56
|
+
|
|
46
57
|
// ============================================================================
|
|
47
58
|
// Helpers
|
|
48
59
|
// ============================================================================
|
|
@@ -73,70 +84,86 @@ export function extractTypeParams2(objectType: string): [string, string] {
|
|
|
73
84
|
throw new Error(`Expected two type parameters in: ${objectType}`);
|
|
74
85
|
}
|
|
75
86
|
|
|
87
|
+
/** Key bytes for Move unit structs (single `0x00` for `dummy_field: bool = false`). */
|
|
88
|
+
const UNIT_STRUCT_KEY_BYTES = new Uint8Array([0x00]);
|
|
89
|
+
|
|
76
90
|
/**
|
|
77
|
-
* True when `e` is a "this object does not exist" error from any
|
|
78
|
-
*
|
|
79
|
-
*
|
|
91
|
+
* True when `e` is a "this object/dynamic field does not exist" error from any
|
|
92
|
+
* of the Sui client transports. This mirrors `@misofm/effect`'s internal
|
|
93
|
+
* classifier (not exported, since `getObjectContent` already covers the common
|
|
94
|
+
* case) — needed here for the reads that don't go through it: type-only object
|
|
95
|
+
* reads (no `content`) and dynamic-field reads. Matches, in order of
|
|
96
|
+
* preference:
|
|
80
97
|
*
|
|
81
98
|
* 1. Structured `ObjectError.code` values thrown by the JSON-RPC core client
|
|
82
99
|
* (`notExists`, `deleted`, `dynamicFieldNotFound`) and the GraphQL core
|
|
83
|
-
* client (`notFound`).
|
|
84
|
-
* so we duck-type on `code`.
|
|
100
|
+
* client (`notFound`).
|
|
85
101
|
* 2. The message shapes those clients (and the gRPC core client, which wraps
|
|
86
|
-
* the server's per-object status message in a plain `Error`) produce
|
|
87
|
-
* "Object 0x… does not exist" / "Object 0x… not found" / "Object 0x… has
|
|
88
|
-
* been deleted" / "Dynamic field not found for object 0x…" / "No object
|
|
89
|
-
* found for id 0x…".
|
|
90
|
-
*
|
|
91
|
-
* Transport/protocol errors must NOT match: every message pattern requires
|
|
92
|
-
* object-ish context ("object" / "dynamic field"), so e.g. a JSON-RPC
|
|
93
|
-
* "Method not found" or a gRPC "peer not found" is never treated as a missing
|
|
94
|
-
* object and propagates to the caller.
|
|
102
|
+
* the server's per-object status message in a plain `Error`) produce.
|
|
95
103
|
*/
|
|
96
|
-
|
|
104
|
+
function isMissingObjectError(e: unknown): boolean {
|
|
97
105
|
if (typeof e === "object" && e !== null && "code" in e) {
|
|
98
106
|
const code = (e as { code: unknown }).code;
|
|
99
|
-
if (
|
|
100
|
-
code === "notExists" ||
|
|
101
|
-
code === "deleted" ||
|
|
102
|
-
code === "dynamicFieldNotFound" ||
|
|
103
|
-
code === "notFound"
|
|
104
|
-
) {
|
|
107
|
+
if (code === "notExists" || code === "deleted" || code === "dynamicFieldNotFound" || code === "notFound") {
|
|
105
108
|
return true;
|
|
106
109
|
}
|
|
107
110
|
}
|
|
108
111
|
const msg = e instanceof Error ? e.message : String(e);
|
|
109
112
|
return (
|
|
110
|
-
/\bobject\b[\s\S]*\b(?:not\s?found|does not exist|has been deleted)\b/i.test(
|
|
111
|
-
msg,
|
|
112
|
-
) ||
|
|
113
|
+
/\bobject\b[\s\S]*\b(?:not\s?found|does not exist|has been deleted)\b/i.test(msg) ||
|
|
113
114
|
/\bdynamic field\b[\s\S]*\bnot\s?found\b/i.test(msg) ||
|
|
114
115
|
/\bno object\b/i.test(msg)
|
|
115
116
|
);
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
/**
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
/** Any generated BCS codec with a `parse` method. */
|
|
122
|
-
export interface BcsParser<T> {
|
|
123
|
-
parse(bytes: Uint8Array): T;
|
|
119
|
+
/** Classifies a Core API rejection against `objectId`: missing -> `ObjectNotFoundError`, else `SuiRpcError`. */
|
|
120
|
+
function classifyMissing(objectId: string, operation: string, cause: unknown): ObjectNotFoundError | SuiRpcError {
|
|
121
|
+
return isMissingObjectError(cause) ? new ObjectNotFoundError({ objectId }) : new SuiRpcError({ operation, cause });
|
|
124
122
|
}
|
|
125
123
|
|
|
124
|
+
/** Fetches just an object's on-chain `type` (no content) through the Core API. */
|
|
125
|
+
const getObjectType = Effect.fn("getObjectType")(function* (
|
|
126
|
+
objectId: string,
|
|
127
|
+
): Effect.fn.Return<string, ObjectNotFoundError | SuiRpcError, SuiClient> {
|
|
128
|
+
const client = yield* SuiClient;
|
|
129
|
+
const { object } = yield* Effect.tryPromise({
|
|
130
|
+
try: (signal) => client.core.getObject({ objectId, signal }),
|
|
131
|
+
catch: (cause) => classifyMissing(objectId, "getObject", cause),
|
|
132
|
+
});
|
|
133
|
+
return object.type;
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
/** Fetches one dynamic field's raw BCS value bytes; not-found becomes `ObjectNotFoundError`. */
|
|
137
|
+
const getDynamicFieldValue = Effect.fn("getDynamicFieldValue")(function* (
|
|
138
|
+
parentId: string,
|
|
139
|
+
name: { type: string; bcs: Uint8Array },
|
|
140
|
+
): Effect.fn.Return<Uint8Array, ObjectNotFoundError | SuiRpcError, SuiClient> {
|
|
141
|
+
const client = yield* SuiClient;
|
|
142
|
+
const { dynamicField } = yield* Effect.tryPromise({
|
|
143
|
+
try: (signal) => client.core.getDynamicField({ parentId, name, signal }),
|
|
144
|
+
catch: (cause) => classifyMissing(parentId, "getDynamicField", cause),
|
|
145
|
+
});
|
|
146
|
+
return dynamicField.value.bcs;
|
|
147
|
+
});
|
|
148
|
+
|
|
126
149
|
/** Exhaust every Core owned-object page; cap discovery must not truncate. */
|
|
127
|
-
|
|
128
|
-
client: ClientWithCoreApi,
|
|
150
|
+
const listAllOwnedObjects = Effect.fn("listAllOwnedObjects")(function* (
|
|
129
151
|
input: Record<string, unknown>,
|
|
130
|
-
):
|
|
152
|
+
): Effect.fn.Return<Array<{ objectId: string; type?: string; json?: unknown }>, SuiRpcError, SuiClient> {
|
|
153
|
+
const client = yield* SuiClient;
|
|
131
154
|
const objects: Array<{ objectId: string; type?: string; json?: unknown }> = [];
|
|
132
155
|
let cursor: string | null | undefined;
|
|
133
156
|
do {
|
|
134
|
-
const page =
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
157
|
+
const page = yield* Effect.tryPromise({
|
|
158
|
+
try: (signal) =>
|
|
159
|
+
(client.core.listOwnedObjects as (args: unknown) => Promise<{
|
|
160
|
+
objects: Array<{ objectId: string; type?: string; json?: unknown }>;
|
|
161
|
+
pageInfo?: { hasNextPage?: boolean; endCursor?: string | null };
|
|
162
|
+
hasNextPage?: boolean;
|
|
163
|
+
cursor?: string | null;
|
|
164
|
+
}>)(cursor ? { ...input, cursor, signal } : { ...input, signal }),
|
|
165
|
+
catch: (cause) => new SuiRpcError({ operation: "listOwnedObjects", cause }),
|
|
166
|
+
});
|
|
140
167
|
objects.push(...page.objects);
|
|
141
168
|
cursor = page.pageInfo?.hasNextPage
|
|
142
169
|
? page.pageInfo.endCursor
|
|
@@ -145,33 +172,22 @@ async function listAllOwnedObjects(
|
|
|
145
172
|
: null;
|
|
146
173
|
} while (cursor);
|
|
147
174
|
return objects;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** Fetches one object's BCS content bytes (or null if absent). */
|
|
151
|
-
async function getContent(
|
|
152
|
-
client: ClientWithCoreApi,
|
|
153
|
-
objectId: string,
|
|
154
|
-
): Promise<Uint8Array | null> {
|
|
155
|
-
const { object } = await client.core.getObject({
|
|
156
|
-
objectId,
|
|
157
|
-
include: { content: true },
|
|
158
|
-
});
|
|
159
|
-
return object.content ?? null;
|
|
160
|
-
}
|
|
175
|
+
});
|
|
161
176
|
|
|
162
177
|
/**
|
|
163
|
-
* Fetch and parse an object through the transport-neutral Core API
|
|
164
|
-
*
|
|
178
|
+
* Fetch and parse an object through the transport-neutral Core API, validating
|
|
179
|
+
* the parsed result against a domain `Schema`. Object content is BCS; never
|
|
180
|
+
* pass the full `objectBcs` envelope to a Move codec.
|
|
165
181
|
*/
|
|
166
|
-
export
|
|
167
|
-
client: ClientWithCoreApi,
|
|
182
|
+
export const getObjectByBcs = Effect.fn("getObjectByBcs")(function* <P, A>(
|
|
168
183
|
objectId: string,
|
|
169
|
-
codec: BcsParser<
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
184
|
+
codec: BcsParser<P>,
|
|
185
|
+
schema: Schema.Codec<A, any, never, never>,
|
|
186
|
+
typeName: string,
|
|
187
|
+
): Effect.fn.Return<A, ObjectNotFoundError | SuiRpcError | BcsDecodeError, SuiClient> {
|
|
188
|
+
const { content } = yield* getObjectContent(objectId);
|
|
189
|
+
return yield* decodeBcs(codec, schema, content, { type: typeName, objectId });
|
|
190
|
+
});
|
|
175
191
|
|
|
176
192
|
export interface ExtensionFieldParams<T> {
|
|
177
193
|
/** Freshly published package address for the extension. */
|
|
@@ -185,27 +201,20 @@ export interface ExtensionFieldParams<T> {
|
|
|
185
201
|
/**
|
|
186
202
|
* Read an optional first-party extension field from a core object's UID. Every
|
|
187
203
|
* current extension uses a fieldless `ExtensionKey`, whose BCS is one false
|
|
188
|
-
* boolean byte. Absence
|
|
204
|
+
* boolean byte. Absence resolves to `Option.none()`; transport errors still fail.
|
|
189
205
|
*/
|
|
190
|
-
export
|
|
191
|
-
client: ClientWithCoreApi,
|
|
206
|
+
export const getExtensionField = Effect.fn("getExtensionField")(function* <T>(
|
|
192
207
|
parentId: string,
|
|
193
208
|
params: ExtensionFieldParams<T>,
|
|
194
|
-
):
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
return params.codec.parse(dynamicField.value.bcs);
|
|
204
|
-
} catch (error) {
|
|
205
|
-
if (isNotFound(error)) return null;
|
|
206
|
-
throw error;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
+
): Effect.fn.Return<Option.Option<T>, SuiRpcError, SuiClient> {
|
|
210
|
+
return yield* getDynamicFieldValue(parentId, {
|
|
211
|
+
type: `${params.packageId}::${params.module}::ExtensionKey`,
|
|
212
|
+
bcs: UNIT_STRUCT_KEY_BYTES,
|
|
213
|
+
}).pipe(
|
|
214
|
+
Effect.map((bytes) => Option.some(params.codec.parse(bytes))),
|
|
215
|
+
Effect.catchTag("ObjectNotFoundError", () => Effect.succeed(Option.none<T>())),
|
|
216
|
+
);
|
|
217
|
+
});
|
|
209
218
|
|
|
210
219
|
export interface ReleaseDspFieldParams<T> {
|
|
211
220
|
/** Freshly published `release_dsp_link` package address. */
|
|
@@ -216,46 +225,37 @@ export interface ReleaseDspFieldParams<T> {
|
|
|
216
225
|
codec: BcsParser<T>;
|
|
217
226
|
}
|
|
218
227
|
|
|
219
|
-
|
|
220
|
-
client: ClientWithCoreApi,
|
|
228
|
+
const getReleaseDspField = Effect.fn("getReleaseDspField")(function* <T>(
|
|
221
229
|
releaseId: string,
|
|
222
230
|
key: "ReleaseLinkKey" | "TrackLinksKey",
|
|
223
231
|
params: ReleaseDspFieldParams<T>,
|
|
224
|
-
):
|
|
232
|
+
): Effect.fn.Return<Option.Option<T>, SuiRpcError, SuiClient> {
|
|
225
233
|
if (!Number.isInteger(params.platform) || params.platform < 0 || params.platform > 255) {
|
|
226
234
|
throw new Error("DSP platform must be a u8 discriminator");
|
|
227
235
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return params.codec.parse(dynamicField.value.bcs);
|
|
237
|
-
} catch (error) {
|
|
238
|
-
if (isNotFound(error)) return null;
|
|
239
|
-
throw error;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
236
|
+
return yield* getDynamicFieldValue(releaseId, {
|
|
237
|
+
type: `${params.packageId}::release_dsp_link::${key}`,
|
|
238
|
+
bcs: Uint8Array.of(params.platform),
|
|
239
|
+
}).pipe(
|
|
240
|
+
Effect.map((bytes) => Option.some(params.codec.parse(bytes))),
|
|
241
|
+
Effect.catchTag("ObjectNotFoundError", () => Effect.succeed(Option.none<T>())),
|
|
242
|
+
);
|
|
243
|
+
});
|
|
242
244
|
|
|
243
245
|
/** Read a release-level DSP link stored under `ReleaseLinkKey(platform)`. */
|
|
244
246
|
export function getReleaseDspLink<T>(
|
|
245
|
-
client: ClientWithCoreApi,
|
|
246
247
|
releaseId: string,
|
|
247
248
|
params: ReleaseDspFieldParams<T>,
|
|
248
|
-
):
|
|
249
|
-
return getReleaseDspField(
|
|
249
|
+
): Effect.Effect<Option.Option<T>, SuiRpcError, SuiClient> {
|
|
250
|
+
return getReleaseDspField(releaseId, "ReleaseLinkKey", params);
|
|
250
251
|
}
|
|
251
252
|
|
|
252
253
|
/** Read the per-track DSP-link array stored under `TrackLinksKey(platform)`. */
|
|
253
254
|
export function getTrackDspLinks<T>(
|
|
254
|
-
client: ClientWithCoreApi,
|
|
255
255
|
releaseId: string,
|
|
256
256
|
params: ReleaseDspFieldParams<T>,
|
|
257
|
-
):
|
|
258
|
-
return getReleaseDspField(
|
|
257
|
+
): Effect.Effect<Option.Option<T>, SuiRpcError, SuiClient> {
|
|
258
|
+
return getReleaseDspField(releaseId, "TrackLinksKey", params);
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
// ============================================================================
|
|
@@ -263,11 +263,10 @@ export function getTrackDspLinks<T>(
|
|
|
263
263
|
// ============================================================================
|
|
264
264
|
|
|
265
265
|
/** Parse the shared canonical core `miso::release::ReleaseRegistry` by ID. */
|
|
266
|
-
export
|
|
267
|
-
client: ClientWithCoreApi,
|
|
266
|
+
export function getReleaseRegistryById(
|
|
268
267
|
registryId: string,
|
|
269
|
-
) {
|
|
270
|
-
return getObjectByBcs(
|
|
268
|
+
): Effect.Effect<ReleaseRegistry, ObjectNotFoundError | SuiRpcError | BcsDecodeError, SuiClient> {
|
|
269
|
+
return getObjectByBcs(registryId, ReleaseRegistryBcs, ReleaseRegistry, "ReleaseRegistry");
|
|
271
270
|
}
|
|
272
271
|
|
|
273
272
|
// ============================================================================
|
|
@@ -339,16 +338,17 @@ interface WorkAddressConnection {
|
|
|
339
338
|
* exposes the first, so one bare Recording scan is shared by every requested
|
|
340
339
|
* recording type and filtered client-side.
|
|
341
340
|
*/
|
|
342
|
-
export
|
|
343
|
-
client: SuiGraphQLClient,
|
|
341
|
+
export const getWorkAddressesByShareTypes = Effect.fn("getWorkAddressesByShareTypes")(function* (
|
|
344
342
|
shareTypes: WorkShareTypes,
|
|
345
343
|
misoPackageId: string,
|
|
346
|
-
):
|
|
344
|
+
): Effect.fn.Return<WorkAddressesByShareType, SuiRpcError, SuiGraphQL> {
|
|
347
345
|
const compositions = [...new Set(shareTypes.compositions)];
|
|
348
346
|
const recordings = new Set(shareTypes.recordings);
|
|
349
347
|
const out: WorkAddressesByShareType = { compositions: {}, recordings: {} };
|
|
350
348
|
if (compositions.length === 0 && recordings.size === 0) return out;
|
|
351
349
|
|
|
350
|
+
const client = yield* SuiGraphQL;
|
|
351
|
+
|
|
352
352
|
const declarations: string[] = [];
|
|
353
353
|
const selections: string[] = [];
|
|
354
354
|
const variables: Record<string, string> = {};
|
|
@@ -359,8 +359,7 @@ export async function getWorkAddressesByShareTypes(
|
|
|
359
359
|
selections.push(
|
|
360
360
|
`composition${index}: objects(first: 1, filter: { type: $${variable} }) { nodes { address } }`,
|
|
361
361
|
);
|
|
362
|
-
variables[variable] =
|
|
363
|
-
`${misoPackageId}::composition::Composition<${shareType}>`;
|
|
362
|
+
variables[variable] = `${misoPackageId}::composition::Composition<${shareType}>`;
|
|
364
363
|
});
|
|
365
364
|
|
|
366
365
|
if (recordings.size > 0) {
|
|
@@ -372,20 +371,24 @@ export async function getWorkAddressesByShareTypes(
|
|
|
372
371
|
variables.recordingType = `${misoPackageId}::recording::Recording`;
|
|
373
372
|
}
|
|
374
373
|
|
|
375
|
-
const result =
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
374
|
+
const result = yield* Effect.tryPromise({
|
|
375
|
+
try: () =>
|
|
376
|
+
client.query<Record<string, WorkAddressConnection | null>, Record<string, string>>({
|
|
377
|
+
query: `query WorkAddressesByShareTypes(${declarations.join(", ")}) {
|
|
378
|
+
${selections.join("\n")}
|
|
379
|
+
}`,
|
|
380
|
+
variables,
|
|
381
|
+
}),
|
|
382
|
+
catch: (cause) => new SuiRpcError({ operation: "workAddressesByShareTypes", cause }),
|
|
383
383
|
});
|
|
384
384
|
if (result.errors?.length) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
385
|
+
return yield* new SuiRpcError({
|
|
386
|
+
operation: "workAddressesByShareTypes",
|
|
387
|
+
cause: new AggregateError(
|
|
388
|
+
result.errors.map((error) => new Error(error.message)),
|
|
389
|
+
"Work type discovery failed",
|
|
390
|
+
),
|
|
391
|
+
});
|
|
389
392
|
}
|
|
390
393
|
|
|
391
394
|
compositions.forEach((shareType, index) => {
|
|
@@ -393,9 +396,7 @@ export async function getWorkAddressesByShareTypes(
|
|
|
393
396
|
if (address) out.compositions[shareType] = address;
|
|
394
397
|
});
|
|
395
398
|
|
|
396
|
-
const readRecordingPage = (
|
|
397
|
-
page: WorkAddressConnection | null | undefined,
|
|
398
|
-
) => {
|
|
399
|
+
const readRecordingPage = (page: WorkAddressConnection | null | undefined) => {
|
|
399
400
|
for (const node of page?.nodes ?? []) {
|
|
400
401
|
const repr = node.asMoveObject?.contents?.type?.repr;
|
|
401
402
|
if (!repr) continue;
|
|
@@ -417,33 +418,35 @@ export async function getWorkAddressesByShareTypes(
|
|
|
417
418
|
recordingPage.pageInfo.endCursor &&
|
|
418
419
|
Object.keys(out.recordings).length < recordings.size
|
|
419
420
|
) {
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
},
|
|
421
|
+
const cursor: string = recordingPage.pageInfo.endCursor;
|
|
422
|
+
const next = yield* Effect.tryPromise({
|
|
423
|
+
try: () =>
|
|
424
|
+
client.query<{ recordings: WorkAddressConnection | null }, { recordingType: string; cursor: string }>({
|
|
425
|
+
query: `query RecordingWorkAddresses($recordingType: String!, $cursor: String!) {
|
|
426
|
+
recordings: objects(first: 50, after: $cursor, filter: { type: $recordingType }) {
|
|
427
|
+
pageInfo { hasNextPage endCursor }
|
|
428
|
+
nodes { address asMoveObject { contents { type { repr } } } }
|
|
429
|
+
}
|
|
430
|
+
}`,
|
|
431
|
+
variables: { recordingType: variables.recordingType!, cursor },
|
|
432
|
+
}),
|
|
433
|
+
catch: (cause) => new SuiRpcError({ operation: "recordingWorkAddresses", cause }),
|
|
434
434
|
});
|
|
435
435
|
if (next.errors?.length) {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
436
|
+
return yield* new SuiRpcError({
|
|
437
|
+
operation: "recordingWorkAddresses",
|
|
438
|
+
cause: new AggregateError(
|
|
439
|
+
next.errors.map((error) => new Error(error.message)),
|
|
440
|
+
"Recording type discovery failed",
|
|
441
|
+
),
|
|
442
|
+
});
|
|
440
443
|
}
|
|
441
444
|
recordingPage = next.data?.recordings;
|
|
442
445
|
readRecordingPage(recordingPage);
|
|
443
446
|
}
|
|
444
447
|
|
|
445
448
|
return out;
|
|
446
|
-
}
|
|
449
|
+
});
|
|
447
450
|
|
|
448
451
|
export interface WorkIds {
|
|
449
452
|
compositions: readonly string[];
|
|
@@ -458,21 +461,16 @@ export interface WorksById {
|
|
|
458
461
|
}
|
|
459
462
|
|
|
460
463
|
/** Fetch and parse heterogeneous work objects through one Core bulk request. */
|
|
461
|
-
export
|
|
462
|
-
client: ClientWithCoreApi,
|
|
464
|
+
export const getWorksByIds = Effect.fn("getWorksByIds")(function* (
|
|
463
465
|
ids: WorkIds,
|
|
464
|
-
):
|
|
466
|
+
): Effect.fn.Return<WorksById, ConflictingWorkKindError | SuiRpcError | BcsDecodeError, SuiClient> {
|
|
465
467
|
const kinds = new Map<string, keyof WorksById>();
|
|
466
|
-
for (const [kind, objectIds] of Object.entries(ids) as Array<
|
|
467
|
-
[keyof WorksById, readonly string[]]
|
|
468
|
-
>) {
|
|
468
|
+
for (const [kind, objectIds] of Object.entries(ids) as Array<[keyof WorksById, readonly string[]]>) {
|
|
469
469
|
for (const objectId of objectIds) {
|
|
470
470
|
const normalized = normalizeSuiAddress(objectId);
|
|
471
471
|
const previous = kinds.get(normalized);
|
|
472
472
|
if (previous && previous !== kind) {
|
|
473
|
-
|
|
474
|
-
`Work ${normalized} was requested as both ${previous} and ${kind}`,
|
|
475
|
-
);
|
|
473
|
+
return yield* new ConflictingWorkKindError({ objectId: normalized, kinds: [previous, kind] });
|
|
476
474
|
}
|
|
477
475
|
kinds.set(normalized, kind);
|
|
478
476
|
}
|
|
@@ -481,93 +479,95 @@ export async function getWorksByIds(
|
|
|
481
479
|
const out: WorksById = { compositions: {}, recordings: {}, releases: {} };
|
|
482
480
|
if (kinds.size === 0) return out;
|
|
483
481
|
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
482
|
+
const contents = yield* getObjectsContent([...kinds.keys()]);
|
|
483
|
+
yield* Effect.forEach(
|
|
484
|
+
[...contents.entries()],
|
|
485
|
+
([objectId, { content }]) =>
|
|
486
|
+
Effect.gen(function* () {
|
|
487
|
+
const kind = kinds.get(normalizeSuiAddress(objectId));
|
|
488
|
+
if (kind === "compositions") {
|
|
489
|
+
out.compositions[objectId] = yield* decodeBcs(
|
|
490
|
+
{ parse: (bytes) => mapComposition(objectId, CompositionBcs.parse(bytes)) },
|
|
491
|
+
Composition,
|
|
492
|
+
content,
|
|
493
|
+
{ type: "Composition", objectId },
|
|
494
|
+
);
|
|
495
|
+
} else if (kind === "recordings") {
|
|
496
|
+
out.recordings[objectId] = yield* decodeBcs(
|
|
497
|
+
{ parse: (bytes) => mapRecording(objectId, RecordingBcs.parse(bytes)) },
|
|
498
|
+
Recording,
|
|
499
|
+
content,
|
|
500
|
+
{ type: "Recording", objectId },
|
|
501
|
+
);
|
|
502
|
+
} else if (kind === "releases") {
|
|
503
|
+
out.releases[objectId] = yield* decodeBcs(
|
|
504
|
+
{ parse: (bytes) => mapRelease(objectId, ReleaseBcs.parse(bytes)) },
|
|
505
|
+
Release,
|
|
506
|
+
content,
|
|
507
|
+
{ type: "Release", objectId },
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
}),
|
|
511
|
+
{ concurrency: "unbounded" },
|
|
512
|
+
);
|
|
508
513
|
return out;
|
|
509
|
-
}
|
|
514
|
+
});
|
|
510
515
|
|
|
511
516
|
// ============================================================================
|
|
512
517
|
// Composition
|
|
513
518
|
// ============================================================================
|
|
514
519
|
|
|
515
520
|
/** Fetches multiple compositions by ID in one Core request. */
|
|
516
|
-
export
|
|
517
|
-
client: ClientWithCoreApi,
|
|
521
|
+
export const getCompositionsByIds = Effect.fn("getCompositionsByIds")(function* (
|
|
518
522
|
compositionIds: string[],
|
|
519
|
-
):
|
|
523
|
+
): Effect.fn.Return<Record<string, Composition>, SuiRpcError | BcsDecodeError, SuiClient> {
|
|
520
524
|
if (compositionIds.length === 0) return {};
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
return
|
|
534
|
-
}
|
|
525
|
+
const contents = yield* getObjectsContent(compositionIds);
|
|
526
|
+
const decoded = yield* Effect.forEach(
|
|
527
|
+
[...contents.entries()],
|
|
528
|
+
([objectId, { content }]) =>
|
|
529
|
+
decodeBcs(
|
|
530
|
+
{ parse: (bytes) => mapComposition(objectId, CompositionBcs.parse(bytes)) },
|
|
531
|
+
Composition,
|
|
532
|
+
content,
|
|
533
|
+
{ type: "Composition", objectId },
|
|
534
|
+
).pipe(Effect.map((composition) => [objectId, composition] as const)),
|
|
535
|
+
{ concurrency: "unbounded" },
|
|
536
|
+
);
|
|
537
|
+
return Object.fromEntries(decoded);
|
|
538
|
+
});
|
|
535
539
|
|
|
536
540
|
/** Fetches a composition by its object ID. */
|
|
537
|
-
export
|
|
538
|
-
client: ClientWithCoreApi,
|
|
541
|
+
export function getCompositionById(
|
|
539
542
|
compositionId: string,
|
|
540
|
-
):
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
543
|
+
): Effect.Effect<Composition, ObjectNotFoundError | SuiRpcError | BcsDecodeError, SuiClient> {
|
|
544
|
+
return getObjectByBcs(
|
|
545
|
+
compositionId,
|
|
546
|
+
{ parse: (bytes) => mapComposition(compositionId, CompositionBcs.parse(bytes)) },
|
|
547
|
+
Composition,
|
|
548
|
+
"Composition",
|
|
549
|
+
);
|
|
544
550
|
}
|
|
545
551
|
|
|
546
552
|
/** Extracts the share type `T` from a `Composition<T>` object. */
|
|
547
|
-
export
|
|
548
|
-
client: ClientWithCoreApi,
|
|
553
|
+
export const getCompositionShareType = Effect.fn("getCompositionShareType")(function* (
|
|
549
554
|
compositionId: string,
|
|
550
|
-
):
|
|
551
|
-
const
|
|
552
|
-
return extractTypeParam(
|
|
553
|
-
}
|
|
555
|
+
): Effect.fn.Return<string, ObjectNotFoundError | SuiRpcError, SuiClient> {
|
|
556
|
+
const type = yield* getObjectType(compositionId);
|
|
557
|
+
return extractTypeParam(type);
|
|
558
|
+
});
|
|
554
559
|
|
|
555
560
|
/** Fetches a composition by its share type (GraphQL discovery + Core read). */
|
|
556
|
-
export
|
|
557
|
-
client: ClientWithCoreApi,
|
|
558
|
-
graphqlClient: SuiGraphQLClient,
|
|
561
|
+
export const getCompositionByShareType = Effect.fn("getCompositionByShareType")(function* (
|
|
559
562
|
shareType: string,
|
|
560
563
|
misoPackageId: string,
|
|
561
|
-
):
|
|
562
|
-
const address =
|
|
563
|
-
|
|
564
|
-
shareType
|
|
565
|
-
|
|
566
|
-
);
|
|
567
|
-
|
|
568
|
-
throw new Error(`Composition not found for share type: ${shareType}`);
|
|
569
|
-
return getCompositionById(client, address);
|
|
570
|
-
}
|
|
564
|
+
): Effect.fn.Return<Composition, ObjectNotFoundError | SuiRpcError | BcsDecodeError, SuiClient | SuiGraphQL> {
|
|
565
|
+
const address = yield* getCompositionAddressByShareType(shareType, misoPackageId);
|
|
566
|
+
if (Option.isNone(address)) {
|
|
567
|
+
return yield* new ObjectNotFoundError({ objectId: `composition::Composition<${shareType}>` });
|
|
568
|
+
}
|
|
569
|
+
return yield* getCompositionById(address.value);
|
|
570
|
+
});
|
|
571
571
|
|
|
572
572
|
/**
|
|
573
573
|
* Resolves a composition share type to its object address.
|
|
@@ -576,22 +576,20 @@ export async function getCompositionByShareType(
|
|
|
576
576
|
* composition's identity but will read extension fields rather than the core
|
|
577
577
|
* Composition contents.
|
|
578
578
|
*/
|
|
579
|
-
export
|
|
580
|
-
graphqlClient: SuiGraphQLClient,
|
|
579
|
+
export function getCompositionAddressByShareType(
|
|
581
580
|
shareType: string,
|
|
582
581
|
misoPackageId: string,
|
|
583
|
-
):
|
|
582
|
+
): Effect.Effect<Option.Option<string>, SuiRpcError, SuiGraphQL> {
|
|
584
583
|
const type = `${misoPackageId}::composition::Composition<${shareType}>`;
|
|
585
|
-
return firstAddressOfType(
|
|
584
|
+
return firstAddressOfType(type);
|
|
586
585
|
}
|
|
587
586
|
|
|
588
|
-
export
|
|
589
|
-
client: ClientWithCoreApi,
|
|
587
|
+
export const getCompositionAdminCapById = Effect.fn("getCompositionAdminCapById")(function* (
|
|
590
588
|
adminCapId: string,
|
|
591
|
-
):
|
|
592
|
-
const
|
|
593
|
-
return { id: adminCapId, shareType: extractTypeParam(
|
|
594
|
-
}
|
|
589
|
+
): Effect.fn.Return<CompositionAdminCap, ObjectNotFoundError | SuiRpcError, SuiClient> {
|
|
590
|
+
const type = yield* getObjectType(adminCapId);
|
|
591
|
+
return new CompositionAdminCap({ id: adminCapId, shareType: extractTypeParam(type) });
|
|
592
|
+
});
|
|
595
593
|
|
|
596
594
|
/**
|
|
597
595
|
* Composition admin caps owned by `owner`.
|
|
@@ -600,25 +598,21 @@ export async function getCompositionAdminCapById(
|
|
|
600
598
|
* object's instantiated `type`, so the share type is read straight off
|
|
601
599
|
* `CompositionAdminCap<CompositionShare>` with no second round-trip.
|
|
602
600
|
*/
|
|
603
|
-
export
|
|
604
|
-
client: ClientWithCoreApi,
|
|
601
|
+
export const getOwnedCompositionAdminCaps = Effect.fn("getOwnedCompositionAdminCaps")(function* (
|
|
605
602
|
owner: string,
|
|
606
603
|
misoPackageId: string,
|
|
607
|
-
):
|
|
604
|
+
): Effect.fn.Return<CompositionAdminCap[], SuiRpcError, SuiClient> {
|
|
608
605
|
const capType = `${misoPackageId}::composition::CompositionAdminCap`;
|
|
609
|
-
const objects =
|
|
606
|
+
const objects = yield* listAllOwnedObjects({ owner, type: capType });
|
|
610
607
|
const caps: CompositionAdminCap[] = [];
|
|
611
608
|
for (const obj of objects) {
|
|
612
609
|
const match = obj.type?.match(/<(.+)>$/);
|
|
613
|
-
if (match?.[1]) caps.push({ id: obj.objectId, shareType: match[1] });
|
|
610
|
+
if (match?.[1]) caps.push(new CompositionAdminCap({ id: obj.objectId, shareType: match[1] }));
|
|
614
611
|
}
|
|
615
612
|
return caps;
|
|
616
|
-
}
|
|
613
|
+
});
|
|
617
614
|
|
|
618
|
-
export function deriveCompositionAdminCapId(
|
|
619
|
-
compositionId: string,
|
|
620
|
-
misoPackageId: string,
|
|
621
|
-
): string {
|
|
615
|
+
export function deriveCompositionAdminCapId(compositionId: string, misoPackageId: string): string {
|
|
622
616
|
return deriveObjectID(
|
|
623
617
|
compositionId,
|
|
624
618
|
`${misoPackageId}::composition::CompositionAdminCapKey`,
|
|
@@ -630,33 +624,34 @@ export function deriveCompositionAdminCapId(
|
|
|
630
624
|
// Recording
|
|
631
625
|
// ============================================================================
|
|
632
626
|
|
|
633
|
-
export
|
|
634
|
-
client: ClientWithCoreApi,
|
|
627
|
+
export const getRecordingsByIds = Effect.fn("getRecordingsByIds")(function* (
|
|
635
628
|
recordingIds: string[],
|
|
636
|
-
):
|
|
629
|
+
): Effect.fn.Return<Record<string, Recording>, SuiRpcError | BcsDecodeError, SuiClient> {
|
|
637
630
|
if (recordingIds.length === 0) return {};
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
return
|
|
651
|
-
}
|
|
631
|
+
const contents = yield* getObjectsContent(recordingIds);
|
|
632
|
+
const decoded = yield* Effect.forEach(
|
|
633
|
+
[...contents.entries()],
|
|
634
|
+
([objectId, { content }]) =>
|
|
635
|
+
decodeBcs(
|
|
636
|
+
{ parse: (bytes) => mapRecording(objectId, RecordingBcs.parse(bytes)) },
|
|
637
|
+
Recording,
|
|
638
|
+
content,
|
|
639
|
+
{ type: "Recording", objectId },
|
|
640
|
+
).pipe(Effect.map((recording) => [objectId, recording] as const)),
|
|
641
|
+
{ concurrency: "unbounded" },
|
|
642
|
+
);
|
|
643
|
+
return Object.fromEntries(decoded);
|
|
644
|
+
});
|
|
652
645
|
|
|
653
|
-
export
|
|
654
|
-
client: ClientWithCoreApi,
|
|
646
|
+
export function getRecordingById(
|
|
655
647
|
recordingId: string,
|
|
656
|
-
):
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
648
|
+
): Effect.Effect<Recording, ObjectNotFoundError | SuiRpcError | BcsDecodeError, SuiClient> {
|
|
649
|
+
return getObjectByBcs(
|
|
650
|
+
recordingId,
|
|
651
|
+
{ parse: (bytes) => mapRecording(recordingId, RecordingBcs.parse(bytes)) },
|
|
652
|
+
Recording,
|
|
653
|
+
"Recording",
|
|
654
|
+
);
|
|
660
655
|
}
|
|
661
656
|
|
|
662
657
|
/**
|
|
@@ -665,16 +660,12 @@ export async function getRecordingById(
|
|
|
665
660
|
* them and returns the first; use {@link getRecordingShareTypes} when the
|
|
666
661
|
* parent composition's share type is needed too.
|
|
667
662
|
*/
|
|
668
|
-
export
|
|
669
|
-
client: ClientWithCoreApi,
|
|
663
|
+
export const getRecordingShareType = Effect.fn("getRecordingShareType")(function* (
|
|
670
664
|
recordingId: string,
|
|
671
|
-
):
|
|
672
|
-
const [recordingShareType] =
|
|
673
|
-
client,
|
|
674
|
-
recordingId,
|
|
675
|
-
);
|
|
665
|
+
): Effect.fn.Return<string, ObjectNotFoundError | SuiRpcError, SuiClient> {
|
|
666
|
+
const [recordingShareType] = yield* getRecordingShareTypes(recordingId);
|
|
676
667
|
return recordingShareType;
|
|
677
|
-
}
|
|
668
|
+
});
|
|
678
669
|
|
|
679
670
|
/**
|
|
680
671
|
* Both of a recording's share types, as `[RecordingShare, CompositionShare]`.
|
|
@@ -682,37 +673,30 @@ export async function getRecordingShareType(
|
|
|
682
673
|
* and the recording credit/pool extensions are all generic over both, in this
|
|
683
674
|
* order.
|
|
684
675
|
*/
|
|
685
|
-
export
|
|
686
|
-
client: ClientWithCoreApi,
|
|
676
|
+
export const getRecordingShareTypes = Effect.fn("getRecordingShareTypes")(function* (
|
|
687
677
|
recordingId: string,
|
|
688
|
-
):
|
|
689
|
-
const
|
|
690
|
-
return extractTypeParams2(
|
|
691
|
-
}
|
|
678
|
+
): Effect.fn.Return<[string, string], ObjectNotFoundError | SuiRpcError, SuiClient> {
|
|
679
|
+
const type = yield* getObjectType(recordingId);
|
|
680
|
+
return extractTypeParams2(type);
|
|
681
|
+
});
|
|
692
682
|
|
|
693
|
-
export
|
|
694
|
-
client: ClientWithCoreApi,
|
|
695
|
-
graphqlClient: SuiGraphQLClient,
|
|
683
|
+
export const getRecordingByShareType = Effect.fn("getRecordingByShareType")(function* (
|
|
696
684
|
shareType: string,
|
|
697
685
|
misoPackageId: string,
|
|
698
|
-
):
|
|
699
|
-
const address =
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
);
|
|
704
|
-
|
|
705
|
-
throw new Error(`Recording not found for share type: ${shareType}`);
|
|
706
|
-
return getRecordingById(client, address);
|
|
707
|
-
}
|
|
686
|
+
): Effect.fn.Return<Recording, ObjectNotFoundError | SuiRpcError | BcsDecodeError, SuiClient | SuiGraphQL> {
|
|
687
|
+
const address = yield* addressOfRecordingWithShareType(misoPackageId, shareType);
|
|
688
|
+
if (Option.isNone(address)) {
|
|
689
|
+
return yield* new ObjectNotFoundError({ objectId: `recording::Recording<${shareType}, ...>` });
|
|
690
|
+
}
|
|
691
|
+
return yield* getRecordingById(address.value);
|
|
692
|
+
});
|
|
708
693
|
|
|
709
|
-
export
|
|
710
|
-
client: ClientWithCoreApi,
|
|
694
|
+
export const getRecordingAdminCapById = Effect.fn("getRecordingAdminCapById")(function* (
|
|
711
695
|
adminCapId: string,
|
|
712
|
-
):
|
|
713
|
-
const
|
|
714
|
-
return { id: adminCapId, shareType: extractTypeParam(
|
|
715
|
-
}
|
|
696
|
+
): Effect.fn.Return<RecordingAdminCap, ObjectNotFoundError | SuiRpcError, SuiClient> {
|
|
697
|
+
const type = yield* getObjectType(adminCapId);
|
|
698
|
+
return new RecordingAdminCap({ id: adminCapId, shareType: extractTypeParam(type) });
|
|
699
|
+
});
|
|
716
700
|
|
|
717
701
|
/**
|
|
718
702
|
* Recording admin caps owned by `owner`.
|
|
@@ -722,108 +706,89 @@ export async function getRecordingAdminCapById(
|
|
|
722
706
|
* so this yields only the recording's own share type — its parent composition's
|
|
723
707
|
* share type is not recoverable from the cap alone.
|
|
724
708
|
*/
|
|
725
|
-
export
|
|
726
|
-
client: ClientWithCoreApi,
|
|
709
|
+
export const getOwnedRecordingAdminCaps = Effect.fn("getOwnedRecordingAdminCaps")(function* (
|
|
727
710
|
owner: string,
|
|
728
711
|
misoPackageId: string,
|
|
729
|
-
):
|
|
712
|
+
): Effect.fn.Return<RecordingAdminCap[], SuiRpcError, SuiClient> {
|
|
730
713
|
const capType = `${misoPackageId}::recording::RecordingAdminCap`;
|
|
731
|
-
const objects =
|
|
714
|
+
const objects = yield* listAllOwnedObjects({ owner, type: capType });
|
|
732
715
|
const caps: RecordingAdminCap[] = [];
|
|
733
716
|
for (const obj of objects) {
|
|
734
717
|
const match = obj.type?.match(/<(.+)>$/);
|
|
735
|
-
if (match?.[1]) caps.push({ id: obj.objectId, shareType: match[1] });
|
|
718
|
+
if (match?.[1]) caps.push(new RecordingAdminCap({ id: obj.objectId, shareType: match[1] }));
|
|
736
719
|
}
|
|
737
720
|
return caps;
|
|
738
|
-
}
|
|
721
|
+
});
|
|
739
722
|
|
|
740
|
-
export function deriveRecordingAdminCapId(
|
|
741
|
-
recordingId
|
|
742
|
-
misoPackageId: string,
|
|
743
|
-
): string {
|
|
744
|
-
return deriveObjectID(
|
|
745
|
-
recordingId,
|
|
746
|
-
`${misoPackageId}::recording::RecordingAdminCapKey`,
|
|
747
|
-
UNIT_STRUCT_KEY_BYTES,
|
|
748
|
-
);
|
|
723
|
+
export function deriveRecordingAdminCapId(recordingId: string, misoPackageId: string): string {
|
|
724
|
+
return deriveObjectID(recordingId, `${misoPackageId}::recording::RecordingAdminCapKey`, UNIT_STRUCT_KEY_BYTES);
|
|
749
725
|
}
|
|
750
726
|
|
|
751
727
|
// ============================================================================
|
|
752
728
|
// Release
|
|
753
729
|
// ============================================================================
|
|
754
730
|
|
|
755
|
-
export
|
|
756
|
-
client: ClientWithCoreApi,
|
|
731
|
+
export const getReleasesByIds = Effect.fn("getReleasesByIds")(function* (
|
|
757
732
|
releaseIds: string[],
|
|
758
|
-
):
|
|
733
|
+
): Effect.fn.Return<Record<string, Release>, SuiRpcError | BcsDecodeError, SuiClient> {
|
|
759
734
|
if (releaseIds.length === 0) return {};
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
}
|
|
735
|
+
const contents = yield* getObjectsContent(releaseIds);
|
|
736
|
+
const decoded = yield* Effect.forEach(
|
|
737
|
+
[...contents.entries()],
|
|
738
|
+
([objectId, { content }]) =>
|
|
739
|
+
decodeBcs(
|
|
740
|
+
{ parse: (bytes) => mapRelease(objectId, ReleaseBcs.parse(bytes)) },
|
|
741
|
+
Release,
|
|
742
|
+
content,
|
|
743
|
+
{ type: "Release", objectId },
|
|
744
|
+
).pipe(Effect.map((release) => [objectId, release] as const)),
|
|
745
|
+
{ concurrency: "unbounded" },
|
|
746
|
+
);
|
|
747
|
+
return Object.fromEntries(decoded);
|
|
748
|
+
});
|
|
771
749
|
|
|
772
|
-
export
|
|
773
|
-
client: ClientWithCoreApi,
|
|
750
|
+
export function getReleaseById(
|
|
774
751
|
releaseId: string,
|
|
775
|
-
):
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
752
|
+
): Effect.Effect<Release, ObjectNotFoundError | SuiRpcError | BcsDecodeError, SuiClient> {
|
|
753
|
+
return getObjectByBcs(
|
|
754
|
+
releaseId,
|
|
755
|
+
{ parse: (bytes) => mapRelease(releaseId, ReleaseBcs.parse(bytes)) },
|
|
756
|
+
Release,
|
|
757
|
+
"Release",
|
|
758
|
+
);
|
|
782
759
|
}
|
|
783
760
|
|
|
784
|
-
export
|
|
785
|
-
client: ClientWithCoreApi,
|
|
761
|
+
export const getReleaseAdminCapById = Effect.fn("getReleaseAdminCapById")(function* (
|
|
786
762
|
adminCapId: string,
|
|
787
|
-
):
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
include: { json: true },
|
|
763
|
+
): Effect.fn.Return<ReleaseAdminCap, ObjectNotFoundError | SuiRpcError, SuiClient> {
|
|
764
|
+
const client = yield* SuiClient;
|
|
765
|
+
const { object } = yield* Effect.tryPromise({
|
|
766
|
+
try: (signal) => client.core.getObject({ objectId: adminCapId, include: { json: true }, signal }),
|
|
767
|
+
catch: (cause) => classifyMissing(adminCapId, "getObject", cause),
|
|
791
768
|
});
|
|
792
769
|
const json = object.json as { release_id: string } | null;
|
|
793
|
-
if (!json?.release_id)
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
}
|
|
770
|
+
if (!json?.release_id) {
|
|
771
|
+
return yield* new ObjectNotFoundError({ objectId: adminCapId });
|
|
772
|
+
}
|
|
773
|
+
return new ReleaseAdminCap({ id: adminCapId, releaseId: json.release_id });
|
|
774
|
+
});
|
|
797
775
|
|
|
798
|
-
export
|
|
799
|
-
client: ClientWithCoreApi,
|
|
776
|
+
export const getOwnedReleaseAdminCaps = Effect.fn("getOwnedReleaseAdminCaps")(function* (
|
|
800
777
|
owner: string,
|
|
801
778
|
misoPackageId: string,
|
|
802
|
-
):
|
|
779
|
+
): Effect.fn.Return<ReleaseAdminCap[], SuiRpcError, SuiClient> {
|
|
803
780
|
const capType = `${misoPackageId}::release::ReleaseAdminCap`;
|
|
804
|
-
const objects =
|
|
805
|
-
owner,
|
|
806
|
-
type: capType,
|
|
807
|
-
include: { json: true },
|
|
808
|
-
});
|
|
781
|
+
const objects = yield* listAllOwnedObjects({ owner, type: capType, include: { json: true } });
|
|
809
782
|
const caps: ReleaseAdminCap[] = [];
|
|
810
783
|
for (const obj of objects) {
|
|
811
784
|
const json = obj.json as { release_id: string } | null;
|
|
812
|
-
if (json?.release_id)
|
|
813
|
-
caps.push({ id: obj.objectId, releaseId: json.release_id });
|
|
785
|
+
if (json?.release_id) caps.push(new ReleaseAdminCap({ id: obj.objectId, releaseId: json.release_id }));
|
|
814
786
|
}
|
|
815
787
|
return caps;
|
|
816
|
-
}
|
|
788
|
+
});
|
|
817
789
|
|
|
818
|
-
export function deriveReleaseAdminCapId(
|
|
819
|
-
releaseId
|
|
820
|
-
misoPackageId: string,
|
|
821
|
-
): string {
|
|
822
|
-
return deriveObjectID(
|
|
823
|
-
releaseId,
|
|
824
|
-
`${misoPackageId}::release::ReleaseAdminCapKey`,
|
|
825
|
-
UNIT_STRUCT_KEY_BYTES,
|
|
826
|
-
);
|
|
790
|
+
export function deriveReleaseAdminCapId(releaseId: string, misoPackageId: string): string {
|
|
791
|
+
return deriveObjectID(releaseId, `${misoPackageId}::release::ReleaseAdminCapKey`, UNIT_STRUCT_KEY_BYTES);
|
|
827
792
|
}
|
|
828
793
|
|
|
829
794
|
// ============================================================================
|
|
@@ -831,13 +796,12 @@ export function deriveReleaseAdminCapId(
|
|
|
831
796
|
// ============================================================================
|
|
832
797
|
|
|
833
798
|
/** Extracts the share type `T` from a `Currency<T>` object. */
|
|
834
|
-
export
|
|
835
|
-
client: ClientWithCoreApi,
|
|
799
|
+
export const getShareCurrencyType = Effect.fn("getShareCurrencyType")(function* (
|
|
836
800
|
shareCurrencyId: string,
|
|
837
|
-
):
|
|
838
|
-
const
|
|
839
|
-
return extractTypeParam(
|
|
840
|
-
}
|
|
801
|
+
): Effect.fn.Return<string, ObjectNotFoundError | SuiRpcError, SuiClient> {
|
|
802
|
+
const type = yield* getObjectType(shareCurrencyId);
|
|
803
|
+
return extractTypeParam(type);
|
|
804
|
+
});
|
|
841
805
|
|
|
842
806
|
/**
|
|
843
807
|
* Finds the `TreasuryCap<ShareType>` owned by `owner`. One Core API call.
|
|
@@ -850,43 +814,40 @@ export async function getShareCurrencyType(
|
|
|
850
814
|
* compose the two:
|
|
851
815
|
*
|
|
852
816
|
* ```ts
|
|
853
|
-
* const shareType =
|
|
854
|
-
* const capId =
|
|
817
|
+
* const shareType = yield* getShareCurrencyType(shareCurrencyId);
|
|
818
|
+
* const capId = yield* getShareCurrencyTreasuryCap(shareType, owner);
|
|
855
819
|
* ```
|
|
856
820
|
*/
|
|
857
|
-
export
|
|
858
|
-
client: ClientWithCoreApi,
|
|
821
|
+
export const getShareCurrencyTreasuryCap = Effect.fn("getShareCurrencyTreasuryCap")(function* (
|
|
859
822
|
shareType: string,
|
|
860
823
|
owner: string,
|
|
861
|
-
):
|
|
862
|
-
const objects =
|
|
863
|
-
owner,
|
|
864
|
-
type: `0x2::coin::TreasuryCap<${shareType}>`,
|
|
865
|
-
});
|
|
824
|
+
): Effect.fn.Return<string, SuiRpcError, SuiClient> {
|
|
825
|
+
const objects = yield* listAllOwnedObjects({ owner, type: `0x2::coin::TreasuryCap<${shareType}>` });
|
|
866
826
|
if (objects.length === 0) {
|
|
867
827
|
throw new Error(`No TreasuryCap found for ${shareType} owned by ${owner}`);
|
|
868
828
|
}
|
|
869
829
|
return objects[0]!.objectId;
|
|
870
|
-
}
|
|
830
|
+
});
|
|
871
831
|
|
|
872
832
|
// ============================================================================
|
|
873
833
|
// Private
|
|
874
834
|
// ============================================================================
|
|
875
835
|
|
|
876
|
-
/** Returns the first object address of a fully-qualified type, or
|
|
877
|
-
|
|
878
|
-
client: SuiGraphQLClient,
|
|
836
|
+
/** Returns the first object address of a fully-qualified type, or `Option.none()`. */
|
|
837
|
+
const firstAddressOfType = Effect.fn("firstAddressOfType")(function* (
|
|
879
838
|
type: string,
|
|
880
|
-
):
|
|
881
|
-
const
|
|
882
|
-
|
|
883
|
-
variables: { type },
|
|
839
|
+
): Effect.fn.Return<Option.Option<string>, SuiRpcError, SuiGraphQL> {
|
|
840
|
+
const client = yield* SuiGraphQL;
|
|
841
|
+
const result = yield* Effect.tryPromise({
|
|
842
|
+
try: () => client.query({ query: AddressesByTypeQuery, variables: { type } }),
|
|
843
|
+
catch: (cause) => new SuiRpcError({ operation: "addressesByType", cause }),
|
|
884
844
|
});
|
|
885
|
-
return result.data?.objects?.nodes?.[0]?.address
|
|
886
|
-
}
|
|
845
|
+
return Option.fromNullishOr(result.data?.objects?.nodes?.[0]?.address);
|
|
846
|
+
});
|
|
887
847
|
|
|
888
848
|
/**
|
|
889
|
-
* Address of the `Recording` whose FIRST type parameter is `shareType`, or
|
|
849
|
+
* Address of the `Recording` whose FIRST type parameter is `shareType`, or
|
|
850
|
+
* `Option.none()`.
|
|
890
851
|
*
|
|
891
852
|
* `Recording<RecordingShare, CompositionShare>` takes two parameters and a type
|
|
892
853
|
* filter must supply all of them or none, so filtering by
|
|
@@ -896,33 +857,34 @@ async function firstAddressOfType(
|
|
|
896
857
|
* first parameter client-side. A recording's share currency is unique to it, so
|
|
897
858
|
* the match is unambiguous.
|
|
898
859
|
*/
|
|
899
|
-
|
|
900
|
-
client: SuiGraphQLClient,
|
|
860
|
+
const addressOfRecordingWithShareType = Effect.fn("addressOfRecordingWithShareType")(function* (
|
|
901
861
|
misoPackageId: string,
|
|
902
862
|
shareType: string,
|
|
903
|
-
):
|
|
863
|
+
): Effect.fn.Return<Option.Option<string>, SuiRpcError, SuiGraphQL> {
|
|
864
|
+
const client = yield* SuiGraphQL;
|
|
904
865
|
let cursor: string | null | undefined;
|
|
905
866
|
do {
|
|
906
|
-
const result =
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
867
|
+
const result = yield* Effect.tryPromise({
|
|
868
|
+
try: () =>
|
|
869
|
+
client.query<{ objects?: WorkAddressConnection | null }, { type: string; cursor?: string | null }>({
|
|
870
|
+
query: `query RecordingAddress($type: String!, $cursor: String) {
|
|
871
|
+
objects(first: 50, after: $cursor, filter: { type: $type }) {
|
|
872
|
+
pageInfo { hasNextPage endCursor }
|
|
873
|
+
nodes { address asMoveObject { contents { type { repr } } } }
|
|
874
|
+
}
|
|
875
|
+
}`,
|
|
876
|
+
variables: { type: `${misoPackageId}::recording::Recording`, cursor },
|
|
877
|
+
}),
|
|
878
|
+
catch: (cause) => new SuiRpcError({ operation: "recordingAddress", cause }),
|
|
917
879
|
});
|
|
918
880
|
const page = result.data?.objects;
|
|
919
881
|
for (const node of page?.nodes ?? []) {
|
|
920
882
|
const repr = node?.asMoveObject?.contents?.type?.repr;
|
|
921
883
|
if (!repr || !node.address) continue;
|
|
922
884
|
const [recordingShareType] = extractTypeParams2(repr);
|
|
923
|
-
if (recordingShareType === shareType) return node.address;
|
|
885
|
+
if (recordingShareType === shareType) return Option.some(node.address);
|
|
924
886
|
}
|
|
925
887
|
cursor = page?.pageInfo?.hasNextPage ? page.pageInfo.endCursor : null;
|
|
926
888
|
} while (cursor);
|
|
927
|
-
return
|
|
928
|
-
}
|
|
889
|
+
return Option.none();
|
|
890
|
+
});
|