@solidxai/core 0.1.6-beta.0 → 0.1.6-beta.10

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 (275) hide show
  1. package/dist/controllers/dashboard-layout.controller.d.ts +47 -0
  2. package/dist/controllers/dashboard-layout.controller.d.ts.map +1 -0
  3. package/dist/controllers/dashboard-layout.controller.js +204 -0
  4. package/dist/controllers/dashboard-layout.controller.js.map +1 -0
  5. package/dist/controllers/scheduled-job.controller.d.ts +1 -0
  6. package/dist/controllers/scheduled-job.controller.d.ts.map +1 -1
  7. package/dist/controllers/scheduled-job.controller.js +12 -0
  8. package/dist/controllers/scheduled-job.controller.js.map +1 -1
  9. package/dist/dtos/create-dashboard-layout.dto.d.ts +8 -0
  10. package/dist/dtos/create-dashboard-layout.dto.d.ts.map +1 -0
  11. package/dist/dtos/create-dashboard-layout.dto.js +53 -0
  12. package/dist/dtos/create-dashboard-layout.dto.js.map +1 -0
  13. package/dist/dtos/create-dashboard-variable.dto.d.ts +1 -0
  14. package/dist/dtos/create-dashboard-variable.dto.d.ts.map +1 -1
  15. package/dist/dtos/create-dashboard-variable.dto.js +7 -1
  16. package/dist/dtos/create-dashboard-variable.dto.js.map +1 -1
  17. package/dist/dtos/update-dashboard-layout.dto.d.ts +8 -0
  18. package/dist/dtos/update-dashboard-layout.dto.d.ts.map +1 -0
  19. package/dist/dtos/update-dashboard-layout.dto.js +53 -0
  20. package/dist/dtos/update-dashboard-layout.dto.js.map +1 -0
  21. package/dist/dtos/update-dashboard-variable.dto.d.ts +1 -0
  22. package/dist/dtos/update-dashboard-variable.dto.d.ts.map +1 -1
  23. package/dist/dtos/update-dashboard-variable.dto.js +7 -1
  24. package/dist/dtos/update-dashboard-variable.dto.js.map +1 -1
  25. package/dist/entities/action-metadata.entity.d.ts.map +1 -1
  26. package/dist/entities/action-metadata.entity.js.map +1 -1
  27. package/dist/entities/ai-interaction.entity.d.ts.map +1 -1
  28. package/dist/entities/ai-interaction.entity.js +5 -4
  29. package/dist/entities/ai-interaction.entity.js.map +1 -1
  30. package/dist/entities/chatter-message-details.entity.d.ts +1 -0
  31. package/dist/entities/chatter-message-details.entity.d.ts.map +1 -1
  32. package/dist/entities/chatter-message-details.entity.js +9 -4
  33. package/dist/entities/chatter-message-details.entity.js.map +1 -1
  34. package/dist/entities/chatter-message.entity.d.ts.map +1 -1
  35. package/dist/entities/chatter-message.entity.js +4 -3
  36. package/dist/entities/chatter-message.entity.js.map +1 -1
  37. package/dist/entities/common.entity.js +1 -1
  38. package/dist/entities/common.entity.js.map +1 -1
  39. package/dist/entities/dashboard-layout.entity.d.ts +9 -0
  40. package/dist/entities/dashboard-layout.entity.d.ts.map +1 -0
  41. package/dist/entities/dashboard-layout.entity.js +41 -0
  42. package/dist/entities/dashboard-layout.entity.js.map +1 -0
  43. package/dist/entities/dashboard-question-sql-dataset-config.entity.d.ts.map +1 -1
  44. package/dist/entities/dashboard-question-sql-dataset-config.entity.js +5 -4
  45. package/dist/entities/dashboard-question-sql-dataset-config.entity.js.map +1 -1
  46. package/dist/entities/dashboard-question.entity.d.ts.map +1 -1
  47. package/dist/entities/dashboard-question.entity.js +5 -4
  48. package/dist/entities/dashboard-question.entity.js.map +1 -1
  49. package/dist/entities/dashboard-variable.entity.d.ts +1 -0
  50. package/dist/entities/dashboard-variable.entity.d.ts.map +1 -1
  51. package/dist/entities/dashboard-variable.entity.js +10 -4
  52. package/dist/entities/dashboard-variable.entity.js.map +1 -1
  53. package/dist/entities/dashboard.entity.d.ts +2 -0
  54. package/dist/entities/dashboard.entity.d.ts.map +1 -1
  55. package/dist/entities/dashboard.entity.js +9 -3
  56. package/dist/entities/dashboard.entity.js.map +1 -1
  57. package/dist/entities/email-attachment.entity.d.ts.map +1 -1
  58. package/dist/entities/email-attachment.entity.js +2 -1
  59. package/dist/entities/email-attachment.entity.js.map +1 -1
  60. package/dist/entities/email-template.entity.js +1 -1
  61. package/dist/entities/email-template.entity.js.map +1 -1
  62. package/dist/entities/export-transaction.entity.d.ts.map +1 -1
  63. package/dist/entities/export-transaction.entity.js +2 -1
  64. package/dist/entities/export-transaction.entity.js.map +1 -1
  65. package/dist/entities/field-metadata.entity.js +2 -2
  66. package/dist/entities/field-metadata.entity.js.map +1 -1
  67. package/dist/entities/import-transaction-error-log.entity.d.ts.map +1 -1
  68. package/dist/entities/import-transaction-error-log.entity.js +3 -2
  69. package/dist/entities/import-transaction-error-log.entity.js.map +1 -1
  70. package/dist/entities/import-transaction.entity.d.ts.map +1 -1
  71. package/dist/entities/import-transaction.entity.js +2 -1
  72. package/dist/entities/import-transaction.entity.js.map +1 -1
  73. package/dist/entities/legacy-common.entity.d.ts.map +1 -1
  74. package/dist/entities/legacy-common.entity.js +1 -1
  75. package/dist/entities/legacy-common.entity.js.map +1 -1
  76. package/dist/entities/mq-message.entity.d.ts.map +1 -1
  77. package/dist/entities/mq-message.entity.js +4 -3
  78. package/dist/entities/mq-message.entity.js.map +1 -1
  79. package/dist/entities/saved-filters.entity.d.ts.map +1 -1
  80. package/dist/entities/saved-filters.entity.js +3 -2
  81. package/dist/entities/saved-filters.entity.js.map +1 -1
  82. package/dist/entities/security-rule.entity.d.ts.map +1 -1
  83. package/dist/entities/security-rule.entity.js +2 -1
  84. package/dist/entities/security-rule.entity.js.map +1 -1
  85. package/dist/entities/sms-template.entity.js +1 -1
  86. package/dist/entities/sms-template.entity.js.map +1 -1
  87. package/dist/entities/user-view-metadata.entity.d.ts.map +1 -1
  88. package/dist/entities/user-view-metadata.entity.js +2 -1
  89. package/dist/entities/user-view-metadata.entity.js.map +1 -1
  90. package/dist/entities/view-metadata.entity.d.ts.map +1 -1
  91. package/dist/entities/view-metadata.entity.js.map +1 -1
  92. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts +1 -0
  93. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts.map +1 -1
  94. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js +8 -9
  95. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js.map +1 -1
  96. package/dist/helpers/solid-registry.d.ts +3 -1
  97. package/dist/helpers/solid-registry.d.ts.map +1 -1
  98. package/dist/helpers/solid-registry.js.map +1 -1
  99. package/dist/helpers/typeorm-db-helper.d.ts.map +1 -1
  100. package/dist/helpers/typeorm-db-helper.js +21 -0
  101. package/dist/helpers/typeorm-db-helper.js.map +1 -1
  102. package/dist/index.d.ts +1 -0
  103. package/dist/index.d.ts.map +1 -1
  104. package/dist/index.js +1 -0
  105. package/dist/index.js.map +1 -1
  106. package/dist/interfaces.d.ts +4 -1
  107. package/dist/interfaces.d.ts.map +1 -1
  108. package/dist/interfaces.js.map +1 -1
  109. package/dist/repository/dashboard-layout.repository.d.ts +12 -0
  110. package/dist/repository/dashboard-layout.repository.d.ts.map +1 -0
  111. package/dist/repository/dashboard-layout.repository.js +34 -0
  112. package/dist/repository/dashboard-layout.repository.js.map +1 -0
  113. package/dist/seeders/module-metadata-seeder.service.js +4 -4
  114. package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
  115. package/dist/seeders/seed-data/solid-core-metadata.json +445 -35
  116. package/dist/services/authentication.service.d.ts.map +1 -1
  117. package/dist/services/authentication.service.js +45 -21
  118. package/dist/services/authentication.service.js.map +1 -1
  119. package/dist/services/chatter-message.service.d.ts.map +1 -1
  120. package/dist/services/chatter-message.service.js +26 -0
  121. package/dist/services/chatter-message.service.js.map +1 -1
  122. package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.d.ts.map +1 -1
  123. package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js +6 -5
  124. package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js.map +1 -1
  125. package/dist/services/computed-fields/entity/sequence-num-computed-field-provider.d.ts.map +1 -1
  126. package/dist/services/computed-fields/entity/sequence-num-computed-field-provider.js +9 -10
  127. package/dist/services/computed-fields/entity/sequence-num-computed-field-provider.js.map +1 -1
  128. package/dist/services/dashboard-layout.service.d.ts +20 -0
  129. package/dist/services/dashboard-layout.service.d.ts.map +1 -0
  130. package/dist/services/dashboard-layout.service.js +120 -0
  131. package/dist/services/dashboard-layout.service.js.map +1 -0
  132. package/dist/services/dashboard-question.service.d.ts +4 -0
  133. package/dist/services/dashboard-question.service.d.ts.map +1 -1
  134. package/dist/services/dashboard-question.service.js +22 -8
  135. package/dist/services/dashboard-question.service.js.map +1 -1
  136. package/dist/services/dashboard.service.d.ts +2 -0
  137. package/dist/services/dashboard.service.d.ts.map +1 -1
  138. package/dist/services/dashboard.service.js +4 -0
  139. package/dist/services/dashboard.service.js.map +1 -1
  140. package/dist/services/model-metadata.service.d.ts +3 -1
  141. package/dist/services/model-metadata.service.d.ts.map +1 -1
  142. package/dist/services/model-metadata.service.js +122 -8
  143. package/dist/services/model-metadata.service.js.map +1 -1
  144. package/dist/services/question-data-providers/chartjs-sql-data-provider.service.d.ts +2 -4
  145. package/dist/services/question-data-providers/chartjs-sql-data-provider.service.d.ts.map +1 -1
  146. package/dist/services/question-data-providers/chartjs-sql-data-provider.service.js +2 -1
  147. package/dist/services/question-data-providers/chartjs-sql-data-provider.service.js.map +1 -1
  148. package/dist/services/question-data-providers/interfaces.d.ts +1 -0
  149. package/dist/services/question-data-providers/interfaces.d.ts.map +1 -0
  150. package/dist/services/question-data-providers/interfaces.js +1 -0
  151. package/dist/services/question-data-providers/interfaces.js.map +1 -0
  152. package/dist/services/question-data-providers/prime-react-datatable-sql-data-provider.service.d.ts +2 -5
  153. package/dist/services/question-data-providers/prime-react-datatable-sql-data-provider.service.d.ts.map +1 -1
  154. package/dist/services/question-data-providers/prime-react-datatable-sql-data-provider.service.js +2 -1
  155. package/dist/services/question-data-providers/prime-react-datatable-sql-data-provider.service.js.map +1 -1
  156. package/dist/services/question-data-providers/prime-react-meter-group-sql-data-provider.service.d.ts +2 -5
  157. package/dist/services/question-data-providers/prime-react-meter-group-sql-data-provider.service.d.ts.map +1 -1
  158. package/dist/services/question-data-providers/prime-react-meter-group-sql-data-provider.service.js +2 -1
  159. package/dist/services/question-data-providers/prime-react-meter-group-sql-data-provider.service.js.map +1 -1
  160. package/dist/services/queues/database-subscriber.service.d.ts +4 -2
  161. package/dist/services/queues/database-subscriber.service.d.ts.map +1 -1
  162. package/dist/services/queues/database-subscriber.service.js +15 -2
  163. package/dist/services/queues/database-subscriber.service.js.map +1 -1
  164. package/dist/services/queues/publisher-factory.service.d.ts.map +1 -1
  165. package/dist/services/queues/publisher-factory.service.js +4 -6
  166. package/dist/services/queues/publisher-factory.service.js.map +1 -1
  167. package/dist/services/queues/rabbitmq-subscriber.service.d.ts +8 -3
  168. package/dist/services/queues/rabbitmq-subscriber.service.d.ts.map +1 -1
  169. package/dist/services/queues/rabbitmq-subscriber.service.js +72 -5
  170. package/dist/services/queues/rabbitmq-subscriber.service.js.map +1 -1
  171. package/dist/services/scheduled-job.service.d.ts +6 -1
  172. package/dist/services/scheduled-job.service.d.ts.map +1 -1
  173. package/dist/services/scheduled-job.service.js +26 -2
  174. package/dist/services/scheduled-job.service.js.map +1 -1
  175. package/dist/services/scheduled-jobs/scheduler.interface.d.ts +2 -0
  176. package/dist/services/scheduled-jobs/scheduler.interface.d.ts.map +1 -1
  177. package/dist/services/scheduled-jobs/scheduler.interface.js.map +1 -1
  178. package/dist/services/scheduled-jobs/scheduler.service.d.ts +6 -2
  179. package/dist/services/scheduled-jobs/scheduler.service.d.ts.map +1 -1
  180. package/dist/services/scheduled-jobs/scheduler.service.js +75 -17
  181. package/dist/services/scheduled-jobs/scheduler.service.js.map +1 -1
  182. package/dist/services/selection-providers/list-of-dashboard-question-providers-selection-provider.service.d.ts.map +1 -1
  183. package/dist/services/selection-providers/list-of-dashboard-question-providers-selection-provider.service.js +4 -1
  184. package/dist/services/selection-providers/list-of-dashboard-question-providers-selection-provider.service.js.map +1 -1
  185. package/dist/services/solid-ts-morph.service.d.ts +9 -0
  186. package/dist/services/solid-ts-morph.service.d.ts.map +1 -1
  187. package/dist/services/solid-ts-morph.service.js +76 -0
  188. package/dist/services/solid-ts-morph.service.js.map +1 -1
  189. package/dist/solid-core.module.d.ts.map +1 -1
  190. package/dist/solid-core.module.js +8 -0
  191. package/dist/solid-core.module.js.map +1 -1
  192. package/dist/subscribers/computed-entity-field.subscriber.d.ts.map +1 -1
  193. package/dist/subscribers/computed-entity-field.subscriber.js +9 -1
  194. package/dist/subscribers/computed-entity-field.subscriber.js.map +1 -1
  195. package/dist/transformers/typeorm/local-date-time-transformer.d.ts +4 -4
  196. package/dist/transformers/typeorm/local-date-time-transformer.d.ts.map +1 -1
  197. package/dist/transformers/typeorm/local-date-time-transformer.js +25 -28
  198. package/dist/transformers/typeorm/local-date-time-transformer.js.map +1 -1
  199. package/dist-tests/api/authenticate.spec.js +119 -0
  200. package/dist-tests/api/authenticate.spec.js.map +1 -0
  201. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js +97 -0
  202. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js.map +1 -0
  203. package/dist-tests/api/ping.spec.js +21 -0
  204. package/dist-tests/api/ping.spec.js.map +1 -0
  205. package/dist-tests/helpers/auth.js +41 -0
  206. package/dist-tests/helpers/auth.js.map +1 -0
  207. package/dist-tests/helpers/env.js +11 -0
  208. package/dist-tests/helpers/env.js.map +1 -0
  209. package/package.json +1 -1
  210. package/sql/default/mariadb/proc_CleanupModelMetadata.sql +153 -0
  211. package/sql/default/mariadb/proc_CleanupModuleMetadata.sql +56 -0
  212. package/sql/default/mysql/proc_CleanupModelMetadata.sql +153 -0
  213. package/sql/default/mysql/proc_CleanupModuleMetadata.sql +56 -0
  214. package/src/controllers/dashboard-layout.controller.ts +106 -0
  215. package/src/controllers/scheduled-job.controller.ts +6 -0
  216. package/src/dtos/create-dashboard-layout.dto.ts +31 -0
  217. package/src/dtos/create-dashboard-variable.dto.ts +4 -0
  218. package/src/dtos/update-dashboard-layout.dto.ts +30 -0
  219. package/src/dtos/update-dashboard-variable.dto.ts +5 -1
  220. package/src/entities/action-metadata.entity.ts +3 -2
  221. package/src/entities/ai-interaction.entity.ts +5 -4
  222. package/src/entities/chatter-message-details.entity.ts +7 -3
  223. package/src/entities/chatter-message.entity.ts +4 -3
  224. package/src/entities/common.entity.ts +2 -2
  225. package/src/entities/dashboard-layout.entity.ts +18 -0
  226. package/src/entities/dashboard-question-sql-dataset-config.entity.ts +5 -4
  227. package/src/entities/dashboard-question.entity.ts +5 -4
  228. package/src/entities/dashboard-variable.entity.ts +9 -4
  229. package/src/entities/dashboard.entity.ts +7 -2
  230. package/src/entities/email-attachment.entity.ts +2 -1
  231. package/src/entities/email-template.entity.ts +1 -1
  232. package/src/entities/export-transaction.entity.ts +2 -1
  233. package/src/entities/field-metadata.entity.ts +2 -2
  234. package/src/entities/import-transaction-error-log.entity.ts +3 -2
  235. package/src/entities/import-transaction.entity.ts +2 -1
  236. package/src/entities/legacy-common.entity.ts +3 -4
  237. package/src/entities/mq-message.entity.ts +4 -3
  238. package/src/entities/saved-filters.entity.ts +3 -2
  239. package/src/entities/security-rule.entity.ts +2 -1
  240. package/src/entities/sms-template.entity.ts +1 -1
  241. package/src/entities/user-view-metadata.entity.ts +2 -1
  242. package/src/entities/view-metadata.entity.ts +3 -0
  243. package/src/helpers/field-crud-managers/MediaFieldCrudManager.ts +9 -9
  244. package/src/helpers/solid-registry.ts +3 -2
  245. package/src/helpers/typeorm-db-helper.ts +26 -0
  246. package/src/index.ts +1 -0
  247. package/src/interfaces.ts +7 -1
  248. package/src/repository/dashboard-layout.repository.ts +17 -0
  249. package/src/seeders/module-metadata-seeder.service.ts +5 -5
  250. package/src/seeders/seed-data/solid-core-metadata.json +446 -36
  251. package/src/services/authentication.service.ts +47 -24
  252. package/src/services/chatter-message.service.ts +26 -0
  253. package/src/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.ts +6 -5
  254. package/src/services/computed-fields/entity/sequence-num-computed-field-provider.ts +17 -22
  255. package/src/services/dashboard-layout.service.ts +111 -0
  256. package/src/services/dashboard-question.service.ts +23 -4
  257. package/src/services/dashboard.service.ts +7 -0
  258. package/src/services/model-metadata.service.ts +131 -50
  259. package/src/services/question-data-providers/chartjs-sql-data-provider.service.ts +3 -7
  260. package/src/services/question-data-providers/interfaces.ts +0 -0
  261. package/src/services/question-data-providers/prime-react-datatable-sql-data-provider.service.ts +4 -8
  262. package/src/services/question-data-providers/prime-react-meter-group-sql-data-provider.service.ts +4 -8
  263. package/src/services/queues/database-subscriber.service.ts +19 -2
  264. package/src/services/queues/publisher-factory.service.ts +8 -6
  265. package/src/services/queues/rabbitmq-subscriber.service.ts +115 -5
  266. package/src/services/scheduled-job.service.ts +31 -2
  267. package/src/services/scheduled-jobs/scheduler.interface.ts +4 -1
  268. package/src/services/scheduled-jobs/scheduler.service.ts +82 -20
  269. package/src/services/selection-providers/list-of-dashboard-question-providers-selection-provider.service.ts +4 -1
  270. package/src/services/solid-ts-morph.service.ts +98 -0
  271. package/src/solid-core.module.ts +12 -0
  272. package/src/subscribers/computed-entity-field.subscriber.ts +9 -3
  273. package/src/transformers/typeorm/local-date-time-transformer.ts +41 -33
  274. package/.claude/settings.local.json +0 -15
  275. package/src/services/1.js +0 -6
