@withone/connect 0.22.1 → 0.23.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/README.md CHANGED
@@ -516,7 +516,7 @@ The job renews both tokens when either is within 3 days of expiring. Without it,
516
516
 
517
517
  | Method | Mode | |
518
518
  |---|---|---|
519
- | `listConnections(userId)` | both | The connections the grant reaches, each with its `access`. |
519
+ | `listConnections(userId, { limit?, page? })` | both | The connections the grant reaches, each with its `access`. Every one of them by default; pass `page` (1-based) and `limit` (up to 150) for one page. |
520
520
  | `searchActions(userId, platform, query, { limit?, mode? })` | both | Best matches first, 5 by default. `mode` is `"execute"` or `"knowledge"`. |
521
521
  | `getActionKnowledge(userId, actionId)` | both | The action's guide, input shape, method and path. |
522
522
  | `listActions(userId, platform)` | both | Every action of a platform. |
@@ -543,6 +543,9 @@ function oneRefusal(response, data) {
543
543
  return "one_error";
544
544
  }
545
545
  const CATALOG_PAGE_SIZE = 100;
546
+ /** One's largest page; reading every connection takes the fewest calls. */
547
+ const CONNECTIONS_PAGE_SIZE = 150;
548
+ const CONNECTIONS_MAX_PAGES = 20;
546
549
  const CATALOG_MAX_PAGES = 20;
547
550
  const SEARCH_DEFAULT_LIMIT = 5;
548
551
  const isPlainObject = value => typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Blob) && !(value instanceof FormData);
@@ -750,15 +753,32 @@ function createOneConnect(config) {
750
753
  if (verdict?.code === refused.code) return false;
751
754
  throw verdict ?? new OneConnectError("request_failed", `One could not confirm the user's consent (HTTP ${probe.status}). Nothing stored was changed; try again.`, probe.status);
752
755
  };
753
- const listConnections = async userId => {
754
- const response = await oneFetch(userId, "/connections/reachable");
756
+ const connectionsPage = async (userId, limit, page) => {
757
+ const response = await oneFetch(userId, `/connections/reachable?limit=${limit}&page=${page}`);
755
758
  if (!response.ok) {
756
759
  const refused = credential.refusal(response.status, await response.text());
757
760
  if (refused) throw refused;
758
761
  throw new OneConnectError("request_failed", `One refused the connections request (HTTP ${response.status}).`, response.status);
759
762
  }
760
- const body = await response.json();
761
- return body.rows ?? [];
763
+ return await response.json();
764
+ };
765
+
766
+ /**
767
+ * One page when the caller names one; otherwise every page, so a user
768
+ * with more connections than one page holds is never cut short. A page
769
+ * that fails fails the whole list rather than returning part of it.
770
+ */
771
+ const listConnections = async (userId, options = {}) => {
772
+ if (options.page !== undefined || options.limit !== undefined) {
773
+ const page = await connectionsPage(userId, Math.max(1, Math.floor(options.limit ?? 20)), Math.max(1, Math.floor(options.page ?? 1)));
774
+ return page.rows ?? [];
775
+ }
776
+ const first = await connectionsPage(userId, CONNECTIONS_PAGE_SIZE, 1);
777
+ const pages = Math.min(Math.max(first.pages ?? 1, 1), CONNECTIONS_MAX_PAGES);
778
+ const rest = await Promise.all(Array.from({
779
+ length: pages - 1
780
+ }, (_, index) => connectionsPage(userId, CONNECTIONS_PAGE_SIZE, index + 2)));
781
+ return [first, ...rest].flatMap(page => page.rows ?? []);
762
782
  };
763
783
 
764
784
  /**
@@ -1,4 +1,4 @@
1
- import { type ActionKnowledge, type CompleteAuthorizationInput, type CompleteAuthorizationResult, type OneConnectKeyConfig, type OneConnectMode, type OneConnectServerConfig, type OneConnectTokenConfig, type OneConnectTokens, type PlatformAction, type ReachableConnection, type RefreshIfExpiringOptions, type RunActionInput, type RunActionResult, type SearchActionsOptions, type StartAuthorizationInput, type StartAuthorizationResult } from "./types";
1
+ import { type ActionKnowledge, type CompleteAuthorizationInput, type CompleteAuthorizationResult, type OneConnectKeyConfig, type OneConnectMode, type OneConnectServerConfig, type OneConnectTokenConfig, type OneConnectTokens, type PlatformAction, type ListConnectionsOptions, type ReachableConnection, type RefreshIfExpiringOptions, type RunActionInput, type RunActionResult, type SearchActionsOptions, type StartAuthorizationInput, type StartAuthorizationResult } from "./types";
2
2
  export * from "./types";
3
3
  export { encodeUserReference, parseUserReference } from "./key";
4
4
  export { refreshTokenExpiresAt, tenancyHeaders, tokenScopes } from "./oauth";
@@ -25,8 +25,9 @@ export interface OneConnectClient {
25
25
  /** Drops the app's copy of what it stored. The user revokes the grant
26
26
  * itself from their One dashboard. */
27
27
  disconnect: (userId: string) => Promise<void>;
28
- /** The connections the grant reaches, each with its access. */
29
- listConnections: (userId: string) => Promise<ReachableConnection[]>;
28
+ /** The connections the grant reaches, each with its access: every one
29
+ * of them, or a single page when `page` or `limit` is given. */
30
+ listConnections: (userId: string, options?: ListConnectionsOptions) => Promise<ReachableConnection[]>;
30
31
  /** Every catalog action of a platform. What exists, not what is
31
32
  * permitted; the grant decides that when the action runs. */
32
33
  listActions: (userId: string, platform: string) => Promise<PlatformAction[]>;
@@ -541,6 +541,9 @@ function oneRefusal(response, data) {
541
541
  return "one_error";
542
542
  }
543
543
  const CATALOG_PAGE_SIZE = 100;
544
+ /** One's largest page; reading every connection takes the fewest calls. */
545
+ const CONNECTIONS_PAGE_SIZE = 150;
546
+ const CONNECTIONS_MAX_PAGES = 20;
544
547
  const CATALOG_MAX_PAGES = 20;
545
548
  const SEARCH_DEFAULT_LIMIT = 5;
546
549
  const isPlainObject = value => typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Blob) && !(value instanceof FormData);
