From 4fcd2d73a82c928533cbecb3639d388a0185ba42 Mon Sep 17 00:00:00 2001 From: Sayantan Karmakar Date: Tue, 12 Sep 2023 16:35:58 +0530 Subject: [PATCH 1/4] feat: add mongodb method for accounts api --- src/accounts/index.ts | 6 ++++ src/accounts/mongodb.ts | 43 ++++++++++++++++++++++ src/types.ts | 17 +++++++++ src/utils/guards.ts | 17 +++++++++ test/accounts-sdk.ts | 79 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 162 insertions(+) create mode 100644 src/accounts/mongodb.ts create mode 100644 src/utils/guards.ts diff --git a/src/accounts/index.ts b/src/accounts/index.ts index b4f0cf2..263093e 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 { accountsMongoDb, AccountsMongoDbConfig } from './mongodb' export { AccountsAggregateConfig } from './aggregate' export { AccountsFilterByTypeConfig } from './filter-by-type' @@ -14,6 +15,7 @@ export { AccountsFilterConfig } from './filter' export { AccountsFindByTypeConfig } from './find-by-type' export { AccountsFindOneConfig } from './find-one' export { AccountsMemcmpConfig } from './memcmp' +export { AccountsMongoDbConfig } from './mongodb' /** * The Accounts SDK provides a set of functions for querying the Ironforge @@ -33,6 +35,10 @@ export class AccountsSdk { public readonly host: string ) {} + mongodb(config: AccountsMongoDbConfig) { + return accountsMongoDb(this.apiKey, this.host, config) + } + /** * Applies an aggregate function to the accounts of a program. * diff --git a/src/accounts/mongodb.ts b/src/accounts/mongodb.ts new file mode 100644 index 0000000..f4b92b7 --- /dev/null +++ b/src/accounts/mongodb.ts @@ -0,0 +1,43 @@ +import { + AccountsMongoDbBody, + AccountsRequestResult, + AccountsRequestResultWithMetadata, +} from '../types' +import { Cluster, requestHeaders, tryExtractResultFromResponse } from '../utils' +import { isAggregateRequest } from '../utils/guards' + +/** Configures the accounts request for mongodb. */ +export type AccountsMongoDbConfig = { + /** The request body. */ + body: AccountsMongoDbBody + /** 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 accountsMongoDb( + apiKey: string, + host: string, + config: AccountsMongoDbConfig +) { + 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', + } + ) + if (isAggregateRequest(body)) { + return tryExtractResultFromResponse>(res) + } else { + return tryExtractResultFromResponse>( + res + ) + } +} diff --git a/src/types.ts b/src/types.ts index 9f5b397..1903344 100644 --- a/src/types.ts +++ b/src/types.ts @@ -103,3 +103,20 @@ export type AccountsMemcmpBody = { export type AccountsAggregateBody = { pipeline: Record[] } + +export type AccountsMongoDbFindBody = { + query?: object + projection?: object + options?: { + limit?: number + skip?: number + sort?: Sort + } +} +export type AccountsMongoDbAggregateBody = { + pipeline: Record[] +} + +export type AccountsMongoDbBody = + | AccountsMongoDbFindBody + | AccountsMongoDbAggregateBody diff --git a/src/utils/guards.ts b/src/utils/guards.ts new file mode 100644 index 0000000..1f6ee84 --- /dev/null +++ b/src/utils/guards.ts @@ -0,0 +1,17 @@ +import { + AccountsMongoDbAggregateBody, + AccountsMongoDbBody, + AccountsMongoDbFindBody, +} from '../types' + +export function isFindRequest( + body: AccountsMongoDbBody +): body is AccountsMongoDbFindBody { + return (body as AccountsMongoDbFindBody).query != null +} + +export function isAggregateRequest( + body: AccountsMongoDbBody +): body is AccountsMongoDbAggregateBody { + return (body as AccountsMongoDbAggregateBody).pipeline != null +} diff --git a/test/accounts-sdk.ts b/test/accounts-sdk.ts index 8519373..e201de5 100644 --- a/test/accounts-sdk.ts +++ b/test/accounts-sdk.ts @@ -171,3 +171,82 @@ test('accounts-sdk: memcmp', async (t) => { }) assert.equal(result.data?.length, 3) }) + +// ----------------- +// MongoDB +// ----------------- +test('accounts-sdk: mongodb', async (t) => { + // TODO: Remove prefix + const sdk = new IronforgeSdk(API_KEY, 'dev') + + await t.test('aggregation pipeline', async () => { + const { result, status } = await sdk.accounts.mongodb({ + 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.ok(item.account_type) + assert.ok(item._id) + }) + }) + + await t.test('query with sort + pagination', async () => { + const { result, status } = await sdk.accounts.mongodb({ + 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 + data.forEach((item) => { + assert.equal(item.account_type, 'CollectionPDA') + }) + + assert.equal(data.length, 5) + for (let i = 0; i < data.length - 1; i++) { + assert.ok(data[i].size <= data[i + 1].size) + } + }) +}) From 4cb58b99d0debf4073f1812c55ad3dc249321919 Mon Sep 17 00:00:00 2001 From: Sayantan Karmakar Date: Wed, 13 Sep 2023 11:30:50 +0530 Subject: [PATCH 2/4] chore: add MongoDb SDK --- src/accounts/index.ts | 12 ++++-- .../{mongodb.ts => mongodb/aggregate.ts} | 27 +++++------- src/accounts/mongodb/find.ts | 36 ++++++++++++++++ src/accounts/mongodb/index.ts | 43 +++++++++++++++++++ src/types.ts | 8 +--- src/utils/guards.ts | 17 -------- test/accounts-sdk.ts | 11 +++-- 7 files changed, 106 insertions(+), 48 deletions(-) rename src/accounts/{mongodb.ts => mongodb/aggregate.ts} (52%) create mode 100644 src/accounts/mongodb/find.ts create mode 100644 src/accounts/mongodb/index.ts delete mode 100644 src/utils/guards.ts diff --git a/src/accounts/index.ts b/src/accounts/index.ts index 263093e..848e4d6 100644 --- a/src/accounts/index.ts +++ b/src/accounts/index.ts @@ -7,7 +7,7 @@ import { import { accountsFindByType, AccountsFindByTypeConfig } from './find-by-type' import { accountsFindOne, AccountsFindOneConfig } from './find-one' import { accountsMemcmp, AccountsMemcmpConfig } from './memcmp' -import { accountsMongoDb, AccountsMongoDbConfig } from './mongodb' +import { MongoDbSDK } from './mongodb' export { AccountsAggregateConfig } from './aggregate' export { AccountsFilterByTypeConfig } from './filter-by-type' @@ -15,7 +15,8 @@ export { AccountsFilterConfig } from './filter' export { AccountsFindByTypeConfig } from './find-by-type' export { AccountsFindOneConfig } from './find-one' export { AccountsMemcmpConfig } from './memcmp' -export { AccountsMongoDbConfig } from './mongodb' + +export * from './mongodb' /** * The Accounts SDK provides a set of functions for querying the Ironforge @@ -35,8 +36,11 @@ export class AccountsSdk { public readonly host: string ) {} - mongodb(config: AccountsMongoDbConfig) { - return accountsMongoDb(this.apiKey, this.host, config) + /** + * Provides access to the MongoDB SDK methods. + */ + get mongodb() { + return new MongoDbSDK(this.apiKey, this.host) } /** diff --git a/src/accounts/mongodb.ts b/src/accounts/mongodb/aggregate.ts similarity index 52% rename from src/accounts/mongodb.ts rename to src/accounts/mongodb/aggregate.ts index f4b92b7..e98ec92 100644 --- a/src/accounts/mongodb.ts +++ b/src/accounts/mongodb/aggregate.ts @@ -1,15 +1,14 @@ +import { AccountsRequestResult, MongoDbAggregateBody } from '../../types' import { - AccountsMongoDbBody, - AccountsRequestResult, - AccountsRequestResultWithMetadata, -} from '../types' -import { Cluster, requestHeaders, tryExtractResultFromResponse } from '../utils' -import { isAggregateRequest } from '../utils/guards' + Cluster, + requestHeaders, + tryExtractResultFromResponse, +} from '../../utils' /** Configures the accounts request for mongodb. */ -export type AccountsMongoDbConfig = { +export type MongoDbAggregateConfig = { /** The request body. */ - body: AccountsMongoDbBody + body: MongoDbAggregateBody /** The cluster to execute the query on, i.e. mainnet or devnet. */ cluster: Cluster /** The program whose accounts we are querying. */ @@ -18,10 +17,10 @@ export type AccountsMongoDbConfig = { cacheControl?: string } -export async function accountsMongoDb( +export async function mongoDbAggregate( apiKey: string, host: string, - config: AccountsMongoDbConfig + config: MongoDbAggregateConfig ) { const { body, cluster, program, cacheControl } = config @@ -33,11 +32,5 @@ export async function accountsMongoDb( method: 'POST', } ) - if (isAggregateRequest(body)) { - return tryExtractResultFromResponse>(res) - } else { - return tryExtractResultFromResponse>( - res - ) - } + 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 1903344..c32f4eb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -104,7 +104,7 @@ export type AccountsAggregateBody = { pipeline: Record[] } -export type AccountsMongoDbFindBody = { +export type MongoDbFindBody = { query?: object projection?: object options?: { @@ -113,10 +113,6 @@ export type AccountsMongoDbFindBody = { sort?: Sort } } -export type AccountsMongoDbAggregateBody = { +export type MongoDbAggregateBody = { pipeline: Record[] } - -export type AccountsMongoDbBody = - | AccountsMongoDbFindBody - | AccountsMongoDbAggregateBody diff --git a/src/utils/guards.ts b/src/utils/guards.ts deleted file mode 100644 index 1f6ee84..0000000 --- a/src/utils/guards.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - AccountsMongoDbAggregateBody, - AccountsMongoDbBody, - AccountsMongoDbFindBody, -} from '../types' - -export function isFindRequest( - body: AccountsMongoDbBody -): body is AccountsMongoDbFindBody { - return (body as AccountsMongoDbFindBody).query != null -} - -export function isAggregateRequest( - body: AccountsMongoDbBody -): body is AccountsMongoDbAggregateBody { - return (body as AccountsMongoDbAggregateBody).pipeline != null -} diff --git a/test/accounts-sdk.ts b/test/accounts-sdk.ts index e201de5..c2ca5a9 100644 --- a/test/accounts-sdk.ts +++ b/test/accounts-sdk.ts @@ -180,7 +180,7 @@ test('accounts-sdk: mongodb', async (t) => { const sdk = new IronforgeSdk(API_KEY, 'dev') await t.test('aggregation pipeline', async () => { - const { result, status } = await sdk.accounts.mongodb({ + const { result, status } = await sdk.accounts.mongodb.aggregate({ body: { pipeline: [ { @@ -216,8 +216,8 @@ test('accounts-sdk: mongodb', async (t) => { }) }) - await t.test('query with sort + pagination', async () => { - const { result, status } = await sdk.accounts.mongodb({ + await t.test('find with sort + pagination', async () => { + const { result, status } = await sdk.accounts.mongodb.find({ body: { query: { account_type: 'CollectionPDA', @@ -240,11 +240,14 @@ test('accounts-sdk: mongodb', async (t) => { }) const data = result.data + assert(data != null) + + assert.equal(data.length, 5) + data.forEach((item) => { assert.equal(item.account_type, 'CollectionPDA') }) - assert.equal(data.length, 5) for (let i = 0; i < data.length - 1; i++) { assert.ok(data[i].size <= data[i + 1].size) } From ef3f93669164243181d13ba63cbb1e79bb896c76 Mon Sep 17 00:00:00 2001 From: Sayantan Karmakar Date: Wed, 13 Sep 2023 11:32:17 +0530 Subject: [PATCH 3/4] nit: assert account_type in test --- test/accounts-sdk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/accounts-sdk.ts b/test/accounts-sdk.ts index c2ca5a9..2381834 100644 --- a/test/accounts-sdk.ts +++ b/test/accounts-sdk.ts @@ -211,7 +211,7 @@ test('accounts-sdk: mongodb', async (t) => { data.forEach((item) => { assert.equal(Object.keys(item).length, 2) - assert.ok(item.account_type) + assert.equal(item.account_type, 'CollectionPDA') assert.ok(item._id) }) }) From 4240ceee058ef21f9af2c4f37cf766370e7a1f63 Mon Sep 17 00:00:00 2001 From: Sayantan Karmakar Date: Thu, 14 Sep 2023 10:52:32 +0530 Subject: [PATCH 4/4] nit: remove dev prefix from mongo tests --- test/accounts-sdk.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/accounts-sdk.ts b/test/accounts-sdk.ts index 2381834..86db87c 100644 --- a/test/accounts-sdk.ts +++ b/test/accounts-sdk.ts @@ -176,8 +176,7 @@ test('accounts-sdk: memcmp', async (t) => { // MongoDB // ----------------- test('accounts-sdk: mongodb', async (t) => { - // TODO: Remove prefix - const sdk = new IronforgeSdk(API_KEY, 'dev') + const sdk = new IronforgeSdk(API_KEY) await t.test('aggregation pipeline', async () => { const { result, status } = await sdk.accounts.mongodb.aggregate({