@quiltt/core 5.0.2 → 5.1.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
@@ -1,5 +1,25 @@
1
1
  # @quiltt/core
2
2
 
3
+ ## 5.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#406](https://github.com/quiltt/quiltt-js/pull/406) [`177a4fc`](https://github.com/quiltt/quiltt-js/commit/177a4fc3d70fa3f313f6586396719dae9763cf4b) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Add OAuth redirect URL support to React SDK
8
+
9
+ ### Patch Changes
10
+
11
+ - [#418](https://github.com/quiltt/quiltt-js/pull/418) [`2d918a0`](https://github.com/quiltt/quiltt-js/commit/2d918a089f8546bb7d20db1eefd958beca296ee0) Thanks [@sirwolfgang](https://github.com/sirwolfgang)! - Internal: Add `headers` prop to QuilttProvider
12
+
13
+ For Quiltt internal usage. Not intended for public use.
14
+
15
+ ## 5.0.3
16
+
17
+ ### Patch Changes
18
+
19
+ - [#412](https://github.com/quiltt/quiltt-js/pull/412) [`099070b`](https://github.com/quiltt/quiltt-js/commit/099070b984f7375491e1c736bc3fbd35c93c7fd3) Thanks [@rubendinho](https://github.com/rubendinho)! - Improve User Agent telemetry
20
+
21
+ - [#410](https://github.com/quiltt/quiltt-js/pull/410) [`628887c`](https://github.com/quiltt/quiltt-js/commit/628887cc592b492a1fb38ff5898aeda969140855) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Re-Export Upstream Packages
22
+
3
23
  ## 5.0.2
4
24
 
5
25
  ### Patch Changes
package/README.md CHANGED
@@ -38,7 +38,7 @@ The Auth API is used by the Quiltt Connector under the hood and can be used to p
38
38
  import { AuthAPI } from '@quiltt/core'
39
39
 
40
40
  // Set up client instance
41
- const auth = new AuthAPI('{CONNECTOR_ID}')
41
+ const auth = new AuthAPI()
42
42
 
43
43
  // Check if a Session token is valid
44
44
  await auth.ping('{SESSION_TOKEN}')
@@ -101,6 +101,8 @@ type ConnectorSDKCallbackMetadata = {
101
101
  type ConnectorSDKConnectOptions = ConnectorSDKCallbacks & {
102
102
  /** The Institution ID or search term to connect */
103
103
  institution?: string;
104
+ /** The OAuth redirect URL for mobile or embedded webview flows */
105
+ oauthRedirectUrl?: string;
104
106
  };
105
107
  /**
106
108
  * Options for the Reconnect flow
@@ -109,6 +111,8 @@ type ConnectorSDKConnectOptions = ConnectorSDKCallbacks & {
109
111
  type ConnectorSDKReconnectOptions = ConnectorSDKCallbacks & {
110
112
  /** The ID of the Connection to reconnect */
111
113
  connectionId: string;
114
+ /** The OAuth redirect URL for mobile or embedded webview flows */
115
+ oauthRedirectUrl?: string;
112
116
  };
113
117
  /** Options to initialize Connector
114
118
  *
@@ -122,6 +126,8 @@ type ConnectorSDKConnectorOptions = ConnectorSDKCallbacks & {
122
126
  connectionId?: string;
123
127
  /** The nonce to use for the script tag */
124
128
  nonce?: string;
129
+ /** The OAuth redirect URL for mobile or embedded webview flows */
130
+ oauthRedirectUrl?: string;
125
131
  };
126
132
 
127
133
  export { ConnectorSDKEventType };
@@ -99,6 +99,7 @@ class ActionCableLink extends core.ApolloLink {
99
99
  this.cables[token] = actioncable.createConsumer(index_cjs$1.endpointWebsockets + (token ? `?token=${token}` : ''));
100
100
  }
101
101
  return new rxjs.Observable((observer)=>{
102
+ let cancelled = false;
102
103
  const channelId = Math.round(Date.now() + Math.random() * 100000).toString(16);
103
104
  const actionName = this.actionName;
104
105
  const connectionParams = typeof this.connectionParams === 'function' ? this.connectionParams(operation) : this.connectionParams;
@@ -108,6 +109,7 @@ class ActionCableLink extends core.ApolloLink {
108
109
  channelId: channelId
109
110
  }, connectionParams), {
110
111
  connected: (args)=>{
112
+ if (cancelled) return;
111
113
  channel.perform(actionName, {
112
114
  query: operation.query ? graphql.print(operation.query) : null,
113
115
  variables: operation.variables,
@@ -118,6 +120,7 @@ class ActionCableLink extends core.ApolloLink {
118
120
  callbacks.connected?.(args);
119
121
  },
120
122
  received: (payload)=>{
123
+ if (cancelled) return;
121
124
  if (payload?.result?.data || payload?.result?.errors) {
122
125
  observer.next(payload.result);
123
126
  }
@@ -126,14 +129,18 @@ class ActionCableLink extends core.ApolloLink {
126
129
  }
127
130
  callbacks.received?.(payload);
128
131
  },
132
+ // Intentionally not guarded by `cancelled` - disconnected represents
133
+ // the WebSocket connection state which users may want to know about
134
+ // for cleanup/status purposes, even after the observable completes.
129
135
  disconnected: ()=>{
130
136
  callbacks.disconnected?.();
131
137
  }
132
138
  });
133
- // Make the ActionCable subscription behave like an Apollo subscription
134
- return Object.assign(channel, {
135
- closed: false
136
- });
139
+ // Return teardown logic
140
+ return ()=>{
141
+ cancelled = true;
142
+ channel.unsubscribe();
143
+ };
137
144
  });
138
145
  }
139
146
  }
@@ -99,6 +99,7 @@ class ActionCableLink extends ApolloLink {
99
99
  this.cables[token] = createConsumer(endpointWebsockets + (token ? `?token=${token}` : ''));
100
100
  }
101
101
  return new Observable((observer)=>{
102
+ let cancelled = false;
102
103
  const channelId = Math.round(Date.now() + Math.random() * 100000).toString(16);
103
104
  const actionName = this.actionName;
104
105
  const connectionParams = typeof this.connectionParams === 'function' ? this.connectionParams(operation) : this.connectionParams;
@@ -108,6 +109,7 @@ class ActionCableLink extends ApolloLink {
108
109
  channelId: channelId
109
110
  }, connectionParams), {
110
111
  connected: (args)=>{
112
+ if (cancelled) return;
111
113
  channel.perform(actionName, {
112
114
  query: operation.query ? print(operation.query) : null,
113
115
  variables: operation.variables,
@@ -118,6 +120,7 @@ class ActionCableLink extends ApolloLink {
118
120
  callbacks.connected?.(args);
119
121
  },
120
122
  received: (payload)=>{
123
+ if (cancelled) return;
121
124
  if (payload?.result?.data || payload?.result?.errors) {
122
125
  observer.next(payload.result);
123
126
  }
@@ -126,14 +129,18 @@ class ActionCableLink extends ApolloLink {
126
129
  }
127
130
  callbacks.received?.(payload);
128
131
  },
132
+ // Intentionally not guarded by `cancelled` - disconnected represents
133
+ // the WebSocket connection state which users may want to know about
134
+ // for cleanup/status purposes, even after the observable completes.
129
135
  disconnected: ()=>{
130
136
  callbacks.disconnected?.();
131
137
  }
132
138
  });
133
- // Make the ActionCable subscription behave like an Apollo subscription
134
- return Object.assign(channel, {
135
- closed: false
136
- });
139
+ // Return teardown logic
140
+ return ()=>{
141
+ cancelled = true;
142
+ channel.unsubscribe();
143
+ };
137
144
  });
138
145
  }
139
146
  }
