@robosystems/client 0.3.0 → 0.3.1

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.
@@ -1,4 +1,5 @@
1
1
  import type { CreatePortfolioRequest, CreatePositionRequest, CreateSecurityRequest, UpdatePortfolioOperation, UpdatePositionOperation, UpdateSecurityOperation } from '../types.gen';
2
+ import type { TokenProvider } from './graphql/client';
2
3
  import { type GetInvestorHoldingsQuery, type GetInvestorPortfolioQuery, type GetInvestorPositionQuery, type GetInvestorSecurityQuery, type ListInvestorPortfoliosQuery, type ListInvestorPositionsQuery, type ListInvestorSecuritiesQuery } from './graphql/generated/graphql';
3
4
  export type InvestorPortfolioList = NonNullable<ListInvestorPortfoliosQuery['portfolios']>;
4
5
  export type InvestorPortfolioSummary = InvestorPortfolioList['portfolios'][number];
@@ -16,7 +17,13 @@ interface InvestorClientConfig {
16
17
  baseUrl: string;
17
18
  credentials?: 'include' | 'same-origin' | 'omit';
18
19
  headers?: Record<string, string>;
20
+ /** Static credential — use `tokenProvider` instead if the JWT rotates. */
19
21
  token?: string;
22
+ /**
23
+ * Dynamic credential callback. When set, invoked on every GraphQL
24
+ * request so refreshes flow through automatically.
25
+ */
26
+ tokenProvider?: TokenProvider;
20
27
  }
