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