diff --git a/docs/pages/_meta.json b/docs/pages/_meta.json index e10cf38f..341ee45d 100644 --- a/docs/pages/_meta.json +++ b/docs/pages/_meta.json @@ -1,4 +1,5 @@ { "index": "Introduction", - "packages": "Packages" + "packages": "Packages", + "changelog": "Changelog" } diff --git a/docs/pages/changelog.mdx b/docs/pages/changelog.mdx new file mode 100644 index 00000000..9e0ddc41 --- /dev/null +++ b/docs/pages/changelog.mdx @@ -0,0 +1,11 @@ +# Changelog + +## 0.2.0 + +- Change types where before there was `any` to be strict. +- Removes `HttpProvider`. +- Rename query utils to create filters and order by. +- Compass response type must not include `data` anymore. +- Compass variables can be typed. +- Compass throws errors on malformed requests/queries. +- Fetch moments order must be value from enum `Order` instead of string. diff --git a/docs/pages/packages/providers/Compass.mdx b/docs/pages/packages/providers/Compass.mdx new file mode 100644 index 00000000..fa43952b --- /dev/null +++ b/docs/pages/packages/providers/Compass.mdx @@ -0,0 +1,88 @@ +# Compass Provider + +To query Compass, a simple request wrapper is provided. To initialize it you'll need an API key. + +```typescript +import { PoapCompass } from '@poap-xyz/providers'; + +const compass = new PoapCompass({ + apiKey: 'my-secret-api-key', +}); +``` + +## Requests + +Query requests can be made by giving the query in string and optionally the variables to pass. All +success requests are returned as an object with a `data` property which the structure differs +depending on the query done. When the request fail, errors are thrown instead. + +### Quering + +To do a query request, a response data type is needed and, in the case the query accepts variables, +a type for its structure is also needed. + +For example, if we want to retrieve the last POAP token ids we could have the query: + +```typescript +const query = ` + query LastTokenIds($limit: Int!) { + poaps(limit: $limit, order_by: { id: desc }) { + id + } + } +`; +``` + +Which will return the type: + +```typescript +type LastTokenIdsResponse = { + poaps: Array<{ + id: number; + }>; +}; +``` + +And have the variables type: + +```typescript +type LastTokenIdsVariables = { + limit: number; +}; +``` + +Then the query can be executed: + +```typescript +const lastTokenIds: number[] = []; +try { + const { data } = await compass.request< + LastTokenIdsResponse, + LastTokenIdsVariables + >(query, { limit: 3 }); + + lastTokenIds.push( + ...data.poaps.map((poap) => poap.id) + ); +} catch (error: unknown) { + console.error(error) +} +``` + +### Errors + +There are two types of errors, HTTP errors and requests errors. The former are thrown when there is +an issue with the HTTP requests itself and the later when the query has some malformed or +unavailable structure. + +#### HTTP errors + +When the HTTP request is malformed a `CompassBadRequestError` is thrown, which should not happen +unless there is a migration to be made. When the API key given is incorrect or expired, then a +`CompassUnauthorizedError` error will be thrown. + +#### Query errors + +All errors derived from a malformed structure on the query will throw a `CompassRequestError` which +has a public property called `errors` of the type `CompassError` with more information about what +went wrong. diff --git a/docs/pages/packages/providers/_meta.json b/docs/pages/packages/providers/_meta.json new file mode 100644 index 00000000..71e0b3b4 --- /dev/null +++ b/docs/pages/packages/providers/_meta.json @@ -0,0 +1,3 @@ +{ + "Compass": "Compass" +} diff --git a/docs/pages/packages/utils/Queries.mdx b/docs/pages/packages/utils/Queries.mdx new file mode 100644 index 00000000..cd68024b --- /dev/null +++ b/docs/pages/packages/utils/Queries.mdx @@ -0,0 +1,77 @@ +# Queries Utils + +When building Compass queries, it might be useful to have some little utilities to not repeat the +same code many times. There two types of utils for queries, building orders and building filters. + +## Order By + +When you have a query that accepts an `$orderBy` variable that let you input the field name and the +sort order, for example: + +```graphql +query MyDrops( + $orderBy: [drops_order_by!] + $where: { + drop_id: { _in: [14] } + } +) { + drops(order_by: $orderBy) { + id + name + } +} +``` + +And an enum that let you choose what fields you want to order by: + +```typescript +enum MyDropSortFields { + Id = 'id', + Name = 'name', +} +``` + +You can then build the variable `$orderBy` with `createOrderBy`: + +```typescript +import { Order, OrderByVariables, createOrderBy } from '@poap-xyz/utils'; + +const variables: OrderByVariables = { + orderBy: createOrderBy(MyDropSortFields.Name, Order.ASC), +}; +``` + +## Filters + +There are many filters with different types and for different results, normally you will have a +query that accepts a `$where` variable and then you can construct the variables with the use of the +filters utils. + +For example: + +```typescript +import { + FilterVariables, + createBoolFilter, + createLikeFilter, + createBetweenFilter, +} from '@poap-xyz/utils'; + +const variables: FilterVariables = { + where: { + ...createBoolFilter('private', true), + ...createLikeFilter('name', 'My Awesome POAP'), + ...createBetweenFilter('created_date', '2023-12-23', '2024-02-28'), + }, +}; +``` + +The possible filters to create are: + +- `createLikeFilter`: searchs for the value to be included case-unsensitive in the field. +- `createEqFilter`: match exact field value. +- `createNeqFilter`: match not equal field value. +- `createBoolFilter`: when the value is true or false. +- `createAddressFilter`: given the address match it case-unsensitive and/or excludes the zero address. +- `createInFilter`: if the field is contained in any of the given values. +- `createBetweenFilter`: from and to values, mostly used for dates. diff --git a/docs/pages/packages/utils/_meta.json b/docs/pages/packages/utils/_meta.json new file mode 100644 index 00000000..842b7b85 --- /dev/null +++ b/docs/pages/packages/utils/_meta.json @@ -0,0 +1,3 @@ +{ + "Queries": "Queries" +} diff --git a/examples/moments/backend/src/index.ts b/examples/moments/backend/src/index.ts index 58b344dd..c27ef3c3 100644 --- a/examples/moments/backend/src/index.ts +++ b/examples/moments/backend/src/index.ts @@ -23,7 +23,9 @@ async function main(): Promise { }); // We use Compass for read operations - const compass = new PoapCompass('your_api_key'); + const compass = new PoapCompass({ + apiKey: 'your_api_key', + }); // Use your library here const client = new MomentsClient(momentsApi, compass); diff --git a/examples/moments/backend/src/methods/fetch_moments_by_drop_ids.ts b/examples/moments/backend/src/methods/fetch_moments_by_drop_ids.ts index 12ef7782..587cee1f 100644 --- a/examples/moments/backend/src/methods/fetch_moments_by_drop_ids.ts +++ b/examples/moments/backend/src/methods/fetch_moments_by_drop_ids.ts @@ -1,5 +1,5 @@ import { FetchMomentsInput, Moment, MomentsClient } from '@poap-xyz/moments'; -import { PaginatedResult } from '@poap-xyz/utils'; +import { Order, PaginatedResult } from '@poap-xyz/utils'; export const fetch_moments_by_drop_ids = async ( client: MomentsClient, @@ -9,7 +9,7 @@ export const fetch_moments_by_drop_ids = async ( offset: 0, limit: 10, drop_ids: [1], - idOrder: 'desc', + idOrder: Order.DESC, }; console.log(input); diff --git a/examples/moments/backend/src/methods/fetch_multiple_moments.ts b/examples/moments/backend/src/methods/fetch_multiple_moments.ts index 01f262b0..18225e5d 100644 --- a/examples/moments/backend/src/methods/fetch_multiple_moments.ts +++ b/examples/moments/backend/src/methods/fetch_multiple_moments.ts @@ -1,5 +1,5 @@ import { FetchMomentsInput, Moment, MomentsClient } from '@poap-xyz/moments'; -import { PaginatedResult } from '@poap-xyz/utils'; +import { Order, PaginatedResult } from '@poap-xyz/utils'; export const fetch_multiple_moments = async ( client: MomentsClient, @@ -7,7 +7,7 @@ export const fetch_multiple_moments = async ( const input: FetchMomentsInput = { offset: 0, limit: 10, - idOrder: 'desc', + idOrder: Order.DESC, }; try { diff --git a/examples/moments/backend/src/methods/fetch_single_moment.ts b/examples/moments/backend/src/methods/fetch_single_moment.ts index ca86aaea..4eed9e41 100644 --- a/examples/moments/backend/src/methods/fetch_single_moment.ts +++ b/examples/moments/backend/src/methods/fetch_single_moment.ts @@ -1,5 +1,5 @@ import { FetchMomentsInput, Moment, MomentsClient } from '@poap-xyz/moments'; -import { PaginatedResult } from '@poap-xyz/utils'; +import { Order, PaginatedResult } from '@poap-xyz/utils'; export const fetch_single_moment = async ( client: MomentsClient, @@ -8,7 +8,7 @@ export const fetch_single_moment = async ( offset: 0, limit: 10, id: '7284219b-1bc7-43b8-ab27-44749bdd91e1', - idOrder: 'desc', + idOrder: Order.DESC, }; try { const data: PaginatedResult = await client.fetch(input); diff --git a/packages/drops/package.json b/packages/drops/package.json index ca049bd3..cf1a7e6a 100644 --- a/packages/drops/package.json +++ b/packages/drops/package.json @@ -1,6 +1,6 @@ { "name": "@poap-xyz/drops", - "version": "0.1.8", + "version": "0.2.0", "description": "Drops module for the poap.js library", "main": "dist/cjs/index.cjs", "module": "dist/esm/index.mjs", @@ -29,7 +29,7 @@ "node": ">=18" }, "dependencies": { - "@poap-xyz/providers": "0.1.8", - "@poap-xyz/utils": "0.1.8" + "@poap-xyz/providers": "0.2.0", + "@poap-xyz/utils": "0.2.0" } } diff --git a/packages/drops/src/DropsClient.ts b/packages/drops/src/DropsClient.ts index b9dc76ee..392ee4a3 100644 --- a/packages/drops/src/DropsClient.ts +++ b/packages/drops/src/DropsClient.ts @@ -4,11 +4,16 @@ import { DropResponse as ProviderDropResponse, } from '@poap-xyz/providers'; import { Drop } from './domain/Drop'; -import { PaginatedDropsResponse, PAGINATED_DROPS_QUERY } from './queries'; +import { + PAGINATED_DROPS_QUERY, + PaginatedDropsResponse, + PaginatedDropsVariables, +} from './queries/PaginatedDrop'; import { CreateDropsInput, DropImageResponse, DropResponse, + DropsSortFields, FetchDropsInput, SearchDropsInput, UpdateDropsInput, @@ -16,17 +21,21 @@ import { import { PaginatedResult, nextCursor, - creatPrivateFilter, - createUndefinedOrder, createBetweenFilter, - createFilter, createInFilter, Order, isNumeric, removeSpecialCharacters, + createOrderBy, + createBoolFilter, + createLikeFilter, } from '@poap-xyz/utils'; import { DropImage } from './types/dropImage'; -import { SEARCH_DROPS_QUERY, SearchDropsResponse } from './queries/SearchDrops'; +import { + SEARCH_DROPS_QUERY, + SearchDropsResponse, + SearchDropsVariables, +} from './queries/SearchDrops'; /** * Represents a client for working with POAP drops. @@ -67,22 +76,22 @@ export class DropsClient { isPrivate, } = input; - const variables = { + const variables: PaginatedDropsVariables = { limit, offset, - orderBy: createUndefinedOrder(sortField, sortDir), + orderBy: createOrderBy(sortField, sortDir), where: { - ...creatPrivateFilter('private', isPrivate), - ...createFilter('name', name), + ...createBoolFilter('private', isPrivate), + ...createLikeFilter('name', name), ...createBetweenFilter('created_date', from, to), ...createInFilter('id', ids), }, }; - const { data } = await this.compassProvider.request( - PAGINATED_DROPS_QUERY, - variables, - ); + const { data } = await this.compassProvider.request< + PaginatedDropsResponse, + PaginatedDropsVariables + >(PAGINATED_DROPS_QUERY, variables); const drops = data.drops.map( (drop: DropResponse): Drop => this.mapDrop(drop), @@ -109,7 +118,7 @@ export class DropsClient { return new PaginatedResult([], null); } - const variables = { + const variables: SearchDropsVariables = { limit, offset, ...(isNumeric(search) && { orderBy: { id: Order.ASC } }), @@ -118,10 +127,10 @@ export class DropsClient { }, }; - const { data } = await this.compassProvider.request( - SEARCH_DROPS_QUERY, - variables, - ); + const { data } = await this.compassProvider.request< + SearchDropsResponse, + SearchDropsVariables + >(SEARCH_DROPS_QUERY, variables); const drops = data.search_drops.map( (drop: DropResponse): Drop => this.mapDrop(drop), diff --git a/packages/drops/src/queries/PaginatedDrop.ts b/packages/drops/src/queries/PaginatedDrop.ts index 04d34525..12a20502 100644 --- a/packages/drops/src/queries/PaginatedDrop.ts +++ b/packages/drops/src/queries/PaginatedDrop.ts @@ -1,3 +1,8 @@ +import { + FilterVariables, + OrderByVariables, + PaginatedVariables, +} from '@poap-xyz/utils'; import { DropResponse } from '../types/DropResponse'; export const PAGINATED_DROPS_QUERY = ` @@ -49,7 +54,9 @@ export const PAGINATED_DROPS_QUERY = ` `; export interface PaginatedDropsResponse { - data: { - drops: DropResponse[]; - }; + drops: DropResponse[]; } + +export type PaginatedDropsVariables = FilterVariables & + OrderByVariables & + PaginatedVariables; diff --git a/packages/drops/src/queries/SearchDrops.ts b/packages/drops/src/queries/SearchDrops.ts index 4cee8066..bbd87df8 100644 --- a/packages/drops/src/queries/SearchDrops.ts +++ b/packages/drops/src/queries/SearchDrops.ts @@ -1,3 +1,4 @@ +import { Order, PaginatedVariables } from '@poap-xyz/utils'; import { DropResponse } from '../types/DropResponse'; export const SEARCH_DROPS_QUERY = ` @@ -54,7 +55,12 @@ export const SEARCH_DROPS_QUERY = ` `; export interface SearchDropsResponse { - data: { - search_drops: DropResponse[]; + search_drops: DropResponse[]; +} + +export interface SearchDropsVariables extends PaginatedVariables { + args: { + search: string; }; + orderBy?: { id: Order.ASC }; } diff --git a/packages/moments/package.json b/packages/moments/package.json index 5d3f09e8..dabe4002 100644 --- a/packages/moments/package.json +++ b/packages/moments/package.json @@ -1,6 +1,6 @@ { "name": "@poap-xyz/moments", - "version": "0.1.8", + "version": "0.2.0", "description": "Moments module for the poap.js library", "main": "dist/cjs/index.cjs", "module": "dist/esm/index.mjs", @@ -26,8 +26,8 @@ "build": "rollup -c --bundleConfigAsCjs" }, "dependencies": { - "@poap-xyz/providers": "0.1.8", - "@poap-xyz/utils": "0.1.8", + "@poap-xyz/providers": "0.2.0", + "@poap-xyz/utils": "0.2.0", "uuid": "^9.0.0" }, "engines": { diff --git a/packages/moments/src/client/MomentsClient.ts b/packages/moments/src/client/MomentsClient.ts index a44cfaab..69b57081 100644 --- a/packages/moments/src/client/MomentsClient.ts +++ b/packages/moments/src/client/MomentsClient.ts @@ -1,10 +1,10 @@ import { CompassProvider, PoapMomentsApi } from '@poap-xyz/providers'; import { createBetweenFilter, - createFilter, + createEqFilter, createInFilter, - creatEqFilter, - filterUndefinedProperties, + createLikeFilter, + createOrderBy, nextCursor, PaginatedResult, } from '@poap-xyz/utils'; @@ -12,11 +12,15 @@ import { Moment } from '../domain/Moment'; import { MomentResponse, MomentsQueryResponse, + MomentsQueryVariables, PAGINATED_MOMENTS_QUERY, } from '../queries'; import { CreateMomentInput } from './dtos/create/CreateInput'; import { CreateSteps } from './dtos/create/CreateSteps'; -import { FetchMomentsInput } from './dtos/fetch/FetchMomentsInput'; +import { + FetchMomentsInput, + MomentsSortFields, +} from './dtos/fetch/FetchMomentsInput'; import { CreateMedia } from './dtos/create/CreateMedia'; export class MomentsClient { @@ -148,28 +152,37 @@ export class MomentsClient { tokenIdOrder, dropIdOrder, }: FetchMomentsInput): Promise> { - const variables = { + const variables: MomentsQueryVariables = { limit, offset, - orderBy: filterUndefinedProperties({ - start_date: createdOrder, - token_id: tokenIdOrder, - drop_id: dropIdOrder, - id: idOrder, - }), + orderBy: { + ...createOrderBy( + MomentsSortFields.StartDate, + createdOrder, + ), + ...createOrderBy( + MomentsSortFields.TokenId, + tokenIdOrder, + ), + ...createOrderBy( + MomentsSortFields.DropId, + dropIdOrder, + ), + ...createOrderBy(MomentsSortFields.Id, idOrder), + }, where: { - ...creatEqFilter('token_id', token_id), + ...createEqFilter('token_id', token_id), ...createInFilter('drop_id', drop_ids), - ...createFilter('author', author), + ...createLikeFilter('author', author), ...createBetweenFilter('created_on', from, to), - ...creatEqFilter('id', id), + ...createEqFilter('id', id), }, }; - const response = await this.CompassProvider.request( - PAGINATED_MOMENTS_QUERY, - variables, - ); + const response = await this.CompassProvider.request< + MomentsQueryResponse, + MomentsQueryVariables + >(PAGINATED_MOMENTS_QUERY, variables); const momentsResponse: Moment[] = response.data.moments.map( this.getMomentFromMomentResponse, diff --git a/packages/moments/src/client/dtos/fetch/FetchMomentsInput.ts b/packages/moments/src/client/dtos/fetch/FetchMomentsInput.ts index ad71cfa4..da00b187 100644 --- a/packages/moments/src/client/dtos/fetch/FetchMomentsInput.ts +++ b/packages/moments/src/client/dtos/fetch/FetchMomentsInput.ts @@ -1,4 +1,18 @@ -import { PaginationInput } from '@poap-xyz/utils'; +import { Order, PaginationInput } from '@poap-xyz/utils'; + +/** + * Enum to define available fields for sorting Moments. + * + * @export + * @enum {string} + */ +export enum MomentsSortFields { + StartDate = 'start_date', + TokenId = 'token_id', + DropId = 'drop_id', + Id = 'id', +} + /** * Interface representing the input needed to fetch moments. * @interface @@ -16,10 +30,10 @@ import { PaginationInput } from '@poap-xyz/utils'; * @property {number} [drop_id] - The drop ID to filter moments by (optional). */ export interface FetchMomentsInput extends PaginationInput { - createdOrder?: string; - tokenIdOrder?: string; - dropIdOrder?: string; - idOrder?: string; + createdOrder?: Order; + tokenIdOrder?: Order; + dropIdOrder?: Order; + idOrder?: Order; author?: string; from?: string; to?: string; diff --git a/packages/moments/src/queries/PaginatedMoments.ts b/packages/moments/src/queries/PaginatedMoments.ts index 0b8322b0..1c2b8066 100644 --- a/packages/moments/src/queries/PaginatedMoments.ts +++ b/packages/moments/src/queries/PaginatedMoments.ts @@ -1,3 +1,9 @@ +import { + FilterVariables, + OrderByVariables, + PaginatedVariables, +} from '@poap-xyz/utils'; + export const PAGINATED_MOMENTS_QUERY = ` query PaginatedMoments( $limit: Int! @@ -30,7 +36,9 @@ export interface MomentResponse { } export interface MomentsQueryResponse { - data: { - moments: MomentResponse[]; - }; + moments: MomentResponse[]; } + +export type MomentsQueryVariables = FilterVariables & + OrderByVariables & + PaginatedVariables; diff --git a/packages/poaps/package.json b/packages/poaps/package.json index 8aa0e7c7..6623a560 100644 --- a/packages/poaps/package.json +++ b/packages/poaps/package.json @@ -1,6 +1,6 @@ { "name": "@poap-xyz/poaps", - "version": "0.1.8", + "version": "0.2.0", "description": "Poaps module for the poap.js library", "main": "dist/cjs/index.cjs", "module": "dist/esm/index.mjs", @@ -26,8 +26,8 @@ "build": "rollup -c --bundleConfigAsCjs" }, "dependencies": { - "@poap-xyz/providers": "0.1.8", - "@poap-xyz/utils": "0.1.8" + "@poap-xyz/providers": "0.2.0", + "@poap-xyz/utils": "0.2.0" }, "engines": { "node": ">=18" diff --git a/packages/poaps/src/PoapsClient.ts b/packages/poaps/src/PoapsClient.ts index df3e40a6..01649912 100644 --- a/packages/poaps/src/PoapsClient.ts +++ b/packages/poaps/src/PoapsClient.ts @@ -5,19 +5,24 @@ import { } from '@poap-xyz/providers'; import { POAP } from './domain/Poap'; import { POAPReservation } from './domain/POAPReservation'; -import { PAGINATED_POAPS_QUERY, PaginatedPoapsResponse } from './queries'; +import { + PAGINATED_POAPS_QUERY, + PaginatedPoapsResponse, + PaginatedPoapsVariables, +} from './queries'; import { EmailReservationInput, FetchPoapsInput, PoapMintStatus, + PoapsSortFields, WalletMintInput, } from './types'; import { - creatAddressFilter, + createAddressFilter, createBetweenFilter, + createEqFilter, createInFilter, - creatEqFilter, - createUndefinedOrder, + createOrderBy, nextCursor, PaginatedResult, } from '@poap-xyz/utils'; @@ -62,27 +67,28 @@ export class PoapsClient { filterByZeroAddress = true, } = input; - const variables = { + const variables: PaginatedPoapsVariables = { limit, offset, - orderBy: createUndefinedOrder(sortField, sortDir), + orderBy: createOrderBy(sortField, sortDir), where: { - ...creatAddressFilter( + ...createAddressFilter( 'collector_address', filterByZeroAddress, collectorAddress, ), - ...creatEqFilter('chain', chain), - ...creatEqFilter('drop_id', dropId), + ...createEqFilter('chain', chain), + ...createEqFilter('drop_id', dropId), ...createBetweenFilter('minted_on', mintedDateFrom, mintedDateTo), ...createInFilter('id', ids), }, }; - const { data } = await this.compassProvider.request( - PAGINATED_POAPS_QUERY, - variables, - ); + const { data } = await this.compassProvider.request< + PaginatedPoapsResponse, + PaginatedPoapsVariables + >(PAGINATED_POAPS_QUERY, variables); + const poaps = data.poaps.map((poap) => { const { drop } = poap; const mintedOn = new Date(0); diff --git a/packages/poaps/src/queries/PaginatedPoaps.ts b/packages/poaps/src/queries/PaginatedPoaps.ts index e8b273ed..59ae8768 100644 --- a/packages/poaps/src/queries/PaginatedPoaps.ts +++ b/packages/poaps/src/queries/PaginatedPoaps.ts @@ -1,3 +1,9 @@ +import { + FilterVariables, + OrderByVariables, + PaginatedVariables, +} from '@poap-xyz/utils'; + export const PAGINATED_POAPS_QUERY = ` query PaginatedPoaps( $limit: Int! @@ -43,7 +49,9 @@ export interface PoapsResponse { } export interface PaginatedPoapsResponse { - data: { - poaps: PoapsResponse[]; - }; + poaps: PoapsResponse[]; } + +export type PaginatedPoapsVariables = FilterVariables & + OrderByVariables & + PaginatedVariables; diff --git a/packages/providers/package.json b/packages/providers/package.json index 3120800d..b7d79bac 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -1,6 +1,6 @@ { "name": "@poap-xyz/providers", - "version": "0.1.8", + "version": "0.2.0", "description": "Providers module for the poap.js library", "main": "dist/cjs/index.cjs", "module": "dist/esm/index.mjs", @@ -11,6 +11,7 @@ "import": "./dist/esm/index.mjs", "browser": "./dist/umd/index.js" }, + "type": "module", "repository": { "type": "git", "url": "git+https://github.com/poap-xyz/poap.js.git" @@ -25,7 +26,7 @@ "build": "rollup -c --bundleConfigAsCjs" }, "dependencies": { - "@poap-xyz/utils": "0.1.8", + "@poap-xyz/utils": "0.2.0", "axios": "^1.3.5" }, "devDependencies": { diff --git a/packages/providers/src/core/PoapCompass/PoapCompass.ts b/packages/providers/src/core/PoapCompass/PoapCompass.ts index 1d253ef3..4901192c 100644 --- a/packages/providers/src/core/PoapCompass/PoapCompass.ts +++ b/packages/providers/src/core/PoapCompass/PoapCompass.ts @@ -3,6 +3,8 @@ import { CompassErrors } from '../../ports/CompassProvider/types/CompassErrors'; import { CompassError } from '../../ports/CompassProvider/types/CompassError'; import { CompassRequestError } from '../../ports/CompassProvider/errors/CompassRequestError'; import { CompassMissingDataError } from '../../ports/CompassProvider/errors/CompassMissingDataError'; +import { CompassBadRequestError } from '../../ports/CompassProvider/errors/CompassBadRequestError'; +import { CompassUnauthorizedError } from '../../ports/CompassProvider/errors/CompassUnauthorizedError'; const DEFAULT_COMPASS_BASE_URL = 'https://public.compass.poap.tech/v1/graphql'; @@ -33,14 +35,14 @@ export class PoapCompass implements CompassProvider { * @function * @name PoapCompass#fetchGraphQL * @param {string} query - The GraphQL query to fetch. - * @param {Record} variables - The variables to include with the query. + * @param {{ readonly [variable: string]: unknown }} variables - The variables to include with the query. * @param {AbortSignal} signal - When given, the request can be aborted with its controller. * @returns {Promise} A Promise that resolves with the result of the query. * @template R - The type of the result. */ private async fetchGraphQL( query: string, - variables: Record, + variables: { readonly [variable: string]: unknown }, signal?: AbortSignal, ): Promise { let response: Response; @@ -62,11 +64,7 @@ export class PoapCompass implements CompassProvider { throw new Error(`Network error, received error ${error}`); } - if (response.status !== 200) { - throw new Error( - `Response error, received status code ${response.status}`, - ); - } + this.handleResponseStatus(response); const body = await response.json(); @@ -75,6 +73,34 @@ export class PoapCompass implements CompassProvider { return body; } + /** + * Handles HTTP status codes and throws corresponding errors. + * + * @private + * @function + * @name PoapCompass#handleHttpStatus + * @param {Response} response - The response from the fetch call. + * @throws {CompassUnauthorizedError} for 401 Unauthorized status codes. + * @throws {CompassBadRequestError} for 400 Bad Request status codes. + * @throws {Error} for other non-200 status codes. + */ + private handleResponseStatus(response: Response): void { + switch (response.status) { + case 400: + throw new CompassBadRequestError(); + case 401: + throw new CompassUnauthorizedError(); + case 200: + return; // OK + default: + // For simplicity, throwing a generic error for all other statuses. + // You can add more cases for other statuses as needed. + throw new Error( + `Response error, received status code ${response.status}`, + ); + } + } + /** * Throws error when the response contains error or does not contain any data. * @@ -139,24 +165,12 @@ export class PoapCompass implements CompassProvider { ); } - /** - * Executes a GraphQL query using the `fetchGraphQL` method. - * - * @async - * @function - * @name PoapCompass#request - * @param {string} query - The GraphQL query to execute. - * @param {Record} [variables] - The variables to include with the query. - * @param {AbortSignal} signal - When given, the request can be aborted with its controller. - * @returns {Promise} A Promise that resolves with the result of the query. - * @template T - The type of the result. - */ - async request( + async request( query: string, - variables?: Record, + variables?: null | undefined | V, signal?: AbortSignal, - ): Promise { - return await this.fetchGraphQL(query, variables ?? {}, signal); + ): Promise<{ data: D }> { + return await this.fetchGraphQL<{ data: D }>(query, variables ?? {}, signal); } } diff --git a/packages/providers/src/core/PoapDropApi/PoapDropApi.ts b/packages/providers/src/core/PoapDropApi/PoapDropApi.ts index ce2bde76..abb68700 100644 --- a/packages/providers/src/core/PoapDropApi/PoapDropApi.ts +++ b/packages/providers/src/core/PoapDropApi/PoapDropApi.ts @@ -1,6 +1,3 @@ -/* eslint-disable max-statements */ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { DropApiProvider } from '../../ports/DropApiProvider/DropApiProvider'; import { DropResponse } from '../../ports/DropApiProvider/types/DropResponse'; import { @@ -51,7 +48,7 @@ export class PoapDropApi implements DropApiProvider { } } } - return await this.secureFetch(`${this.baseUrl}/events`, { + return await this.secureFetch(`${this.baseUrl}/events`, { method: 'POST', body: form, headers: {}, @@ -68,7 +65,7 @@ export class PoapDropApi implements DropApiProvider { * @returns {Promise} A Promise that resolves with the response from the API. */ async updateDrop(input: UpdateDropInput): Promise { - return await this.secureFetch(`${this.baseUrl}/events`, { + return await this.secureFetch(`${this.baseUrl}/events`, { method: 'PUT', body: JSON.stringify(input), headers: { @@ -77,7 +74,6 @@ export class PoapDropApi implements DropApiProvider { }); } - // TODO: Change variable type any to a more specific type /** * Sends a secure HTTP request to the POAP Drop API. * @@ -86,10 +82,10 @@ export class PoapDropApi implements DropApiProvider { * @function * @name PoapDropApi#secureFetch * @param {string} url - The URL for the HTTP request. - * @param {any} options - The options for the HTTP request. - * @returns {Promise} A Promise that resolves with the response from the API. + * @param {RequestInit} options - The options for the HTTP request. + * @returns {Promise} A Promise that resolves with the response from the API. */ - private async secureFetch(url: string, options: any): Promise { + private async secureFetch(url: string, options: RequestInit): Promise { const headersWithApiKey = { ...options.headers, 'x-api-key': this.apiKey, diff --git a/packages/providers/src/core/PoapTokenApi/PoapTokenApi.ts b/packages/providers/src/core/PoapTokenApi/PoapTokenApi.ts index d8313970..9b98b9c5 100644 --- a/packages/providers/src/core/PoapTokenApi/PoapTokenApi.ts +++ b/packages/providers/src/core/PoapTokenApi/PoapTokenApi.ts @@ -114,7 +114,7 @@ export class PoapTokenApi implements TokensApiProvider { * @private * @template R - Type of the expected response data. * @param {string} url - The complete URL for the HTTP request. - * @param {any} options - Configuration options for the HTTP request. + * @param {RequestInit} options - Configuration options for the HTTP request. * @returns {Promise} A promise that resolves with the parsed API response. */ private async secureFetch(url: string, options: RequestInit): Promise { diff --git a/packages/providers/src/ports/CompassProvider/CompassProvider.ts b/packages/providers/src/ports/CompassProvider/CompassProvider.ts index 8a4142cd..c851724e 100644 --- a/packages/providers/src/ports/CompassProvider/CompassProvider.ts +++ b/packages/providers/src/ports/CompassProvider/CompassProvider.ts @@ -10,14 +10,14 @@ export interface CompassProvider { * @function * @name CompassProvider#request * @param {string} query - The query string to execute. - * @param {Record} [variables] - The variables to pass with the query. + * @param {null | undefined | { readonly [variable: string]: unknown }} [variables] - The variables to pass with the query. * @param {AbortSignal} signal - When given, the request can be aborted with its controller. - * @returns {Promise} A Promise that resolves with the result of the query. - * @template T - The type of the result. + * @returns {Promise<{ data: D }>} A Promise that resolves with the result of the query. + * @template D - The type of the result's data. */ - request( + request( query: string, - variables?: Record, + variables?: null | undefined | V, signal?: AbortSignal, - ): Promise; + ): Promise<{ data: D }>; } diff --git a/packages/providers/src/ports/CompassProvider/errors/CompassBadRequestError.ts b/packages/providers/src/ports/CompassProvider/errors/CompassBadRequestError.ts new file mode 100644 index 00000000..32c6a66b --- /dev/null +++ b/packages/providers/src/ports/CompassProvider/errors/CompassBadRequestError.ts @@ -0,0 +1,5 @@ +export class CompassBadRequestError extends Error { + constructor() { + super('Compass malformed request'); + } +} diff --git a/packages/providers/src/ports/CompassProvider/errors/CompassUnauthorizedError.ts b/packages/providers/src/ports/CompassProvider/errors/CompassUnauthorizedError.ts new file mode 100644 index 00000000..10b200c7 --- /dev/null +++ b/packages/providers/src/ports/CompassProvider/errors/CompassUnauthorizedError.ts @@ -0,0 +1,5 @@ +export class CompassUnauthorizedError extends Error { + constructor() { + super('Unauthorized access, API key may be invalid or expired'); + } +} diff --git a/packages/providers/src/ports/CompassProvider/errors/index.ts b/packages/providers/src/ports/CompassProvider/errors/index.ts index 81119521..74757600 100644 --- a/packages/providers/src/ports/CompassProvider/errors/index.ts +++ b/packages/providers/src/ports/CompassProvider/errors/index.ts @@ -1,2 +1,4 @@ export * from './CompassRequestError'; export * from './CompassMissingDataError'; +export * from './CompassBadRequestError'; +export * from './CompassUnauthorizedError'; diff --git a/packages/providers/src/ports/DropApiProvider/DropApiProvider.ts b/packages/providers/src/ports/DropApiProvider/DropApiProvider.ts index 7924c888..719b5c4b 100644 --- a/packages/providers/src/ports/DropApiProvider/DropApiProvider.ts +++ b/packages/providers/src/ports/DropApiProvider/DropApiProvider.ts @@ -1,7 +1,4 @@ -import { - CreateDropInput, - UpdateDropInput, -} from './types/DropInput'; +import { CreateDropInput, UpdateDropInput } from './types/DropInput'; import { DropResponse } from './types/DropResponse'; /** diff --git a/packages/providers/src/ports/HttpProvider/HttpProvider.ts b/packages/providers/src/ports/HttpProvider/HttpProvider.ts deleted file mode 100644 index 751b3e9d..00000000 --- a/packages/providers/src/ports/HttpProvider/HttpProvider.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -// TODO: Change variable type any to a more specific type - -/** - * Provides a `request` method for making HTTP requests. - * - * @interface HttpProvider - */ -export interface HttpProvider { - /** - * Makes an HTTP request with the provided configuration. - * - * @async - * @function - * @name HttpProvider#request - * @param {{ endpoint: string; method: string; body: any; headers: any; }} requestInput - The configuration for the request. - * @returns {Promise} A Promise that resolves with the response to the request. - * @template R - The type of the response. - */ - request(requestInput: { - endpoint: string; - method: string; - body: any; - headers: any; - }): Promise; -} diff --git a/packages/providers/src/ports/HttpProvider/index.ts b/packages/providers/src/ports/HttpProvider/index.ts deleted file mode 100644 index abcb8456..00000000 --- a/packages/providers/src/ports/HttpProvider/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './HttpProvider'; diff --git a/packages/providers/src/ports/index.ts b/packages/providers/src/ports/index.ts index ff885048..7d43ef71 100644 --- a/packages/providers/src/ports/index.ts +++ b/packages/providers/src/ports/index.ts @@ -1,6 +1,5 @@ export * from './MomentsApiProvider'; export * from './DropApiProvider'; export * from './TokensApiProvider'; -export * from './HttpProvider'; export * from './CompassProvider'; export * from './AuthenticationProvider'; diff --git a/packages/utils/package.json b/packages/utils/package.json index e2cfd61b..e4d57667 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,8 +1,7 @@ { "name": "@poap-xyz/utils", - "version": "0.1.8", + "version": "0.2.0", "description": "Utils module for the poap.js library", - "type": "module", "main": "dist/cjs/index.cjs", "module": "dist/esm/index.mjs", "typings": "dist/cjs/index.d.ts", @@ -12,6 +11,7 @@ "import": "./dist/esm/index.mjs", "browser": "./dist/umd/index.js" }, + "type": "module", "repository": { "type": "git", "url": "git+https://github.com/poap-xyz/poap.js.git" diff --git a/packages/utils/src/functions/index.ts b/packages/utils/src/functions/index.ts new file mode 100644 index 00000000..f4fad1a2 --- /dev/null +++ b/packages/utils/src/functions/index.ts @@ -0,0 +1 @@ +export * from './nextCursor'; diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 8a2e9f4f..c864bc3d 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,21 +1,5 @@ -export { TransactionRequestStatus } from './types/TransactionRequestStatus'; -export { Chain } from './types/chain'; -export { Order } from './types/filter'; -export { PaginationInput } from './types/input'; export * from './types'; -export { PaginatedResult } from './types/pagination'; -export { nextCursor } from './functions/nextCursor'; -export { - filterUndefinedProperties, - createUndefinedOrder, - createFilter, - creatEqFilter, - creatNeqFilter, - filterZeroAddress, - creatAddressFilter, - createInFilter, - createBetweenFilter, - creatPrivateFilter, -} from './queries/utils'; +export * from './functions'; +export * from './queries'; export * from './format'; export * from './validation'; diff --git a/packages/utils/src/queries/filter.ts b/packages/utils/src/queries/filter.ts new file mode 100644 index 00000000..bdeba2a0 --- /dev/null +++ b/packages/utils/src/queries/filter.ts @@ -0,0 +1,73 @@ +export function createLikeFilter( + key: string, + value?: string, +): { [key: string]: { _ilike: string } } { + return value ? { [key]: { _ilike: `%${value}%` } } : {}; +} + +export function createEqFilter( + key: string, + value?: string | number, +): { [key: string]: { _eq: string | number } } { + return value ? { [key]: { _eq: value } } : {}; +} + +export function createNeqFilter( + key: string, + value?: string | number, +): { [key: string]: { _neq: string | number } } { + return value ? { [key]: { _neq: value } } : {}; +} + +export function createBoolFilter( + key: string, + value?: boolean, +): { [key: string]: { _eq: 'true' | 'false' } } { + return typeof value === 'boolean' + ? { [key]: { _eq: value ? 'true' : 'false' } } + : {}; +} + +export function createAddressFilter( + key: string, + filterZeroAddress: boolean, + value?: string, +): { + [key: string]: { + _neq?: string; + _eq?: string; + }; +} { + return filterZeroAddress || value + ? { + [key]: { + ...(filterZeroAddress + ? { _neq: '0x0000000000000000000000000000000000000000' } + : {}), + ...(value ? { _eq: value.toLowerCase() } : {}), + }, + } + : {}; +} + +export function createInFilter( + key: string, + values?: Array, +): { [key: string]: { _in: Array } } { + return values && values.length ? { [key]: { _in: values } } : {}; +} + +export function createBetweenFilter( + key: string, + from?: string, + to?: string, +): { [key: string]: { _gte?: string; _lte?: string } } { + const betweenFilter: { _gte?: string; _lte?: string } = {}; + if (from) { + betweenFilter._gte = from; + } + if (to) { + betweenFilter._lte = to; + } + return from || to ? { [key]: betweenFilter } : {}; +} diff --git a/packages/utils/src/queries/index.ts b/packages/utils/src/queries/index.ts new file mode 100644 index 00000000..14bca8fc --- /dev/null +++ b/packages/utils/src/queries/index.ts @@ -0,0 +1,2 @@ +export * from './filter'; +export * from './order'; diff --git a/packages/utils/src/queries/order.ts b/packages/utils/src/queries/order.ts new file mode 100644 index 00000000..37395b8e --- /dev/null +++ b/packages/utils/src/queries/order.ts @@ -0,0 +1,8 @@ +import { Order } from '../types/order'; + +export function createOrderBy( + key: E | undefined, + value?: Order | undefined, +): { [key: string]: Order } { + return key && value ? { [key]: value } : {}; +} diff --git a/packages/utils/src/queries/utils.ts b/packages/utils/src/queries/utils.ts deleted file mode 100644 index fb9502cd..00000000 --- a/packages/utils/src/queries/utils.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -export function filterUndefinedProperties>( - obj: T, -): Partial { - const filteredObj: Partial = {}; - for (const key in obj) { - if (obj[key] !== undefined) { - filteredObj[key] = obj[key]; - } - } - return filteredObj; -} - -export function createUndefinedOrder( - key: string | undefined, - value?: string | undefined, -): Record { - return key && value ? { [key]: value } : {}; -} - -export function createFilter(key: string, value?: string): Record { - return value ? { [key]: { _ilike: `%${value}%` } } : {}; -} - -export function creatEqFilter( - key: string, - value?: string | number, -): Record { - return value ? { [key]: { _eq: value } } : {}; -} - -export function creatNeqFilter( - key: string, - value?: string | number, -): Record { - return value ? { [key]: { _neq: value } } : {}; -} - -export function creatPrivateFilter( - key: string, - value?: boolean, -): Record { - return typeof value === 'boolean' - ? { [key]: { _eq: value ? 'true' : 'false' } } - : {}; -} - -export function filterZeroAddress(filter: boolean): Record { - return filter ? { _neq: '0x0000000000000000000000000000000000000000' } : {}; -} - -export function creatAddressFilter( - key: string, - filter: boolean, - value?: string, -): Record { - const addressFilter = { - [key]: { - ...filterZeroAddress(filter), - }, - }; - - if (value) { - addressFilter[key]._eq = value.toLocaleLowerCase(); - } - return filter || value ? addressFilter : {}; -} - -export function createInFilter( - key: string, - values?: Array, -): Record { - return values && values.length ? { [key]: { _in: values } } : {}; -} - -export function createBetweenFilter( - key: string, - from?: string, - to?: string, -): Record { - const dateFilter: { _gte?: string; _lte?: string } = {}; - if (from) { - dateFilter._gte = from; - } - if (to) { - dateFilter._lte = to; - } - return from || to ? { [key]: dateFilter } : {}; -} diff --git a/packages/utils/src/types/filter.ts b/packages/utils/src/types/filter.ts index 9b0a631d..fc20f7df 100644 --- a/packages/utils/src/types/filter.ts +++ b/packages/utils/src/types/filter.ts @@ -1,4 +1,14 @@ -export enum Order { - ASC = 'asc', - DESC = 'desc', +export interface Filter { + [key: string]: { + _eq?: string | number; + _neq?: string | number; + _ilike?: string; + _in?: Array; + _gte?: string; + _lte?: string; + }; +} + +export interface FilterVariables { + where: Filter; } diff --git a/packages/utils/src/types/index.ts b/packages/utils/src/types/index.ts index cad955b8..bf9ad731 100644 --- a/packages/utils/src/types/index.ts +++ b/packages/utils/src/types/index.ts @@ -1,2 +1,6 @@ export * from './pagination'; export * from './filter'; +export * from './order'; +export * from './input'; +export * from './chain'; +export * from './TransactionRequestStatus'; diff --git a/packages/utils/src/types/order.ts b/packages/utils/src/types/order.ts new file mode 100644 index 00000000..1f1f1076 --- /dev/null +++ b/packages/utils/src/types/order.ts @@ -0,0 +1,8 @@ +export enum Order { + ASC = 'asc', + DESC = 'desc', +} + +export interface OrderByVariables { + orderBy: { [key: string]: Order }; +} diff --git a/packages/utils/src/types/pagination.ts b/packages/utils/src/types/pagination.ts index 32819c6c..d8274db4 100644 --- a/packages/utils/src/types/pagination.ts +++ b/packages/utils/src/types/pagination.ts @@ -33,3 +33,18 @@ export class PaginatedResult { this.nextCursor = nextCursor; } } + +/** + * Variables pass to queries that do pagination. + */ +export interface PaginatedVariables { + /** + * When the page results start. + */ + offset: number; + + /** + * How many results to retrieve. + */ + limit: number; +} diff --git a/yarn.lock b/yarn.lock index 06515b7d..85dac6d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -884,8 +884,8 @@ __metadata: version: 0.0.0-use.local resolution: "@poap-xyz/drops@workspace:packages/drops" dependencies: - "@poap-xyz/providers": 0.1.8 - "@poap-xyz/utils": 0.1.8 + "@poap-xyz/providers": 0.2.0 + "@poap-xyz/utils": 0.2.0 languageName: unknown linkType: soft @@ -901,8 +901,8 @@ __metadata: version: 0.0.0-use.local resolution: "@poap-xyz/moments@workspace:packages/moments" dependencies: - "@poap-xyz/providers": 0.1.8 - "@poap-xyz/utils": 0.1.8 + "@poap-xyz/providers": 0.2.0 + "@poap-xyz/utils": 0.2.0 "@types/uuid": ^9.0.2 uuid: ^9.0.0 languageName: unknown @@ -912,22 +912,22 @@ __metadata: version: 0.0.0-use.local resolution: "@poap-xyz/poaps@workspace:packages/poaps" dependencies: - "@poap-xyz/providers": 0.1.8 - "@poap-xyz/utils": 0.1.8 + "@poap-xyz/providers": 0.2.0 + "@poap-xyz/utils": 0.2.0 languageName: unknown linkType: soft -"@poap-xyz/providers@*, @poap-xyz/providers@0.1.8, @poap-xyz/providers@workspace:packages/providers": +"@poap-xyz/providers@*, @poap-xyz/providers@0.2.0, @poap-xyz/providers@workspace:packages/providers": version: 0.0.0-use.local resolution: "@poap-xyz/providers@workspace:packages/providers" dependencies: - "@poap-xyz/utils": 0.1.8 + "@poap-xyz/utils": 0.2.0 axios: ^1.3.5 axios-mock-adapter: ^1.21.4 languageName: unknown linkType: soft -"@poap-xyz/utils@*, @poap-xyz/utils@0.1.8, @poap-xyz/utils@workspace:packages/utils": +"@poap-xyz/utils@*, @poap-xyz/utils@0.2.0, @poap-xyz/utils@workspace:packages/utils": version: 0.0.0-use.local resolution: "@poap-xyz/utils@workspace:packages/utils" languageName: unknown