@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,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Naming Constants
|
|
3
|
-
*
|
|
4
|
-
* Shared constants for magic strings used across hook and client generators.
|
|
5
|
-
* These constants help identify special endpoints that get unique naming treatment.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Authentication action endpoints
|
|
10
|
-
* These endpoints are named directly without CRUD prefixes (e.g., 'login' not 'createLogin')
|
|
11
|
-
*/
|
|
12
|
-
export const AUTH_ACTIONS = [
|
|
13
|
-
'login',
|
|
14
|
-
'logout',
|
|
15
|
-
'register',
|
|
16
|
-
'signup',
|
|
17
|
-
'signin',
|
|
18
|
-
'refresh'
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Health/status check endpoints
|
|
23
|
-
* These are singleton endpoints that return system status
|
|
24
|
-
*/
|
|
25
|
-
export const HEALTH_ENDPOINTS = [
|
|
26
|
-
'health',
|
|
27
|
-
'ready',
|
|
28
|
-
'status',
|
|
29
|
-
'info',
|
|
30
|
-
'version'
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* User profile endpoints
|
|
35
|
-
* These endpoints reference the current/authenticated user
|
|
36
|
-
*/
|
|
37
|
-
export const USER_PROFILE_ENDPOINTS = [
|
|
38
|
-
'me',
|
|
39
|
-
'self',
|
|
40
|
-
'profile',
|
|
41
|
-
'current'
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Singleton resources
|
|
46
|
-
* Resources that return a single item, not a collection
|
|
47
|
-
* Includes all auth, health, and user profile endpoints plus common singleton patterns
|
|
48
|
-
*/
|
|
49
|
-
export const SINGLETON_RESOURCES = [
|
|
50
|
-
'me',
|
|
51
|
-
'self',
|
|
52
|
-
'current',
|
|
53
|
-
'profile',
|
|
54
|
-
'health',
|
|
55
|
-
'ready',
|
|
56
|
-
'status',
|
|
57
|
-
'info',
|
|
58
|
-
'version',
|
|
59
|
-
'metadata',
|
|
60
|
-
'config',
|
|
61
|
-
'settings',
|
|
62
|
-
'preferences',
|
|
63
|
-
'summary',
|
|
64
|
-
'stats',
|
|
65
|
-
'statistics',
|
|
66
|
-
'dashboard',
|
|
67
|
-
'schema',
|
|
68
|
-
'spec',
|
|
69
|
-
'openapi'
|
|
70
|
-
];
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Collection action verbs
|
|
74
|
-
* Actions that operate on collections (e.g., /activities/search)
|
|
75
|
-
*/
|
|
76
|
-
export const COLLECTION_ACTIONS = [
|
|
77
|
-
'search',
|
|
78
|
-
'export',
|
|
79
|
-
'import',
|
|
80
|
-
'bulk',
|
|
81
|
-
'batch',
|
|
82
|
-
'count',
|
|
83
|
-
'stats',
|
|
84
|
-
'validate'
|
|
85
|
-
];
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Special actions - combination of auth, health, and user profile endpoints
|
|
89
|
-
* These endpoints get special naming treatment and keep their action verb
|
|
90
|
-
*/
|
|
91
|
-
export const SPECIAL_ACTIONS = [
|
|
92
|
-
...AUTH_ACTIONS,
|
|
93
|
-
...HEALTH_ENDPOINTS,
|
|
94
|
-
...USER_PROFILE_ENDPOINTS,
|
|
95
|
-
'check',
|
|
96
|
-
'verify',
|
|
97
|
-
'validate'
|
|
98
|
-
];
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared naming utilities for OpenAPI code generation
|
|
3
|
-
* Handles singular/plural conversions with proper edge case handling
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Map of irregular plural forms
|
|
8
|
-
*/
|
|
9
|
-
export const IRREGULAR_PLURALS = {
|
|
10
|
-
person: 'people',
|
|
11
|
-
child: 'children',
|
|
12
|
-
data: 'data', // already plural
|
|
13
|
-
media: 'media', // already plural
|
|
14
|
-
metadata: 'metadata', // already plural
|
|
15
|
-
people: 'people', // already plural
|
|
16
|
-
children: 'children', // already plural
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Map of irregular singular forms (reverse lookup)
|
|
21
|
-
*/
|
|
22
|
-
const IRREGULAR_SINGULARS = {
|
|
23
|
-
people: 'person',
|
|
24
|
-
children: 'child',
|
|
25
|
-
data: 'data', // already singular
|
|
26
|
-
media: 'media', // already singular
|
|
27
|
-
metadata: 'metadata', // already singular
|
|
28
|
-
person: 'person', // already singular
|
|
29
|
-
child: 'child', // already singular
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Words that naturally end in 's' but are singular
|
|
34
|
-
*/
|
|
35
|
-
const SINGULAR_EXCEPTIONS = [
|
|
36
|
-
'status',
|
|
37
|
-
'class',
|
|
38
|
-
'address',
|
|
39
|
-
'access',
|
|
40
|
-
'success',
|
|
41
|
-
'process',
|
|
42
|
-
'bus',
|
|
43
|
-
'focus',
|
|
44
|
-
'virus',
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Check if a word is already plural
|
|
49
|
-
* @param {string} word - The word to check
|
|
50
|
-
* @returns {boolean} - True if the word is plural
|
|
51
|
-
*/
|
|
52
|
-
export function isPlural(word) {
|
|
53
|
-
// Check irregular plurals first
|
|
54
|
-
if (IRREGULAR_PLURALS[word.toLowerCase()]) {
|
|
55
|
-
return IRREGULAR_PLURALS[word.toLowerCase()] === word.toLowerCase();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Check singular exceptions - these end in 's' but are not plural
|
|
59
|
-
if (SINGULAR_EXCEPTIONS.includes(word.toLowerCase())) {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Check common plural endings
|
|
64
|
-
if (word.endsWith('ies') || word.endsWith('es') || word.endsWith('s')) {
|
|
65
|
-
return true;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Convert plural word to singular
|
|
73
|
-
* @param {string} word - The word to singularize
|
|
74
|
-
* @returns {string} - The singular form of the word
|
|
75
|
-
*/
|
|
76
|
-
export function singularize(word) {
|
|
77
|
-
// Handle empty or short words
|
|
78
|
-
if (!word || word.length <= 1) {
|
|
79
|
-
return word;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Check irregular singulars first
|
|
83
|
-
const lowerWord = word.toLowerCase();
|
|
84
|
-
if (IRREGULAR_SINGULARS[lowerWord]) {
|
|
85
|
-
return IRREGULAR_SINGULARS[lowerWord];
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Already singular (check exceptions)
|
|
89
|
-
if (SINGULAR_EXCEPTIONS.includes(lowerWord)) {
|
|
90
|
-
return word;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// -ies → -y (activities → activity)
|
|
94
|
-
if (word.endsWith('ies')) {
|
|
95
|
-
return word.slice(0, -3) + 'y';
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// -ses → -s (processes → process, but 'process' is already singular)
|
|
99
|
-
// -xes → -x (boxes → box)
|
|
100
|
-
// -ches → -ch (watches → watch)
|
|
101
|
-
// -shes → -sh (dishes → dish)
|
|
102
|
-
if (word.endsWith('ses') || word.endsWith('xes') || word.endsWith('ches') || word.endsWith('shes')) {
|
|
103
|
-
return word.slice(0, -2);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Regular -s ending (cats → cat)
|
|
107
|
-
// But avoid words ending in -ss (class, success, etc.)
|
|
108
|
-
if (word.endsWith('s') && !word.endsWith('ss') && word.length > 1) {
|
|
109
|
-
return word.slice(0, -1);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return word;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Convert singular word to plural
|
|
117
|
-
* @param {string} word - The word to pluralize
|
|
118
|
-
* @returns {string} - The plural form of the word
|
|
119
|
-
*/
|
|
120
|
-
export function pluralize(word) {
|
|
121
|
-
// Handle empty or short words
|
|
122
|
-
if (!word || word.length <= 1) {
|
|
123
|
-
return word;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Check if already plural
|
|
127
|
-
if (isPlural(word)) {
|
|
128
|
-
return word;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Check irregular plurals first
|
|
132
|
-
const lowerWord = word.toLowerCase();
|
|
133
|
-
if (IRREGULAR_PLURALS[lowerWord]) {
|
|
134
|
-
return IRREGULAR_PLURALS[lowerWord];
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// -y ending (not vowel+y) → -ies (activity → activities)
|
|
138
|
-
if (word.endsWith('y') && !['ay', 'ey', 'iy', 'oy', 'uy'].includes(word.slice(-2))) {
|
|
139
|
-
return word.slice(0, -1) + 'ies';
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// -x, -ch, -sh endings → add 'es' (box → boxes, watch → watches, dish → dishes)
|
|
143
|
-
if (word.endsWith('x') || word.endsWith('ch') || word.endsWith('sh')) {
|
|
144
|
-
return word + 'es';
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Regular: add 's' (cat → cats, dog → dogs)
|
|
148
|
-
return word + 's';
|
|
149
|
-
}
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenAPI Parser Implementation
|
|
3
|
-
*
|
|
4
|
-
* Core parser that reads OpenAPI 3.0 specifications and converts them
|
|
5
|
-
* into an intermediate representation for code generation.
|
|
6
|
-
*
|
|
7
|
-
* Part of FRO-11: OpenAPI Parser Implementation
|
|
8
|
-
*/
|
|
9
|
-
import { promises as fs } from 'fs';
|
|
10
|
-
import { resolve } from 'path';
|
|
11
|
-
// Main parser class
|
|
12
|
-
export class OpenAPIParser {
|
|
13
|
-
spec;
|
|
14
|
-
refs = new Map();
|
|
15
|
-
constructor(spec) {
|
|
16
|
-
this.spec = spec;
|
|
17
|
-
this.buildRefsMap();
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Parse the OpenAPI specification into our intermediate representation
|
|
21
|
-
*/
|
|
22
|
-
parse() {
|
|
23
|
-
return {
|
|
24
|
-
info: this.parseInfo(),
|
|
25
|
-
servers: this.parseServers(),
|
|
26
|
-
endpoints: this.parseEndpoints(),
|
|
27
|
-
schemas: this.parseSchemas(),
|
|
28
|
-
security: this.parseSecurity()
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
parseInfo() {
|
|
32
|
-
return {
|
|
33
|
-
title: this.spec.info.title,
|
|
34
|
-
version: this.spec.info.version,
|
|
35
|
-
description: this.spec.info.description
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
parseServers() {
|
|
39
|
-
if (!this.spec.servers)
|
|
40
|
-
return [];
|
|
41
|
-
return this.spec.servers.map(server => ({
|
|
42
|
-
url: server.url,
|
|
43
|
-
description: server.description,
|
|
44
|
-
variables: server.variables ?
|
|
45
|
-
Object.fromEntries(Object.entries(server.variables).map(([key, variable]) => [
|
|
46
|
-
key,
|
|
47
|
-
variable.default || ''
|
|
48
|
-
])) : undefined
|
|
49
|
-
}));
|
|
50
|
-
}
|
|
51
|
-
parseEndpoints() {
|
|
52
|
-
const endpoints = [];
|
|
53
|
-
for (const [path, pathItem] of Object.entries(this.spec.paths || {})) {
|
|
54
|
-
if (!pathItem)
|
|
55
|
-
continue;
|
|
56
|
-
const methods = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options'];
|
|
57
|
-
for (const method of methods) {
|
|
58
|
-
const operation = pathItem[method];
|
|
59
|
-
if (!operation)
|
|
60
|
-
continue;
|
|
61
|
-
endpoints.push({
|
|
62
|
-
path,
|
|
63
|
-
method,
|
|
64
|
-
operationId: operation.operationId,
|
|
65
|
-
summary: operation.summary,
|
|
66
|
-
description: operation.description,
|
|
67
|
-
tags: operation.tags,
|
|
68
|
-
parameters: this.parseParameters(operation.parameters),
|
|
69
|
-
requestBody: operation.requestBody ? this.parseRequestBody(operation.requestBody) : undefined,
|
|
70
|
-
responses: this.parseResponses(operation.responses),
|
|
71
|
-
security: operation.security ? this.parseOperationSecurity(operation.security) : undefined
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return endpoints;
|
|
76
|
-
}
|
|
77
|
-
parseParameters(parameters) {
|
|
78
|
-
if (!parameters)
|
|
79
|
-
return [];
|
|
80
|
-
return parameters.map(param => {
|
|
81
|
-
const resolved = this.resolveRef(param);
|
|
82
|
-
return {
|
|
83
|
-
name: resolved.name,
|
|
84
|
-
in: resolved.in,
|
|
85
|
-
required: resolved.required || false,
|
|
86
|
-
schema: this.parseSchema(resolved.schema),
|
|
87
|
-
description: resolved.description
|
|
88
|
-
};
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
parseRequestBody(requestBody) {
|
|
92
|
-
const resolved = this.resolveRef(requestBody);
|
|
93
|
-
return {
|
|
94
|
-
required: resolved.required || false,
|
|
95
|
-
description: resolved.description,
|
|
96
|
-
content: Object.fromEntries(Object.entries(resolved.content || {}).map(([mediaType, mediaTypeObj]) => [
|
|
97
|
-
mediaType,
|
|
98
|
-
{ schema: this.parseSchema(mediaTypeObj.schema) }
|
|
99
|
-
]))
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
parseResponses(responses) {
|
|
103
|
-
return Object.entries(responses).map(([statusCode, response]) => {
|
|
104
|
-
const resolved = this.resolveRef(response);
|
|
105
|
-
return {
|
|
106
|
-
statusCode,
|
|
107
|
-
description: resolved.description,
|
|
108
|
-
content: resolved.content ?
|
|
109
|
-
Object.fromEntries(Object.entries(resolved.content).map(([mediaType, mediaTypeObj]) => [
|
|
110
|
-
mediaType,
|
|
111
|
-
{ schema: this.parseSchema(mediaTypeObj.schema) }
|
|
112
|
-
])) : undefined,
|
|
113
|
-
headers: resolved.headers ?
|
|
114
|
-
Object.fromEntries(Object.entries(resolved.headers).map(([headerName, header]) => [
|
|
115
|
-
headerName,
|
|
116
|
-
this.parseSchema(this.resolveRef(header).schema)
|
|
117
|
-
])) : undefined
|
|
118
|
-
};
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
parseSchema(schema) {
|
|
122
|
-
if (!schema) {
|
|
123
|
-
return { type: 'any' };
|
|
124
|
-
}
|
|
125
|
-
// Handle $ref
|
|
126
|
-
if ('$ref' in schema) {
|
|
127
|
-
return {
|
|
128
|
-
type: 'any', // Will be resolved later
|
|
129
|
-
ref: schema.$ref,
|
|
130
|
-
originalRef: schema.$ref
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
const resolved = schema;
|
|
134
|
-
// Handle array type
|
|
135
|
-
if (resolved.type === 'array') {
|
|
136
|
-
return {
|
|
137
|
-
type: 'array',
|
|
138
|
-
items: this.parseSchema(resolved.items),
|
|
139
|
-
description: resolved.description,
|
|
140
|
-
nullable: resolved.nullable
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
// Handle object type
|
|
144
|
-
if (resolved.type === 'object' || resolved.properties) {
|
|
145
|
-
return {
|
|
146
|
-
type: 'object',
|
|
147
|
-
properties: resolved.properties ?
|
|
148
|
-
Object.fromEntries(Object.entries(resolved.properties).map(([propName, propSchema]) => [
|
|
149
|
-
propName,
|
|
150
|
-
this.parseSchema(propSchema)
|
|
151
|
-
])) : undefined,
|
|
152
|
-
required: resolved.required,
|
|
153
|
-
description: resolved.description,
|
|
154
|
-
nullable: resolved.nullable
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
// Handle primitive types
|
|
158
|
-
const type = this.mapOpenAPIType(resolved.type);
|
|
159
|
-
return {
|
|
160
|
-
type,
|
|
161
|
-
format: resolved.format,
|
|
162
|
-
enum: resolved.enum,
|
|
163
|
-
description: resolved.description,
|
|
164
|
-
example: resolved.example,
|
|
165
|
-
nullable: resolved.nullable
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
parseSchemas() {
|
|
169
|
-
if (!this.spec.components?.schemas)
|
|
170
|
-
return [];
|
|
171
|
-
return Object.entries(this.spec.components.schemas).map(([name, schema]) => ({
|
|
172
|
-
...this.parseSchema(schema),
|
|
173
|
-
ref: `#/components/schemas/${name}`
|
|
174
|
-
}));
|
|
175
|
-
}
|
|
176
|
-
parseSecurity() {
|
|
177
|
-
if (!this.spec.components?.securitySchemes)
|
|
178
|
-
return [];
|
|
179
|
-
return Object.entries(this.spec.components.securitySchemes).map(([_name, scheme]) => {
|
|
180
|
-
const resolved = this.resolveRef(scheme);
|
|
181
|
-
return {
|
|
182
|
-
type: resolved.type,
|
|
183
|
-
scheme: 'scheme' in resolved ? resolved.scheme : undefined,
|
|
184
|
-
bearerFormat: 'bearerFormat' in resolved ? resolved.bearerFormat : undefined,
|
|
185
|
-
in: 'in' in resolved ? resolved.in : undefined,
|
|
186
|
-
name: 'name' in resolved ? resolved.name : undefined,
|
|
187
|
-
flows: 'flows' in resolved ? resolved.flows : undefined,
|
|
188
|
-
openIdConnectUrl: 'openIdConnectUrl' in resolved ? resolved.openIdConnectUrl : undefined
|
|
189
|
-
};
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
parseOperationSecurity(_security) {
|
|
193
|
-
// Simplified - would need to match with security schemes
|
|
194
|
-
return [];
|
|
195
|
-
}
|
|
196
|
-
mapOpenAPIType(type) {
|
|
197
|
-
switch (type) {
|
|
198
|
-
case 'string': return 'string';
|
|
199
|
-
case 'number': return 'number';
|
|
200
|
-
case 'integer': return 'integer';
|
|
201
|
-
case 'boolean': return 'boolean';
|
|
202
|
-
case 'array': return 'array';
|
|
203
|
-
case 'object': return 'object';
|
|
204
|
-
case 'null': return 'null';
|
|
205
|
-
default: return 'any';
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
buildRefsMap() {
|
|
209
|
-
// Build a map of all $ref targets for quick resolution
|
|
210
|
-
this.traverseAndMapRefs(this.spec, '');
|
|
211
|
-
}
|
|
212
|
-
traverseAndMapRefs(obj, currentPath) {
|
|
213
|
-
if (typeof obj !== 'object' || obj === null)
|
|
214
|
-
return;
|
|
215
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
216
|
-
const path = currentPath ? `${currentPath}/${key}` : key;
|
|
217
|
-
if (key === '$ref' && typeof value === 'string') {
|
|
218
|
-
// Don't store the ref itself, we'll resolve it when needed
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
|
-
if (typeof value === 'object') {
|
|
222
|
-
// Store objects that could be referenced
|
|
223
|
-
if (currentPath.includes('/components/')) {
|
|
224
|
-
this.refs.set(`#/${path}`, value);
|
|
225
|
-
}
|
|
226
|
-
this.traverseAndMapRefs(value, path);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
resolveRef(item) {
|
|
231
|
-
if (typeof item === 'object' && item !== null && '$ref' in item) {
|
|
232
|
-
const ref = item.$ref;
|
|
233
|
-
const resolved = this.refs.get(ref);
|
|
234
|
-
if (!resolved) {
|
|
235
|
-
throw new Error(`Could not resolve reference: ${ref}`);
|
|
236
|
-
}
|
|
237
|
-
return resolved;
|
|
238
|
-
}
|
|
239
|
-
return item;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
// Factory function for easy usage
|
|
243
|
-
export async function parseOpenAPI(spec) {
|
|
244
|
-
const parser = new OpenAPIParser(spec);
|
|
245
|
-
return parser.parse();
|
|
246
|
-
}
|
|
247
|
-
// Utility function to load OpenAPI from URL or file
|
|
248
|
-
export async function loadOpenAPISpec(source) {
|
|
249
|
-
if (source.startsWith('http://') || source.startsWith('https://')) {
|
|
250
|
-
// Load from URL
|
|
251
|
-
const response = await fetch(source);
|
|
252
|
-
if (!response.ok) {
|
|
253
|
-
throw new Error(`Failed to load OpenAPI spec from ${source}: ${response.statusText}`);
|
|
254
|
-
}
|
|
255
|
-
return response.json();
|
|
256
|
-
}
|
|
257
|
-
else {
|
|
258
|
-
// Try to parse as JSON string first
|
|
259
|
-
try {
|
|
260
|
-
return JSON.parse(source);
|
|
261
|
-
}
|
|
262
|
-
catch {
|
|
263
|
-
// If JSON parse fails, try to read as file
|
|
264
|
-
try {
|
|
265
|
-
const absolutePath = resolve(source);
|
|
266
|
-
const content = await fs.readFile(absolutePath, 'utf8');
|
|
267
|
-
return JSON.parse(content);
|
|
268
|
-
}
|
|
269
|
-
catch (fileError) {
|
|
270
|
-
throw new Error(`Failed to load OpenAPI spec: Not a valid URL, JSON string, or file path. ${fileError}`);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|