From 668e280237dcaa2a78f4edcb375bc62eb08d4645 Mon Sep 17 00:00:00 2001 From: Joscha Feth Date: Thu, 25 Jul 2024 16:36:07 +0100 Subject: [PATCH] refactor: search iterator HOC --- src/v1/create_search_iterator_fn.ts | 39 ++++++++++++++++++++++ src/v1/field_value_changes.ts | 19 +++-------- src/v1/organizations.ts | 52 ++++++----------------------- src/v1/paged_request.ts | 16 +++++++++ src/v1/paged_response.ts | 3 ++ src/v1/persons.ts | 37 ++++++++------------ src/v1/types.ts | 11 ++++++ 7 files changed, 97 insertions(+), 80 deletions(-) create mode 100644 src/v1/create_search_iterator_fn.ts create mode 100644 src/v1/paged_request.ts create mode 100644 src/v1/paged_response.ts diff --git a/src/v1/create_search_iterator_fn.ts b/src/v1/create_search_iterator_fn.ts new file mode 100644 index 0000000..34b4eaa --- /dev/null +++ b/src/v1/create_search_iterator_fn.ts @@ -0,0 +1,39 @@ +import type { PagedResponse } from './paged_response.ts' +import type { PagedRequest } from './paged_request.ts' + +export const createSearchIteratorFn = < + FN extends (r: PAGED_REQUEST) => Promise, + PAGED_REQUEST extends PagedRequest, + PAGED_RESPONSE extends + & PagedResponse + & Record, + PAYLOAD_KEY extends string = + & keyof Omit + & string, + SINGLE_RESPONSE = object, +>(searchFn: FN, key: PAYLOAD_KEY) => { + async function* searchIterator( + params: Omit, + ): AsyncGenerator { + let page_token: string | undefined = undefined + while (true) { + const response: PAGED_RESPONSE = await searchFn( + // TODO(@joscha): remove cast + (page_token + ? { ...params, page_token } + : params) as PAGED_REQUEST, + ) + + // TODO(@joscha): remove cast + yield response[key] as unknown as PAGED_RESPONSE[] + + if (response.next_page_token === null) { + // no more pages to fetch + return + } else { + page_token = response.next_page_token + } + } + } + return searchIterator +} diff --git a/src/v1/field_value_changes.ts b/src/v1/field_value_changes.ts index e64cfd9..7ab250a 100644 --- a/src/v1/field_value_changes.ts +++ b/src/v1/field_value_changes.ts @@ -1,22 +1,11 @@ import type { AxiosInstance } from 'axios' -import type { Person } from './list_entries.ts' -import type { DateTime, Replace } from './types.ts' -import { fieldValueChangesUrl } from './urls.ts' import { defaultTransformers } from './axios_default_transformers.ts' -import type { Field } from './lists.ts' import type { Value, ValueRaw } from './field_values.ts' - -/** - * Via https://stackoverflow.com/questions/40510611 - */ -export type RequireOnlyOne = - & Pick> - & { - [K in Keys]-?: - & Required> - & Partial, never>> - }[Keys] +import type { Person } from './list_entries.ts' +import type { Field } from './lists.ts' +import type { DateTime, Replace, RequireOnlyOne } from './types.ts' +import { fieldValueChangesUrl } from './urls.ts' /** * Enum for Action Type. diff --git a/src/v1/organizations.ts b/src/v1/organizations.ts index d335425..865538b 100644 --- a/src/v1/organizations.ts +++ b/src/v1/organizations.ts @@ -1,12 +1,15 @@ import type { AxiosInstance } from 'axios' -import { organizationFieldsUrl, organizationsUrl } from './urls.ts' import { defaultTransformers } from './axios_default_transformers.ts' +import { createSearchIteratorFn } from './create_search_iterator_fn.ts' import type { DateTime } from './field_values.ts' import type { ListEntryReferenceRaw } from './list_entries.ts' -import type { PersonResponse as Person } from './persons.ts' -import type { Opportunity } from './opportunities.ts' import type { Field } from './lists.ts' +import type { Opportunity } from './opportunities.ts' +import type { PagedRequest } from './paged_request.ts' +import type { PagedResponse } from './paged_response.ts' +import type { PersonResponse as Person } from './persons.ts' import type { Replace } from './types.ts' +import { organizationFieldsUrl, organizationsUrl } from './urls.ts' export type InteractionOccurrenceQuantifier = 'first' | 'last' @@ -160,10 +163,6 @@ export type OrganizationResponse = Replace< InteractionDateResponse > -export type PagedResponse = { - next_page_token: string | null -} - export type ListEntryReference = Replace @@ -259,22 +258,6 @@ export type OpportunitiesQueryParams = { with_opportunities?: boolean } -// TODO(@joscha): see if we need to unify some of this with the `PagingParameters`. -export type PagedRequest = { - /** - * The number of items to return per page. - * - * Default is the maximum value of 500. - */ - page_size?: number - - /** - * The page token to retrieve the next page of items. - * if you do not pass the `page_size` parameter, the next page will have the default page size of 500. - */ - page_token?: string -} - export type SearchOrganizationsRequest = & { /** @@ -436,25 +419,10 @@ export class Organizations { * } * ``` */ - async *searchIterator( - params: Omit, - ): AsyncGenerator { - let page_token: string | undefined = undefined - while (true) { - const response: PagedOrganizationResponse = await this.search( - page_token ? { ...params, page_token } : params, - ) - - yield response.organizations - - if (response.next_page_token === null) { - // no more pages to fetch - return - } else { - page_token = response.next_page_token - } - } - } + searchIterator = createSearchIteratorFn( + this.search.bind(this), + 'organizations', + ) /** * Creates a new organization with the supplied parameters. diff --git a/src/v1/paged_request.ts b/src/v1/paged_request.ts new file mode 100644 index 0000000..d042cb2 --- /dev/null +++ b/src/v1/paged_request.ts @@ -0,0 +1,16 @@ +// TODO(@joscha): see if we need to unify some of this with the `PagingParameters`. + +export type PagedRequest = { + /** + * The number of items to return per page. + * + * Default is the maximum value of 500. + */ + page_size?: number + + /** + * The page token to retrieve the next page of items. + * if you do not pass the `page_size` parameter, the next page will have the default page size of 500. + */ + page_token?: string +} diff --git a/src/v1/paged_response.ts b/src/v1/paged_response.ts new file mode 100644 index 0000000..d5ebe7d --- /dev/null +++ b/src/v1/paged_response.ts @@ -0,0 +1,3 @@ +export type PagedResponse = { + next_page_token: string | null +} diff --git a/src/v1/persons.ts b/src/v1/persons.ts index 14d40b0..3f1cfbf 100644 --- a/src/v1/persons.ts +++ b/src/v1/persons.ts @@ -1,5 +1,6 @@ import type { AxiosInstance } from 'axios' import { defaultTransformers } from './axios_default_transformers.ts' +import { createSearchIteratorFn } from './create_search_iterator_fn.ts' import type { ListEntryReferenceRaw } from './list_entries.ts' import { InteractionDateResponse, @@ -10,12 +11,12 @@ import { type OpportunityIdResponseRaw, OptionalMaxQueryParams, OptionalMinQueryParams, - PagedRequest, - PagedResponse, transformInteractionDateResponseRaw, } from './organizations.ts' -import { personsUrl } from './urls.ts' +import type { PagedRequest } from './paged_request.ts' +import type { PagedResponse } from './paged_response.ts' import type { Replace } from './types.ts' +import { personsUrl } from './urls.ts' /** * The type of person. @@ -55,7 +56,7 @@ export type PersonResponseRaw = organization_ids: number[] /** An array of unique identifiers of organizations that the person is currently associated with according to the Affinity Data: Current Organization in-app column. - * Only returned when `with_current_organizations=true`. + * Only returned when `{@link WithCurrentOrganizatonParams.with_current_organizations}=true`. * * TODO(@joscha): model this in the type system, so the return type is based on the query parameter type. */ @@ -66,6 +67,13 @@ export type PersonResponseRaw = export type PersonResponse = Replace +export type WithCurrentOrganizatonParams = { + /** + * When true, the organization IDs of each person's current organizations (according to the Affinity Data: Current Organizations column) will be returned. + */ + with_current_organizations?: boolean +} + export type SearchPersonsRequest = & { /** @@ -79,6 +87,7 @@ export type SearchPersonsRequest = & OptionalMinQueryParams & OptionalMaxQueryParams & InteractionDatesQueryParams + & WithCurrentOrganizatonParams export type PagedPersonResponseRaw = & { @@ -194,23 +203,5 @@ export class Persons { * } * ``` */ - async *searchIterator( - params: Omit, - ): AsyncGenerator { - let page_token: string | undefined = undefined - while (true) { - const response: PagedPersonResponse = await this.search( - page_token ? { ...params, page_token } : params, - ) - - yield response.persons - - if (response.next_page_token === null) { - // no more pages to fetch - return - } else { - page_token = response.next_page_token - } - } - } + searchIterator = createSearchIteratorFn(this.search.bind(this), 'persons') } diff --git a/src/v1/types.ts b/src/v1/types.ts index 1a20375..ebd6cfd 100644 --- a/src/v1/types.ts +++ b/src/v1/types.ts @@ -4,3 +4,14 @@ export type DateTime = string & { _datetimeBrand: never } export type Replace = Omit & T + +/** + * Via https://stackoverflow.com/questions/40510611 + */ +export type RequireOnlyOne = + & Pick> + & { + [K in Keys]-?: + & Required> + & Partial, never>> + }[Keys]