Skip to content

Commit

Permalink
refactor: search iterator HOC
Browse files Browse the repository at this point in the history
  • Loading branch information
joscha committed Jul 25, 2024
1 parent 9ab4d71 commit 668e280
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 80 deletions.
39 changes: 39 additions & 0 deletions src/v1/create_search_iterator_fn.ts
Original file line number Diff line number Diff line change
@@ -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_RESPONSE>,
PAGED_REQUEST extends PagedRequest,
PAGED_RESPONSE extends
& PagedResponse
& Record<PAYLOAD_KEY, SINGLE_RESPONSE[]>,
PAYLOAD_KEY extends string =
& keyof Omit<PAGED_RESPONSE, keyof PagedResponse>
& string,
SINGLE_RESPONSE = object,
>(searchFn: FN, key: PAYLOAD_KEY) => {
async function* searchIterator(
params: Omit<PAGED_REQUEST, 'page_token'>,
): AsyncGenerator<PAGED_RESPONSE[]> {
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
}
19 changes: 4 additions & 15 deletions src/v1/field_value_changes.ts
Original file line number Diff line number Diff line change
@@ -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<T, Keys extends keyof T = keyof T> =
& Pick<T, Exclude<keyof T, Keys>>
& {
[K in Keys]-?:
& Required<Pick<T, K>>
& Partial<Record<Exclude<Keys, K>, 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.
Expand Down
52 changes: 10 additions & 42 deletions src/v1/organizations.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -160,10 +163,6 @@ export type OrganizationResponse = Replace<
InteractionDateResponse
>

export type PagedResponse = {
next_page_token: string | null
}

export type ListEntryReference = Replace<ListEntryReferenceRaw, {
created_at: Date
}>
Expand Down Expand Up @@ -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 =
& {
/**
Expand Down Expand Up @@ -436,25 +419,10 @@ export class Organizations {
* }
* ```
*/
async *searchIterator(
params: Omit<SearchOrganizationsRequest, 'page_token'>,
): AsyncGenerator<OrganizationResponse[]> {
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.
Expand Down
16 changes: 16 additions & 0 deletions src/v1/paged_request.ts
Original file line number Diff line number Diff line change
@@ -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
}
3 changes: 3 additions & 0 deletions src/v1/paged_response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type PagedResponse = {
next_page_token: string | null
}
37 changes: 14 additions & 23 deletions src/v1/persons.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -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.
*/
Expand All @@ -66,6 +67,13 @@ export type PersonResponseRaw =

export type PersonResponse = Replace<PersonResponseRaw, InteractionDateResponse>

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 =
& {
/**
Expand All @@ -79,6 +87,7 @@ export type SearchPersonsRequest =
& OptionalMinQueryParams
& OptionalMaxQueryParams
& InteractionDatesQueryParams
& WithCurrentOrganizatonParams

export type PagedPersonResponseRaw =
& {
Expand Down Expand Up @@ -194,23 +203,5 @@ export class Persons {
* }
* ```
*/
async *searchIterator(
params: Omit<SearchPersonsRequest, 'page_token'>,
): AsyncGenerator<PersonResponse[]> {
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')
}
11 changes: 11 additions & 0 deletions src/v1/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,14 @@
export type DateTime = string & { _datetimeBrand: never }

export type Replace<S, T> = Omit<S, keyof T> & T

/**
* Via https://stackoverflow.com/questions/40510611
*/
export type RequireOnlyOne<T, Keys extends keyof T = keyof T> =
& Pick<T, Exclude<keyof T, Keys>>
& {
[K in Keys]-?:
& Required<Pick<T, K>>
& Partial<Record<Exclude<Keys, K>, never>>
}[Keys]

0 comments on commit 668e280

Please sign in to comment.