Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@
"@azure/logger": "^1.0.0",
"tslib": "^2.6.2",
"@azure/core-lro": "^3.0.0",
"@azure/abort-controller": "^2.1.2",
"@azure/core-paging": "^1.5.0"
"@azure/abort-controller": "^2.1.2"
},
"devDependencies": {
"dotenv": "^16.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,163 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import {
getPagedAsyncIterator,
PagedAsyncIterableIterator,
PagedResult,
} from "@azure/core-paging";
import {
Client,
createRestError,
PathUncheckedResponse,
} from "@azure-rest/core-client";

/**
* returns an async iterator that iterates over results. It also has a `byPage`
* method that returns pages of items at once.
*
* @param pagedResult - an object that specifies how to get pages.
* @returns a paged async iterator that iterates over results.
*/
function getPagedAsyncIterator<
TElement,
TPage = TElement[],
TPageSettings = PageSettings,
TLink = string,
>(
pagedResult: PagedResult<TPage, TPageSettings, TLink>,
): PagedAsyncIterableIterator<TElement, TPage, TPageSettings> {
const iter = getItemAsyncIterator<TElement, TPage, TLink, TPageSettings>(
pagedResult,
);
return {
next() {
return iter.next();
},
[Symbol.asyncIterator]() {
return this;
},
byPage:
pagedResult?.byPage ??
(((settings?: PageSettings) => {
const { continuationToken } = settings ?? {};
return getPageAsyncIterator(pagedResult, {
pageLink: continuationToken as unknown as TLink | undefined,
});
}) as unknown as (
settings?: TPageSettings,
) => AsyncIterableIterator<TPage>),
};
}

async function* getItemAsyncIterator<TElement, TPage, TLink, TPageSettings>(
pagedResult: PagedResult<TPage, TPageSettings, TLink>,
): AsyncIterableIterator<TElement> {
const pages = getPageAsyncIterator(pagedResult);
const firstVal = await pages.next();
// if the result does not have an array shape, i.e. TPage = TElement, then we return it as is
if (!Array.isArray(firstVal.value)) {
// can extract elements from this page
const { toElements } = pagedResult;
if (toElements) {
yield* toElements(firstVal.value) as TElement[];
for await (const page of pages) {
yield* toElements(page) as TElement[];
}
} else {
yield firstVal.value;
// `pages` is of type `AsyncIterableIterator<TPage>` but TPage = TElement in this case
yield* pages as unknown as AsyncIterableIterator<TElement>;
}
} else {
yield* firstVal.value;
for await (const page of pages) {
// pages is of type `AsyncIterableIterator<TPage>` so `page` is of type `TPage`. In this branch,
// it must be the case that `TPage = TElement[]`
yield* page as unknown as TElement[];
}
}
}

async function* getPageAsyncIterator<TPage, TLink, TPageSettings>(
pagedResult: PagedResult<TPage, TPageSettings, TLink>,
options: {
pageLink?: TLink;
} = {},
): AsyncIterableIterator<TPage> {
const { pageLink } = options;
let response = await pagedResult.getPage(
pageLink ?? pagedResult.firstPageLink,
);
if (!response) {
return;
}
yield response.page;
while (response.nextPageLink) {
response = await pagedResult.getPage(response.nextPageLink);
if (!response) {
return;
}
yield response.page;
}
}

/**
* An interface that tracks the settings for paged iteration
*/
export interface PageSettings {
/**
* The token that keeps track of where to continue the iterator
*/
continuationToken?: string;
}

/**
* An interface that allows async iterable iteration both to completion and by page.
*/
export interface PagedAsyncIterableIterator<
TElement,
TPage = TElement[],
TPageSettings = PageSettings,
> {
/**
* The next method, part of the iteration protocol
*/
next(): Promise<IteratorResult<TElement>>;
/**
* The connection to the async iterator, part of the iteration protocol
*/
[Symbol.asyncIterator](): PagedAsyncIterableIterator<
TElement,
TPage,
TPageSettings
>;
/**
* Return an AsyncIterableIterator that works a page at a time
*/
byPage: (settings?: TPageSettings) => AsyncIterableIterator<TPage>;
}

/**
* An interface that describes how to communicate with the service.
*/
interface PagedResult<TPage, TPageSettings = PageSettings, TLink = string> {
/**
* Link to the first page of results.
*/
firstPageLink: TLink;
/**
* A method that returns a page of results.
*/
getPage: (
pageLink: TLink,
) => Promise<{ page: TPage; nextPageLink?: TLink } | undefined>;
/**
* a function to implement the `byPage` method on the paged async iterator.
*/
byPage?: (settings?: TPageSettings) => AsyncIterableIterator<TPage>;

/**
* A function to extract elements from a page.
*/
toElements?: (page: TPage) => unknown[];
}

/**
* Helper type to extract the type of an array
*/
Expand All @@ -20,10 +166,7 @@ export type GetArrayType<T> = T extends Array<infer TData> ? TData : never;
/**
* The type of a custom function that defines how to get a page and a link to the next one if any.
*/
export type GetPage<TPage> = (
pageLink: string,
maxPageSize?: number,
) => Promise<{
export type GetPage<TPage> = (pageLink: string) => Promise<{
page: TPage;
nextPageLink?: string;
}>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@
"@azure/logger": "^1.0.0",
"tslib": "^2.6.2",
"@azure/core-lro": "^3.0.0",
"@azure/abort-controller": "^2.1.2",
"@azure/core-paging": "^1.5.0"
"@azure/abort-controller": "^2.1.2"
},
"devDependencies": {
"dotenv": "^16.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,163 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import {
getPagedAsyncIterator,
PagedAsyncIterableIterator,
PagedResult,
} from "@azure/core-paging";
import {
Client,
createRestError,
PathUncheckedResponse,
} from "@azure-rest/core-client";

