diff --git a/src/accounts/index.ts b/src/accounts/index.ts index b4f0cf2..848e4d6 100644 --- a/src/accounts/index.ts +++ b/src/accounts/index.ts @@ -7,6 +7,7 @@ import { import { accountsFindByType, AccountsFindByTypeConfig } from './find-by-type' import { accountsFindOne, AccountsFindOneConfig } from './find-one' import { accountsMemcmp, AccountsMemcmpConfig } from './memcmp' +import { MongoDbSDK } from './mongodb' export { AccountsAggregateConfig } from './aggregate' export { AccountsFilterByTypeConfig } from './filter-by-type' @@ -15,6 +16,8 @@ export { AccountsFindByTypeConfig } from './find-by-type' export { AccountsFindOneConfig } from './find-one' export { AccountsMemcmpConfig } from './memcmp' +export * from './mongodb' + /** * The Accounts SDK provides a set of functions for querying the Ironforge * accounts @@ -33,6 +36,13 @@ export class AccountsSdk { public readonly host: string ) {} + /** + * Provides access to the MongoDB SDK methods. + */ + get mongodb() { + return new MongoDbSDK(this.apiKey, this.host) + } + /** * Applies an aggregate function to the accounts of a program. * diff --git a/src/accounts/mongodb/aggregate.ts b/src/accounts/mongodb/aggregate.ts new file mode 100644 index 0000000..e98ec92 --- /dev/null +++ b/src/accounts/mongodb/aggregate.ts @@ -0,0 +1,36 @@ +import { AccountsRequestResult, MongoDbAggregateBody } from '../../types' +import { + Cluster, + requestHeaders, + tryExtractResultFromResponse, +} from '../../utils' + +/** Configures the accounts request for mongodb. */ +export type MongoDbAggregateConfig = { + /** The request body. */ + body: MongoDbAggregateBody + /** The cluster to execute the query on, i.e. mainnet or devnet. */ + cluster: Cluster + /** The program whose accounts we are querying. */ + program: string + /** The cache control header to use for the request. */ + cacheControl?: string +} + +export async function mongoDbAggregate( + apiKey: string, + host: string, + config: MongoDbAggregateConfig +) { + const { body, cluster, program, cacheControl } = config + + const res = await fetch( + `https://${host}/v1/${cluster}/${program}/mongodb` + `?apiKey=${apiKey}`, + { + headers: requestHeaders({ cacheControl }), + body: JSON.stringify(body), + method: 'POST', + } + ) + return tryExtractResultFromResponse>(res) +} diff --git a/src/accounts/mongodb/find.ts b/src/accounts/mongodb/find.ts new file mode 100644 index 0000000..df72cd9 --- /dev/null +++ b/src/accounts/mongodb/find.ts @@ -0,0 +1,36 @@ +import { AccountsRequestResultWithMetadata, MongoDbFindBody } from '../../types' +import { + Cluster, + requestHeaders, + tryExtractResultFromResponse, +} from '../../utils' + +/** Configures the accounts request for mongodb. */ +export type MongoDbFindConfig = { + /** The request body. */ + body: MongoDbFindBody + /** The cluster to execute the query on, i.e. mainnet or devnet. */ + cluster: Cluster + /** The program whose accounts we are querying. */ + program: string + /** The cache control header to use for the request. */ + cacheControl?: string +} + +export async function mongoDbFind( + apiKey: string, + host: string, + config: MongoDbFindConfig +) { + const { body, cluster, program, cacheControl } = config + + const res = await fetch( + `https://${host}/v1/${cluster}/${program}/mongodb` + `?apiKey=${apiKey}`, + { + headers: requestHeaders({ cacheControl }), + body: JSON.stringify(body), + method: 'POST', + } + ) + return tryExtractResultFromResponse>(res) +} diff --git a/src/accounts/mongodb/index.ts b/src/accounts/mongodb/index.ts new file mode 100644 index 0000000..14901f4 --- /dev/null +++ b/src/accounts/mongodb/index.ts @@ -0,0 +1,43 @@ +import { MongoDbAggregateConfig, mongoDbAggregate } from './aggregate' +import { MongoDbFindConfig, mongoDbFind } from './find' + +export { MongoDbFindConfig } from './find' +export { MongoDbAggregateConfig } from './aggregate' + +/** + * The MongoDbSDK provides a set of functions for querying + * Ironforge accounts via MongoDB + */ +export class MongoDbSDK { + /** + * Creates a new MongoDB SDK instance. + * Invoked via the Accounts SDK mongodb property. + * + * @param apiKey The API key to use for all requests. + * @param host The host to use for all requests. + */ + constructor( + public readonly apiKey: string, + public readonly host: string + ) {} + + /** + * Query accounts of a program using a MongoDB find query. + * + * @template T The expected type of the find result. + * @returns The result along with the response status. + */ + find(config: MongoDbFindConfig) { + return mongoDbFind(this.apiKey, this.host, config) + } + + /** + * Apply an aggregation pipeline to the accounts of a program using MongoDB aggregation query. + * + * @template T The expected type of the aggregation result. + * @returns The result along with the response status. + */ + aggregate(config: MongoDbAggregateConfig) { + return mongoDbAggregate(this.apiKey, this.host, config) + } +} diff --git a/src/types.ts b/src/types.ts index 9f5b397..c32f4eb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -103,3 +103,16 @@ export type AccountsMemcmpBody = { export type AccountsAggregateBody = { pipeline: Record[] } + +export type MongoDbFindBody = { + query?: object + projection?: object + options?: { + limit?: number + skip?: number + sort?: Sort + } +} +export type MongoDbAggregateBody = { + pipeline: Record[] +} diff --git a/test/accounts-sdk.ts b/test/accounts-sdk.ts index 8519373..86db87c 100644 --- a/test/accounts-sdk.ts +++ b/test/accounts-sdk.ts @@ -171,3 +171,84 @@ test('accounts-sdk: memcmp', async (t) => { }) assert.equal(result.data?.length, 3) }) + +// ----------------- +// MongoDB +// ----------------- +test('accounts-sdk: mongodb', async (t) => { + const sdk = new IronforgeSdk(API_KEY) + + await t.test('aggregation pipeline', async () => { + const { result, status } = await sdk.accounts.mongodb.aggregate({ + body: { + pipeline: [ + { + $match: { + account_type: 'CollectionPDA', + }, + }, + { + $project: { + account_type: 1, + _id: 1, + }, + }, + { + $limit: 10, + }, + ], + }, + cluster: 'devnet', + program: 'cndy3Z4yapfJBmL3ShUp5exZKqR3z33thTzeNMm2gRZ', + }) + + assert.equal(status, 200) + + const data = result.data + + assert.equal(data.length, 10) + + data.forEach((item) => { + assert.equal(Object.keys(item).length, 2) + assert.equal(item.account_type, 'CollectionPDA') + assert.ok(item._id) + }) + }) + + await t.test('find with sort + pagination', async () => { + const { result, status } = await sdk.accounts.mongodb.find({ + body: { + query: { + account_type: 'CollectionPDA', + }, + options: { + limit: 5, + skip: 10, + sort: [['size', 'asc']], + }, + }, + cluster: 'devnet', + program: 'cndy3Z4yapfJBmL3ShUp5exZKqR3z33thTzeNMm2gRZ', + }) + + assert.equal(status, 200) + + spok(t, result, { + metadata: { count: 5, offset: 10, limit: 5, hasMore: true }, + error: null, + }) + + const data = result.data + assert(data != null) + + assert.equal(data.length, 5) + + data.forEach((item) => { + assert.equal(item.account_type, 'CollectionPDA') + }) + + for (let i = 0; i < data.length - 1; i++) { + assert.ok(data[i].size <= data[i + 1].size) + } + }) +})