@@ -748,15 +751,32 @@ function createOneConnect(config) {
748
751
  if (verdict?.code === refused.code) return false;
749
752
  throw verdict ?? new OneConnectError("request_failed", `One could not confirm the user's consent (HTTP ${probe.status}). Nothing stored was changed; try again.`, probe.status);
750
753
  };
751
- const listConnections = async userId => {
752
- const response = await oneFetch(userId, "/connections/reachable");
754
+ const connectionsPage = async (userId, limit, page) => {
755
+ const response = await oneFetch(userId, `/connections/reachable?limit=${limit}&page=${page}`);
753
756
  if (!response.ok) {
754
757
  const refused = credential.refusal(response.status, await response.text());
755
758
  if (refused) throw refused;
756
759
  throw new OneConnectError("request_failed", `One refused the connections request (HTTP ${response.status}).`, response.status);
757
760
  }
758
- const body = await response.json();
759
- return body.rows ?? [];
761
+ return await response.json();
762
+ };
763
+
764
+ /**
765
+ * One page when the caller names one; otherwise every page, so a user
766
+ * with more connections than one page holds is never cut short. A page
767
+ * that fails fails the whole list rather than returning part of it.
768
+ */
769
+ const listConnections = async (userId, options = {}) => {
770
+ if (options.page !== undefined || options.limit !== undefined) {
771
+ const page = await connectionsPage(userId, Math.max(1, Math.floor(options.limit ?? 20)), Math.max(1, Math.floor(options.page ?? 1)));
772
+ return page.rows ?? [];
773
+ }
774
+ const first = await connectionsPage(userId, CONNECTIONS_PAGE_SIZE, 1);
775
+ const pages = Math.min(Math.max(first.pages ?? 1, 1), CONNECTIONS_MAX_PAGES);
776
+ const rest = await Promise.all(Array.from({
777
+ length: pages - 1
778
+ }, (_, index) => connectionsPage(userId, CONNECTIONS_PAGE_SIZE, index + 2)));
779
+ return [first, ...rest].flatMap(page => page.rows ?? []);
760
780
  };
761
781
 
762
782
  /**
@@ -216,6 +216,14 @@ export interface PlatformAction {
216
216
  * serves, which takes the connection key in its body. */
217
217
  tags?: string[];
218
218
  }
