@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.
Files changed (262) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +902 -0
  3. package/bin/appkit.js +71 -0
  4. package/bin/commands/generate.js +1050 -0
  5. package/bin/templates/backend/README.md.template +39 -0
  6. package/bin/templates/backend/api.http.template +0 -0
  7. package/bin/templates/backend/docs/APPKIT_CLI.md +507 -0
  8. package/bin/templates/backend/docs/APPKIT_COMMENTS_GUIDELINES.md +61 -0
  9. package/bin/templates/backend/docs/APPKIT_LLM_GUIDE.md +2539 -0
  10. package/bin/templates/backend/package.json.template +34 -0
  11. package/bin/templates/backend/src/api/features/welcome/welcome.http.template +29 -0
  12. package/bin/templates/backend/src/api/features/welcome/welcome.route.ts.template +36 -0
  13. package/bin/templates/backend/src/api/features/welcome/welcome.service.ts.template +88 -0
  14. package/bin/templates/backend/src/api/features/welcome/welcome.types.ts.template +18 -0
  15. package/bin/templates/backend/src/api/lib/api-router.ts.template +84 -0
  16. package/bin/templates/backend/src/api/server.ts.template +188 -0
  17. package/bin/templates/backend/tsconfig.api.json.template +24 -0
  18. package/bin/templates/backend/tsconfig.json.template +40 -0
  19. package/bin/templates/feature/feature.http.template +63 -0
  20. package/bin/templates/feature/feature.route.ts.template +36 -0
  21. package/bin/templates/feature/feature.service.ts.template +81 -0
  22. package/bin/templates/feature/feature.types.ts.template +23 -0
  23. package/bin/templates/feature-db/feature.http.template +63 -0
  24. package/bin/templates/feature-db/feature.model.ts.template +74 -0
  25. package/bin/templates/feature-db/feature.route.ts.template +58 -0
  26. package/bin/templates/feature-db/feature.service.ts.template +231 -0
  27. package/bin/templates/feature-db/feature.types.ts.template +25 -0
  28. package/bin/templates/feature-db/schema-addition.prisma.template +9 -0
  29. package/bin/templates/feature-db/seeding/README.md.template +57 -0
  30. package/bin/templates/feature-db/seeding/feature.seed.js.template +67 -0
  31. package/bin/templates/feature-user/schema-addition.prisma.template +19 -0
  32. package/bin/templates/feature-user/user.http.template +157 -0
  33. package/bin/templates/feature-user/user.model.ts.template +244 -0
  34. package/bin/templates/feature-user/user.route.ts.template +379 -0
  35. package/bin/templates/feature-user/user.seed.js.template +182 -0
  36. package/bin/templates/feature-user/user.service.ts.template +426 -0
  37. package/bin/templates/feature-user/user.types.ts.template +127 -0
  38. package/dist/auth/auth.d.ts +182 -0
  39. package/dist/auth/auth.d.ts.map +1 -0
  40. package/dist/auth/auth.js +477 -0
  41. package/dist/auth/auth.js.map +1 -0
  42. package/dist/auth/defaults.d.ts +104 -0
  43. package/dist/auth/defaults.d.ts.map +1 -0
  44. package/dist/auth/defaults.js +374 -0
  45. package/dist/auth/defaults.js.map +1 -0
  46. package/dist/auth/index.d.ts +70 -0
  47. package/dist/auth/index.d.ts.map +1 -0
  48. package/dist/auth/index.js +94 -0
  49. package/dist/auth/index.js.map +1 -0
  50. package/dist/cache/cache.d.ts +118 -0
  51. package/dist/cache/cache.d.ts.map +1 -0
  52. package/dist/cache/cache.js +249 -0
  53. package/dist/cache/cache.js.map +1 -0
  54. package/dist/cache/defaults.d.ts +63 -0
  55. package/dist/cache/defaults.d.ts.map +1 -0
  56. package/dist/cache/defaults.js +193 -0
  57. package/dist/cache/defaults.js.map +1 -0
  58. package/dist/cache/index.d.ts +101 -0
  59. package/dist/cache/index.d.ts.map +1 -0
  60. package/dist/cache/index.js +203 -0
  61. package/dist/cache/index.js.map +1 -0
  62. package/dist/cache/strategies/memory.d.ts +138 -0
  63. package/dist/cache/strategies/memory.d.ts.map +1 -0
  64. package/dist/cache/strategies/memory.js +348 -0
  65. package/dist/cache/strategies/memory.js.map +1 -0
  66. package/dist/cache/strategies/redis.d.ts +105 -0
  67. package/dist/cache/strategies/redis.d.ts.map +1 -0
  68. package/dist/cache/strategies/redis.js +318 -0
  69. package/dist/cache/strategies/redis.js.map +1 -0
  70. package/dist/config/config.d.ts +62 -0
  71. package/dist/config/config.d.ts.map +1 -0
  72. package/dist/config/config.js +107 -0
  73. package/dist/config/config.js.map +1 -0
  74. package/dist/config/defaults.d.ts +44 -0
  75. package/dist/config/defaults.d.ts.map +1 -0
  76. package/dist/config/defaults.js +217 -0
  77. package/dist/config/defaults.js.map +1 -0
  78. package/dist/config/index.d.ts +105 -0
  79. package/dist/config/index.d.ts.map +1 -0
  80. package/dist/config/index.js +163 -0
  81. package/dist/config/index.js.map +1 -0
  82. package/dist/database/adapters/mongoose.d.ts +106 -0
  83. package/dist/database/adapters/mongoose.d.ts.map +1 -0
  84. package/dist/database/adapters/mongoose.js +480 -0
  85. package/dist/database/adapters/mongoose.js.map +1 -0
  86. package/dist/database/adapters/prisma.d.ts +106 -0
  87. package/dist/database/adapters/prisma.d.ts.map +1 -0
  88. package/dist/database/adapters/prisma.js +494 -0
  89. package/dist/database/adapters/prisma.js.map +1 -0
  90. package/dist/database/defaults.d.ts +87 -0
  91. package/dist/database/defaults.d.ts.map +1 -0
  92. package/dist/database/defaults.js +271 -0
  93. package/dist/database/defaults.js.map +1 -0
  94. package/dist/database/index.d.ts +137 -0
  95. package/dist/database/index.d.ts.map +1 -0
  96. package/dist/database/index.js +490 -0
  97. package/dist/database/index.js.map +1 -0
  98. package/dist/email/defaults.d.ts +100 -0
  99. package/dist/email/defaults.d.ts.map +1 -0
  100. package/dist/email/defaults.js +400 -0
  101. package/dist/email/defaults.js.map +1 -0
  102. package/dist/email/email.d.ts +139 -0
  103. package/dist/email/email.d.ts.map +1 -0
  104. package/dist/email/email.js +316 -0
  105. package/dist/email/email.js.map +1 -0
  106. package/dist/email/index.d.ts +176 -0
  107. package/dist/email/index.d.ts.map +1 -0
  108. package/dist/email/index.js +251 -0
  109. package/dist/email/index.js.map +1 -0
  110. package/dist/email/strategies/console.d.ts +90 -0
  111. package/dist/email/strategies/console.d.ts.map +1 -0
  112. package/dist/email/strategies/console.js +268 -0
  113. package/dist/email/strategies/console.js.map +1 -0
  114. package/dist/email/strategies/resend.d.ts +84 -0
  115. package/dist/email/strategies/resend.d.ts.map +1 -0
  116. package/dist/email/strategies/resend.js +266 -0
  117. package/dist/email/strategies/resend.js.map +1 -0
  118. package/dist/email/strategies/smtp.d.ts +77 -0
  119. package/dist/email/strategies/smtp.d.ts.map +1 -0
  120. package/dist/email/strategies/smtp.js +286 -0
  121. package/dist/email/strategies/smtp.js.map +1 -0
  122. package/dist/error/defaults.d.ts +40 -0
  123. package/dist/error/defaults.d.ts.map +1 -0
  124. package/dist/error/defaults.js +75 -0
  125. package/dist/error/defaults.js.map +1 -0
  126. package/dist/error/error.d.ts +140 -0
  127. package/dist/error/error.d.ts.map +1 -0
  128. package/dist/error/error.js +200 -0
  129. package/dist/error/error.js.map +1 -0
  130. package/dist/error/index.d.ts +145 -0
  131. package/dist/error/index.d.ts.map +1 -0
  132. package/dist/error/index.js +145 -0
  133. package/dist/error/index.js.map +1 -0
  134. package/dist/event/defaults.d.ts +111 -0
  135. package/dist/event/defaults.d.ts.map +1 -0
  136. package/dist/event/defaults.js +378 -0
  137. package/dist/event/defaults.js.map +1 -0
  138. package/dist/event/event.d.ts +171 -0
  139. package/dist/event/event.d.ts.map +1 -0
  140. package/dist/event/event.js +391 -0
  141. package/dist/event/event.js.map +1 -0
  142. package/dist/event/index.d.ts +173 -0
  143. package/dist/event/index.d.ts.map +1 -0
  144. package/dist/event/index.js +302 -0
  145. package/dist/event/index.js.map +1 -0
  146. package/dist/event/strategies/memory.d.ts +122 -0
  147. package/dist/event/strategies/memory.d.ts.map +1 -0
  148. package/dist/event/strategies/memory.js +331 -0
  149. package/dist/event/strategies/memory.js.map +1 -0
  150. package/dist/event/strategies/redis.d.ts +115 -0
  151. package/dist/event/strategies/redis.d.ts.map +1 -0
  152. package/dist/event/strategies/redis.js +434 -0
  153. package/dist/event/strategies/redis.js.map +1 -0
  154. package/dist/index.d.ts +58 -0
  155. package/dist/index.d.ts.map +1 -0
  156. package/dist/index.js +72 -0
  157. package/dist/index.js.map +1 -0
  158. package/dist/logger/defaults.d.ts +67 -0
  159. package/dist/logger/defaults.d.ts.map +1 -0
  160. package/dist/logger/defaults.js +213 -0
  161. package/dist/logger/defaults.js.map +1 -0
  162. package/dist/logger/index.d.ts +84 -0
  163. package/dist/logger/index.d.ts.map +1 -0
  164. package/dist/logger/index.js +101 -0
  165. package/dist/logger/index.js.map +1 -0
  166. package/dist/logger/logger.d.ts +165 -0
  167. package/dist/logger/logger.d.ts.map +1 -0
  168. package/dist/logger/logger.js +843 -0
  169. package/dist/logger/logger.js.map +1 -0
  170. package/dist/logger/transports/console.d.ts +102 -0
  171. package/dist/logger/transports/console.d.ts.map +1 -0
  172. package/dist/logger/transports/console.js +276 -0
  173. package/dist/logger/transports/console.js.map +1 -0
  174. package/dist/logger/transports/database.d.ts +153 -0
  175. package/dist/logger/transports/database.d.ts.map +1 -0
  176. package/dist/logger/transports/database.js +539 -0
  177. package/dist/logger/transports/database.js.map +1 -0
  178. package/dist/logger/transports/file.d.ts +146 -0
  179. package/dist/logger/transports/file.d.ts.map +1 -0
  180. package/dist/logger/transports/file.js +464 -0
  181. package/dist/logger/transports/file.js.map +1 -0
  182. package/dist/logger/transports/http.d.ts +128 -0
  183. package/dist/logger/transports/http.d.ts.map +1 -0
  184. package/dist/logger/transports/http.js +401 -0
  185. package/dist/logger/transports/http.js.map +1 -0
  186. package/dist/logger/transports/webhook.d.ts +152 -0
  187. package/dist/logger/transports/webhook.d.ts.map +1 -0
  188. package/dist/logger/transports/webhook.js +485 -0
  189. package/dist/logger/transports/webhook.js.map +1 -0
  190. package/dist/queue/defaults.d.ts +66 -0
  191. package/dist/queue/defaults.d.ts.map +1 -0
  192. package/dist/queue/defaults.js +205 -0
  193. package/dist/queue/defaults.js.map +1 -0
  194. package/dist/queue/index.d.ts +124 -0
  195. package/dist/queue/index.d.ts.map +1 -0
  196. package/dist/queue/index.js +116 -0
  197. package/dist/queue/index.js.map +1 -0
  198. package/dist/queue/queue.d.ts +156 -0
  199. package/dist/queue/queue.d.ts.map +1 -0
  200. package/dist/queue/queue.js +387 -0
  201. package/dist/queue/queue.js.map +1 -0
  202. package/dist/queue/transports/database.d.ts +165 -0
  203. package/dist/queue/transports/database.d.ts.map +1 -0
  204. package/dist/queue/transports/database.js +595 -0
  205. package/dist/queue/transports/database.js.map +1 -0
  206. package/dist/queue/transports/memory.d.ts +143 -0
  207. package/dist/queue/transports/memory.d.ts.map +1 -0
  208. package/dist/queue/transports/memory.js +415 -0
  209. package/dist/queue/transports/memory.js.map +1 -0
  210. package/dist/queue/transports/redis.d.ts +203 -0
  211. package/dist/queue/transports/redis.d.ts.map +1 -0
  212. package/dist/queue/transports/redis.js +744 -0
  213. package/dist/queue/transports/redis.js.map +1 -0
  214. package/dist/security/defaults.d.ts +64 -0
  215. package/dist/security/defaults.d.ts.map +1 -0
  216. package/dist/security/defaults.js +159 -0
  217. package/dist/security/defaults.js.map +1 -0
  218. package/dist/security/index.d.ts +110 -0
  219. package/dist/security/index.d.ts.map +1 -0
  220. package/dist/security/index.js +160 -0
  221. package/dist/security/index.js.map +1 -0
  222. package/dist/security/security.d.ts +138 -0
  223. package/dist/security/security.d.ts.map +1 -0
  224. package/dist/security/security.js +419 -0
  225. package/dist/security/security.js.map +1 -0
  226. package/dist/storage/defaults.d.ts +79 -0
  227. package/dist/storage/defaults.d.ts.map +1 -0
  228. package/dist/storage/defaults.js +358 -0
  229. package/dist/storage/defaults.js.map +1 -0
  230. package/dist/storage/index.d.ts +153 -0
  231. package/dist/storage/index.d.ts.map +1 -0
  232. package/dist/storage/index.js +242 -0
  233. package/dist/storage/index.js.map +1 -0
  234. package/dist/storage/storage.d.ts +151 -0
  235. package/dist/storage/storage.d.ts.map +1 -0
  236. package/dist/storage/storage.js +439 -0
  237. package/dist/storage/storage.js.map +1 -0
  238. package/dist/storage/strategies/local.d.ts +117 -0
  239. package/dist/storage/strategies/local.d.ts.map +1 -0
  240. package/dist/storage/strategies/local.js +368 -0
  241. package/dist/storage/strategies/local.js.map +1 -0
  242. package/dist/storage/strategies/r2.d.ts +130 -0
  243. package/dist/storage/strategies/r2.d.ts.map +1 -0
  244. package/dist/storage/strategies/r2.js +470 -0
  245. package/dist/storage/strategies/r2.js.map +1 -0
  246. package/dist/storage/strategies/s3.d.ts +121 -0
  247. package/dist/storage/strategies/s3.d.ts.map +1 -0
  248. package/dist/storage/strategies/s3.js +461 -0
  249. package/dist/storage/strategies/s3.js.map +1 -0
  250. package/dist/util/defaults.d.ts +77 -0
  251. package/dist/util/defaults.d.ts.map +1 -0
  252. package/dist/util/defaults.js +193 -0
  253. package/dist/util/defaults.js.map +1 -0
  254. package/dist/util/index.d.ts +97 -0
  255. package/dist/util/index.d.ts.map +1 -0
  256. package/dist/util/index.js +165 -0
  257. package/dist/util/index.js.map +1 -0
  258. package/dist/util/util.d.ts +145 -0
  259. package/dist/util/util.d.ts.map +1 -0
  260. package/dist/util/util.js +481 -0
  261. package/dist/util/util.js.map +1 -0
  262. 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,9 @@
1
+ model {{FeatureName}} {
2
+ id Int @id @default(autoincrement())
3
+ name String
4
+ tenant_id String? // Mandatory for AppKit multi-tenant support
5
+ createdAt DateTime @default(now())
6
+ updatedAt DateTime @updatedAt
7
+
8
+ @@map("{{tableName}}")
9
+ }
@@ -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
+ }