diff --git a/packages/typescript/src/experimental/client.ts b/packages/typescript/src/experimental/client.ts index d6bd3c79a..2179d4812 100644 --- a/packages/typescript/src/experimental/client.ts +++ b/packages/typescript/src/experimental/client.ts @@ -10,13 +10,17 @@ import type { Experimental_SuiClientTypes, SuiClientRegistration, } from './types.js'; +import { ClientMiddlewareStack } from './middleware.js'; export abstract class Experimental_BaseClient { network: Experimental_SuiClientTypes.Network; cache = new ClientCache(); + middleware = new ClientMiddlewareStack(); + root: Experimental_BaseClient; - constructor({ network }: Experimental_SuiClientTypes.SuiClientOptions) { + constructor({ network, root }: Experimental_SuiClientTypes.SuiClientOptions) { this.network = network; + this.root = root ?? this; } abstract core: Experimental_CoreClient; diff --git a/packages/typescript/src/experimental/middleware.ts b/packages/typescript/src/experimental/middleware.ts new file mode 100644 index 000000000..6e33bea50 --- /dev/null +++ b/packages/typescript/src/experimental/middleware.ts @@ -0,0 +1,83 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { SuiClient } from '../client/index.js'; +import type { Experimental_SuiClientTypes } from './types.js'; + +type MiddlewareFunction = ( + scope: string, + method: string, + args: Args, + next: (...args: Args) => Promise, +) => Promise; + +export class ClientMiddlewareStack { + #middleware: MiddlewareFunction[] = []; + + use(middleware: MiddlewareFunction) { + if (this.#middleware.includes(middleware)) { + throw new Error(`Middleware ${middleware.name} is already registered`); + } + this.#middleware.push(middleware); + } + + wrap( + scope: string, + name: string, + fn: (...args: Args) => Promise, + ) { + const stack = this; + return function (this: T, ...args: Args) { + return stack.run(scope, name, fn, this, args); + }; + } + + run( + scope: string, + name: string, + fn: (...args: Args) => Promise, + ctx: unknown, + args: Args, + ) { + const createNext = (i: number) => { + let calledNext = false; + + return async (...args: Args): Promise => { + if (calledNext) { + throw new Error(`next() was call multiple times`); + } + + calledNext = true; + + if (i >= this.#middleware.length) { + return fn.apply(ctx, args); + } + + const middleware = this.#middleware[i]; + return middleware(scope, name, args, async (...args) => createNext(i + 1)(...args)); + }; + }; + + return createNext(0)(...args); + } +} + +new SuiClient({ + network: 'mainnet', + url: 'https://fullnode.mainnet.sui.io', +}).middleware.use(async (scope, name, args, next) => { + if (scope === 'core' && name === 'getObjects') { + const [options] = args as unknown as [Experimental_SuiClientTypes.GetObjectsOptions]; + + return next( + ...([ + { + ...options, + objectIds: await resolveMvrTypes(options.objectIds), + }, + ] as never), + ); + } + + return next(...args); +}); diff --git a/packages/typescript/src/experimental/transports/jsonRPC.ts b/packages/typescript/src/experimental/transports/jsonRPC.ts index 537f17e82..e80180f1e 100644 --- a/packages/typescript/src/experimental/transports/jsonRPC.ts +++ b/packages/typescript/src/experimental/transports/jsonRPC.ts @@ -23,37 +23,41 @@ export class JSONRpcTransport extends Experimental_CoreClient { #jsonRpcClient: SuiClient; constructor(jsonRpcClient: SuiClient) { - super({ network: jsonRpcClient.network }); + super({ network: jsonRpcClient.network, root: jsonRpcClient.root }); this.#jsonRpcClient = jsonRpcClient; } - async getObjects(options: Experimental_SuiClientTypes.GetObjectsOptions) { - const batches = batch(options.objectIds, 50); - const results: Experimental_SuiClientTypes.GetObjectsResponse['objects'] = []; - - for (const batch of batches) { - const objects = await this.#jsonRpcClient.multiGetObjects({ - ids: batch, - options: { - showOwner: true, - showType: true, - showBcs: true, - }, - }); + getObjects = this.root.middleware.wrap( + 'core', + 'getObjects', + async (options: Experimental_SuiClientTypes.GetObjectsOptions) => { + const batches = batch(options.objectIds, 50); + const results: Experimental_SuiClientTypes.GetObjectsResponse['objects'] = []; + + for (const batch of batches) { + const objects = await this.#jsonRpcClient.multiGetObjects({ + ids: batch, + options: { + showOwner: true, + showType: true, + showBcs: true, + }, + }); - for (const [idx, object] of objects.entries()) { - if (object.error) { - results.push(ObjectError.fromResponse(object.error, batch[idx])); - } else { - results.push(parseObject(object.data!)); + for (const [idx, object] of objects.entries()) { + if (object.error) { + results.push(ObjectError.fromResponse(object.error, batch[idx])); + } else { + results.push(parseObject(object.data!)); + } } } - } - return { - objects: results, - }; - } + return { + objects: results, + }; + }, + ); async getOwnedObjects(options: Experimental_SuiClientTypes.GetOwnedObjectsOptions) { const objects = await this.#jsonRpcClient.getOwnedObjects({ owner: options.address, diff --git a/packages/typescript/src/experimental/types.ts b/packages/typescript/src/experimental/types.ts index 32c1da8eb..7288fe6d9 100644 --- a/packages/typescript/src/experimental/types.ts +++ b/packages/typescript/src/experimental/types.ts @@ -36,6 +36,7 @@ export namespace Experimental_SuiClientTypes { export interface SuiClientOptions { network: Network; + root?: Experimental_BaseClient; } export interface CoreClientMethodOptions {