@pattern-stack/frontend-patterns 0.2.0-alpha.1 → 0.2.0-alpha.11

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 (165) hide show
  1. package/dist/atoms/components/core/Badge/Badge.d.ts +1 -1
  2. package/dist/atoms/components/data/DataTable/ColumnFilterDropdown.d.ts +32 -0
  3. package/dist/atoms/components/data/DataTable/ColumnFilterDropdown.d.ts.map +1 -0
  4. package/dist/atoms/components/data/DataTable/ColumnVisibilityToggle.d.ts +32 -0
  5. package/dist/atoms/components/data/DataTable/ColumnVisibilityToggle.d.ts.map +1 -0
  6. package/dist/atoms/components/data/DataTable/DataTable.d.ts +5 -2
  7. package/dist/atoms/components/data/DataTable/DataTable.d.ts.map +1 -1
  8. package/dist/atoms/components/data/DataTable/DataTable.expansion.d.ts +91 -0
  9. package/dist/atoms/components/data/DataTable/DataTable.expansion.d.ts.map +1 -0
  10. package/dist/atoms/components/data/DataTable/DataTable.filters.d.ts +271 -0
  11. package/dist/atoms/components/data/DataTable/DataTable.filters.d.ts.map +1 -0
  12. package/dist/atoms/components/data/DataTable/DataTable.types.d.ts +155 -5
  13. package/dist/atoms/components/data/DataTable/DataTable.types.d.ts.map +1 -1
  14. package/dist/atoms/components/data/DataTable/ExpandButton.d.ts +37 -0
  15. package/dist/atoms/components/data/DataTable/ExpandButton.d.ts.map +1 -0
  16. package/dist/atoms/components/data/DataTable/FilterPill.d.ts +25 -0
  17. package/dist/atoms/components/data/DataTable/FilterPill.d.ts.map +1 -0
  18. package/dist/atoms/components/data/DataTable/QuickFilterBar.d.ts +35 -0
  19. package/dist/atoms/components/data/DataTable/QuickFilterBar.d.ts.map +1 -0
  20. package/dist/atoms/components/data/DataTable/filters/BooleanFilterEditor.d.ts +10 -0
  21. package/dist/atoms/components/data/DataTable/filters/BooleanFilterEditor.d.ts.map +1 -0
  22. package/dist/atoms/components/data/DataTable/filters/DateFilterEditor.d.ts +11 -0
  23. package/dist/atoms/components/data/DataTable/filters/DateFilterEditor.d.ts.map +1 -0
  24. package/dist/atoms/components/data/DataTable/filters/MultiSelectFilterEditor.d.ts +10 -0
  25. package/dist/atoms/components/data/DataTable/filters/MultiSelectFilterEditor.d.ts.map +1 -0
  26. package/dist/atoms/components/data/DataTable/filters/NumberFilterEditor.d.ts +10 -0
  27. package/dist/atoms/components/data/DataTable/filters/NumberFilterEditor.d.ts.map +1 -0
  28. package/dist/atoms/components/data/DataTable/filters/SelectFilterEditor.d.ts +10 -0
  29. package/dist/atoms/components/data/DataTable/filters/SelectFilterEditor.d.ts.map +1 -0
  30. package/dist/atoms/components/data/DataTable/filters/TextFilterEditor.d.ts +10 -0
  31. package/dist/atoms/components/data/DataTable/filters/TextFilterEditor.d.ts.map +1 -0
  32. package/dist/atoms/components/data/DataTable/filters/index.d.ts +14 -0
  33. package/dist/atoms/components/data/DataTable/filters/index.d.ts.map +1 -0
  34. package/dist/atoms/components/data/DataTable/index.d.ts +9 -0
  35. package/dist/atoms/components/data/DataTable/index.d.ts.map +1 -1
  36. package/dist/atoms/components/data/ProgressBar/ProgressBar.d.ts +1 -1
  37. package/dist/atoms/components/data/ProgressBar/ProgressBar.d.ts.map +1 -1
  38. package/dist/atoms/components/data/index.d.ts +3 -2
  39. package/dist/atoms/components/data/index.d.ts.map +1 -1
  40. package/dist/atoms/composed/ConnectionStatus/ConnectionStatus.d.ts +16 -0
  41. package/dist/atoms/composed/ConnectionStatus/ConnectionStatus.d.ts.map +1 -0
  42. package/dist/atoms/composed/ConnectionStatus/index.d.ts +3 -0
  43. package/dist/atoms/composed/ConnectionStatus/index.d.ts.map +1 -0
  44. package/dist/atoms/hooks/index.d.ts +9 -0
  45. package/dist/atoms/hooks/index.d.ts.map +1 -1
  46. package/dist/atoms/hooks/useAdaptiveTable.d.ts +49 -0
  47. package/dist/atoms/hooks/useAdaptiveTable.d.ts.map +1 -0
  48. package/dist/atoms/hooks/useApi.d.ts +1 -1
  49. package/dist/atoms/hooks/useApi.d.ts.map +1 -1
  50. package/dist/atoms/hooks/useColumnVisibility.d.ts +75 -0
  51. package/dist/atoms/hooks/useColumnVisibility.d.ts.map +1 -0
  52. package/dist/atoms/hooks/useEntityData.d.ts +36 -0
  53. package/dist/atoms/hooks/useEntityData.d.ts.map +1 -0
  54. package/dist/atoms/hooks/useEntityDetail.d.ts +43 -0
  55. package/dist/atoms/hooks/useEntityDetail.d.ts.map +1 -0
  56. package/dist/atoms/hooks/useExpandedRows.d.ts +66 -0
  57. package/dist/atoms/hooks/useExpandedRows.d.ts.map +1 -0
  58. package/dist/atoms/hooks/useFieldMetadata.d.ts +18 -0
  59. package/dist/atoms/hooks/useFieldMetadata.d.ts.map +1 -0
  60. package/dist/atoms/hooks/useOnlineStatus.d.ts +16 -0
  61. package/dist/atoms/hooks/useOnlineStatus.d.ts.map +1 -0
  62. package/dist/atoms/hooks/useResponsiveTable.d.ts +123 -0
  63. package/dist/atoms/hooks/useResponsiveTable.d.ts.map +1 -0
  64. package/dist/atoms/hooks/useTableFilters.d.ts +92 -0
  65. package/dist/atoms/hooks/useTableFilters.d.ts.map +1 -0
  66. package/dist/atoms/index.d.ts +1 -0
  67. package/dist/atoms/index.d.ts.map +1 -1
  68. package/dist/atoms/primitives/sheet.d.ts +23 -0
  69. package/dist/atoms/primitives/sheet.d.ts.map +1 -0
  70. package/dist/atoms/primitives/table.d.ts.map +1 -1
  71. package/dist/atoms/services/api/client.d.ts +12 -2
  72. package/dist/atoms/services/api/client.d.ts.map +1 -1
  73. package/dist/atoms/services/auth-service.d.ts +15 -0
  74. package/dist/atoms/services/auth-service.d.ts.map +1 -1
  75. package/dist/atoms/services/index.d.ts +2 -2
  76. package/dist/atoms/services/index.d.ts.map +1 -1
  77. package/dist/atoms/shared/config/table-config.d.ts +79 -0
  78. package/dist/atoms/shared/config/table-config.d.ts.map +1 -0
  79. package/dist/atoms/shared/index.d.ts +1 -0
  80. package/dist/atoms/shared/index.d.ts.map +1 -1
  81. package/dist/atoms/types/auth.d.ts +95 -2
  82. package/dist/atoms/types/auth.d.ts.map +1 -1
  83. package/dist/atoms/types/index.d.ts +1 -0
  84. package/dist/atoms/types/index.d.ts.map +1 -1
  85. package/dist/atoms/types/navigation.d.ts +1 -1
  86. package/dist/atoms/types/navigation.d.ts.map +1 -1
  87. package/dist/atoms/types/ui-config.d.ts +46 -11
  88. package/dist/atoms/types/ui-config.d.ts.map +1 -1
  89. package/dist/atoms/types/ui-metadata.d.ts +103 -0
  90. package/dist/atoms/types/ui-metadata.d.ts.map +1 -0
  91. package/dist/atoms/utils/entity-card-mapping.d.ts +105 -0
  92. package/dist/atoms/utils/entity-card-mapping.d.ts.map +1 -0
  93. package/dist/atoms/utils/field-detection.d.ts +2 -2
  94. package/dist/atoms/utils/field-detection.d.ts.map +1 -1
  95. package/dist/atoms/utils/icon-map.d.ts +48 -0
  96. package/dist/atoms/utils/icon-map.d.ts.map +1 -1
  97. package/dist/atoms/utils/index.d.ts +2 -0
  98. package/dist/atoms/utils/index.d.ts.map +1 -1
  99. package/dist/atoms/utils/ui-mapping.d.ts +9 -3
  100. package/dist/atoms/utils/ui-mapping.d.ts.map +1 -1
  101. package/dist/features/auth/components/ProtectedRoute.d.ts +3 -1
  102. package/dist/features/auth/components/ProtectedRoute.d.ts.map +1 -1
  103. package/dist/features/auth/hooks/useAuth.d.ts.map +1 -1
  104. package/dist/features/auth/providers/NoAuthProvider.d.ts +17 -0
  105. package/dist/features/auth/providers/NoAuthProvider.d.ts.map +1 -0
  106. package/dist/features/auth/providers/index.d.ts +1 -0
  107. package/dist/features/auth/providers/index.d.ts.map +1 -1
  108. package/dist/frontend-patterns.css +1 -4554
  109. package/dist/index.d.ts +12 -4
  110. package/dist/index.d.ts.map +1 -1
  111. package/dist/index.es.js +8793 -18275
  112. package/dist/index.es.js.map +1 -1
  113. package/dist/index.js +8790 -18271
  114. package/dist/index.js.map +1 -1
  115. package/dist/molecules/layout/AppHeader/AppHeader.d.ts.map +1 -1
  116. package/dist/molecules/layout/BulkSelectionBar.d.ts +14 -2
  117. package/dist/molecules/layout/BulkSelectionBar.d.ts.map +1 -1
  118. package/dist/molecules/layout/FieldGrid/FieldGrid.d.ts +61 -0
  119. package/dist/molecules/layout/FieldGrid/FieldGrid.d.ts.map +1 -0
  120. package/dist/molecules/layout/FieldGrid/index.d.ts +2 -0
  121. package/dist/molecules/layout/FieldGrid/index.d.ts.map +1 -0
  122. package/dist/molecules/layout/ListToolbar/ListToolbar.d.ts +37 -0
  123. package/dist/molecules/layout/ListToolbar/ListToolbar.d.ts.map +1 -0
  124. package/dist/molecules/layout/ListToolbar/index.d.ts +2 -0
  125. package/dist/molecules/layout/ListToolbar/index.d.ts.map +1 -0
  126. package/dist/molecules/layout/PageTitle/PageTitle.d.ts +17 -0
  127. package/dist/molecules/layout/PageTitle/PageTitle.d.ts.map +1 -0
  128. package/dist/molecules/layout/PageTitle/index.d.ts +2 -0
  129. package/dist/molecules/layout/PageTitle/index.d.ts.map +1 -0
  130. package/dist/molecules/layout/index.d.ts +3 -0
  131. package/dist/molecules/layout/index.d.ts.map +1 -1
  132. package/dist/molecules/layout/navigation-context.d.ts.map +1 -1
  133. package/dist/sync/EntityStoreProvider.d.ts +35 -0
  134. package/dist/sync/EntityStoreProvider.d.ts.map +1 -0
  135. package/dist/sync/createEntityHooks.d.ts +29 -0
  136. package/dist/sync/createEntityHooks.d.ts.map +1 -0
  137. package/dist/sync/createStore.d.ts +65 -0
  138. package/dist/sync/createStore.d.ts.map +1 -0
  139. package/dist/sync/index.d.ts +6 -0
  140. package/dist/sync/index.d.ts.map +1 -0
  141. package/dist/sync/types.d.ts +383 -0
  142. package/dist/sync/types.d.ts.map +1 -0
  143. package/dist/templates/ListPageTemplate.d.ts +21 -0
  144. package/dist/templates/ListPageTemplate.d.ts.map +1 -0
  145. package/dist/templates/admin/AdminCRUDTemplate.d.ts.map +1 -1
  146. package/dist/templates/factory.d.ts +11 -0
  147. package/dist/templates/factory.d.ts.map +1 -1
  148. package/dist/templates/index.d.ts +1 -0
  149. package/dist/templates/index.d.ts.map +1 -1
  150. package/package.json +11 -7
  151. package/cli/commands/generate-hooks.ts +0 -325
  152. package/cli/commands/init.ts +0 -33
  153. package/cli/commands/scaffold.ts +0 -224
  154. package/cli/index.ts +0 -122
  155. package/cli/src/codegen/openapi/__tests__/naming-utils.test.js +0 -367
  156. package/cli/src/codegen/openapi/client-generator.js +0 -727
  157. package/cli/src/codegen/openapi/confidence-scorer.js +0 -93
  158. package/cli/src/codegen/openapi/hook-config.js +0 -48
  159. package/cli/src/codegen/openapi/hook-generator.js +0 -763
  160. package/cli/src/codegen/openapi/naming-constants.js +0 -98
  161. package/cli/src/codegen/openapi/naming-utils.js +0 -149
  162. package/cli/src/codegen/openapi/parser.js +0 -274
  163. package/cli/src/codegen/openapi/type-generator.js +0 -329
  164. package/dist/codegen/openapi/bulk-types.d.ts +0 -142
  165. 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
- }