219
+ export interface ListConnectionsOptions {
220
+ /** Return just this page, 1-based. Omit it (and `limit`) to get every
221
+ * connection the grant reaches. */
222
+ page?: number;
223
+ /** Connections per page when asking for one page. One caps it at 150;
224
+ * 20 when omitted. */
225
+ limit?: number;
226
+ }
219
227
  export interface SearchActionsOptions {
220
228
  /** How many candidates to return. Five when omitted. */
221
229
  limit?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withone/connect",
3
- "version": "0.22.1",
3
+ "version": "0.23.0",
4
4
  "description": "One Connect for your app: the button your users press, the two backend routes as one import, and a server client that calls One with the grant. Users keep their connections in One; your app holds only what they granted.",
5
5
  "files": [
6
6
  "dist",
@@ -56,6 +56,7 @@ import {
56
56
  type OneConnectTokenConfig,
57
57
  type OneConnectTokens,
58
58
  type PlatformAction,
59
+ type ListConnectionsOptions,
59
60
  type ReachableConnection,
60
61
  type RefreshIfExpiringOptions,
61
62
  type RunActionInput,
@@ -89,6 +90,9 @@ function oneRefusal(response: Response, data: unknown): OneRefusal | null {
89
90
  }
90
91
 
91
92
  const CATALOG_PAGE_SIZE = 100;
93
+ /** One's largest page; reading every connection takes the fewest calls. */
94
+ const CONNECTIONS_PAGE_SIZE = 150;
95
+ const CONNECTIONS_MAX_PAGES = 20;
92
96
  const CATALOG_MAX_PAGES = 20;
93
97
  const SEARCH_DEFAULT_LIMIT = 5;
94
98
 
@@ -166,8 +170,12 @@ export interface OneConnectClient {
166
170
  /** Drops the app's copy of what it stored. The user revokes the grant
167
171
  * itself from their One dashboard. */
168
172
  disconnect: (userId: string) => Promise<void>;
169
- /** The connections the grant reaches, each with its access. */
170
- listConnections: (userId: string) => Promise<ReachableConnection[]>;
173
+ /** The connections the grant reaches, each with its access: every one
174
+ * of them, or a single page when `page` or `limit` is given. */
175
+ listConnections: (
176
+ userId: string,
177
+ options?: ListConnectionsOptions,
178
+ ) => Promise<ReachableConnection[]>;
171
179
  /** Every catalog action of a platform. What exists, not what is
172
180
  * permitted; the grant decides that when the action runs. */
173
181
  listActions: (userId: string, platform: string) => Promise<PlatformAction[]>;
@@ -445,8 +453,17 @@ export function createOneConnect(
445
453
  );
446
454
  };
447
455
 
448
- const listConnections = async (userId: string): Promise<ReachableConnection[]> => {
449
- const response = await oneFetch(userId, "/connections/reachable");
456
+ type ConnectionsPage = { rows?: ReachableConnection[]; pages?: number };
457
+
458
+ const connectionsPage = async (
459
+ userId: string,
460
+ limit: number,
461
+ page: number,
462
+ ): Promise<ConnectionsPage> => {
463
+ const response = await oneFetch(
464
+ userId,
465
+ `/connections/reachable?limit=${limit}&page=${page}`,
466
+ );
450
467
  if (!response.ok) {
451
468
  const refused = credential.refusal(response.status, await response.text());
452
469
  if (refused) throw refused;
@@ -456,8 +473,34 @@ export function createOneConnect(
456
473
  response.status,
457
474
  );
458
475
  }
459
- const body = (await response.json()) as { rows?: ReachableConnection[] };
460
- return body.rows ?? [];
476
+ return (await response.json()) as ConnectionsPage;
477
+ };
478
+
479
+ /**
480
+ * One page when the caller names one; otherwise every page, so a user
481
+ * with more connections than one page holds is never cut short. A page
482
+ * that fails fails the whole list rather than returning part of it.
483
+ */
484
+ const listConnections = async (
485
+ userId: string,
486
+ options: ListConnectionsOptions = {},
487
+ ): Promise<ReachableConnection[]> => {
488
+ if (options.page !== undefined || options.limit !== undefined) {
489
+ const page = await connectionsPage(
490
+ userId,
491
+ Math.max(1, Math.floor(options.limit ?? 20)),
492
+ Math.max(1, Math.floor(options.page ?? 1)),
493
+ );
494
+ return page.rows ?? [];
495
+ }
496
+ const first = await connectionsPage(userId, CONNECTIONS_PAGE_SIZE, 1);
497
+ const pages = Math.min(Math.max(first.pages ?? 1, 1), CONNECTIONS_MAX_PAGES);
498
+ const rest = await Promise.all(
499
+ Array.from({ length: pages - 1 }, (_, index) =>
500
+ connectionsPage(userId, CONNECTIONS_PAGE_SIZE, index + 2),
501
+ ),
502
+ );
503
+ return [first, ...rest].flatMap((page) => page.rows ?? []);
461
504
  };
462
505
 
463
506
  /**
@@ -231,6 +231,15 @@ export interface PlatformAction {
231
231
  tags?: string[];
232
232
  }
233
233
 
234
+ export interface ListConnectionsOptions {
235
+ /** Return just this page, 1-based. Omit it (and `limit`) to get every
236
+ * connection the grant reaches. */
237
+ page?: number;
238
+ /** Connections per page when asking for one page. One caps it at 150;
239
+ * 20 when omitted. */
240
+ limit?: number;
241
+ }
242
+
234
243
  export interface SearchActionsOptions {
235
244
  /** How many candidates to return. Five when omitted. */
236
245
  limit?: number;