nuxt-openapi-hyperfetch 0.1.0-alpha.1
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/.editorconfig +26 -0
- package/.prettierignore +17 -0
- package/.prettierrc.json +12 -0
- package/CONTRIBUTING.md +292 -0
- package/INSTRUCTIONS.md +327 -0
- package/LICENSE +202 -0
- package/README.md +202 -0
- package/dist/cli/config.d.ts +57 -0
- package/dist/cli/config.js +85 -0
- package/dist/cli/logger.d.ts +44 -0
- package/dist/cli/logger.js +58 -0
- package/dist/cli/logo.d.ts +6 -0
- package/dist/cli/logo.js +21 -0
- package/dist/cli/messages.d.ts +65 -0
- package/dist/cli/messages.js +86 -0
- package/dist/cli/prompts.d.ts +30 -0
- package/dist/cli/prompts.js +118 -0
- package/dist/cli/types.d.ts +43 -0
- package/dist/cli/types.js +4 -0
- package/dist/cli/utils.d.ts +26 -0
- package/dist/cli/utils.js +45 -0
- package/dist/generate.d.ts +6 -0
- package/dist/generate.js +48 -0
- package/dist/generators/nuxt-server/bff-templates.d.ts +25 -0
- package/dist/generators/nuxt-server/bff-templates.js +737 -0
- package/dist/generators/nuxt-server/generator.d.ts +7 -0
- package/dist/generators/nuxt-server/generator.js +206 -0
- package/dist/generators/nuxt-server/parser.d.ts +5 -0
- package/dist/generators/nuxt-server/parser.js +5 -0
- package/dist/generators/nuxt-server/templates.d.ts +35 -0
- package/dist/generators/nuxt-server/templates.js +412 -0
- package/dist/generators/nuxt-server/types.d.ts +5 -0
- package/dist/generators/nuxt-server/types.js +5 -0
- package/dist/generators/shared/parsers/heyapi-parser.d.ts +11 -0
- package/dist/generators/shared/parsers/heyapi-parser.js +248 -0
- package/dist/generators/shared/parsers/official-parser.d.ts +5 -0
- package/dist/generators/shared/parsers/official-parser.js +5 -0
- package/dist/generators/shared/runtime/apiHelpers.d.ts +183 -0
- package/dist/generators/shared/runtime/apiHelpers.js +268 -0
- package/dist/generators/shared/templates/api-callbacks-plugin.d.ts +178 -0
- package/dist/generators/shared/templates/api-callbacks-plugin.js +338 -0
- package/dist/generators/shared/types.d.ts +25 -0
- package/dist/generators/shared/types.js +4 -0
- package/dist/generators/tanstack-query/generator.d.ts +5 -0
- package/dist/generators/tanstack-query/generator.js +11 -0
- package/dist/generators/use-async-data/generator.d.ts +5 -0
- package/dist/generators/use-async-data/generator.js +156 -0
- package/dist/generators/use-async-data/parser.d.ts +5 -0
- package/dist/generators/use-async-data/parser.js +5 -0
- package/dist/generators/use-async-data/runtime/useApiAsyncData.d.ts +38 -0
- package/dist/generators/use-async-data/runtime/useApiAsyncData.js +122 -0
- package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.d.ts +54 -0
- package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.js +126 -0
- package/dist/generators/use-async-data/templates.d.ts +20 -0
- package/dist/generators/use-async-data/templates.js +191 -0
- package/dist/generators/use-async-data/types.d.ts +4 -0
- package/dist/generators/use-async-data/types.js +4 -0
- package/dist/generators/use-fetch/generator.d.ts +5 -0
- package/dist/generators/use-fetch/generator.js +131 -0
- package/dist/generators/use-fetch/parser.d.ts +9 -0
- package/dist/generators/use-fetch/parser.js +282 -0
- package/dist/generators/use-fetch/runtime/useApiRequest.d.ts +46 -0
- package/dist/generators/use-fetch/runtime/useApiRequest.js +158 -0
- package/dist/generators/use-fetch/templates.d.ts +16 -0
- package/dist/generators/use-fetch/templates.js +169 -0
- package/dist/generators/use-fetch/types.d.ts +5 -0
- package/dist/generators/use-fetch/types.js +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +213 -0
- package/docs/API-REFERENCE.md +887 -0
- package/docs/ARCHITECTURE.md +649 -0
- package/docs/DEVELOPMENT.md +918 -0
- package/docs/QUICK-START.md +323 -0
- package/docs/README.md +155 -0
- package/docs/TROUBLESHOOTING.md +881 -0
- package/eslint.config.js +72 -0
- package/package.json +65 -0
- package/src/cli/config.ts +140 -0
- package/src/cli/logger.ts +66 -0
- package/src/cli/logo.ts +25 -0
- package/src/cli/messages.ts +97 -0
- package/src/cli/prompts.ts +143 -0
- package/src/cli/types.ts +50 -0
- package/src/cli/utils.ts +49 -0
- package/src/generate.ts +57 -0
- package/src/generators/nuxt-server/bff-templates.ts +754 -0
- package/src/generators/nuxt-server/generator.ts +270 -0
- package/src/generators/nuxt-server/parser.ts +5 -0
- package/src/generators/nuxt-server/templates.ts +483 -0
- package/src/generators/nuxt-server/types.ts +5 -0
- package/src/generators/shared/parsers/heyapi-parser.ts +307 -0
- package/src/generators/shared/parsers/official-parser.ts +5 -0
- package/src/generators/shared/runtime/apiHelpers.ts +466 -0
- package/src/generators/shared/templates/api-callbacks-plugin.ts +352 -0
- package/src/generators/shared/types.ts +27 -0
- package/src/generators/tanstack-query/generator.ts +11 -0
- package/src/generators/use-async-data/generator.ts +204 -0
- package/src/generators/use-async-data/parser.ts +5 -0
- package/src/generators/use-async-data/runtime/useApiAsyncData.ts +220 -0
- package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +236 -0
- package/src/generators/use-async-data/templates.ts +250 -0
- package/src/generators/use-async-data/types.ts +4 -0
- package/src/generators/use-fetch/generator.ts +169 -0
- package/src/generators/use-fetch/parser.ts +341 -0
- package/src/generators/use-fetch/runtime/useApiRequest.ts +223 -0
- package/src/generators/use-fetch/templates.ts +214 -0
- package/src/generators/use-fetch/types.ts +5 -0
- package/src/index.ts +265 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { pascalCase, camelCase } from 'change-case';
|
|
3
|
+
/**
|
|
4
|
+
* Generate the Auth Context stub file
|
|
5
|
+
* This file is generated ONCE and never overwritten
|
|
6
|
+
*/
|
|
7
|
+
export function generateAuthContextStub() {
|
|
8
|
+
return `import type { H3Event } from 'h3';
|
|
9
|
+
import type { AuthContext } from './types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get authentication context from the current request
|
|
13
|
+
*
|
|
14
|
+
* TODO: Implement your authentication logic here
|
|
15
|
+
* This function is called automatically by all generated server routes.
|
|
16
|
+
*
|
|
17
|
+
* IMPORTANT: This file is NEVER regenerated - your changes are safe!
|
|
18
|
+
*
|
|
19
|
+
* Examples for popular auth modules:
|
|
20
|
+
*
|
|
21
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
22
|
+
* Option 1: @sidebase/nuxt-auth
|
|
23
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
24
|
+
*
|
|
25
|
+
* import { getServerSession } from '#auth';
|
|
26
|
+
*
|
|
27
|
+
* export async function getAuthContext(event: H3Event): Promise<AuthContext> {
|
|
28
|
+
* const session = await getServerSession(event);
|
|
29
|
+
*
|
|
30
|
+
* if (!session) {
|
|
31
|
+
* return {
|
|
32
|
+
* isAuthenticated: false,
|
|
33
|
+
* userId: null,
|
|
34
|
+
* roles: [],
|
|
35
|
+
* permissions: [],
|
|
36
|
+
* };
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
* return {
|
|
40
|
+
* isAuthenticated: true,
|
|
41
|
+
* userId: session.user.id,
|
|
42
|
+
* email: session.user.email,
|
|
43
|
+
* roles: session.user.roles || [],
|
|
44
|
+
* permissions: session.user.permissions || [],
|
|
45
|
+
* };
|
|
46
|
+
* }
|
|
47
|
+
*
|
|
48
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
49
|
+
* Option 2: Custom JWT
|
|
50
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
51
|
+
*
|
|
52
|
+
* import { getCookie } from 'h3';
|
|
53
|
+
* import jwt from 'jsonwebtoken';
|
|
54
|
+
*
|
|
55
|
+
* export async function getAuthContext(event: H3Event): Promise<AuthContext> {
|
|
56
|
+
* const token = getCookie(event, 'auth-token');
|
|
57
|
+
*
|
|
58
|
+
* if (!token) {
|
|
59
|
+
* return {
|
|
60
|
+
* isAuthenticated: false,
|
|
61
|
+
* userId: null,
|
|
62
|
+
* roles: [],
|
|
63
|
+
* permissions: [],
|
|
64
|
+
* };
|
|
65
|
+
* }
|
|
66
|
+
*
|
|
67
|
+
* try {
|
|
68
|
+
* const decoded = jwt.verify(token, process.env.JWT_SECRET!) as any;
|
|
69
|
+
* return {
|
|
70
|
+
* isAuthenticated: true,
|
|
71
|
+
* userId: decoded.sub,
|
|
72
|
+
* email: decoded.email,
|
|
73
|
+
* roles: decoded.roles || [],
|
|
74
|
+
* permissions: decoded.permissions || [],
|
|
75
|
+
* };
|
|
76
|
+
* } catch (error) {
|
|
77
|
+
* return {
|
|
78
|
+
* isAuthenticated: false,
|
|
79
|
+
* userId: null,
|
|
80
|
+
* roles: [],
|
|
81
|
+
* permissions: [],
|
|
82
|
+
* };
|
|
83
|
+
* }
|
|
84
|
+
* }
|
|
85
|
+
*
|
|
86
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
87
|
+
* Option 3: Session Cookies
|
|
88
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
89
|
+
*
|
|
90
|
+
* import { getCookie } from 'h3';
|
|
91
|
+
*
|
|
92
|
+
* export async function getAuthContext(event: H3Event): Promise<AuthContext> {
|
|
93
|
+
* const sessionId = getCookie(event, 'session-id');
|
|
94
|
+
*
|
|
95
|
+
* if (!sessionId) {
|
|
96
|
+
* return {
|
|
97
|
+
* isAuthenticated: false,
|
|
98
|
+
* userId: null,
|
|
99
|
+
* roles: [],
|
|
100
|
+
* permissions: [],
|
|
101
|
+
* };
|
|
102
|
+
* }
|
|
103
|
+
*
|
|
104
|
+
* // TODO: Look up session in your database/store
|
|
105
|
+
* const session = await db.sessions.findOne({ id: sessionId });
|
|
106
|
+
*
|
|
107
|
+
* if (!session || session.expiresAt < Date.now()) {
|
|
108
|
+
* return {
|
|
109
|
+
* isAuthenticated: false,
|
|
110
|
+
* userId: null,
|
|
111
|
+
* roles: [],
|
|
112
|
+
* permissions: [],
|
|
113
|
+
* };
|
|
114
|
+
* }
|
|
115
|
+
*
|
|
116
|
+
* return {
|
|
117
|
+
* isAuthenticated: true,
|
|
118
|
+
* userId: session.userId,
|
|
119
|
+
* roles: session.roles || [],
|
|
120
|
+
* permissions: session.permissions || [],
|
|
121
|
+
* };
|
|
122
|
+
* }
|
|
123
|
+
*/
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Default implementation - No authentication
|
|
127
|
+
* Replace this with your actual auth logic above
|
|
128
|
+
*/
|
|
129
|
+
export async function getAuthContext(event: H3Event): Promise<AuthContext> {
|
|
130
|
+
// TODO: Implement your authentication logic
|
|
131
|
+
// See examples above for popular auth modules
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
isAuthenticated: false,
|
|
135
|
+
userId: null,
|
|
136
|
+
roles: [],
|
|
137
|
+
permissions: [],
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
`;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Generate the Auth Types stub file
|
|
144
|
+
* This file is generated ONCE and never overwritten
|
|
145
|
+
*/
|
|
146
|
+
export function generateAuthTypesStub() {
|
|
147
|
+
return `/**
|
|
148
|
+
* Authentication context type
|
|
149
|
+
*
|
|
150
|
+
* IMPORTANT: This file is NEVER regenerated - your changes are safe!
|
|
151
|
+
*
|
|
152
|
+
* You can extend this interface with any properties you need:
|
|
153
|
+
* - User information (email, name, avatar)
|
|
154
|
+
* - Permissions and roles
|
|
155
|
+
* - Organization/tenant context
|
|
156
|
+
* - Feature flags
|
|
157
|
+
* - Custom metadata
|
|
158
|
+
*/
|
|
159
|
+
export interface AuthContext {
|
|
160
|
+
/** Whether the user is authenticated */
|
|
161
|
+
isAuthenticated: boolean;
|
|
162
|
+
|
|
163
|
+
/** User ID (null if not authenticated) */
|
|
164
|
+
userId: string | null;
|
|
165
|
+
|
|
166
|
+
/** User roles (e.g., ['admin', 'user']) */
|
|
167
|
+
roles: string[];
|
|
168
|
+
|
|
169
|
+
/** User permissions (e.g., ['pet:read', 'pet:write']) */
|
|
170
|
+
permissions: string[];
|
|
171
|
+
|
|
172
|
+
// Add more fields as needed:
|
|
173
|
+
// email?: string;
|
|
174
|
+
// name?: string;
|
|
175
|
+
// avatar?: string;
|
|
176
|
+
// organizationId?: string;
|
|
177
|
+
// tenantId?: string;
|
|
178
|
+
// features?: string[];
|
|
179
|
+
// metadata?: Record<string, any>;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Helper to check if user has a specific permission
|
|
184
|
+
*/
|
|
185
|
+
export function hasPermission(auth: AuthContext, permission: string): boolean {
|
|
186
|
+
return auth.isAuthenticated && auth.permissions.includes(permission);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Helper to check if user has a specific role
|
|
191
|
+
*/
|
|
192
|
+
export function hasRole(auth: AuthContext, role: string): boolean {
|
|
193
|
+
return auth.isAuthenticated && auth.roles.includes(role);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Helper to check if user has ANY of the specified roles
|
|
198
|
+
*/
|
|
199
|
+
export function hasAnyRole(auth: AuthContext, roles: string[]): boolean {
|
|
200
|
+
return auth.isAuthenticated && roles.some(role => auth.roles.includes(role));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Helper to check if user has ALL of the specified roles
|
|
205
|
+
*/
|
|
206
|
+
export function hasAllRoles(auth: AuthContext, roles: string[]): boolean {
|
|
207
|
+
return auth.isAuthenticated && roles.every(role => auth.roles.includes(role));
|
|
208
|
+
}
|
|
209
|
+
`;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Generate a transformer stub for a specific resource
|
|
213
|
+
* This file is generated ONCE and never overwritten
|
|
214
|
+
*/
|
|
215
|
+
export function generateTransformerStub(resource, methods, inputDir) {
|
|
216
|
+
const resourcePascal = pascalCase(resource);
|
|
217
|
+
const resourceCamel = camelCase(resource);
|
|
218
|
+
// Compute ~/path for model types import
|
|
219
|
+
const projectRoot = process.cwd();
|
|
220
|
+
const relativeInputDir = path.relative(projectRoot, path.resolve(inputDir)).replace(/\\/g, '/');
|
|
221
|
+
const modelsImportPath = `~/${relativeInputDir}/models`;
|
|
222
|
+
// Extract unique type names from methods
|
|
223
|
+
const typeNames = new Set();
|
|
224
|
+
methods.forEach((method) => {
|
|
225
|
+
if (method.responseType) {
|
|
226
|
+
const baseType = extractBaseType(method.responseType);
|
|
227
|
+
if (baseType) {
|
|
228
|
+
typeNames.add(baseType);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
const importTypes = Array.from(typeNames).join(', ');
|
|
233
|
+
return `import type { H3Event } from 'h3';
|
|
234
|
+
import type { AuthContext } from '~/server/auth/types';
|
|
235
|
+
${importTypes ? `import type { ${importTypes} } from '${modelsImportPath}';\n` : ''}
|
|
236
|
+
/**
|
|
237
|
+
* Transformer for ${resource} endpoints
|
|
238
|
+
*
|
|
239
|
+
* IMPORTANT: This file is NEVER regenerated - your changes are safe!
|
|
240
|
+
*
|
|
241
|
+
* This transformer is automatically called by generated server routes.
|
|
242
|
+
* Add your business logic here:
|
|
243
|
+
* - Data transformation
|
|
244
|
+
* - Permission checks
|
|
245
|
+
* - Filtering sensitive data
|
|
246
|
+
* - Combining multiple sources
|
|
247
|
+
* - Caching logic
|
|
248
|
+
* - Rate limiting
|
|
249
|
+
*
|
|
250
|
+
* The transformer receives:
|
|
251
|
+
* - data: The raw response from the backend API
|
|
252
|
+
* - event: The h3 event (for accessing headers, query params, etc.)
|
|
253
|
+
* - auth: The authentication context
|
|
254
|
+
*/
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Transform ${resource} data
|
|
258
|
+
*
|
|
259
|
+
* TODO: Implement your transformation logic here
|
|
260
|
+
*
|
|
261
|
+
* Examples:
|
|
262
|
+
*
|
|
263
|
+
* 1. Add computed fields:
|
|
264
|
+
* return { ...data, fullName: \`\${data.firstName} \${data.lastName}\` }
|
|
265
|
+
*
|
|
266
|
+
* 2. Filter sensitive data:
|
|
267
|
+
* const { password, internalId, ...safe } = data
|
|
268
|
+
* return safe
|
|
269
|
+
*
|
|
270
|
+
* 3. Add permissions:
|
|
271
|
+
* return {
|
|
272
|
+
* ...data,
|
|
273
|
+
* canEdit: auth.permissions.includes('${resourceCamel}:write'),
|
|
274
|
+
* canDelete: auth.permissions.includes('${resourceCamel}:delete'),
|
|
275
|
+
* }
|
|
276
|
+
*
|
|
277
|
+
* 4. Filter based on permissions:
|
|
278
|
+
* if (!auth.permissions.includes('${resourceCamel}:read:all')) {
|
|
279
|
+
* return { ...data, sensitiveField: undefined }
|
|
280
|
+
* }
|
|
281
|
+
* return data
|
|
282
|
+
*/
|
|
283
|
+
export async function transform${resourcePascal}<T = any>(
|
|
284
|
+
data: T,
|
|
285
|
+
event: H3Event,
|
|
286
|
+
auth: AuthContext | null
|
|
287
|
+
): Promise<T> {
|
|
288
|
+
// TODO: Add your transformation logic here
|
|
289
|
+
|
|
290
|
+
// Example: Add permission flags
|
|
291
|
+
// if (typeof data === 'object' && data !== null) {
|
|
292
|
+
// return {
|
|
293
|
+
// ...data,
|
|
294
|
+
// canEdit: auth?.permissions.includes('${resourceCamel}:write') ?? false,
|
|
295
|
+
// canDelete: auth?.permissions.includes('${resourceCamel}:delete') ?? false,
|
|
296
|
+
// } as T;
|
|
297
|
+
// }
|
|
298
|
+
|
|
299
|
+
// Default: Return data unchanged
|
|
300
|
+
return data;
|
|
301
|
+
}
|
|
302
|
+
`;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Generate transformer examples file
|
|
306
|
+
* This file is ALWAYS regenerated as a reference
|
|
307
|
+
*/
|
|
308
|
+
export function generateTransformerExamples() {
|
|
309
|
+
return `/**
|
|
310
|
+
* ⚠️ EXAMPLES ONLY - DO NOT EDIT
|
|
311
|
+
*
|
|
312
|
+
* This file contains examples of transformer patterns.
|
|
313
|
+
* Copy these examples to your actual transformer files.
|
|
314
|
+
*
|
|
315
|
+
* This file is regenerated on every generation - changes will be lost!
|
|
316
|
+
*
|
|
317
|
+
* @generated by nuxt-openapi-generator
|
|
318
|
+
*/
|
|
319
|
+
|
|
320
|
+
import type { H3Event } from 'h3';
|
|
321
|
+
import type { AuthContext } from '~/server/auth/types';
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
325
|
+
* Example 1: Basic Transformation
|
|
326
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
327
|
+
*
|
|
328
|
+
* Transform backend data to frontend format
|
|
329
|
+
*/
|
|
330
|
+
export async function exampleBasicTransform<T>(
|
|
331
|
+
data: T,
|
|
332
|
+
event: H3Event,
|
|
333
|
+
auth: AuthContext | null
|
|
334
|
+
): Promise<T> {
|
|
335
|
+
// Add computed fields
|
|
336
|
+
if (typeof data === 'object' && data !== null) {
|
|
337
|
+
return {
|
|
338
|
+
...data,
|
|
339
|
+
// Example: Format dates
|
|
340
|
+
// createdAtFormatted: new Date(data.createdAt).toLocaleDateString(),
|
|
341
|
+
|
|
342
|
+
// Example: Add computed values
|
|
343
|
+
// fullName: \`\${data.firstName} \${data.lastName}\`,
|
|
344
|
+
|
|
345
|
+
// Example: Add metadata
|
|
346
|
+
// _metadata: {
|
|
347
|
+
// retrievedAt: Date.now(),
|
|
348
|
+
// userId: auth?.userId,
|
|
349
|
+
// },
|
|
350
|
+
} as T;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return data;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
358
|
+
* Example 2: Filter Sensitive Data
|
|
359
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
360
|
+
*
|
|
361
|
+
* Remove fields that shouldn't be exposed to the client
|
|
362
|
+
*/
|
|
363
|
+
export async function exampleFilterSensitiveData(
|
|
364
|
+
data: any,
|
|
365
|
+
event: H3Event,
|
|
366
|
+
auth: AuthContext | null
|
|
367
|
+
): Promise<any> {
|
|
368
|
+
// Define sensitive fields
|
|
369
|
+
const sensitiveFields = ['password', 'passwordHash', 'ssn', 'internalId', 'secretKey'];
|
|
370
|
+
|
|
371
|
+
if (Array.isArray(data)) {
|
|
372
|
+
return data.map(item => filterObject(item, sensitiveFields));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return filterObject(data, sensitiveFields);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function filterObject(obj: any, fieldsToRemove: string[]): any {
|
|
379
|
+
if (typeof obj !== 'object' || obj === null) return obj;
|
|
380
|
+
|
|
381
|
+
const filtered = { ...obj };
|
|
382
|
+
fieldsToRemove.forEach(field => delete filtered[field]);
|
|
383
|
+
return filtered;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
388
|
+
* Example 3: Add Permission Flags
|
|
389
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
390
|
+
*
|
|
391
|
+
* Enrich data with user-specific permissions
|
|
392
|
+
*/
|
|
393
|
+
export async function exampleAddPermissions(
|
|
394
|
+
data: any,
|
|
395
|
+
event: H3Event,
|
|
396
|
+
auth: AuthContext | null
|
|
397
|
+
): Promise<any> {
|
|
398
|
+
if (typeof data !== 'object' || data === null) return data;
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
...data,
|
|
402
|
+
// Add permission flags
|
|
403
|
+
canEdit: auth?.permissions.includes('resource:write') ?? false,
|
|
404
|
+
canDelete: auth?.permissions.includes('resource:delete') ?? false,
|
|
405
|
+
canShare: auth?.permissions.includes('resource:share') ?? false,
|
|
406
|
+
|
|
407
|
+
// Add ownership check
|
|
408
|
+
isOwner: auth?.userId === data.userId,
|
|
409
|
+
|
|
410
|
+
// Add role-based flags
|
|
411
|
+
isAdmin: auth?.roles.includes('admin') ?? false,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
417
|
+
* Example 4: Combine Multiple Sources
|
|
418
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
419
|
+
*
|
|
420
|
+
* Fetch additional data from other endpoints
|
|
421
|
+
*/
|
|
422
|
+
export async function exampleCombineSources(
|
|
423
|
+
data: any,
|
|
424
|
+
event: H3Event,
|
|
425
|
+
auth: AuthContext | null
|
|
426
|
+
): Promise<any> {
|
|
427
|
+
const config = useRuntimeConfig();
|
|
428
|
+
|
|
429
|
+
// Example: Fetch related data
|
|
430
|
+
// const reviews = await $fetch(\`\${config.apiBaseUrl}/reviews/\${data.id}\`);
|
|
431
|
+
// const availability = await $fetch(\`\${config.apiBaseUrl}/availability/\${data.id}\`);
|
|
432
|
+
|
|
433
|
+
return {
|
|
434
|
+
...data,
|
|
435
|
+
// reviews,
|
|
436
|
+
// availability,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
442
|
+
* Example 5: Permission-Based Filtering
|
|
443
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
444
|
+
*
|
|
445
|
+
* Show/hide fields based on user permissions
|
|
446
|
+
*/
|
|
447
|
+
export async function examplePermissionBasedFiltering(
|
|
448
|
+
data: any,
|
|
449
|
+
event: H3Event,
|
|
450
|
+
auth: AuthContext | null
|
|
451
|
+
): Promise<any> {
|
|
452
|
+
if (typeof data !== 'object' || data === null) return data;
|
|
453
|
+
|
|
454
|
+
const result = { ...data };
|
|
455
|
+
|
|
456
|
+
// Hide sensitive fields for non-admins
|
|
457
|
+
if (!auth?.roles.includes('admin')) {
|
|
458
|
+
delete result.internalNotes;
|
|
459
|
+
delete result.costPrice;
|
|
460
|
+
delete result.supplierInfo;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Show detailed info only for specific permission
|
|
464
|
+
if (!auth?.permissions.includes('resource:read:detailed')) {
|
|
465
|
+
delete result.analytics;
|
|
466
|
+
delete result.auditLog;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return result;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
474
|
+
* Example 6: Array Transformation
|
|
475
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
476
|
+
*
|
|
477
|
+
* Transform each item in an array
|
|
478
|
+
*/
|
|
479
|
+
export async function exampleArrayTransform(
|
|
480
|
+
data: any[],
|
|
481
|
+
event: H3Event,
|
|
482
|
+
auth: AuthContext | null
|
|
483
|
+
): Promise<any[]> {
|
|
484
|
+
if (!Array.isArray(data)) return data;
|
|
485
|
+
|
|
486
|
+
return data.map(item => ({
|
|
487
|
+
...item,
|
|
488
|
+
// Add permission checks for each item
|
|
489
|
+
canEdit: auth?.userId === item.ownerId,
|
|
490
|
+
canDelete: auth?.roles.includes('admin') || auth?.userId === item.ownerId,
|
|
491
|
+
}));
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
496
|
+
* Example 7: Error Handling
|
|
497
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
498
|
+
*
|
|
499
|
+
* Handle transformation errors gracefully
|
|
500
|
+
*/
|
|
501
|
+
export async function exampleErrorHandling(
|
|
502
|
+
data: any,
|
|
503
|
+
event: H3Event,
|
|
504
|
+
auth: AuthContext | null
|
|
505
|
+
): Promise<any> {
|
|
506
|
+
try {
|
|
507
|
+
// Attempt transformation
|
|
508
|
+
return {
|
|
509
|
+
...data,
|
|
510
|
+
processed: true,
|
|
511
|
+
};
|
|
512
|
+
} catch (error) {
|
|
513
|
+
console.error('Transformation error:', error);
|
|
514
|
+
|
|
515
|
+
// Return original data on error
|
|
516
|
+
return data;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
`;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Generate BFF README file
|
|
523
|
+
*/
|
|
524
|
+
export function generateBffReadme() {
|
|
525
|
+
return `# Backend for Frontend (BFF) - Transformers
|
|
526
|
+
|
|
527
|
+
This directory contains **transformer functions** that add business logic to your API routes.
|
|
528
|
+
|
|
529
|
+
## 🎯 What are Transformers?
|
|
530
|
+
|
|
531
|
+
Transformers allow you to add custom logic between the backend API and your Nuxt client:
|
|
532
|
+
|
|
533
|
+
\`\`\`
|
|
534
|
+
Client → Generated Route → Transformer → Backend API
|
|
535
|
+
↓
|
|
536
|
+
Your Logic
|
|
537
|
+
\`\`\`
|
|
538
|
+
|
|
539
|
+
## 📂 File Structure
|
|
540
|
+
|
|
541
|
+
\`\`\`
|
|
542
|
+
server/
|
|
543
|
+
auth/
|
|
544
|
+
context.ts ← Authentication logic (YOU implement once)
|
|
545
|
+
types.ts ← Auth types and helpers
|
|
546
|
+
api/ ← Generated routes (NEVER edit these)
|
|
547
|
+
pet/
|
|
548
|
+
[id].get.ts ← Calls transformPet() automatically
|
|
549
|
+
bff/
|
|
550
|
+
transformers/ ← Your business logic (SAFE to edit)
|
|
551
|
+
pet.ts ← Transform pet data
|
|
552
|
+
store.ts ← Transform store data
|
|
553
|
+
_transformers.example.ts ← Examples (regenerated for reference)
|
|
554
|
+
README.md ← This file
|
|
555
|
+
\`\`\`
|
|
556
|
+
|
|
557
|
+
## 🔒 Which Files Are Safe to Edit?
|
|
558
|
+
|
|
559
|
+
| File | Safe to Edit? | Regenerated? |
|
|
560
|
+
|------|---------------|--------------|
|
|
561
|
+
| \`server/auth/context.ts\` | ✅ YES | ❌ NO |
|
|
562
|
+
| \`server/auth/types.ts\` | ✅ YES | ❌ NO |
|
|
563
|
+
| \`server/bff/transformers/*.ts\` | ✅ YES | ❌ NO |
|
|
564
|
+
| \`server/api/**/*.ts\` | ❌ NO | ✅ YES |
|
|
565
|
+
| \`server/bff/_transformers.example.ts\` | ❌ NO | ✅ YES |
|
|
566
|
+
|
|
567
|
+
## 🚀 Quick Start
|
|
568
|
+
|
|
569
|
+
### 1. Implement Authentication (Optional)
|
|
570
|
+
|
|
571
|
+
Edit \`server/auth/context.ts\` and implement \`getAuthContext()\`:
|
|
572
|
+
|
|
573
|
+
\`\`\`typescript
|
|
574
|
+
// server/auth/context.ts
|
|
575
|
+
import { getServerSession } from '#auth';
|
|
576
|
+
|
|
577
|
+
export async function getAuthContext(event: H3Event): Promise<AuthContext> {
|
|
578
|
+
const session = await getServerSession(event);
|
|
579
|
+
|
|
580
|
+
return {
|
|
581
|
+
isAuthenticated: !!session,
|
|
582
|
+
userId: session?.user.id ?? null,
|
|
583
|
+
roles: session?.user.roles ?? [],
|
|
584
|
+
permissions: session?.user.permissions ?? [],
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
\`\`\`
|
|
588
|
+
|
|
589
|
+
### 2. Add Transformation Logic
|
|
590
|
+
|
|
591
|
+
Edit transformer files in \`server/bff/transformers/\`:
|
|
592
|
+
|
|
593
|
+
\`\`\`typescript
|
|
594
|
+
// server/bff/transformers/pet.ts
|
|
595
|
+
export async function transformPet(
|
|
596
|
+
data: any,
|
|
597
|
+
event: H3Event,
|
|
598
|
+
auth: AuthContext | null
|
|
599
|
+
): Promise<any> {
|
|
600
|
+
// Add permission flags
|
|
601
|
+
return {
|
|
602
|
+
...data,
|
|
603
|
+
canEdit: auth?.permissions.includes('pet:write') ?? false,
|
|
604
|
+
canDelete: auth?.permissions.includes('pet:delete') ?? false,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
\`\`\`
|
|
608
|
+
|
|
609
|
+
### 3. Use From Client
|
|
610
|
+
|
|
611
|
+
No changes needed - your generated routes automatically use transformers:
|
|
612
|
+
|
|
613
|
+
\`\`\`vue
|
|
614
|
+
<script setup>
|
|
615
|
+
const { data: pet } = await useFetch('/api/pet/123');
|
|
616
|
+
|
|
617
|
+
// pet.canEdit and pet.canDelete are now available!
|
|
618
|
+
</script>
|
|
619
|
+
\`\`\`
|
|
620
|
+
|
|
621
|
+
## 📚 Common Use Cases
|
|
622
|
+
|
|
623
|
+
### Filter Sensitive Data
|
|
624
|
+
|
|
625
|
+
\`\`\`typescript
|
|
626
|
+
export async function transformUser(data: any, event: H3Event, auth: AuthContext | null) {
|
|
627
|
+
const { password, ssn, internalId, ...safe } = data;
|
|
628
|
+
return safe;
|
|
629
|
+
}
|
|
630
|
+
\`\`\`
|
|
631
|
+
|
|
632
|
+
### Add Permission Flags
|
|
633
|
+
|
|
634
|
+
\`\`\`typescript
|
|
635
|
+
export async function transformPost(data: any, event: H3Event, auth: AuthContext | null) {
|
|
636
|
+
return {
|
|
637
|
+
...data,
|
|
638
|
+
canEdit: auth?.userId === data.authorId,
|
|
639
|
+
canDelete: auth?.roles.includes('admin'),
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
\`\`\`
|
|
643
|
+
|
|
644
|
+
### Combine Multiple Sources
|
|
645
|
+
|
|
646
|
+
\`\`\`typescript
|
|
647
|
+
export async function transformProduct(data: any, event: H3Event, auth: AuthContext | null) {
|
|
648
|
+
const config = useRuntimeConfig();
|
|
649
|
+
|
|
650
|
+
const [reviews, inventory] = await Promise.all([
|
|
651
|
+
$fetch(\`\${config.apiBaseUrl}/reviews/\${data.id}\`),
|
|
652
|
+
$fetch(\`\${config.apiBaseUrl}/inventory/\${data.id}\`),
|
|
653
|
+
]);
|
|
654
|
+
|
|
655
|
+
return {
|
|
656
|
+
...data,
|
|
657
|
+
reviews,
|
|
658
|
+
inventory,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
\`\`\`
|
|
662
|
+
|
|
663
|
+
### Permission-Based Filtering
|
|
664
|
+
|
|
665
|
+
\`\`\`typescript
|
|
666
|
+
export async function transformReport(data: any, event: H3Event, auth: AuthContext | null) {
|
|
667
|
+
const result = { ...data };
|
|
668
|
+
|
|
669
|
+
// Hide sensitive data for non-admins
|
|
670
|
+
if (!auth?.roles.includes('admin')) {
|
|
671
|
+
delete result.financialDetails;
|
|
672
|
+
delete result.internalNotes;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
return result;
|
|
676
|
+
}
|
|
677
|
+
\`\`\`
|
|
678
|
+
|
|
679
|
+
## 🔄 Regeneration Safety
|
|
680
|
+
|
|
681
|
+
**IMPORTANT:** When you regenerate routes with the CLI:
|
|
682
|
+
- ✅ Transformer files are **PRESERVED**
|
|
683
|
+
- ✅ Auth context files are **PRESERVED**
|
|
684
|
+
- ❌ Generated routes are **OVERWRITTEN** (but this is OK!)
|
|
685
|
+
|
|
686
|
+
Your custom logic is always safe because it lives in separate files.
|
|
687
|
+
|
|
688
|
+
## 🎓 Best Practices
|
|
689
|
+
|
|
690
|
+
1. **Keep Transformers Pure**: Avoid side effects, focus on data transformation
|
|
691
|
+
2. **One Transformer Per Resource**: \`pet.ts\`, \`store.ts\`, etc.
|
|
692
|
+
3. **Use TypeScript**: Add proper types for better IntelliSense
|
|
693
|
+
4. **Document Your Logic**: Add comments explaining business rules
|
|
694
|
+
5. **Test Edge Cases**: Handle \`null\`, arrays, missing fields gracefully
|
|
695
|
+
6. **Performance**: Use \`Promise.all()\` for parallel fetches
|
|
696
|
+
7. **Security**: Always validate auth before accessing sensitive data
|
|
697
|
+
|
|
698
|
+
## 🆘 Troubleshooting
|
|
699
|
+
|
|
700
|
+
**Transformer not being called?**
|
|
701
|
+
- Check that the transformer file exists
|
|
702
|
+
- Verify the function name matches: \`transform{Resource}\`
|
|
703
|
+
- Check console for import errors
|
|
704
|
+
|
|
705
|
+
**Auth context is null?**
|
|
706
|
+
- Verify \`getAuthContext()\` is implemented in \`server/auth/context.ts\`
|
|
707
|
+
- Check for errors in the auth implementation
|
|
708
|
+
- Auth is optional - transformers work without it
|
|
709
|
+
|
|
710
|
+
**Types not working?**
|
|
711
|
+
- Ensure types are exported from \`~/types/api\`
|
|
712
|
+
- Add explicit type parameters to transformer functions
|
|
713
|
+
- Check TypeScript errors in the console
|
|
714
|
+
|
|
715
|
+
## 📖 Learn More
|
|
716
|
+
|
|
717
|
+
See \`_transformers.example.ts\` for more examples.
|
|
718
|
+
`;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Extract base type name from a type string
|
|
722
|
+
* Examples:
|
|
723
|
+
* "Pet" -> "Pet"
|
|
724
|
+
* "Pet[]" -> "Pet"
|
|
725
|
+
* "Array<Pet>" -> "Pet"
|
|
726
|
+
*/
|
|
727
|
+
function extractBaseType(typeString) {
|
|
728
|
+
// Remove array brackets
|
|
729
|
+
let baseType = typeString.replace(/\[\]$/, '').replace(/^Array<(.+)>$/, '$1');
|
|
730
|
+
// Remove whitespace
|
|
731
|
+
baseType = baseType.trim();
|
|
732
|
+
// Only return if it looks like a custom type (starts with capital)
|
|
733
|
+
if (baseType && /^[A-Z]/.test(baseType)) {
|
|
734
|
+
return baseType;
|
|
735
|
+
}
|
|
736
|
+
return null;
|
|
737
|
+
}
|