@relayfile/sdk 0.7.8 → 0.7.10

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/dist/setup.d.ts CHANGED
@@ -74,6 +74,91 @@ export declare class WorkspaceHandle {
74
74
  waitForNotion(options?: WaitForConnectionOptions): Promise<void>;
75
75
  isConnected(provider: WorkspaceIntegrationProvider, connectionId: string): Promise<boolean>;
76
76
  disconnectIntegration(provider: WorkspaceIntegrationProvider, _connectionId?: string): Promise<void>;
77
+ /**
78
+ * Bind an existing Nango connection to this workspace + provider slot
79
+ * without going through the OAuth re-mint flow. Use this when an operator
80
+ * has already minted the connection out-of-band (Nango UI, third-party
81
+ * setup) and just wants Cloud to start routing sync webhooks for it.
82
+ *
83
+ * The Cloud-side adopt route validates that the Nango connection exists
84
+ * upstream and that its end-user/workspace tag matches this workspace.
85
+ * On success returns the bound `connectionId` and, when a stale prior
86
+ * row was atomically replaced, a `replacedConnectionId` so callers can
87
+ * surface that a migration happened.
88
+ *
89
+ * Failure modes (HTTP body carries `code`):
90
+ * - `connection_not_found` (404): Nango doesn't know this connectionId
91
+ * - `workspace_mismatch` (409): connection belongs to a different
92
+ * workspace; the body includes `pathWorkspaceId` and
93
+ * `connectionWorkspaceId`
94
+ * - `existing_connection_live_or_unknown` (409): a different
95
+ * connection is already bound here and is either still live
96
+ * upstream or has indeterminate state; operator must disconnect
97
+ * first
98
+ */
99
+ adoptIntegration(provider: WorkspaceIntegrationProvider, connectionId: string, options?: {
100
+ providerConfigKey?: string;
101
+ }): Promise<{
102
+ connectionId: string;
103
+ replacedConnectionId?: string;
104
+ }>;
105
+ /**
106
+ * List the upstream resources the current connection's OAuth grant
107
+ * covers. Today only Atlassian-family providers (`jira`, `confluence`)
108
+ * have meaningful entries here — every Atlassian OAuth grant can cover
109
+ * multiple sites (cloudIds), and the operator needs to bind one of them
110
+ * to this workspace via {@link setIntegrationMetadata} before sync can
111
+ * run.
112
+ *
113
+ * Cloud returns 400 `provider_has_no_accessible_resources` for providers
114
+ * that don't model this concept (currently everything non-Atlassian);
115
+ * that error surfaces here as a `CloudApiError` so callers can handle
116
+ * it explicitly rather than treating an empty list as ambiguous.
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * const sites = await workspace.listAccessibleResources("jira")
121
+ * if (sites.length > 1) {
122
+ * const choice = await promptOperator(sites)
123
+ * await workspace.setIntegrationMetadata("jira", {
124
+ * cloudId: choice.id,
125
+ * baseUrl: choice.url
126
+ * })
127
+ * }
128
+ * ```
129
+ */
130
+ listAccessibleResources(provider: WorkspaceIntegrationProvider | string): Promise<Array<{
131
+ id: string;
132
+ url: string;
133
+ name?: string;
134
+ scopes?: string[];
135
+ avatarUrl?: string;
136
+ }>>;
137
+ /**
138
+ * Set the operator-controlled metadata namespace on the workspace's
139
+ * connection for `provider`. The cloud route forwards the payload to
140
+ * Nango's `setMetadata` (full-replacement, not merge), so the value
141
+ * passed here becomes the connection's metadata wholesale. Callers that
142
+ * want merge semantics should read existing metadata first.
143
+ *
144
+ * This is the second half of the post-OAuth picker flow for Jira /
145
+ * Confluence (see {@link listAccessibleResources}), but is intentionally
146
+ * general-purpose — any provider whose sync requires operator-supplied
147
+ * connection-level config (e.g. a non-default API host) can use this.
148
+ *
149
+ * The cloud side rejects top-level keys that look like Nango plumbing
150
+ * (`_*`, `connection_*`, `auth_*`, `provider_config_key`, `connection_id`)
151
+ * with `code: "invalid_metadata"` so a typo can't clobber the connection.
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * await workspace.setIntegrationMetadata("jira", {
156
+ * cloudId: "abc-123",
157
+ * baseUrl: "https://foo.atlassian.net"
158
+ * })
159
+ * ```
160
+ */
161
+ setIntegrationMetadata(provider: WorkspaceIntegrationProvider | string, metadata: Record<string, unknown>): Promise<Record<string, unknown>>;
77
162
  getToken(): string;
78
163
  requestJson(options: Omit<CloudRequestOptions, "tokenProvider">): Promise<unknown>;
79
164
  mountEnv(options?: WorkspaceMountEnvOptions): WorkspaceMountEnv;
package/dist/setup.js CHANGED
@@ -388,6 +388,147 @@ export class WorkspaceHandle {
388
388
  });
