@cosmoops/connect-sdk 0.46.0 → 0.47.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
@@ -3,6 +3,40 @@
3
3
  `@cosmoops/connect-sdk` follows semver, with the 0.x convention that **minor
4
4
  versions may carry breaking changes** and patch versions never do.
5
5
 
6
+ ## 0.47.0
7
+
8
+ ### `connect.integrations.request()` reaches the endpoints a toolkit has no action for
9
+
10
+ A Composio toolkit's action catalogue is not the provider's API: Zoho Books
11
+ ships no action to create a sales order, an estimate or an invoice.
12
+ `integrations.request(workspaceId, { toolkitSlug, method, endpoint, query, body })`
13
+ sends one request to the provider through the workspace's connected account,
14
+ under the `integrations:execute` scope - Connect still holds the credential.
15
+
16
+ `endpoint` is a path relative to the provider's API base (`/salesorders`).
17
+ Connect refuses an absolute URL, a query string in the path and dot segments,
18
+ and there is no way to set a header: whoever chooses the host chooses where
19
+ the credential goes. The call resolves whenever Connect relayed it - read the
20
+ provider's own `status` and `data` from the result.
21
+
22
+ ### `initiateConnect` takes `connectionData`
23
+
24
+ Pre-fills a toolkit's own connection fields on the provider's screen.
25
+ `{ extension: "in" }` puts a Zoho connection on the India data centre, which
26
+ otherwise defaults to `zoho.com`.
27
+
28
+ ### A 503 without `Retry-After` is no longer retried
29
+
30
+ The SDK retried every 503 on an idempotent request, twice, sleeping the
31
+ server's `Retry-After` between. Connect now leaves that header off a 503 it
32
+ knows will not heal - a dependency rejecting Connect's own credential, such
33
+ as the provider behind `connect.integrations.list()` - and the SDK now takes
34
+ that at its word: no `Retry-After`, no retry, and `ConnectError.isRetryable`
35
+ is `false`. A misconfigured dependency used to cost every read three round
36
+ trips and about four seconds of sleeping to return the same error. Every 503
37
+ Connect means to be retried still carries the header, so nothing that
38
+ recovers today stops recovering.
39
+
6
40
  ## 0.46.0
7
41
 
8
42
  ### `secondary` buttons are soft indigo
@@ -11,7 +11,7 @@ const provider_js_1 = require("./provider.js");
11
11
  *
12
12
  * Connect answers any of these paths for a signed-in user on the bare URL -
13
13
  * these two params only add the `"Back to <your app>"` banner
