@solidstarters/solid-core 1.2.200 → 1.2.201

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 (371) hide show
  1. package/dist/commands/fixtures/fixtures-setup.command.d.ts +15 -0
  2. package/dist/commands/fixtures/fixtures-setup.command.d.ts.map +1 -0
  3. package/dist/commands/fixtures/fixtures-setup.command.js +58 -0
  4. package/dist/commands/fixtures/fixtures-setup.command.js.map +1 -0
  5. package/dist/commands/fixtures/fixtures-tear-down.command.d.ts +16 -0
  6. package/dist/commands/fixtures/fixtures-tear-down.command.d.ts.map +1 -0
  7. package/dist/commands/fixtures/fixtures-tear-down.command.js +59 -0
  8. package/dist/commands/fixtures/fixtures-tear-down.command.js.map +1 -0
  9. package/dist/commands/refresh-model.command.d.ts.map +1 -1
  10. package/dist/commands/refresh-model.command.js +4 -0
  11. package/dist/commands/refresh-model.command.js.map +1 -1
  12. package/dist/constants/error-messages.d.ts +2 -0
  13. package/dist/constants/error-messages.d.ts.map +1 -1
  14. package/dist/constants/error-messages.js +4 -0
  15. package/dist/constants/error-messages.js.map +1 -1
  16. package/dist/controllers/model-sequence.controller.d.ts +43 -0
  17. package/dist/controllers/model-sequence.controller.d.ts.map +1 -0
  18. package/dist/controllers/model-sequence.controller.js +179 -0
  19. package/dist/controllers/model-sequence.controller.js.map +1 -0
  20. package/dist/controllers/setting.controller.d.ts +1 -0
  21. package/dist/controllers/setting.controller.d.ts.map +1 -1
  22. package/dist/controllers/setting.controller.js +15 -0
  23. package/dist/controllers/setting.controller.js.map +1 -1
  24. package/dist/dtos/basic-filters.dto.d.ts +3 -1
  25. package/dist/dtos/basic-filters.dto.d.ts.map +1 -1
  26. package/dist/dtos/basic-filters.dto.js +8 -2
  27. package/dist/dtos/basic-filters.dto.js.map +1 -1
  28. package/dist/dtos/basic-group-filters.dto.d.ts +6 -0
  29. package/dist/dtos/basic-group-filters.dto.d.ts.map +1 -0
  30. package/dist/dtos/basic-group-filters.dto.js +46 -0
  31. package/dist/dtos/basic-group-filters.dto.js.map +1 -0
  32. package/dist/dtos/create-field-metadata.dto.js +2 -2
  33. package/dist/dtos/create-field-metadata.dto.js.map +1 -1
  34. package/dist/dtos/create-model-sequence.dto.d.ts +14 -0
  35. package/dist/dtos/create-model-sequence.dto.d.ts.map +1 -0
  36. package/dist/dtos/create-model-sequence.dto.js +90 -0
  37. package/dist/dtos/create-model-sequence.dto.js.map +1 -0
  38. package/dist/dtos/create-role-metadata.dto.d.ts.map +1 -1
  39. package/dist/dtos/create-role-metadata.dto.js +1 -0
  40. package/dist/dtos/create-role-metadata.dto.js.map +1 -1
  41. package/dist/dtos/get-mcp-url.dto.d.ts +5 -0
  42. package/dist/dtos/get-mcp-url.dto.d.ts.map +1 -0
  43. package/dist/dtos/get-mcp-url.dto.js +31 -0
  44. package/dist/dtos/get-mcp-url.dto.js.map +1 -0
  45. package/dist/dtos/resolve-s3-url.dto.d.ts +5 -5
  46. package/dist/dtos/resolve-s3-url.dto.d.ts.map +1 -1
  47. package/dist/dtos/resolve-s3-url.dto.js +7 -7
  48. package/dist/dtos/resolve-s3-url.dto.js.map +1 -1
  49. package/dist/dtos/update-model-sequence.dto.d.ts +15 -0
  50. package/dist/dtos/update-model-sequence.dto.d.ts.map +1 -0
  51. package/dist/dtos/update-model-sequence.dto.js +94 -0
  52. package/dist/dtos/update-model-sequence.dto.js.map +1 -0
  53. package/dist/entities/common.entity.d.ts.map +1 -1
  54. package/dist/entities/common.entity.js +1 -0
  55. package/dist/entities/common.entity.js.map +1 -1
  56. package/dist/entities/legacy-common.entity.d.ts.map +1 -1
  57. package/dist/entities/legacy-common.entity.js +1 -0
  58. package/dist/entities/legacy-common.entity.js.map +1 -1
  59. package/dist/entities/model-sequence.entity.d.ts +15 -0
  60. package/dist/entities/model-sequence.entity.d.ts.map +1 -0
  61. package/dist/entities/model-sequence.entity.js +67 -0
  62. package/dist/entities/model-sequence.entity.js.map +1 -0
  63. package/dist/helpers/field-crud-managers/BigIntFieldCrudManager.d.ts.map +1 -1
  64. package/dist/helpers/field-crud-managers/BigIntFieldCrudManager.js +13 -2
  65. package/dist/helpers/field-crud-managers/BigIntFieldCrudManager.js.map +1 -1
  66. package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.d.ts +0 -1
  67. package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.d.ts.map +1 -1
  68. package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.js +4 -9
  69. package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.js.map +1 -1
  70. package/dist/helpers/field-crud-managers/ManyToOneRelationFieldCrudManager.d.ts +0 -1
  71. package/dist/helpers/field-crud-managers/ManyToOneRelationFieldCrudManager.d.ts.map +1 -1
  72. package/dist/helpers/field-crud-managers/ManyToOneRelationFieldCrudManager.js +7 -8
  73. package/dist/helpers/field-crud-managers/ManyToOneRelationFieldCrudManager.js.map +1 -1
  74. package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.d.ts +0 -1
  75. package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.d.ts.map +1 -1
  76. package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.js +4 -9
  77. package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.js.map +1 -1
  78. package/dist/helpers/model-metadata-helper.service.d.ts.map +1 -1
  79. package/dist/helpers/model-metadata-helper.service.js +6 -2
  80. package/dist/helpers/model-metadata-helper.service.js.map +1 -1
  81. package/dist/helpers/module-metadata-helper.service.d.ts +1 -0
  82. package/dist/helpers/module-metadata-helper.service.d.ts.map +1 -1
  83. package/dist/helpers/module-metadata-helper.service.js +9 -0
  84. package/dist/helpers/module-metadata-helper.service.js.map +1 -1
  85. package/dist/helpers/module.helper.d.ts +1 -0
  86. package/dist/helpers/module.helper.d.ts.map +1 -1
  87. package/dist/helpers/module.helper.js +29 -3
  88. package/dist/helpers/module.helper.js.map +1 -1
  89. package/dist/helpers/solid-registry.d.ts +11 -0
  90. package/dist/helpers/solid-registry.d.ts.map +1 -1
  91. package/dist/helpers/solid-registry.js.map +1 -1
  92. package/dist/index.d.ts +3 -1
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +4 -2
  95. package/dist/index.js.map +1 -1
  96. package/dist/jobs/computed-field-evaluation-subscriber.service.d.ts +1 -0
  97. package/dist/jobs/computed-field-evaluation-subscriber.service.d.ts.map +1 -1
  98. package/dist/jobs/computed-field-evaluation-subscriber.service.js +16 -4
  99. package/dist/jobs/computed-field-evaluation-subscriber.service.js.map +1 -1
  100. package/dist/repository/media.repository.d.ts.map +1 -1
  101. package/dist/repository/media.repository.js +4 -0
  102. package/dist/repository/media.repository.js.map +1 -1
  103. package/dist/repository/model-sequence.repository.d.ts +14 -0
  104. package/dist/repository/model-sequence.repository.d.ts.map +1 -0
  105. package/dist/repository/model-sequence.repository.js +103 -0
  106. package/dist/repository/model-sequence.repository.js.map +1 -0
  107. package/dist/seeders/module-metadata-seeder.service.d.ts +7 -12
  108. package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
  109. package/dist/seeders/module-metadata-seeder.service.js +64 -26
  110. package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
  111. package/dist/seeders/seed-data/solid-core-metadata.json +343 -27
  112. package/dist/seeders/system-fields-seeder.service.d.ts +1 -0
  113. package/dist/seeders/system-fields-seeder.service.d.ts.map +1 -1
  114. package/dist/seeders/system-fields-seeder.service.js +11 -2
  115. package/dist/seeders/system-fields-seeder.service.js.map +1 -1
  116. package/dist/services/action-metadata.service.d.ts.map +1 -1
  117. package/dist/services/action-metadata.service.js +1 -0
  118. package/dist/services/action-metadata.service.js.map +1 -1
  119. package/dist/services/ai-interaction.service.d.ts.map +1 -1
  120. package/dist/services/ai-interaction.service.js +1 -0
  121. package/dist/services/ai-interaction.service.js.map +1 -1
  122. package/dist/services/authentication.service.d.ts.map +1 -1
  123. package/dist/services/authentication.service.js +22 -14
  124. package/dist/services/authentication.service.js.map +1 -1
  125. package/dist/services/chatter-message-details.service.d.ts.map +1 -1
  126. package/dist/services/chatter-message-details.service.js +1 -0
  127. package/dist/services/chatter-message-details.service.js.map +1 -1
  128. package/dist/services/chatter-message.service.d.ts.map +1 -1
  129. package/dist/services/chatter-message.service.js +7 -3
  130. package/dist/services/chatter-message.service.js.map +1 -1
  131. package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.d.ts.map +1 -1
  132. package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js +7 -5
  133. package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js.map +1 -1
  134. package/dist/services/computed-fields/entity/sequence-num-computed-field-provider.d.ts +15 -0
  135. package/dist/services/computed-fields/entity/sequence-num-computed-field-provider.d.ts.map +1 -0
  136. package/dist/services/computed-fields/entity/sequence-num-computed-field-provider.js +72 -0
  137. package/dist/services/computed-fields/entity/sequence-num-computed-field-provider.js.map +1 -0
  138. package/dist/services/crud-helper.service.d.ts +23 -6
  139. package/dist/services/crud-helper.service.d.ts.map +1 -1
  140. package/dist/services/crud-helper.service.js +257 -45
  141. package/dist/services/crud-helper.service.js.map +1 -1
  142. package/dist/services/crud.service.d.ts +3 -1
  143. package/dist/services/crud.service.d.ts.map +1 -1
  144. package/dist/services/crud.service.js +53 -24
  145. package/dist/services/crud.service.js.map +1 -1
  146. package/dist/services/database/database-bootstrap.service.d.ts +12 -0
  147. package/dist/services/database/database-bootstrap.service.d.ts.map +1 -0
  148. package/dist/services/database/database-bootstrap.service.js +115 -0
  149. package/dist/services/database/database-bootstrap.service.js.map +1 -0
  150. package/dist/services/email-template.service.d.ts +7 -7
  151. package/dist/services/email-template.service.d.ts.map +1 -1
  152. package/dist/services/email-template.service.js +8 -7
  153. package/dist/services/email-template.service.js.map +1 -1
  154. package/dist/services/excel.service.d.ts +10 -0
  155. package/dist/services/excel.service.d.ts.map +1 -1
  156. package/dist/services/excel.service.js +100 -0
  157. package/dist/services/excel.service.js.map +1 -1
  158. package/dist/services/field-metadata.service.d.ts +4 -1
  159. package/dist/services/field-metadata.service.d.ts.map +1 -1
  160. package/dist/services/field-metadata.service.js +35 -30
  161. package/dist/services/field-metadata.service.js.map +1 -1
  162. package/dist/services/file.service.d.ts +1 -0
  163. package/dist/services/file.service.d.ts.map +1 -1
  164. package/dist/services/file.service.js +9 -0
  165. package/dist/services/file.service.js.map +1 -1
  166. package/dist/services/fixtures.service.d.ts +13 -0
  167. package/dist/services/fixtures.service.d.ts.map +1 -0
  168. package/dist/services/fixtures.service.js +95 -0
  169. package/dist/services/fixtures.service.js.map +1 -0
  170. package/dist/services/genai/ingest-metadata.service.d.ts.map +1 -1
  171. package/dist/services/genai/ingest-metadata.service.js +1 -1
  172. package/dist/services/genai/ingest-metadata.service.js.map +1 -1
  173. package/dist/services/import-transaction-error-log.service.d.ts.map +1 -1
  174. package/dist/services/import-transaction-error-log.service.js +1 -0
  175. package/dist/services/import-transaction-error-log.service.js.map +1 -1
  176. package/dist/services/import-transaction.service.d.ts.map +1 -1
  177. package/dist/services/import-transaction.service.js +7 -1
  178. package/dist/services/import-transaction.service.js.map +1 -1
  179. package/dist/services/list-of-values.service.d.ts +2 -2
  180. package/dist/services/list-of-values.service.d.ts.map +1 -1
  181. package/dist/services/list-of-values.service.js +2 -1
  182. package/dist/services/list-of-values.service.js.map +1 -1
  183. package/dist/services/locale.service.d.ts.map +1 -1
  184. package/dist/services/locale.service.js +1 -0
  185. package/dist/services/locale.service.js.map +1 -1
  186. package/dist/services/mail/smtp-email.service.js +0 -1
  187. package/dist/services/mail/smtp-email.service.js.map +1 -1
  188. package/dist/services/media.service.d.ts +3 -3
  189. package/dist/services/media.service.d.ts.map +1 -1
  190. package/dist/services/media.service.js +6 -4
  191. package/dist/services/media.service.js.map +1 -1
  192. package/dist/services/mediaStorageProviders/file-s3-storage-provider.d.ts.map +1 -1
  193. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js +17 -6
  194. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js.map +1 -1
  195. package/dist/services/mediaStorageProviders/file-storage-provider.d.ts.map +1 -1
  196. package/dist/services/mediaStorageProviders/file-storage-provider.js +0 -13
  197. package/dist/services/mediaStorageProviders/file-storage-provider.js.map +1 -1
  198. package/dist/services/menu-item-metadata.service.d.ts.map +1 -1
  199. package/dist/services/menu-item-metadata.service.js +4 -0
  200. package/dist/services/menu-item-metadata.service.js.map +1 -1
  201. package/dist/services/model-metadata.service.d.ts.map +1 -1
  202. package/dist/services/model-metadata.service.js +2 -42
  203. package/dist/services/model-metadata.service.js.map +1 -1
  204. package/dist/services/model-sequence.service.d.ts +23 -0
  205. package/dist/services/model-sequence.service.d.ts.map +1 -0
  206. package/dist/services/model-sequence.service.js +55 -0
  207. package/dist/services/model-sequence.service.js.map +1 -0
  208. package/dist/services/module-metadata.service.d.ts +1 -0
  209. package/dist/services/module-metadata.service.d.ts.map +1 -1
  210. package/dist/services/module-metadata.service.js +35 -1
  211. package/dist/services/module-metadata.service.js.map +1 -1
  212. package/dist/services/permission-metadata.service.d.ts +5 -5
  213. package/dist/services/permission-metadata.service.d.ts.map +1 -1
  214. package/dist/services/permission-metadata.service.js +6 -5
  215. package/dist/services/permission-metadata.service.js.map +1 -1
  216. package/dist/services/queues/database-subscriber.service.d.ts.map +1 -1
  217. package/dist/services/queues/database-subscriber.service.js +2 -1
  218. package/dist/services/queues/database-subscriber.service.js.map +1 -1
  219. package/dist/services/queues/rabbitmq-subscriber.service.d.ts.map +1 -1
  220. package/dist/services/queues/rabbitmq-subscriber.service.js +2 -2
  221. package/dist/services/queues/rabbitmq-subscriber.service.js.map +1 -1
  222. package/dist/services/role-metadata.service.d.ts.map +1 -1
  223. package/dist/services/role-metadata.service.js +1 -0
  224. package/dist/services/role-metadata.service.js.map +1 -1
  225. package/dist/services/scheduled-job.service.d.ts +6 -6
  226. package/dist/services/scheduled-job.service.d.ts.map +1 -1
  227. package/dist/services/scheduled-job.service.js +8 -8
  228. package/dist/services/scheduled-job.service.js.map +1 -1
  229. package/dist/services/scheduled-jobs/scheduler.service.d.ts.map +1 -1
  230. package/dist/services/scheduled-jobs/scheduler.service.js +4 -0
  231. package/dist/services/scheduled-jobs/scheduler.service.js.map +1 -1
  232. package/dist/services/security-rule.service.d.ts.map +1 -1
  233. package/dist/services/security-rule.service.js +1 -0
  234. package/dist/services/security-rule.service.js.map +1 -1
  235. package/dist/services/selection-providers/list-of-models-selection-provider.service.d.ts.map +1 -1
  236. package/dist/services/selection-providers/list-of-models-selection-provider.service.js +4 -0
  237. package/dist/services/selection-providers/list-of-models-selection-provider.service.js.map +1 -1
  238. package/dist/services/setting.service.d.ts +7 -5
  239. package/dist/services/setting.service.d.ts.map +1 -1
  240. package/dist/services/setting.service.js +26 -4
  241. package/dist/services/setting.service.js.map +1 -1
  242. package/dist/services/sms-template.service.d.ts +7 -7
  243. package/dist/services/sms-template.service.d.ts.map +1 -1
  244. package/dist/services/sms-template.service.js +8 -7
  245. package/dist/services/sms-template.service.js.map +1 -1
  246. package/dist/services/solid-introspect.service.d.ts +4 -13
  247. package/dist/services/solid-introspect.service.d.ts.map +1 -1
  248. package/dist/services/solid-introspect.service.js +4 -22
  249. package/dist/services/solid-introspect.service.js.map +1 -1
  250. package/dist/services/solid-ts-morph.service.js +2 -2
  251. package/dist/services/solid-ts-morph.service.js.map +1 -1
  252. package/dist/services/user-activity-history.service.d.ts.map +1 -1
  253. package/dist/services/user-activity-history.service.js +1 -0
  254. package/dist/services/user-activity-history.service.js.map +1 -1
  255. package/dist/services/user-view-metadata.service.d.ts.map +1 -1
  256. package/dist/services/user-view-metadata.service.js +3 -2
  257. package/dist/services/user-view-metadata.service.js.map +1 -1
  258. package/dist/services/user.service.d.ts.map +1 -1
  259. package/dist/services/user.service.js +1 -0
  260. package/dist/services/user.service.js.map +1 -1
  261. package/dist/services/view-metadata.service.d.ts +1 -1
  262. package/dist/services/view-metadata.service.d.ts.map +1 -1
  263. package/dist/services/view-metadata.service.js +3 -1
  264. package/dist/services/view-metadata.service.js.map +1 -1
  265. package/dist/solid-core-cli-db.module.d.ts.map +1 -1
  266. package/dist/solid-core-cli-db.module.js +5 -2
  267. package/dist/solid-core-cli-db.module.js.map +1 -1
  268. package/dist/solid-core.module.d.ts.map +1 -1
  269. package/dist/solid-core.module.js +18 -0
  270. package/dist/solid-core.module.js.map +1 -1
  271. package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
  272. package/dist/subscribers/audit.subscriber.js +5 -1
  273. package/dist/subscribers/audit.subscriber.js.map +1 -1
  274. package/dist/subscribers/computed-entity-field.subscriber.d.ts +4 -2
  275. package/dist/subscribers/computed-entity-field.subscriber.d.ts.map +1 -1
  276. package/dist/subscribers/computed-entity-field.subscriber.js +53 -12
  277. package/dist/subscribers/computed-entity-field.subscriber.js.map +1 -1
  278. package/dist/transformers/typeorm/local-date-time-transformer.d.ts +5 -0
  279. package/dist/transformers/typeorm/local-date-time-transformer.d.ts.map +1 -0
  280. package/dist/transformers/typeorm/local-date-time-transformer.js +26 -0
  281. package/dist/transformers/typeorm/local-date-time-transformer.js.map +1 -0
  282. package/dist/tsconfig.tsbuildinfo +1 -1
  283. package/docs/grouping-enhancements.md +89 -0
  284. package/package.json +1 -1
  285. package/src/commands/fixtures/fixtures-setup.command.ts +44 -0
  286. package/src/commands/fixtures/fixtures-tear-down.command.ts +45 -0
  287. package/src/commands/refresh-model.command.ts +3 -1
  288. package/src/constants/error-messages.ts +7 -1
  289. package/src/controllers/model-sequence.controller.ts +93 -0
  290. package/src/controllers/setting.controller.ts +33 -21
  291. package/src/dtos/basic-filters.dto.ts +6 -1
  292. package/src/dtos/basic-group-filters.dto.ts +23 -0
  293. package/src/dtos/create-field-metadata.dto.ts +1 -1
  294. package/src/dtos/create-model-sequence.dto.ts +51 -0
  295. package/src/dtos/create-role-metadata.dto.ts +16 -3
  296. package/src/dtos/get-mcp-url.dto.ts +13 -0
  297. package/src/dtos/resolve-s3-url.dto.ts +9 -11
  298. package/src/dtos/update-model-sequence.dto.ts +53 -0
  299. package/src/entities/common.entity.ts +2 -2
  300. package/src/entities/legacy-common.entity.ts +2 -1
  301. package/src/entities/model-sequence.entity.ts +32 -0
  302. package/src/helpers/field-crud-managers/BigIntFieldCrudManager.ts +18 -5
  303. package/src/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.ts +9 -9
  304. package/src/helpers/field-crud-managers/ManyToOneRelationFieldCrudManager.ts +16 -8
  305. package/src/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.ts +9 -9
  306. package/src/helpers/model-metadata-helper.service.ts +6 -4
  307. package/src/helpers/module-metadata-helper.service.ts +18 -1
  308. package/src/helpers/module.helper.ts +40 -5
  309. package/src/helpers/solid-registry.ts +14 -0
  310. package/src/index.ts +3 -1
  311. package/src/jobs/computed-field-evaluation-subscriber.service.ts +15 -4
  312. package/src/repository/media.repository.ts +3 -2
  313. package/src/repository/model-sequence.repository.ts +97 -0
  314. package/src/seeders/module-metadata-seeder.service.ts +103 -29
  315. package/src/seeders/seed-data/solid-core-metadata.json +343 -27
  316. package/src/seeders/system-fields-seeder.service.ts +6 -2
  317. package/src/services/action-metadata.service.ts +3 -2
  318. package/src/services/ai-interaction.service.ts +2 -1
  319. package/src/services/authentication.service.ts +46 -14
  320. package/src/services/chatter-message-details.service.ts +2 -1
  321. package/src/services/chatter-message.service.ts +10 -4
  322. package/src/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.ts +8 -7
  323. package/src/services/computed-fields/entity/sequence-num-computed-field-provider.ts +86 -0
  324. package/src/services/crud-helper.service.ts +287 -49
  325. package/src/services/crud.service.ts +83 -32
  326. package/src/services/database/database-bootstrap.service.ts +91 -0
  327. package/src/services/email-template.service.ts +11 -13
  328. package/src/services/excel.service.ts +146 -3
  329. package/src/services/field-metadata.service.ts +102 -55
  330. package/src/services/file.service.ts +9 -0
  331. package/src/services/fixtures.service.ts +108 -0
  332. package/src/services/genai/ingest-metadata.service.ts +4 -3
  333. package/src/services/import-transaction-error-log.service.ts +2 -1
  334. package/src/services/import-transaction.service.ts +8 -4
  335. package/src/services/list-of-values.service.ts +4 -4
  336. package/src/services/locale.service.ts +2 -1
  337. package/src/services/mail/smtp-email.service.ts +1 -1
  338. package/src/services/media.service.ts +10 -11
  339. package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +22 -7
  340. package/src/services/mediaStorageProviders/file-storage-provider.ts +18 -13
  341. package/src/services/menu-item-metadata.service.ts +6 -2
  342. package/src/services/model-metadata.service.ts +50 -44
  343. package/src/services/model-sequence.service.ts +33 -0
  344. package/src/services/module-metadata.service.ts +49 -2
  345. package/src/services/permission-metadata.service.ts +8 -9
  346. package/src/services/queues/database-subscriber.service.ts +3 -1
  347. package/src/services/queues/rabbitmq-subscriber.service.ts +4 -2
  348. package/src/services/role-metadata.service.ts +1 -0
  349. package/src/services/scheduled-job.service.ts +9 -9
  350. package/src/services/scheduled-jobs/scheduler.service.ts +5 -0
  351. package/src/services/security-rule.service.ts +1 -0
  352. package/src/services/selection-providers/list-of-models-selection-provider.service.ts +5 -2
  353. package/src/services/setting.service.ts +33 -6
  354. package/src/services/sms-template.service.ts +11 -13
  355. package/src/services/solid-introspect.service.ts +6 -19
  356. package/src/services/solid-ts-morph.service.ts +2 -2
  357. package/src/services/user-activity-history.service.ts +3 -2
  358. package/src/services/user-view-metadata.service.ts +4 -3
  359. package/src/services/user.service.ts +2 -1
  360. package/src/services/view-metadata.service.ts +5 -4
  361. package/src/solid-core-cli-db.module.ts +5 -4
  362. package/src/solid-core.module.ts +18 -0
  363. package/src/subscribers/audit.subscriber.ts +3 -2
  364. package/src/subscribers/computed-entity-field.subscriber.ts +60 -17
  365. package/src/transformers/typeorm/local-date-time-transformer.ts +30 -0
  366. /package/sql/{mssql → default/mssql}/proc_CleanupModelMetadata.sql +0 -0
  367. /package/sql/{mssql → default/mssql}/proc_CleanupModuleMetadata.sql +0 -0
  368. /package/sql/{mssql/scratchpad.sql → default/mssql/scratchpad.sql.txt} +0 -0
  369. /package/sql/{postgres → default/postgres}/proc_CleanupModelMetadata.sql +0 -0
  370. /package/sql/{postgres → default/postgres}/proc_CleanupModuleMetadata.sql +0 -0
  371. /package/sql/{postgres/scratchpad.sql → default/postgres/scratchpad.sql.txt} +0 -0