@@ -1,6 +1,7 @@
1
1
  import { BadRequestException, forwardRef, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
2
2
  import { InjectDataSource } from '@nestjs/typeorm';
3
3
  import * as fs from 'fs/promises'; // Use the Promise-based version of fs for async/await
4
+ import * as path from 'path';
4
5
  import { DataSource, EntityManager, In, Repository, SelectQueryBuilder } from 'typeorm';
5
6
  import { CreateModelMetadataDto } from '../dtos/create-model-metadata.dto';
6
7
  import { ModelMetadata } from '../entities/model-metadata.entity';
@@ -33,6 +34,7 @@ import { RoleMetadataService } from './role-metadata.service';
33
34
  import { NavigationDto } from 'src/dtos/navigation.dto';
34
35
  import { SolidIntrospectService } from './solid-introspect.service';
35
36
  import { CRUDService } from './crud.service';
37
+ import { SolidTsMorphService } from './solid-ts-morph.service';
36
38
 
37
39
  @Injectable()
38
40
  export class ModelMetadataService {
@@ -54,8 +56,9 @@ export class ModelMetadataService {
54
56
  private readonly roleService: RoleMetadataService,
55
57
  private readonly moduleMetadataHelperService: ModuleMetadataHelperService,
56
58
  readonly introspectService: SolidIntrospectService,
59
+ private readonly solidTsMorphService: SolidTsMorphService,
57
60
 
58
- // No longer used.
61
+ // No longer used.
59
62
  // private readonly generateCodePublihser: GenerateCodePublisherDatabase,
60
63
  ) { }
61
64
 
@@ -225,6 +228,7 @@ export class ModelMetadataService {
225
228
  let userKeyField = null;
226
229
  const listViewLayout = [];
227
230
  const formViewLayout = [];
231
+ const treeViewLayout = [];
228
232
 
229
233
  for (let k = 0; k < fieldsMetadata.length; k++) {
230
234
  const fieldMetadata = fieldsMetadata[k];
@@ -245,6 +249,7 @@ export class ModelMetadataService {
245
249
  }
246
250
  listViewLayout.push({ type: "field", attrs: { name: `${affectedField.name}` } })
247
251
  formViewLayout.push({ type: "field", attrs: { name: `${affectedField.name}` } })
252
+ treeViewLayout.push({ type: "field", attrs: { name: `${affectedField.name}` } })
248
253
 
249
254
  }
250
255
 
@@ -705,48 +710,24 @@ export class ModelMetadataService {
705
710
  await fs.writeFile(filePath, updatedContent);
706
711
  }
707
712
 
708
- // <moduleName>.module.ts | Remove all references and imports of the above files. | Manual (X)
709
- // const moduleFilePath = path.resolve(modulePath, `${dasherize(modelEntity.module.name)}.module.ts`);
710
-
711
- // this.logger.log(`Working on module file ${moduleFilePath}`);
712
- // const project = new Project();
713
- // const sourceFile = project.addSourceFileAtPath(moduleFilePath);
714
-
715
- // // Remove import declarations related to deleted files
716
- // sourceFile.getImportDeclarations().forEach(importDecl => {
717
- // const moduleSpecifier = importDecl.getModuleSpecifierValue();
718
- // const resolvedPath = importDecl.getModuleSpecifierSourceFile()?.getFilePath() || '';
719
- // if (filesToDelete.some(file => resolvedPath.endsWith(file))) {
720
- // importDecl.remove();
721
- // }
722
- // });
723
-
724
- // // Remove identifiers from `@Module` metadata (imports, providers, controllers)
725
- // const moduleDecorator = sourceFile.getFirstDescendantByKind(SyntaxKind.Decorator);
726
- // const objectLiteral = moduleDecorator?.getCallExpression()?.getArguments()?.[0];
727
-
728
- // if (objectLiteral && objectLiteral.getKind() === SyntaxKind.ObjectLiteralExpression) {
729
- // const objectLiteralExpr = objectLiteral.asKindOrThrow(SyntaxKind.ObjectLiteralExpression);
730
-
731
- // for (const propName of ['imports', 'providers', 'controllers', 'exports']) {
732
- // const prop = objectLiteralExpr.getProperty(propName);
733
- // if (prop && prop.getKind() === SyntaxKind.PropertyAssignment) {
734
- // const elements = prop.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression);
735
- // elements?.getElements().forEach(el => {
736
- // const text = el.getText();
737
- // if (filesToDelete.some(file => text.toLowerCase().includes(file.split('.')[0]))) {
738
- // // @ts-ignore
739
- // el.remove();
740
- // }
741
- // });
742
- // }
743
- // }
744
- // }
745
-
746
- // // Save changes
747
- // sourceFile.saveSync();
748
-
749
- // Run seeder to reflect the removal.
713
+ // <moduleName>.module.ts | Remove all references and imports of the deleted model files. | Automatic
714
+ if (modulePath) {
715
+ const moduleFilePath = path.resolve(modulePath, `${dasherize(modelEntity.module?.name)}.module.ts`);
716
+ this.logger.log(`Removing model '${modelEntity.singularName}' references from module file: ${moduleFilePath}`);
717
+ try {
718
+ this.solidTsMorphService.begin();
719
+ const modelPathSegment = `/${dasherize(modelEntity.singularName)}.`;
720
+ const { removedIdentifiers } = this.solidTsMorphService.removeImports(
721
+ moduleFilePath,
722
+ spec => spec.includes(modelPathSegment)
723
+ );
724
+ this.solidTsMorphService.removeModuleMembers(moduleFilePath, removedIdentifiers);
725
+ await this.solidTsMorphService.commit();
726
+ } catch (error) {
727
+ this.solidTsMorphService.rollback();
728
+ this.logger.error(`Failed to clean up module file for model '${modelEntity.singularName}':`, error);
729
+ }
730
+ }
750
731
 
751
732
  // - | Drop database table | Removes the database table from the DB, this is a very risky step. Best to review all relations to other models etc and then do this manually | Manual (X)
752
733
 
@@ -818,6 +799,7 @@ export class ModelMetadataService {
818
799
  const metaData = await this.moduleMetadataHelperService.getModuleMetadataConfiguration(filePath);
819
800
 
820
801
  const listViewLayoutFields = [{ type: "field", attrs: { name: `id` } }];
802
+ const treeViewLayoutFields = [{ type: "field", attrs: { name: `id` } }];
821
803
  const formViewLayoutFields = [];
822
804
 
823
805
  for (let i = 0; i < model.fields.length; i++) {
@@ -825,8 +807,9 @@ export class ModelMetadataService {
825
807
  if (field.isSystem) continue;
826
808
  listViewLayoutFields.push({ type: "field", attrs: { name: `${field.name}` } })
827
809
  formViewLayoutFields.push({ type: "field", attrs: { name: `${field.name}` } })
810
+ treeViewLayoutFields.push({ type: "field", attrs: { name: `${field.name}` } })
828
811
  }
829
- this.populateVAMConfigInFileInternal(formViewLayoutFields, model, listViewLayoutFields, metaData);
812
+ this.populateVAMConfigInFileInternal(formViewLayoutFields, model, listViewLayoutFields, treeViewLayoutFields, metaData);
830
813
  // Write the updated object back to the file
831
814
  const updatedContent = JSON.stringify(metaData, null, 2);
832
815
  await fs.writeFile(filePath, updatedContent);
@@ -839,7 +822,7 @@ export class ModelMetadataService {
839
822
  }
840
823
 
841
824
  // Populate the View, Actions and Menus in the config file
842
- private populateVAMConfigInFileInternal(formViewLayoutFields: any[], model: ModelMetadata, listViewLayoutFields: { type: string; attrs: { name: string; }; }[], metaData: any) {
825
+ private populateVAMConfigInFileInternal(formViewLayoutFields: any[], model: ModelMetadata, listViewLayoutFields: { type: string; attrs: { name: string; }; }[], treeViewLayoutFields: { type: string; attrs: { name: string; }; }[], metaData: any) {
843
826
  const column1Fields = [];
844
827
  const column2Fields = [];
845
828
 
@@ -852,7 +835,9 @@ export class ModelMetadataService {
852
835
  }
853
836
  }
854
837
  const actionName = `${model.singularName}-list-action`;
855
- const viewName = `${model.singularName}-list-view`;
838
+ const treeViewActionName = `${model.singularName}-tree-view-action`;
839
+ const listViewName = `${model.singularName}-list-view`;
840
+ const treeViewName = `${model.singularName}-tree-view`;
856
841
  const formViewName = `${model.singularName}-form-view`;
857
842
  const menuName = `${model.singularName}-menu-item`;
858
843
 
@@ -865,7 +850,21 @@ export class ModelMetadataService {
865
850
  customComponent: ``,
866
851
  customIsModal: true,
867
852
  serverEndpoint: "",
868
- viewUserKey: viewName,
853
+ viewUserKey: listViewName,
854
+ moduleUserKey: `${model.module.name}`,
855
+ modelUserKey: `${model.singularName}`
856
+ };
857
+
858
+ const treeViewAction = {
859
+ displayName: `${model.displayName} Tree View Action`,
860
+ name: treeViewActionName,
861
+ type: "solid",
862
+ domain: "",
863
+ context: "",
864
+ customComponent: ``,
865
+ customIsModal: true,
866
+ serverEndpoint: "",
867
+ viewUserKey: treeViewName,
869
868
  moduleUserKey: `${model.module.name}`,
870
869
  modelUserKey: `${model.singularName}`
871
870
  };
@@ -877,11 +876,11 @@ export class ModelMetadataService {
877
876
  actionUserKey: actionName,
878
877
  moduleUserKey: `${model.module.name}`,
879
878
  parentMenuItemUserKey: "",
880
- iconName : ""
879
+ iconName: ""
881
880
  };
882
881
 
883
882
  const modelListview = {
884
- name: viewName,
883
+ name: listViewName,
885
884
  displayName: `${model.displayName}`,
886
885
  type: "list",
887
886
  context: "{}",
@@ -905,6 +904,31 @@ export class ModelMetadataService {
905
904
  }
906
905
  };
907
906
 
907
+ const modelTreeview = {
908
+ name: treeViewName,
909
+ displayName: `${model.displayName}`,
910
+ type: "tree",
911
+ context: "{}",
912
+ moduleUserKey: `${model.module.name}`,
913
+ modelUserKey: `${model.singularName}`,
914
+ layout: {
915
+ type: "tree",
916
+ attrs: {
917
+ pagination: true,
918
+ pageSizeOptions: [
919
+ 10,
920
+ 25,
921
+ 50
922
+ ],
923
+ enableGlobalSearch: true,
924
+ create: true,
925
+ edit: true,
926
+ delete: true
927
+ },
928
+ children: treeViewLayoutFields
929
+ }
930
+ };
931
+
908
932
 
909
933
  const modelFormView = {
910
934
  name: formViewName,
@@ -954,10 +978,18 @@ export class ModelMetadataService {
954
978
  metaData.actions.push(action);
955
979
  }
956
980
 
957
- if (notExists(metaData.views, viewName)) {
981
+ if (notExists(metaData.actions, treeViewActionName)) {
982
+ metaData.actions.push(treeViewAction);
983
+ }
984
+
985
+ if (notExists(metaData.views, listViewName)) {
958
986
  metaData.views.push(modelListview);
959
987
  }
960
988
 
989
+ if (notExists(metaData.views, treeViewName)) {
990
+ metaData.views.push(modelTreeview);
991
+ }
992
+
961
993
  if (notExists(metaData.views, formViewName)) {
962
994
  metaData.views.push(modelFormView);
963
995
  }
@@ -979,6 +1011,14 @@ export class ModelMetadataService {
979
1011
  }
980
1012
  }));
981
1013
 
1014
+ const treeViewLayout = jsonFieldsList.map(field => ({
1015
+ type: "field",
1016
+ attrs: {
1017
+ name: `${field.name}`,
1018
+ isSearchable: true,
1019
+ }
1020
+ }));
1021
+
982
1022
  const formViewLayout = jsonFieldsList.map(field => ({
983
1023
  type: "field",
984
1024
  attrs: {
@@ -1019,6 +1059,26 @@ export class ModelMetadataService {
1019
1059
  children: listViewLayout
1020
1060
  }, null, 3)
1021
1061
  },
1062
+ {
1063
+ name: `${model.singularName}-tree-view`,
1064
+ displayName: `${model.displayName}`,
1065
+ type: 'tree',
1066
+ context: "{}",
1067
+ module: resolvedModule,
1068
+ model: model,
1069
+ layout: JSON.stringify({
1070
+ type: "tree",
1071
+ attrs: {
1072
+ pagination: true,
1073
+ pageSizeOptions: [10, 25, 50],
1074
+ enableGlobalSearch: true,
1075
+ create: true,
1076
+ edit: true,
1077
+ delete: true
1078
+ },
1079
+ children: treeViewLayout
1080
+ }, null, 3)
1081
+ },
1022
1082
  {
1023
1083
  name: `${model.singularName}-form-view`,
1024
1084
  displayName: `${model.displayName}`,
@@ -1067,6 +1127,7 @@ export class ModelMetadataService {
1067
1127
  }
1068
1128
 
1069
1129
  let view = await viewRepo.findOne({ where: { name: `${model.singularName}-list-view` } });
1130
+ let treeView = await viewRepo.findOne({ where: { name: `${model.singularName}-tree-view` } });
1070
1131
 
1071
1132
  const actionData = {
1072
1133
  displayName: `${model.displayName} List Action`,
@@ -1082,13 +1143,33 @@ export class ModelMetadataService {
1082
1143
  model: model
1083
1144
  };
1084
1145
 
1146
+ const treeViewActionData = {
1147
+ displayName: `${model.displayName} Tree View Action`,
1148
+ name: `${model.singularName}-tree-view-action`,
1149
+ type: "solid",
1150
+ domain: "",
1151
+ context: "",
1152
+ customComponent: ``,
1153
+ customIsModal: true,
1154
+ serverEndpoint: "",
1155
+ view: treeView,
1156
+ module: resolvedModule,
1157
+ model: model
1158
+ };
1159
+
1085
1160
  let existingAction = await actionRepo.findOne({ where: { name: actionData.name } });
1161
+ let existingTreeViewAction = await actionRepo.findOne({ where: { name: treeViewActionData.name } });
1086
1162
 
1087
1163
  if (!existingAction) {
1088
1164
  const createdAction = actionRepo.create(actionData);
1089
1165
  existingAction = await actionRepo.save(createdAction);
1090
1166
  }
1091
1167
 
1168
+ if (!existingTreeViewAction) {
1169
+ const createdTreeViewAction = actionRepo.create(treeViewActionData);
1170
+ existingTreeViewAction = await actionRepo.save(createdTreeViewAction);
1171
+ }
1172
+
1092
1173
  const adminRole = await this.roleService.findRoleByName('Admin');
1093
1174
 
1094
1175
  const menuData = {
@@ -1,16 +1,11 @@
1
1
  import { Injectable, Logger } from "@nestjs/common";
2
2
  import { DashboardQuestionDataProvider } from "src/decorators/dashboard-question-data-provider.decorator";
3
3
  import { DashboardQuestion } from "src/entities/dashboard-question.entity";
4
- import { IDashboardQuestionDataProvider } from "src/interfaces";
4
+ import { IDashboardQuestionDataProvider, QuestionSqlDataProviderContext } from "src/interfaces";
5
5
  import { EntityManager } from "typeorm";
6
6
  import { SqlExpressionResolverService } from "../sql-expression-resolver.service";
7
7
  import { getKpi, getLabels } from "./helpers";
8
8
 
9
- export interface QuestionSqlDataProviderContext {
10
- // questionSqlDatasetConfig: QuestionSqlDatasetConfig;
11
- // questionId: number;
12
- // question: Question;
13
- }
14
9
 
15
10
  export enum SqlExpressionOperator {
16
11
  EQUALS = '$equals',
@@ -49,7 +44,8 @@ export class ChartJsSqlDataProvider implements IDashboardQuestionDataProvider<Qu
49
44
  return "ChartJsSqlDataProvider";
50
45
  }
51
46
 
52
- async getData(question: DashboardQuestion, expressions?: SqlExpression[], context?: QuestionSqlDataProviderContext): Promise<any> {
47
+ async getData(question: DashboardQuestion, context?: QuestionSqlDataProviderContext): Promise<any> {
48
+ const expressions: SqlExpression[] = context?.expressions || [];
53
49
  // TODO: put some validation to check if the results of each SQL in each dataset returns the same number of rows
54
50
 
55
51
  // This is what we have to return.
@@ -1,19 +1,13 @@
1
1
  import { Injectable } from "@nestjs/common";
2
2
  import { DashboardQuestionDataProvider } from "src/decorators/dashboard-question-data-provider.decorator";
3
3
  import { DashboardQuestion } from "src/entities/dashboard-question.entity";
4
- import { IDashboardQuestionDataProvider } from "src/interfaces";
4
+ import { IDashboardQuestionDataProvider, QuestionSqlDataProviderContext } from "src/interfaces";
5
5
  import { EntityManager } from "typeorm";
6
6
  import { SqlExpressionResolverService } from "../sql-expression-resolver.service";
7
7
  import { Logger } from '@nestjs/common';
8
8
  import { SqlExpression } from "./chartjs-sql-data-provider.service";
9
9
  import { getKpi } from "./helpers";
10
10
 
11
- export interface QuestionSqlDataProviderContext {
12
- // questionSqlDatasetConfig: QuestionSqlDatasetConfig;
13
- // questionId: number;
14
- // question: Question;
15
- }
16
-
17
11
  @DashboardQuestionDataProvider()
18
12
  @Injectable()
19
13
  export class PrimeReactDatatableSqlDataProvider implements IDashboardQuestionDataProvider<QuestionSqlDataProviderContext, any> {
@@ -29,7 +23,9 @@ export class PrimeReactDatatableSqlDataProvider implements IDashboardQuestionDat
29
23
  return "PrimeReactDatatableSqlDataProvider";
30
24
  }
31
25
 
32
- async getData(question: DashboardQuestion, expressions?: SqlExpression[], context?: QuestionSqlDataProviderContext): Promise<any> {
26
+ async getData(question: DashboardQuestion, context?: QuestionSqlDataProviderContext): Promise<any> {
27
+ const expressions: SqlExpression[] = context?.expressions || [];
28
+
33
29
  // TODO: put some validation to check if the results of each SQL in each dataset returns the same number of rows
34
30
 
35
31
  // Check the expected response for prime react data tables to understand what is going on here...
@@ -1,19 +1,13 @@
1
1
  import { Injectable } from "@nestjs/common";
2
2
  import { DashboardQuestionDataProvider } from "src/decorators/dashboard-question-data-provider.decorator";
3
3
  import { DashboardQuestion } from "src/entities/dashboard-question.entity";
4
- import { IDashboardQuestionDataProvider } from "src/interfaces";
4
+ import { IDashboardQuestionDataProvider, QuestionSqlDataProviderContext } from "src/interfaces";
5
5
  import { EntityManager } from "typeorm";
6
6
  import { SqlExpressionResolverService } from "../sql-expression-resolver.service";
7
7
  import { Logger } from '@nestjs/common';
8
8
  import { SqlExpression } from "./chartjs-sql-data-provider.service";
9
9
  import { getKpi } from "./helpers";
10
10
 
11
- export interface QuestionSqlDataProviderContext {
12
- // questionSqlDatasetConfig: QuestionSqlDatasetConfig;
13
- // questionId: number;
14
- // question: Question;
15
- }
16
-
17
11
  @DashboardQuestionDataProvider()
18
12
  @Injectable()
19
13
  export class PrimeReactMeterGroupSqlDataProvider implements IDashboardQuestionDataProvider<QuestionSqlDataProviderContext, any> {
@@ -58,7 +52,9 @@ export class PrimeReactMeterGroupSqlDataProvider implements IDashboardQuestionDa
58
52
  return colors;
59
53
  }
60
54
 
61
- async getData(question: DashboardQuestion, expressions?: SqlExpression[], context?: QuestionSqlDataProviderContext): Promise<any> {
55
+ async getData(question: DashboardQuestion, context?: QuestionSqlDataProviderContext): Promise<any> {
56
+ const expressions: SqlExpression[] = context?.expressions || [];
57
+
62
58
  // TODO: put some validation to check if the results of each SQL in each dataset returns the same number of rows
63
59
 
64
60
  // This is what we have to return.
@@ -7,7 +7,7 @@ import { PollerService } from '../poller.service';
7
7
  import { buildNamespacedQueueName } from './common';
8
8
 
9
9
  export abstract class DatabaseSubscriber<T> implements OnModuleInit, QueueSubscriber<T> {
10
- private readonly logger = new Logger(DatabaseSubscriber.name);
10
+ private _loggerInstance?: Logger;
11
11
  private readonly url: string;
12
12
  private readonly serviceRole: string;
13
13
 
@@ -23,6 +23,17 @@ export abstract class DatabaseSubscriber<T> implements OnModuleInit, QueueSubscr
23
23
  // this.logger.debug(`DatabaseSubscriber instance created with options: ${JSON.stringify(this.options())}`);
24
24
  }
25
25
 
26
+ protected get loggerContext(): string {
27
+ return this.constructor.name;
28
+ }
29
+
30
+ protected get logger(): Logger {
31
+ if (!this._loggerInstance) {
32
+ this._loggerInstance = new Logger(this.loggerContext);
33
+ }
34
+ return this._loggerInstance;
35
+ }
36
+
26
37
  abstract subscribe(message: QueueMessage<T>);
27
38
 
28
39
  abstract options(): QueuesModuleOptions;
@@ -78,9 +89,15 @@ export abstract class DatabaseSubscriber<T> implements OnModuleInit, QueueSubscr
78
89
  const defaultBroker = process.env.QUEUES_DEFAULT_BROKER || 'database';
79
90
  const solidCliRunning = process.env.SOLID_CLI_RUNNING || "false";
80
91
  const queueNameRegex = (process.env.QUEUES_QUEUE_NAME_REGEX_TO_ENABLE || '').trim();
92
+ const roleAllowed = ['both', 'subscriber'].includes(this.serviceRole);
93
+
94
+ if (!roleAllowed) {
95
+ this.logger.log(`DatabaseSubscriber is disabled because QUEUES_SERVICE_ROLE is "${this.serviceRole}". Expected "both" or "subscriber".`);
96
+ return;
97
+ }
81
98
 
82
99
  // we will start subscriber only if the current service role is subscriber.
83
- if (['both', 'subscriber'].includes(this.serviceRole) && defaultBroker === 'database' && solidCliRunning === "false") {
100
+ if (defaultBroker === 'database' && solidCliRunning === "false") {
84
101
  const options = this.options();
85
102
  const queueName = options.queueName;
86
103
 
@@ -23,14 +23,16 @@ export class PublisherFactory<T> {
23
23
  // Register all ISolidDatabaseModules implementations
24
24
  let actualPublisherToUse = this.solidIntrospectionService.getProvider(resolvedPublisherName);
25
25
  if (!actualPublisherToUse) {
26
+ // Relaxed extra check in place to make sure we do not have to refactor old publishers or publishers named without the ____RabbitMq or ____Database convention
27
+ actualPublisherToUse = this.solidIntrospectionService.getProvider(publisherName);
26
28
 
27
29
  // Extra check in place to make sure we do not have to refactor old publishers which have been created earlier.
28
- if (defaultBrokerToUse === 'rabbitmq') {
29
- actualPublisherToUse = this.solidIntrospectionService.getProvider(publisherName);
30
- if (!actualPublisherToUse) {
31
- throw new Error(`Unable to locate publisher with name ${resolvedPublisherName}`);
32
- }
33
- }
30
+ // if (defaultBrokerToUse === 'rabbitmq') {
31
+ // actualPublisherToUse = this.solidIntrospectionService.getProvider(publisherName);
32
+ // }
33
+ }
34
+ if (!actualPublisherToUse) {
35
+ throw new Error(`Unable to locate publisher with name ${resolvedPublisherName}`);
34
36
  }
35
37
 
36
38
  // type safe
@@ -6,9 +6,20 @@ import { MqMessageQueueService } from '../mq-message-queue.service';
6
6
  import { MqMessageService } from '../mq-message.service';
7
7
  import { buildNamespacedQueueName } from './common';
8
8
 
9
+ class ConsumerProcessingTimeoutError extends Error {
10
+ constructor(
11
+ readonly queueName: string,
12
+ readonly messageId: string,
13
+ readonly timeoutMs: number,
14
+ ) {
15
+ super(`Subscriber processing timed out after ${timeoutMs}ms for queue ${queueName} and messageId ${messageId}`);
16
+ this.name = 'ConsumerProcessingTimeoutError';
17
+ }
18
+ }
19
+
9
20
 
10
21
  export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscriber<T> { // TODO This can be made a generic type for better type visibility
11
- private readonly logger = new Logger(RabbitMqSubscriber.name);
22
+ private _loggerInstance?: Logger;
12
23
  private readonly url: string;
13
24
  private readonly serviceRole: string;
14
25
  private connection: amqp.Connection | null = null;
@@ -30,6 +41,17 @@ export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscr
30
41
  // this.logger.debug(`RabbitMqSubscriber instance created with options: ${JSON.stringify(this.options())} and url: ${this.url}`);
31
42
  }
32
43
 
44
+ protected get loggerContext(): string {
45
+ return this.constructor.name;
46
+ }
47
+
48
+ protected get logger(): Logger {
49
+ if (!this._loggerInstance) {
50
+ this._loggerInstance = new Logger(this.loggerContext);
51
+ }
52
+ return this._loggerInstance;
53
+ }
54
+
33
55
  abstract subscribe(message: QueueMessage<T>);
34
56
 
35
57
  abstract options(): QueuesModuleOptions;
@@ -62,9 +84,15 @@ export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscr
62
84
  const defaultBroker = process.env.QUEUES_DEFAULT_BROKER || 'rabbitmq';
63
85
  const solidCliRunning = process.env.SOLID_CLI_RUNNING || "false";
64
86
  const queueNameRegex = (process.env.QUEUES_QUEUE_NAME_REGEX_TO_ENABLE || '').trim();
87
+ const roleAllowed = ['both', 'subscriber'].includes(this.serviceRole);
88
+
89
+ if (!roleAllowed) {
90
+ this.logger.log(`RabbitMqSubscriber is disabled because QUEUES_SERVICE_ROLE is "${this.serviceRole}". Expected "both" or "subscriber".`);
91
+ return;
92
+ }
65
93
 
66
94
  // we will start subscriber only if the current service role is subscriber.
67
- if (this.url && ['both', 'subscriber'].includes(this.serviceRole) && solidCliRunning === "false" && defaultBroker === 'rabbitmq') {
95
+ if (this.url && solidCliRunning === "false" && defaultBroker === 'rabbitmq') {
68
96
  const options = this.options();
69
97
  const queueName = options.queueName;
70
98
 
@@ -102,6 +130,10 @@ export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscr
102
130
  if (prefetch < 1) {
103
131
  throw new Error(`RabbitMqSubscriber prefetch must be >= 1 for queue ${queueName}`);
104
132
  }
133
+ const processingTimeoutMs = this.resolveProcessingTimeoutMs();
134
+ if (processingTimeoutMs > 0) {
135
+ this.logger.log(`RabbitMqSubscriber using processing timeout ${processingTimeoutMs}ms for queue ${queueName}`);
136
+ }
105
137
 
106
138
  let connection: amqp.Connection;
107
139
  try {
@@ -186,7 +218,7 @@ export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscr
186
218
  if (!message.currentRetry) message.currentRetry = 0;
187
219
 
188
220
  try {
189
- await this.processMessage(message, rawMessage, channel);
221
+ await this.processMessage(message, rawMessage, channel, queueName);
190
222
  } catch (error) {
191
223
  await this.handleProcessingError(message, rawMessage, channel, error, queueName);
192
224
  }
@@ -322,11 +354,11 @@ export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscr
322
354
  /**
323
355
  * Abstract method for message processing logic.
324
356
  */
325
- protected async processMessage(message: QueueMessage<T>, rawMessage, channel): Promise<void> {
357
+ protected async processMessage(message: QueueMessage<T>, rawMessage, channel, queueName: string): Promise<void> {
326
358
  await this.updateStatusInDatabase('started', message);
327
359
 
328
360
  // Capture the results of handling the task.
329
- const result = await this.subscribe(message);
361
+ const result = await this.subscribeWithTimeout(message, queueName);
330
362
 
331
363
  // Ack the message.
332
364
  channel.ack(rawMessage);
@@ -370,4 +402,82 @@ export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscr
370
402
 
371
403
  }
372
404
 
405
+ private resolveProcessingTimeoutMs(): number {
406
+ // Broker-side delivery-ack timeout (ms). If not provided, assume RabbitMQ default
407
+ // behavior used in this project: 30 minutes.
408
+ // Example (RabbitMQ broker):
409
+ // - Broker ack timeout: 30m => 1,800,000ms (QUEUES_RABBITMQ_CONSUMER_ACK_TIMEOUT_MS)
410
+ // - App soft timeout should be slightly lower, e.g. 29m30s => 1,770,000ms
411
+ // (QUEUES_RABBITMQ_SUBSCRIBER_PROCESSING_TIMEOUT_MS), so application code fails first,
412
+ // records DB state/error, and avoids broker-forced channel close as primary failure signal.
413
+ const brokerTimeoutMs = this.parsePositiveInt(process.env.QUEUES_RABBITMQ_CONSUMER_ACK_TIMEOUT_MS, 30 * 60 * 1000);
414
+
415
+ // Soft timeout should fire *before* broker timeout so we can fail explicitly,
416
+ // persist status/error, and avoid broker-forced channel closure as primary signal.
417
+ // Keep at least 1s to avoid zero/negative values when broker timeout is very small.
418
+ const defaultSoftTimeoutMs = Math.max(1_000, brokerTimeoutMs - 30_000);
419
+
420
+ // Final timeout precedence:
421
+ // 1) QUEUES_RABBITMQ_SUBSCRIBER_PROCESSING_TIMEOUT_MS (if valid positive int)
422
+ // 2) Derived defaultSoftTimeoutMs (broker timeout - 30s)
423
+ return this.parsePositiveInt(process.env.QUEUES_RABBITMQ_SUBSCRIBER_PROCESSING_TIMEOUT_MS, defaultSoftTimeoutMs);
424
+ }
425
+
426
+ private parsePositiveInt(value: string | undefined, fallback: number): number {
427
+ // Shared env parsing helper:
428
+ // - missing/invalid/non-positive => fallback
429
+ // - valid positive integer => parsed value
430
+ if (!value) return fallback;
431
+ const parsed = Number.parseInt(value, 10);
432
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
433
+ }
434
+
435
+ private async subscribeWithTimeout(message: QueueMessage<T>, queueName: string): Promise<any> {
436
+ const timeoutMs = this.resolveProcessingTimeoutMs();
437
+ const messageId = message?.messageId || 'unknown';
438
+
439
+ // Allow an escape hatch: non-positive timeout means run without a soft timeout.
440
+ if (timeoutMs <= 0) {
441
+ return this.subscribe(message);
442
+ }
443
+
444
+ let timedOut = false;
445
+ let timeoutHandle: NodeJS.Timeout | null = null;
446
+
447
+ // Main subscriber work promise.
448
+ // If timeout has already fired, suppress rethrow to avoid unhandled rejection noise
449
+ // (the timeout error is already the authoritative failure we track).
450
+ const subscribePromise = this.subscribe(message).catch((error) => {
451
+ if (timedOut) {
452
+ this.logger.error(
453
+ `Subscriber promise rejected after timeout for queue ${queueName} and messageId ${messageId}: ${(error as Error)?.message || String(error)}`,
454
+ (error as Error)?.stack,
455
+ );
456
+ return undefined;
457
+ }
458
+ throw error;
459
+ });
460
+
461
+ // Timeout promise rejects after timeoutMs with an explicit domain-specific error.
462
+ const timeoutPromise = new Promise<never>((_, reject) => {
463
+ timeoutHandle = setTimeout(() => {
464
+ timedOut = true;
465
+ reject(new ConsumerProcessingTimeoutError(queueName, messageId, timeoutMs));
466
+ }, timeoutMs);
467
+ });
468
+
469
+ try {
470
+ // Promise.race settles as soon as the *first* promise settles.
471
+ // - If subscribePromise resolves/rejects first, we use that outcome.
472
+ // - If timeoutPromise rejects first, we fail fast with timeout error.
473
+ // This ensures we mark DB status via normal error handling before broker ack-timeout.
474
+ return await Promise.race([subscribePromise, timeoutPromise]);
475
+ } finally {
476
+ // Always clear timer once race settles to avoid timer leaks.
477
+ if (timeoutHandle) {
478
+ clearTimeout(timeoutHandle);
479
+ }
480
+ }
481
+ }
482
+
373
483
  }