@slates/provider 1.0.0-rc.31 → 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
@@ -192,6 +192,7 @@ type SlateAuthWithOauth<InputType extends {}, OutputType extends {
192
192
  adapters?: string[];
193
193
  syncTokensAcrossConnections?: boolean;
194
194
  adminSetup?: {
195
+ required?: boolean;
195
196
  getAuthorizationUrl?: (ctx: SlateOauthAuthorizationUrlContext<InputType>) => Promise<SlateOauthAuthorizationUrlResult<InputType>>;
196
197
  handleCallback?: (ctx: SlateOauthAdminCallbackContext<InputType>) => Promise<{
197
198
  instructions?: string;
@@ -1076,6 +1077,7 @@ interface SlateErrorResponse {
1076
1077
  issues?: SlateErrorIssue[];
1077
1078
  provider?: SlateErrorProviderInfo;
1078
1079
  upstream?: SlateErrorUpstreamInfo;
1080
+ upstreamCode?: string;
1079
1081
  baggage?: Record<string, unknown>;
1080
1082
  requestTraces?: SlateHttpTrace[];
1081
1083
  }
package/dist/index.d.ts CHANGED
@@ -192,6 +192,7 @@ type SlateAuthWithOauth<InputType extends {}, OutputType extends {
192
192
  adapters?: string[];
193
193
  syncTokensAcrossConnections?: boolean;
194
194
  adminSetup?: {
195
+ required?: boolean;
195
196
  getAuthorizationUrl?: (ctx: SlateOauthAuthorizationUrlContext<InputType>) => Promise<SlateOauthAuthorizationUrlResult<InputType>>;
196
197
  handleCallback?: (ctx: SlateOauthAdminCallbackContext<InputType>) => Promise<{
197
198
  instructions?: string;
@@ -1076,6 +1077,7 @@ interface SlateErrorResponse {
1076
1077
  issues?: SlateErrorIssue[];
1077
1078
  provider?: SlateErrorProviderInfo;
1078
1079
  upstream?: SlateErrorUpstreamInfo;
1080
+ upstreamCode?: string;
1079
1081
  baggage?: Record<string, unknown>;
1080
1082
  requestTraces?: SlateHttpTrace[];
1081
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.31",
3
+ "version": "1.0.0-rc.32",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/auth/types.ts CHANGED
@@ -65,8 +65,11 @@ export type SlateAuthWithOauth<
65
65
  syncTokensAcrossConnections?: boolean;
66
66
 
67
67
  // Missing hooks fall back to the regular flow (callback output discarded).
68
- // Returned instructions are markdown shown to the admin.
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.
69
70
  adminSetup?: {
71
+ required?: boolean;
72
+
70
73
  getAuthorizationUrl?: (
71
74
  ctx: SlateOauthAuthorizationUrlContext<InputType>
72
75
  ) => Promise<SlateOauthAuthorizationUrlResult<InputType>>;
@@ -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
  };