@@ -1,4 +1,4 @@
1
- import { Injectable } from '@nestjs/common';
1
+ import { forwardRef, Inject, Injectable } from '@nestjs/common';
2
2
  import { DiscoveryService, ModuleRef } from "@nestjs/core";
3
3
  import { InjectEntityManager } from '@nestjs/typeorm';
4
4
  import { Brackets, EntityManager, EntityMetadata } from 'typeorm';
@@ -26,6 +26,7 @@ import { RequestContextService } from './request-context.service';
26
26
  @Injectable()
27
27
  export class ChatterMessageService extends CRUDService<ChatterMessage> {
28
28
  constructor(
29
+ @Inject(forwardRef(() => ModelMetadataService))
29
30
  readonly modelMetadataService: ModelMetadataService,
30
31
  readonly moduleMetadataService: ModuleMetadataService,
31
32
  readonly configService: ConfigService,
@@ -44,6 +45,7 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
44
45
  readonly moduleRef: ModuleRef,
45
46
  // @InjectRepository(ModelMetadata)
46
47
  // private readonly modelMetadataRepo: Repository<ModelMetadata>,
48
+ @Inject(forwardRef(() => ModelMetadataRepository))
47
49
  private readonly modelMetadataRepo: ModelMetadataRepository,
48
50
  readonly requestContextService: RequestContextService,
49
51
  private readonly modelMetadataHelperService: ModelMetadataHelperService,
@@ -116,7 +118,7 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
116
118
 
117
119
  const auditFields = model.fields.filter(field =>
118
120
  field.enableAuditTracking &&
119
- !['mediaSingle', 'mediaMultiple', 'computed', 'richText', 'json'].includes(field.type) &&
121
+ !['mediaSingle', 'mediaMultiple', 'richText', 'json'].includes(field.type) &&
120
122
  !(field.type === 'relation' && field.relationType === 'one-to-many')
121
123
  );
122
124
 
@@ -179,7 +181,7 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
179
181
 
180
182
  const auditFields = modelFields.filter(field =>
181
183
  field.enableAuditTracking &&
182
- !['mediaSingle', 'mediaMultiple', 'computed', 'richText', 'json'].includes(field.type) &&
184
+ !['mediaSingle', 'mediaMultiple', 'richText', 'json'].includes(field.type) &&
183
185
  !(field.type === 'relation' && field.relationType === 'one-to-many')
184
186
  );
185
187
 
@@ -542,7 +544,11 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
542
544
  });
