UNPKG

@mysten/sui

Version:

Sui TypeScript API(Work in Progress)

186 lines (156 loc) 5.17 kB
// Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 import { parseStructTag } from '../../utils/sui-types.js'; import type { BuildTransactionOptions } from '../json-rpc-resolver.js'; import type { TransactionDataBuilder } from '../TransactionData.js'; import type { NamedPackagesPluginCache } from './utils.js'; import { batch, findNamesInTransaction, getFirstLevelNamedTypes, populateNamedTypesFromCache, replaceNames, } from './utils.js'; export type NamedPackagesPluginOptions = { /** * The URL of the MVR API to use for resolving names. */ url: string; /** * The number of names to resolve in each batch request. * Needs to be calculated based on the GraphQL query limits. */ pageSize?: number; /** * Local overrides for the resolution plugin. Pass this to pre-populate * the cache with known packages / types (especially useful for local or CI testing). * * The type cache expects ONLY first-level types to ensure the cache is more composable. * * Expected format example: * { * packages: { * '@framework/std': '0x1234', * }, * types: { * '@framework/std::string::String': '0x1234::string::String', * }, * } * */ overrides?: NamedPackagesPluginCache; }; /** * @experimental This plugin is in experimental phase and there might be breaking changes in the future * * Adds named resolution so that you can use .move names in your transactions. * e.g. `@org/app::type::Type` will be resolved to `0x1234::type::Type`. * This plugin will resolve all names & types in the transaction block. * * To install this plugin globally in your app, use: * ``` * Transaction.registerGlobalSerializationPlugin("namedPackagesPlugin", namedPackagesPlugin({ suiGraphQLClient })); * ``` * * You can also define `overrides` to pre-populate name resolutions locally (removes the GraphQL request). */ export const namedPackagesPlugin = ({ url, pageSize = 50, overrides = { packages: {}, types: {} }, }: NamedPackagesPluginOptions) => { // validate that types are first-level only. Object.keys(overrides.types).forEach((type) => { if (parseStructTag(type).typeParams.length > 0) throw new Error( 'Type overrides must be first-level only. If you want to supply generic types, just pass each type individually.', ); }); const cache = overrides; return async ( transactionData: TransactionDataBuilder, _buildOptions: BuildTransactionOptions, next: () => Promise<void>, ) => { const names = findNamesInTransaction(transactionData); const [packages, types] = await Promise.all([ resolvePackages( names.packages.filter((x) => !cache.packages[x]), url, pageSize, ), resolveTypes( [...getFirstLevelNamedTypes(names.types)].filter((x) => !cache.types[x]), url, pageSize, ), ]); // save first-level mappings to cache. Object.assign(cache.packages, packages); Object.assign(cache.types, types); const composedTypes = populateNamedTypesFromCache(names.types, cache.types); // when replacing names, we also need to replace the "composed" types collected above. replaceNames(transactionData, { packages: { ...cache.packages }, // we include the "composed" type cache too. types: composedTypes, }); await next(); }; async function resolvePackages(packages: string[], apiUrl: string, pageSize: number) { if (packages.length === 0) return {}; const batches = batch(packages, pageSize); const results: Record<string, string> = {}; await Promise.all( batches.map(async (batch) => { const response = await fetch(`${apiUrl}/v1/resolution/bulk`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ names: batch, }), }); if (!response.ok) { const errorBody = await response.json().catch(() => ({})); throw new Error(`Failed to resolve packages: ${errorBody?.message}`); } const data = await response.json(); if (!data?.resolution) return; for (const pkg of Object.keys(data?.resolution)) { const pkgData = data.resolution[pkg]?.package_id; if (!pkgData) continue; results[pkg] = pkgData; } }), ); return results; } async function resolveTypes(types: string[], apiUrl: string, pageSize: number) { if (types.length === 0) return {}; const batches = batch(types, pageSize); const results: Record<string, string> = {}; await Promise.all( batches.map(async (batch) => { const response = await fetch(`${apiUrl}/v1/struct-definition/bulk`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ types: batch, }), }); if (!response.ok) { const errorBody = await response.json().catch(() => ({})); throw new Error(`Failed to resolve types: ${errorBody?.message}`); } const data = await response.json(); if (!data?.resolution) return; for (const type of Object.keys(data?.resolution)) { const typeData = data.resolution[type]?.type_tag; if (!typeData) continue; results[type] = typeData; } }), ); return results; } };