21
28
  export declare class InvestorClient {
22
29
  private config;
@@ -38,6 +38,7 @@ import type {
38
38
  UpdatePositionOperation,
39
39
  UpdateSecurityOperation,
40
40
  } from '../types.gen'
41
+ import type { TokenProvider } from './graphql/client'
41
42
  import { GraphQLClientCache } from './graphql/client'
42
43
  import {
43
44
  GetInvestorHoldingsDocument,
@@ -80,7 +81,13 @@ interface InvestorClientConfig {
80
81
  baseUrl: string
81
82
  credentials?: 'include' | 'same-origin' | 'omit'
82
83
  headers?: Record<string, string>
84
+ /** Static credential — use `tokenProvider` instead if the JWT rotates. */
83
85
  token?: string
86
+ /**
87
+ * Dynamic credential callback. When set, invoked on every GraphQL
88
+ * request so refreshes flow through automatically.
89
+ */
90
+ tokenProvider?: TokenProvider
84
91
  }
85
92
 
86
93
  export class InvestorClient {
@@ -1,4 +1,5 @@
1
1
  import type { AutoMapElementsOperation, CreateMappingAssociationOperation, CreateStructureRequest, CreateTaxonomyRequest, DeleteMappingAssociationOperation, OperationEnvelope, UpdateEntityRequest } from '../types.gen';
2
+ import type { TokenProvider } from './graphql/client';
2
3
  import { type GetLedgerAccountRollupsQuery, type GetLedgerAccountTreeQuery, type GetLedgerClosingBookStructuresQuery, type GetLedgerEntityQuery, type GetLedgerFiscalCalendarQuery, type GetLedgerMappedTrialBalanceQuery, type GetLedgerMappingCoverageQuery, type GetLedgerMappingQuery, type GetLedgerPeriodCloseStatusQuery, type GetLedgerPeriodDraftsQuery, type GetLedgerReportingTaxonomyQuery, type GetLedgerScheduleFactsQuery, type GetLedgerSummaryQuery, type GetLedgerTransactionQuery, type GetLedgerTrialBalanceQuery, type ListLedgerAccountsQuery, type ListLedgerElementsQuery, type ListLedgerEntitiesQuery, type ListLedgerMappingsQuery, type ListLedgerSchedulesQuery, type ListLedgerStructuresQuery, type ListLedgerTaxonomiesQuery, type ListLedgerTransactionsQuery, type ListLedgerUnmappedElementsQuery } from './graphql/generated/graphql';
3
4
  export type LedgerEntity = NonNullable<GetLedgerEntityQuery['entity']>;
4
5
  export type LedgerEntitySummary = ListLedgerEntitiesQuery['entities'][number];
@@ -125,7 +126,13 @@ interface LedgerClientConfig {
125
126
  baseUrl: string;
126
127
  credentials?: 'include' | 'same-origin' | 'omit';
127
128
  headers?: Record<string, string>;
129
+ /** Static credential — use `tokenProvider` instead if the JWT rotates. */
128
130
  token?: string;
131
+ /**
132
+ * Dynamic credential callback. When set, invoked on every GraphQL
133
+ * request so refreshes flow through automatically.
134
+ */
135
+ tokenProvider?: TokenProvider;
129
136
  }
130
137
  export declare class LedgerClient {
131
138
  private config;
@@ -57,6 +57,7 @@ import type {
57
57
  TruncateScheduleOperation,
58
58
  UpdateEntityRequest,
59
59
  } from '../types.gen'
60
+ import type { TokenProvider } from './graphql/client'
60
61
  import { GraphQLClientCache } from './graphql/client'
61
62
  import {
62
63
  GetLedgerAccountRollupsDocument,
@@ -347,7 +348,13 @@ interface LedgerClientConfig {
347
348
  baseUrl: string
348
349
  credentials?: 'include' | 'same-origin' | 'omit'
349
350
  headers?: Record<string, string>
351
+ /** Static credential — use `tokenProvider` instead if the JWT rotates. */
350
352
  token?: string
353
+ /**
354
+ * Dynamic credential callback. When set, invoked on every GraphQL
355
+ * request so refreshes flow through automatically.
356
+ */
357
+ tokenProvider?: TokenProvider
351
358
  }
352
359
 
353
360
  export class LedgerClient {
@@ -1,4 +1,5 @@
1
1
  import type { OperationEnvelope } from '../types.gen';
2
+ import type { TokenProvider } from './graphql/client';
2
3
  import { type GetLedgerPublishListQuery, type GetLedgerReportQuery, type GetLedgerStatementQuery, type ListLedgerPublishListsQuery, type ListLedgerReportsQuery } from './graphql/generated/graphql';
3
4
  export type Report = NonNullable<GetLedgerReportQuery['report']>;
4
5
  export type ReportListItem = NonNullable<ListLedgerReportsQuery['reports']>['reports'][number];
@@ -50,7 +51,13 @@ interface ReportClientConfig {
50
51
  baseUrl: string;
51
52
  credentials?: 'include' | 'same-origin' | 'omit';
52
53
  headers?: Record<string, string>;
54
+ /** Static credential — use `tokenProvider` instead if the JWT rotates. */
53
55
  token?: string;
56
+ /**
57
+ * Dynamic credential callback. When set, invoked on every GraphQL
58
+ * request so refreshes flow through automatically.
59
+ */
60
+ tokenProvider?: TokenProvider;
54
61
  }
55
62
  export declare class ReportClient {
56
63
  private config;
@@ -42,6 +42,7 @@ import type {
42
42
  OperationEnvelope,
43
43
  UpdatePublishListOperation,
44
44
  } from '../types.gen'
45
+ import type { TokenProvider } from './graphql/client'
45
46
  import { GraphQLClientCache } from './graphql/client'
46
47
  import {
47
48
  GetLedgerPublishListDocument,
@@ -119,7 +120,13 @@ interface ReportClientConfig {
119
120
  baseUrl: string
120
121
  credentials?: 'include' | 'same-origin' | 'omit'
121
122
  headers?: Record<string, string>
123
+ /** Static credential — use `tokenProvider` instead if the JWT rotates. */
122
124
  token?: string
125
+ /**
126
+ * Dynamic credential callback. When set, invoked on every GraphQL
127
+ * request so refreshes flow through automatically.
128
+ */
129
+ tokenProvider?: TokenProvider
123
130
  }
124
131
 
125
132
  export class ReportClient {
@@ -2,11 +2,30 @@
2
2
  * Configuration for SDK extensions
3
3
  * Provides centralized configuration for CORS, credentials, and other settings
4
4
  */
5
+ import type { TokenProvider } from './graphql/client';
5
6
  export interface SDKExtensionsConfig {
6
7
  baseUrl?: string;
7
8
  credentials?: 'include' | 'same-origin' | 'omit';
8
9
  headers?: Record<string, string>;
10
+ /**
11
+ * Static credential captured at singleton construction. Fine for
12
+ * long-lived API keys — use `tokenProvider` instead when the
13
+ * JWT can rotate mid-session (browser login flows).
14
+ *
15
+ * When both `token` and `tokenProvider` are set, `tokenProvider`
16
+ * wins — the static value is effectively ignored. Pass
17
+ * `tokenProvider: undefined` to explicitly switch back to the
18
+ * static-token path.
19
+ */
9
20
  token?: string;
21
+ /**
22
+ * Dynamic credential callback, invoked on every GraphQL request.
23
+ * When set, JWT refresh flows through automatically: the lazy
24
+ * `extensions` singleton picks it up at construction, and each
25
+ * request consults the callback for the current token instead of
26
+ * reusing a stale captured value.
27
+ */
28
+ tokenProvider?: TokenProvider;
10
29
  timeout?: number;
11
30
  maxRetries?: number;
12
31
  retryDelay?: number;
@@ -5,11 +5,31 @@
5
5
  * Provides centralized configuration for CORS, credentials, and other settings
6
6
  */
7
7
 
8
+ import type { TokenProvider } from './graphql/client'
9
+
8
10
  export interface SDKExtensionsConfig {
9
11
  baseUrl?: string
10
12
  credentials?: 'include' | 'same-origin' | 'omit'
11
13
  headers?: Record<string, string>
12
- token?: string // JWT token for authentication
14
+ /**
15
+ * Static credential captured at singleton construction. Fine for
16
+ * long-lived API keys — use `tokenProvider` instead when the
17
+ * JWT can rotate mid-session (browser login flows).
18
+ *
19
+ * When both `token` and `tokenProvider` are set, `tokenProvider`
20
+ * wins — the static value is effectively ignored. Pass
21
+ * `tokenProvider: undefined` to explicitly switch back to the
22
+ * static-token path.
23
+ */
24
+ token?: string
25
+ /**
26
+ * Dynamic credential callback, invoked on every GraphQL request.
27
+ * When set, JWT refresh flows through automatically: the lazy
28
+ * `extensions` singleton picks it up at construction, and each
29
+ * request consults the callback for the current token instead of
30
+ * reusing a stale captured value.
31
+ */
32
+ tokenProvider?: TokenProvider
13
33
  timeout?: number
14
34
  maxRetries?: number
15
35
  retryDelay?: number
@@ -19,9 +19,36 @@
19
19
  * from the query files in sdk-extensions/graphql/queries/.
20
20
  */
21
21
  import { GraphQLClient } from 'graphql-request';
22
+ /**
23
+ * Callback that returns the current auth credential on demand.
24
+ *
25
+ * Use this instead of the static `token` field when the credential
26
+ * can rotate during the lifetime of the client — the primary case
27
+ * is short-lived JWTs that auto-refresh from browser token storage.
28
+ * The provider is invoked on **every** GraphQL request, so the
29
+ * returned value should be cheap to obtain (a localStorage read or
30
+ * an in-memory lookup, not a network call if avoidable).
31
+ *
32
+ * May return `null`/`undefined` to indicate "no credential available
33
+ * right now" — the request will be sent unauthenticated in that
34
+ * case, which lets the caller distinguish auth-expired errors from
35
+ * never-authed errors at the transport layer.
36
+ */
37
+ export type TokenProvider = () => string | null | undefined | Promise<string | null | undefined>;
22
38
  export interface GraphQLClientConfig {
23
39
  baseUrl: string;
40
+ /**
41
+ * Static credential captured at construction time. Use this when
42
+ * the token won't rotate (e.g. CLI/server flows using a long-lived
43
+ * API key). For JWT flows prefer `tokenProvider` so refreshes are
44
+ * picked up without rebuilding the client.
45
+ */
24
46
  token?: string;
47
+ /**
48
+ * Dynamic credential callback, read on every request. Wins over
49
+ * `token` when both are set. See `TokenProvider` for semantics.
50
+ */
51
+ tokenProvider?: TokenProvider;
25
52
  headers?: Record<string, string>;
26
53
  credentials?: 'include' | 'same-origin' | 'omit';
27
54
  }
@@ -41,6 +68,15 @@ export declare class GraphQLClientCache {
41
68
  private clients;
42
69
  constructor(config: GraphQLClientConfig);
43
70
  get(graphId: string): GraphQLClient;
44
- /** Drop all cached clients. Useful after a token rotation. */
71
+ /**
72
+ * Drop all cached clients. Only needed when swapping **static**
73
+ * credentials — e.g. replacing the `token` field on a long-lived
74
+ * facade between tenants, or resetting a CLI session. When the
75
+ * cache was built from a `tokenProvider`, rotation is handled
76
+ * per-request inside `requestMiddleware` and `clear()` is a no-op
77
+ * for auth purposes (the cached `GraphQLClient` instances keep
78
+ * using the same provider reference and will pick up the next
79
+ * token automatically).
80
+ */
45
81
  clear(): void;
46
82
  }
@@ -24,6 +24,26 @@ exports.createGraphQLClient = createGraphQLClient;
24
24
  * from the query files in sdk-extensions/graphql/queries/.
25
25
  */
26
26
  const graphql_request_1 = require("graphql-request");
27
+ /**
28
+ * Apply a credential to an in-progress request's headers, choosing
29
+ * the right header based on token shape:
30
+ *
31
+ * - `rfs…` prefix → `X-API-Key` (long-lived API key, validated
32
+ * against the database's api_keys table).
33
+ * - anything else → `Authorization: Bearer …` (short-lived JWT,
34
+ * validated by JWT middleware).
35
+ *
36
+ * The two credential types are NOT interchangeable at the backend —
37
+ * sending a JWT as `X-API-Key` or an API key as Bearer both 401.
38
+ */
39
+ function applyAuthHeader(headers, token) {
40
+ if (token.startsWith('rfs')) {
41
+ headers.set('X-API-Key', token);
42
+ }
43
+ else {
44
+ headers.set('Authorization', `Bearer ${token}`);
45
+ }
46
+ }
27
47
  /**
28
48
  * Build a new GraphQL client for the given graph. Prefer
29
49
  * `GraphQLClientCache.get(graphId)` in facade code — this is the raw
@@ -34,17 +54,62 @@ function createGraphQLClient(config, graphId) {
34
54
  throw new Error('createGraphQLClient requires a non-empty graphId');
35
55
  }
36
56
  const url = `${config.baseUrl.replace(/\/$/, '')}/extensions/${graphId}/graphql`;
37
- const headers = {
57
+ const staticHeaders = {
38
58
  ...(config.headers ?? {}),
39
59
  };
60
+ // Dynamic-token path: defer credential injection to a per-request
61
+ // middleware so JWT refreshes are picked up without rebuilding or
62
+ // clearing the client. This is the recommended path for browser
63
+ // flows where the token in localStorage rotates every ~30 minutes.
64
+ if (config.tokenProvider) {
65
+ const providerFn = config.tokenProvider;
66
+ return new graphql_request_1.GraphQLClient(url, {
67
+ headers: staticHeaders,
68
+ credentials: config.credentials,
69
+ requestMiddleware: async (request) => {
70
+ let token;
71
+ try {
72
+ token = await providerFn();
73
+ }
74
+ catch (err) {
75
+ // A provider failure shouldn't crash the request — fall
76
+ // through unauthenticated so the backend returns a clean
77
+ // 401, which is easier to diagnose than a thrown middleware.
78
+ // We still log a breadcrumb so the failure is visible in
79
+ // devtools/log aggregators instead of silently disappearing;
80
+ // silently swallowing provider bugs in production is worse
81
+ // than the noise.
82
+ // eslint-disable-next-line no-console
83
+ console.warn('[RoboSystems SDK] tokenProvider threw — sending unauthenticated request:', err);
84
+ token = undefined;
85
+ }
86
+ if (!token) {
87
+ return request;
88
+ }
89
+ // `request.headers` is a HeadersInit (Headers | string[][] |
90
+ // Record<string, string>), so normalize via Headers before
91
+ // mutating. Spreading it directly loses keys when it's a
92
+ // Headers instance.
93
+ const merged = new Headers(request.headers);
94
+ applyAuthHeader(merged, token);
95
+ return { ...request, headers: merged };
96
+ },
97
+ });
98
+ }
99
+ // Static-token path: pick the right header at construction time.
100
+ // Suitable for long-lived API keys that never rotate.
40
101
  if (config.token) {
41
- // The backend accepts both Authorization: Bearer and X-API-Key. Match
42
- // the existing facade convention of sending the token as X-API-Key,
43
- // which is what the local dev workflow uses.
44
- headers['X-API-Key'] = config.token;
102
+ const headers = new Headers(staticHeaders);
103
+ applyAuthHeader(headers, config.token);
104
+ return new graphql_request_1.GraphQLClient(url, {
105
+ headers,
106
+ credentials: config.credentials,
107
+ });
45
108
  }
109
+ // No credentials at all — used by unauthenticated introspection
110
+ // queries against public dev endpoints.
46
111
  return new graphql_request_1.GraphQLClient(url, {
47
- headers,
112
+ headers: staticHeaders,
48
113
  credentials: config.credentials,
49
114
  });
50
115
  }
@@ -67,7 +132,16 @@ class GraphQLClientCache {
67
132
  this.clients.set(graphId, client);
68
133
  return client;
69
134
  }
70
- /** Drop all cached clients. Useful after a token rotation. */
135
+ /**
136
+ * Drop all cached clients. Only needed when swapping **static**
137
+ * credentials — e.g. replacing the `token` field on a long-lived
138
+ * facade between tenants, or resetting a CLI session. When the
139
+ * cache was built from a `tokenProvider`, rotation is handled
140
+ * per-request inside `requestMiddleware` and `clear()` is a no-op
141
+ * for auth purposes (the cached `GraphQLClient` instances keep
142
+ * using the same provider reference and will pick up the next
143
+ * token automatically).
144
+ */
71
145
  clear() {
72
146
  this.clients.clear();
73
147
  }
@@ -23,13 +23,61 @@
23
23
 
24
24
  import { GraphQLClient } from 'graphql-request'
25
25
 
26
+ /**
27
+ * Callback that returns the current auth credential on demand.
28
+ *
29
+ * Use this instead of the static `token` field when the credential
30
+ * can rotate during the lifetime of the client — the primary case
31
+ * is short-lived JWTs that auto-refresh from browser token storage.
32
+ * The provider is invoked on **every** GraphQL request, so the
33
+ * returned value should be cheap to obtain (a localStorage read or
34
+ * an in-memory lookup, not a network call if avoidable).
35
+ *
36
+ * May return `null`/`undefined` to indicate "no credential available
37
+ * right now" — the request will be sent unauthenticated in that
38
+ * case, which lets the caller distinguish auth-expired errors from
39
+ * never-authed errors at the transport layer.
40
+ */
41
+ export type TokenProvider = () => string | null | undefined | Promise<string | null | undefined>
42
+
26
43
  export interface GraphQLClientConfig {
27
44
  baseUrl: string
45
+ /**
46
+ * Static credential captured at construction time. Use this when
47
+ * the token won't rotate (e.g. CLI/server flows using a long-lived
48
+ * API key). For JWT flows prefer `tokenProvider` so refreshes are
49
+ * picked up without rebuilding the client.
50
+ */
28
51
  token?: string
52
+ /**
53
+ * Dynamic credential callback, read on every request. Wins over
54
+ * `token` when both are set. See `TokenProvider` for semantics.
55
+ */
56
+ tokenProvider?: TokenProvider
29
57
  headers?: Record<string, string>
30
58
  credentials?: 'include' | 'same-origin' | 'omit'
31
59
  }
32
60
 
61
+ /**
62
+ * Apply a credential to an in-progress request's headers, choosing
63
+ * the right header based on token shape:
64
+ *
65
+ * - `rfs…` prefix → `X-API-Key` (long-lived API key, validated
66
+ * against the database's api_keys table).
67
+ * - anything else → `Authorization: Bearer …` (short-lived JWT,
68
+ * validated by JWT middleware).
69
+ *
70
+ * The two credential types are NOT interchangeable at the backend —
71
+ * sending a JWT as `X-API-Key` or an API key as Bearer both 401.
72
+ */
73
+ function applyAuthHeader(headers: Headers, token: string): void {
74
+ if (token.startsWith('rfs')) {
75
+ headers.set('X-API-Key', token)
76
+ } else {
77
+ headers.set('Authorization', `Bearer ${token}`)
78
+ }
79
+ }
80
+
33
81
  /**
34
82
  * Build a new GraphQL client for the given graph. Prefer
35
83
  * `GraphQLClientCache.get(graphId)` in facade code — this is the raw
@@ -40,17 +88,67 @@ export function createGraphQLClient(config: GraphQLClientConfig, graphId: string
40
88
  throw new Error('createGraphQLClient requires a non-empty graphId')
41
89
  }
42
90
  const url = `${config.baseUrl.replace(/\/$/, '')}/extensions/${graphId}/graphql`
43
- const headers: Record<string, string> = {
91
+ const staticHeaders: Record<string, string> = {
44
92
  ...(config.headers ?? {}),
45
93
  }
94
+
95
+ // Dynamic-token path: defer credential injection to a per-request
96
+ // middleware so JWT refreshes are picked up without rebuilding or
97
+ // clearing the client. This is the recommended path for browser
98
+ // flows where the token in localStorage rotates every ~30 minutes.
99
+ if (config.tokenProvider) {
100
+ const providerFn = config.tokenProvider
101
+ return new GraphQLClient(url, {
102
+ headers: staticHeaders,
103
+ credentials: config.credentials,
104
+ requestMiddleware: async (request) => {
105
+ let token: string | null | undefined
106
+ try {
107
+ token = await providerFn()
108
+ } catch (err) {
109
+ // A provider failure shouldn't crash the request — fall
110
+ // through unauthenticated so the backend returns a clean
111
+ // 401, which is easier to diagnose than a thrown middleware.
112
+ // We still log a breadcrumb so the failure is visible in
113
+ // devtools/log aggregators instead of silently disappearing;
114
+ // silently swallowing provider bugs in production is worse
115
+ // than the noise.
116
+ // eslint-disable-next-line no-console
117
+ console.warn(
118
+ '[RoboSystems SDK] tokenProvider threw — sending unauthenticated request:',
119
+ err
120
+ )
121
+ token = undefined
122
+ }
123
+ if (!token) {
124
+ return request
125
+ }
126
+ // `request.headers` is a HeadersInit (Headers | string[][] |
127
+ // Record<string, string>), so normalize via Headers before
128
+ // mutating. Spreading it directly loses keys when it's a
129
+ // Headers instance.
130
+ const merged = new Headers(request.headers as HeadersInit | undefined)
131
+ applyAuthHeader(merged, token)
132
+ return { ...request, headers: merged }
133
+ },
134
+ })
135
+ }
136
+
137
+ // Static-token path: pick the right header at construction time.
138
+ // Suitable for long-lived API keys that never rotate.
46
139
  if (config.token) {
47
- // The backend accepts both Authorization: Bearer and X-API-Key. Match
48
- // the existing facade convention of sending the token as X-API-Key,
49
- // which is what the local dev workflow uses.
50
- headers['X-API-Key'] = config.token
140
+ const headers = new Headers(staticHeaders)
141
+ applyAuthHeader(headers, config.token)
142
+ return new GraphQLClient(url, {
143
+ headers,
144
+ credentials: config.credentials,
145
+ })
51
146
  }
147
+
148
+ // No credentials at all — used by unauthenticated introspection
149
+ // queries against public dev endpoints.
52
150
  return new GraphQLClient(url, {
53
- headers,
151
+ headers: staticHeaders,
54
152
  credentials: config.credentials,
55
153
  })
56
154
  }
@@ -75,7 +173,16 @@ export class GraphQLClientCache {
75
173
  return client
76
174
  }
77
175
 
78
- /** Drop all cached clients. Useful after a token rotation. */
176
+ /**
177
+ * Drop all cached clients. Only needed when swapping **static**
178
+ * credentials — e.g. replacing the `token` field on a long-lived
179
+ * facade between tenants, or resetting a CLI session. When the
180
+ * cache was built from a `tokenProvider`, rotation is handled
181
+ * per-request inside `requestMiddleware` and `clear()` is a no-op
182
+ * for auth purposes (the cached `GraphQLClient` instances keep
183
+ * using the same provider reference and will pick up the next
184
+ * token automatically).
185
+ */
79
186
  clear(): void {
80
187
  this.clients.clear()
81
188
  }
@@ -2,6 +2,7 @@
2
2
  * RoboSystems SDK Extensions
3
3
  * Enhanced clients with SSE support for the RoboSystems API
4
4
  */
5
+ import type { TokenProvider } from './graphql/client';
5
6
  import { OperationClient } from './OperationClient';
6
7
  import { QueryClient } from './QueryClient';
7
8
  import { AgentClient } from './AgentClient';
@@ -9,10 +10,22 @@ import { SSEClient } from './SSEClient';
9
10
  import { InvestorClient } from './InvestorClient';
10
11
  import { LedgerClient } from './LedgerClient';
11
12
  import { ReportClient } from './ReportClient';
13
+ export type { TokenProvider } from './graphql/client';
12
14
  export interface RoboSystemsExtensionConfig {
13
15
  baseUrl?: string;
14
16
  credentials?: 'include' | 'same-origin' | 'omit';
17
+ /**
18
+ * Static credential captured at construction time. Fine for
19
+ * long-lived API keys; use `tokenProvider` instead when the JWT
20
+ * can rotate (browser login flows).
21
+ */
15
22
  token?: string;
23
+ /**
24
+ * Dynamic credential callback invoked on every GraphQL request.
25
+ * When set, JWT refreshes are picked up automatically — no need
26
+ * to rebuild or clear cached clients after a refresh.
27
+ */
28
+ tokenProvider?: TokenProvider;
16
29
  headers?: Record<string, string>;
17
30
  maxRetries?: number;
18
31
  retryDelay?: number;
@@ -41,10 +41,15 @@ class RoboSystemsExtensions {
41
41
  const sdkConfig = client_gen_1.client.getConfig();
42
42
  // Extract JWT token using centralized logic
43
43
  const token = config.token || (0, config_1.extractTokenFromSDKClient)();
44
+ // `tokenProvider` falls back to the global SDK extensions config so
45
+ // apps can wire refresh once via `setSDKExtensionsConfig({ tokenProvider })`
46
+ // and have the lazy default singleton pick it up automatically.
47
+ const tokenProvider = config.tokenProvider || (0, config_1.getSDKExtensionsConfig)().tokenProvider;
44
48
  this.config = {
45
49
  baseUrl: config.baseUrl || sdkConfig.baseUrl || 'http://localhost:8000',
46
50
  credentials: config.credentials || 'include',
47
51
  token,
52
+ tokenProvider,
48
53
  headers: config.headers,
49
54
  maxRetries: config.maxRetries || 5,
50
55
  retryDelay: config.retryDelay || 1000,
@@ -68,22 +73,30 @@ class RoboSystemsExtensions {
68
73
  maxRetries: this.config.maxRetries,
69
74
  retryDelay: this.config.retryDelay,
70
75
  });
76
+ // LedgerClient / InvestorClient / ReportClient all use GraphQL
77
+ // internally, so they get the tokenProvider — REST-only clients
78
+ // (QueryClient / AgentClient / OperationClient / SSEClient) do
79
+ // not, because their refresh story lives in the main SDK client
80
+ // wrapper, not here.
71
81
  this.ledger = new LedgerClient_1.LedgerClient({
72
82
  baseUrl: this.config.baseUrl,
73
83
  credentials: this.config.credentials,
74
84
  token: this.config.token,
85
+ tokenProvider: this.config.tokenProvider,
75
86
  headers: this.config.headers,
76
87
  });
77
88
  this.investor = new InvestorClient_1.InvestorClient({
78
89
  baseUrl: this.config.baseUrl,
79
90
  credentials: this.config.credentials,
80
91
  token: this.config.token,
92
+ tokenProvider: this.config.tokenProvider,
81
93
  headers: this.config.headers,
82
94
  });
83
95
  this.reports = new ReportClient_1.ReportClient({
84
96
  baseUrl: this.config.baseUrl,
85
97
  credentials: this.config.credentials,
86
98
  token: this.config.token,
99
+ tokenProvider: this.config.tokenProvider,
87
100
  headers: this.config.headers,
88
101
  });
89
102
  }
@@ -4,7 +4,8 @@
4
4
  */
5
5
 
6
6
  import { client } from '../client.gen'
7
- import { extractTokenFromSDKClient } from './config'
7
+ import { extractTokenFromSDKClient, getSDKExtensionsConfig } from './config'
8
+ import type { TokenProvider } from './graphql/client'
8
9
  import { OperationClient } from './OperationClient'
9
10
  import { QueryClient } from './QueryClient'
10
11
  import { AgentClient } from './AgentClient'
@@ -13,10 +14,27 @@ import { InvestorClient } from './InvestorClient'
13
14
  import { LedgerClient } from './LedgerClient'
14
15
  import { ReportClient } from './ReportClient'
15
16
 
17
+ // Re-export the `TokenProvider` type so consumers who want to type
18
+ // their own callback (`const provider: TokenProvider = () => …`) can
19
+ // pull it from the package root instead of reaching into the
20
+ // internal `./graphql/client` module path.
21
+ export type { TokenProvider } from './graphql/client'
22
+
16
23
  export interface RoboSystemsExtensionConfig {
17
24
  baseUrl?: string
18
25
  credentials?: 'include' | 'same-origin' | 'omit'
19
- token?: string // JWT token for authentication
26
+ /**
27
+ * Static credential captured at construction time. Fine for
28
+ * long-lived API keys; use `tokenProvider` instead when the JWT
29
+ * can rotate (browser login flows).
30
+ */
31
+ token?: string
32
+ /**
33
+ * Dynamic credential callback invoked on every GraphQL request.
34
+ * When set, JWT refreshes are picked up automatically — no need
35
+ * to rebuild or clear cached clients after a refresh.
36
+ */
37
+ tokenProvider?: TokenProvider
20
38
  headers?: Record<string, string>
21
39
  maxRetries?: number
22
40
  retryDelay?: number
@@ -27,6 +45,7 @@ interface ResolvedConfig {
27
45
  baseUrl: string
28
46
  credentials: 'include' | 'same-origin' | 'omit'
29
47
  token?: string
48
+ tokenProvider?: TokenProvider
30
49
  headers?: Record<string, string>
31
50
  maxRetries: number
32
51
  retryDelay: number
@@ -48,10 +67,16 @@ export class RoboSystemsExtensions {
48
67
  // Extract JWT token using centralized logic
49
68
  const token = config.token || extractTokenFromSDKClient()
50
69
 
70
+ // `tokenProvider` falls back to the global SDK extensions config so
71
+ // apps can wire refresh once via `setSDKExtensionsConfig({ tokenProvider })`
72
+ // and have the lazy default singleton pick it up automatically.
73
+ const tokenProvider = config.tokenProvider || getSDKExtensionsConfig().tokenProvider
74
+
51
75
  this.config = {
52
76
  baseUrl: config.baseUrl || sdkConfig.baseUrl || 'http://localhost:8000',
53
77
  credentials: config.credentials || 'include',
54
78
  token,
79
+ tokenProvider,
55
80
  headers: config.headers,
56
81
  maxRetries: config.maxRetries || 5,
57
82
  retryDelay: config.retryDelay || 1000,
@@ -79,10 +104,16 @@ export class RoboSystemsExtensions {
79
104
  retryDelay: this.config.retryDelay,
80
105
  })
81
106
 
107
+ // LedgerClient / InvestorClient / ReportClient all use GraphQL
108
+ // internally, so they get the tokenProvider — REST-only clients
109
+ // (QueryClient / AgentClient / OperationClient / SSEClient) do
110
+ // not, because their refresh story lives in the main SDK client
111
+ // wrapper, not here.
82
112
  this.ledger = new LedgerClient({
83
113
  baseUrl: this.config.baseUrl,
84
114
  credentials: this.config.credentials,
85
115
  token: this.config.token,
116
+ tokenProvider: this.config.tokenProvider,
86
117
  headers: this.config.headers,
87
118
  })
88
119
 
@@ -90,6 +121,7 @@ export class RoboSystemsExtensions {
90
121
  baseUrl: this.config.baseUrl,
91
122
  credentials: this.config.credentials,
92
123
  token: this.config.token,
124
+ tokenProvider: this.config.tokenProvider,
93
125
  headers: this.config.headers,
94
126
  })
95
127
 
@@ -97,6 +129,7 @@ export class RoboSystemsExtensions {
97
129
  baseUrl: this.config.baseUrl,
98
130
  credentials: this.config.credentials,
99
131
  token: this.config.token,
132
+ tokenProvider: this.config.tokenProvider,
100
133
  headers: this.config.headers,
101
134
  })
102
135
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robosystems/client",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "TypeScript client library for RoboSystems Financial Knowledge Graph API",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",