@pattern-stack/frontend-patterns 0.2.0-alpha.1 → 0.2.0-alpha.12
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/atoms/components/core/Badge/Badge.d.ts +1 -1
- package/dist/atoms/components/data/DataTable/ColumnFilterDropdown.d.ts +32 -0
- package/dist/atoms/components/data/DataTable/ColumnFilterDropdown.d.ts.map +1 -0
- package/dist/atoms/components/data/DataTable/ColumnVisibilityToggle.d.ts +32 -0
- package/dist/atoms/components/data/DataTable/ColumnVisibilityToggle.d.ts.map +1 -0
- package/dist/atoms/components/data/DataTable/DataTable.d.ts +5 -2
- package/dist/atoms/components/data/DataTable/DataTable.d.ts.map +1 -1
- package/dist/atoms/components/data/DataTable/DataTable.expansion.d.ts +91 -0
- package/dist/atoms/components/data/DataTable/DataTable.expansion.d.ts.map +1 -0
- package/dist/atoms/components/data/DataTable/DataTable.filters.d.ts +271 -0
- package/dist/atoms/components/data/DataTable/DataTable.filters.d.ts.map +1 -0
- package/dist/atoms/components/data/DataTable/DataTable.types.d.ts +155 -5
- package/dist/atoms/components/data/DataTable/DataTable.types.d.ts.map +1 -1
- package/dist/atoms/components/data/DataTable/ExpandButton.d.ts +37 -0
- package/dist/atoms/components/data/DataTable/ExpandButton.d.ts.map +1 -0
- package/dist/atoms/components/data/DataTable/FilterPill.d.ts +25 -0
- package/dist/atoms/components/data/DataTable/FilterPill.d.ts.map +1 -0
- package/dist/atoms/components/data/DataTable/QuickFilterBar.d.ts +35 -0
- package/dist/atoms/components/data/DataTable/QuickFilterBar.d.ts.map +1 -0
- package/dist/atoms/components/data/DataTable/filters/BooleanFilterEditor.d.ts +10 -0
- package/dist/atoms/components/data/DataTable/filters/BooleanFilterEditor.d.ts.map +1 -0
- package/dist/atoms/components/data/DataTable/filters/DateFilterEditor.d.ts +11 -0
- package/dist/atoms/components/data/DataTable/filters/DateFilterEditor.d.ts.map +1 -0
- package/dist/atoms/components/data/DataTable/filters/MultiSelectFilterEditor.d.ts +10 -0
- package/dist/atoms/components/data/DataTable/filters/MultiSelectFilterEditor.d.ts.map +1 -0
- package/dist/atoms/components/data/DataTable/filters/NumberFilterEditor.d.ts +10 -0
- package/dist/atoms/components/data/DataTable/filters/NumberFilterEditor.d.ts.map +1 -0
- package/dist/atoms/components/data/DataTable/filters/SelectFilterEditor.d.ts +10 -0
- package/dist/atoms/components/data/DataTable/filters/SelectFilterEditor.d.ts.map +1 -0
- package/dist/atoms/components/data/DataTable/filters/TextFilterEditor.d.ts +10 -0
- package/dist/atoms/components/data/DataTable/filters/TextFilterEditor.d.ts.map +1 -0
- package/dist/atoms/components/data/DataTable/filters/index.d.ts +14 -0
- package/dist/atoms/components/data/DataTable/filters/index.d.ts.map +1 -0
- package/dist/atoms/components/data/DataTable/index.d.ts +9 -0
- package/dist/atoms/components/data/DataTable/index.d.ts.map +1 -1
- package/dist/atoms/components/data/ProgressBar/ProgressBar.d.ts +1 -1
- package/dist/atoms/components/data/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/atoms/components/data/index.d.ts +3 -2
- package/dist/atoms/components/data/index.d.ts.map +1 -1
- package/dist/atoms/composed/ConnectionStatus/ConnectionStatus.d.ts +16 -0
- package/dist/atoms/composed/ConnectionStatus/ConnectionStatus.d.ts.map +1 -0
- package/dist/atoms/composed/ConnectionStatus/index.d.ts +3 -0
- package/dist/atoms/composed/ConnectionStatus/index.d.ts.map +1 -0
- package/dist/atoms/hooks/index.d.ts +9 -0
- package/dist/atoms/hooks/index.d.ts.map +1 -1
- package/dist/atoms/hooks/useAdaptiveTable.d.ts +49 -0
- package/dist/atoms/hooks/useAdaptiveTable.d.ts.map +1 -0
- package/dist/atoms/hooks/useApi.d.ts +1 -1
- package/dist/atoms/hooks/useApi.d.ts.map +1 -1
- package/dist/atoms/hooks/useColumnVisibility.d.ts +75 -0
- package/dist/atoms/hooks/useColumnVisibility.d.ts.map +1 -0
- package/dist/atoms/hooks/useEntityData.d.ts +36 -0
- package/dist/atoms/hooks/useEntityData.d.ts.map +1 -0
- package/dist/atoms/hooks/useEntityDetail.d.ts +43 -0
- package/dist/atoms/hooks/useEntityDetail.d.ts.map +1 -0
- package/dist/atoms/hooks/useExpandedRows.d.ts +66 -0
- package/dist/atoms/hooks/useExpandedRows.d.ts.map +1 -0
- package/dist/atoms/hooks/useFieldMetadata.d.ts +18 -0
- package/dist/atoms/hooks/useFieldMetadata.d.ts.map +1 -0
- package/dist/atoms/hooks/useOnlineStatus.d.ts +16 -0
- package/dist/atoms/hooks/useOnlineStatus.d.ts.map +1 -0
- package/dist/atoms/hooks/useResponsiveTable.d.ts +123 -0
- package/dist/atoms/hooks/useResponsiveTable.d.ts.map +1 -0
- package/dist/atoms/hooks/useTableFilters.d.ts +92 -0
- package/dist/atoms/hooks/useTableFilters.d.ts.map +1 -0
- package/dist/atoms/index.d.ts +1 -0
- package/dist/atoms/index.d.ts.map +1 -1
- package/dist/atoms/primitives/sheet.d.ts +23 -0
- package/dist/atoms/primitives/sheet.d.ts.map +1 -0
- package/dist/atoms/primitives/table.d.ts.map +1 -1
- package/dist/atoms/services/api/client.d.ts +12 -2
- package/dist/atoms/services/api/client.d.ts.map +1 -1
- package/dist/atoms/services/auth-service.d.ts +15 -0
- package/dist/atoms/services/auth-service.d.ts.map +1 -1
- package/dist/atoms/services/index.d.ts +2 -2
- package/dist/atoms/services/index.d.ts.map +1 -1
- package/dist/atoms/shared/config/table-config.d.ts +79 -0
- package/dist/atoms/shared/config/table-config.d.ts.map +1 -0
- package/dist/atoms/shared/index.d.ts +1 -0
- package/dist/atoms/shared/index.d.ts.map +1 -1
- package/dist/atoms/types/auth.d.ts +95 -2
- package/dist/atoms/types/auth.d.ts.map +1 -1
- package/dist/atoms/types/index.d.ts +1 -0
- package/dist/atoms/types/index.d.ts.map +1 -1
- package/dist/atoms/types/navigation.d.ts +1 -1
- package/dist/atoms/types/navigation.d.ts.map +1 -1
- package/dist/atoms/types/ui-config.d.ts +46 -11
- package/dist/atoms/types/ui-config.d.ts.map +1 -1
- package/dist/atoms/types/ui-metadata.d.ts +103 -0
- package/dist/atoms/types/ui-metadata.d.ts.map +1 -0
- package/dist/atoms/utils/entity-card-mapping.d.ts +105 -0
- package/dist/atoms/utils/entity-card-mapping.d.ts.map +1 -0
- package/dist/atoms/utils/field-detection.d.ts +2 -2
- package/dist/atoms/utils/field-detection.d.ts.map +1 -1
- package/dist/atoms/utils/icon-map.d.ts +48 -0
- package/dist/atoms/utils/icon-map.d.ts.map +1 -1
- package/dist/atoms/utils/index.d.ts +2 -0
- package/dist/atoms/utils/index.d.ts.map +1 -1
- package/dist/atoms/utils/ui-mapping.d.ts +9 -3
- package/dist/atoms/utils/ui-mapping.d.ts.map +1 -1
- package/dist/features/auth/components/ProtectedRoute.d.ts +3 -1
- package/dist/features/auth/components/ProtectedRoute.d.ts.map +1 -1
- package/dist/features/auth/hooks/useAuth.d.ts.map +1 -1
- package/dist/features/auth/providers/NoAuthProvider.d.ts +17 -0
- package/dist/features/auth/providers/NoAuthProvider.d.ts.map +1 -0
- package/dist/features/auth/providers/index.d.ts +1 -0
- package/dist/features/auth/providers/index.d.ts.map +1 -1
- package/dist/frontend-patterns.css +1 -4554
- package/dist/index.d.ts +12 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.js +8816 -18278
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +8813 -18274
- package/dist/index.js.map +1 -1
- package/dist/molecules/layout/AppHeader/AppHeader.d.ts +19 -3
- package/dist/molecules/layout/AppHeader/AppHeader.d.ts.map +1 -1
- package/dist/molecules/layout/AppHeader/index.d.ts +1 -1
- package/dist/molecules/layout/AppHeader/index.d.ts.map +1 -1
- package/dist/molecules/layout/AppLayout.d.ts +12 -1
- package/dist/molecules/layout/AppLayout.d.ts.map +1 -1
- package/dist/molecules/layout/BulkSelectionBar.d.ts +14 -2
- package/dist/molecules/layout/BulkSelectionBar.d.ts.map +1 -1
- package/dist/molecules/layout/FieldGrid/FieldGrid.d.ts +61 -0
- package/dist/molecules/layout/FieldGrid/FieldGrid.d.ts.map +1 -0
- package/dist/molecules/layout/FieldGrid/index.d.ts +2 -0
- package/dist/molecules/layout/FieldGrid/index.d.ts.map +1 -0
- package/dist/molecules/layout/ListToolbar/ListToolbar.d.ts +37 -0
- package/dist/molecules/layout/ListToolbar/ListToolbar.d.ts.map +1 -0
- package/dist/molecules/layout/ListToolbar/index.d.ts +2 -0
- package/dist/molecules/layout/ListToolbar/index.d.ts.map +1 -0
- package/dist/molecules/layout/PageTitle/PageTitle.d.ts +17 -0
- package/dist/molecules/layout/PageTitle/PageTitle.d.ts.map +1 -0
- package/dist/molecules/layout/PageTitle/index.d.ts +2 -0
- package/dist/molecules/layout/PageTitle/index.d.ts.map +1 -0
- package/dist/molecules/layout/index.d.ts +5 -2
- package/dist/molecules/layout/index.d.ts.map +1 -1
- package/dist/molecules/layout/navigation-context.d.ts.map +1 -1
- package/dist/sync/EntityStoreProvider.d.ts +35 -0
- package/dist/sync/EntityStoreProvider.d.ts.map +1 -0
- package/dist/sync/createEntityHooks.d.ts +29 -0
- package/dist/sync/createEntityHooks.d.ts.map +1 -0
- package/dist/sync/createStore.d.ts +65 -0
- package/dist/sync/createStore.d.ts.map +1 -0
- package/dist/sync/index.d.ts +6 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/types.d.ts +383 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/templates/ListPageTemplate.d.ts +21 -0
- package/dist/templates/ListPageTemplate.d.ts.map +1 -0
- package/dist/templates/admin/AdminCRUDTemplate.d.ts.map +1 -1
- package/dist/templates/factory.d.ts +20 -0
- package/dist/templates/factory.d.ts.map +1 -1
- package/dist/templates/index.d.ts +1 -0
- package/dist/templates/index.d.ts.map +1 -1
- package/package.json +11 -7
- package/cli/commands/generate-hooks.ts +0 -325
- package/cli/commands/init.ts +0 -33
- package/cli/commands/scaffold.ts +0 -224
- package/cli/index.ts +0 -122
- package/cli/src/codegen/openapi/__tests__/naming-utils.test.js +0 -367
- package/cli/src/codegen/openapi/client-generator.js +0 -727
- package/cli/src/codegen/openapi/confidence-scorer.js +0 -93
- package/cli/src/codegen/openapi/hook-config.js +0 -48
- package/cli/src/codegen/openapi/hook-generator.js +0 -763
- package/cli/src/codegen/openapi/naming-constants.js +0 -98
- package/cli/src/codegen/openapi/naming-utils.js +0 -149
- package/cli/src/codegen/openapi/parser.js +0 -274
- package/cli/src/codegen/openapi/type-generator.js +0 -329
- package/dist/codegen/openapi/bulk-types.d.ts +0 -142
- package/dist/codegen/openapi/bulk-types.d.ts.map +0 -1
|
@@ -1,727 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* API Client Generator
|
|
3
|
-
*
|
|
4
|
-
* Creates a type-safe API client with interceptors and error handling
|
|
5
|
-
* based on parsed OpenAPI specifications.
|
|
6
|
-
*
|
|
7
|
-
* Part of FRO-12: API Client Generator
|
|
8
|
-
*/
|
|
9
|
-
import { singularize, pluralize, isPlural } from './naming-utils.js';
|
|
10
|
-
import { AUTH_ACTIONS, HEALTH_ENDPOINTS, USER_PROFILE_ENDPOINTS, SINGLETON_RESOURCES, COLLECTION_ACTIONS } from './naming-constants.js';
|
|
11
|
-
|
|
12
|
-
export class APIClientGenerator {
|
|
13
|
-
options;
|
|
14
|
-
constructor(options = {}) {
|
|
15
|
-
this.options = {
|
|
16
|
-
clientType: options.clientType || 'axios',
|
|
17
|
-
baseUrl: options.baseUrl || '',
|
|
18
|
-
includeAuth: options.includeAuth !== false,
|
|
19
|
-
authType: options.authType || 'bearer',
|
|
20
|
-
timeout: options.timeout || 10000,
|
|
21
|
-
retries: options.retries || 3,
|
|
22
|
-
includeInterceptors: options.includeInterceptors !== false
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
generate(parsedAPI) {
|
|
26
|
-
return {
|
|
27
|
-
client: this.generateClientSetup(),
|
|
28
|
-
methods: this.generateApiMethods(parsedAPI.endpoints),
|
|
29
|
-
types: this.generateClientTypes(),
|
|
30
|
-
config: this.generateConfiguration(),
|
|
31
|
-
index: this.generateIndexFile()
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
generateClientSetup() {
|
|
35
|
-
if (this.options.clientType === 'axios') {
|
|
36
|
-
return this.generateAxiosClient();
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
return this.generateFetchClient();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
generateAxiosClient() {
|
|
43
|
-
return `/**
|
|
44
|
-
* Axios API Client
|
|
45
|
-
*
|
|
46
|
-
* Auto-generated API client with interceptors and error handling
|
|
47
|
-
*/
|
|
48
|
-
|
|
49
|
-
import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
|
|
50
|
-
import { APIClientConfig, RequestOptions, APIError } from './types'
|
|
51
|
-
|
|
52
|
-
export class APIClient {
|
|
53
|
-
private client: AxiosInstance
|
|
54
|
-
private config: APIClientConfig
|
|
55
|
-
|
|
56
|
-
constructor(config: APIClientConfig) {
|
|
57
|
-
this.config = config
|
|
58
|
-
this.client = this.createAxiosInstance()
|
|
59
|
-
this.setupInterceptors()
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
private createAxiosInstance(): AxiosInstance {
|
|
63
|
-
return axios.create({
|
|
64
|
-
baseURL: this.config.baseUrl,
|
|
65
|
-
timeout: this.config.timeout || ${this.options.timeout},
|
|
66
|
-
headers: {
|
|
67
|
-
'Content-Type': 'application/json',
|
|
68
|
-
...this.config.defaultHeaders
|
|
69
|
-
}
|
|
70
|
-
})
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
${this.options.includeInterceptors ? this.generateAxiosInterceptors() : ''}
|
|
74
|
-
|
|
75
|
-
private async makeRequest<T>(
|
|
76
|
-
method: string,
|
|
77
|
-
url: string,
|
|
78
|
-
options: RequestOptions = {}
|
|
79
|
-
): Promise<T> {
|
|
80
|
-
try {
|
|
81
|
-
const config: AxiosRequestConfig = {
|
|
82
|
-
method: method as any,
|
|
83
|
-
url,
|
|
84
|
-
...options.config
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (options.params) {
|
|
88
|
-
config.params = options.params
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (options.data) {
|
|
92
|
-
config.data = options.data
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (options.headers) {
|
|
96
|
-
config.headers = { ...config.headers, ...options.headers }
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
${this.options.includeAuth ? this.generateAuthInjection() : ''}
|
|
100
|
-
|
|
101
|
-
const response: AxiosResponse<T> = await this.client.request(config)
|
|
102
|
-
return response.data
|
|
103
|
-
} catch (error) {
|
|
104
|
-
throw this.handleError(error as AxiosError)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private handleError(error: AxiosError): APIError {
|
|
109
|
-
const apiError: APIError = {
|
|
110
|
-
message: error.message,
|
|
111
|
-
status: error.response?.status,
|
|
112
|
-
statusText: error.response?.statusText,
|
|
113
|
-
data: error.response?.data,
|
|
114
|
-
config: {
|
|
115
|
-
url: error.config?.url,
|
|
116
|
-
method: error.config?.method?.toUpperCase(),
|
|
117
|
-
headers: error.config?.headers
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (this.config.onError) {
|
|
122
|
-
this.config.onError(apiError)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return apiError
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
public get<T>(url: string, options?: RequestOptions): Promise<T> {
|
|
129
|
-
return this.makeRequest<T>('GET', url, options)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
public post<T>(url: string, data?: any, options?: RequestOptions): Promise<T> {
|
|
133
|
-
return this.makeRequest<T>('POST', url, { ...options, data })
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
public put<T>(url: string, data?: any, options?: RequestOptions): Promise<T> {
|
|
137
|
-
return this.makeRequest<T>('PUT', url, { ...options, data })
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
public patch<T>(url: string, data?: any, options?: RequestOptions): Promise<T> {
|
|
141
|
-
return this.makeRequest<T>('PATCH', url, { ...options, data })
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
public delete<T>(url: string, options?: RequestOptions): Promise<T> {
|
|
145
|
-
return this.makeRequest<T>('DELETE', url, options)
|
|
146
|
-
}
|
|
147
|
-
}`;
|
|
148
|
-
}
|
|
149
|
-
generateFetchClient() {
|
|
150
|
-
return `/**
|
|
151
|
-
* Fetch API Client
|
|
152
|
-
*
|
|
153
|
-
* Auto-generated API client using native fetch with error handling
|
|
154
|
-
*/
|
|
155
|
-
|
|
156
|
-
import { APIClientConfig, RequestOptions, APIError } from './types'
|
|
157
|
-
|
|
158
|
-
export class APIClient {
|
|
159
|
-
private config: APIClientConfig
|
|
160
|
-
|
|
161
|
-
constructor(config: APIClientConfig) {
|
|
162
|
-
this.config = config
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
private async makeRequest<T>(
|
|
166
|
-
method: string,
|
|
167
|
-
url: string,
|
|
168
|
-
options: RequestOptions = {}
|
|
169
|
-
): Promise<T> {
|
|
170
|
-
try {
|
|
171
|
-
const fullUrl = this.buildUrl(url, options.params)
|
|
172
|
-
|
|
173
|
-
const fetchOptions: RequestInit = {
|
|
174
|
-
method,
|
|
175
|
-
headers: {
|
|
176
|
-
'Content-Type': 'application/json',
|
|
177
|
-
...this.config.defaultHeaders,
|
|
178
|
-
...options.headers
|
|
179
|
-
},
|
|
180
|
-
signal: options.signal
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (options.data && method !== 'GET') {
|
|
184
|
-
fetchOptions.body = JSON.stringify(options.data)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
${this.options.includeAuth ? this.generateFetchAuthInjection() : ''}
|
|
188
|
-
|
|
189
|
-
const response = await fetch(fullUrl, fetchOptions)
|
|
190
|
-
|
|
191
|
-
if (!response.ok) {
|
|
192
|
-
throw await this.createErrorFromResponse(response)
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const contentType = response.headers.get('content-type')
|
|
196
|
-
if (contentType && contentType.includes('application/json')) {
|
|
197
|
-
return await response.json()
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return await response.text() as any
|
|
201
|
-
} catch (error) {
|
|
202
|
-
if (error instanceof APIError) {
|
|
203
|
-
throw error
|
|
204
|
-
}
|
|
205
|
-
throw this.createGenericError(error as Error)
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
private buildUrl(path: string, params?: Record<string, any>): string {
|
|
210
|
-
const url = new URL(path, this.config.baseUrl)
|
|
211
|
-
|
|
212
|
-
if (params) {
|
|
213
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
214
|
-
if (value !== undefined && value !== null) {
|
|
215
|
-
url.searchParams.append(key, String(value))
|
|
216
|
-
}
|
|
217
|
-
})
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return url.toString()
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
private async createErrorFromResponse(response: Response): Promise<APIError> {
|
|
224
|
-
let data: any
|
|
225
|
-
try {
|
|
226
|
-
data = await response.json()
|
|
227
|
-
} catch {
|
|
228
|
-
data = await response.text()
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const error: APIError = {
|
|
232
|
-
message: data?.message || response.statusText || 'Request failed',
|
|
233
|
-
status: response.status,
|
|
234
|
-
statusText: response.statusText,
|
|
235
|
-
data,
|
|
236
|
-
config: {
|
|
237
|
-
url: response.url,
|
|
238
|
-
method: 'Unknown'
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (this.config.onError) {
|
|
243
|
-
this.config.onError(error)
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return error
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
private createGenericError(error: Error): APIError {
|
|
250
|
-
const apiError: APIError = {
|
|
251
|
-
message: error.message,
|
|
252
|
-
config: {}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (this.config.onError) {
|
|
256
|
-
this.config.onError(apiError)
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return apiError
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
public get<T>(url: string, options?: RequestOptions): Promise<T> {
|
|
263
|
-
return this.makeRequest<T>('GET', url, options)
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
public post<T>(url: string, data?: any, options?: RequestOptions): Promise<T> {
|
|
267
|
-
return this.makeRequest<T>('POST', url, { ...options, data })
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
public put<T>(url: string, data?: any, options?: RequestOptions): Promise<T> {
|
|
271
|
-
return this.makeRequest<T>('PUT', url, { ...options, data })
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
public patch<T>(url: string, data?: any, options?: RequestOptions): Promise<T> {
|
|
275
|
-
return this.makeRequest<T>('PATCH', url, { ...options, data })
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
public delete<T>(url: string, options?: RequestOptions): Promise<T> {
|
|
279
|
-
return this.makeRequest<T>('DELETE', url, options)
|
|
280
|
-
}
|
|
281
|
-
}`;
|
|
282
|
-
}
|
|
283
|
-
generateAxiosInterceptors() {
|
|
284
|
-
return `
|
|
285
|
-
private setupInterceptors(): void {
|
|
286
|
-
// Request interceptor
|
|
287
|
-
this.client.interceptors.request.use(
|
|
288
|
-
(config) => {
|
|
289
|
-
if (this.config.onRequest) {
|
|
290
|
-
return this.config.onRequest(config) || config
|
|
291
|
-
}
|
|
292
|
-
return config
|
|
293
|
-
},
|
|
294
|
-
(error) => {
|
|
295
|
-
if (this.config.onRequestError) {
|
|
296
|
-
this.config.onRequestError(error)
|
|
297
|
-
}
|
|
298
|
-
return Promise.reject(error)
|
|
299
|
-
}
|
|
300
|
-
)
|
|
301
|
-
|
|
302
|
-
// Response interceptor
|
|
303
|
-
this.client.interceptors.response.use(
|
|
304
|
-
(response) => {
|
|
305
|
-
if (this.config.onResponse) {
|
|
306
|
-
return this.config.onResponse(response) || response
|
|
307
|
-
}
|
|
308
|
-
return response
|
|
309
|
-
},
|
|
310
|
-
async (error) => {
|
|
311
|
-
if (this.config.onResponseError) {
|
|
312
|
-
const result = await this.config.onResponseError(error)
|
|
313
|
-
if (result) {
|
|
314
|
-
return result
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Auto-retry logic
|
|
319
|
-
if (this.shouldRetry(error)) {
|
|
320
|
-
return this.retryRequest(error.config)
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
return Promise.reject(error)
|
|
324
|
-
}
|
|
325
|
-
)
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
private shouldRetry(error: AxiosError): boolean {
|
|
329
|
-
const retryCount = (error.config as any)?._retryCount || 0
|
|
330
|
-
const maxRetries = this.config.retries || ${this.options.retries}
|
|
331
|
-
|
|
332
|
-
return (
|
|
333
|
-
retryCount < maxRetries &&
|
|
334
|
-
(!error.response || error.response.status >= 500) &&
|
|
335
|
-
error.code !== 'ECONNABORTED'
|
|
336
|
-
)
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
private async retryRequest(config: any): Promise<any> {
|
|
340
|
-
config._retryCount = (config._retryCount || 0) + 1
|
|
341
|
-
|
|
342
|
-
// Exponential backoff
|
|
343
|
-
const delay = Math.pow(2, config._retryCount) * 1000
|
|
344
|
-
await new Promise(resolve => setTimeout(resolve, delay))
|
|
345
|
-
|
|
346
|
-
return this.client.request(config)
|
|
347
|
-
}`;
|
|
348
|
-
}
|
|
349
|
-
generateAuthInjection() {
|
|
350
|
-
switch (this.options.authType) {
|
|
351
|
-
case 'bearer':
|
|
352
|
-
return `
|
|
353
|
-
// Inject bearer token if available
|
|
354
|
-
if (this.config.getAuthToken) {
|
|
355
|
-
const token = await this.config.getAuthToken()
|
|
356
|
-
if (token) {
|
|
357
|
-
config.headers = {
|
|
358
|
-
...config.headers,
|
|
359
|
-
Authorization: \`Bearer \${token}\`
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}`;
|
|
363
|
-
case 'apiKey':
|
|
364
|
-
return `
|
|
365
|
-
// Inject API key if available
|
|
366
|
-
if (this.config.getApiKey) {
|
|
367
|
-
const apiKey = await this.config.getApiKey()
|
|
368
|
-
if (apiKey) {
|
|
369
|
-
if (this.config.apiKeyHeader) {
|
|
370
|
-
config.headers = {
|
|
371
|
-
...config.headers,
|
|
372
|
-
[this.config.apiKeyHeader]: apiKey
|
|
373
|
-
}
|
|
374
|
-
} else {
|
|
375
|
-
config.params = { ...config.params, api_key: apiKey }
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}`;
|
|
379
|
-
case 'basic':
|
|
380
|
-
return `
|
|
381
|
-
// Inject basic auth if available
|
|
382
|
-
if (this.config.getBasicAuth) {
|
|
383
|
-
const auth = await this.config.getBasicAuth()
|
|
384
|
-
if (auth) {
|
|
385
|
-
const encoded = btoa(\`\${auth.username}:\${auth.password}\`)
|
|
386
|
-
config.headers = {
|
|
387
|
-
...config.headers,
|
|
388
|
-
Authorization: \`Basic \${encoded}\`
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}`;
|
|
392
|
-
default:
|
|
393
|
-
return '';
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
generateFetchAuthInjection() {
|
|
397
|
-
switch (this.options.authType) {
|
|
398
|
-
case 'bearer':
|
|
399
|
-
return `
|
|
400
|
-
// Inject bearer token if available
|
|
401
|
-
if (this.config.getAuthToken) {
|
|
402
|
-
const token = await this.config.getAuthToken()
|
|
403
|
-
if (token) {
|
|
404
|
-
fetchOptions.headers = {
|
|
405
|
-
...fetchOptions.headers,
|
|
406
|
-
Authorization: \`Bearer \${token}\`
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}`;
|
|
410
|
-
case 'apiKey':
|
|
411
|
-
return `
|
|
412
|
-
// Inject API key if available
|
|
413
|
-
if (this.config.getApiKey) {
|
|
414
|
-
const apiKey = await this.config.getApiKey()
|
|
415
|
-
if (apiKey && this.config.apiKeyHeader) {
|
|
416
|
-
fetchOptions.headers = {
|
|
417
|
-
...fetchOptions.headers,
|
|
418
|
-
[this.config.apiKeyHeader]: apiKey
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}`;
|
|
422
|
-
default:
|
|
423
|
-
return '';
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
generateApiMethods(endpoints) {
|
|
427
|
-
const methods = [];
|
|
428
|
-
methods.push(this.generateFileHeader('API Methods'));
|
|
429
|
-
methods.push('');
|
|
430
|
-
methods.push('import { APIClient } from \'./client\'');
|
|
431
|
-
methods.push('import * as Types from \'./types\'');
|
|
432
|
-
methods.push('');
|
|
433
|
-
methods.push('export class APIService {');
|
|
434
|
-
methods.push(' constructor(private client: APIClient) {}');
|
|
435
|
-
methods.push('');
|
|
436
|
-
// Group endpoints by tags or path prefix
|
|
437
|
-
const groupedEndpoints = this.groupEndpoints(endpoints);
|
|
438
|
-
for (const [group, groupEndpoints] of Object.entries(groupedEndpoints)) {
|
|
439
|
-
methods.push(` // ${group} methods`);
|
|
440
|
-
for (const endpoint of groupEndpoints) {
|
|
441
|
-
const method = this.generateEndpointMethod(endpoint);
|
|
442
|
-
methods.push(method);
|
|
443
|
-
methods.push('');
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
methods.push('}');
|
|
447
|
-
return methods.join('\n');
|
|
448
|
-
}
|
|
449
|
-
generateEndpointMethod(endpoint) {
|
|
450
|
-
const methodName = this.getMethodName(endpoint);
|
|
451
|
-
const pathWithParams = this.generatePathWithParams(endpoint);
|
|
452
|
-
const httpMethod = endpoint.method.toLowerCase();
|
|
453
|
-
const lines = [];
|
|
454
|
-
// Generate JSDoc
|
|
455
|
-
lines.push(' /**');
|
|
456
|
-
if (endpoint.summary) {
|
|
457
|
-
lines.push(` * ${endpoint.summary}`);
|
|
458
|
-
}
|
|
459
|
-
if (endpoint.description) {
|
|
460
|
-
lines.push(` * ${endpoint.description}`);
|
|
461
|
-
}
|
|
462
|
-
lines.push(` * @param options Request options`);
|
|
463
|
-
lines.push(' */');
|
|
464
|
-
// Generate method signature
|
|
465
|
-
const hasPathParams = endpoint.parameters.some(p => p.in === 'path');
|
|
466
|
-
const hasQueryParams = endpoint.parameters.some(p => p.in === 'query');
|
|
467
|
-
const hasBody = endpoint.requestBody !== undefined;
|
|
468
|
-
let params = 'options: RequestOptions = {}';
|
|
469
|
-
if (hasPathParams) {
|
|
470
|
-
const pathParams = endpoint.parameters
|
|
471
|
-
.filter(p => p.in === 'path')
|
|
472
|
-
.map(p => `${p.name}: ${this.getParameterType(p)}`)
|
|
473
|
-
.join(', ');
|
|
474
|
-
params = `${pathParams}, ${params}`;
|
|
475
|
-
}
|
|
476
|
-
const responseType = this.getResponseType(endpoint);
|
|
477
|
-
lines.push(` async ${methodName}(${params}): Promise<${responseType}> {`);
|
|
478
|
-
// Generate method body
|
|
479
|
-
const urlConstruction = hasPathParams ?
|
|
480
|
-
`const url = \`${pathWithParams}\`` :
|
|
481
|
-
`const url = '${endpoint.path}'`;
|
|
482
|
-
lines.push(` ${urlConstruction}`);
|
|
483
|
-
if (hasQueryParams) {
|
|
484
|
-
lines.push(' const queryParams = {');
|
|
485
|
-
endpoint.parameters
|
|
486
|
-
.filter(p => p.in === 'query')
|
|
487
|
-
.forEach(p => {
|
|
488
|
-
lines.push(` ${p.name}: options.${p.name},`);
|
|
489
|
-
});
|
|
490
|
-
lines.push(' }');
|
|
491
|
-
lines.push(' options.params = { ...queryParams, ...options.params }');
|
|
492
|
-
}
|
|
493
|
-
if (hasBody) {
|
|
494
|
-
lines.push(` return this.client.${httpMethod}<${responseType}>(url, options.data, options)`);
|
|
495
|
-
}
|
|
496
|
-
else {
|
|
497
|
-
lines.push(` return this.client.${httpMethod}<${responseType}>(url, options)`);
|
|
498
|
-
}
|
|
499
|
-
lines.push(' }');
|
|
500
|
-
return lines.join('\n');
|
|
501
|
-
}
|
|
502
|
-
generateClientTypes() {
|
|
503
|
-
const authTypes = this.generateAuthTypes();
|
|
504
|
-
return `/**
|
|
505
|
-
* API Client Types
|
|
506
|
-
*
|
|
507
|
-
* Configuration and utility types for the generated API client
|
|
508
|
-
*/
|
|
509
|
-
|
|
510
|
-
${this.options.clientType === 'axios' ? "import { AxiosRequestConfig, AxiosResponse } from 'axios'" : ''}
|
|
511
|
-
|
|
512
|
-
export interface APIClientConfig {
|
|
513
|
-
baseUrl: string
|
|
514
|
-
timeout?: number
|
|
515
|
-
defaultHeaders?: Record<string, string>
|
|
516
|
-
retries?: number
|
|
517
|
-
${this.options.includeAuth ? authTypes : ''}
|
|
518
|
-
onRequest?: (config: any) => any
|
|
519
|
-
onRequestError?: (error: any) => void
|
|
520
|
-
onResponse?: (response: any) => any
|
|
521
|
-
onResponseError?: (error: any) => Promise<any> | any
|
|
522
|
-
onError?: (error: APIError) => void
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
export interface RequestOptions {
|
|
526
|
-
params?: Record<string, any>
|
|
527
|
-
data?: any
|
|
528
|
-
headers?: Record<string, string>
|
|
529
|
-
config?: any
|
|
530
|
-
signal?: AbortSignal
|
|
531
|
-
[key: string]: any
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
export interface APIError {
|
|
535
|
-
message: string
|
|
536
|
-
status?: number
|
|
537
|
-
statusText?: string
|
|
538
|
-
data?: any
|
|
539
|
-
config: {
|
|
540
|
-
url?: string
|
|
541
|
-
method?: string
|
|
542
|
-
headers?: any
|
|
543
|
-
}
|
|
544
|
-
}`;
|
|
545
|
-
}
|
|
546
|
-
generateAuthTypes() {
|
|
547
|
-
switch (this.options.authType) {
|
|
548
|
-
case 'bearer':
|
|
549
|
-
return 'getAuthToken?: () => Promise<string | null> | string | null';
|
|
550
|
-
case 'apiKey':
|
|
551
|
-
return `getApiKey?: () => Promise<string | null> | string | null
|
|
552
|
-
apiKeyHeader?: string`;
|
|
553
|
-
case 'basic':
|
|
554
|
-
return 'getBasicAuth?: () => Promise<{ username: string; password: string } | null> | { username: string; password: string } | null';
|
|
555
|
-
default:
|
|
556
|
-
return '';
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
generateConfiguration() {
|
|
560
|
-
return `/**
|
|
561
|
-
* API Client Configuration
|
|
562
|
-
*
|
|
563
|
-
* Default configuration and factory functions
|
|
564
|
-
*/
|
|
565
|
-
|
|
566
|
-
import { APIClient } from './client'
|
|
567
|
-
import { APIService } from './methods'
|
|
568
|
-
import { APIClientConfig } from './types'
|
|
569
|
-
|
|
570
|
-
export function createAPIClient(config: APIClientConfig): APIService {
|
|
571
|
-
const client = new APIClient(config)
|
|
572
|
-
return new APIService(client)
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
export const defaultConfig: Partial<APIClientConfig> = {
|
|
576
|
-
timeout: ${this.options.timeout},
|
|
577
|
-
retries: ${this.options.retries},
|
|
578
|
-
defaultHeaders: {
|
|
579
|
-
'Content-Type': 'application/json'
|
|
580
|
-
}
|
|
581
|
-
}`;
|
|
582
|
-
}
|
|
583
|
-
generateIndexFile() {
|
|
584
|
-
return `/**
|
|
585
|
-
* Generated API Client
|
|
586
|
-
*
|
|
587
|
-
* Auto-generated from OpenAPI specification
|
|
588
|
-
*/
|
|
589
|
-
|
|
590
|
-
export * from './client'
|
|
591
|
-
export * from './methods'
|
|
592
|
-
export * from './types'
|
|
593
|
-
export * from './config'
|
|
594
|
-
export { createAPIClient, defaultConfig } from './config'`;
|
|
595
|
-
}
|
|
596
|
-
generateFileHeader(title) {
|
|
597
|
-
return `/**
|
|
598
|
-
* ${title}
|
|
599
|
-
*
|
|
600
|
-
* Auto-generated from OpenAPI specification
|
|
601
|
-
* Do not edit manually
|
|
602
|
-
*/`;
|
|
603
|
-
}
|
|
604
|
-
getMethodName(endpoint) {
|
|
605
|
-
// Use semantic naming instead of raw operationId
|
|
606
|
-
return this.generateSemanticMethodName(endpoint);
|
|
607
|
-
}
|
|
608
|
-
generateSemanticMethodName(endpoint) {
|
|
609
|
-
// Extract resource from path
|
|
610
|
-
const pathSegments = endpoint.path.split('/').filter(s => s && !s.startsWith('{') && !['api', 'v1', 'v2'].includes(s));
|
|
611
|
-
const urlSegments = endpoint.path.split('/');
|
|
612
|
-
const hasPathParam = endpoint.path.includes('{');
|
|
613
|
-
const lastSegment = pathSegments[pathSegments.length - 1]?.replace(/-/g, '_') || 'resource';
|
|
614
|
-
const prevSegment = pathSegments[pathSegments.length - 2]?.replace(/-/g, '_');
|
|
615
|
-
|
|
616
|
-
// Auth endpoints - use action name directly
|
|
617
|
-
if (AUTH_ACTIONS.includes(lastSegment.toLowerCase())) {
|
|
618
|
-
return this.camelCase(lastSegment);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
// Health/status singletons
|
|
622
|
-
if (HEALTH_ENDPOINTS.includes(lastSegment.toLowerCase())) {
|
|
623
|
-
return this.camelCase(lastSegment);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// User profile singletons
|
|
627
|
-
if (USER_PROFILE_ENDPOINTS.includes(lastSegment.toLowerCase())) {
|
|
628
|
-
return this.camelCase(`get_current_user`);
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
// Metadata endpoints - include parent resource to avoid collision
|
|
632
|
-
if (lastSegment.toLowerCase() === 'metadata' && prevSegment) {
|
|
633
|
-
return hasPathParam
|
|
634
|
-
? this.camelCase(`get_${singularize(prevSegment)}_metadata`)
|
|
635
|
-
: this.camelCase(`get_${prevSegment}_metadata`);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// Collection actions (e.g., /activities/search)
|
|
639
|
-
if (COLLECTION_ACTIONS.includes(lastSegment.toLowerCase()) && prevSegment) {
|
|
640
|
-
return this.camelCase(`${lastSegment}_${prevSegment}`);
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// Determine resource name - for nested paths, include context
|
|
644
|
-
let resource = lastSegment;
|
|
645
|
-
if (pathSegments.length > 2 && hasPathParam) {
|
|
646
|
-
// e.g., /accounts/{id}/stages/allowed → get_account_allowed_stages
|
|
647
|
-
const parentResource = pathSegments.find((s, i) => {
|
|
648
|
-
const nextSeg = urlSegments[urlSegments.findIndex(u => u === s) + 1];
|
|
649
|
-
return nextSeg?.startsWith('{');
|
|
650
|
-
});
|
|
651
|
-
if (parentResource && parentResource !== lastSegment) {
|
|
652
|
-
resource = `${singularize(parentResource)}_${lastSegment}`;
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
// Generate name based on HTTP method
|
|
657
|
-
switch (endpoint.method) {
|
|
658
|
-
case 'get':
|
|
659
|
-
return hasPathParam
|
|
660
|
-
? this.camelCase(`get_${singularize(resource)}`)
|
|
661
|
-
: this.camelCase(`list_${resource}`);
|
|
662
|
-
case 'post':
|
|
663
|
-
// Check for custom actions like /accounts/{id}/stage
|
|
664
|
-
const lastUrl = urlSegments[urlSegments.length - 1];
|
|
665
|
-
const prevUrl = urlSegments[urlSegments.length - 2];
|
|
666
|
-
if (!lastUrl.startsWith('{') && prevUrl?.startsWith('{')) {
|
|
667
|
-
return this.camelCase(`${lastUrl}_${singularize(prevSegment || resource)}`);
|
|
668
|
-
}
|
|
669
|
-
return this.camelCase(`create_${singularize(resource)}`);
|
|
670
|
-
case 'put':
|
|
671
|
-
case 'patch':
|
|
672
|
-
return this.camelCase(`update_${singularize(resource)}`);
|
|
673
|
-
case 'delete':
|
|
674
|
-
return this.camelCase(`delete_${singularize(resource)}`);
|
|
675
|
-
default:
|
|
676
|
-
return this.camelCase(`${endpoint.method}_${resource}`);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
generatePathWithParams(endpoint) {
|
|
680
|
-
return endpoint.path.replace(/{([^}]+)}/g, '${$1}');
|
|
681
|
-
}
|
|
682
|
-
getParameterType(param) {
|
|
683
|
-
switch (param.schema.type) {
|
|
684
|
-
case 'string': return 'string';
|
|
685
|
-
case 'number':
|
|
686
|
-
case 'integer': return 'number';
|
|
687
|
-
case 'boolean': return 'boolean';
|
|
688
|
-
default: return 'any';
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
getResponseType(endpoint) {
|
|
692
|
-
// Find success response (2xx)
|
|
693
|
-
const successResponse = endpoint.responses.find(r => r.statusCode.startsWith('2') || r.statusCode === 'default');
|
|
694
|
-
if (!successResponse?.content) {
|
|
695
|
-
return 'void';
|
|
696
|
-
}
|
|
697
|
-
const jsonContent = successResponse.content['application/json'];
|
|
698
|
-
if (!jsonContent) {
|
|
699
|
-
return 'any';
|
|
700
|
-
}
|
|
701
|
-
// This would need to be enhanced to generate proper type references
|
|
702
|
-
return 'any';
|
|
703
|
-
}
|
|
704
|
-
groupEndpoints(endpoints) {
|
|
705
|
-
const groups = {};
|
|
706
|
-
for (const endpoint of endpoints) {
|
|
707
|
-
const group = endpoint.tags?.[0] || 'default';
|
|
708
|
-
if (!groups[group]) {
|
|
709
|
-
groups[group] = [];
|
|
710
|
-
}
|
|
711
|
-
groups[group].push(endpoint);
|
|
712
|
-
}
|
|
713
|
-
return groups;
|
|
714
|
-
}
|
|
715
|
-
camelCase(str) {
|
|
716
|
-
return str.replace(/[-_](.)/g, (_, char) => char.toUpperCase())
|
|
717
|
-
.replace(/^./, char => char.toLowerCase());
|
|
718
|
-
}
|
|
719
|
-
capitalize(str) {
|
|
720
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
// Factory function
|
|
724
|
-
export function generateAPIClient(parsedAPI, options) {
|
|
725
|
-
const generator = new APIClientGenerator(options);
|
|
726
|
-
return generator.generate(parsedAPI);
|
|
727
|
-
}
|