@xbg.solutions/create-frontend 1.1.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/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +4 -0
- package/lib/index.js.map +1 -0
- package/package.json +21 -0
- package/src/generate-component.cjs +602 -0
- package/src/generate-route.cjs +774 -0
- package/src/generate-service.cjs +1306 -0
- package/src/index.ts +2 -0
- package/src/manage-auth-users.cjs +410 -0
- package/src/setup.cjs +1049 -0
- package/src/validate-setup.cjs +341 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,1306 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Service Generator
|
|
5
|
+
*
|
|
6
|
+
* Generates a new service with TypeScript support, error handling, and tests.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node __scripts__/generate-service.js <ServiceName> [options]
|
|
10
|
+
* npm run generate:service <ServiceName> [options]
|
|
11
|
+
*
|
|
12
|
+
* Options:
|
|
13
|
+
* --type <type> Service type: api|store|util|auth|data (default: api)
|
|
14
|
+
* --with-test Generate test file
|
|
15
|
+
* --with-types Generate TypeScript types file
|
|
16
|
+
* --with-cache Add caching functionality
|
|
17
|
+
* --with-events Add event publishing
|
|
18
|
+
*
|
|
19
|
+
* Examples:
|
|
20
|
+
* node __scripts__/generate-service.js UserService --type=api --with-test --with-cache
|
|
21
|
+
* node __scripts__/generate-service.js NotificationService --with-events --with-test
|
|
22
|
+
* node __scripts__/generate-service.js DataProcessor --type=util --with-types
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
|
|
28
|
+
// Parse command line arguments
|
|
29
|
+
const args = process.argv.slice(2);
|
|
30
|
+
const serviceName = args[0];
|
|
31
|
+
|
|
32
|
+
if (!serviceName) {
|
|
33
|
+
console.error('โ Service name is required!');
|
|
34
|
+
console.log('Usage: node __scripts__/generate-service.js <ServiceName> [options]');
|
|
35
|
+
console.log('Examples:');
|
|
36
|
+
console.log(' node __scripts__/generate-service.js UserService --type=api');
|
|
37
|
+
console.log(' node __scripts__/generate-service.js NotificationStore --type=store');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Parse options
|
|
42
|
+
const options = {
|
|
43
|
+
type: 'api',
|
|
44
|
+
withTest: false,
|
|
45
|
+
withTypes: false,
|
|
46
|
+
withCache: false,
|
|
47
|
+
withEvents: false
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
args.slice(1).forEach(arg => {
|
|
51
|
+
if (arg.startsWith('--type=')) {
|
|
52
|
+
options.type = arg.split('=')[1];
|
|
53
|
+
} else if (arg === '--with-test') {
|
|
54
|
+
options.withTest = true;
|
|
55
|
+
} else if (arg === '--with-types') {
|
|
56
|
+
options.withTypes = true;
|
|
57
|
+
} else if (arg === '--with-cache') {
|
|
58
|
+
options.withCache = true;
|
|
59
|
+
} else if (arg === '--with-events') {
|
|
60
|
+
options.withEvents = true;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Validate service name
|
|
65
|
+
if (!/^[A-Z][a-zA-Z0-9]*$/.test(serviceName)) {
|
|
66
|
+
console.error('โ Service name must be PascalCase (e.g., UserService, DataProcessor)');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Validate service type
|
|
71
|
+
if (!['api', 'store', 'util', 'auth', 'data'].includes(options.type)) {
|
|
72
|
+
console.error('โ Invalid service type. Must be: api, store, util, auth, or data');
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Generate file paths
|
|
77
|
+
const serviceTypePath = {
|
|
78
|
+
api: 'src/lib/services/api',
|
|
79
|
+
store: 'src/lib/stores',
|
|
80
|
+
util: 'src/lib/utils',
|
|
81
|
+
auth: 'src/lib/services/auth',
|
|
82
|
+
data: 'src/lib/services/data'
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const basePath = serviceTypePath[options.type];
|
|
86
|
+
const kebabName = serviceName.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
87
|
+
const fileName = kebabName.replace('-service', '') + (options.type === 'store' ? '.store' : options.type === 'util' ? '' : '.service');
|
|
88
|
+
const serviceFile = path.join(basePath, `${fileName}.ts`);
|
|
89
|
+
const testFile = path.join(basePath, `${fileName}.test.ts`);
|
|
90
|
+
const typesFile = path.join(basePath, `${fileName}.types.ts`);
|
|
91
|
+
|
|
92
|
+
// Ensure directory exists
|
|
93
|
+
const dir = path.dirname(serviceFile);
|
|
94
|
+
if (!fs.existsSync(dir)) {
|
|
95
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Generate service template based on type
|
|
99
|
+
function generateServiceTemplate() {
|
|
100
|
+
const templates = {
|
|
101
|
+
api: generateApiServiceTemplate(),
|
|
102
|
+
store: generateStoreServiceTemplate(),
|
|
103
|
+
util: generateUtilServiceTemplate(),
|
|
104
|
+
auth: generateAuthServiceTemplate(),
|
|
105
|
+
data: generateDataServiceTemplate()
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return templates[options.type];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function generateApiServiceTemplate() {
|
|
112
|
+
const cacheImport = options.withCache ? `import { cacheService } from '$lib/services/cache/cache.service';` : '';
|
|
113
|
+
const eventsImport = options.withEvents ? `import { publish } from '$lib/services/events';` : '';
|
|
114
|
+
const typesImport = options.withTypes ? `import type { ${serviceName}Types } from './${fileName}.types';` : '';
|
|
115
|
+
|
|
116
|
+
const cacheLogic = options.withCache ? `
|
|
117
|
+
// Cache configuration
|
|
118
|
+
private cacheKey = '${kebabName}';
|
|
119
|
+
private cacheTTL = 5 * 60 * 1000; // 5 minutes` : '';
|
|
120
|
+
|
|
121
|
+
const eventLogic = options.withEvents ? `
|
|
122
|
+
// Event publishing
|
|
123
|
+
private publishEvent(eventType: string, data: any) {
|
|
124
|
+
publish(\`${kebabName}:\${eventType}\`, {
|
|
125
|
+
timestamp: Date.now(),
|
|
126
|
+
source: '${serviceName}',
|
|
127
|
+
data
|
|
128
|
+
});
|
|
129
|
+
}` : '';
|
|
130
|
+
|
|
131
|
+
return `/**
|
|
132
|
+
* ${serviceName}
|
|
133
|
+
*
|
|
134
|
+
* API service for managing ${serviceName.replace('Service', '').toLowerCase()} operations.
|
|
135
|
+
* Handles CRUD operations, error handling, and data transformation.
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
import { apiService } from '$lib/services/api/api.service';
|
|
139
|
+
import { handleError } from '$lib/utils/error-handler';
|
|
140
|
+
import { loggerService } from '$lib/services/logging/logging.service';${cacheImport}${eventsImport}${typesImport}
|
|
141
|
+
|
|
142
|
+
// Types
|
|
143
|
+
export interface ${serviceName.replace('Service', '')}Item {
|
|
144
|
+
id: string;
|
|
145
|
+
name: string;
|
|
146
|
+
description?: string;
|
|
147
|
+
createdAt: Date;
|
|
148
|
+
updatedAt: Date;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface Create${serviceName.replace('Service', '')}Request {
|
|
152
|
+
name: string;
|
|
153
|
+
description?: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface Update${serviceName.replace('Service', '')}Request {
|
|
157
|
+
name?: string;
|
|
158
|
+
description?: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface ${serviceName.replace('Service', '')}ListOptions {
|
|
162
|
+
limit?: number;
|
|
163
|
+
offset?: number;
|
|
164
|
+
search?: string;
|
|
165
|
+
sortBy?: string;
|
|
166
|
+
sortOrder?: 'asc' | 'desc';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface ${serviceName.replace('Service', '')}Response {
|
|
170
|
+
data: ${serviceName.replace('Service', '')}Item[];
|
|
171
|
+
meta: {
|
|
172
|
+
total: number;
|
|
173
|
+
limit: number;
|
|
174
|
+
offset: number;
|
|
175
|
+
hasMore: boolean;
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
class ${serviceName} {
|
|
180
|
+
private baseUrl = '/api/${kebabName}';${cacheLogic}${eventLogic}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get all items with optional filtering and pagination
|
|
184
|
+
*/
|
|
185
|
+
async getAll(options: ${serviceName.replace('Service', '')}ListOptions = {}): Promise<${serviceName.replace('Service', '')}Response> {
|
|
186
|
+
try {
|
|
187
|
+
const params = new URLSearchParams();
|
|
188
|
+
|
|
189
|
+
if (options.limit) params.set('limit', options.limit.toString());
|
|
190
|
+
if (options.offset) params.set('offset', options.offset.toString());
|
|
191
|
+
if (options.search) params.set('search', options.search);
|
|
192
|
+
if (options.sortBy) params.set('sortBy', options.sortBy);
|
|
193
|
+
if (options.sortOrder) params.set('sortOrder', options.sortOrder);
|
|
194
|
+
|
|
195
|
+
const url = \`\${this.baseUrl}?\${params.toString()}\`;${options.withCache ? `
|
|
196
|
+
|
|
197
|
+
// Check cache first
|
|
198
|
+
const cached = await cacheService.get<${serviceName.replace('Service', '')}Response>(url);
|
|
199
|
+
if (cached) {
|
|
200
|
+
loggerService.debug('${serviceName}: Returning cached data');
|
|
201
|
+
return cached;
|
|
202
|
+
}` : ''}
|
|
203
|
+
|
|
204
|
+
const response = await apiService.get<${serviceName.replace('Service', '')}Response>(url);${options.withCache ? `
|
|
205
|
+
|
|
206
|
+
// Cache the response
|
|
207
|
+
await cacheService.set(url, response.data, this.cacheTTL);` : ''}${options.withEvents ? `
|
|
208
|
+
|
|
209
|
+
this.publishEvent('list', { count: response.data.data.length, options });` : ''}
|
|
210
|
+
|
|
211
|
+
return response.data;
|
|
212
|
+
|
|
213
|
+
} catch (error) {
|
|
214
|
+
loggerService.error('${serviceName}: Failed to get all items', { error, options });
|
|
215
|
+
throw handleError(error, 'Failed to load ${serviceName.replace('Service', '').toLowerCase()}s');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get a single item by ID
|
|
221
|
+
*/
|
|
222
|
+
async getById(id: string): Promise<${serviceName.replace('Service', '')}Item> {
|
|
223
|
+
if (!id) {
|
|
224
|
+
throw new Error('ID is required');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const url = \`\${this.baseUrl}/\${id}\`;${options.withCache ? `
|
|
229
|
+
|
|
230
|
+
// Check cache first
|
|
231
|
+
const cached = await cacheService.get<${serviceName.replace('Service', '')}Item>(url);
|
|
232
|
+
if (cached) {
|
|
233
|
+
return cached;
|
|
234
|
+
}` : ''}
|
|
235
|
+
|
|
236
|
+
const response = await apiService.get<${serviceName.replace('Service', '')}Item>(url);${options.withCache ? `
|
|
237
|
+
|
|
238
|
+
// Cache the response
|
|
239
|
+
await cacheService.set(url, response.data, this.cacheTTL);` : ''}${options.withEvents ? `
|
|
240
|
+
|
|
241
|
+
this.publishEvent('get', { id });` : ''}
|
|
242
|
+
|
|
243
|
+
return response.data;
|
|
244
|
+
|
|
245
|
+
} catch (error) {
|
|
246
|
+
loggerService.error('${serviceName}: Failed to get item by ID', { error, id });
|
|
247
|
+
throw handleError(error, \`Failed to load \${serviceName.replace('Service', '').toLowerCase()}\`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Create a new item
|
|
253
|
+
*/
|
|
254
|
+
async create(data: Create${serviceName.replace('Service', '')}Request): Promise<${serviceName.replace('Service', '')}Item> {
|
|
255
|
+
try {
|
|
256
|
+
// Validate input
|
|
257
|
+
if (!data.name?.trim()) {
|
|
258
|
+
throw new Error('Name is required');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const response = await apiService.post<${serviceName.replace('Service', '')}Item>(this.baseUrl, data);${options.withCache ? `
|
|
262
|
+
|
|
263
|
+
// Invalidate related cache entries
|
|
264
|
+
await this.invalidateCache();` : ''}${options.withEvents ? `
|
|
265
|
+
|
|
266
|
+
this.publishEvent('create', { item: response.data });` : ''}
|
|
267
|
+
|
|
268
|
+
loggerService.info('${serviceName}: Item created successfully', { id: response.data.id });
|
|
269
|
+
return response.data;
|
|
270
|
+
|
|
271
|
+
} catch (error) {
|
|
272
|
+
loggerService.error('${serviceName}: Failed to create item', { error, data });
|
|
273
|
+
throw handleError(error, 'Failed to create ${serviceName.replace('Service', '').toLowerCase()}');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Update an existing item
|
|
279
|
+
*/
|
|
280
|
+
async update(id: string, data: Update${serviceName.replace('Service', '')}Request): Promise<${serviceName.replace('Service', '')}Item> {
|
|
281
|
+
if (!id) {
|
|
282
|
+
throw new Error('ID is required');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
const response = await apiService.put<${serviceName.replace('Service', '')}Item>(\`\${this.baseUrl}/\${id}\`, data);${options.withCache ? `
|
|
287
|
+
|
|
288
|
+
// Invalidate cache
|
|
289
|
+
await this.invalidateCache();
|
|
290
|
+
await cacheService.delete(\`\${this.baseUrl}/\${id}\`);` : ''}${options.withEvents ? `
|
|
291
|
+
|
|
292
|
+
this.publishEvent('update', { id, item: response.data });` : ''}
|
|
293
|
+
|
|
294
|
+
loggerService.info('${serviceName}: Item updated successfully', { id });
|
|
295
|
+
return response.data;
|
|
296
|
+
|
|
297
|
+
} catch (error) {
|
|
298
|
+
loggerService.error('${serviceName}: Failed to update item', { error, id, data });
|
|
299
|
+
throw handleError(error, 'Failed to update ${serviceName.replace('Service', '').toLowerCase()}');
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Delete an item
|
|
305
|
+
*/
|
|
306
|
+
async delete(id: string): Promise<void> {
|
|
307
|
+
if (!id) {
|
|
308
|
+
throw new Error('ID is required');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
await apiService.delete(\`\${this.baseUrl}/\${id}\`);${options.withCache ? `
|
|
313
|
+
|
|
314
|
+
// Invalidate cache
|
|
315
|
+
await this.invalidateCache();
|
|
316
|
+
await cacheService.delete(\`\${this.baseUrl}/\${id}\`);` : ''}${options.withEvents ? `
|
|
317
|
+
|
|
318
|
+
this.publishEvent('delete', { id });` : ''}
|
|
319
|
+
|
|
320
|
+
loggerService.info('${serviceName}: Item deleted successfully', { id });
|
|
321
|
+
|
|
322
|
+
} catch (error) {
|
|
323
|
+
loggerService.error('${serviceName}: Failed to delete item', { error, id });
|
|
324
|
+
throw handleError(error, 'Failed to delete ${serviceName.replace('Service', '').toLowerCase()}');
|
|
325
|
+
}
|
|
326
|
+
}${options.withCache ? `
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Clear all cached data for this service
|
|
330
|
+
*/
|
|
331
|
+
async clearCache(): Promise<void> {
|
|
332
|
+
await this.invalidateCache();
|
|
333
|
+
loggerService.debug('${serviceName}: Cache cleared');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Invalidate cache entries
|
|
338
|
+
*/
|
|
339
|
+
private async invalidateCache(): Promise<void> {
|
|
340
|
+
await cacheService.deleteByPattern(\`\${this.baseUrl}*\`);
|
|
341
|
+
}` : ''}${options.withEvents ? `
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Subscribe to service events
|
|
345
|
+
*/
|
|
346
|
+
onEvent(eventType: string, callback: (data: any) => void): () => void {
|
|
347
|
+
const fullEventType = \`${kebabName}:\${eventType}\`;
|
|
348
|
+
// Implementation depends on your event system
|
|
349
|
+
// Return unsubscribe function
|
|
350
|
+
return () => {};
|
|
351
|
+
}` : ''}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Export singleton instance
|
|
355
|
+
export const ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)} = new ${serviceName}();
|
|
356
|
+
export default ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)};`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function generateStoreServiceTemplate() {
|
|
360
|
+
return `/**
|
|
361
|
+
* ${serviceName}
|
|
362
|
+
*
|
|
363
|
+
* Svelte store for managing ${serviceName.replace('Service', '').replace('Store', '').toLowerCase()} state.
|
|
364
|
+
*/
|
|
365
|
+
|
|
366
|
+
import { writable, derived, type Writable, type Readable } from 'svelte/store';
|
|
367
|
+
import { browser } from '$app/environment';${options.withEvents ? `
|
|
368
|
+
import { publish } from '$lib/services/events';` : ''}
|
|
369
|
+
|
|
370
|
+
// Types
|
|
371
|
+
export interface ${serviceName.replace('Service', '').replace('Store', '')}State {
|
|
372
|
+
data: any[];
|
|
373
|
+
loading: boolean;
|
|
374
|
+
error: string | null;
|
|
375
|
+
lastUpdated: Date | null;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export interface ${serviceName.replace('Service', '').replace('Store', '')}Item {
|
|
379
|
+
id: string;
|
|
380
|
+
// Add your item properties here
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Initial state
|
|
384
|
+
const initialState: ${serviceName.replace('Service', '').replace('Store', '')}State = {
|
|
385
|
+
data: [],
|
|
386
|
+
loading: false,
|
|
387
|
+
error: null,
|
|
388
|
+
lastUpdated: null
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// Create the store
|
|
392
|
+
function create${serviceName}() {
|
|
393
|
+
const { subscribe, set, update }: Writable<${serviceName.replace('Service', '').replace('Store', '')}State> = writable(initialState);
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
subscribe,
|
|
397
|
+
|
|
398
|
+
// Actions
|
|
399
|
+
setLoading(loading: boolean) {
|
|
400
|
+
update(state => ({ ...state, loading }));
|
|
401
|
+
},
|
|
402
|
+
|
|
403
|
+
setError(error: string | null) {
|
|
404
|
+
update(state => ({ ...state, error, loading: false }));
|
|
405
|
+
},
|
|
406
|
+
|
|
407
|
+
setData(data: any[]) {
|
|
408
|
+
update(state => ({
|
|
409
|
+
...state,
|
|
410
|
+
data,
|
|
411
|
+
loading: false,
|
|
412
|
+
error: null,
|
|
413
|
+
lastUpdated: new Date()
|
|
414
|
+
}));${options.withEvents ? `
|
|
415
|
+
|
|
416
|
+
publish('${kebabName}:dataChanged', { count: data.length });` : ''}
|
|
417
|
+
},
|
|
418
|
+
|
|
419
|
+
addItem(item: ${serviceName.replace('Service', '').replace('Store', '')}Item) {
|
|
420
|
+
update(state => ({
|
|
421
|
+
...state,
|
|
422
|
+
data: [...state.data, item],
|
|
423
|
+
lastUpdated: new Date()
|
|
424
|
+
}));${options.withEvents ? `
|
|
425
|
+
|
|
426
|
+
publish('${kebabName}:itemAdded', item);` : ''}
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
updateItem(id: string, updates: Partial<${serviceName.replace('Service', '').replace('Store', '')}Item>) {
|
|
430
|
+
update(state => ({
|
|
431
|
+
...state,
|
|
432
|
+
data: state.data.map(item =>
|
|
433
|
+
item.id === id ? { ...item, ...updates } : item
|
|
434
|
+
),
|
|
435
|
+
lastUpdated: new Date()
|
|
436
|
+
}));${options.withEvents ? `
|
|
437
|
+
|
|
438
|
+
publish('${kebabName}:itemUpdated', { id, updates });` : ''}
|
|
439
|
+
},
|
|
440
|
+
|
|
441
|
+
removeItem(id: string) {
|
|
442
|
+
update(state => ({
|
|
443
|
+
...state,
|
|
444
|
+
data: state.data.filter(item => item.id !== id),
|
|
445
|
+
lastUpdated: new Date()
|
|
446
|
+
}));${options.withEvents ? `
|
|
447
|
+
|
|
448
|
+
publish('${kebabName}:itemRemoved', { id });` : ''}
|
|
449
|
+
},
|
|
450
|
+
|
|
451
|
+
reset() {
|
|
452
|
+
set(initialState);
|
|
453
|
+
},
|
|
454
|
+
|
|
455
|
+
// Load data from API
|
|
456
|
+
async load() {
|
|
457
|
+
this.setLoading(true);
|
|
458
|
+
|
|
459
|
+
try {
|
|
460
|
+
// Replace with your actual API call
|
|
461
|
+
// const response = await apiService.get('/api/${kebabName}');
|
|
462
|
+
// this.setData(response.data);
|
|
463
|
+
|
|
464
|
+
// Mock data for now
|
|
465
|
+
this.setData([]);
|
|
466
|
+
|
|
467
|
+
} catch (error) {
|
|
468
|
+
this.setError(error instanceof Error ? error.message : 'Failed to load data');
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Create store instance
|
|
475
|
+
export const ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)} = create${serviceName}();
|
|
476
|
+
|
|
477
|
+
// Derived stores
|
|
478
|
+
export const ${serviceName.replace('Service', '').replace('Store', '').toLowerCase()}Items: Readable<${serviceName.replace('Service', '').replace('Store', '')}Item[]> = derived(
|
|
479
|
+
${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)},
|
|
480
|
+
($store) => $store.data
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
export const ${serviceName.replace('Service', '').replace('Store', '').toLowerCase()}Loading: Readable<boolean> = derived(
|
|
484
|
+
${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)},
|
|
485
|
+
($store) => $store.loading
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
export const ${serviceName.replace('Service', '').replace('Store', '').toLowerCase()}Error: Readable<string | null> = derived(
|
|
489
|
+
${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)},
|
|
490
|
+
($store) => $store.error
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
export const ${serviceName.replace('Service', '').replace('Store', '').toLowerCase()}Count: Readable<number> = derived(
|
|
494
|
+
${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)},
|
|
495
|
+
($store) => $store.data.length
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
// Auto-load data in browser
|
|
499
|
+
if (browser) {
|
|
500
|
+
${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)}.load();
|
|
501
|
+
}`;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function generateUtilServiceTemplate() {
|
|
505
|
+
return `/**
|
|
506
|
+
* ${serviceName}
|
|
507
|
+
*
|
|
508
|
+
* Utility functions for ${serviceName.replace('Service', '').toLowerCase()} operations.
|
|
509
|
+
*/
|
|
510
|
+
|
|
511
|
+
import { loggerService } from '$lib/services/logging/logging.service';${options.withEvents ? `
|
|
512
|
+
import { publish } from '$lib/services/events';` : ''}${options.withTypes ? `
|
|
513
|
+
import type { ${serviceName}Types } from './${fileName}.types';` : ''}
|
|
514
|
+
|
|
515
|
+
// Types
|
|
516
|
+
export interface ${serviceName.replace('Service', '')}Options {
|
|
517
|
+
// Define your options here
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
export interface ${serviceName.replace('Service', '')}Result {
|
|
521
|
+
success: boolean;
|
|
522
|
+
data?: any;
|
|
523
|
+
error?: string;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Main ${serviceName.replace('Service', '').toLowerCase()} function
|
|
528
|
+
*/
|
|
529
|
+
export function ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1).replace('Service', '')}(
|
|
530
|
+
input: any,
|
|
531
|
+
options: ${serviceName.replace('Service', '')}Options = {}
|
|
532
|
+
): ${serviceName.replace('Service', '')}Result {
|
|
533
|
+
try {
|
|
534
|
+
loggerService.debug('${serviceName}: Processing input', { input, options });
|
|
535
|
+
|
|
536
|
+
// Your utility logic here
|
|
537
|
+
const result = processInput(input, options);${options.withEvents ? `
|
|
538
|
+
|
|
539
|
+
publish('${kebabName}:processed', { input, result, options });` : ''}
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
success: true,
|
|
543
|
+
data: result
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
} catch (error) {
|
|
547
|
+
loggerService.error('${serviceName}: Processing failed', { error, input, options });
|
|
548
|
+
|
|
549
|
+
return {
|
|
550
|
+
success: false,
|
|
551
|
+
error: error instanceof Error ? error.message : 'Processing failed'
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Async version of ${serviceName.replace('Service', '').toLowerCase()}
|
|
558
|
+
*/
|
|
559
|
+
export async function ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1).replace('Service', '')}Async(
|
|
560
|
+
input: any,
|
|
561
|
+
options: ${serviceName.replace('Service', '')}Options = {}
|
|
562
|
+
): Promise<${serviceName.replace('Service', '')}Result> {
|
|
563
|
+
try {
|
|
564
|
+
loggerService.debug('${serviceName}: Processing input async', { input, options });
|
|
565
|
+
|
|
566
|
+
// Your async utility logic here
|
|
567
|
+
const result = await processInputAsync(input, options);${options.withEvents ? `
|
|
568
|
+
|
|
569
|
+
publish('${kebabName}:processedAsync', { input, result, options });` : ''}
|
|
570
|
+
|
|
571
|
+
return {
|
|
572
|
+
success: true,
|
|
573
|
+
data: result
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
} catch (error) {
|
|
577
|
+
loggerService.error('${serviceName}: Async processing failed', { error, input, options });
|
|
578
|
+
|
|
579
|
+
return {
|
|
580
|
+
success: false,
|
|
581
|
+
error: error instanceof Error ? error.message : 'Async processing failed'
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Validate input data
|
|
588
|
+
*/
|
|
589
|
+
export function validate${serviceName.replace('Service', '')}Input(input: any): boolean {
|
|
590
|
+
if (!input) {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Add your validation logic here
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Transform input data
|
|
600
|
+
*/
|
|
601
|
+
export function transform${serviceName.replace('Service', '')}Input(input: any): any {
|
|
602
|
+
if (!input) {
|
|
603
|
+
throw new Error('Input is required');
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Add your transformation logic here
|
|
607
|
+
return input;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Helper functions
|
|
611
|
+
function processInput(input: any, options: ${serviceName.replace('Service', '')}Options): any {
|
|
612
|
+
// Validate input
|
|
613
|
+
if (!validate${serviceName.replace('Service', '')}Input(input)) {
|
|
614
|
+
throw new Error('Invalid input data');
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Transform input
|
|
618
|
+
const transformed = transform${serviceName.replace('Service', '')}Input(input);
|
|
619
|
+
|
|
620
|
+
// Process the data
|
|
621
|
+
// Add your processing logic here
|
|
622
|
+
|
|
623
|
+
return transformed;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
async function processInputAsync(input: any, options: ${serviceName.replace('Service', '')}Options): Promise<any> {
|
|
627
|
+
// Async processing logic
|
|
628
|
+
return new Promise((resolve, reject) => {
|
|
629
|
+
try {
|
|
630
|
+
const result = processInput(input, options);
|
|
631
|
+
// Simulate async operation
|
|
632
|
+
setTimeout(() => resolve(result), 100);
|
|
633
|
+
} catch (error) {
|
|
634
|
+
reject(error);
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Export utility object
|
|
640
|
+
export const ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)} = {
|
|
641
|
+
process: ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1).replace('Service', '')},
|
|
642
|
+
processAsync: ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1).replace('Service', '')}Async,
|
|
643
|
+
validate: validate${serviceName.replace('Service', '')}Input,
|
|
644
|
+
transform: transform${serviceName.replace('Service', '')}Input
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
export default ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)};`;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function generateAuthServiceTemplate() {
|
|
651
|
+
return `/**
|
|
652
|
+
* ${serviceName}
|
|
653
|
+
*
|
|
654
|
+
* Authentication service for ${serviceName.replace('Service', '').replace('Auth', '').toLowerCase()} operations.
|
|
655
|
+
*/
|
|
656
|
+
|
|
657
|
+
import { authService } from '$lib/services/auth/auth.service';
|
|
658
|
+
import { tokenService } from '$lib/services/token/token.service';
|
|
659
|
+
import { handleError } from '$lib/utils/error-handler';
|
|
660
|
+
import { loggerService } from '$lib/services/logging/logging.service';${options.withEvents ? `
|
|
661
|
+
import { publish } from '$lib/services/events';` : ''}
|
|
662
|
+
|
|
663
|
+
// Types
|
|
664
|
+
export interface ${serviceName.replace('Service', '')}Credentials {
|
|
665
|
+
// Define your credential structure
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
export interface ${serviceName.replace('Service', '')}Result {
|
|
669
|
+
success: boolean;
|
|
670
|
+
user?: any;
|
|
671
|
+
token?: string;
|
|
672
|
+
error?: string;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
class ${serviceName} {
|
|
676
|
+
/**
|
|
677
|
+
* Authenticate user
|
|
678
|
+
*/
|
|
679
|
+
async authenticate(credentials: ${serviceName.replace('Service', '')}Credentials): Promise<${serviceName.replace('Service', '')}Result> {
|
|
680
|
+
try {
|
|
681
|
+
loggerService.info('${serviceName}: Starting authentication');
|
|
682
|
+
|
|
683
|
+
// Validate credentials
|
|
684
|
+
if (!this.validateCredentials(credentials)) {
|
|
685
|
+
throw new Error('Invalid credentials');
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Perform authentication logic
|
|
689
|
+
const authResult = await this.performAuth(credentials);
|
|
690
|
+
|
|
691
|
+
if (!authResult.success) {
|
|
692
|
+
throw new Error(authResult.error || 'Authentication failed');
|
|
693
|
+
}${options.withEvents ? `
|
|
694
|
+
|
|
695
|
+
publish('${kebabName}:authenticated', {
|
|
696
|
+
userId: authResult.user?.id,
|
|
697
|
+
timestamp: Date.now()
|
|
698
|
+
});` : ''}
|
|
699
|
+
|
|
700
|
+
loggerService.info('${serviceName}: Authentication successful');
|
|
701
|
+
return authResult;
|
|
702
|
+
|
|
703
|
+
} catch (error) {
|
|
704
|
+
loggerService.error('${serviceName}: Authentication failed', { error });
|
|
705
|
+
return {
|
|
706
|
+
success: false,
|
|
707
|
+
error: error instanceof Error ? error.message : 'Authentication failed'
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Validate user session
|
|
714
|
+
*/
|
|
715
|
+
async validateSession(): Promise<boolean> {
|
|
716
|
+
try {
|
|
717
|
+
// Check if user is authenticated
|
|
718
|
+
const isAuthenticated = await authService.isAuthenticated();
|
|
719
|
+
if (!isAuthenticated) {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Validate token
|
|
724
|
+
const token = await tokenService.getToken();
|
|
725
|
+
if (!token) {
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// Additional validation logic here
|
|
730
|
+
return true;
|
|
731
|
+
|
|
732
|
+
} catch (error) {
|
|
733
|
+
loggerService.error('${serviceName}: Session validation failed', { error });
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Refresh authentication
|
|
740
|
+
*/
|
|
741
|
+
async refresh(): Promise<${serviceName.replace('Service', '')}Result> {
|
|
742
|
+
try {
|
|
743
|
+
loggerService.info('${serviceName}: Refreshing authentication');
|
|
744
|
+
|
|
745
|
+
// Get current token
|
|
746
|
+
const currentToken = await tokenService.getToken();
|
|
747
|
+
if (!currentToken) {
|
|
748
|
+
throw new Error('No token to refresh');
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Refresh token
|
|
752
|
+
await tokenService.refreshToken();
|
|
753
|
+
|
|
754
|
+
return {
|
|
755
|
+
success: true
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
} catch (error) {
|
|
759
|
+
loggerService.error('${serviceName}: Refresh failed', { error });
|
|
760
|
+
return {
|
|
761
|
+
success: false,
|
|
762
|
+
error: error instanceof Error ? error.message : 'Refresh failed'
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Sign out user
|
|
769
|
+
*/
|
|
770
|
+
async signOut(): Promise<void> {
|
|
771
|
+
try {
|
|
772
|
+
loggerService.info('${serviceName}: Signing out user');
|
|
773
|
+
|
|
774
|
+
// Sign out from main auth service
|
|
775
|
+
await authService.logout();${options.withEvents ? `
|
|
776
|
+
|
|
777
|
+
publish('${kebabName}:signedOut', {
|
|
778
|
+
timestamp: Date.now()
|
|
779
|
+
});` : ''}
|
|
780
|
+
|
|
781
|
+
} catch (error) {
|
|
782
|
+
loggerService.error('${serviceName}: Sign out failed', { error });
|
|
783
|
+
throw handleError(error, 'Sign out failed');
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Helper methods
|
|
788
|
+
private validateCredentials(credentials: ${serviceName.replace('Service', '')}Credentials): boolean {
|
|
789
|
+
// Add your credential validation logic
|
|
790
|
+
return !!credentials;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
private async performAuth(credentials: ${serviceName.replace('Service', '')}Credentials): Promise<${serviceName.replace('Service', '')}Result> {
|
|
794
|
+
// Add your authentication logic here
|
|
795
|
+
// This might involve API calls, token exchange, etc.
|
|
796
|
+
|
|
797
|
+
return {
|
|
798
|
+
success: true,
|
|
799
|
+
user: {
|
|
800
|
+
id: 'user-id',
|
|
801
|
+
// Add user properties
|
|
802
|
+
},
|
|
803
|
+
token: 'auth-token'
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Export singleton instance
|
|
809
|
+
export const ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)} = new ${serviceName}();
|
|
810
|
+
export default ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)};`;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function generateDataServiceTemplate() {
|
|
814
|
+
return `/**
|
|
815
|
+
* ${serviceName}
|
|
816
|
+
*
|
|
817
|
+
* Data service for managing ${serviceName.replace('Service', '').replace('Data', '').toLowerCase()} data operations.
|
|
818
|
+
*/
|
|
819
|
+
|
|
820
|
+
import { apiService } from '$lib/services/api/api.service';
|
|
821
|
+
import { handleError } from '$lib/utils/error-handler';
|
|
822
|
+
import { loggerService } from '$lib/services/logging/logging.service';${options.withCache ? `
|
|
823
|
+
import { cacheService } from '$lib/services/cache/cache.service';` : ''}${options.withEvents ? `
|
|
824
|
+
import { publish } from '$lib/services/events';` : ''}
|
|
825
|
+
|
|
826
|
+
// Types
|
|
827
|
+
export interface ${serviceName.replace('Service', '').replace('Data', '')}DataItem {
|
|
828
|
+
id: string;
|
|
829
|
+
// Define your data structure
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
export interface ${serviceName.replace('Service', '').replace('Data', '')}Query {
|
|
833
|
+
filter?: Record<string, any>;
|
|
834
|
+
sort?: Record<string, 'asc' | 'desc'>;
|
|
835
|
+
limit?: number;
|
|
836
|
+
offset?: number;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
export interface ${serviceName.replace('Service', '').replace('Data', '')}QueryResult {
|
|
840
|
+
data: ${serviceName.replace('Service', '').replace('Data', '')}DataItem[];
|
|
841
|
+
total: number;
|
|
842
|
+
hasMore: boolean;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
class ${serviceName} {
|
|
846
|
+
private baseUrl = '/api/data/${kebabName}';${options.withCache ? `
|
|
847
|
+
private cachePrefix = '${kebabName}:';
|
|
848
|
+
private cacheTTL = 10 * 60 * 1000; // 10 minutes` : ''}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Query data with filtering and pagination
|
|
852
|
+
*/
|
|
853
|
+
async query(query: ${serviceName.replace('Service', '').replace('Data', '')}Query = {}): Promise<${serviceName.replace('Service', '').replace('Data', '')}QueryResult> {
|
|
854
|
+
try {
|
|
855
|
+
loggerService.debug('${serviceName}: Querying data', { query });${options.withCache ? `
|
|
856
|
+
|
|
857
|
+
// Check cache
|
|
858
|
+
const cacheKey = this.cachePrefix + JSON.stringify(query);
|
|
859
|
+
const cached = await cacheService.get<${serviceName.replace('Service', '').replace('Data', '')}QueryResult>(cacheKey);
|
|
860
|
+
if (cached) {
|
|
861
|
+
loggerService.debug('${serviceName}: Returning cached query result');
|
|
862
|
+
return cached;
|
|
863
|
+
}` : ''}
|
|
864
|
+
|
|
865
|
+
// Build query parameters
|
|
866
|
+
const params = new URLSearchParams();
|
|
867
|
+
if (query.filter) params.set('filter', JSON.stringify(query.filter));
|
|
868
|
+
if (query.sort) params.set('sort', JSON.stringify(query.sort));
|
|
869
|
+
if (query.limit) params.set('limit', query.limit.toString());
|
|
870
|
+
if (query.offset) params.set('offset', query.offset.toString());
|
|
871
|
+
|
|
872
|
+
const response = await apiService.get<${serviceName.replace('Service', '').replace('Data', '')}QueryResult>(
|
|
873
|
+
\`\${this.baseUrl}?\${params.toString()}\`
|
|
874
|
+
);${options.withCache ? `
|
|
875
|
+
|
|
876
|
+
// Cache the result
|
|
877
|
+
await cacheService.set(cacheKey, response.data, this.cacheTTL);` : ''}${options.withEvents ? `
|
|
878
|
+
|
|
879
|
+
publish('${kebabName}:queried', { query, resultCount: response.data.data.length });` : ''}
|
|
880
|
+
|
|
881
|
+
return response.data;
|
|
882
|
+
|
|
883
|
+
} catch (error) {
|
|
884
|
+
loggerService.error('${serviceName}: Query failed', { error, query });
|
|
885
|
+
throw handleError(error, 'Failed to query data');
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Get data by ID
|
|
891
|
+
*/
|
|
892
|
+
async getById(id: string): Promise<${serviceName.replace('Service', '').replace('Data', '')}DataItem | null> {
|
|
893
|
+
if (!id) {
|
|
894
|
+
throw new Error('ID is required');
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
try {${options.withCache ? `
|
|
898
|
+
// Check cache
|
|
899
|
+
const cacheKey = \`\${this.cachePrefix}item:\${id}\`;
|
|
900
|
+
const cached = await cacheService.get<${serviceName.replace('Service', '').replace('Data', '')}DataItem>(cacheKey);
|
|
901
|
+
if (cached) {
|
|
902
|
+
return cached;
|
|
903
|
+
}` : ''}
|
|
904
|
+
|
|
905
|
+
const response = await apiService.get<${serviceName.replace('Service', '').replace('Data', '')}DataItem>(\`\${this.baseUrl}/\${id}\`);${options.withCache ? `
|
|
906
|
+
|
|
907
|
+
// Cache the item
|
|
908
|
+
await cacheService.set(cacheKey, response.data, this.cacheTTL);` : ''}
|
|
909
|
+
|
|
910
|
+
return response.data;
|
|
911
|
+
|
|
912
|
+
} catch (error) {
|
|
913
|
+
if (error.status === 404) {
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
loggerService.error('${serviceName}: Get by ID failed', { error, id });
|
|
917
|
+
throw handleError(error, 'Failed to get data item');
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Save data item
|
|
923
|
+
*/
|
|
924
|
+
async save(item: Omit<${serviceName.replace('Service', '').replace('Data', '')}DataItem, 'id'> | ${serviceName.replace('Service', '').replace('Data', '')}DataItem): Promise<${serviceName.replace('Service', '').replace('Data', '')}DataItem> {
|
|
925
|
+
try {
|
|
926
|
+
const isUpdate = 'id' in item && item.id;
|
|
927
|
+
|
|
928
|
+
const response = isUpdate
|
|
929
|
+
? await apiService.put<${serviceName.replace('Service', '').replace('Data', '')}DataItem>(\`\${this.baseUrl}/\${item.id}\`, item)
|
|
930
|
+
: await apiService.post<${serviceName.replace('Service', '').replace('Data', '')}DataItem>(this.baseUrl, item);${options.withCache ? `
|
|
931
|
+
|
|
932
|
+
// Invalidate cache
|
|
933
|
+
await this.invalidateCache();` : ''}${options.withEvents ? `
|
|
934
|
+
|
|
935
|
+
publish(\`${kebabName}:\${isUpdate ? 'updated' : 'created'}\`, { item: response.data });` : ''}
|
|
936
|
+
|
|
937
|
+
loggerService.info(\`${serviceName}: Item \${isUpdate ? 'updated' : 'created'}\`, { id: response.data.id });
|
|
938
|
+
return response.data;
|
|
939
|
+
|
|
940
|
+
} catch (error) {
|
|
941
|
+
loggerService.error('${serviceName}: Save failed', { error, item });
|
|
942
|
+
throw handleError(error, 'Failed to save data item');
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* Delete data item
|
|
948
|
+
*/
|
|
949
|
+
async delete(id: string): Promise<void> {
|
|
950
|
+
if (!id) {
|
|
951
|
+
throw new Error('ID is required');
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
try {
|
|
955
|
+
await apiService.delete(\`\${this.baseUrl}/\${id}\`);${options.withCache ? `
|
|
956
|
+
|
|
957
|
+
// Invalidate cache
|
|
958
|
+
await this.invalidateCache();
|
|
959
|
+
await cacheService.delete(\`\${this.cachePrefix}item:\${id}\`);` : ''}${options.withEvents ? `
|
|
960
|
+
|
|
961
|
+
publish('${kebabName}:deleted', { id });` : ''}
|
|
962
|
+
|
|
963
|
+
loggerService.info('${serviceName}: Item deleted', { id });
|
|
964
|
+
|
|
965
|
+
} catch (error) {
|
|
966
|
+
loggerService.error('${serviceName}: Delete failed', { error, id });
|
|
967
|
+
throw handleError(error, 'Failed to delete data item');
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Bulk operations
|
|
973
|
+
*/
|
|
974
|
+
async bulkSave(items: Array<Omit<${serviceName.replace('Service', '').replace('Data', '')}DataItem, 'id'> | ${serviceName.replace('Service', '').replace('Data', '')}DataItem>): Promise<${serviceName.replace('Service', '').replace('Data', '')}DataItem[]> {
|
|
975
|
+
try {
|
|
976
|
+
const response = await apiService.post<${serviceName.replace('Service', '').replace('Data', '')}DataItem[]>(\`\${this.baseUrl}/bulk\`, { items });${options.withCache ? `
|
|
977
|
+
|
|
978
|
+
// Invalidate cache
|
|
979
|
+
await this.invalidateCache();` : ''}${options.withEvents ? `
|
|
980
|
+
|
|
981
|
+
publish('${kebabName}:bulkSaved', { count: response.data.length });` : ''}
|
|
982
|
+
|
|
983
|
+
return response.data;
|
|
984
|
+
|
|
985
|
+
} catch (error) {
|
|
986
|
+
loggerService.error('${serviceName}: Bulk save failed', { error, itemCount: items.length });
|
|
987
|
+
throw handleError(error, 'Failed to bulk save items');
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
async bulkDelete(ids: string[]): Promise<void> {
|
|
992
|
+
try {
|
|
993
|
+
await apiService.post(\`\${this.baseUrl}/bulk-delete\`, { ids });${options.withCache ? `
|
|
994
|
+
|
|
995
|
+
// Invalidate cache
|
|
996
|
+
await this.invalidateCache();` : ''}${options.withEvents ? `
|
|
997
|
+
|
|
998
|
+
publish('${kebabName}:bulkDeleted', { count: ids.length });` : ''}
|
|
999
|
+
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
loggerService.error('${serviceName}: Bulk delete failed', { error, ids });
|
|
1002
|
+
throw handleError(error, 'Failed to bulk delete items');
|
|
1003
|
+
}
|
|
1004
|
+
}${options.withCache ? `
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* Cache management
|
|
1008
|
+
*/
|
|
1009
|
+
async clearCache(): Promise<void> {
|
|
1010
|
+
await this.invalidateCache();
|
|
1011
|
+
loggerService.debug('${serviceName}: Cache cleared');
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
private async invalidateCache(): Promise<void> {
|
|
1015
|
+
await cacheService.deleteByPattern(\`\${this.cachePrefix}*\`);
|
|
1016
|
+
}` : ''}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// Export singleton instance
|
|
1020
|
+
export const ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)} = new ${serviceName}();
|
|
1021
|
+
export default ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)};`;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// Generate types file
|
|
1025
|
+
function generateTypesTemplate() {
|
|
1026
|
+
return `/**
|
|
1027
|
+
* ${serviceName} Types
|
|
1028
|
+
*
|
|
1029
|
+
* TypeScript type definitions for ${serviceName}.
|
|
1030
|
+
*/
|
|
1031
|
+
|
|
1032
|
+
// Core types
|
|
1033
|
+
export interface ${serviceName}Config {
|
|
1034
|
+
// Configuration options
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
export interface ${serviceName}Options {
|
|
1038
|
+
// Service options
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
export interface ${serviceName}Result<T = any> {
|
|
1042
|
+
success: boolean;
|
|
1043
|
+
data?: T;
|
|
1044
|
+
error?: string;
|
|
1045
|
+
meta?: Record<string, any>;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// Event types
|
|
1049
|
+
export interface ${serviceName}Events {
|
|
1050
|
+
// Define event types
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// API types
|
|
1054
|
+
export interface ${serviceName}ApiResponse<T = any> {
|
|
1055
|
+
data: T;
|
|
1056
|
+
status: number;
|
|
1057
|
+
message?: string;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// Error types
|
|
1061
|
+
export interface ${serviceName}Error {
|
|
1062
|
+
code: string;
|
|
1063
|
+
message: string;
|
|
1064
|
+
details?: any;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// Export namespace
|
|
1068
|
+
export namespace ${serviceName}Types {
|
|
1069
|
+
export type Config = ${serviceName}Config;
|
|
1070
|
+
export type Options = ${serviceName}Options;
|
|
1071
|
+
export type Result<T = any> = ${serviceName}Result<T>;
|
|
1072
|
+
export type Events = ${serviceName}Events;
|
|
1073
|
+
export type ApiResponse<T = any> = ${serviceName}ApiResponse<T>;
|
|
1074
|
+
export type Error = ${serviceName}Error;
|
|
1075
|
+
}`;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// Generate test file
|
|
1079
|
+
function generateTestTemplate() {
|
|
1080
|
+
const serviceInstance = serviceName.charAt(0).toLowerCase() + serviceName.slice(1);
|
|
1081
|
+
|
|
1082
|
+
return `import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
1083
|
+
import { ${serviceInstance} } from './${fileName}';${options.type === 'api' ? `
|
|
1084
|
+
import { apiService } from '$lib/services/api/api.service';` : ''}${options.withCache ? `
|
|
1085
|
+
import { cacheService } from '$lib/services/cache/cache.service';` : ''}
|
|
1086
|
+
|
|
1087
|
+
// Mock dependencies
|
|
1088
|
+
${options.type === 'api' ? `vi.mock('$lib/services/api/api.service');` : ''}${options.withCache ? `
|
|
1089
|
+
vi.mock('$lib/services/cache/cache.service');` : ''}
|
|
1090
|
+
vi.mock('$lib/services/logging/logging.service');
|
|
1091
|
+
|
|
1092
|
+
describe('${serviceName}', () => {
|
|
1093
|
+
beforeEach(() => {
|
|
1094
|
+
// Reset all mocks before each test
|
|
1095
|
+
vi.clearAllMocks();
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
afterEach(() => {
|
|
1099
|
+
// Clean up after each test
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
describe('Basic functionality', () => {
|
|
1103
|
+
it('should be defined', () => {
|
|
1104
|
+
expect(${serviceInstance}).toBeDefined();
|
|
1105
|
+
});${options.type === 'api' ? `
|
|
1106
|
+
|
|
1107
|
+
it('should get all items', async () => {
|
|
1108
|
+
const mockData = [{ id: '1', name: 'Test Item' }];
|
|
1109
|
+
const mockResponse = { data: { data: mockData, meta: { total: 1 } } };
|
|
1110
|
+
|
|
1111
|
+
vi.mocked(apiService.get).mockResolvedValue(mockResponse);
|
|
1112
|
+
|
|
1113
|
+
const result = await ${serviceInstance}.getAll();
|
|
1114
|
+
|
|
1115
|
+
expect(apiService.get).toHaveBeenCalledWith(expect.stringContaining('/api/${kebabName}'));
|
|
1116
|
+
expect(result.data).toEqual(mockData);
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
it('should get item by ID', async () => {
|
|
1120
|
+
const mockItem = { id: '1', name: 'Test Item' };
|
|
1121
|
+
const mockResponse = { data: mockItem };
|
|
1122
|
+
|
|
1123
|
+
vi.mocked(apiService.get).mockResolvedValue(mockResponse);
|
|
1124
|
+
|
|
1125
|
+
const result = await ${serviceInstance}.getById('1');
|
|
1126
|
+
|
|
1127
|
+
expect(apiService.get).toHaveBeenCalledWith('/api/${kebabName}/1');
|
|
1128
|
+
expect(result).toEqual(mockItem);
|
|
1129
|
+
});
|
|
1130
|
+
|
|
1131
|
+
it('should create new item', async () => {
|
|
1132
|
+
const newItem = { name: 'New Item' };
|
|
1133
|
+
const createdItem = { id: '1', ...newItem };
|
|
1134
|
+
const mockResponse = { data: createdItem };
|
|
1135
|
+
|
|
1136
|
+
vi.mocked(apiService.post).mockResolvedValue(mockResponse);
|
|
1137
|
+
|
|
1138
|
+
const result = await ${serviceInstance}.create(newItem);
|
|
1139
|
+
|
|
1140
|
+
expect(apiService.post).toHaveBeenCalledWith('/api/${kebabName}', newItem);
|
|
1141
|
+
expect(result).toEqual(createdItem);
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
it('should update existing item', async () => {
|
|
1145
|
+
const updates = { name: 'Updated Item' };
|
|
1146
|
+
const updatedItem = { id: '1', ...updates };
|
|
1147
|
+
const mockResponse = { data: updatedItem };
|
|
1148
|
+
|
|
1149
|
+
vi.mocked(apiService.put).mockResolvedValue(mockResponse);
|
|
1150
|
+
|
|
1151
|
+
const result = await ${serviceInstance}.update('1', updates);
|
|
1152
|
+
|
|
1153
|
+
expect(apiService.put).toHaveBeenCalledWith('/api/${kebabName}/1', updates);
|
|
1154
|
+
expect(result).toEqual(updatedItem);
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
it('should delete item', async () => {
|
|
1158
|
+
vi.mocked(apiService.delete).mockResolvedValue({ data: null });
|
|
1159
|
+
|
|
1160
|
+
await ${serviceInstance}.delete('1');
|
|
1161
|
+
|
|
1162
|
+
expect(apiService.delete).toHaveBeenCalledWith('/api/${kebabName}/1');
|
|
1163
|
+
});` : ''}${options.type === 'store' ? `
|
|
1164
|
+
|
|
1165
|
+
it('should set loading state', () => {
|
|
1166
|
+
${serviceInstance}.setLoading(true);
|
|
1167
|
+
|
|
1168
|
+
// Test loading state change
|
|
1169
|
+
// Note: You'll need to implement proper store testing
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
it('should set data', () => {
|
|
1173
|
+
const testData = [{ id: '1', name: 'Test' }];
|
|
1174
|
+
|
|
1175
|
+
${serviceInstance}.setData(testData);
|
|
1176
|
+
|
|
1177
|
+
// Test data state change
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
it('should add item', () => {
|
|
1181
|
+
const newItem = { id: '1', name: 'New Item' };
|
|
1182
|
+
|
|
1183
|
+
${serviceInstance}.addItem(newItem);
|
|
1184
|
+
|
|
1185
|
+
// Test item addition
|
|
1186
|
+
});` : ''}${options.type === 'util' ? `
|
|
1187
|
+
|
|
1188
|
+
it('should process input correctly', () => {
|
|
1189
|
+
const input = { test: 'data' };
|
|
1190
|
+
const result = ${serviceInstance}.process(input);
|
|
1191
|
+
|
|
1192
|
+
expect(result.success).toBe(true);
|
|
1193
|
+
expect(result.data).toBeDefined();
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
it('should handle invalid input', () => {
|
|
1197
|
+
const result = ${serviceInstance}.process(null);
|
|
1198
|
+
|
|
1199
|
+
expect(result.success).toBe(false);
|
|
1200
|
+
expect(result.error).toBeDefined();
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
it('should validate input', () => {
|
|
1204
|
+
expect(${serviceInstance}.validate({ test: 'data' })).toBe(true);
|
|
1205
|
+
expect(${serviceInstance}.validate(null)).toBe(false);
|
|
1206
|
+
});` : ''}
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
describe('Error handling', () => {
|
|
1210
|
+
it('should handle API errors gracefully', async () => {
|
|
1211
|
+
const errorMessage = 'API Error';
|
|
1212
|
+
${options.type === 'api' ? `vi.mocked(apiService.get).mockRejectedValue(new Error(errorMessage));
|
|
1213
|
+
|
|
1214
|
+
await expect(${serviceInstance}.getAll()).rejects.toThrow();` : `
|
|
1215
|
+
// Add error handling tests for your service type`}
|
|
1216
|
+
});
|
|
1217
|
+
});${options.withCache ? `
|
|
1218
|
+
|
|
1219
|
+
describe('Caching', () => {
|
|
1220
|
+
it('should return cached data when available', async () => {
|
|
1221
|
+
const cachedData = { data: [{ id: '1' }], meta: { total: 1 } };
|
|
1222
|
+
vi.mocked(cacheService.get).mockResolvedValue(cachedData);
|
|
1223
|
+
|
|
1224
|
+
const result = await ${serviceInstance}.getAll();
|
|
1225
|
+
|
|
1226
|
+
expect(cacheService.get).toHaveBeenCalled();
|
|
1227
|
+
expect(result).toEqual(cachedData);
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1230
|
+
it('should cache API responses', async () => {
|
|
1231
|
+
const mockResponse = { data: { data: [], meta: { total: 0 } } };
|
|
1232
|
+
vi.mocked(apiService.get).mockResolvedValue(mockResponse);
|
|
1233
|
+
vi.mocked(cacheService.get).mockResolvedValue(null);
|
|
1234
|
+
|
|
1235
|
+
await ${serviceInstance}.getAll();
|
|
1236
|
+
|
|
1237
|
+
expect(cacheService.set).toHaveBeenCalled();
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
it('should clear cache', async () => {
|
|
1241
|
+
await ${serviceInstance}.clearCache();
|
|
1242
|
+
|
|
1243
|
+
expect(cacheService.deleteByPattern).toHaveBeenCalled();
|
|
1244
|
+
});
|
|
1245
|
+
});` : ''}${options.withEvents ? `
|
|
1246
|
+
|
|
1247
|
+
describe('Events', () => {
|
|
1248
|
+
it('should publish events for operations', async () => {
|
|
1249
|
+
// Mock publish function
|
|
1250
|
+
const mockPublish = vi.fn();
|
|
1251
|
+
vi.doMock('$lib/services/events', () => ({ publish: mockPublish }));
|
|
1252
|
+
|
|
1253
|
+
// Test event publishing
|
|
1254
|
+
// Implementation depends on your specific service
|
|
1255
|
+
});
|
|
1256
|
+
});` : ''}
|
|
1257
|
+
});`;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// Write files
|
|
1261
|
+
try {
|
|
1262
|
+
console.log(`๐ Generating ${serviceName} (${options.type} service)...`);
|
|
1263
|
+
|
|
1264
|
+
let filesToCreate = [];
|
|
1265
|
+
|
|
1266
|
+
// Write main service file
|
|
1267
|
+
fs.writeFileSync(serviceFile, generateServiceTemplate());
|
|
1268
|
+
filesToCreate.push(serviceFile);
|
|
1269
|
+
|
|
1270
|
+
// Write test file if requested
|
|
1271
|
+
if (options.withTest) {
|
|
1272
|
+
fs.writeFileSync(testFile, generateTestTemplate());
|
|
1273
|
+
filesToCreate.push(testFile);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
// Write types file if requested
|
|
1277
|
+
if (options.withTypes) {
|
|
1278
|
+
fs.writeFileSync(typesFile, generateTypesTemplate());
|
|
1279
|
+
filesToCreate.push(typesFile);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
console.log(`โ
Service files created:`);
|
|
1283
|
+
filesToCreate.forEach(file => {
|
|
1284
|
+
console.log(` ${file}`);
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
console.log(`\n๐ ${serviceName} generated successfully!`);
|
|
1288
|
+
console.log(`\n๐ Import your service:`);
|
|
1289
|
+
console.log(` import { ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)} } from '${serviceFile.replace(/^src\//, '$').replace(/\.ts$/, '')}';`);
|
|
1290
|
+
|
|
1291
|
+
if (options.withTest) {
|
|
1292
|
+
console.log(`\n๐งช Run tests:`);
|
|
1293
|
+
console.log(` npm test ${testFile}`);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
console.log(`\n๐ง Features enabled:`);
|
|
1297
|
+
console.log(` - Type: ${options.type}`);
|
|
1298
|
+
if (options.withCache) console.log(` - Caching: โ
`);
|
|
1299
|
+
if (options.withEvents) console.log(` - Events: โ
`);
|
|
1300
|
+
if (options.withTest) console.log(` - Tests: โ
`);
|
|
1301
|
+
if (options.withTypes) console.log(` - Types: โ
`);
|
|
1302
|
+
|
|
1303
|
+
} catch (error) {
|
|
1304
|
+
console.error('โ Error generating service:', error.message);
|
|
1305
|
+
process.exit(1);
|
|
1306
|
+
}
|