@bloomneo/appkit 1.2.9
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/LICENSE +21 -0
- package/README.md +902 -0
- package/bin/appkit.js +71 -0
- package/bin/commands/generate.js +1050 -0
- package/bin/templates/backend/README.md.template +39 -0
- package/bin/templates/backend/api.http.template +0 -0
- package/bin/templates/backend/docs/APPKIT_CLI.md +507 -0
- package/bin/templates/backend/docs/APPKIT_COMMENTS_GUIDELINES.md +61 -0
- package/bin/templates/backend/docs/APPKIT_LLM_GUIDE.md +2539 -0
- package/bin/templates/backend/package.json.template +34 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.http.template +29 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.route.ts.template +36 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.service.ts.template +88 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.types.ts.template +18 -0
- package/bin/templates/backend/src/api/lib/api-router.ts.template +84 -0
- package/bin/templates/backend/src/api/server.ts.template +188 -0
- package/bin/templates/backend/tsconfig.api.json.template +24 -0
- package/bin/templates/backend/tsconfig.json.template +40 -0
- package/bin/templates/feature/feature.http.template +63 -0
- package/bin/templates/feature/feature.route.ts.template +36 -0
- package/bin/templates/feature/feature.service.ts.template +81 -0
- package/bin/templates/feature/feature.types.ts.template +23 -0
- package/bin/templates/feature-db/feature.http.template +63 -0
- package/bin/templates/feature-db/feature.model.ts.template +74 -0
- package/bin/templates/feature-db/feature.route.ts.template +58 -0
- package/bin/templates/feature-db/feature.service.ts.template +231 -0
- package/bin/templates/feature-db/feature.types.ts.template +25 -0
- package/bin/templates/feature-db/schema-addition.prisma.template +9 -0
- package/bin/templates/feature-db/seeding/README.md.template +57 -0
- package/bin/templates/feature-db/seeding/feature.seed.js.template +67 -0
- package/bin/templates/feature-user/schema-addition.prisma.template +19 -0
- package/bin/templates/feature-user/user.http.template +157 -0
- package/bin/templates/feature-user/user.model.ts.template +244 -0
- package/bin/templates/feature-user/user.route.ts.template +379 -0
- package/bin/templates/feature-user/user.seed.js.template +182 -0
- package/bin/templates/feature-user/user.service.ts.template +426 -0
- package/bin/templates/feature-user/user.types.ts.template +127 -0
- package/dist/auth/auth.d.ts +182 -0
- package/dist/auth/auth.d.ts.map +1 -0
- package/dist/auth/auth.js +477 -0
- package/dist/auth/auth.js.map +1 -0
- package/dist/auth/defaults.d.ts +104 -0
- package/dist/auth/defaults.d.ts.map +1 -0
- package/dist/auth/defaults.js +374 -0
- package/dist/auth/defaults.js.map +1 -0
- package/dist/auth/index.d.ts +70 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +94 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/cache/cache.d.ts +118 -0
- package/dist/cache/cache.d.ts.map +1 -0
- package/dist/cache/cache.js +249 -0
- package/dist/cache/cache.js.map +1 -0
- package/dist/cache/defaults.d.ts +63 -0
- package/dist/cache/defaults.d.ts.map +1 -0
- package/dist/cache/defaults.js +193 -0
- package/dist/cache/defaults.js.map +1 -0
- package/dist/cache/index.d.ts +101 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +203 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/strategies/memory.d.ts +138 -0
- package/dist/cache/strategies/memory.d.ts.map +1 -0
- package/dist/cache/strategies/memory.js +348 -0
- package/dist/cache/strategies/memory.js.map +1 -0
- package/dist/cache/strategies/redis.d.ts +105 -0
- package/dist/cache/strategies/redis.d.ts.map +1 -0
- package/dist/cache/strategies/redis.js +318 -0
- package/dist/cache/strategies/redis.js.map +1 -0
- package/dist/config/config.d.ts +62 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +107 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/defaults.d.ts +44 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +217 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +105 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +163 -0
- package/dist/config/index.js.map +1 -0
- package/dist/database/adapters/mongoose.d.ts +106 -0
- package/dist/database/adapters/mongoose.d.ts.map +1 -0
- package/dist/database/adapters/mongoose.js +480 -0
- package/dist/database/adapters/mongoose.js.map +1 -0
- package/dist/database/adapters/prisma.d.ts +106 -0
- package/dist/database/adapters/prisma.d.ts.map +1 -0
- package/dist/database/adapters/prisma.js +494 -0
- package/dist/database/adapters/prisma.js.map +1 -0
- package/dist/database/defaults.d.ts +87 -0
- package/dist/database/defaults.d.ts.map +1 -0
- package/dist/database/defaults.js +271 -0
- package/dist/database/defaults.js.map +1 -0
- package/dist/database/index.d.ts +137 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +490 -0
- package/dist/database/index.js.map +1 -0
- package/dist/email/defaults.d.ts +100 -0
- package/dist/email/defaults.d.ts.map +1 -0
- package/dist/email/defaults.js +400 -0
- package/dist/email/defaults.js.map +1 -0
- package/dist/email/email.d.ts +139 -0
- package/dist/email/email.d.ts.map +1 -0
- package/dist/email/email.js +316 -0
- package/dist/email/email.js.map +1 -0
- package/dist/email/index.d.ts +176 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/email/index.js +251 -0
- package/dist/email/index.js.map +1 -0
- package/dist/email/strategies/console.d.ts +90 -0
- package/dist/email/strategies/console.d.ts.map +1 -0
- package/dist/email/strategies/console.js +268 -0
- package/dist/email/strategies/console.js.map +1 -0
- package/dist/email/strategies/resend.d.ts +84 -0
- package/dist/email/strategies/resend.d.ts.map +1 -0
- package/dist/email/strategies/resend.js +266 -0
- package/dist/email/strategies/resend.js.map +1 -0
- package/dist/email/strategies/smtp.d.ts +77 -0
- package/dist/email/strategies/smtp.d.ts.map +1 -0
- package/dist/email/strategies/smtp.js +286 -0
- package/dist/email/strategies/smtp.js.map +1 -0
- package/dist/error/defaults.d.ts +40 -0
- package/dist/error/defaults.d.ts.map +1 -0
- package/dist/error/defaults.js +75 -0
- package/dist/error/defaults.js.map +1 -0
- package/dist/error/error.d.ts +140 -0
- package/dist/error/error.d.ts.map +1 -0
- package/dist/error/error.js +200 -0
- package/dist/error/error.js.map +1 -0
- package/dist/error/index.d.ts +145 -0
- package/dist/error/index.d.ts.map +1 -0
- package/dist/error/index.js +145 -0
- package/dist/error/index.js.map +1 -0
- package/dist/event/defaults.d.ts +111 -0
- package/dist/event/defaults.d.ts.map +1 -0
- package/dist/event/defaults.js +378 -0
- package/dist/event/defaults.js.map +1 -0
- package/dist/event/event.d.ts +171 -0
- package/dist/event/event.d.ts.map +1 -0
- package/dist/event/event.js +391 -0
- package/dist/event/event.js.map +1 -0
- package/dist/event/index.d.ts +173 -0
- package/dist/event/index.d.ts.map +1 -0
- package/dist/event/index.js +302 -0
- package/dist/event/index.js.map +1 -0
- package/dist/event/strategies/memory.d.ts +122 -0
- package/dist/event/strategies/memory.d.ts.map +1 -0
- package/dist/event/strategies/memory.js +331 -0
- package/dist/event/strategies/memory.js.map +1 -0
- package/dist/event/strategies/redis.d.ts +115 -0
- package/dist/event/strategies/redis.d.ts.map +1 -0
- package/dist/event/strategies/redis.js +434 -0
- package/dist/event/strategies/redis.js.map +1 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/defaults.d.ts +67 -0
- package/dist/logger/defaults.d.ts.map +1 -0
- package/dist/logger/defaults.js +213 -0
- package/dist/logger/defaults.js.map +1 -0
- package/dist/logger/index.d.ts +84 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +101 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/logger.d.ts +165 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/logger.js +843 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/logger/transports/console.d.ts +102 -0
- package/dist/logger/transports/console.d.ts.map +1 -0
- package/dist/logger/transports/console.js +276 -0
- package/dist/logger/transports/console.js.map +1 -0
- package/dist/logger/transports/database.d.ts +153 -0
- package/dist/logger/transports/database.d.ts.map +1 -0
- package/dist/logger/transports/database.js +539 -0
- package/dist/logger/transports/database.js.map +1 -0
- package/dist/logger/transports/file.d.ts +146 -0
- package/dist/logger/transports/file.d.ts.map +1 -0
- package/dist/logger/transports/file.js +464 -0
- package/dist/logger/transports/file.js.map +1 -0
- package/dist/logger/transports/http.d.ts +128 -0
- package/dist/logger/transports/http.d.ts.map +1 -0
- package/dist/logger/transports/http.js +401 -0
- package/dist/logger/transports/http.js.map +1 -0
- package/dist/logger/transports/webhook.d.ts +152 -0
- package/dist/logger/transports/webhook.d.ts.map +1 -0
- package/dist/logger/transports/webhook.js +485 -0
- package/dist/logger/transports/webhook.js.map +1 -0
- package/dist/queue/defaults.d.ts +66 -0
- package/dist/queue/defaults.d.ts.map +1 -0
- package/dist/queue/defaults.js +205 -0
- package/dist/queue/defaults.js.map +1 -0
- package/dist/queue/index.d.ts +124 -0
- package/dist/queue/index.d.ts.map +1 -0
- package/dist/queue/index.js +116 -0
- package/dist/queue/index.js.map +1 -0
- package/dist/queue/queue.d.ts +156 -0
- package/dist/queue/queue.d.ts.map +1 -0
- package/dist/queue/queue.js +387 -0
- package/dist/queue/queue.js.map +1 -0
- package/dist/queue/transports/database.d.ts +165 -0
- package/dist/queue/transports/database.d.ts.map +1 -0
- package/dist/queue/transports/database.js +595 -0
- package/dist/queue/transports/database.js.map +1 -0
- package/dist/queue/transports/memory.d.ts +143 -0
- package/dist/queue/transports/memory.d.ts.map +1 -0
- package/dist/queue/transports/memory.js +415 -0
- package/dist/queue/transports/memory.js.map +1 -0
- package/dist/queue/transports/redis.d.ts +203 -0
- package/dist/queue/transports/redis.d.ts.map +1 -0
- package/dist/queue/transports/redis.js +744 -0
- package/dist/queue/transports/redis.js.map +1 -0
- package/dist/security/defaults.d.ts +64 -0
- package/dist/security/defaults.d.ts.map +1 -0
- package/dist/security/defaults.js +159 -0
- package/dist/security/defaults.js.map +1 -0
- package/dist/security/index.d.ts +110 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +160 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/security.d.ts +138 -0
- package/dist/security/security.d.ts.map +1 -0
- package/dist/security/security.js +419 -0
- package/dist/security/security.js.map +1 -0
- package/dist/storage/defaults.d.ts +79 -0
- package/dist/storage/defaults.d.ts.map +1 -0
- package/dist/storage/defaults.js +358 -0
- package/dist/storage/defaults.js.map +1 -0
- package/dist/storage/index.d.ts +153 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +242 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/storage.d.ts +151 -0
- package/dist/storage/storage.d.ts.map +1 -0
- package/dist/storage/storage.js +439 -0
- package/dist/storage/storage.js.map +1 -0
- package/dist/storage/strategies/local.d.ts +117 -0
- package/dist/storage/strategies/local.d.ts.map +1 -0
- package/dist/storage/strategies/local.js +368 -0
- package/dist/storage/strategies/local.js.map +1 -0
- package/dist/storage/strategies/r2.d.ts +130 -0
- package/dist/storage/strategies/r2.d.ts.map +1 -0
- package/dist/storage/strategies/r2.js +470 -0
- package/dist/storage/strategies/r2.js.map +1 -0
- package/dist/storage/strategies/s3.d.ts +121 -0
- package/dist/storage/strategies/s3.d.ts.map +1 -0
- package/dist/storage/strategies/s3.js +461 -0
- package/dist/storage/strategies/s3.js.map +1 -0
- package/dist/util/defaults.d.ts +77 -0
- package/dist/util/defaults.d.ts.map +1 -0
- package/dist/util/defaults.js +193 -0
- package/dist/util/defaults.js.map +1 -0
- package/dist/util/index.d.ts +97 -0
- package/dist/util/index.d.ts.map +1 -0
- package/dist/util/index.js +165 -0
- package/dist/util/index.js.map +1 -0
- package/dist/util/util.d.ts +145 -0
- package/dist/util/util.d.ts.map +1 -0
- package/dist/util/util.js +481 -0
- package/dist/util/util.js.map +1 -0
- package/package.json +234 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{featureName}} Feature Service - Business logic with AppKit integration
|
|
3
|
+
* @module {{projectName}}/{{featureName}}-service
|
|
4
|
+
* @file src/api/features/{{featureName}}/{{featureName}}.service.ts
|
|
5
|
+
*
|
|
6
|
+
* @llm-rule WHEN: Need business logic layer with validation, logging, and config
|
|
7
|
+
* @llm-rule AVOID: Direct database calls from routes - always use service layer
|
|
8
|
+
* @llm-rule NOTE: Demonstrates AppKit logger, config, and error patterns for FBCA
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { loggerClass } from '@bloomneo/appkit/logger';
|
|
12
|
+
import { configClass } from '@bloomneo/appkit/config';
|
|
13
|
+
import { errorClass } from '@bloomneo/appkit/error';
|
|
14
|
+
import type { {{featureName}}Response, {{featureName}}CreateRequest } from './{{featureName}}.types.js';
|
|
15
|
+
|
|
16
|
+
// Initialize AppKit modules following the pattern
|
|
17
|
+
const logger = loggerClass.get('{{featureName}}');
|
|
18
|
+
const config = configClass.get();
|
|
19
|
+
const error = errorClass.get();
|
|
20
|
+
|
|
21
|
+
export const {{featureName}}Service = {
|
|
22
|
+
/**
|
|
23
|
+
* Get all {{featureName}} items
|
|
24
|
+
*/
|
|
25
|
+
async getAll(): Promise<{{featureName}}Response[]> {
|
|
26
|
+
try {
|
|
27
|
+
logger.info('Processing get all {{featureName}} request');
|
|
28
|
+
|
|
29
|
+
// Example implementation - replace with your business logic
|
|
30
|
+
const items = [
|
|
31
|
+
{
|
|
32
|
+
id: '1',
|
|
33
|
+
name: 'Sample {{featureName}}',
|
|
34
|
+
timestamp: new Date().toISOString()
|
|
35
|
+
}
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
logger.info('Get all {{featureName}} request completed', { count: items.length });
|
|
39
|
+
return items;
|
|
40
|
+
|
|
41
|
+
} catch (err) {
|
|
42
|
+
logger.error('Failed to get all {{featureName}} items', { error: err });
|
|
43
|
+
throw error.serverError('Failed to retrieve {{featureName}} items');
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get {{featureName}} by ID
|
|
49
|
+
*/
|
|
50
|
+
async getById(id: string): Promise<{{featureName}}Response> {
|
|
51
|
+
try {
|
|
52
|
+
logger.info('Processing get {{featureName}} by ID request', { id });
|
|
53
|
+
|
|
54
|
+
// Validate input
|
|
55
|
+
if (!id || typeof id !== 'string') {
|
|
56
|
+
logger.warn('Invalid ID provided', { id });
|
|
57
|
+
throw error.badRequest('ID must be a valid string');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Example implementation - replace with your business logic
|
|
61
|
+
const item = {
|
|
62
|
+
id,
|
|
63
|
+
name: `Sample {{featureName}} ${id}`,
|
|
64
|
+
timestamp: new Date().toISOString()
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
logger.info('Get {{featureName}} by ID request completed', { id, result: item });
|
|
68
|
+
return item;
|
|
69
|
+
|
|
70
|
+
} catch (err: any) {
|
|
71
|
+
// Re-throw AppKit errors as-is
|
|
72
|
+
if (err.statusCode) {
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Convert other errors to server errors
|
|
77
|
+
logger.error('Failed to get {{featureName}} by ID', { id, error: err });
|
|
78
|
+
throw error.serverError('Failed to retrieve {{featureName}} item');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{featureName}} Feature Type Definitions - Shared interfaces and types
|
|
3
|
+
* @module {{projectName}}/{{featureName}}-types
|
|
4
|
+
* @file src/api/features/{{featureName}}/{{featureName}}.types.ts
|
|
5
|
+
*
|
|
6
|
+
* @llm-rule WHEN: Need type safety for {{featureName}} feature data structures
|
|
7
|
+
* @llm-rule AVOID: Defining types inline - reduces reusability and consistency
|
|
8
|
+
* @llm-rule NOTE: Shared across service and route layers for type consistency
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface {{featureName}}Response {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
timestamp: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface {{featureName}}CreateRequest {
|
|
18
|
+
name: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface {{featureName}}UpdateRequest {
|
|
22
|
+
name?: string;
|
|
23
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
### {{FeatureName}} API Testing with Frontend Key
|
|
2
|
+
### Base URL: http://localhost:3000
|
|
3
|
+
### Note: In development, X-Frontend-Key is not required but shown for testing
|
|
4
|
+
|
|
5
|
+
### 1. Get all {{featureName}} (without key - should work in dev)
|
|
6
|
+
GET http://localhost:3000/api/{{featureName}}
|
|
7
|
+
|
|
8
|
+
###
|
|
9
|
+
|
|
10
|
+
### 2. Get all {{featureName}} (with frontend key)
|
|
11
|
+
GET http://localhost:3000/api/{{featureName}}
|
|
12
|
+
X-Frontend-Key: {{frontendKey}}
|
|
13
|
+
|
|
14
|
+
###
|
|
15
|
+
|
|
16
|
+
### 3. Create a new {{featureName}} (with frontend key)
|
|
17
|
+
POST http://localhost:3000/api/{{featureName}}
|
|
18
|
+
Content-Type: application/json
|
|
19
|
+
X-Frontend-Key: {{frontendKey}}
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
"name": "Sample {{FeatureName}}"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
###
|
|
26
|
+
|
|
27
|
+
### 4. Get {{featureName}} by ID (with frontend key)
|
|
28
|
+
GET http://localhost:3000/api/{{featureName}}/1
|
|
29
|
+
X-Frontend-Key: {{frontendKey}}
|
|
30
|
+
|
|
31
|
+
###
|
|
32
|
+
|
|
33
|
+
### 5. Update {{featureName}} by ID (with frontend key)
|
|
34
|
+
PUT http://localhost:3000/api/{{featureName}}/1
|
|
35
|
+
Content-Type: application/json
|
|
36
|
+
X-Frontend-Key: {{frontendKey}}
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
"name": "Updated {{FeatureName}}"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
###
|
|
43
|
+
|
|
44
|
+
### 6. Delete {{featureName}} by ID (with frontend key)
|
|
45
|
+
DELETE http://localhost:3000/api/{{featureName}}/1
|
|
46
|
+
X-Frontend-Key: {{frontendKey}}
|
|
47
|
+
|
|
48
|
+
###
|
|
49
|
+
|
|
50
|
+
### 7. Test with wrong key (should fail in production)
|
|
51
|
+
GET http://localhost:3000/api/{{featureName}}
|
|
52
|
+
X-Frontend-Key: wrong_key_123
|
|
53
|
+
|
|
54
|
+
###
|
|
55
|
+
|
|
56
|
+
### Health Check (not protected)
|
|
57
|
+
GET http://localhost:3000/health
|
|
58
|
+
|
|
59
|
+
###
|
|
60
|
+
|
|
61
|
+
### API Info (protected)
|
|
62
|
+
GET http://localhost:3000/api
|
|
63
|
+
X-Frontend-Key: {{frontendKey}}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{FeatureName}} Model - Database operations with Prisma
|
|
3
|
+
* @module {{projectName}}/{{featureName}}-model
|
|
4
|
+
* @file src/api/features/{{featureName}}/{{featureName}}.model.ts
|
|
5
|
+
*
|
|
6
|
+
* @llm-rule WHEN: Need database layer separation for {{featureName}} feature
|
|
7
|
+
* @llm-rule AVOID: Database operations directly in service - use model layer
|
|
8
|
+
* @llm-rule NOTE: Handles all Prisma database operations for {{featureName}} feature
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { databaseClass } from '@bloomneo/appkit/database';
|
|
12
|
+
import { loggerClass } from '@bloomneo/appkit/logger';
|
|
13
|
+
|
|
14
|
+
const logger = loggerClass.get('{{featureName}}-model');
|
|
15
|
+
|
|
16
|
+
export const model = {
|
|
17
|
+
/**
|
|
18
|
+
* Get all {{featureName}} from database
|
|
19
|
+
*/
|
|
20
|
+
async findAll() {
|
|
21
|
+
logger.info('Finding all {{featureName}}');
|
|
22
|
+
const db = await databaseClass.get();
|
|
23
|
+
return await db.{{tableName}}.findMany({
|
|
24
|
+
orderBy: { createdAt: 'desc' }
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Find {{featureName}} by ID
|
|
30
|
+
*/
|
|
31
|
+
async findById(id: number) {
|
|
32
|
+
logger.info('Finding {{featureName}} by ID', { id });
|
|
33
|
+
const db = await databaseClass.get();
|
|
34
|
+
return await db.{{tableName}}.findUnique({
|
|
35
|
+
where: { id }
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Create new {{featureName}}
|
|
41
|
+
*/
|
|
42
|
+
async create(data: { name: string }) {
|
|
43
|
+
logger.info('Creating new {{featureName}}', { data });
|
|
44
|
+
const db = await databaseClass.get();
|
|
45
|
+
return await db.{{tableName}}.create({
|
|
46
|
+
data: {
|
|
47
|
+
name: data.name.trim(),
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Update {{featureName}} by ID
|
|
54
|
+
*/
|
|
55
|
+
async update(id: number, data: { name?: string }) {
|
|
56
|
+
logger.info('Updating {{featureName}}', { id, data });
|
|
57
|
+
const db = await databaseClass.get();
|
|
58
|
+
return await db.{{tableName}}.update({
|
|
59
|
+
where: { id },
|
|
60
|
+
data
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Delete {{featureName}} by ID
|
|
66
|
+
*/
|
|
67
|
+
async delete(id: number) {
|
|
68
|
+
logger.info('Deleting {{featureName}}', { id });
|
|
69
|
+
const db = await databaseClass.get();
|
|
70
|
+
return await db.{{tableName}}.delete({
|
|
71
|
+
where: { id }
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{FeatureName}} Feature Routes - Express endpoints with AppKit integration
|
|
3
|
+
* @module {{projectName}}/{{featureName}}-routes
|
|
4
|
+
* @file src/api/features/{{featureName}}/{{featureName}}.route.ts
|
|
5
|
+
*
|
|
6
|
+
* @llm-rule WHEN: Need HTTP endpoints for {{featureName}} feature with error handling and database operations
|
|
7
|
+
* @llm-rule AVOID: Adding routes without asyncRoute wrapper - breaks error handling
|
|
8
|
+
* @llm-rule NOTE: Auto-discovered by router.ts, exports default Express router, supports full CRUD operations
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import express from 'express';
|
|
12
|
+
import { errorClass } from '@bloomneo/appkit/error';
|
|
13
|
+
import { loggerClass } from '@bloomneo/appkit/logger';
|
|
14
|
+
import { {{featureName}}Service } from './{{featureName}}.service.js';
|
|
15
|
+
|
|
16
|
+
const router = express.Router();
|
|
17
|
+
const error = errorClass.get();
|
|
18
|
+
const logger = loggerClass.get('{{featureName}}-routes');
|
|
19
|
+
|
|
20
|
+
// GET /api/{{featureName}} - Get all {{featureName}} items
|
|
21
|
+
router.get('/', error.asyncRoute(async (_req, res) => {
|
|
22
|
+
logger.info('GET /api/{{featureName}} request received');
|
|
23
|
+
const result = await {{featureName}}Service.getAll();
|
|
24
|
+
res.json(result);
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
// POST /api/{{featureName}} - Create new {{featureName}}
|
|
28
|
+
router.post('/', error.asyncRoute(async (req, res) => {
|
|
29
|
+
logger.info('POST /api/{{featureName}} request received', { body: req.body });
|
|
30
|
+
const result = await {{featureName}}Service.create(req.body);
|
|
31
|
+
res.status(201).json(result);
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
// GET /api/{{featureName}}/:id - Get specific {{featureName}} by ID
|
|
35
|
+
router.get('/:id', error.asyncRoute(async (req, res) => {
|
|
36
|
+
const id = req.params.id;
|
|
37
|
+
logger.info('GET /api/{{featureName}}/:id request received', { id });
|
|
38
|
+
const result = await {{featureName}}Service.getById(id);
|
|
39
|
+
res.json(result);
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
// PUT /api/{{featureName}}/:id - Update {{featureName}} by ID
|
|
43
|
+
router.put('/:id', error.asyncRoute(async (req, res) => {
|
|
44
|
+
const id = req.params.id;
|
|
45
|
+
logger.info('PUT /api/{{featureName}}/:id request received', { id, body: req.body });
|
|
46
|
+
const result = await {{featureName}}Service.update(id, req.body);
|
|
47
|
+
res.json(result);
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
// DELETE /api/{{featureName}}/:id - Delete {{featureName}} by ID
|
|
51
|
+
router.delete('/:id', error.asyncRoute(async (req, res) => {
|
|
52
|
+
const id = req.params.id;
|
|
53
|
+
logger.info('DELETE /api/{{featureName}}/:id request received', { id });
|
|
54
|
+
await {{featureName}}Service.delete(id);
|
|
55
|
+
res.status(200).json({ message: 'Deleted successfully' });
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
export default router;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{FeatureName}} Feature Service - Business logic with AppKit database integration
|
|
3
|
+
* @module {{projectName}}/{{featureName}}-service
|
|
4
|
+
* @file src/api/features/{{featureName}}/{{featureName}}.service.ts
|
|
5
|
+
*
|
|
6
|
+
* @llm-rule WHEN: Need business logic layer with validation, logging, config, and database access
|
|
7
|
+
* @llm-rule AVOID: Direct database calls from routes - always use service layer
|
|
8
|
+
* @llm-rule NOTE: Demonstrates AppKit database, logger, config, and error patterns for FBCA with Prisma
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { loggerClass } from '@bloomneo/appkit/logger';
|
|
12
|
+
import { errorClass } from '@bloomneo/appkit/error';
|
|
13
|
+
import { model } from './{{featureName}}.model.js';
|
|
14
|
+
import type { {{FeatureName}}Response, {{FeatureName}}CreateRequest, {{FeatureName}}UpdateRequest } from './{{featureName}}.types.js';
|
|
15
|
+
|
|
16
|
+
// Initialize AppKit modules following the pattern
|
|
17
|
+
const logger = loggerClass.get('{{featureName}}');
|
|
18
|
+
const error = errorClass.get();
|
|
19
|
+
|
|
20
|
+
export const {{featureName}}Service = {
|
|
21
|
+
/**
|
|
22
|
+
* Get all {{featureName}} items
|
|
23
|
+
*/
|
|
24
|
+
async getAll(): Promise<{{FeatureName}}Response[]> {
|
|
25
|
+
try {
|
|
26
|
+
logger.info('Processing get all {{featureName}} request');
|
|
27
|
+
|
|
28
|
+
const items = await model.findAll();
|
|
29
|
+
|
|
30
|
+
// Transform to response format
|
|
31
|
+
const result = items.map((item: any) => ({
|
|
32
|
+
...item,
|
|
33
|
+
createdAt: item.createdAt.toISOString(),
|
|
34
|
+
updatedAt: item.updatedAt.toISOString()
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
logger.info('Get all {{featureName}} request completed', { count: result.length });
|
|
38
|
+
return result;
|
|
39
|
+
|
|
40
|
+
} catch (err) {
|
|
41
|
+
logger.error('Failed to get all {{featureName}} items', { error: err });
|
|
42
|
+
throw error.serverError('Failed to retrieve {{featureName}} items');
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get {{featureName}} by ID
|
|
48
|
+
*/
|
|
49
|
+
async getById(id: string): Promise<{{FeatureName}}Response> {
|
|
50
|
+
try {
|
|
51
|
+
logger.info('Processing get {{featureName}} by ID request', { id });
|
|
52
|
+
|
|
53
|
+
// Validate and convert ID to number
|
|
54
|
+
const numId = parseInt(id, 10);
|
|
55
|
+
if (!id || isNaN(numId)) {
|
|
56
|
+
logger.warn('Invalid ID provided', { id });
|
|
57
|
+
throw error.badRequest('ID must be a valid number');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const item = await model.findById(numId);
|
|
61
|
+
|
|
62
|
+
if (!item) {
|
|
63
|
+
logger.warn('{{FeatureName}} not found', { id });
|
|
64
|
+
throw error.notFound('{{FeatureName}} not found');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Transform to response format
|
|
68
|
+
const result = {
|
|
69
|
+
...item,
|
|
70
|
+
createdAt: item.createdAt.toISOString(),
|
|
71
|
+
updatedAt: item.updatedAt.toISOString()
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
logger.info('Get {{featureName}} by ID request completed', { id, result });
|
|
75
|
+
return result;
|
|
76
|
+
|
|
77
|
+
} catch (err: any) {
|
|
78
|
+
// Re-throw AppKit errors as-is
|
|
79
|
+
if (err.statusCode) {
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Convert other errors to server errors
|
|
84
|
+
logger.error('Failed to get {{featureName}} by ID', { id, error: err });
|
|
85
|
+
throw error.serverError('Failed to retrieve {{featureName}} item');
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Create a new {{featureName}}
|
|
91
|
+
*/
|
|
92
|
+
async create(data: {{FeatureName}}CreateRequest): Promise<{{FeatureName}}Response> {
|
|
93
|
+
try {
|
|
94
|
+
logger.info('Processing create {{featureName}} request', { data });
|
|
95
|
+
|
|
96
|
+
// Validate input
|
|
97
|
+
if (!data.name || typeof data.name !== 'string' || data.name.trim().length === 0) {
|
|
98
|
+
logger.warn('Invalid name provided', { data });
|
|
99
|
+
throw error.badRequest('Name is required and must be a non-empty string');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (data.name.length > 200) {
|
|
103
|
+
logger.warn('Name too long', { nameLength: data.name.length });
|
|
104
|
+
throw error.badRequest('Name must be less than 200 characters');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const item = await model.create({
|
|
108
|
+
name: data.name.trim()
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Transform to response format
|
|
112
|
+
const result = {
|
|
113
|
+
...item,
|
|
114
|
+
createdAt: item.createdAt.toISOString(),
|
|
115
|
+
updatedAt: item.updatedAt.toISOString()
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
logger.info('Create {{featureName}} request completed', { id: item.id, result });
|
|
119
|
+
return result;
|
|
120
|
+
|
|
121
|
+
} catch (err: any) {
|
|
122
|
+
// Re-throw AppKit errors as-is
|
|
123
|
+
if (err.statusCode) {
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Convert other errors to server errors
|
|
128
|
+
logger.error('Failed to create {{featureName}}', { data, error: err });
|
|
129
|
+
throw error.serverError('Failed to create {{featureName}} item');
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Update a {{featureName}}
|
|
135
|
+
*/
|
|
136
|
+
async update(id: string, data: {{FeatureName}}UpdateRequest): Promise<{{FeatureName}}Response> {
|
|
137
|
+
try {
|
|
138
|
+
logger.info('Processing update {{featureName}} request', { id, data });
|
|
139
|
+
|
|
140
|
+
// Validate and convert ID to number
|
|
141
|
+
const numId = parseInt(id, 10);
|
|
142
|
+
if (!id || isNaN(numId)) {
|
|
143
|
+
logger.warn('Invalid ID provided', { id });
|
|
144
|
+
throw error.badRequest('ID must be a valid number');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (data.name !== undefined) {
|
|
148
|
+
if (typeof data.name !== 'string' || data.name.trim().length === 0) {
|
|
149
|
+
logger.warn('Invalid name provided', { data });
|
|
150
|
+
throw error.badRequest('Name must be a non-empty string');
|
|
151
|
+
}
|
|
152
|
+
if (data.name.length > 200) {
|
|
153
|
+
logger.warn('Name too long', { nameLength: data.name.length });
|
|
154
|
+
throw error.badRequest('Name must be less than 200 characters');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Check if item exists
|
|
159
|
+
const existingItem = await model.findById(numId);
|
|
160
|
+
|
|
161
|
+
if (!existingItem) {
|
|
162
|
+
logger.warn('{{FeatureName}} not found for update', { id });
|
|
163
|
+
throw error.notFound('{{FeatureName}} not found');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Build update data
|
|
167
|
+
const updateData: any = {};
|
|
168
|
+
if (data.name !== undefined) updateData.name = data.name.trim();
|
|
169
|
+
|
|
170
|
+
const item = await model.update(numId, updateData);
|
|
171
|
+
|
|
172
|
+
// Transform to response format
|
|
173
|
+
const result = {
|
|
174
|
+
...item,
|
|
175
|
+
createdAt: item.createdAt.toISOString(),
|
|
176
|
+
updatedAt: item.updatedAt.toISOString()
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
logger.info('Update {{featureName}} request completed', { id, result });
|
|
180
|
+
return result;
|
|
181
|
+
|
|
182
|
+
} catch (err: any) {
|
|
183
|
+
// Re-throw AppKit errors as-is
|
|
184
|
+
if (err.statusCode) {
|
|
185
|
+
throw err;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Convert other errors to server errors
|
|
189
|
+
logger.error('Failed to update {{featureName}}', { id, data, error: err });
|
|
190
|
+
throw error.serverError('Failed to update {{featureName}} item');
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Delete a {{featureName}}
|
|
196
|
+
*/
|
|
197
|
+
async delete(id: string): Promise<void> {
|
|
198
|
+
try {
|
|
199
|
+
logger.info('Processing delete {{featureName}} request', { id });
|
|
200
|
+
|
|
201
|
+
// Validate and convert ID to number
|
|
202
|
+
const numId = parseInt(id, 10);
|
|
203
|
+
if (!id || isNaN(numId)) {
|
|
204
|
+
logger.warn('Invalid ID provided', { id });
|
|
205
|
+
throw error.badRequest('ID must be a valid number');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Check if item exists
|
|
209
|
+
const existingItem = await model.findById(numId);
|
|
210
|
+
|
|
211
|
+
if (!existingItem) {
|
|
212
|
+
logger.warn('{{FeatureName}} not found for deletion', { id });
|
|
213
|
+
throw error.notFound('{{FeatureName}} not found');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
await model.delete(numId);
|
|
217
|
+
|
|
218
|
+
logger.info('Delete {{featureName}} request completed', { id });
|
|
219
|
+
|
|
220
|
+
} catch (err: any) {
|
|
221
|
+
// Re-throw AppKit errors as-is
|
|
222
|
+
if (err.statusCode) {
|
|
223
|
+
throw err;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Convert other errors to server errors
|
|
227
|
+
logger.error('Failed to delete {{featureName}}', { id, error: err });
|
|
228
|
+
throw error.serverError('Failed to delete {{featureName}} item');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{FeatureName}} Feature Type Definitions - Shared interfaces and types
|
|
3
|
+
* @module {{projectName}}/{{featureName}}-types
|
|
4
|
+
* @file src/api/features/{{featureName}}/{{featureName}}.types.ts
|
|
5
|
+
*
|
|
6
|
+
* @llm-rule WHEN: Need type safety for {{featureName}} feature data structures with database support
|
|
7
|
+
* @llm-rule AVOID: Defining types inline - reduces reusability and consistency
|
|
8
|
+
* @llm-rule NOTE: Shared across service and route layers for type consistency, includes Prisma model types
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface {{FeatureName}}Response {
|
|
12
|
+
id: number;
|
|
13
|
+
name: string;
|
|
14
|
+
tenant_id: string | null;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
updatedAt: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface {{FeatureName}}CreateRequest {
|
|
20
|
+
name: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface {{FeatureName}}UpdateRequest {
|
|
24
|
+
name?: string;
|
|
25
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Database Seeding
|
|
2
|
+
|
|
3
|
+
This folder contains individual seed files for each feature. Each seed file is independent and can be run separately.
|
|
4
|
+
|
|
5
|
+
## Running Individual Seeds
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Seed {{featureName}} data only
|
|
9
|
+
node prisma/seeding/{{featureName}}.seed.js
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Running All Seeds
|
|
13
|
+
|
|
14
|
+
Create a main seed file in your project root to run all feature seeds:
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
// prisma/seed.js
|
|
18
|
+
import { seed{{FeatureName}} } from './seeding/{{featureName}}.seed.js';
|
|
19
|
+
|
|
20
|
+
async function main() {
|
|
21
|
+
console.log('🌱 Starting database seeding...');
|
|
22
|
+
|
|
23
|
+
await seed{{FeatureName}}();
|
|
24
|
+
// Add other feature seeds here as you create them
|
|
25
|
+
|
|
26
|
+
console.log('✅ Database seeding completed!');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
main()
|
|
30
|
+
.catch((e) => {
|
|
31
|
+
console.error(e);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
})
|
|
34
|
+
.finally(async () => {
|
|
35
|
+
await prisma.$disconnect();
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Adding to package.json
|
|
40
|
+
|
|
41
|
+
Add these scripts to your `package.json`:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"scripts": {
|
|
46
|
+
"seed": "node prisma/seed.js",
|
|
47
|
+
"seed:{{featureName}}": "node prisma/seeding/{{featureName}}.seed.js"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
- ✅ **Non-destructive**: Won't overwrite existing data
|
|
55
|
+
- ✅ **Independent**: Each feature can be seeded separately
|
|
56
|
+
- ✅ **Safe**: Checks for existing data before seeding
|
|
57
|
+
- ✅ **Informative**: Clear console output about what's happening
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{FeatureName}} Feature Seed Data
|
|
3
|
+
* @file prisma/seeding/{{featureName}}.seed.js
|
|
4
|
+
*
|
|
5
|
+
* Run this seed file individually:
|
|
6
|
+
* node prisma/seeding/{{featureName}}.seed.js
|
|
7
|
+
*
|
|
8
|
+
* Or include in your main seed file:
|
|
9
|
+
* require('./seeding/{{featureName}}.seed.js');
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { PrismaClient } from '@prisma/client';
|
|
13
|
+
|
|
14
|
+
const prisma = new PrismaClient();
|
|
15
|
+
|
|
16
|
+
export async function seed{{FeatureName}}() {
|
|
17
|
+
console.log('🌱 Seeding {{featureName}} data...');
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Check if {{featureName}} data already exists
|
|
21
|
+
const existingCount = await prisma.{{tableName}}.count();
|
|
22
|
+
|
|
23
|
+
if (existingCount > 0) {
|
|
24
|
+
console.log(`⚠️ {{FeatureName}} table already has ${existingCount} records. Skipping seed...`);
|
|
25
|
+
return { skipped: true, count: existingCount };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Create sample {{featureName}} data
|
|
29
|
+
const {{featureName}}Data = [
|
|
30
|
+
{
|
|
31
|
+
name: 'Sample {{FeatureName}} 1',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'Sample {{FeatureName}} 2',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'Sample {{FeatureName}} 3',
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const result = await prisma.{{tableName}}.createMany({
|
|
42
|
+
data: {{featureName}}Data
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
console.log(`✅ Successfully seeded ${result.count} {{featureName}} records`);
|
|
46
|
+
return { seeded: true, count: result.count };
|
|
47
|
+
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error(`❌ Error seeding {{featureName}} data:`, error);
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Run directly if this file is executed
|
|
55
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
56
|
+
seed{{FeatureName}}()
|
|
57
|
+
.then((result) => {
|
|
58
|
+
console.log('{{FeatureName}} seeding completed:', result);
|
|
59
|
+
})
|
|
60
|
+
.catch((error) => {
|
|
61
|
+
console.error('{{FeatureName}} seeding failed:', error);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
})
|
|
64
|
+
.finally(async () => {
|
|
65
|
+
await prisma.$disconnect();
|
|
66
|
+
});
|
|
67
|
+
}
|