389
389
  this._pendingConnections.delete(provider);
390
390
  }
391
+ /**
392
+ * Bind an existing Nango connection to this workspace + provider slot
393
+ * without going through the OAuth re-mint flow. Use this when an operator
394
+ * has already minted the connection out-of-band (Nango UI, third-party
395
+ * setup) and just wants Cloud to start routing sync webhooks for it.
396
+ *
397
+ * The Cloud-side adopt route validates that the Nango connection exists
398
+ * upstream and that its end-user/workspace tag matches this workspace.
399
+ * On success returns the bound `connectionId` and, when a stale prior
400
+ * row was atomically replaced, a `replacedConnectionId` so callers can
401
+ * surface that a migration happened.
402
+ *
403
+ * Failure modes (HTTP body carries `code`):
404
+ * - `connection_not_found` (404): Nango doesn't know this connectionId
405
+ * - `workspace_mismatch` (409): connection belongs to a different
406
+ * workspace; the body includes `pathWorkspaceId` and
407
+ * `connectionWorkspaceId`
408
+ * - `existing_connection_live_or_unknown` (409): a different
409
+ * connection is already bound here and is either still live
410
+ * upstream or has indeterminate state; operator must disconnect
411
+ * first
412
+ */
413
+ async adoptIntegration(provider, connectionId, options = {}) {
414
+ assertProvider(provider);
415
+ const trimmedConnectionId = connectionId?.trim();
416
+ if (!trimmedConnectionId) {
417
+ throw new Error("connectionId is required to adopt an integration");
418
+ }
419
+ const body = { connectionId: trimmedConnectionId };
420
+ const providerConfigKey = options.providerConfigKey?.trim();
421
+ if (providerConfigKey) {
422
+ body.providerConfigKey = providerConfigKey;
423
+ }
424
+ const response = (await this._setup.requestJson({
425
+ operation: "adoptIntegration",
426
+ method: "POST",
427
+ path: `api/v1/workspaces/${encodeURIComponent(this.workspaceId)}/integrations/${encodeURIComponent(provider)}/adopt`,
428
+ body,
429
+ tokenProvider: async () => this.getOrRefreshToken()
430
+ }));
431
+ const boundConnectionId = typeof response.connectionId === "string" && response.connectionId.trim()
432
+ ? response.connectionId.trim()
433
+ : trimmedConnectionId;
434
+ const replacedConnectionId = typeof response.replacedConnectionId === "string" &&
435
+ response.replacedConnectionId.trim()
436
+ ? response.replacedConnectionId.trim()
437
+ : undefined;
438
+ this._pendingConnections.delete(provider);
439
+ return replacedConnectionId
440
+ ? { connectionId: boundConnectionId, replacedConnectionId }
441
+ : { connectionId: boundConnectionId };
442
+ }
443
+ /**
444
+ * List the upstream resources the current connection's OAuth grant
445
+ * covers. Today only Atlassian-family providers (`jira`, `confluence`)
446
+ * have meaningful entries here — every Atlassian OAuth grant can cover
447
+ * multiple sites (cloudIds), and the operator needs to bind one of them
448
+ * to this workspace via {@link setIntegrationMetadata} before sync can
449
+ * run.
450
+ *
451
+ * Cloud returns 400 `provider_has_no_accessible_resources` for providers
452
+ * that don't model this concept (currently everything non-Atlassian);
453
+ * that error surfaces here as a `CloudApiError` so callers can handle
454
+ * it explicitly rather than treating an empty list as ambiguous.
455
+ *
456
+ * @example
457
+ * ```ts
458
+ * const sites = await workspace.listAccessibleResources("jira")
459
+ * if (sites.length > 1) {
460
+ * const choice = await promptOperator(sites)
461
+ * await workspace.setIntegrationMetadata("jira", {
462
+ * cloudId: choice.id,
463
+ * baseUrl: choice.url
464
+ * })
465
+ * }
466
+ * ```
467
+ */
468
+ async listAccessibleResources(provider) {
469
+ const normalized = normalizeProviderId(provider);
470
+ const response = (await this._setup.requestJson({
471
+ operation: "listAccessibleResources",
472
+ method: "GET",
473
+ path: `api/v1/workspaces/${encodeURIComponent(this.workspaceId)}/integrations/${encodeURIComponent(normalized)}/accessible-resources`,
474
+ tokenProvider: async () => this.getOrRefreshToken()
475
+ }));
476
+ if (!response || !Array.isArray(response.resources)) {
477
+ return [];
478
+ }
479
+ return response.resources
480
+ .filter((entry) => {
481
+ if (!entry || typeof entry !== "object")
482
+ return false;
483
+ const record = entry;
484
+ return (typeof record.id === "string" &&
485
+ record.id.length > 0 &&
486
+ typeof record.url === "string" &&
487
+ record.url.length > 0);
488
+ })
489
+ .map((entry) => ({ ...entry }));
490
+ }
491
+ /**
492
+ * Set the operator-controlled metadata namespace on the workspace's
493
+ * connection for `provider`. The cloud route forwards the payload to
494
+ * Nango's `setMetadata` (full-replacement, not merge), so the value
495
+ * passed here becomes the connection's metadata wholesale. Callers that
496
+ * want merge semantics should read existing metadata first.
497
+ *
498
+ * This is the second half of the post-OAuth picker flow for Jira /
499
+ * Confluence (see {@link listAccessibleResources}), but is intentionally
500
+ * general-purpose — any provider whose sync requires operator-supplied
501
+ * connection-level config (e.g. a non-default API host) can use this.
502
+ *
503
+ * The cloud side rejects top-level keys that look like Nango plumbing
504
+ * (`_*`, `connection_*`, `auth_*`, `provider_config_key`, `connection_id`)
505
+ * with `code: "invalid_metadata"` so a typo can't clobber the connection.
506
+ *
507
+ * @example
508
+ * ```ts
509
+ * await workspace.setIntegrationMetadata("jira", {
510
+ * cloudId: "abc-123",
511
+ * baseUrl: "https://foo.atlassian.net"
512
+ * })
513
+ * ```
514
+ */
515
+ async setIntegrationMetadata(provider, metadata) {
516
+ const normalized = normalizeProviderId(provider);
517
+ if (!isPlainRecord(metadata)) {
518
+ throw new Error("metadata must be a plain object");
519
+ }
520
+ const response = (await this._setup.requestJson({
521
+ operation: "setIntegrationMetadata",
522
+ method: "PUT",
523
+ path: `api/v1/workspaces/${encodeURIComponent(this.workspaceId)}/integrations/${encodeURIComponent(normalized)}/metadata`,
524
+ body: { metadata },
525
+ tokenProvider: async () => this.getOrRefreshToken()
526
+ }));
527
+ if (isPlainRecord(response?.metadata)) {
528
+ return { ...response.metadata };
529
+ }
530
+ throw new Error("invalid cloud response: expected metadata to be a plain object");
531
+ }
391
532
  getToken() {
392
533
  return this._token;
393
534
  }