@@ -3,7 +3,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
3
3
  var core = require('@apollo/client/core');
4
4
  var index_cjs = require('../../config/index.cjs');
5
5
  var rxjs = require('rxjs');
6
- var SubscriptionLink12s = require('./SubscriptionLink-12s-wjkChfxO.cjs');
6
+ var SubscriptionLink12s = require('./SubscriptionLink-12s-DUvHs5WL.cjs');
7
7
  var batchHttp = require('@apollo/client/link/batch-http');
8
8
  var crossfetch = require('cross-fetch');
9
9
  var error = require('@apollo/client/link/error');
@@ -88,6 +88,62 @@ const ErrorLink = new error.ErrorLink(({ error, result })=>{
88
88
 
89
89
  const ForwardableLink = new core.ApolloLink((operation, forward)=>forward(operation));
90
90
 
91
+ /**
92
+ * Apollo Link that adds custom headers to GraphQL requests.
93
+ *
94
+ * Headers can be provided statically or dynamically via a function.
95
+ * These headers are added before the AuthLink, so they will be preserved
96
+ * alongside the authorization header.
97
+ */ class HeadersLink extends core.ApolloLink {
98
+ constructor(options = {}){
99
+ super();
100
+ this.headers = options.headers ?? {};
101
+ this.getHeaders = options.getHeaders;
102
+ }
103
+ request(operation, forward) {
104
+ // If we have a dynamic headers function, handle it
105
+ if (this.getHeaders) {
106
+ return new rxjs.Observable((observer)=>{
107
+ let innerSubscription;
108
+ let cancelled = false;
109
+ Promise.resolve(this.getHeaders()).then((dynamicHeaders)=>{
110
+ // Guard against late resolution after unsubscribe
111
+ if (cancelled) return;
112
+ operation.setContext(({ headers = {} })=>({
113
+ headers: {
114
+ ...headers,
115
+ ...this.headers,
116
+ ...dynamicHeaders
117
+ }
118
+ }));
119
+ innerSubscription = forward(operation).subscribe({
120
+ next: (value)=>!cancelled && observer.next(value),
121
+ error: (err)=>!cancelled && observer.error(err),
122
+ complete: ()=>!cancelled && observer.complete()
123
+ });
124
+ }).catch((error)=>{
125
+ if (!cancelled) {
126
+ observer.error(error);
127
+ }
128
+ });
129
+ // Return teardown logic
130
+ return ()=>{
131
+ cancelled = true;
132
+ innerSubscription?.unsubscribe();
133
+ };
134
+ });
135
+ }
136
+ // Static headers only
137
+ operation.setContext(({ headers = {} })=>({
138
+ headers: {
139
+ ...headers,
140
+ ...this.headers
141
+ }
142
+ }));
143
+ return forward(operation);
144
+ }
145
+ }
146
+
91
147
  // Use `cross-fetch` only if `fetch` is not available on the `globalThis` object
92
148
  const effectiveFetch = typeof fetch === 'undefined' ? crossfetch__default.default : fetch;
93
149
  const HttpLink = new http.HttpLink({
@@ -145,6 +201,7 @@ const createVersionLink = (platformInfo)=>{
145
201
  headers: {
146
202
  ...headers,
147
203
  'Quiltt-Client-Version': index_cjs.version,
204
+ 'Quiltt-SDK-Agent': userAgent,
148
205
  'User-Agent': userAgent
149
206
  }
150
207
  }));
@@ -211,6 +268,7 @@ exports.AuthLink = AuthLink;
211
268
  exports.BatchHttpLink = BatchHttpLink;
212
269
  exports.ErrorLink = ErrorLink;
213
270
  exports.ForwardableLink = ForwardableLink;
271
+ exports.HeadersLink = HeadersLink;
214
272
  exports.HttpLink = HttpLink;
215
273
  exports.QuilttClient = QuilttClient;
216
274
  exports.RetryLink = RetryLink;
@@ -38,6 +38,26 @@ declare const ErrorLink: ErrorLink$1;
38
38
 
39
39
  declare const ForwardableLink: ApolloLink;
40
40
 
41
+ type HeadersLinkOptions = {
42
+ /** Static headers to add to every request */
43
+ headers?: Record<string, string>;
44
+ /** Dynamic headers function called on each request */
45
+ getHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
46
+ };
47
+ /**
48
+ * Apollo Link that adds custom headers to GraphQL requests.
49
+ *
50
+ * Headers can be provided statically or dynamically via a function.
51
+ * These headers are added before the AuthLink, so they will be preserved
52
+ * alongside the authorization header.
53
+ */
54
+ declare class HeadersLink extends ApolloLink {
55
+ private headers;
56
+ private getHeaders?;
57
+ constructor(options?: HeadersLinkOptions);
58
+ request(operation: ApolloLink.Operation, forward: ApolloLink.ForwardFunction): Observable<ApolloLink.Result>;
59
+ }
60
+
41
61
  declare const HttpLink: HttpLink$1;
42
62
 
43
63
  declare const RetryLink: RetryLink$1;
@@ -78,5 +98,5 @@ declare const TerminatingLink: ApolloLink;
78
98
 
79
99
  declare const createVersionLink: (platformInfo: string) => ApolloLink;
80
100
 
81
- export { AuthLink, BatchHttpLink, ErrorLink, ForwardableLink, HttpLink, QuilttClient, RetryLink, SubscriptionLink, TerminatingLink, createVersionLink };
82
- export type { QuilttClientOptions };
101
+ export { AuthLink, BatchHttpLink, ErrorLink, ForwardableLink, HeadersLink, HttpLink, QuilttClient, RetryLink, SubscriptionLink, TerminatingLink, createVersionLink };
102
+ export type { HeadersLinkOptions, QuilttClientOptions };
@@ -2,7 +2,7 @@ import { ApolloLink, ApolloClient } from '@apollo/client/core';
2
2
  export { gql } from '@apollo/client/core';
3
3
  import { endpointGraphQL, version, debugging } from '../../config/index.js';
4
4
  import { Observable } from 'rxjs';
5
- import { v as validateSessionToken, S as SubscriptionLink } from './SubscriptionLink-12s-ufJBKwu1.js';
5
+ import { v as validateSessionToken, S as SubscriptionLink } from './SubscriptionLink-12s-UDBUsy8w.js';
6
6
  import { BatchHttpLink as BatchHttpLink$1 } from '@apollo/client/link/batch-http';
7
7
  import crossfetch from 'cross-fetch';
8
8
  import { ErrorLink as ErrorLink$1 } from '@apollo/client/link/error';
@@ -83,6 +83,62 @@ const ErrorLink = new ErrorLink$1(({ error, result })=>{
83
83
 
84
84
  const ForwardableLink = new ApolloLink((operation, forward)=>forward(operation));
85
85
 
86
+ /**
87
+ * Apollo Link that adds custom headers to GraphQL requests.
88
+ *
89
+ * Headers can be provided statically or dynamically via a function.
90
+ * These headers are added before the AuthLink, so they will be preserved
91
+ * alongside the authorization header.
92
+ */ class HeadersLink extends ApolloLink {
93
+ constructor(options = {}){
94
+ super();
95
+ this.headers = options.headers ?? {};
96
+ this.getHeaders = options.getHeaders;
97
+ }
98
+ request(operation, forward) {
99
+ // If we have a dynamic headers function, handle it
100
+ if (this.getHeaders) {
101
+ return new Observable((observer)=>{
102
+ let innerSubscription;
103
+ let cancelled = false;
104
+ Promise.resolve(this.getHeaders()).then((dynamicHeaders)=>{
105
+ // Guard against late resolution after unsubscribe
106
+ if (cancelled) return;
107
+ operation.setContext(({ headers = {} })=>({
108
+ headers: {
109
+ ...headers,
110
+ ...this.headers,
111
+ ...dynamicHeaders
112
+ }
113
+ }));
114
+ innerSubscription = forward(operation).subscribe({
115
+ next: (value)=>!cancelled && observer.next(value),
116
+ error: (err)=>!cancelled && observer.error(err),
117
+ complete: ()=>!cancelled && observer.complete()
118
+ });
119
+ }).catch((error)=>{
120
+ if (!cancelled) {
121
+ observer.error(error);
122
+ }
123
+ });
124
+ // Return teardown logic
125
+ return ()=>{
126
+ cancelled = true;
127
+ innerSubscription?.unsubscribe();
128
+ };
129
+ });
130
+ }
131
+ // Static headers only
132
+ operation.setContext(({ headers = {} })=>({
133
+ headers: {
134
+ ...headers,
135
+ ...this.headers
136
+ }
137
+ }));
138
+ return forward(operation);
139
+ }
140
+ }
141
+
86
142
  // Use `cross-fetch` only if `fetch` is not available on the `globalThis` object
87
143
  const effectiveFetch = typeof fetch === 'undefined' ? crossfetch : fetch;
88
144
  const HttpLink = new HttpLink$1({
@@ -140,6 +196,7 @@ const createVersionLink = (platformInfo)=>{
140
196
  headers: {
141
197
  ...headers,
142
198
  'Quiltt-Client-Version': version,
199
+ 'Quiltt-SDK-Agent': userAgent,
143
200
  'User-Agent': userAgent
144
201
  }
145
202
  }));
@@ -181,4 +238,4 @@ class QuilttClient extends ApolloClient {
181
238
  }
182
239
  }
183
240
 
184
- export { AuthLink, BatchHttpLink, ErrorLink, ForwardableLink, HttpLink, QuilttClient, RetryLink, SubscriptionLink, TerminatingLink, createVersionLink };
241
+ export { AuthLink, BatchHttpLink, ErrorLink, ForwardableLink, HeadersLink, HttpLink, QuilttClient, RetryLink, SubscriptionLink, TerminatingLink, createVersionLink };
@@ -60,6 +60,7 @@ var AuthStrategies = /*#__PURE__*/ function(AuthStrategies) {
60
60
  }({});
61
61
  // https://www.quiltt.dev/api-reference/auth
62
62
  class AuthAPI {
63
+ // @todo userAgent: string | undefined - allow SDKs and callers to set a userAgent
63
64
  constructor(clientId){
64
65
  /**
65
66
  * Response Statuses:
@@ -207,6 +208,7 @@ class ConnectorsAPI {
207
208
  headers.set('Content-Type', 'application/json');
208
209
  headers.set('Accept', 'application/json');
209
210
  headers.set('User-Agent', this.userAgent);
211
+ headers.set('Quiltt-SDK-Agent', this.userAgent);
210
212
  headers.set('Authorization', `Bearer ${token}`);
211
213
  return {
212
214
  headers,
@@ -48,6 +48,7 @@ type Authenticate = SessionData | UnauthorizedData | UnprocessableData;
48
48
  type Revoke = NoContentData | UnauthorizedData;
49
49
  type SessionResponse = FetchResponse<SessionData>;
50
50
  declare class AuthAPI {
51
+ /** The Connector ID, required for identify & authenticate calls */
51
52
  clientId: string | undefined;
52
53
  constructor(clientId?: string | undefined);
53
54
  /**
@@ -54,6 +54,7 @@ var AuthStrategies = /*#__PURE__*/ function(AuthStrategies) {
54
54
  }({});
55
55
  // https://www.quiltt.dev/api-reference/auth
56
56
  class AuthAPI {
57
+ // @todo userAgent: string | undefined - allow SDKs and callers to set a userAgent
57
58
  constructor(clientId){
58
59
  /**
59
60
  * Response Statuses:
@@ -201,6 +202,7 @@ class ConnectorsAPI {
201
202
  headers.set('Content-Type', 'application/json');
202
203
  headers.set('Accept', 'application/json');
203
204
  headers.set('User-Agent', this.userAgent);
205
+ headers.set('Quiltt-SDK-Agent', this.userAgent);
204
206
  headers.set('Authorization', `Bearer ${token}`);
205
207
  return {
206
208
  headers,
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
3
  var name = "@quiltt/core";
4
- var version$1 = "5.0.2";
4
+ var version$1 = "5.1.0";
5
5
 
6
6
  const QUILTT_API_INSECURE = (()=>{
7
7
  try {
@@ -1,5 +1,5 @@
1
1
  var name = "@quiltt/core";
2
- var version$1 = "5.0.2";
2
+ var version$1 = "5.1.0";
3
3
 
4
4
  const QUILTT_API_INSECURE = (()=>{
5
5
  try {
package/dist/index.cjs CHANGED
@@ -7,7 +7,6 @@ var index_cjs$3 = require('./observables/index.cjs');
7
7
  var index_cjs$4 = require('./storage/index.cjs');
8
8
  var index_cjs$5 = require('./timing/index.cjs');
9
9
  var types_cjs = require('./types.cjs');
10
- var index_cjs$6 = require('./utils/index.cjs');
11
10
 
12
11
 
13
12
 
@@ -53,9 +52,3 @@ Object.keys(types_cjs).forEach(function (k) {
53
52
  get: function () { return types_cjs[k]; }
54
53
  });
55
54
  });
56
- Object.keys(index_cjs$6).forEach(function (k) {
57
- if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
58
- enumerable: true,
59
- get: function () { return index_cjs$6[k]; }
60
- });
61
- });
package/dist/index.d.ts CHANGED
@@ -5,4 +5,3 @@ export * from './observables/index.js';
5
5
  export * from './storage/index.js';
6
6
  export * from './timing/index.js';
7
7
  export * from './types.js';
8
- export * from './utils/index.js';
package/dist/index.js CHANGED
@@ -5,4 +5,3 @@ export * from './observables/index.js';
5
5
  export * from './storage/index.js';
6
6
  export * from './timing/index.js';
7
7
  export * from './types.js';
8
- export * from './utils/index.js';
package/dist/types.d.ts CHANGED
@@ -1,26 +1,34 @@
1
- /** Utility types to extend default TS utilities */
1
+ /** Represents a value that can be T or null */
2
2
  type Maybe<T> = T | null;
3
+ /** Input variant of Maybe for function parameters */
3
4
  type InputMaybe<T> = Maybe<T>;
5
+ /** Ensures all properties of T are exactly their declared types */
4
6
  type Exact<T extends {
5
7
  [key: string]: unknown;
6
8
  }> = {
7
9
  [K in keyof T]: T[K];
8
10
  };
11
+ /** Makes specific keys K optional and nullable in type T */
9
12
  type MakeOptional<T, K extends keyof T> = Omit<T, K> & {
10
13
  [SubKey in K]?: Maybe<T[SubKey]>;
11
14
  };
15
+ /** Makes specific keys K nullable (but still required) in type T */
12
16
  type MakeMaybe<T, K extends keyof T> = Omit<T, K> & {
13
17
  [SubKey in K]: Maybe<T[SubKey]>;
14
18
  };
19
+ /** Makes all properties of T nullable */
15
20
  type Nullable<T> = {
16
21
  [K in keyof T]: T[K] | null;
17
22
  };
23
+ /** Removes readonly modifier from all properties of Type */
18
24
  type Mutable<Type> = {
19
25
  -readonly [Key in keyof Type]: Type[Key];
20
26
  };
27
+ /** Recursively makes all properties of T optional */
21
28
  type DeepPartial<T> = T extends object ? {
22
29
  [P in keyof T]?: DeepPartial<T[P]>;
23
30
  } : T;
31
+ /** Recursively makes all properties of T readonly */
24
32
  type DeepReadonly<T> = T extends object ? {
25
33
  [P in keyof T]: DeepReadonly<T[P]>;
26
34
  } : T;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quiltt/core",
3
- "version": "5.0.2",
3
+ "version": "5.1.0",
4
4
  "description": "Javascript API client and utilities for Quiltt",
5
5
  "keywords": [
6
6
  "quiltt",
@@ -88,7 +88,7 @@
88
88
  ],
89
89
  "main": "dist/index.js",
90
90
  "dependencies": {
91
- "@apollo/client": "^4.1.3",
91
+ "@apollo/client": "^4.1.4",
92
92
  "@rails/actioncable": "^8.1.200",
93
93
  "braces": "^3.0.3",
94
94
  "cross-fetch": "^4.1.0",
@@ -96,12 +96,12 @@
96
96
  "rxjs": "^7.8.2"
97
97
  },
98
98
  "devDependencies": {
99
- "@biomejs/biome": "2.3.14",
100
- "@types/node": "24.10.10",
99
+ "@biomejs/biome": "2.4.0",
100
+ "@types/node": "24.10.13",
101
101
  "@types/rails__actioncable": "8.0.3",
102
- "@types/react": "19.2.11",
102
+ "@types/react": "19.2.14",
103
103
  "bunchee": "6.9.4",
104
- "rimraf": "6.1.2",
104
+ "rimraf": "6.1.3",
105
105
  "typescript": "5.9.3"
106
106
  },
107
107
  "tags": [
@@ -125,6 +125,8 @@ export type ConnectorSDKCallbackMetadata = {
125
125
  export type ConnectorSDKConnectOptions = ConnectorSDKCallbacks & {
126
126
  /** The Institution ID or search term to connect */
127
127
  institution?: string
128
+ /** The OAuth redirect URL for mobile or embedded webview flows */
129
+ oauthRedirectUrl?: string
128
130
  }
129
131
 
130
132
  /**
@@ -134,6 +136,8 @@ export type ConnectorSDKConnectOptions = ConnectorSDKCallbacks & {
134
136
  export type ConnectorSDKReconnectOptions = ConnectorSDKCallbacks & {
135
137
  /** The ID of the Connection to reconnect */
136
138
  connectionId: string
139
+ /** The OAuth redirect URL for mobile or embedded webview flows */
140
+ oauthRedirectUrl?: string
137
141
  }
138
142
 
139
143
  /** Options to initialize Connector
@@ -148,4 +152,6 @@ export type ConnectorSDKConnectorOptions = ConnectorSDKCallbacks & {
148
152
  connectionId?: string
149
153
  /** The nonce to use for the script tag */
150
154
  nonce?: string
155
+ /** The OAuth redirect URL for mobile or embedded webview flows */
156
+ oauthRedirectUrl?: string
151
157
  }
@@ -39,7 +39,7 @@ class ActionCableLink extends ApolloLink {
39
39
 
40
40
  // Interestingly, this link does _not_ call through to `next` because
41
41
  // instead, it sends the request to ActionCable.
42
- request(
42
+ override request(
43
43
  operation: ApolloLink.Operation,
44
44
  _next: ApolloLink.ForwardFunction
45
45
  ): Observable<RequestResult> {
@@ -58,6 +58,7 @@ class ActionCableLink extends ApolloLink {
58
58
  }
59
59
 
60
60
  return new Observable((observer) => {
61
+ let cancelled = false
61
62
  const channelId = Math.round(Date.now() + Math.random() * 100000).toString(16)
62
63
  const actionName = this.actionName
63
64
  const connectionParams =
@@ -77,6 +78,7 @@ class ActionCableLink extends ApolloLink {
77
78
  ),
78
79
  {
79
80
  connected: (args?: { reconnected: boolean }) => {
81
+ if (cancelled) return
80
82
  channel.perform(actionName, {
81
83
  query: operation.query ? print(operation.query) : null,
82
84
  variables: operation.variables,
@@ -88,6 +90,8 @@ class ActionCableLink extends ApolloLink {
88
90
  },
89
91
 
90
92
  received: (payload: { result: RequestResult; more: any }) => {
93
+ if (cancelled) return
94
+
91
95
  if (payload?.result?.data || payload?.result?.errors) {
92
96
  observer.next(payload.result)
93
97
  }
@@ -98,13 +102,20 @@ class ActionCableLink extends ApolloLink {
98
102
 
99
103
  callbacks.received?.(payload)
100
104
  },
105
+ // Intentionally not guarded by `cancelled` - disconnected represents
106
+ // the WebSocket connection state which users may want to know about
107
+ // for cleanup/status purposes, even after the observable completes.
101
108
  disconnected: () => {
102
109
  callbacks.disconnected?.()
103
110
  },
104
111
  }
105
112
  )
106
- // Make the ActionCable subscription behave like an Apollo subscription
107
- return Object.assign(channel, { closed: false })
113
+
114
+ // Return teardown logic
115
+ return () => {
116
+ cancelled = true
117
+ channel.unsubscribe()
118
+ }
108
119
  })
109
120
  }
110
121
  }
@@ -13,7 +13,7 @@ import { validateSessionToken } from '@/utils/token-validation'
13
13
  * - Emits GraphQL errors for consistent Apollo error handling
14
14
  */
15
15
  export class AuthLink extends ApolloLink {
16
- request(
16
+ override request(
17
17
  operation: ApolloLink.Operation,
18
18
  forward: ApolloLink.ForwardFunction
19
19
  ): Observable<ApolloLink.Result> {
@@ -0,0 +1,84 @@
1
+ import { ApolloLink } from '@apollo/client/core'
2
+ import type { Subscription } from 'rxjs'
3
+ import { Observable } from 'rxjs'
4
+
5
+ export type HeadersLinkOptions = {
6
+ /** Static headers to add to every request */
7
+ headers?: Record<string, string>
8
+ /** Dynamic headers function called on each request */
9
+ getHeaders?: () => Record<string, string> | Promise<Record<string, string>>
10
+ }
11
+
12
+ /**
13
+ * Apollo Link that adds custom headers to GraphQL requests.
14
+ *
15
+ * Headers can be provided statically or dynamically via a function.
16
+ * These headers are added before the AuthLink, so they will be preserved
17
+ * alongside the authorization header.
18
+ */
19
+ export class HeadersLink extends ApolloLink {
20
+ private headers: Record<string, string>
21
+ private getHeaders?: () => Record<string, string> | Promise<Record<string, string>>
22
+
23
+ constructor(options: HeadersLinkOptions = {}) {
24
+ super()
25
+ this.headers = options.headers ?? {}
26
+ this.getHeaders = options.getHeaders
27
+ }
28
+
29
+ override request(
30
+ operation: ApolloLink.Operation,
31
+ forward: ApolloLink.ForwardFunction
32
+ ): Observable<ApolloLink.Result> {
33
+ // If we have a dynamic headers function, handle it
34
+ if (this.getHeaders) {
35
+ return new Observable((observer) => {
36
+ let innerSubscription: Subscription | undefined
37
+ let cancelled = false
38
+
39
+ Promise.resolve(this.getHeaders!())
40
+ .then((dynamicHeaders) => {
41
+ // Guard against late resolution after unsubscribe
42
+ if (cancelled) return
43
+
44
+ operation.setContext(({ headers = {} }) => ({
45
+ headers: {
46
+ ...headers,
47
+ ...this.headers,
48
+ ...dynamicHeaders,
49
+ },
50
+ }))
51
+
52
+ innerSubscription = forward(operation).subscribe({
53
+ next: (value) => !cancelled && observer.next(value),
54
+ error: (err) => !cancelled && observer.error(err),
55
+ complete: () => !cancelled && observer.complete(),
56
+ })
57
+ })
58
+ .catch((error) => {
59
+ if (!cancelled) {
60
+ observer.error(error)
61
+ }
62
+ })
63
+
64
+ // Return teardown logic
65
+ return () => {
66
+ cancelled = true
67
+ innerSubscription?.unsubscribe()
68
+ }
69
+ })
70
+ }
71
+
72
+ // Static headers only
73
+ operation.setContext(({ headers = {} }) => ({
74
+ headers: {
75
+ ...headers,
76
+ ...this.headers,
77
+ },
78
+ }))
79
+
80
+ return forward(operation)
81
+ }
82
+ }
83
+
84
+ export default HeadersLink
@@ -12,6 +12,7 @@ export const createVersionLink = (platformInfo: string) => {
12
12
  headers: {
13
13
  ...headers,
14
14
  'Quiltt-Client-Version': version,
15
+ 'Quiltt-SDK-Agent': userAgent,
15
16
  'User-Agent': userAgent,
16
17
  },
17
18
  }))
@@ -2,6 +2,7 @@ export * from './AuthLink'
2
2
  export * from './BatchHttpLink'
3
3
  export * from './ErrorLink'
4
4
  export * from './ForwardableLink'
5
+ export * from './HeadersLink'
5
6
  export * from './HttpLink'
6
7
  export * from './RetryLink'
7
8
  export * from './SubscriptionLink'
@@ -33,8 +33,11 @@ export type SessionResponse = FetchResponse<SessionData>
33
33
 
34
34
  // https://www.quiltt.dev/api-reference/auth
35
35
  export class AuthAPI {
36
+ /** The Connector ID, required for identify & authenticate calls */
36
37
  clientId: string | undefined
37
38
 
39
+ // @todo userAgent: string | undefined - allow SDKs and callers to set a userAgent
40
+
38
41
  constructor(clientId?: string | undefined) {
39
42
  this.clientId = clientId
40
43
  }
@@ -92,6 +92,7 @@ export class ConnectorsAPI {
92
92
  headers.set('Content-Type', 'application/json')
93
93
  headers.set('Accept', 'application/json')
94
94
  headers.set('User-Agent', this.userAgent)
95
+ headers.set('Quiltt-SDK-Agent', this.userAgent)
95
96
  headers.set('Authorization', `Bearer ${token}`)
96
97
 
97
98
  return {
package/src/index.ts CHANGED
@@ -1,3 +1,23 @@
1
+ // ============================================================================
2
+ // @quiltt/core - Core Quiltt SDK functionality
3
+ // ============================================================================
4
+ // This package provides the foundational TypeScript primitives for interacting
5
+ // with the Quiltt API. It works across all JavaScript environments (browser,
6
+ // Node.js, React Native) and contains no UI-specific dependencies.
7
+ //
8
+ // Main exports:
9
+ // - API clients (AuthAPI, ConnectorsAPI, QuilttClient)
10
+ // - Authentication utilities (JsonWebTokenParse, AuthStrategies)
11
+ // - Configuration helpers
12
+ // - Observable patterns for reactive data
13
+ // - Storage abstractions (GlobalStorage)
14
+ // - Timeout utilities (Timeoutable)
15
+ // - TypeScript utility types
16
+ //
17
+ // Note: Utils are NOT exported from the main entry point to keep the public
18
+ // API clean. Access utils via subpath import: '@quiltt/core/utils'
19
+ // ============================================================================
20
+
1
21
  export * from './api'
2
22
  export * from './auth'
3
23
  export * from './config'
@@ -5,4 +25,3 @@ export * from './observables'
5
25
  export * from './storage'
6
26
  export * from './timing'
7
27
  export * from './types'
8
- export * from './utils'
package/src/types.ts CHANGED
@@ -1,10 +1,33 @@
1
- /** Utility types to extend default TS utilities */
1
+ // ============================================================================
2
+ // TypeScript Utility Types
3
+ // ============================================================================
4
+ // Extended utility types to complement TypeScript's built-in utilities.
5
+ // These are commonly used throughout the Quiltt SDK for type safety.
6
+ // ============================================================================
7
+
8
+ /** Represents a value that can be T or null */
2
9
  export type Maybe<T> = T | null
10
+
11
+ /** Input variant of Maybe for function parameters */
3
12
  export type InputMaybe<T> = Maybe<T>
13
+
14
+ /** Ensures all properties of T are exactly their declared types */
4
15
  export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] }
16
+
17
+ /** Makes specific keys K optional and nullable in type T */
5
18
  export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> }
19
+
20
+ /** Makes specific keys K nullable (but still required) in type T */
6
21
  export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> }
22
+
23
+ /** Makes all properties of T nullable */
7
24
  export type Nullable<T> = { [K in keyof T]: T[K] | null }
25
+
26
+ /** Removes readonly modifier from all properties of Type */
8
27
  export type Mutable<Type> = { -readonly [Key in keyof Type]: Type[Key] }
28
+
29
+ /** Recursively makes all properties of T optional */
9
30
  export type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T
31
+
32
+ /** Recursively makes all properties of T readonly */
10
33
  export type DeepReadonly<T> = T extends object ? { [P in keyof T]: DeepReadonly<T[P]> } : T