543
545
 
544
546
  if (coModel) {
545
- const relatedEntityRepository = this.entityManager.getRepository(classify(coModelName));
547
+ //const relatedEntityRepository = this.entityManager.getRepository(classify(coModelName));
548
+ const dsName = coModel.dataSource || 'default';
549
+ const em = dsName === 'default' ? this.entityManager : this.moduleRef.get(`${dsName}EntityManager`, { strict: false });
550
+
551
+ const relatedEntityRepository = em.getRepository(classify(coModelName));
546
552
 
547
553
  const relatedEntities = await relatedEntityRepository.find({
548
554
  where: { [coModelFieldName]: { id: entityId } }
@@ -30,8 +30,9 @@ export class AlphaNumExternalIdComputationProvider<T extends CommonEntity> imple
30
30
 
31
31
  async preComputeValue(triggerEntity: T, computedFieldMetadata: ComputedFieldMetadata<AlphaNumExternalIdContext>
32
32
  ) {
33
- const { prefix, length, dynamicFieldPrefix } =
34
- computedFieldMetadata.computedFieldValueProviderCtxt;
33
+ const { prefix, length, dynamicFieldPrefix } = computedFieldMetadata.computedFieldValueProviderCtxt;
34
+ const eventContext = computedFieldMetadata.eventContext;
35
+ const entityName = eventContext?.metadataName ?? eventContext.databaseEntity?.constructor?.name ?? '';
35
36
 
36
37
  const codeLength = length || 5;
37
38
 
@@ -45,7 +46,7 @@ export class AlphaNumExternalIdComputationProvider<T extends CommonEntity> imple
45
46
  }
46
47
  }
47
48
 
48
- const uniqueCode = await this.generateUniqueExternalId(resolvedPrefix, codeLength, triggerEntity, computedFieldMetadata.fieldName);
49
+ const uniqueCode = await this.generateUniqueExternalId(resolvedPrefix, codeLength, computedFieldMetadata.fieldName, entityName);
49
50
  const finalExternalId = resolvedPrefix ? `${resolvedPrefix}-${uniqueCode}` : uniqueCode;
50
51
 
51
52
  triggerEntity[computedFieldMetadata.fieldName] = finalExternalId;
@@ -60,8 +61,8 @@ export class AlphaNumExternalIdComputationProvider<T extends CommonEntity> imple
60
61
  return result;
61
62
  }
62
63
 
63
- private async isExternalIdUnique(externalId: string, triggerEntity: T, fieldName: string): Promise<boolean> {
64
- const count = await this.entityManager.count(triggerEntity.constructor as any,
64
+ private async isExternalIdUnique(externalId: string, fieldName: string, entityName: string): Promise<boolean> {
65
+ const count = await this.entityManager.count(entityName as any,
65
66
  {
66
67
  where: { [fieldName]: externalId },
67
68
  }
@@ -69,7 +70,7 @@ export class AlphaNumExternalIdComputationProvider<T extends CommonEntity> imple
69
70
  return count === 0;
70
71
  }
71
72
 
72
- private async generateUniqueExternalId(resolvedPrefix: string, codeLength: number, triggerEntity: T, fieldName: string): Promise<string> {
73
+ private async generateUniqueExternalId(resolvedPrefix: string, codeLength: number, fieldName: string, entityName: string): Promise<string> {
73
74
  const maxAttempts = 10;
74
75
 
75
76
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
@@ -78,7 +79,7 @@ export class AlphaNumExternalIdComputationProvider<T extends CommonEntity> imple
78
79
 
79
80
  const fullId = resolvedPrefix ? `${resolvedPrefix}-${newId}` : newId;
80
81
 
81
- const isUnique = await this.isExternalIdUnique(fullId, triggerEntity, fieldName);
82
+ const isUnique = await this.isExternalIdUnique(fullId, fieldName, entityName);
82
83
 
83
84
  if (isUnique) {
84
85
  return newId;
@@ -0,0 +1,86 @@
1
+ import { Injectable } from "@nestjs/common";
2
+ import { InjectDataSource } from "@nestjs/typeorm";
3
+ import { ComputedFieldProvider } from "src/decorators/computed-field-provider.decorator";
4
+ import { CommonEntity } from "src/entities/common.entity";
5
+ import { ModelSequence } from "src/entities/model-sequence.entity";
6
+ import { ComputedFieldMetadata } from "src/helpers/solid-registry";
7
+ import { IEntityPreComputeFieldProvider } from "src/interfaces";
8
+ import { DataSource, EntityTarget } from "typeorm";
9
+
10
+
11
+ export interface SequenceNumComputedFieldContext {
12
+ sequenceName: string; // The separator to use between concatenated values
13
+ }
14
+
15
+ @ComputedFieldProvider()
16
+ @Injectable()
17
+ export class SequenceNumComputedFieldProvider<T extends CommonEntity> implements IEntityPreComputeFieldProvider<T, SequenceNumComputedFieldContext> {
18
+ constructor(
19
+ @InjectDataSource()
20
+ private readonly dataSource: DataSource
21
+ ) { }
22
+
23
+ name(): string {
24
+ return "SequenceNumComputedFieldProvider";
25
+ }
26
+
27
+ help(): string {
28
+ return "Computed field provider used to create fields whose value is based on some prefix, padding & sequence number.";
29
+ }
30
+
31
+ async preComputeValue(triggerEntity: T, computedFieldMetadata: ComputedFieldMetadata<SequenceNumComputedFieldContext>) {
32
+ const { sequenceName } =
33
+ computedFieldMetadata.computedFieldValueProviderCtxt ?? {};
34
+
35
+ if (!sequenceName) {
36
+ throw new Error("sequenceName is required for sequence computation");
37
+ }
38
+
39
+ await this.dataSource.transaction(async (manager) => {
40
+ /**
41
+ * 1️⃣ Lock sequence row (prevents race conditions)
42
+ */
43
+ // 1️⃣ Fetch sequence row
44
+ const modelSequenceRepo = manager.getRepository(ModelSequence)
45
+ const modelSequence = await modelSequenceRepo.findOne({
46
+ where: { sequenceName },
47
+ lock: { mode: "pessimistic_write" }
48
+ });
49
+
50
+ if (!modelSequence) {
51
+ throw new Error(`ModelSequence not found for ${sequenceName}`);
52
+ }
53
+
54
+ // 2️⃣ Generate next sequence value
55
+ const nextValue = modelSequence.currentValue + 1;
56
+
57
+ const paddedValue = String(nextValue).padStart(modelSequence.padding ?? 5, "0");
58
+
59
+ const prefix = modelSequence.prefix ?? "";
60
+ const separator = modelSequence.separator ?? "";
61
+
62
+ const sequenceString = `${prefix}${separator}${paddedValue}`;
63
+
64
+ // 3️⃣ Duplicate check on TARGET ENTITY (extra safety)
65
+ const entityRepo = manager.getRepository(triggerEntity.constructor as any);
66
+
67
+ const existing = await entityRepo.findOne({
68
+ where: {
69
+ [computedFieldMetadata.fieldName]: sequenceString,
70
+ },
71
+ });
72
+
73
+ if (existing) {
74
+ throw new Error(`Duplicate Sequence generated: ${sequenceString}`);
75
+ }
76
+
77
+ // 4️⃣ set the computed field on the entity
78
+ (triggerEntity as any)[computedFieldMetadata.fieldName] = sequenceString;
79
+
80
+ // 5️⃣ Persist updated sequence current value
81
+ modelSequence.currentValue = nextValue;
82
+ await modelSequenceRepo.save(modelSequence);
83
+ });
84
+ }
85
+
86
+ }
@@ -24,11 +24,20 @@ export class CrudHelperService {
24
24
  private orderOptions(sort: any[] = []) {
25
25
  const orderOptions = {};
26
26
  sort.forEach((s: string) => {
27
- const [field, order] = s.split(':');
28
- orderOptions[field] = order?.toUpperCase() ?? 'ASC';
29
- if (!['ASC', 'DESC'].includes(orderOptions[field])) {
27
+ const parts = s.split(':');
28
+ let order: string | undefined;
29
+ let field: string;
30
+ if (parts.length > 1) {
31
+ order = parts.pop();
32
+ field = parts.join(':');
33
+ } else {
34
+ field = parts[0];
35
+ }
36
+ const normalizedOrder = order ? order.toUpperCase() : 'ASC';
37
+ if (!['ASC', 'DESC'].includes(normalizedOrder)) {
30
38
  throw new Error(`Invalid sort order provided: ${order}`);
31
39
  }
40
+ orderOptions[field] = normalizedOrder;
32
41
  });
33
42
  return orderOptions;
34
43
  }
@@ -185,10 +194,12 @@ export class CrudHelperService {
185
194
  internationalisation?: boolean,
186
195
  draftPublishWorkflow?: boolean,
187
196
  moduleRef?: any,
188
- filterCombinator: FilterCombinator = FilterCombinator.AND
197
+ filterCombinator: FilterCombinator = FilterCombinator.AND,
198
+ applyPagination: boolean = true,
199
+ applySorting: boolean = true
189
200
  ): SelectQueryBuilder<any> { // TODO : Check how to pass a type to SelectQueryBuilder instead of any
190
201
  let { limit, offset, showSoftDeleted, filters } = basicFilterDto;
191
- const { fields, sort, groupBy, populate = [], populateMedia = [], locale, status } = basicFilterDto;
202
+ const { fields, sort, populate = [], populateMedia = [], locale, status } = basicFilterDto;
192
203
 
193
204
  // Normalize the fields, sort, groupBy and populate options i.e (since they can be either a string or an array of strings, when coming from the request)
194
205
  const normalizedFields = this.normalize(fields);
@@ -201,10 +212,6 @@ export class CrudHelperService {
201
212
  normalizedAndFilteredPopulateAttributes.push(...additionalPopulate.filter((relation) => !normalizedAndFilteredPopulateAttributes.includes(relation)));
202
213
 
203
214
  const normalizedSort = this.normalize(sort);
204
- const normalizedGroupBy = this.normalize(groupBy);
205
- if (normalizedGroupBy.length > 1) {
206
- throw new Error(ERROR_MESSAGES.GROUP_BY_LIMIT);
207
- }
208
215
 
209
216
  // Depending upon the populate option, apply the join clause
210
217
  if (normalizedAndFilteredPopulateAttributes && normalizedAndFilteredPopulateAttributes.length) {
@@ -255,7 +262,7 @@ export class CrudHelperService {
255
262
  }
256
263
 
257
264
  // Depending upon the order option, apply the order by clause
258
- if (normalizedSort && normalizedSort.length) {
265
+ if (applySorting && normalizedSort && normalizedSort.length) {
259
266
  const orderOptions = this.orderOptions(normalizedSort);
260
267
  if (orderOptions) {
261
268
  const orderOptionKeys = Object.keys(orderOptions) as Array<keyof typeof orderOptions>;
@@ -276,16 +283,11 @@ export class CrudHelperService {
276
283
  qb.where(`${entityAlias}.deletedAt IS NOT NULL`);
277
284
  }
278
285
 
279
- // Apply the group by options
280
- if (normalizedGroupBy && normalizedGroupBy.length) {
281
- normalizedGroupBy.forEach((field: string) => {
282
- qb.addGroupBy(`${entityAlias}.${field}`);
283
- });
284
- }
285
-
286
286
  // Apply the pagination options & handle the case when the query has joins
287
- if (limit) this.hasJoins(qb) ? qb.take(limit) : qb.limit(limit);
288
- if (offset) this.hasJoins(qb) ? qb.skip(offset) : qb.offset(offset);
287
+ if (applyPagination) {
288
+ if (limit) this.hasJoins(qb) ? qb.take(limit) : qb.limit(limit);
289
+ if (offset) this.hasJoins(qb) ? qb.skip(offset) : qb.offset(offset);
290
+ }
289
291
  return qb;
290
292
  }
291
293
 
@@ -307,6 +309,185 @@ export class CrudHelperService {
307
309
  return qb;
308
310
  }
309
311
 
312
+ private sanitizeAlias(alias: string) {
313
+ return alias.replace(/[^a-zA-Z0-9_]/g, '_');
314
+ }
315
+
316
+ private isAliasJoined(queryBuilder: SelectQueryBuilder<any>, alias: string): boolean {
317
+ return queryBuilder.expressionMap.joinAttributes.some(join => join.alias?.name === alias);
318
+ }
319
+
320
+ private getExistingJoinAlias(qb: SelectQueryBuilder<any>, joinProperty: string): string | undefined {
321
+ const existingJoin = qb.expressionMap.joinAttributes.find(join => join.entityOrProperty === joinProperty);
322
+ return existingJoin?.alias?.name;
323
+ }
324
+
325
+ private ensureRelationPathJoined(qb: SelectQueryBuilder<any>, rootAlias: string, pathParts: string[]) {
326
+ const mainAlias =
327
+ qb.expressionMap?.mainAlias?.name ||
328
+ qb.expressionMap?.aliases?.find(a => a.metadata)?.name ||
329
+ qb.expressionMap?.aliases?.[0]?.name;
330
+ let parentAlias = mainAlias || rootAlias;
331
+ for (let i = 0; i < pathParts.length - 1; i++) {
332
+ const part = pathParts[i];
333
+ const joinProperty = `${parentAlias}.${part}`;
334
+ const existingAlias = this.getExistingJoinAlias(qb, joinProperty);
335
+ const joinAlias = existingAlias ?? this.sanitizeAlias(`${parentAlias}_${part}`);
336
+ if (!existingAlias && !this.isRelationJoined(qb, joinProperty) && !this.isAliasJoined(qb, joinAlias)) {
337
+ qb.leftJoin(joinProperty, joinAlias);
338
+ }
339
+ parentAlias = joinAlias;
340
+ }
341
+ return { alias: parentAlias, property: pathParts[pathParts.length - 1] };
342
+ }
343
+
344
+ private getDriver(qb: SelectQueryBuilder<any>) {
345
+ return qb.connection.options.type as string;
346
+ }
347
+
348
+ private buildDateGranularityExpression(driver: string, columnExpr: string, granularity: string) {
349
+ switch (driver) {
350
+ case 'postgres':
351
+ case 'cockroachdb':
352
+ return `DATE_TRUNC('${granularity}', ${columnExpr})`;
353
+ case 'mysql':
354
+ case 'mariadb':
355
+ switch (granularity) {
356
+ case 'day': return `DATE(${columnExpr})`;
357
+ case 'week': return `STR_TO_DATE(DATE_FORMAT(${columnExpr}, '%x-%v-1'), '%x-%v-%w')`;
358
+ case 'month': return `DATE_FORMAT(${columnExpr}, '%Y-%m-01')`;
359
+ case 'year': return `DATE_FORMAT(${columnExpr}, '%Y-01-01')`;
360
+ default: throw new Error(`Unsupported granularity ${granularity} for driver ${driver}`);
361
+ }
362
+ case 'mssql':
363
+ case 'sqlserver':
364
+ switch (granularity) {
365
+ case 'day': return `CONVERT(date, ${columnExpr})`;
366
+ case 'week': return `DATEADD(week, DATEDIFF(week, 0, ${columnExpr}), 0)`;
367
+ case 'month': return `DATEFROMPARTS(YEAR(${columnExpr}), MONTH(${columnExpr}), 1)`;
368
+ case 'year': return `DATEFROMPARTS(YEAR(${columnExpr}), 1, 1)`;
369
+ default: throw new Error(`Unsupported granularity ${granularity} for driver ${driver}`);
370
+ }
371
+ default:
372
+ throw new Error(`Granularity not supported for driver ${driver}`);
373
+ }
374
+ }
375
+
376
+ private buildGroupByExpression(qb: SelectQueryBuilder<any>, rootAlias: string, field: string) {
377
+ const parts = field.split(':');
378
+ const rawField = parts[0];
379
+ const granularity = parts[1];
380
+ const format = parts[2];
381
+ const pathParts = rawField.split('.');
382
+ const { alias, property } = this.ensureRelationPathJoined(qb, rootAlias, pathParts);
383
+ const columnExpr = `${alias}.${property}`;
384
+ const groupExpr = granularity ? this.buildDateGranularityExpression(this.getDriver(qb), columnExpr, granularity) : columnExpr;
385
+ const selectAlias = this.sanitizeAlias(`${rawField.replace(/\./g, '_')}${granularity ? '_' + granularity : ''}`);
386
+ return { groupExpr, selectAlias, sourceKey: field, format };
387
+ }
388
+
389
+ applyGroupBySelections(
390
+ qb: SelectQueryBuilder<any>,
391
+ groupBy: string[],
392
+ entityAlias: string
393
+ ) {
394
+ const aliasMap: Record<string, string> = {};
395
+ const formatMap: Record<string, string | undefined> = {};
396
+ const expressionMap: Record<string, string> = {};
397
+ qb.select([]);
398
+ groupBy.forEach((field) => {
399
+ const { groupExpr, selectAlias, sourceKey, format } = this.buildGroupByExpression(qb, entityAlias, field);
400
+ qb.addSelect(groupExpr, selectAlias);
401
+ qb.addGroupBy(groupExpr);
402
+ aliasMap[sourceKey] = selectAlias;
403
+ formatMap[selectAlias] = format;
404
+ expressionMap[selectAlias] = groupExpr;
405
+ });
406
+ return { aliasMap, formatMap, expressionMap };
407
+ }
408
+
409
+ private buildAggregateExpression(qb: SelectQueryBuilder<any>, rootAlias: string, aggregate: string) {
410
+ const [rawField, rawFn] = aggregate.split(':');
411
+ const fn = (rawFn || 'count').toLowerCase();
412
+ if ((!rawField || rawField.toLowerCase() === 'count') && fn === 'count') {
413
+ return { expression: 'COUNT(*)', selectAlias: 'count' };
414
+ }
415
+ if (!rawField) throw new Error(`Invalid aggregate specification: ${aggregate}`);
416
+ const pathParts = rawField.split('.');
417
+ const { alias, property } = this.ensureRelationPathJoined(qb, rootAlias, pathParts);
418
+ const columnExpr = `${alias}.${property}`;
419
+ const selectAlias = this.sanitizeAlias(`${rawField.replace(/\./g, '_')}_${fn}`);
420
+ let expression = '';
421
+ switch (fn) {
422
+ case 'count': expression = `COUNT(${columnExpr})`; break;
423
+ case 'count_distinct': expression = `COUNT(DISTINCT ${columnExpr})`; break;
424
+ case 'sum': expression = `SUM(${columnExpr})`; break;
425
+ case 'avg': expression = `AVG(${columnExpr})`; break;
426
+ case 'min': expression = `MIN(${columnExpr})`; break;
427
+ case 'max': expression = `MAX(${columnExpr})`; break;
428
+ default: throw new Error(`Unsupported aggregate function ${fn}`);
429
+ }
430
+ return { expression, selectAlias, sourceKey: aggregate };
431
+ }
432
+
433
+ applyAggregates(
434
+ qb: SelectQueryBuilder<any>,
435
+ aggregates: string[] | undefined,
436
+ entityAlias: string
437
+ ) {
438
+ const aggregateList = this.normalize(aggregates);
439
+ const aggregateAliasMap: Record<string, string> = {};
440
+ if (!aggregateList.length) {
441
+ qb.addSelect('COUNT(*)', 'count');
442
+ aggregateAliasMap['count'] = 'count';
443
+ return aggregateAliasMap;
444
+ }
445
+ aggregateList.forEach((agg) => {
446
+ const { expression, selectAlias, sourceKey } = this.buildAggregateExpression(qb, entityAlias, agg);
447
+ qb.addSelect(expression, selectAlias);
448
+ aggregateAliasMap[sourceKey] = selectAlias;
449
+ });
450
+ return aggregateAliasMap;
451
+ }
452
+
453
+ applyGroupSortingAndPagination(
454
+ qb: SelectQueryBuilder<any>,
455
+ sort: string[] | undefined,
456
+ aliasMap: Record<string, string>,
457
+ limit?: number,
458
+ offset?: number
459
+ ) {
460
+ const normalizedSort = this.normalize(sort);
461
+ if (normalizedSort.length) {
462
+ const orderOptions = this.orderOptions(normalizedSort);
463
+ const orderOptionKeys = Object.keys(orderOptions) as Array<keyof typeof orderOptions>;
464
+ orderOptionKeys.forEach((key) => {
465
+ const resolvedKey = aliasMap[key] || key as string;
466
+ const value = orderOptions[key] as 'ASC' | 'DESC';
467
+ qb.addOrderBy(`"${resolvedKey}"`, value);
468
+ });
469
+ }
470
+ const hasLimit = limit !== undefined && limit !== null;
471
+ const hasOffset = offset !== undefined && offset !== null;
472
+
473
+ // Use both take/skip and limit/offset to ensure pagination is applied even when joins are present.
474
+ if (hasLimit) {
475
+ qb.take(limit);
476
+ qb.limit(limit);
477
+ }
478
+ if (hasOffset) {
479
+ qb.skip(offset);
480
+ qb.offset(offset);
481
+ }
482
+ }
483
+
484
+ async countGroups(qb: SelectQueryBuilder<any>) {
485
+ const clone = qb.clone();
486
+ clone.limit(undefined).offset(undefined).take(undefined).skip(undefined);
487
+ const rows = await clone.getRawMany();
488
+ return rows.length;
489
+ }
490
+
310
491
  private buildJoinQueryForRelation(qb: SelectQueryBuilder<any>, entityAlias: string, relation: string) {
311
492
  // We split the joinProperty to get the alias of the entity we are joining
312
493
  const relationParts = relation.split('.');
@@ -341,46 +522,109 @@ export class CrudHelperService {
341
522
  return field.includes('(');
342
523
  }
343
524
 
344
- isAggregateFieldKey(key: string, alias: string): boolean {
345
- return !key.startsWith(`${alias}_`)
525
+ isAggregateFieldKey(key: string, aggregateAliases: Set<string>): boolean {
526
+ return aggregateAliases.has(key);
346
527
  }
347
528
 
348
529
  getFieldFromQueryFieldKey(queryFieldKey: string, alias: string): string {
349
530
  return queryFieldKey.replace(`${alias}_`, '');
350
531
  }
351
532
 
352
- buildGroupByRecordsQuery(qb: SelectQueryBuilder<any>, group: any, alias: string): SelectQueryBuilder<any> {
533
+ buildGroupByRecordsQuery(
534
+ qb: SelectQueryBuilder<any>,
535
+ group: any,
536
+ alias: string,
537
+ groupAliasMap: Record<string, string> = {},
538
+ aggregateAliasMap: Record<string, string> = {},
539
+ groupExpressionMap: Record<string, string> = {}
540
+ ): SelectQueryBuilder<any> {
541
+ const rootAlias = qb.expressionMap?.mainAlias?.name
542
+ ?? qb.expressionMap?.aliases?.find(a => a.metadata)?.name
543
+ ?? qb.expressionMap?.aliases?.[0]?.name
544
+ ?? (qb as any).alias
545
+ ?? alias;
353
546
  qb.andWhere(new Brackets(qb => {
547
+ const aggregateAliasSet = new Set(Object.values(aggregateAliasMap));
548
+ const reverseGroupAliasMap = Object.entries(groupAliasMap).reduce((acc, [sourceKey, aliasKey]) => {
549
+ acc[aliasKey] = sourceKey;
550
+ return acc;
551
+ }, {} as Record<string, string>);
354
552
  for (const key in group) {
355
- if (group.hasOwnProperty(key) && !this.isAggregateFieldKey(key, alias)) {
553
+ if (group.hasOwnProperty(key) && !this.isAggregateFieldKey(key, aggregateAliasSet)) {
356
554
  const value = group[key];
357
- const field = this.getFieldFromQueryFieldKey(key, alias);
358
- qb.andWhere(`${alias}.${field} = :${field}`, { [field]: value });
555
+ const sourceField = reverseGroupAliasMap[key] || key;
556
+ const cleanedField = sourceField.split(':')[0];
557
+ const pathParts = cleanedField.split('.');
558
+ const { alias: resolvedAlias, property } = this.ensureRelationPathJoined(qb as any, rootAlias, pathParts);
559
+ const paramKey = this.sanitizeAlias(`${resolvedAlias}_${property}_${key}`);
560
+ const expr = (sourceField.includes(':') && groupExpressionMap[key])
561
+ ? groupExpressionMap[key]
562
+ : `${resolvedAlias}.${property}`;
563
+ qb.andWhere(`${expr} = :${paramKey}`, { [paramKey]: value });
359
564
  }
360
565
  }
361
566
  }));
362
567
  return qb;
363
568
  }
364
569
 
365
- getGroupName(group: any, alias: string): string {
366
- return Object.keys(group)
367
- .filter(key => !this.isAggregateFieldKey(key, alias))
368
- .map(key => group[key])
369
- .join('_');
570
+ private formatGroupValue(value: any, format?: string) {
571
+ if (!format) return value;
572
+ if (value === null || value === undefined) return value;
573
+ const dateVal = value instanceof Date ? value : new Date(value);
574
+ if (isNaN(dateVal.getTime())) return value;
575
+ switch (format) {
576
+ case 'MMM':
577
+ return dateVal.toLocaleString('en', { month: 'short' });
578
+ case 'MMMM':
579
+ return dateVal.toLocaleString('en', { month: 'long' });
580
+ case 'YYYY':
581
+ return dateVal.getFullYear();
582
+ case 'YYYY-MM':
583
+ return `${dateVal.getFullYear()}-${String(dateVal.getMonth() + 1).padStart(2, '0')}`;
584
+ case 'YYYY-MM-DD':
585
+ return `${dateVal.getFullYear()}-${String(dateVal.getMonth() + 1).padStart(2, '0')}-${String(dateVal.getDate()).padStart(2, '0')}`;
586
+ default:
587
+ return value;
588
+ }
370
589
  }
371
590
 
372
- createGroupRecords(group: any, alias: string, groupData: any) {
373
- const groupName = this.getGroupName(group, alias);
591
+ getGroupName(
592
+ group: any,
593
+ aggregateAliases: Set<string>,
594
+ groupByFields: string[],
595
+ groupAliasMap: Record<string, string>,
596
+ groupFormatMap: Record<string, string | undefined>
597
+ ): string {
598
+ const orderedValues = groupByFields
599
+ .map(field => {
600
+ const alias = groupAliasMap[field] ?? this.sanitizeAlias(field.replace(/\./g, '_'));
601
+ const rawVal = group[alias] ?? group[field] ?? group[field.replace(/\./g, '_')];
602
+ return this.formatGroupValue(rawVal, groupFormatMap[alias]);
603
+ })
604
+ .filter(v => v !== undefined && v !== null);
605
+
606
+ if (orderedValues.length === 0) {
607
+ return Object.keys(group)
608
+ .filter(key => !this.isAggregateFieldKey(key, aggregateAliases))
609
+ .map(key => group[key])
610
+ .join('_');
611
+ }
612
+
613
+ return orderedValues.join('_');
614
+ }
615
+
616
+ createGroupRecords(group: any, aggregateAliases: Set<string>, groupData: any, groupByFields: string[], groupAliasMap: Record<string, string>, groupFormatMap: Record<string, string | undefined>) {
617
+ const groupName = this.getGroupName(group, aggregateAliases, groupByFields, groupAliasMap, groupFormatMap);
374
618
  return {
375
619
  groupName,
376
620
  groupData
377
621
  }
378
622
  }
379
- createGroupMeta(group: any, alias: string) {
380
- const groupName = this.getGroupName(group, alias);
623
+ createGroupMeta(group: any, aggregateAliases: Set<string>, groupByFields: string[], groupAliasMap: Record<string, string>, groupFormatMap: Record<string, string | undefined>) {
624
+ const groupName = this.getGroupName(group, aggregateAliases, groupByFields, groupAliasMap, groupFormatMap);
381
625
  const groupAggregateValues = {}
382
626
  for (const key in group) {
383
- if (group.hasOwnProperty(key) && this.isAggregateFieldKey(key, alias)) {
627
+ if (group.hasOwnProperty(key) && this.isAggregateFieldKey(key, aggregateAliases)) {
384
628
  const value = group[key];
385
629
  groupAggregateValues[key] = value;
386
630
  }
@@ -393,27 +637,21 @@ export class CrudHelperService {
393
637
 
394
638
  async countGroupedRecords(qb: SelectQueryBuilder<any>, basicFilterDto: BasicFilterDto, entityAlias: string) { //TODO : Check how to pass a type to SelectQueryBuilder instead of any
395
639
  const { limit, offset, ...rest } = basicFilterDto;
396
-
397
640
  const filteredDto = { ...rest, limit: undefined, offset: undefined };
398
641
 
399
- const filteredQB = this.buildFilterQuery(qb, filteredDto as BasicFilterDto, entityAlias);
642
+ const filteredQB = this.buildFilterQuery(qb, filteredDto as BasicFilterDto, entityAlias, undefined, undefined, undefined, FilterCombinator.AND, false, false);
400
643
 
401
- // Select only the group field and count distinct rows
402
- const groupByField = filteredDto.groupBy;
644
+ const groupByFields = this.normalize(filteredDto.groupBy);
403
645
 
404
- if (!groupByField || (Array.isArray(groupByField) && groupByField.length !== 1)) {
646
+ if (!groupByFields || groupByFields.length === 0) {
405
647
  throw new Error(ERROR_MESSAGES.INVALID_GROUP_BY_COUNT);
406
648
  }
407
649
 
408
- const field = Array.isArray(groupByField) ? groupByField[0] : groupByField;
409
- const rawResults = await filteredQB
410
- .select([]) // Remove prior select fields
411
- .addSelect(`${entityAlias}.${field}`, 'groupField')
412
- .groupBy(`${entityAlias}.${field}`)
413
- .limit(undefined) // Important: prevent LIMIT 1 from propagating
414
- .offset(undefined)
415
- .getRawMany();
650
+ this.applyGroupBySelections(filteredQB, groupByFields, entityAlias);
651
+ this.applyAggregates(filteredQB, ['count'], entityAlias);
652
+ filteredQB.limit(undefined).offset(undefined).take(undefined).skip(undefined);
416
653
 
654
+ const rawResults = await filteredQB.getRawMany();
417
655
  return rawResults.length;
418
656
  }
419
657
 
@@ -465,4 +703,4 @@ export class CrudHelperService {
465
703
 
466
704
 
467
705
 
468
- }
706
+ }