oauth2-cli 1.2.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
4
4
 
5
+ ## [2.0.0](https://github.com/battis/oauth2-cli/compare/oauth2-cli/1.2.8...oauth2-cli/2.0.0) (2026-03-22)
6
+
7
+
8
+ ### ⚠ BREAKING CHANGES
9
+
10
+ * detect and automate traversing paginated collections
11
+
12
+ ### Features
13
+
14
+ * detect and automate traversing paginated collections ([28d6c6a](https://github.com/battis/oauth2-cli/commit/28d6c6ad90deb01059804d11ee692ec413a03345))
15
+
5
16
  ## [1.2.8](https://github.com/battis/oauth2-cli/compare/oauth2-cli/1.2.7...oauth2-cli/1.2.8) (2026-03-17)
6
17
 
7
18
  ### Bug Fixes
package/README.md CHANGED
@@ -69,7 +69,7 @@ If you would prefer an `https` connection to localhost, you have to roll your ow
69
69
 
70
70
  ### Request an endpoint
71
71
 
72
- #### `request()`
72
+ #### `requestRaw()`
73
73
 
74
74
  As noted above, `oauth2-cli` is built on top of [openid-client](https://www.npmjs.com/package/openid-client). The `request()` method is a pass-through to the `openid-client` [fetchProtectedResource()](https://github.com/panva/openid-client/blob/b77d87c1e2f5fef6fab501de615fb83a74a0251f/docs/functions/fetchProtectedResource.md) function, with the configuration and accessToken managed by the Client.
75
75
 
@@ -77,7 +77,7 @@ As noted above, `oauth2-cli` is built on top of [openid-client](https://www.npmj
77
77
  class Client {
78
78
  // ...
79
79
 
80
- public async request(
80
+ public async requestRaw(
81
81
  url: requestish.URL.ish,
82
82
  method = 'GET',
83
83
  body?: requestish.Body.ish,
@@ -95,7 +95,7 @@ class Client {
95
95
 
96
96
  If you would prefer to make requests to relative paths, rather than absolute paths, either configure a `base_url` or include an `issuer` in the `credentials` when instantiating the client. A `base_url` will preempt an `issuer`, if both are defined (handy for when the `issuer` is a different subdomain than the API endpoints).
97
97
 
98
- ### `requestJSON<J>()`
98
+ ### `request<T>()`
99
99
 
100
100
  Given that many APIs return JSON-formatted responses, it is convenient to just get that JSON (optionally pre-typed based on what you expect to receive) rather than having to process the response yourself.
101
101
 
@@ -103,9 +103,7 @@ Given that many APIs return JSON-formatted responses, it is convenient to just g
103
103
  class Client {
104
104
  // ...
105
105
 
106
- public async requestJSON<
107
- J extends OpenIDClient.JsonValue = OpenIDClient.JsonValue
108
- >(
106
+ public async request<T extends JSONValue = JSONValue>(
109
107
  url: requestish.URL.ish,
110
108
  method = 'GET',
111
109
  body?: requestish.Body.ish,
@@ -117,9 +115,9 @@ class Client {
117
115
  }
118
116
  ```
119
117
 
120
- ### `fetch()` and `fetchJSON<J>()`
118
+ ### `fetchRaw()` and `fetch<T>()`
121
119
 
122
- Aliases for `request()` and `requestJSON<J>()` that use [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)-style arguments.
120
+ Aliases for `requestRaw()` and `request<T>()` that use [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)-style arguments.
123
121
 
124
122
  ## Examples
125
123
 
package/dist/Client.d.ts CHANGED
@@ -7,6 +7,7 @@ import { Credentials } from './Credentials.js';
7
7
  import { Injection } from './Injection.js';
8
8
  import * as Localhost from './Localhost/index.js';
9
9
  import * as Options from './Options.js';
10
+ import { PaginatedCollection } from './PaginatedCollection.js';
10
11
  import * as Token from './Token/index.js';
11
12
  export type PreparedRequest = Parameters<(typeof OpenIDClient)['fetchProtectedResource']>;
12
13
  /**
@@ -117,7 +118,7 @@ export declare class Client<C extends Credentials = Credentials> extends EventEm
117
118
  * @param body Optional
118
119
  * @param dPoPOptions Optional, see {@link OpenIDClient.DPoPOptions}
119
120
  */
120
- request(url: requestish.URL.ish, method?: string, body?: requestish.Body.ish, headers?: requestish.Headers.ish, dPoPOptions?: OpenIDClient.DPoPOptions): Promise<Response>;
121
+ requestRaw(url: requestish.URL.ish, method?: string, body?: requestish.Body.ish, headers?: requestish.Headers.ish, dPoPOptions?: OpenIDClient.DPoPOptions): Promise<Response>;
121
122
  /**
122
123
  * Available hook to manipulate a fully-prepared request before sending to the
123
124
  * server
@@ -128,13 +129,27 @@ export declare class Client<C extends Credentials = Credentials> extends EventEm
128
129
  * processing it
129
130
  */
130
131
  protected prepareResponse(response: Response): Promise<Response>;
131
- /** Parse a fetch response as JSON, typing it as J */
132
- private toJSON;
133
132
  /**
134
- * Returns the result of {@link request} as a parsed JSON object, optionally
135
- * typed as `J`
133
+ * Available hook to check for a paginated response and return an interable
134
+ * PaginatedCollection
136
135
  */
137
- requestJSON<J extends JSONValue = JSONValue>(url: requestish.URL.ish, method?: string, body?: requestish.Body.ish, headers?: requestish.Headers.ish, dPoPOptions?: OpenIDClient.DPoPOptions): Promise<J>;
136
+ protected checkForPagination<T extends JSONValue = JSONValue>(_response: Response, _data: JSONValue): PaginatedCollection<T> | undefined;
137
+ /**
138
+ * Process a raw response int JSON, typing it as `T` or as a
139
+ * `PaginatedCollection<T>`
140
+ *
141
+ * @param response Raw response
142
+ */
143
+ processResponse<T extends JSONValue = JSONValue>(response: Response, paginationCheck: false): Promise<T>;
144
+ processResponse<T extends JSONValue = JSONValue>(response: Response, paginationCheck?: true): Promise<T | PaginatedCollection<T>>;
145
+ /**
146
+ * Returns the result of {@link requestRaw} as a parsed JSON object, optionally
147
+ * typed as `T`
148
+ *
149
+ * @param pagination Optional function to test conversion of response into a
150
+ * `PaginatedCollection<T>`
151
+ */
152
+ request<T extends JSONValue = JSONValue>(url: requestish.URL.ish, method?: string, body?: requestish.Body.ish, headers?: requestish.Headers.ish, dPoPOptions?: OpenIDClient.DPoPOptions): Promise<T | PaginatedCollection<T>>;
138
153
  /**
139
154
  * Request a protected resource using the client's access token.
140
155
  *
@@ -145,12 +160,12 @@ export declare class Client<C extends Credentials = Credentials> extends EventEm
145
160
  * paths relative to the `issuer` URL as well as absolute URLs
146
161
  * @param init Optional
147
162
  * @param dPoPOptions Optional, see {@link OpenIDClient.DPoPOptions}
148
- * @see {@link request} for which this is an alias for {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API Fetch API}-style requests
163
+ * @see {@link requestRaw} for which this is an alias for {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API Fetch API}-style requests
149
164
  */
150
- fetch(input: requestish.URL.ish, init?: RequestInit, dPoPOptions?: OpenIDClient.DPoPOptions): Promise<Response>;
165
+ fetchRaw(input: requestish.URL.ish, init?: RequestInit, dPoPOptions?: OpenIDClient.DPoPOptions): Promise<Response>;
151
166
  /**
152
167
  * Returns the result of {@link fetch} as a parsed JSON object, optionally
153
- * typed as `J`
168
+ * typed as `T`
154
169
  */
155
- fetchJSON<J extends JSONValue = JSONValue>(input: requestish.URL.ish, init?: RequestInit, dPoPOptions?: OpenIDClient.DPoPOptions): Promise<J>;
170
+ fetch<T extends JSONValue = JSONValue>(input: requestish.URL.ish, init?: RequestInit, dPoPOptions?: OpenIDClient.DPoPOptions): Promise<T | PaginatedCollection<T>>;
156
171
  }
package/dist/Client.js CHANGED
@@ -253,7 +253,7 @@ export class Client extends EventEmitter {
253
253
  * @param body Optional
254
254
  * @param dPoPOptions Optional, see {@link OpenIDClient.DPoPOptions}
255
255
  */
256
- async request(url, method = 'GET', body, headers = {}, dPoPOptions) {
256
+ async requestRaw(url, method = 'GET', body, headers = {}, dPoPOptions) {
257
257
  url = new URL(url, this.base_url || this.credentials.issuer);
258
258
  url = requestish.URL.from(requestish.URLSearchParams.appendTo(url, requestish.URLSearchParams.merge(this.inject?.search, url.searchParams)));
259
259
  const request = async () => await OpenIDClient.fetchProtectedResource(...(await this.prepareRequest(await this.getConfiguration(), (await this.getToken()).access_token, url, method, await requestish.Body.from(body), requestish.Headers.merge(this.inject?.headers, headers), dPoPOptions)));
@@ -284,12 +284,24 @@ export class Client extends EventEmitter {
284
284
  async prepareResponse(response) {
285
285
  return response;
286
286
  }
287
- /** Parse a fetch response as JSON, typing it as J */
288
- async toJSON(response) {
287
+ /**
288
+ * Available hook to check for a paginated response and return an interable
289
+ * PaginatedCollection
290
+ */
291
+ checkForPagination(_response, _data) {
292
+ return undefined;
293
+ }
294
+ async processResponse(response, paginationCheck = true) {
289
295
  if (response.ok) {
290
296
  const body = await response.text();
291
297
  try {
292
- return JSON.parse(body);
298
+ const data = JSON.parse(body);
299
+ let paginatedCollection;
300
+ if (paginationCheck &&
301
+ (paginatedCollection = this.checkForPagination(response, data))) {
302
+ return paginatedCollection;
303
+ }
304
+ return data;
293
305
  }
294
306
  catch (error) {
295
307
  throw new Error(`${this.name} response could not be parsed as JSON`, {
@@ -312,11 +324,15 @@ export class Client extends EventEmitter {
312
324
  }
313
325
  }
314
326
  /**
315
- * Returns the result of {@link request} as a parsed JSON object, optionally
316
- * typed as `J`
327
+ * Returns the result of {@link requestRaw} as a parsed JSON object, optionally
328
+ * typed as `T`
329
+ *
330
+ * @param pagination Optional function to test conversion of response into a
331
+ * `PaginatedCollection<T>`
317
332
  */
318
- async requestJSON(url, method = 'GET', body, headers = {}, dPoPOptions) {
319
- return await this.toJSON(await this.request(url, method, body, headers, dPoPOptions));
333
+ async request(url, method = 'GET', body, headers = {}, dPoPOptions) {
334
+ const response = await this.requestRaw(url, method, body, headers, dPoPOptions);
335
+ return await this.processResponse(response);
320
336
  }
321
337
  /**
322
338
  * Request a protected resource using the client's access token.
@@ -328,16 +344,16 @@ export class Client extends EventEmitter {
328
344
  * paths relative to the `issuer` URL as well as absolute URLs
329
345
  * @param init Optional
330
346
  * @param dPoPOptions Optional, see {@link OpenIDClient.DPoPOptions}
331
- * @see {@link request} for which this is an alias for {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API Fetch API}-style requests
347
+ * @see {@link requestRaw} for which this is an alias for {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API Fetch API}-style requests
332
348
  */
333
- async fetch(input, init, dPoPOptions) {
334
- return await this.request(input, init?.method, await requestish.Body.from(init?.body), requestish.Headers.from(init?.headers), dPoPOptions);
349
+ async fetchRaw(input, init, dPoPOptions) {
350
+ return await this.requestRaw(input, init?.method, await requestish.Body.from(init?.body), requestish.Headers.from(init?.headers), dPoPOptions);
335
351
  }
336
352
  /**
337
353
  * Returns the result of {@link fetch} as a parsed JSON object, optionally
338
- * typed as `J`
354
+ * typed as `T`
339
355
  */
340
- async fetchJSON(input, init, dPoPOptions) {
341
- return await this.toJSON(await this.fetch(input, init, dPoPOptions));
356
+ async fetch(input, init, dPoPOptions) {
357
+ return await this.request(input, init?.method, await requestish.Body.from(init?.body), requestish.Headers.from(init?.headers), dPoPOptions);
342
358
  }
343
359
  }
@@ -0,0 +1,18 @@
1
+ import { JSONValue } from '@battis/typescript-tricks';
2
+ import { Client } from './Client.js';
3
+ type Options<T extends JSONValue = JSONValue> = {
4
+ page: T[];
5
+ response: Response;
6
+ client: Client;
7
+ };
8
+ export declare abstract class PaginatedCollection<T extends JSONValue> implements AsyncIterable<T>, AsyncIterableIterator<T> {
9
+ protected currentResponse: Response;
10
+ protected currentElt: number;
11
+ protected currentPage: T[];
12
+ protected client: Client;
13
+ constructor({ page, response, client }: Options<T>);
14
+ [Symbol.asyncIterator](): this;
15
+ protected abstract nextPage(): Promise<T[] | undefined>;
16
+ next(): Promise<IteratorResult<T>>;
17
+ }
18
+ export {};
@@ -0,0 +1,34 @@
1
+ export class PaginatedCollection {
2
+ currentResponse;
3
+ currentElt = 0;
4
+ currentPage;
5
+ client;
6
+ constructor({ page, response, client }) {
7
+ this.currentResponse = response;
8
+ this.currentPage = page;
9
+ this.client = client;
10
+ }
11
+ [Symbol.asyncIterator]() {
12
+ return this;
13
+ }
14
+ async next() {
15
+ return new Promise((resolve) => {
16
+ const value = this.currentPage[this.currentElt++];
17
+ if (this.currentElt === this.currentPage.length) {
18
+ this.nextPage().then((page) => {
19
+ if (page) {
20
+ this.currentPage = page;
21
+ this.currentElt = 0;
22
+ resolve({ value, done: this.currentPage.length === 0 });
23
+ }
24
+ else {
25
+ resolve({ value, done: true });
26
+ }
27
+ });
28
+ }
29
+ else {
30
+ resolve({ value, done: false });
31
+ }
32
+ });
33
+ }
34
+ }
package/dist/index.d.ts CHANGED
@@ -3,4 +3,5 @@ export * from './Credentials.js';
3
3
  export * from './Injection.js';
4
4
  export * as Localhost from './Localhost/index.js';
5
5
  export { Client as Options } from './Options.js';
6
+ export * from './PaginatedCollection.js';
6
7
  export * as Token from './Token/index.js';
package/dist/index.js CHANGED
@@ -2,4 +2,5 @@ export * from './Client.js';
2
2
  export * from './Credentials.js';
3
3
  export * from './Injection.js';
4
4
  export * as Localhost from './Localhost/index.js';
5
+ export * from './PaginatedCollection.js';
5
6
  export * as Token from './Token/index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oauth2-cli",
3
- "version": "1.2.8",
3
+ "version": "2.0.0",
4
4
  "description": "Acquire API access tokens via OAuth 2.0 / OpenID Connect within CLI tools",
5
5
  "homepage": "https://github.com/battis/oauth2-cli/tree/main/packages/oauth2-cli#readme",
6
6
  "repository": {