oauth2-cli 1.2.7 → 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,56 +2,64 @@
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
- ## [1.2.7](https://github.com/battis/oauth2-cli/compare/oauth2-cli/1.2.6...oauth2-cli/1.2.7) (2026-03-10)
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
6
 
7
- ## [1.2.6](https://github.com/battis/oauth2-cli/compare/oauth2-cli/1.2.5...oauth2-cli/1.2.6) (2026-03-10)
8
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
+
16
+ ## [1.2.8](https://github.com/battis/oauth2-cli/compare/oauth2-cli/1.2.7...oauth2-cli/1.2.8) (2026-03-17)
9
17
 
10
18
  ### Bug Fixes
11
19
 
12
- * update dev vs peer dependencies ([f21dc01](https://github.com/battis/oauth2-cli/commit/f21dc0108fcc786fa6edd5015fa835da856a9af0))
20
+ - log unparseable body if unparseable as JSON ([183bcc7](https://github.com/battis/oauth2-cli/commit/183bcc750f4f2dded5bc5f4d9fbc01fb6624e651))
13
21
 
14
- ## [1.2.5](https://github.com/battis/oauth2-cli/compare/oauth2-cli/1.2.4...oauth2-cli/1.2.5) (2026-03-08)
22
+ ## [1.2.6](https://github.com/battis/oauth2-cli/compare/oauth2-cli/1.2.5...oauth2-cli/1.2.6) (2026-03-10)
15
23
 
24
+ ### Bug Fixes
25
+
26
+ - update dev vs peer dependencies ([f21dc01](https://github.com/battis/oauth2-cli/commit/f21dc0108fcc786fa6edd5015fa835da856a9af0))
27
+
28
+ ## [1.2.5](https://github.com/battis/oauth2-cli/compare/oauth2-cli/1.2.4...oauth2-cli/1.2.5) (2026-03-08)
16
29
 
17
30
  ### Bug Fixes
18
31
 
19
- * use the full power of requestish to build request URL ([ff4da89](https://github.com/battis/oauth2-cli/commit/ff4da8900e83df0d4dbd941058084523ad7ce3a6))
32
+ - use the full power of requestish to build request URL ([ff4da89](https://github.com/battis/oauth2-cli/commit/ff4da8900e83df0d4dbd941058084523ad7ce3a6))
20
33
 
21
34
  ## [1.2.4](https://github.com/battis/oauth2-cli/compare/oauth2-cli/1.2.3...oauth2-cli/1.2.4) (2026-03-08)
22
35
 
23
-
24
36
  ### Bug Fixes
25
37
 
26
- * actually fix base_url completion removing search params ([2575df7](https://github.com/battis/oauth2-cli/commit/2575df7250ec1c5c0ba76ab5f7babc40b2e5a07d))
38
+ - actually fix base_url completion removing search params ([2575df7](https://github.com/battis/oauth2-cli/commit/2575df7250ec1c5c0ba76ab5f7babc40b2e5a07d))
27
39
 
28
40
  ## [1.2.3](https://github.com/battis/oauth2-cli/compare/oauth2-cli/1.2.2...oauth2-cli/1.2.3) (2026-03-08)
29
41
 
30
42
  ## [1.2.2](https://github.com/battis/oauth2-cli/compare/oauth2-cli/1.2.1...oauth2-cli/1.2.2) (2026-03-08)
31
43
 
32
-
33
44
  ### Bug Fixes
34
45
 
35
- * don't lose search params when building url from base or credentials.issuer ([a328c96](https://github.com/battis/oauth2-cli/commit/a328c9693d0a39cccc7d6f38bd24065b6f633c1b))
46
+ - don't lose search params when building url from base or credentials.issuer ([a328c96](https://github.com/battis/oauth2-cli/commit/a328c9693d0a39cccc7d6f38bd24065b6f633c1b))
36
47
 
37
48
  ## [1.2.1](https://github.com/battis/oauth2-cli/compare/oauth2-cli/1.2.0...oauth2-cli/1.2.1) (2026-03-08)
38
49
 
39
-
40
50
  ### Bug Fixes
41
51
 
42
- * still more detail on JSON error ([d9d28ce](https://github.com/battis/oauth2-cli/commit/d9d28ce8145a5c29288f28317041e8545b232a1e))
52
+ - still more detail on JSON error ([d9d28ce](https://github.com/battis/oauth2-cli/commit/d9d28ce8145a5c29288f28317041e8545b232a1e))
43
53
 
44
54
  ## [1.2.0](https://github.com/battis/oauth2-cli/compare/oauth2-cli/1.1.5...oauth2-cli/1.2.0) (2026-03-08)
45
55
 
46
-
47
56
  ### Features
48
57
 
49
- * add prepareResponse hook ([c1ada04](https://github.com/battis/oauth2-cli/commit/c1ada04c20f08a5dac11414b44ea188f4aafe76d))
50
-
58
+ - add prepareResponse hook ([c1ada04](https://github.com/battis/oauth2-cli/commit/c1ada04c20f08a5dac11414b44ea188f4aafe76d))
51
59
 
52
60
  ### Bug Fixes
53
61
 
54
- * more detailed error response on JSON parse fail ([b8c414d](https://github.com/battis/oauth2-cli/commit/b8c414d650a7acfce8d2d297639fbecddd5f2382))
62
+ - more detailed error response on JSON parse fail ([b8c414d](https://github.com/battis/oauth2-cli/commit/b8c414d650a7acfce8d2d297639fbecddd5f2382))
55
63
 
56
64
  ## [1.1.5](https://github.com/battis/oauth2-cli/compare/oauth2-cli/1.1.4...oauth2-cli/1.1.5) (2026-03-07)
57
65
 
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,15 +284,28 @@ 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) {
296
+ const body = await response.text();
290
297
  try {
291
- return (await response.json());
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;
292
305
  }
293
- catch (cause) {
306
+ catch (error) {
294
307
  throw new Error(`${this.name} response could not be parsed as JSON`, {
295
- cause
308
+ cause: { error, body }
296
309
  });
297
310
  }
298
311
  }
@@ -311,11 +324,15 @@ export class Client extends EventEmitter {
311
324
  }
312
325
  }
313
326
  /**
314
- * Returns the result of {@link request} as a parsed JSON object, optionally
315
- * 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>`
316
332
  */
317
- async requestJSON(url, method = 'GET', body, headers = {}, dPoPOptions) {
318
- 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);
319
336
  }
320
337
  /**
321
338
  * Request a protected resource using the client's access token.
@@ -327,16 +344,16 @@ export class Client extends EventEmitter {
327
344
  * paths relative to the `issuer` URL as well as absolute URLs
328
345
  * @param init Optional
329
346
  * @param dPoPOptions Optional, see {@link OpenIDClient.DPoPOptions}
330
- * @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
331
348
  */
332
- async fetch(input, init, dPoPOptions) {
333
- 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);
334
351
  }
335
352
  /**
336
353
  * Returns the result of {@link fetch} as a parsed JSON object, optionally
337
- * typed as `J`
354
+ * typed as `T`
338
355
  */
339
- async fetchJSON(input, init, dPoPOptions) {
340
- 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);
341
358
  }
342
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.7",
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": {