@@ -592,6 +733,24 @@ class MountedWorkspaceHandleImpl {
592
733
  await safeStopLauncher(this.launcherInstance);
593
734
  }
594
735
  }
736
+ // The metadata + accessible-resources verbs accept `WorkspaceIntegrationProvider | string`
737
+ // so operators can target dynamically-discovered providers (Composio toolkit
738
+ // slugs, future Atlassian-family additions) without an SDK release. We still
739
+ // strip trailing whitespace and lower-case so consumers don't have to.
740
+ function normalizeProviderId(provider) {
741
+ const trimmed = typeof provider === "string" ? provider.trim() : "";
742
+ if (!trimmed) {
743
+ throw new Error("provider is required");
744
+ }
745
+ return trimmed.toLowerCase();
746
+ }
747
+ function isPlainRecord(value) {
748
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
749
+ return false;
750
+ }
751
+ const proto = Object.getPrototypeOf(value);
752
+ return proto === Object.prototype || proto === null;
753
+ }
595
754
  function assertProvider(provider) {
596
755
  if (!WORKSPACE_INTEGRATION_PROVIDERS.includes(provider)) {
597
756
  throw new UnknownProviderError(provider);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relayfile/sdk",
3
- "version": "0.7.8",
3
+ "version": "0.7.10",
4
4
  "description": "TypeScript SDK for relayfile — real-time filesystem for humans and agents",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -22,7 +22,7 @@
22
22
  "prepublishOnly": "npm run build"
23
23
  },
24
24
  "dependencies": {
25
- "@relayfile/core": "0.7.8"
25
+ "@relayfile/core": "0.7.10"
26
26
  },
27
27
  "devDependencies": {
28
28
  "typescript": "^5.7.3",