@slates/provider 1.0.0-rc.2 → 1.0.0-rc.21

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.
Files changed (125) hide show
  1. package/dist/index.cjs +2885 -2
  2. package/dist/index.d.cts +1019 -0
  3. package/dist/index.d.ts +1019 -9
  4. package/dist/index.module.js +2775 -2
  5. package/package.json +9 -6
  6. package/src/action/action.ts +92 -4
  7. package/src/action/attachment.ts +87 -0
  8. package/src/action/builder.test.ts +136 -0
  9. package/src/action/builder.ts +113 -15
  10. package/src/action/directUpload.test.ts +70 -0
  11. package/src/action/directUpload.ts +105 -0
  12. package/src/action/getFileUrlTool.ts +41 -0
  13. package/src/action/index.ts +4 -2
  14. package/src/action/publicTool.ts +36 -0
  15. package/src/action/scopes.test.ts +76 -0
  16. package/src/action/scopes.ts +103 -0
  17. package/src/action/tool.ts +22 -6
  18. package/src/action/trigger.ts +19 -11
  19. package/src/adapter/adapter.ts +38 -0
  20. package/src/adapter/index.test.ts +322 -0
  21. package/src/adapter/index.ts +2 -0
  22. package/src/adapter/spec.ts +154 -0
  23. package/src/auth/auth.ts +3 -3
  24. package/src/auth/index.ts +2 -0
  25. package/src/auth/oauth.test.ts +83 -0
  26. package/src/auth/oauth.ts +150 -0
  27. package/src/auth/redact.test.ts +103 -0
  28. package/src/auth/redact.ts +111 -0
  29. package/src/auth/types.ts +72 -13
  30. package/src/axios/http.test.ts +83 -0
  31. package/src/axios/index.ts +116 -10
  32. package/src/axios/trace.test.ts +377 -0
  33. package/src/axios/trace.ts +803 -0
  34. package/src/config/config.ts +14 -3
  35. package/src/context/context.ts +264 -17
  36. package/src/context/hook.test.ts +46 -0
  37. package/src/context/hook.ts +15 -6
  38. package/src/context/index.ts +1 -0
  39. package/src/context/pQueue.ts +4 -0
  40. package/src/crypto/hmac.test.ts +40 -0
  41. package/src/crypto/hmac.ts +32 -0
  42. package/src/crypto/index.ts +1 -0
  43. package/src/docs.ts +22 -0
  44. package/src/error/api.test.ts +114 -0
  45. package/src/error/api.ts +236 -0
  46. package/src/error/axios.ts +220 -0
  47. package/src/error/base.test.ts +178 -0
  48. package/src/error/base.ts +108 -2
  49. package/src/error/declaration.ts +7 -2
  50. package/src/error/defaults.ts +50 -0
  51. package/src/error/index.ts +4 -0
  52. package/src/error/service.ts +170 -0
  53. package/src/error/types.ts +75 -0
  54. package/src/error/utils.ts +157 -0
  55. package/src/file/index.test.ts +36 -0
  56. package/src/file/index.ts +27 -0
  57. package/src/index.ts +8 -0
  58. package/src/logger/logger.test.ts +57 -0
  59. package/src/logger/logger.ts +72 -11
  60. package/src/module/resolveDefaultExport.test.ts +32 -0
  61. package/src/module/resolveDefaultExport.ts +14 -0
  62. package/src/object/index.test.ts +38 -0
  63. package/src/object/index.ts +12 -0
  64. package/src/specification/slate.ts +40 -4
  65. package/src/specification/specification.ts +10 -6
  66. package/src/webhook/graph.test.ts +37 -0
  67. package/src/webhook/graph.ts +22 -0
  68. package/src/webhook/index.ts +2 -0
  69. package/src/webhook/meta.test.ts +52 -0
  70. package/src/webhook/meta.ts +43 -0
  71. package/dist/action/action.d.ts +0 -98
  72. package/dist/action/action.d.ts.map +0 -1
  73. package/dist/action/builder.d.ts +0 -27
  74. package/dist/action/builder.d.ts.map +0 -1
  75. package/dist/action/index.d.ts +0 -5
  76. package/dist/action/index.d.ts.map +0 -1
  77. package/dist/action/tool.d.ts +0 -14
  78. package/dist/action/tool.d.ts.map +0 -1
  79. package/dist/action/trigger.d.ts +0 -32
  80. package/dist/action/trigger.d.ts.map +0 -1
  81. package/dist/auth/auth.d.ts +0 -30
  82. package/dist/auth/auth.d.ts.map +0 -1
  83. package/dist/auth/index.d.ts +0 -3
  84. package/dist/auth/index.d.ts.map +0 -1
  85. package/dist/auth/types.d.ts +0 -148
  86. package/dist/auth/types.d.ts.map +0 -1
  87. package/dist/axios/index.d.ts +0 -4
  88. package/dist/axios/index.d.ts.map +0 -1
  89. package/dist/config/config.d.ts +0 -26
  90. package/dist/config/config.d.ts.map +0 -1
  91. package/dist/config/index.d.ts +0 -2
  92. package/dist/config/index.d.ts.map +0 -1
  93. package/dist/context/context.d.ts +0 -20
  94. package/dist/context/context.d.ts.map +0 -1
  95. package/dist/context/hook.d.ts +0 -4
  96. package/dist/context/hook.d.ts.map +0 -1
  97. package/dist/context/index.d.ts +0 -2
  98. package/dist/context/index.d.ts.map +0 -1
  99. package/dist/error/base.d.ts +0 -5
  100. package/dist/error/base.d.ts.map +0 -1
  101. package/dist/error/declaration.d.ts +0 -6
  102. package/dist/error/declaration.d.ts.map +0 -1
  103. package/dist/error/index.d.ts +0 -3
  104. package/dist/error/index.d.ts.map +0 -1
  105. package/dist/index.cjs.map +0 -1
  106. package/dist/index.d.ts.map +0 -1
  107. package/dist/index.modern.js +0 -2
  108. package/dist/index.modern.js.map +0 -1
  109. package/dist/index.module.js.map +0 -1
  110. package/dist/index.umd.js +0 -2
  111. package/dist/index.umd.js.map +0 -1
  112. package/dist/logger/index.d.ts +0 -2
  113. package/dist/logger/index.d.ts.map +0 -1
  114. package/dist/logger/logger.d.ts +0 -20
  115. package/dist/logger/logger.d.ts.map +0 -1
  116. package/dist/specification/index.d.ts +0 -3
  117. package/dist/specification/index.d.ts.map +0 -1
  118. package/dist/specification/slate.d.ts +0 -15
  119. package/dist/specification/slate.d.ts.map +0 -1
  120. package/dist/specification/specification.d.ts +0 -30
  121. package/dist/specification/specification.d.ts.map +0 -1
  122. package/dist/specification/zero.d.ts +0 -13
  123. package/dist/specification/zero.d.ts.map +0 -1
  124. package/dist/tokens.d.ts +0 -34
  125. package/dist/tokens.d.ts.map +0 -1