14
- * (`ApplicationReturnBar` on Connect's side). It validates `return_to`
14
+ * (the hand-off strip in `ShellAnnouncements` on Connect's side). It validates `return_to`
15
15
  * against this `client_id`'s registered redirect origins and silently drops
16
16
  * the banner if they don't match, so a wrong or stale `appUrl`/`clientId`
17
17
  * pairing degrades to what linking there did before rather than erroring.
@@ -37,8 +37,17 @@ class ConnectError extends Error {
37
37
  * already retries these internally (see `ConnectClientOptions.maxRetries`);
38
38
  * this is for callers deciding whether to queue their own retry after the
39
39
  * SDK has given up.
40
+ *
41
+ * **A 503 is retryable only when it carries `Retry-After`.** Connect sends
42
+ * that header on every 503 it means to be retried - a dependency blip, a
43
+ * contended transaction - and leaves it off when the dependency is
44
+ * *misconfigured* (a revoked provider key, say), where every retry gets the
45
+ * identical answer. Retrying those anyway cost a page three round trips and
46
+ * two sleeps per read to learn nothing.
40
47
  */
41
48
  get isRetryable() {
49
+ if (this.status === 503)
50
+ return this.retryAfterMs !== null;
42
51
  return this.status === 0 || this.status === 429 || this.status >= 500;
43
52
  }
44
53
  /**
@@ -183,4 +183,4 @@ class ConnectClient {
183
183
  }
184
184
  exports.ConnectClient = ConnectClient;
185
185
  /** Kept in step with `package.json` by `sdk.test.ts`. */
186
- exports.SDK_VERSION = "0.46.0";
186
+ exports.SDK_VERSION = "0.47.0";
@@ -189,7 +189,10 @@ async function request(options, spec) {
189
189
  return (await readBody(response));
190
190
  }
191
191
  lastError = toConnectError(response, await readBody(response), endpoint);
192
- if (!RETRYABLE_STATUS.has(response.status))
192
+ // The status decides, and then the error's own verdict - which is what
193
+ // keeps a 503 sent without `Retry-After` from being replayed. See
194
+ // `ConnectError.isRetryable`.
195
+ if (!RETRYABLE_STATUS.has(response.status) || !lastError.isRetryable)
193
196
  throw lastError;
194
197
  }
195
198
  finally {
@@ -56,6 +56,22 @@ class IntegrationsResource {
56
56
  json: { workspaceId, toolkitSlug },
57
57
  });
58
58
  }
59
+ /**
60
+ * One raw request to a connected provider's own API, for an endpoint the
61
+ * toolkit has no action for (Zoho Books ships no "create sales order").
62
+ *
63
+ * **Resolves whenever Connect relayed the request**, whatever the provider
64
+ * answered: read `status` and `data` for that. A provider-side 4xx is a
65
+ * business answer ("that GSTIN is invalid") rather than a transport
66
+ * failure, so it is not thrown as one.
67
+ */
68
+ async request(workspaceId, input) {
69
+ return (0, http_js_1.request)(this.transport, {
70
+ method: "POST",
71
+ path: "/integrations/proxy",
72
+ json: { workspaceId, ...input },
73
+ });
74
+ }
59
75
  /** Calls a Composio action against this workspace's connected account for the named toolkit. */
60
76
  async execute(workspaceId, input) {
61
77
  return (0, http_js_1.request)(this.transport, {
@@ -113,11 +113,20 @@ class WorkspacesResource {
113
113
  * Connect roles - so you never reimplement Connect's role resolution, and
114
114
  * you are told nothing about the customer's Connect account beyond what your
115
115
  * application needs.
116
+ *
117
+ * **Seat holders only, by default.** A workspace member with no seat on your
118
+ * application cannot sign in to it, so they are not part of your team and
119
+ * do not belong in an owner picker or an assignee list. Pass
120
+ * `includeUnassigned: true` from the screen where an admin *gives* a seat -
121
+ * it needs the people who do not have one yet. That form needs the signed-in
122
+ * member to be able to change access (a 403 otherwise), and every row
123
+ * carries `assigned` so the screen can tell the two apart.
116
124
  */
117
125
  async members(options) {
118
126
  const result = await (0, http_js_1.request)(this.transport, {
119
127
  method: "GET",
120
128
  path: "/workspaces/members",
129
+ query: { include: options.includeUnassigned ? "unassigned" : undefined },
121
130
  headers: (0, acting_as_js_1.actingAs)(options.asUser),
122
131
  });
123
132
  return result.members;
@@ -7,7 +7,7 @@ import { useConnect } from "./provider.js";
7
7
  *
8
8
  * Connect answers any of these paths for a signed-in user on the bare URL -
9
9
  * these two params only add the `"Back to <your app>"` banner
10
- * (`ApplicationReturnBar` on Connect's side). It validates `return_to`
10
+ * (the hand-off strip in `ShellAnnouncements` on Connect's side). It validates `return_to`
11
11
  * against this `client_id`'s registered redirect origins and silently drops
12
12
  * the banner if they don't match, so a wrong or stale `appUrl`/`clientId`
13
13
  * pairing degrades to what linking there did before rather than erroring.
@@ -33,8 +33,17 @@ export class ConnectError extends Error {
33
33
  * already retries these internally (see `ConnectClientOptions.maxRetries`);
34
34
  * this is for callers deciding whether to queue their own retry after the
35
35
  * SDK has given up.
36
+ *
37
+ * **A 503 is retryable only when it carries `Retry-After`.** Connect sends
38
+ * that header on every 503 it means to be retried - a dependency blip, a
39
+ * contended transaction - and leaves it off when the dependency is
40
+ * *misconfigured* (a revoked provider key, say), where every retry gets the
41
+ * identical answer. Retrying those anyway cost a page three round trips and
42
+ * two sleeps per read to learn nothing.
36
43
  */
37
44
  get isRetryable() {
45
+ if (this.status === 503)
46
+ return this.retryAfterMs !== null;
38
47
  return this.status === 0 || this.status === 429 || this.status >= 500;
39
48
  }
40
49
  /**
@@ -179,4 +179,4 @@ export class ConnectClient {
179
179
  }
180
180
  }
181
181
  /** Kept in step with `package.json` by `sdk.test.ts`. */
182
- export const SDK_VERSION = "0.46.0";
182
+ export const SDK_VERSION = "0.47.0";
@@ -184,7 +184,10 @@ export async function request(options, spec) {
184
184
  return (await readBody(response));
185
185
  }
186
186
  lastError = toConnectError(response, await readBody(response), endpoint);
187
- if (!RETRYABLE_STATUS.has(response.status))
187
+ // The status decides, and then the error's own verdict - which is what
188
+ // keeps a 503 sent without `Retry-After` from being replayed. See
189
+ // `ConnectError.isRetryable`.
190
+ if (!RETRYABLE_STATUS.has(response.status) || !lastError.isRetryable)
188
191
  throw lastError;
189
192
  }
190
193
  finally {
@@ -53,6 +53,22 @@ export class IntegrationsResource {
53
53
  json: { workspaceId, toolkitSlug },
54
54
  });
55
55
  }
56
+ /**
57
+ * One raw request to a connected provider's own API, for an endpoint the
58
+ * toolkit has no action for (Zoho Books ships no "create sales order").
59
+ *
60
+ * **Resolves whenever Connect relayed the request**, whatever the provider
61
+ * answered: read `status` and `data` for that. A provider-side 4xx is a
62
+ * business answer ("that GSTIN is invalid") rather than a transport
63
+ * failure, so it is not thrown as one.
64
+ */
65
+ async request(workspaceId, input) {
66
+ return request(this.transport, {
67
+ method: "POST",
68
+ path: "/integrations/proxy",
69
+ json: { workspaceId, ...input },
70
+ });
71
+ }
56
72
  /** Calls a Composio action against this workspace's connected account for the named toolkit. */
57
73
  async execute(workspaceId, input) {
58
74
  return request(this.transport, {
@@ -110,11 +110,20 @@ export class WorkspacesResource {
110
110
  * Connect roles - so you never reimplement Connect's role resolution, and
111
111
  * you are told nothing about the customer's Connect account beyond what your
112
112
  * application needs.
113
+ *
114
+ * **Seat holders only, by default.** A workspace member with no seat on your
115
+ * application cannot sign in to it, so they are not part of your team and
116
+ * do not belong in an owner picker or an assignee list. Pass
117
+ * `includeUnassigned: true` from the screen where an admin *gives* a seat -
118
+ * it needs the people who do not have one yet. That form needs the signed-in
119
+ * member to be able to change access (a 403 otherwise), and every row
120
+ * carries `assigned` so the screen can tell the two apart.
113
121
  */
114
122
  async members(options) {
115
123
  const result = await request(this.transport, {
116
124
  method: "GET",
117
125
  path: "/workspaces/members",
126
+ query: { include: options.includeUnassigned ? "unassigned" : undefined },
118
127
  headers: actingAs(options.asUser),
119
128
  });
120
129
  return result.members;
@@ -25,7 +25,9 @@ import type { CallingAvailability } from "../../types.js";
25
25
  * a number and the member must have "Make calls". What comes back lets this
26
26
  * browser place exactly this call and no other.
27
27
  * 3. The browser places it. Connect dials the number from the workspace's own
28
- * number; nothing is recorded.
28
+ * number. It is recorded only when the workspace has turned recording on
29
+ * (`connect.calls.getCallSettings`), and then the other party hears a
30
+ * disclosure before the conversation starts.
29
31
  *
30
32
  * ## Logging a call
31
33
  *
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Connect answers any of these paths for a signed-in user on the bare URL -
7
7
  * these two params only add the `"Back to <your app>"` banner
8
- * (`ApplicationReturnBar` on Connect's side). It validates `return_to`
8
+ * (the hand-off strip in `ShellAnnouncements` on Connect's side). It validates `return_to`
9
9
  * against this `client_id`'s registered redirect origins and silently drops
10
10
  * the banner if they don't match, so a wrong or stale `appUrl`/`clientId`
11
11
  * pairing degrades to what linking there did before rather than erroring.
@@ -34,6 +34,13 @@ export declare class ConnectError extends Error {
34
34
  * already retries these internally (see `ConnectClientOptions.maxRetries`);
35
35
  * this is for callers deciding whether to queue their own retry after the
36
36
  * SDK has given up.
37
+ *
38
+ * **A 503 is retryable only when it carries `Retry-After`.** Connect sends
39
+ * that header on every 503 it means to be retried - a dependency blip, a
40
+ * contended transaction - and leaves it off when the dependency is
41
+ * *misconfigured* (a revoked provider key, say), where every retry gets the
42
+ * identical answer. Retrying those anyway cost a page three round trips and
43
+ * two sleeps per read to learn nothing.
37
44
  */
38
45
  get isRetryable(): boolean;
39
46
  /**
@@ -141,4 +141,4 @@ export declare class ConnectClient {
141
141
  constructor(options: ConnectClientOptions);
142
142
  }
143
143
  /** Kept in step with `package.json` by `sdk.test.ts`. */
144
- export declare const SDK_VERSION = "0.46.0";
144
+ export declare const SDK_VERSION = "0.47.0";
@@ -26,7 +26,7 @@ export type { OtpResource } from "./resources/otp.js";
26
26
  export type { LogsResource } from "./resources/logs.js";
27
27
  export type { AnalyticsResource } from "./resources/analytics.js";
28
28
  export type { SessionsResource } from "./resources/sessions.js";
29
- export type { ExecuteIntegrationActionInput, InitiateIntegrationConnectionInput, IntegrationsResource, } from "./resources/integrations.js";
29
+ export type { ExecuteIntegrationActionInput, InitiateIntegrationConnectionInput, IntegrationsResource, ProxyIntegrationRequestInput, } from "./resources/integrations.js";
30
30
  export type { PlatformResource } from "./resources/platform.js";
31
31
  export type { SchedulingResource, FindSlotsInput, ListBookingsInput, CreateBookingInput, RescheduleBookingInput, } from "./resources/scheduling.js";
32
32
  export type { ContactsResource, LookupContactInput } from "./resources/contacts.js";
@@ -1,5 +1,5 @@
1
1
  import { type TransportOptions } from "../http.js";
2
- import type { DisconnectIntegrationResponse, ExecuteIntegrationActionResponse, InitiateIntegrationConnectionResponse, IntegrationsListResponse } from "../../types.js";
2
+ import type { DisconnectIntegrationResponse, ExecuteIntegrationActionResponse, InitiateIntegrationConnectionResponse, IntegrationProxyMethod, IntegrationsListResponse, ProxyIntegrationRequestResponse } from "../../types.js";
3
3
  export interface InitiateIntegrationConnectionInput {
4
4
  toolkitSlug: string;
5
5
  returnUrl: string;
@@ -13,6 +13,13 @@ export interface InitiateIntegrationConnectionInput {
13
13
  * is present to finish the OAuth screen either.
14
14
  */
15
15
  userId: string;
16
+ /**
17
+ * Pre-fills the toolkit's own connection fields on the provider screen -
18
+ * `{ extension: "in" }` puts a Zoho connection on the India data centre,
19
+ * which otherwise defaults to `zoho.com`. Which keys a toolkit reads is
20
+ * Composio's catalogue; Connect passes them through.
21
+ */
22
+ connectionData?: Record<string, string>;
16
23
  }
17
24
  export interface ExecuteIntegrationActionInput {
18
25
  toolkitSlug: string;
@@ -20,6 +27,21 @@ export interface ExecuteIntegrationActionInput {
20
27
  /** Passed through to Composio verbatim - its shape is defined by the toolkit's own action schema, which Connect does not model. */
21
28
  params?: Record<string, unknown>;
22
29
  }
30
+ export interface ProxyIntegrationRequestInput {
31
+ toolkitSlug: string;
32
+ method: IntegrationProxyMethod;
33
+ /**
34
+ * A path relative to the provider's API base - `/salesorders`, not a URL.
35
+ * Connect refuses anything with a scheme, a host, a `?` or a dot segment,
36
+ * because whoever picks the host picks where the workspace's credential
37
+ * goes. Parameters belong in `query`.
38
+ */
39
+ endpoint: string;
40
+ query?: Record<string, string>;
41
+ body?: unknown;
42
+ /** Whose connection to act through - omitted or `null` for the workspace's shared one. */
43
+ onBehalfOfUserId?: string | null;
44
+ }
23
45
  /**
24
46
  * Composio-hosted third-party connections (Google Calendar, Slack,
25
47
  * Twitter/X, ...) a workspace has connected. Connect proxies every call
@@ -51,6 +73,16 @@ export declare class IntegrationsResource {
51
73
  initiateConnect(workspaceId: string, input: InitiateIntegrationConnectionInput): Promise<InitiateIntegrationConnectionResponse>;
52
74
  /** Idempotent - disconnecting a toolkit that was never connected, or is already disconnected, is a success. */
53
75
  disconnect(workspaceId: string, toolkitSlug: string): Promise<DisconnectIntegrationResponse>;
76
+ /**
77
+ * One raw request to a connected provider's own API, for an endpoint the
78
+ * toolkit has no action for (Zoho Books ships no "create sales order").
79
+ *
80
+ * **Resolves whenever Connect relayed the request**, whatever the provider
81
+ * answered: read `status` and `data` for that. A provider-side 4xx is a
82
+ * business answer ("that GSTIN is invalid") rather than a transport
83
+ * failure, so it is not thrown as one.
84
+ */
85
+ request(workspaceId: string, input: ProxyIntegrationRequestInput): Promise<ProxyIntegrationRequestResponse>;
54
86
  /** Calls a Composio action against this workspace's connected account for the named toolkit. */
55
87
  execute(workspaceId: string, input: ExecuteIntegrationActionInput): Promise<ExecuteIntegrationActionResponse>;
56
88
  }
@@ -104,9 +104,18 @@ export declare class WorkspacesResource {
104
104
  * Connect roles - so you never reimplement Connect's role resolution, and
105
105
  * you are told nothing about the customer's Connect account beyond what your
106
106
  * application needs.
107
+ *
108
+ * **Seat holders only, by default.** A workspace member with no seat on your
109
+ * application cannot sign in to it, so they are not part of your team and
110
+ * do not belong in an owner picker or an assignee list. Pass
111
+ * `includeUnassigned: true` from the screen where an admin *gives* a seat -
112
+ * it needs the people who do not have one yet. That form needs the signed-in
113
+ * member to be able to change access (a 403 otherwise), and every row
114
+ * carries `assigned` so the screen can tell the two apart.
107
115
  */
108
116
  members(options: {
109
117
  asUser: string;
118
+ includeUnassigned?: boolean;
110
119
  }): Promise<ConnectWorkspaceMember[]>;
111
120
  /**
112
121
  * Seats, what the caller may do with them, and the invitations already
@@ -1052,6 +1052,12 @@ export interface ExecuteIntegrationActionResponse {
1052
1052
  data: unknown;
1053
1053
  successful: boolean;
1054
1054
  }
1055
+ export type IntegrationProxyMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
1056
+ /** See `IntegrationsResource.request` - `status` is the provider's, not Connect's. */
1057
+ export interface ProxyIntegrationRequestResponse {
1058
+ status: number;
1059
+ data: unknown;
1060
+ }
1055
1061
  /**
1056
1062
  * The metrics Connect can report for your application.
1057
1063
  *
@@ -1912,6 +1918,12 @@ export interface ConnectWorkspaceMember {
1912
1918
  */
1913
1919
  memberId: string;
1914
1920
  userId: string;
1921
+ /**
1922
+ * Whether this member holds a seat on your application. Only assigned
1923
+ * members can sign in to it; unassigned rows appear only when you asked
1924
+ * for them with `members({ includeUnassigned: true })`, to assign from.
1925
+ */
1926
+ assigned: boolean;
1915
1927
  email: string;
1916
1928
  displayName: string | null;
1917
1929
  photoUrl: string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmoops/connect-sdk",
3
- "version": "0.46.0",
3
+ "version": "0.47.0",
4
4
  "description": "Official server-side SDK for the CosmoOps Connect platform API - Login with Connect, entitlements, billing and metering.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "commonjs",