@slates/provider 1.0.0-rc.30 → 1.0.0-rc.32

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/index.cjs CHANGED
@@ -1309,6 +1309,8 @@ var sanitizeForBaggage = (value, options = {}) => {
1309
1309
  return BAGGAGE_UNSERIALIZABLE_VALUE;
1310
1310
  }
1311
1311
  };
1312
+ var UPSTREAM_CODE_PATTERN = /^[\w.:-]{1,128}$/;
1313
+ var normalizeUpstreamCode = (value) => typeof value === "string" && UPSTREAM_CODE_PATTERN.test(value) ? value : void 0;
1312
1314
  var normalizeSlateErrorInput = (input) => {
1313
1315
  let code = input.code || DEFAULT_CODE;
1314
1316
  return {
@@ -1323,6 +1325,7 @@ var normalizeSlateErrorInput = (input) => {
1323
1325
  ...input.upstream,
1324
1326
  status: input.upstream?.status ?? input.status
1325
1327
  }),
1328
+ upstreamCode: normalizeUpstreamCode(input.upstreamCode) ?? normalizeUpstreamCode(input.upstream?.code),
1326
1329
  baggage: cleanRecord(input.baggage),
1327
1330
  requestTraces: input.requestTraces
1328
1331
  };
@@ -1785,6 +1788,7 @@ var mapServiceErrorToSlateErrorInput = (error, defaults = {}) => {
1785
1788
  issues: defaults.issues ?? normalizeServiceErrorIssues(data.errors),
1786
1789
  provider: defaults.provider,
1787
1790
  upstream: defaults.upstream,
1791
+ upstreamCode: normalizeUpstreamCode(defaults.upstreamCode) ?? normalizeUpstreamCode(data.upstreamCode),
1788
1792
  baggage: cleanRecord({
1789
1793
  ...defaults.baggage ?? {},
1790
1794
  ...getServiceErrorBaggage(data)
package/dist/index.d.cts CHANGED
@@ -147,6 +147,35 @@ type SlateActionDocsReference = SlateTypedDocsReference<'docs.action.general'>;
147
147
  type SlateAuthDocsReference = SlateTypedDocsReference<'docs.auth.oauth' | 'docs.auth.oauth_scopes' | 'docs.auth.token' | 'docs.auth.service_account' | 'docs.auth.custom'>;
148
148
 
149
149
  type SlateAuthCallbackConfig = Record<string, any>;
150
+ type SlateOauthAuthorizationUrlContext<InputType> = {
151
+ redirectUri: string;
152
+ state: string;
153
+ input: InputType;
154
+ clientId: string;
155
+ clientSecret: string;
156
+ scopes: string[];
157
+ config?: SlateAuthCallbackConfig;
158
+ };
159
+ type SlateOauthAuthorizationUrlResult<InputType> = {
160
+ url: string;
161
+ input?: InputType;
162
+ callbackState?: Record<string, any>;
163
+ };
164
+ type SlateOauthCallbackContext<InputType> = {
165
+ code: string;
166
+ state: string;
167
+ redirectUri: string;
168
+ input: InputType;
169
+ clientId: string;
170
+ clientSecret: string;
171
+ scopes: string[];
172
+ callbackParams?: Record<string, string>;
173
+ callbackState: Record<string, any>;
174
+ config?: SlateAuthCallbackConfig;
175
+ };
176
+ type SlateOauthAdminCallbackContext<InputType> = Omit<SlateOauthCallbackContext<InputType>, 'code'> & {
177
+ code?: string;
178
+ };
150
179
  type SlateAuthWithOauth<InputType extends {}, OutputType extends {
151
180
  token: string;
152
181
  refreshToken?: string;
@@ -162,6 +191,13 @@ type SlateAuthWithOauth<InputType extends {}, OutputType extends {
162
191
  }[];
163
192
  adapters?: string[];
164
193
  syncTokensAcrossConnections?: boolean;
194
+ adminSetup?: {
195
+ required?: boolean;
196
+ getAuthorizationUrl?: (ctx: SlateOauthAuthorizationUrlContext<InputType>) => Promise<SlateOauthAuthorizationUrlResult<InputType>>;
197
+ handleCallback?: (ctx: SlateOauthAdminCallbackContext<InputType>) => Promise<{
198
+ instructions?: string;
199
+ }>;
200
+ };
165
201
  docs?: SlateAuthDocsReference[];
166
202
  inputSchema?: z.ZodType<InputType>;
167
203
  onInputChanged?: (params: {
@@ -171,31 +207,8 @@ type SlateAuthWithOauth<InputType extends {}, OutputType extends {
171
207
  input?: InputType;
172
208
  } | undefined>;
173
209
  getDefaultInput?: () => Promise<InputType>;
174
- getAuthorizationUrl: (ctx: {
175
- redirectUri: string;
176
- state: string;
177
- input: InputType;
178
- clientId: string;
179
- clientSecret: string;
180
- scopes: string[];
181
- config?: SlateAuthCallbackConfig;
182
- }) => Promise<{
183
- url: string;
184
- input?: InputType;
185
- callbackState?: Record<string, any>;
186
- }>;
187
- handleCallback: (ctx: {
188
- code: string;
189
- state: string;
190
- redirectUri: string;
191
- input: InputType;
192
- clientId: string;
193
- clientSecret: string;
194
- scopes: string[];
195
- callbackParams?: Record<string, string>;
196
- callbackState: Record<string, any>;
197
- config?: SlateAuthCallbackConfig;
198
- }) => Promise<{
210
+ getAuthorizationUrl: (ctx: SlateOauthAuthorizationUrlContext<InputType>) => Promise<SlateOauthAuthorizationUrlResult<InputType>>;
211
+ handleCallback: (ctx: SlateOauthCallbackContext<InputType>) => Promise<{
199
212
  output: OutputType;
200
213
  input?: InputType;
201
214
  scopes?: string[];
@@ -1064,6 +1077,7 @@ interface SlateErrorResponse {
1064
1077
  issues?: SlateErrorIssue[];
1065
1078
  provider?: SlateErrorProviderInfo;
1066
1079
  upstream?: SlateErrorUpstreamInfo;
1080
+ upstreamCode?: string;
1067
1081
  baggage?: Record<string, unknown>;
1068
1082
  requestTraces?: SlateHttpTrace[];
1069
1083
  }
package/dist/index.d.ts CHANGED
@@ -147,6 +147,35 @@ type SlateActionDocsReference = SlateTypedDocsReference<'docs.action.general'>;
147
147
  type SlateAuthDocsReference = SlateTypedDocsReference<'docs.auth.oauth' | 'docs.auth.oauth_scopes' | 'docs.auth.token' | 'docs.auth.service_account' | 'docs.auth.custom'>;
148
148
 
149
149
  type SlateAuthCallbackConfig = Record<string, any>;
150
+ type SlateOauthAuthorizationUrlContext<InputType> = {
151
+ redirectUri: string;
152
+ state: string;
153
+ input: InputType;
154
+ clientId: string;
155
+ clientSecret: string;
156
+ scopes: string[];
157
+ config?: SlateAuthCallbackConfig;
158
+ };
159
+ type SlateOauthAuthorizationUrlResult<InputType> = {
160
+ url: string;
161
+ input?: InputType;
162
+ callbackState?: Record<string, any>;
163
+ };
164
+ type SlateOauthCallbackContext<InputType> = {
165
+ code: string;
166
+ state: string;
167
+ redirectUri: string;
168
+ input: InputType;
169
+ clientId: string;
170
+ clientSecret: string;
171
+ scopes: string[];
172
+ callbackParams?: Record<string, string>;
173
+ callbackState: Record<string, any>;
174
+ config?: SlateAuthCallbackConfig;
175
+ };
176
+ type SlateOauthAdminCallbackContext<InputType> = Omit<SlateOauthCallbackContext<InputType>, 'code'> & {
177
+ code?: string;
178
+ };
150
179
  type SlateAuthWithOauth<InputType extends {}, OutputType extends {
151
180
  token: string;
152
181
  refreshToken?: string;
@@ -162,6 +191,13 @@ type SlateAuthWithOauth<InputType extends {}, OutputType extends {
162
191
  }[];
163
192
  adapters?: string[];
164
193
  syncTokensAcrossConnections?: boolean;
194
+ adminSetup?: {
195
+ required?: boolean;
196
+ getAuthorizationUrl?: (ctx: SlateOauthAuthorizationUrlContext<InputType>) => Promise<SlateOauthAuthorizationUrlResult<InputType>>;
197
+ handleCallback?: (ctx: SlateOauthAdminCallbackContext<InputType>) => Promise<{
198
+ instructions?: string;
199
+ }>;
200
+ };
165
201
  docs?: SlateAuthDocsReference[];
166
202
  inputSchema?: z.ZodType<InputType>;
167
203
  onInputChanged?: (params: {
@@ -171,31 +207,8 @@ type SlateAuthWithOauth<InputType extends {}, OutputType extends {
171
207
  input?: InputType;
172
208
  } | undefined>;
173
209
  getDefaultInput?: () => Promise<InputType>;
174
- getAuthorizationUrl: (ctx: {
175
- redirectUri: string;
176
- state: string;
177
- input: InputType;
178
- clientId: string;
179
- clientSecret: string;
180
- scopes: string[];
181
- config?: SlateAuthCallbackConfig;
182
- }) => Promise<{
183
- url: string;
184
- input?: InputType;
185
- callbackState?: Record<string, any>;
186
- }>;
187
- handleCallback: (ctx: {
188
- code: string;
189
- state: string;
190
- redirectUri: string;
191
- input: InputType;
192
- clientId: string;
193
- clientSecret: string;
194
- scopes: string[];
195
- callbackParams?: Record<string, string>;
196
- callbackState: Record<string, any>;
197
- config?: SlateAuthCallbackConfig;
198
- }) => Promise<{
210
+ getAuthorizationUrl: (ctx: SlateOauthAuthorizationUrlContext<InputType>) => Promise<SlateOauthAuthorizationUrlResult<InputType>>;
211
+ handleCallback: (ctx: SlateOauthCallbackContext<InputType>) => Promise<{
199
212
  output: OutputType;
200
213
  input?: InputType;
201
214
  scopes?: string[];
@@ -1064,6 +1077,7 @@ interface SlateErrorResponse {
1064
1077
  issues?: SlateErrorIssue[];
1065
1078
  provider?: SlateErrorProviderInfo;
1066
1079
  upstream?: SlateErrorUpstreamInfo;
1080
+ upstreamCode?: string;
1067
1081
  baggage?: Record<string, unknown>;
1068
1082
  requestTraces?: SlateHttpTrace[];
1069
1083
  }
@@ -1184,6 +1184,8 @@ var sanitizeForBaggage = (value, options = {}) => {
1184
1184
  return BAGGAGE_UNSERIALIZABLE_VALUE;
1185
1185
  }
1186
1186
  };
1187
+ var UPSTREAM_CODE_PATTERN = /^[\w.:-]{1,128}$/;
1188
+ var normalizeUpstreamCode = (value) => typeof value === "string" && UPSTREAM_CODE_PATTERN.test(value) ? value : void 0;
1187
1189
  var normalizeSlateErrorInput = (input) => {
1188
1190
  let code = input.code || DEFAULT_CODE;
1189
1191
  return {
@@ -1198,6 +1200,7 @@ var normalizeSlateErrorInput = (input) => {
1198
1200
  ...input.upstream,
1199
1201
  status: input.upstream?.status ?? input.status
1200
1202
  }),
1203
+ upstreamCode: normalizeUpstreamCode(input.upstreamCode) ?? normalizeUpstreamCode(input.upstream?.code),
1201
1204
  baggage: cleanRecord(input.baggage),
1202
1205
  requestTraces: input.requestTraces
1203
1206
  };
@@ -1660,6 +1663,7 @@ var mapServiceErrorToSlateErrorInput = (error, defaults = {}) => {
1660
1663
  issues: defaults.issues ?? normalizeServiceErrorIssues(data.errors),
1661
1664
  provider: defaults.provider,
1662
1665
  upstream: defaults.upstream,
1666
+ upstreamCode: normalizeUpstreamCode(defaults.upstreamCode) ?? normalizeUpstreamCode(data.upstreamCode),
1663
1667
  baggage: cleanRecord({
1664
1668
  ...defaults.baggage ?? {},
1665
1669
  ...getServiceErrorBaggage(data)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slates/provider",
3
- "version": "1.0.0-rc.30",
3
+ "version": "1.0.0-rc.32",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/auth/types.ts CHANGED
@@ -3,6 +3,42 @@ import type { SlateAuthDocsReference } from '../docs';
3
3
 
4
4
  type SlateAuthCallbackConfig = Record<string, any>;
5
5
 
6
+ type SlateOauthAuthorizationUrlContext<InputType> = {
7
+ redirectUri: string;
8
+ state: string;
9
+ input: InputType;
10
+ clientId: string;
11
+ clientSecret: string;
12
+ scopes: string[];
13
+ config?: SlateAuthCallbackConfig;
14
+ };
15
+
16
+ type SlateOauthAuthorizationUrlResult<InputType> = {
17
+ url: string;
18
+ input?: InputType;
19
+ callbackState?: Record<string, any>;
20
+ };
21
+
22
+ type SlateOauthCallbackContext<InputType> = {
23
+ code: string;
24
+ state: string;
25
+ redirectUri: string;
26
+ input: InputType;
27
+ clientId: string;
28
+ clientSecret: string;
29
+ scopes: string[];
30
+ callbackParams?: Record<string, string>;
31
+ callbackState: Record<string, any>;
32
+ config?: SlateAuthCallbackConfig;
33
+ };
34
+
35
+ type SlateOauthAdminCallbackContext<InputType> = Omit<
36
+ SlateOauthCallbackContext<InputType>,
37
+ 'code'
38
+ > & {
39
+ code?: string;
40
+ };
41
+
6
42
  export type SlateAuthWithOauth<
7
43
  InputType extends {},
8
44
  OutputType extends {
@@ -28,6 +64,21 @@ export type SlateAuthWithOauth<
28
64
  // connection of one installation, so a refresh must update all of them.
29
65
  syncTokensAcrossConnections?: boolean;
30
66
 
67
+ // Missing hooks fall back to the regular flow (callback output discarded).
68
+ // Returned instructions are markdown shown to the admin. `required` means users
69
+ // can't connect until an admin has completed setup; otherwise it is optional.
70
+ adminSetup?: {
71
+ required?: boolean;
72
+
73
+ getAuthorizationUrl?: (
74
+ ctx: SlateOauthAuthorizationUrlContext<InputType>
75
+ ) => Promise<SlateOauthAuthorizationUrlResult<InputType>>;
76
+
77
+ handleCallback?: (
78
+ ctx: SlateOauthAdminCallbackContext<InputType>
79
+ ) => Promise<{ instructions?: string }>;
80
+ };
81
+
31
82
  docs?: SlateAuthDocsReference[];
32
83
 
33
84
  inputSchema?: z.ZodType<InputType>;
@@ -39,32 +90,11 @@ export type SlateAuthWithOauth<
39
90
 
40
91
  getDefaultInput?: () => Promise<InputType>;
41
92
 
42
- getAuthorizationUrl: (ctx: {
43
- redirectUri: string;
44
- state: string;
45
- input: InputType;
46
- clientId: string;
47
- clientSecret: string;
48
- scopes: string[];
49
- config?: SlateAuthCallbackConfig;
50
- }) => Promise<{
51
- url: string;
52
- input?: InputType;
53
- callbackState?: Record<string, any>;
54
- }>;
93
+ getAuthorizationUrl: (
94
+ ctx: SlateOauthAuthorizationUrlContext<InputType>
95
+ ) => Promise<SlateOauthAuthorizationUrlResult<InputType>>;
55
96
 
56
- handleCallback: (ctx: {
57
- code: string;
58
- state: string;
59
- redirectUri: string;
60
- input: InputType;
61
- clientId: string;
62
- clientSecret: string;
63
- scopes: string[];
64
- callbackParams?: Record<string, string>;
65
- callbackState: Record<string, any>;
66
- config?: SlateAuthCallbackConfig;
67
- }) => Promise<{
97
+ handleCallback: (ctx: SlateOauthCallbackContext<InputType>) => Promise<{
68
98
  output: OutputType;
69
99
  input?: InputType;
70
100
  scopes?: string[];
@@ -17,6 +17,7 @@ import {
17
17
  } from '@lowerdeck/error';
18
18
  import { AxiosError, type AxiosResponse, type InternalAxiosRequestConfig } from 'axios';
19
19
  import { describe, expect, it } from 'vitest';
20
+ import { createApiServiceError } from './api';
20
21
  import { SlateError } from './base';
21
22
 
22
23
  let createConfig = (overrides: Partial<InternalAxiosRequestConfig> = {}) =>
@@ -175,4 +176,80 @@ describe('SlateError', () => {
175
176
  });
176
177
  }
177
178
  });
179
+
180
+ it('exposes the upstream API error code as a top-level upstreamCode', () => {
181
+ expect(
182
+ new SlateError({ code: 'upstream.error', message: 'x', upstream: { code: 'E42' } }).data
183
+ .upstreamCode
184
+ ).toBe('E42');
185
+ expect(
186
+ new SlateError({
187
+ code: 'upstream.error',
188
+ message: 'x',
189
+ upstream: { code: 'nested' },
190
+ upstreamCode: 'explicit'
191
+ }).data.upstreamCode
192
+ ).toBe('explicit');
193
+ expect(
194
+ SlateError.fromServiceError(
195
+ createApiServiceError('x', { upstreamCode: 'card_declined' })
196
+ ).data.upstreamCode
197
+ ).toBe('card_declined');
198
+ expect(new SlateError({ code: 'upstream.error', message: 'x' }).data.upstreamCode).toBe(
199
+ undefined
200
+ );
201
+ });
202
+
203
+ it('accepts only code-shaped upstreamCode values', () => {
204
+ for (let code of [
205
+ 'AADSTS700016',
206
+ 'invalid_grant',
207
+ 'RATE_LIMITED',
208
+ 'request.bad',
209
+ 'a:b-c'
210
+ ]) {
211
+ expect(
212
+ new SlateError({ code: 'upstream.error', message: 'x', upstreamCode: code }).data
213
+ .upstreamCode
214
+ ).toBe(code);
215
+ }
216
+
217
+ for (let code of ['', 'x'.repeat(129), 'has space', 'token=abc&x', 'line\nbreak', '`x`']) {
218
+ expect(
219
+ new SlateError({ code: 'upstream.error', message: 'x', upstreamCode: code }).data
220
+ .upstreamCode
221
+ ).toBe(undefined);
222
+ expect(
223
+ SlateError.fromServiceError(createApiServiceError('x', { upstreamCode: code })).data
224
+ .upstreamCode
225
+ ).toBe(undefined);
226
+ }
227
+
228
+ expect(
229
+ SlateError.fromServiceError(
230
+ createApiServiceError('x', { upstreamCode: { nested: true } as unknown as string })
231
+ ).data.upstreamCode
232
+ ).toBe(undefined);
233
+ expect(
234
+ new SlateError({ code: 'upstream.error', message: 'x', upstreamCode: 'x'.repeat(128) })
235
+ .data.upstreamCode
236
+ ).toBe('x'.repeat(128));
237
+ expect(
238
+ new SlateError({
239
+ code: 'upstream.error',
240
+ message: 'x',
241
+ upstreamCode: 'bad value',
242
+ upstream: { code: 'fallback_code' }
243
+ }).data.upstreamCode
244
+ ).toBe('fallback_code');
245
+ });
246
+
247
+ it('also keeps upstreamCode in service error baggage for older hubs', () => {
248
+ let error = SlateError.fromServiceError(
249
+ createApiServiceError('x', { upstreamCode: 'card_declined' })
250
+ );
251
+ expect(error.data.baggage?.serviceErrorData).toMatchObject({
252
+ upstreamCode: 'card_declined'
253
+ });
254
+ });
178
255
  });
@@ -7,6 +7,7 @@ import {
7
7
  isRecord,
8
8
  normalizeKind,
9
9
  normalizeRetryable,
10
+ normalizeUpstreamCode,
10
11
  sanitizeForBaggage
11
12
  } from './utils';
12
13
 
@@ -162,6 +163,8 @@ export let mapServiceErrorToSlateErrorInput = (
162
163
  issues: defaults.issues ?? normalizeServiceErrorIssues(data.errors),
163
164
  provider: defaults.provider,
164
165
  upstream: defaults.upstream,
166
+ upstreamCode:
167
+ normalizeUpstreamCode(defaults.upstreamCode) ?? normalizeUpstreamCode(data.upstreamCode),
165
168
  baggage: cleanRecord({
166
169
  ...(defaults.baggage ?? {}),
167
170
  ...getServiceErrorBaggage(data)
@@ -45,6 +45,8 @@ export interface SlateErrorResponse {
45
45
  issues?: SlateErrorIssue[];
46
46
  provider?: SlateErrorProviderInfo;
47
47
  upstream?: SlateErrorUpstreamInfo;
48
+ // The upstream API's own error code, next to the normalized protocol `code`.
49
+ upstreamCode?: string;
48
50
  baggage?: Record<string, unknown>;
49
51
  requestTraces?: SlateHttpTrace[];
50
52
  }
@@ -389,6 +389,13 @@ export let sanitizeForBaggage = (
389
389
  }
390
390
  };
391
391
 
392
+ let UPSTREAM_CODE_PATTERN = /^[\w.:-]{1,128}$/;
393
+
394
+ // upstreamCode is copied from untrusted upstream responses to the top level of the
395
+ // error, outside the baggage sanitizer, so anything not code-shaped is dropped.
396
+ export let normalizeUpstreamCode = (value: unknown): string | undefined =>
397
+ typeof value === 'string' && UPSTREAM_CODE_PATTERN.test(value) ? value : undefined;
398
+
392
399
  export let normalizeSlateErrorInput = (input: SlateErrorInput): SlateErrorResponse => {
393
400
  let code = input.code || DEFAULT_CODE;
394
401
  return {
@@ -405,6 +412,8 @@ export let normalizeSlateErrorInput = (input: SlateErrorInput): SlateErrorRespon
405
412
  ...input.upstream,
406
413
  status: input.upstream?.status ?? input.status
407
414
  }) as SlateErrorUpstreamInfo | undefined,
415
+ upstreamCode:
416
+ normalizeUpstreamCode(input.upstreamCode) ?? normalizeUpstreamCode(input.upstream?.code),
408
417
  baggage: cleanRecord(input.baggage),
409
418
  requestTraces: input.requestTraces
410
419
  };