package/src/error/base.ts CHANGED
@@ -1,10 +1,116 @@
1
+ import { inferSlateErrorFromAxios, isAxiosErrorLike } from './axios';
2
+ import { DEFAULT_CODE } from './defaults';
3
+ import { getServiceErrorData, mapServiceErrorToSlateErrorInput } from './service';
4
+ import type { SlateAxiosErrorOptions, SlateErrorInput, SlateErrorResponse } from './types';
5
+ import {
6
+ getDefaultMessage,
7
+ isSlateErrorResponse,
8
+ mergeRecords,
9
+ mergeSlateErrorData,
10
+ normalizeSlateErrorInput,
11
+ sanitizeForBaggage
12
+ } from './utils';
13
+
1
14
  export class SlateError extends Error {
2
- constructor(message: string) {
3
- super(message);
15
+ cause?: unknown;
16
+ data: SlateErrorResponse;
17
+
18
+ constructor(input: SlateErrorInput) {
19
+ let normalized = normalizeSlateErrorInput(input);
20
+ super(normalized.message);
4
21
  this.name = 'SlateError';
22
+ this.data = normalized;
23
+ this.cause = input.cause;
24
+ }
25
+
26
+ get code() {
27
+ return this.data.code;
28
+ }
29
+
30
+ get kind() {
31
+ return this.data.kind;
32
+ }
33
+
34
+ get status() {
35
+ return this.data.status;
36
+ }
37
+
38
+ get retryable() {
39
+ return this.data.retryable;
40
+ }
41
+
42
+ toResponse(): SlateErrorResponse {
43
+ return {
44
+ ...this.data
45
+ };
46
+ }
47
+
48
+ toJSON(): SlateErrorResponse {
49
+ return this.toResponse();
5
50
  }
6
51
 
7
52
  static is(error: unknown): error is SlateError {
8
53
  return error instanceof SlateError;
9
54
  }
55
+
56
+ static fromUnknown(error: unknown, defaults: Partial<SlateErrorResponse> = {}): SlateError {
57
+ if (SlateError.is(error)) return error;
58
+ if (isSlateErrorResponse(error)) return new SlateError({ ...defaults, ...error });
59
+ if (getServiceErrorData(error)) return SlateError.fromServiceError(error, defaults);
60
+ if (isAxiosErrorLike(error)) return SlateError.fromAxios(error, { defaults });
61
+
62
+ if (error instanceof Error) {
63
+ return new SlateError({
64
+ code: defaults.code ?? DEFAULT_CODE,
65
+ message: error.message,
66
+ kind: defaults.kind,
67
+ retryable: defaults.retryable,
68
+ status: defaults.status,
69
+ issues: defaults.issues,
70
+ provider: defaults.provider,
71
+ upstream: defaults.upstream,
72
+ baggage: mergeRecords(defaults.baggage, {
73
+ originalName: error.name
74
+ }),
75
+ cause: error
76
+ });
77
+ }
78
+
79
+ return new SlateError({
80
+ code: defaults.code ?? DEFAULT_CODE,
81
+ message: getDefaultMessage(defaults.code ?? DEFAULT_CODE, defaults.message),
82
+ kind: defaults.kind,
83
+ retryable: defaults.retryable,
84
+ status: defaults.status,
85
+ issues: defaults.issues,
86
+ provider: defaults.provider,
87
+ upstream: defaults.upstream,
88
+ baggage: mergeRecords(defaults.baggage, {
89
+ originalValue: sanitizeForBaggage(error)
90
+ })
91
+ });
92
+ }
93
+
94
+ static fromServiceError(
95
+ error: unknown,
96
+ defaults: Partial<SlateErrorInput> = {}
97
+ ): SlateError {
98
+ return new SlateError(mapServiceErrorToSlateErrorInput(error as any, defaults));
99
+ }
100
+
101
+ static fromAxios(error: unknown, options: SlateAxiosErrorOptions = {}): SlateError {
102
+ if (SlateError.is(error)) return error;
103
+ if (getServiceErrorData(error))
104
+ return SlateError.fromServiceError(error, options.defaults);
105
+ if (!isAxiosErrorLike(error)) return SlateError.fromUnknown(error, options.defaults);
106
+
107
+ let inferred = inferSlateErrorFromAxios(error, options);
108
+
109
+ let mapped = options.mapAxiosError?.(error, inferred);
110
+ if (SlateError.is(mapped)) return mapped;
111
+
112
+ return new SlateError(mergeSlateErrorData(inferred, mapped ?? undefined));
113
+ }
10
114
  }
115
+
116
+ export let createSlateError = (input: SlateErrorInput) => new SlateError(input);
@@ -1,8 +1,13 @@
1
1
  import { SlateError } from './base';
2
2
 
3
3
  export class SlateDeclarationError extends SlateError {
4
- constructor(message: string) {
5
- super(`[Declaration Error]: ${message}`);
4
+ constructor(message: string, input: Partial<SlateError['data']> = {}) {
5
+ super({
6
+ code: 'declaration.invalid_definition',
7
+ kind: 'declaration',
8
+ message,
9
+ ...input
10
+ });
6
11
  this.name = 'SlateError.DeclarationError';
7
12
  }
8
13
 
@@ -0,0 +1,50 @@
1
+ export let DEFAULT_CODE = 'internal.unexpected';
2
+
3
+ export let DEFAULT_MESSAGES: Record<string, string> = {
4
+ 'declaration.invalid_definition': 'The slate definition is invalid.',
5
+ 'context.missing': 'No Slate context is available.',
6
+ 'input.invalid': 'The provided input is invalid.',
7
+ 'request.bad': 'The request is invalid.',
8
+ 'request.invalid_version': 'The requested version is invalid.',
9
+ 'request.precondition_failed': 'The request precondition failed.',
10
+ 'request.not_acceptable': 'The request is not acceptable.',
11
+ 'request.rate_limited': 'Too many requests were made in a short period of time.',
12
+ 'config.invalid': 'The provided configuration is invalid.',
13
+ 'auth.required': 'Authentication is required.',
14
+ 'auth.invalid': 'Authentication failed.',
15
+ 'auth.expired': 'Authentication has expired.',
16
+ 'permission.denied': 'Permission was denied.',
17
+ 'resource.not_found': 'The requested resource was not found.',
18
+ 'resource.conflict': 'The requested resource is in conflict.',
19
+ 'resource.gone': 'The requested resource is no longer available.',
20
+ 'payment.required': 'Payment is required to continue.',
21
+ 'operation.not_implemented': 'The requested operation is not implemented.',
22
+ 'upstream.invalid_request': 'The upstream service rejected the request.',
23
+ 'upstream.rate_limited': 'The upstream service rate limited the request.',
24
+ 'upstream.timeout': 'The upstream service timed out.',
25
+ 'upstream.network_error': 'A network error occurred while contacting the upstream service.',
26
+ 'upstream.unavailable': 'The upstream service is unavailable.',
27
+ 'upstream.invalid_response': 'The upstream service returned an invalid response.',
28
+ 'upstream.error': 'The upstream service returned an error.',
29
+ 'transport.invoke_failed': 'The provider transport failed.',
30
+ 'internal.timeout': 'The operation timed out.',
31
+ 'internal.unexpected': 'An unexpected internal error occurred.'
32
+ };
33
+
34
+ export let SERVICE_ERROR_CODE_MAP: Record<string, string> = {
35
+ invalid_data: 'input.invalid',
36
+ bad_request: 'request.bad',
37
+ not_found: 'resource.not_found',
38
+ unauthorized: 'auth.required',
39
+ forbidden: 'permission.denied',
40
+ invalid_version: 'request.invalid_version',
41
+ conflict: 'resource.conflict',
42
+ gone: 'resource.gone',
43
+ payment_required: 'payment.required',
44
+ precondition_failed: 'request.precondition_failed',
45
+ not_acceptable: 'request.not_acceptable',
46
+ not_implemented: 'operation.not_implemented',
47
+ too_many_requests: 'request.rate_limited',
48
+ timeout: 'internal.timeout',
49
+ internal_server_error: 'internal.unexpected'
50
+ };
@@ -1,2 +1,6 @@
1
+ export * from './api';
2
+ export * from './axios';
1
3
  export * from './base';
2
4
  export * from './declaration';
5
+ export * from './service';
6
+ export * from './types';
@@ -0,0 +1,170 @@
1
+ import type { ErrorData, ErrorRecord, ServiceError } from '@lowerdeck/error';
2
+ import { isServiceError } from '@lowerdeck/error';
3
+ import { DEFAULT_CODE, SERVICE_ERROR_CODE_MAP } from './defaults';
4
+ import type { SlateErrorInput, SlateErrorIssue, SlateServiceErrorData } from './types';
5
+ import {
6
+ cleanRecord,
7
+ isRecord,
8
+ normalizeKind,
9
+ normalizeRetryable,
10
+ sanitizeForBaggage
11
+ } from './utils';
12
+
13
+ let SERVICE_ERROR_DATA_RESERVED_KEYS = new Set([
14
+ 'status',
15
+ 'code',
16
+ 'message',
17
+ 'hint',
18
+ 'description',
19
+ 'reason',
20
+ 'errors',
21
+ 'object',
22
+ 'ok'
23
+ ]);
24
+
25
+ let isStructuredValue = (value: unknown): value is Record<string, unknown> | Function =>
26
+ (typeof value === 'object' || typeof value === 'function') && value !== null;
27
+
28
+ let mapServiceErrorCodeByStatus = (status?: number) => {
29
+ if (status === 400) return 'request.bad';
30
+ if (status === 401) return 'auth.required';
31
+ if (status === 402) return 'payment.required';
32
+ if (status === 403) return 'permission.denied';
33
+ if (status === 404) return 'resource.not_found';
34
+ if (status === 406) return 'request.not_acceptable';
35
+ if (status === 409) return 'resource.conflict';
36
+ if (status === 410) return 'resource.gone';
37
+ if (status === 412) return 'request.precondition_failed';
38
+ if (status === 429) return 'request.rate_limited';
39
+ if (status === 501) return 'operation.not_implemented';
40
+ if (status === 504) return 'internal.timeout';
41
+ if (status && status >= 500) return 'internal.unexpected';
42
+ return DEFAULT_CODE;
43
+ };
44
+
45
+ let normalizeServiceErrorIssues = (errors: unknown): SlateErrorIssue[] | undefined => {
46
+ if (!Array.isArray(errors)) return undefined;
47
+
48
+ let issues = errors.map(error => {
49
+ if (!isRecord(error)) {
50
+ return {
51
+ message: String(error)
52
+ };
53
+ }
54
+
55
+ let path = Array.isArray(error.path) ? error.path.map(entry => String(entry)) : undefined;
56
+
57
+ return {
58
+ ...error,
59
+ path,
60
+ code: typeof error.code === 'string' ? error.code : undefined,
61
+ message:
62
+ typeof error.message === 'string'
63
+ ? error.message
64
+ : typeof error.hint === 'string'
65
+ ? error.hint
66
+ : 'Validation issue'
67
+ };
68
+ });
69
+
70
+ return issues.length > 0 ? issues : undefined;
71
+ };
72
+
73
+ let getServiceErrorBaggage = (data: SlateServiceErrorData) => {
74
+ let extraEntries = Object.entries(data).filter(
75
+ ([key]) => !SERVICE_ERROR_DATA_RESERVED_KEYS.has(key)
76
+ );
77
+
78
+ return cleanRecord({
79
+ serviceError: {
80
+ code: data.code,
81
+ status: data.status,
82
+ hint: data.hint,
83
+ description: data.description,
84
+ reason: data.reason
85
+ },
86
+ ...(extraEntries.length > 0
87
+ ? {
88
+ serviceErrorData: sanitizeForBaggage(Object.fromEntries(extraEntries))
89
+ }
90
+ : {})
91
+ });
92
+ };
93
+
94
+ export let mapServiceErrorCode = (code: string, status?: number) =>
95
+ SERVICE_ERROR_CODE_MAP[code] ?? mapServiceErrorCodeByStatus(status);
96
+
97
+ export let isServiceErrorData = (value: unknown): value is SlateServiceErrorData =>
98
+ isRecord(value) &&
99
+ typeof value.status === 'number' &&
100
+ typeof value.code === 'string' &&
101
+ typeof value.message === 'string' &&
102
+ !('config' in value) &&
103
+ !('request' in value) &&
104
+ !('response' in value) &&
105
+ !('isAxiosError' in value);
106
+
107
+ export let getServiceErrorData = (error: unknown): SlateServiceErrorData | null => {
108
+ if (isServiceError(error)) {
109
+ return isServiceErrorData(error.data) ? error.data : null;
110
+ }
111
+
112
+ if (isServiceErrorData(error)) {
113
+ return error;
114
+ }
115
+
116
+ if (isStructuredValue(error) && 'data' in error && isServiceErrorData(error.data)) {
117
+ return error.data;
118
+ }
119
+
120
+ if (
121
+ isStructuredValue(error) &&
122
+ 'toResponse' in error &&
123
+ typeof error.toResponse === 'function'
124
+ ) {
125
+ let response = error.toResponse();
126
+ return isServiceErrorData(response) ? response : null;
127
+ }
128
+
129
+ return null;
130
+ };
131
+
132
+ export let mapServiceErrorToSlateErrorInput = (
133
+ error:
134
+ | ServiceError<ErrorRecord<any, any>>
135
+ | ErrorRecord<any, any>
136
+ | ErrorData<any, any>
137
+ | SlateServiceErrorData,
138
+ defaults: Partial<SlateErrorInput> = {}
139
+ ): SlateErrorInput => {
140
+ let data = getServiceErrorData(error);
141
+ if (!data) {
142
+ return {
143
+ code: defaults.code ?? DEFAULT_CODE,
144
+ message: defaults.message ?? 'An unexpected internal error occurred.',
145
+ kind: defaults.kind,
146
+ retryable: defaults.retryable,
147
+ status: defaults.status,
148
+ provider: defaults.provider,
149
+ upstream: defaults.upstream,
150
+ baggage: defaults.baggage
151
+ };
152
+ }
153
+
154
+ let code = defaults.code ?? mapServiceErrorCode(data.code, data.status);
155
+
156
+ return {
157
+ code,
158
+ message: defaults.message ?? data.message,
159
+ kind: defaults.kind ?? normalizeKind(code),
160
+ retryable: defaults.retryable ?? normalizeRetryable(code),
161
+ status: defaults.status ?? data.status,
162
+ issues: defaults.issues ?? normalizeServiceErrorIssues(data.errors),
163
+ provider: defaults.provider,
164
+ upstream: defaults.upstream,
165
+ baggage: cleanRecord({
166
+ ...(defaults.baggage ?? {}),
167
+ ...getServiceErrorBaggage(data)
168
+ })
169
+ };
170
+ };
@@ -0,0 +1,75 @@
1
+ import type { AxiosError, AxiosResponse } from 'axios';
2
+ import type { SlateHttpTrace } from '../axios/trace';
3
+ import type { SlateError } from './base';
4
+
5
+ export type SlateErrorKind =
6
+ | 'declaration'
7
+ | 'validation'
8
+ | 'request'
9
+ | 'auth'
10
+ | 'config'
11
+ | 'resource'
12
+ | 'payment'
13
+ | 'upstream'
14
+ | 'transport'
15
+ | 'internal';
16
+
17
+ export interface SlateErrorIssue {
18
+ path?: string[];
19
+ code?: string;
20
+ message: string;
21
+ [key: string]: unknown;
22
+ }
23
+
24
+ export interface SlateErrorProviderInfo {
25
+ key?: string;
26
+ service?: string;
27
+ operation?: string;
28
+ }
29
+
30
+ export interface SlateErrorUpstreamInfo {
31
+ status?: number;
32
+ code?: string;
33
+ type?: string;
34
+ requestId?: string;
35
+ method?: string;
36
+ url?: string;
37
+ }
38
+
39
+ export interface SlateErrorResponse {
40
+ code: string;
41
+ message: string;
42
+ kind: SlateErrorKind;
43
+ retryable?: boolean;
44
+ status?: number;
45
+ issues?: SlateErrorIssue[];
46
+ provider?: SlateErrorProviderInfo;
47
+ upstream?: SlateErrorUpstreamInfo;
48
+ baggage?: Record<string, unknown>;
49
+ requestTraces?: SlateHttpTrace[];
50
+ }
51
+
52
+ export interface SlateErrorInput extends Omit<SlateErrorResponse, 'kind'> {
53
+ kind?: SlateErrorKind;
54
+ cause?: unknown;
55
+ }
56
+
57
+ export interface SlateAxiosErrorOptions {
58
+ defaults?: Partial<SlateErrorResponse>;
59
+ extractResponseData?: (response: AxiosResponse) => unknown;
60
+ mapAxiosError?: (
61
+ error: AxiosError,
62
+ inferred: SlateErrorResponse
63
+ ) => SlateError | Partial<SlateErrorResponse> | undefined;
64
+ }
65
+
66
+ export interface SlateServiceErrorData {
67
+ status: number;
68
+ code: string;
69
+ message: string;
70
+ hint?: string;
71
+ description?: string;
72
+ reason?: string;
73
+ errors?: unknown;
74
+ [key: string]: unknown;
75
+ }
@@ -0,0 +1,157 @@
1
+ import { DEFAULT_CODE, DEFAULT_MESSAGES } from './defaults';
2
+ import type {
3
+ SlateErrorInput,
4
+ SlateErrorKind,
5
+ SlateErrorProviderInfo,
6
+ SlateErrorResponse,
7
+ SlateErrorUpstreamInfo
8
+ } from './types';
9
+
10
+ export let isRecord = (value: unknown): value is Record<string, unknown> =>
11
+ typeof value === 'object' && value !== null && !Array.isArray(value);
12
+
13
+ export let normalizeKind = (code: string, kind?: SlateErrorKind): SlateErrorKind => {
14
+ if (kind) return kind;
15
+ if (code.startsWith('declaration.')) return 'declaration';
16
+ if (code.startsWith('input.')) return 'validation';
17
+ if (code.startsWith('request.')) return 'request';
18
+ if (code.startsWith('config.')) return 'config';
19
+ if (code.startsWith('auth.') || code.startsWith('permission.')) return 'auth';
20
+ if (code.startsWith('resource.')) return 'resource';
21
+ if (code.startsWith('payment.')) return 'payment';
22
+ if (code.startsWith('transport.')) return 'transport';
23
+ if (code.startsWith('upstream.')) return 'upstream';
24
+ return 'internal';
25
+ };
26
+
27
+ export let normalizeStatus = (code: string, status?: number) => {
28
+ if (status) return status;
29
+ if (code === 'input.invalid' || code === 'request.bad' || code === 'config.invalid')
30
+ return 400;
31
+ if (code === 'request.invalid_version') return 400;
32
+ if (code === 'auth.required' || code === 'auth.invalid' || code === 'auth.expired')
33
+ return 401;
34
+ if (code === 'payment.required') return 402;
35
+ if (code === 'permission.denied') return 403;
36
+ if (code === 'resource.not_found') return 404;
37
+ if (code === 'resource.conflict') return 409;
38
+ if (code === 'resource.gone') return 410;
39
+ if (code === 'request.not_acceptable') return 406;
40
+ if (code === 'request.precondition_failed') return 412;
41
+ if (code === 'request.rate_limited' || code === 'upstream.rate_limited') return 429;
42
+ if (code === 'operation.not_implemented') return 501;
43
+ if (code === 'upstream.invalid_request') return 400;
44
+ if (code === 'upstream.timeout' || code === 'internal.timeout') return 504;
45
+ if (code === 'upstream.unavailable') return 503;
46
+ if (code === 'upstream.error') return 502;
47
+ return undefined;
48
+ };
49
+
50
+ export let normalizeRetryable = (code: string, retryable?: boolean) => {
51
+ if (typeof retryable === 'boolean') return retryable;
52
+ return (
53
+ code === 'request.rate_limited' ||
54
+ code === 'upstream.rate_limited' ||
55
+ code === 'upstream.timeout' ||
56
+ code === 'upstream.network_error' ||
57
+ code === 'upstream.unavailable' ||
58
+ code === 'upstream.error' ||
59
+ code === 'transport.invoke_failed' ||
60
+ code === 'internal.timeout'
61
+ );
62
+ };
63
+
64
+ export let getDefaultMessage = (code: string, message?: string) => {
65
+ if (message?.trim()) return message;
66
+ return DEFAULT_MESSAGES[code] ?? DEFAULT_MESSAGES[DEFAULT_CODE];
67
+ };
68
+
69
+ export let cleanRecord = <Value extends Record<string, unknown> | undefined>(value: Value) => {
70
+ if (!value) return undefined;
71
+
72
+ let entries = Object.entries(value).filter(([, entry]) => entry !== undefined);
73
+ if (entries.length === 0) return undefined;
74
+ return Object.fromEntries(entries) as Value;
75
+ };
76
+
77
+ export let mergeRecords = (
78
+ left?: Record<string, unknown>,
79
+ right?: Record<string, unknown>
80
+ ): Record<string, unknown> | undefined => {
81
+ if (!left && !right) return undefined;
82
+ return cleanRecord({
83
+ ...(left ?? {}),
84
+ ...(right ?? {})
85
+ });
86
+ };
87
+
88
+ export let mergeSlateErrorData = (
89
+ base: SlateErrorResponse,
90
+ override?: Partial<SlateErrorResponse>
91
+ ): SlateErrorResponse => {
92
+ if (!override) return base;
93
+
94
+ return {
95
+ ...base,
96
+ ...override,
97
+ provider: cleanRecord({
98
+ ...(base.provider ?? {}),
99
+ ...(override.provider ?? {})
100
+ }) as SlateErrorProviderInfo | undefined,
101
+ upstream: cleanRecord({
102
+ ...(base.upstream ?? {}),
103
+ ...(override.upstream ?? {})
104
+ }) as SlateErrorUpstreamInfo | undefined,
105
+ baggage: mergeRecords(base.baggage, override.baggage),
106
+ issues: override.issues ?? base.issues,
107
+ requestTraces: override.requestTraces ?? base.requestTraces
108
+ };
109
+ };
110
+
111
+ export let sanitizeForBaggage = (value: unknown, depth = 0): unknown => {
112
+ if (value == null) return value;
113
+ if (depth >= 4) return '[truncated]';
114
+ if (typeof value === 'string') {
115
+ return value.length > 2_000 ? `${value.slice(0, 2_000)}...[truncated]` : value;
116
+ }
117
+ if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
118
+ return value;
119
+ }
120
+ if (Array.isArray(value)) {
121
+ return value.slice(0, 20).map(item => sanitizeForBaggage(item, depth + 1));
122
+ }
123
+ if (isRecord(value)) {
124
+ return Object.fromEntries(
125
+ Object.entries(value)
126
+ .slice(0, 30)
127
+ .map(([key, entry]) => [key, sanitizeForBaggage(entry, depth + 1)])
128
+ );
129
+ }
130
+ return String(value);
131
+ };
132
+
133
+ export let normalizeSlateErrorInput = (input: SlateErrorInput): SlateErrorResponse => {
134
+ let code = input.code || DEFAULT_CODE;
135
+ return {
136
+ code,
137
+ message: getDefaultMessage(code, input.message),
138
+ kind: normalizeKind(code, input.kind),
139
+ retryable: normalizeRetryable(code, input.retryable),
140
+ status: normalizeStatus(code, input.status ?? input.upstream?.status),
141
+ issues: input.issues,
142
+ provider: cleanRecord(input.provider as Record<string, unknown> | undefined) as
143
+ | SlateErrorProviderInfo
144
+ | undefined,
145
+ upstream: cleanRecord({
146
+ ...input.upstream,
147
+ status: input.upstream?.status ?? input.status
148
+ }) as SlateErrorUpstreamInfo | undefined,
149
+ baggage: cleanRecord(input.baggage),
150
+ requestTraces: input.requestTraces
151
+ };
152
+ };
153
+
154
+ export let isSlateErrorResponse = (error: unknown): error is SlateErrorResponse => {
155
+ if (!isRecord(error)) return false;
156
+ return typeof error.code === 'string' && typeof error.message === 'string';
157
+ };
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getBase64ByteLength, getResponseHeaderValue } from './index';
3
+
4
+ describe('file helpers', () => {
5
+ it('finds response headers case-insensitively from plain objects', () => {
6
+ expect(
7
+ getResponseHeaderValue(
8
+ {
9
+ 'Content-Type': 'application/pdf',
10
+ 'x-empty': ''
11
+ },
12
+ 'content-type'
13
+ )
14
+ ).toBe('application/pdf');
15
+ expect(getResponseHeaderValue({ 'x-empty': '' }, 'X-Empty')).toBe('');
16
+ });
17
+
18
+ it('returns the first string value from array headers', () => {
19
+ expect(
20
+ getResponseHeaderValue({ 'content-type': ['text/plain', 'text/html'] }, 'content-type')
21
+ ).toBe('text/plain');
22
+ expect(
23
+ getResponseHeaderValue({ 'content-type': [123, 'text/html'] }, 'content-type')
24
+ ).toBe('text/html');
25
+ });
26
+
27
+ it('supports Headers-like objects', () => {
28
+ let headers = new Headers({ 'content-type': 'image/png' });
29
+
30
+ expect(getResponseHeaderValue(headers, 'Content-Type')).toBe('image/png');
31
+ });
32
+
33
+ it('computes decoded base64 byte length', () => {
34
+ expect(getBase64ByteLength(Buffer.from('hello').toString('base64'))).toBe(5);
35
+ });
36
+ });
@@ -0,0 +1,27 @@
1
+ export let getResponseHeaderValue = (headers: unknown, name: string): string | undefined => {
2
+ if (!headers) return undefined;
3
+
4
+ if (typeof (headers as { get?: unknown }).get === 'function') {
5
+ let value = (headers as { get(headerName: string): unknown }).get(name);
6
+ return typeof value === 'string' ? value : undefined;
7
+ }
8
+
9
+ if (typeof headers !== 'object') return undefined;
10
+
11
+ let lowerName = name.toLowerCase();
12
+ for (let [headerName, value] of Object.entries(headers as Record<string, unknown>)) {
13
+ if (headerName.toLowerCase() !== lowerName) continue;
14
+
15
+ if (Array.isArray(value)) {
16
+ let first = value.find(item => typeof item === 'string');
17
+ return typeof first === 'string' ? first : undefined;
18
+ }
19
+
20
+ return typeof value === 'string' ? value : undefined;
21
+ }
22
+
23
+ return undefined;
24
+ };
25
+
26
+ export let getBase64ByteLength = (contentBase64: string) =>
27
+ Buffer.from(contentBase64, 'base64').byteLength;
package/src/index.ts CHANGED
@@ -1,8 +1,16 @@
1
1
  export * from './action';
2
+ export * from './action/attachment';
3
+ export * from './action/directUpload';
4
+ export * from './adapter';
2
5
  export * from './auth';
3
6
  export * from './axios';
4
7
  export * from './config';
5
8
  export * from './context';
9
+ export * from './crypto';
10
+ export * from './docs';
6
11
  export * from './error';
12
+ export * from './file';
7
13
  export * from './logger';
14
+ export * from './object';
8
15
  export * from './specification';
16
+ export * from './webhook';