/**
* returns an async iterator that iterates over results. It also has a `byPage`
* method that returns pages of items at once.
*
* @param pagedResult - an object that specifies how to get pages.
* @returns a paged async iterator that iterates over results.
*/
function getPagedAsyncIterator<
TElement,
TPage = TElement[],
TPageSettings = PageSettings,
TLink = string,
>(
pagedResult: PagedResult<TPage, TPageSettings, TLink>,
): PagedAsyncIterableIterator<TElement, TPage, TPageSettings> {
const iter = getItemAsyncIterator<TElement, TPage, TLink, TPageSettings>(
pagedResult,
);
return {
next() {
return iter.next();
},
[Symbol.asyncIterator]() {
return this;
},
byPage:
pagedResult?.byPage ??
(((settings?: PageSettings) => {
const { continuationToken } = settings ?? {};
return getPageAsyncIterator(pagedResult, {
pageLink: continuationToken as unknown as TLink | undefined,
});
}) as unknown as (
settings?: TPageSettings,
) => AsyncIterableIterator<TPage>),
};
}

async function* getItemAsyncIterator<TElement, TPage, TLink, TPageSettings>(
pagedResult: PagedResult<TPage, TPageSettings, TLink>,
): AsyncIterableIterator<TElement> {
const pages = getPageAsyncIterator(pagedResult);
const firstVal = await pages.next();
// if the result does not have an array shape, i.e. TPage = TElement, then we return it as is
if (!Array.isArray(firstVal.value)) {
// can extract elements from this page
const { toElements } = pagedResult;
if (toElements) {
yield* toElements(firstVal.value) as TElement[];
for await (const page of pages) {
yield* toElements(page) as TElement[];
}
} else {
yield firstVal.value;
// `pages` is of type `AsyncIterableIterator<TPage>` but TPage = TElement in this case
yield* pages as unknown as AsyncIterableIterator<TElement>;
}
} else {
yield* firstVal.value;
for await (const page of pages) {
// pages is of type `AsyncIterableIterator<TPage>` so `page` is of type `TPage`. In this branch,
// it must be the case that `TPage = TElement[]`
yield* page as unknown as TElement[];
}
}
}

async function* getPageAsyncIterator<TPage, TLink, TPageSettings>(
pagedResult: PagedResult<TPage, TPageSettings, TLink>,
options: {
pageLink?: TLink;
} = {},
): AsyncIterableIterator<TPage> {
const { pageLink } = options;
let response = await pagedResult.getPage(
pageLink ?? pagedResult.firstPageLink,
);
if (!response) {
return;
}
yield response.page;
while (response.nextPageLink) {
response = await pagedResult.getPage(response.nextPageLink);
if (!response) {
return;
}
yield response.page;
}
}

/**
* An interface that tracks the settings for paged iteration
*/
export interface PageSettings {
/**
* The token that keeps track of where to continue the iterator
*/
continuationToken?: string;
}

/**
* An interface that allows async iterable iteration both to completion and by page.
*/
export interface PagedAsyncIterableIterator<
TElement,
TPage = TElement[],
TPageSettings = PageSettings,
> {
/**
* The next method, part of the iteration protocol
*/
next(): Promise<IteratorResult<TElement>>;
/**
* The connection to the async iterator, part of the iteration protocol
*/
[Symbol.asyncIterator](): PagedAsyncIterableIterator<
TElement,
TPage,
TPageSettings
>;
/**
* Return an AsyncIterableIterator that works a page at a time
*/
byPage: (settings?: TPageSettings) => AsyncIterableIterator<TPage>;
}

/**
* An interface that describes how to communicate with the service.
*/
interface PagedResult<TPage, TPageSettings = PageSettings, TLink = string> {
/**
* Link to the first page of results.
*/
firstPageLink: TLink;
/**
* A method that returns a page of results.
*/
getPage: (
pageLink: TLink,
) => Promise<{ page: TPage; nextPageLink?: TLink } | undefined>;
/**
* a function to implement the `byPage` method on the paged async iterator.
*/
byPage?: (settings?: TPageSettings) => AsyncIterableIterator<TPage>;

/**
* A function to extract elements from a page.
*/
toElements?: (page: TPage) => unknown[];
}

/**
* Helper type to extract the type of an array
*/
Expand All @@ -20,10 +166,7 @@ export type GetArrayType<T> = T extends Array<infer TData> ? TData : never;
/**
* The type of a custom function that defines how to get a page and a link to the next one if any.
*/
export type GetPage<TPage> = (
pageLink: string,
maxPageSize?: number,
) => Promise<{
export type GetPage<TPage> = (pageLink: string) => Promise<{
page: TPage;
nextPageLink?: string;
}>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@
"@azure/logger": "^1.0.0",
"tslib": "^2.6.2",
"@azure/core-lro": "^3.0.0",
"@azure/abort-controller": "^2.1.2",
"@azure/core-paging": "^1.5.0"
"@azure/abort-controller": "^2.1.2"
},
"devDependencies": {
"dotenv": "^16.0.0",
Expand Down
Loading