@solidxai/core 0.1.2 → 0.1.5-beta.0

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 (469) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/dist/commands/run-tests.command.d.ts +37 -0
  3. package/dist/commands/run-tests.command.d.ts.map +1 -0
  4. package/dist/commands/run-tests.command.js +345 -0
  5. package/dist/commands/run-tests.command.js.map +1 -0
  6. package/dist/commands/test-data.command.d.ts +6 -6
  7. package/dist/commands/test-data.command.d.ts.map +1 -1
  8. package/dist/commands/test-data.command.js +25 -25
  9. package/dist/commands/test-data.command.js.map +1 -1
  10. package/dist/commands/test.command.d.ts +5 -0
  11. package/dist/commands/test.command.d.ts.map +1 -0
  12. package/dist/commands/test.command.js +26 -0
  13. package/dist/commands/test.command.js.map +1 -0
  14. package/dist/constants/error-messages.d.ts +1 -0
  15. package/dist/constants/error-messages.d.ts.map +1 -1
  16. package/dist/constants/error-messages.js +1 -0
  17. package/dist/constants/error-messages.js.map +1 -1
  18. package/dist/constants.d.ts +3 -3
  19. package/dist/constants.d.ts.map +1 -1
  20. package/dist/constants.js +12 -12
  21. package/dist/constants.js.map +1 -1
  22. package/dist/controllers/otp-authentication.controller.d.ts +1 -4
  23. package/dist/controllers/otp-authentication.controller.d.ts.map +1 -1
  24. package/dist/controllers/otp-authentication.controller.js +1 -1
  25. package/dist/controllers/role-metadata.controller.d.ts +1 -0
  26. package/dist/controllers/role-metadata.controller.d.ts.map +1 -1
  27. package/dist/controllers/role-metadata.controller.js +15 -0
  28. package/dist/controllers/role-metadata.controller.js.map +1 -1
  29. package/dist/controllers/service.controller.d.ts +0 -9
  30. package/dist/controllers/service.controller.d.ts.map +1 -1
  31. package/dist/controllers/service.controller.js +0 -45
  32. package/dist/controllers/service.controller.js.map +1 -1
  33. package/dist/dtos/basic-filters.dto.d.ts.map +1 -1
  34. package/dist/dtos/basic-filters.dto.js.map +1 -1
  35. package/dist/dtos/create-email-template.dto.d.ts.map +1 -1
  36. package/dist/dtos/create-email-template.dto.js.map +1 -1
  37. package/dist/dtos/create-list-of-values.dto.d.ts.map +1 -1
  38. package/dist/dtos/create-list-of-values.dto.js.map +1 -1
  39. package/dist/dtos/create-menu-item-metadata.dto.d.ts.map +1 -1
  40. package/dist/dtos/create-menu-item-metadata.dto.js.map +1 -1
  41. package/dist/dtos/create-role-metadata.dto.d.ts.map +1 -1
  42. package/dist/dtos/create-role-metadata.dto.js.map +1 -1
  43. package/dist/dtos/create-scheduled-job.dto.d.ts.map +1 -1
  44. package/dist/dtos/create-scheduled-job.dto.js.map +1 -1
  45. package/dist/dtos/create-security-rule.dto.d.ts.map +1 -1
  46. package/dist/dtos/create-security-rule.dto.js.map +1 -1
  47. package/dist/dtos/create-sms-template.dto.d.ts.map +1 -1
  48. package/dist/dtos/create-sms-template.dto.js.map +1 -1
  49. package/dist/dtos/create-user.dto.d.ts +1 -0
  50. package/dist/dtos/create-user.dto.d.ts.map +1 -1
  51. package/dist/dtos/create-user.dto.js +2 -1
  52. package/dist/dtos/create-user.dto.js.map +1 -1
  53. package/dist/dtos/create-view-metadata.dto.d.ts.map +1 -1
  54. package/dist/dtos/create-view-metadata.dto.js.map +1 -1
  55. package/dist/dtos/otp-sign-in.dto.d.ts +1 -1
  56. package/dist/dtos/otp-sign-in.dto.d.ts.map +1 -1
  57. package/dist/dtos/otp-sign-in.dto.js +2 -2
  58. package/dist/dtos/otp-sign-in.dto.js.map +1 -1
  59. package/dist/dtos/otp-sign-up.dto.d.ts +2 -2
  60. package/dist/dtos/otp-sign-up.dto.d.ts.map +1 -1
  61. package/dist/dtos/otp-sign-up.dto.js +2 -2
  62. package/dist/dtos/otp-sign-up.dto.js.map +1 -1
  63. package/dist/dtos/resolve-s3-url.dto.d.ts +2 -5
  64. package/dist/dtos/resolve-s3-url.dto.d.ts.map +1 -1
  65. package/dist/dtos/resolve-s3-url.dto.js +1 -13
  66. package/dist/dtos/resolve-s3-url.dto.js.map +1 -1
  67. package/dist/dtos/sign-up.dto.d.ts.map +1 -1
  68. package/dist/dtos/sign-up.dto.js.map +1 -1
  69. package/dist/dtos/update-email-template.dto.d.ts.map +1 -1
  70. package/dist/dtos/update-email-template.dto.js.map +1 -1
  71. package/dist/dtos/update-list-of-values.dto.d.ts.map +1 -1
  72. package/dist/dtos/update-list-of-values.dto.js.map +1 -1
  73. package/dist/dtos/update-menu-item-metadata.dto.d.ts.map +1 -1
  74. package/dist/dtos/update-menu-item-metadata.dto.js.map +1 -1
  75. package/dist/dtos/update-scheduled-job.dto.d.ts.map +1 -1
  76. package/dist/dtos/update-scheduled-job.dto.js.map +1 -1
  77. package/dist/dtos/update-security-rule.dto.d.ts.map +1 -1
  78. package/dist/dtos/update-security-rule.dto.js.map +1 -1
  79. package/dist/dtos/update-sms-template.dto.d.ts.map +1 -1
  80. package/dist/dtos/update-sms-template.dto.js.map +1 -1
  81. package/dist/dtos/update-view-metadata.dto.d.ts.map +1 -1
  82. package/dist/dtos/update-view-metadata.dto.js.map +1 -1
  83. package/dist/entities/user.entity.d.ts +1 -0
  84. package/dist/entities/user.entity.d.ts.map +1 -1
  85. package/dist/entities/user.entity.js +6 -1
  86. package/dist/entities/user.entity.js.map +1 -1
  87. package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.d.ts +2 -0
  88. package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.d.ts.map +1 -1
  89. package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.js +33 -23
  90. package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.js.map +1 -1
  91. package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.d.ts +3 -0
  92. package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.d.ts.map +1 -1
  93. package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.js +36 -23
  94. package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.js.map +1 -1
  95. package/dist/helpers/security.helper.js +1 -0
  96. package/dist/helpers/security.helper.js.map +1 -1
  97. package/dist/index.d.ts +3 -4
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +3 -4
  100. package/dist/index.js.map +1 -1
  101. package/dist/repository/solid-base.repository.d.ts +10 -1
  102. package/dist/repository/solid-base.repository.d.ts.map +1 -1
  103. package/dist/repository/solid-base.repository.js +109 -0
  104. package/dist/repository/solid-base.repository.js.map +1 -1
  105. package/dist/seeders/module-metadata-seeder.service.d.ts +2 -0
  106. package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
  107. package/dist/seeders/module-metadata-seeder.service.js +142 -91
  108. package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
  109. package/dist/seeders/module-test-data.service.d.ts.map +1 -1
  110. package/dist/seeders/module-test-data.service.js +3 -3
  111. package/dist/seeders/module-test-data.service.js.map +1 -1
  112. package/dist/seeders/permission-metadata-seeder.service.d.ts +1 -1
  113. package/dist/seeders/permission-metadata-seeder.service.d.ts.map +1 -1
  114. package/dist/seeders/permission-metadata-seeder.service.js +1 -1
  115. package/dist/seeders/permission-metadata-seeder.service.js.map +1 -1
  116. package/dist/seeders/seed-data/solid-core-metadata.json +12 -25
  117. package/dist/services/authentication.service.d.ts +22 -8
  118. package/dist/services/authentication.service.d.ts.map +1 -1
  119. package/dist/services/authentication.service.js +228 -214
  120. package/dist/services/authentication.service.js.map +1 -1
  121. package/dist/services/chatter-message.service.d.ts +2 -0
  122. package/dist/services/chatter-message.service.d.ts.map +1 -1
  123. package/dist/services/chatter-message.service.js +18 -2
  124. package/dist/services/chatter-message.service.js.map +1 -1
  125. package/dist/services/crud-helper.service.d.ts +4 -0
  126. package/dist/services/crud-helper.service.d.ts.map +1 -1
  127. package/dist/services/crud-helper.service.js +66 -32
  128. package/dist/services/crud-helper.service.js.map +1 -1
  129. package/dist/services/crud.service.d.ts.map +1 -1
  130. package/dist/services/crud.service.js +7 -4
  131. package/dist/services/crud.service.js.map +1 -1
  132. package/dist/services/field-metadata.service.d.ts.map +1 -1
  133. package/dist/services/field-metadata.service.js.map +1 -1
  134. package/dist/services/file/disk-file.service.d.ts +0 -2
  135. package/dist/services/file/disk-file.service.d.ts.map +1 -1
  136. package/dist/services/file/disk-file.service.js +7 -16
  137. package/dist/services/file/disk-file.service.js.map +1 -1
  138. package/dist/services/file/index.d.ts +1 -0
  139. package/dist/services/file/index.d.ts.map +1 -1
  140. package/dist/services/file/index.js +1 -0
  141. package/dist/services/file/index.js.map +1 -1
  142. package/dist/services/file/storage-path-builder.d.ts +17 -0
  143. package/dist/services/file/storage-path-builder.d.ts.map +1 -0
  144. package/dist/{seeders/sms-template-seeder.service.js → services/file/storage-path-builder.js} +45 -35
  145. package/dist/services/file/storage-path-builder.js.map +1 -0
  146. package/dist/services/media.service.d.ts +1 -1
  147. package/dist/services/media.service.d.ts.map +1 -1
  148. package/dist/services/media.service.js +45 -6
  149. package/dist/services/media.service.js.map +1 -1
  150. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js.map +1 -1
  151. package/dist/services/mediaStorageProviders/file-storage-provider.d.ts.map +1 -1
  152. package/dist/services/mediaStorageProviders/file-storage-provider.js +46 -7
  153. package/dist/services/mediaStorageProviders/file-storage-provider.js.map +1 -1
  154. package/dist/services/module-metadata.service.d.ts +4 -6
  155. package/dist/services/module-metadata.service.d.ts.map +1 -1
  156. package/dist/services/module-metadata.service.js +16 -14
  157. package/dist/services/module-metadata.service.js.map +1 -1
  158. package/dist/services/queues/common.d.ts +3 -0
  159. package/dist/services/queues/common.d.ts.map +1 -0
  160. package/dist/services/queues/common.js +39 -0
  161. package/dist/services/queues/common.js.map +1 -0
  162. package/dist/services/queues/database-publisher.service.d.ts.map +1 -1
  163. package/dist/services/queues/database-publisher.service.js +3 -1
  164. package/dist/services/queues/database-publisher.service.js.map +1 -1
  165. package/dist/services/queues/database-subscriber.service.d.ts.map +1 -1
  166. package/dist/services/queues/database-subscriber.service.js +5 -2
  167. package/dist/services/queues/database-subscriber.service.js.map +1 -1
  168. package/dist/services/queues/rabbitmq-publisher.service.d.ts.map +1 -1
  169. package/dist/services/queues/rabbitmq-publisher.service.js +13 -6
  170. package/dist/services/queues/rabbitmq-publisher.service.js.map +1 -1
  171. package/dist/services/queues/rabbitmq-subscriber.service.d.ts.map +1 -1
  172. package/dist/services/queues/rabbitmq-subscriber.service.js +9 -5
  173. package/dist/services/queues/rabbitmq-subscriber.service.js.map +1 -1
  174. package/dist/services/setting.service.d.ts +3 -2
  175. package/dist/services/setting.service.d.ts.map +1 -1
  176. package/dist/services/setting.service.js +7 -4
  177. package/dist/services/setting.service.js.map +1 -1
  178. package/dist/services/settings/default-settings-provider.service.d.ts +24 -2
  179. package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
  180. package/dist/services/settings/default-settings-provider.service.js +8 -6
  181. package/dist/services/settings/default-settings-provider.service.js.map +1 -1
  182. package/dist/solid-core.module.d.ts +3 -1
  183. package/dist/solid-core.module.d.ts.map +1 -1
  184. package/dist/solid-core.module.js +49 -9
  185. package/dist/solid-core.module.js.map +1 -1
  186. package/dist/testing/__examples__/register-example-specs.d.ts +3 -0
  187. package/dist/testing/__examples__/register-example-specs.d.ts.map +1 -0
  188. package/dist/testing/__examples__/register-example-specs.js +8 -0
  189. package/dist/testing/__examples__/register-example-specs.js.map +1 -0
  190. package/dist/testing/__examples__/specs/custom-health.spec.d.ts +17 -0
  191. package/dist/testing/__examples__/specs/custom-health.spec.d.ts.map +1 -0
  192. package/dist/testing/__examples__/specs/custom-health.spec.js +30 -0
  193. package/dist/testing/__examples__/specs/custom-health.spec.js.map +1 -0
  194. package/dist/testing/adapters/api/api-adapter.d.ts +9 -0
  195. package/dist/testing/adapters/api/api-adapter.d.ts.map +1 -0
  196. package/dist/testing/adapters/api/api-adapter.js +76 -0
  197. package/dist/testing/adapters/api/api-adapter.js.map +1 -0
  198. package/dist/testing/adapters/api/api.types.d.ts +14 -0
  199. package/dist/testing/adapters/api/api.types.d.ts.map +1 -0
  200. package/dist/testing/adapters/api/api.types.js +3 -0
  201. package/dist/testing/adapters/api/api.types.js.map +1 -0
  202. package/dist/testing/adapters/ui/playwright-adapter.d.ts +14 -0
  203. package/dist/testing/adapters/ui/playwright-adapter.d.ts.map +1 -0
  204. package/dist/testing/adapters/ui/playwright-adapter.js +80 -0
  205. package/dist/testing/adapters/ui/playwright-adapter.js.map +1 -0
  206. package/dist/testing/adapters/ui/ui.types.d.ts +5 -0
  207. package/dist/testing/adapters/ui/ui.types.d.ts.map +1 -0
  208. package/dist/testing/adapters/ui/ui.types.js +3 -0
  209. package/dist/testing/adapters/ui/ui.types.js.map +1 -0
  210. package/dist/testing/contracts/runtime-context.types.d.ts +35 -0
  211. package/dist/testing/contracts/runtime-context.types.d.ts.map +1 -0
  212. package/dist/testing/contracts/runtime-context.types.js +3 -0
  213. package/dist/testing/contracts/runtime-context.types.js.map +1 -0
  214. package/dist/testing/contracts/test-spec.types.d.ts +21 -0
  215. package/dist/testing/contracts/test-spec.types.d.ts.map +1 -0
  216. package/dist/testing/contracts/test-spec.types.js +3 -0
  217. package/dist/testing/contracts/test-spec.types.js.map +1 -0
  218. package/dist/testing/contracts/testing-metadata.types.d.ts +41 -0
  219. package/dist/testing/contracts/testing-metadata.types.d.ts.map +1 -0
  220. package/dist/testing/contracts/testing-metadata.types.js +3 -0
  221. package/dist/testing/contracts/testing-metadata.types.js.map +1 -0
  222. package/dist/testing/core/interpolation.d.ts +4 -0
  223. package/dist/testing/core/interpolation.d.ts.map +1 -0
  224. package/dist/testing/core/interpolation.js +180 -0
  225. package/dist/testing/core/interpolation.js.map +1 -0
  226. package/dist/testing/core/normalize-steps.d.ts +7 -0
  227. package/dist/testing/core/normalize-steps.d.ts.map +1 -0
  228. package/dist/testing/core/normalize-steps.js +20 -0
  229. package/dist/testing/core/normalize-steps.js.map +1 -0
  230. package/dist/testing/core/resource-store.d.ts +8 -0
  231. package/dist/testing/core/resource-store.d.ts.map +1 -0
  232. package/dist/testing/core/resource-store.js +41 -0
  233. package/dist/testing/core/resource-store.js.map +1 -0
  234. package/dist/testing/core/spec-registry.d.ts +10 -0
  235. package/dist/testing/core/spec-registry.d.ts.map +1 -0
  236. package/dist/testing/core/spec-registry.js +32 -0
  237. package/dist/testing/core/spec-registry.js.map +1 -0
  238. package/dist/testing/core/step-registry.d.ts +10 -0
  239. package/dist/testing/core/step-registry.d.ts.map +1 -0
  240. package/dist/testing/core/step-registry.js +26 -0
  241. package/dist/testing/core/step-registry.js.map +1 -0
  242. package/dist/testing/core/testing-engine.d.ts +14 -0
  243. package/dist/testing/core/testing-engine.d.ts.map +1 -0
  244. package/dist/testing/core/testing-engine.js +97 -0
  245. package/dist/testing/core/testing-engine.js.map +1 -0
  246. package/dist/testing/core/timeout.d.ts +2 -0
  247. package/dist/testing/core/timeout.d.ts.map +1 -0
  248. package/dist/testing/core/timeout.js +18 -0
  249. package/dist/testing/core/timeout.js.map +1 -0
  250. package/dist/testing/reporter/attachments.d.ts +4 -0
  251. package/dist/testing/reporter/attachments.d.ts.map +1 -0
  252. package/dist/testing/reporter/attachments.js +25 -0
  253. package/dist/testing/reporter/attachments.js.map +1 -0
  254. package/dist/testing/reporter/console-reporter.d.ts +45 -0
  255. package/dist/testing/reporter/console-reporter.d.ts.map +1 -0
  256. package/dist/testing/reporter/console-reporter.js +189 -0
  257. package/dist/testing/reporter/console-reporter.js.map +1 -0
  258. package/dist/testing/reporter/reporter.types.d.ts +37 -0
  259. package/dist/testing/reporter/reporter.types.d.ts.map +1 -0
  260. package/dist/testing/reporter/reporter.types.js +3 -0
  261. package/dist/testing/reporter/reporter.types.js.map +1 -0
  262. package/dist/testing/runner/lifecycle.d.ts +9 -0
  263. package/dist/testing/runner/lifecycle.d.ts.map +1 -0
  264. package/dist/testing/runner/lifecycle.js +33 -0
  265. package/dist/testing/runner/lifecycle.js.map +1 -0
  266. package/dist/testing/runner/run-from-metadata.d.ts +24 -0
  267. package/dist/testing/runner/run-from-metadata.d.ts.map +1 -0
  268. package/dist/testing/runner/run-from-metadata.js +70 -0
  269. package/dist/testing/runner/run-from-metadata.js.map +1 -0
  270. package/dist/testing/runner/scenario-filter.d.ts +9 -0
  271. package/dist/testing/runner/scenario-filter.d.ts.map +1 -0
  272. package/dist/testing/runner/scenario-filter.js +22 -0
  273. package/dist/testing/runner/scenario-filter.js.map +1 -0
  274. package/dist/testing/steps/api/auth.step.d.ts +3 -0
  275. package/dist/testing/steps/api/auth.step.d.ts.map +1 -0
  276. package/dist/testing/steps/api/auth.step.js +38 -0
  277. package/dist/testing/steps/api/auth.step.js.map +1 -0
  278. package/dist/testing/steps/api/index.d.ts +3 -0
  279. package/dist/testing/steps/api/index.d.ts.map +1 -0
  280. package/dist/testing/steps/api/index.js +10 -0
  281. package/dist/testing/steps/api/index.js.map +1 -0
  282. package/dist/testing/steps/api/request.step.d.ts +3 -0
  283. package/dist/testing/steps/api/request.step.d.ts.map +1 -0
  284. package/dist/testing/steps/api/request.step.js +281 -0
  285. package/dist/testing/steps/api/request.step.js.map +1 -0
  286. package/dist/testing/steps/assert/http.step.d.ts +3 -0
  287. package/dist/testing/steps/assert/http.step.d.ts.map +1 -0
  288. package/dist/testing/steps/assert/http.step.js +27 -0
  289. package/dist/testing/steps/assert/http.step.js.map +1 -0
  290. package/dist/testing/steps/assert/index.d.ts +3 -0
  291. package/dist/testing/steps/assert/index.d.ts.map +1 -0
  292. package/dist/testing/steps/assert/index.js +12 -0
  293. package/dist/testing/steps/assert/index.js.map +1 -0
  294. package/dist/testing/steps/assert/jsonpath.step.d.ts +3 -0
  295. package/dist/testing/steps/assert/jsonpath.step.d.ts.map +1 -0
  296. package/dist/testing/steps/assert/jsonpath.step.js +40 -0
  297. package/dist/testing/steps/assert/jsonpath.step.js.map +1 -0
  298. package/dist/testing/steps/assert/primitives.step.d.ts +3 -0
  299. package/dist/testing/steps/assert/primitives.step.d.ts.map +1 -0
  300. package/dist/testing/steps/assert/primitives.step.js +43 -0
  301. package/dist/testing/steps/assert/primitives.step.js.map +1 -0
  302. package/dist/testing/steps/test/index.d.ts +3 -0
  303. package/dist/testing/steps/test/index.d.ts.map +1 -0
  304. package/dist/testing/steps/test/index.js +8 -0
  305. package/dist/testing/steps/test/index.js.map +1 -0
  306. package/dist/testing/steps/test/test-spec.step.d.ts +3 -0
  307. package/dist/testing/steps/test/test-spec.step.d.ts.map +1 -0
  308. package/dist/testing/steps/test/test-spec.step.js +41 -0
  309. package/dist/testing/steps/test/test-spec.step.js.map +1 -0
  310. package/dist/testing/steps/ui/actions.step.d.ts +3 -0
  311. package/dist/testing/steps/ui/actions.step.d.ts.map +1 -0
  312. package/dist/testing/steps/ui/actions.step.js +31 -0
  313. package/dist/testing/steps/ui/actions.step.js.map +1 -0
  314. package/dist/testing/steps/ui/assertions.step.d.ts +3 -0
  315. package/dist/testing/steps/ui/assertions.step.d.ts.map +1 -0
  316. package/dist/testing/steps/ui/assertions.step.js +41 -0
  317. package/dist/testing/steps/ui/assertions.step.js.map +1 -0
  318. package/dist/testing/steps/ui/form.step.d.ts +3 -0
  319. package/dist/testing/steps/ui/form.step.d.ts.map +1 -0
  320. package/dist/testing/steps/ui/form.step.js +34 -0
  321. package/dist/testing/steps/ui/form.step.js.map +1 -0
  322. package/dist/testing/steps/ui/index.d.ts +3 -0
  323. package/dist/testing/steps/ui/index.d.ts.map +1 -0
  324. package/dist/testing/steps/ui/index.js +14 -0
  325. package/dist/testing/steps/ui/index.js.map +1 -0
  326. package/dist/testing/steps/ui/navigation.step.d.ts +3 -0
  327. package/dist/testing/steps/ui/navigation.step.d.ts.map +1 -0
  328. package/dist/testing/steps/ui/navigation.step.js +39 -0
  329. package/dist/testing/steps/ui/navigation.step.js.map +1 -0
  330. package/dist/testing/steps/util/index.d.ts +3 -0
  331. package/dist/testing/steps/util/index.d.ts.map +1 -0
  332. package/dist/testing/steps/util/index.js +12 -0
  333. package/dist/testing/steps/util/index.js.map +1 -0
  334. package/dist/testing/steps/util/log.step.d.ts +3 -0
  335. package/dist/testing/steps/util/log.step.d.ts.map +1 -0
  336. package/dist/testing/steps/util/log.step.js +18 -0
  337. package/dist/testing/steps/util/log.step.js.map +1 -0
  338. package/dist/testing/steps/util/require.step.d.ts +3 -0
  339. package/dist/testing/steps/util/require.step.d.ts.map +1 -0
  340. package/dist/testing/steps/util/require.step.js +16 -0
  341. package/dist/testing/steps/util/require.step.js.map +1 -0
  342. package/dist/testing/steps/util/sleep.step.d.ts +3 -0
  343. package/dist/testing/steps/util/sleep.step.d.ts.map +1 -0
  344. package/dist/testing/steps/util/sleep.step.js +13 -0
  345. package/dist/testing/steps/util/sleep.step.js.map +1 -0
  346. package/docs/test-data-workflow.md +51 -11
  347. package/package.json +10 -2
  348. package/src/commands/run-tests.command.ts +278 -0
  349. package/src/commands/test-data.command.ts +26 -26
  350. package/src/commands/test.command.ts +14 -0
  351. package/src/constants/error-messages.ts +1 -0
  352. package/src/constants.ts +3 -3
  353. package/src/controllers/role-metadata.controller.ts +26 -18
  354. package/src/controllers/service.controller.ts +58 -59
  355. package/src/dtos/basic-filters.dto.ts +0 -2
  356. package/src/dtos/create-email-template.dto.ts +7 -0
  357. package/src/dtos/create-list-of-values.dto.ts +7 -0
  358. package/src/dtos/create-menu-item-metadata.dto.ts +12 -1
  359. package/src/dtos/create-role-metadata.dto.ts +9 -0
  360. package/src/dtos/create-scheduled-job.dto.ts +14 -0
  361. package/src/dtos/create-security-rule.dto.ts +6 -0
  362. package/src/dtos/create-sms-template.dto.ts +6 -0
  363. package/src/dtos/create-user.dto.ts +1 -0
  364. package/src/dtos/create-view-metadata.dto.ts +11 -0
  365. package/src/dtos/otp-sign-in.dto.ts +3 -3
  366. package/src/dtos/otp-sign-up.dto.ts +3 -3
  367. package/src/dtos/resolve-s3-url.dto.ts +2 -12
  368. package/src/dtos/sign-up.dto.ts +0 -2
  369. package/src/dtos/update-email-template.dto.ts +6 -0
  370. package/src/dtos/update-list-of-values.dto.ts +8 -0
  371. package/src/dtos/update-menu-item-metadata.dto.ts +12 -0
  372. package/src/dtos/update-scheduled-job.dto.ts +15 -0
  373. package/src/dtos/update-security-rule.dto.ts +7 -0
  374. package/src/dtos/update-sms-template.dto.ts +32 -32
  375. package/src/dtos/update-view-metadata.dto.ts +12 -0
  376. package/src/entities/user.entity.ts +3 -0
  377. package/src/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.ts +43 -32
  378. package/src/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.ts +45 -33
  379. package/src/helpers/security.helper.ts +1 -1
  380. package/src/index.ts +3 -4
  381. package/src/repository/solid-base.repository.ts +172 -1
  382. package/src/seeders/module-metadata-seeder.service.ts +191 -150
  383. package/src/seeders/module-test-data.service.ts +5 -3
  384. package/src/seeders/permission-metadata-seeder.service.ts +1 -4
  385. package/src/seeders/seed-data/solid-core-metadata.json +12 -25
  386. package/src/services/authentication.service.ts +268 -266
  387. package/src/services/chatter-message.service.ts +18 -1
  388. package/src/services/crud-helper.service.ts +79 -36
  389. package/src/services/crud.service.ts +10 -4
  390. package/src/services/field-metadata.service.ts +0 -71
  391. package/src/services/file/disk-file.service.ts +8 -18
  392. package/src/services/file/index.ts +1 -0
  393. package/src/services/file/storage-path-builder.ts +56 -0
  394. package/src/services/media.service.ts +13 -7
  395. package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +1 -1
  396. package/src/services/mediaStorageProviders/file-storage-provider.ts +13 -8
  397. package/src/services/module-metadata.service.ts +18 -15
  398. package/src/services/queues/common.ts +75 -0
  399. package/src/services/queues/database-publisher.service.ts +4 -1
  400. package/src/services/queues/database-subscriber.service.ts +5 -3
  401. package/src/services/queues/rabbitmq-publisher.service.ts +17 -7
  402. package/src/services/queues/rabbitmq-subscriber.service.ts +9 -5
  403. package/src/services/setting.service.ts +5 -3
  404. package/src/services/settings/default-settings-provider.service.ts +5 -3
  405. package/src/solid-core.module.ts +20 -12
  406. package/src/testing/README.md +364 -0
  407. package/src/testing/__examples__/register-example-specs.ts +6 -0
  408. package/src/testing/__examples__/specs/custom-health.spec.ts +29 -0
  409. package/src/testing/__examples__/testing.sample.json +82 -0
  410. package/src/testing/adapters/api/api-adapter.ts +85 -0
  411. package/src/testing/adapters/api/api.types.ts +15 -0
  412. package/src/testing/adapters/ui/playwright-adapter.ts +54 -0
  413. package/src/testing/adapters/ui/ui.types.ts +4 -0
  414. package/src/testing/contracts/runtime-context.types.ts +36 -0
  415. package/src/testing/contracts/test-spec.types.ts +24 -0
  416. package/src/testing/contracts/testing-metadata.types.ts +46 -0
  417. package/src/testing/core/interpolation.ts +189 -0
  418. package/src/testing/core/normalize-steps.ts +21 -0
  419. package/src/testing/core/resource-store.ts +38 -0
  420. package/src/testing/core/spec-registry.ts +33 -0
  421. package/src/testing/core/step-registry.ts +27 -0
  422. package/src/testing/core/testing-engine.ts +127 -0
  423. package/src/testing/core/timeout.ts +19 -0
  424. package/src/testing/reporter/attachments.ts +25 -0
  425. package/src/testing/reporter/console-reporter.ts +229 -0
  426. package/src/testing/reporter/reporter.types.ts +36 -0
  427. package/src/testing/runner/lifecycle.ts +31 -0
  428. package/src/testing/runner/run-from-metadata.ts +87 -0
  429. package/src/testing/runner/scenario-filter.ts +33 -0
  430. package/src/testing/steps/api/auth.step.ts +66 -0
  431. package/src/testing/steps/api/index.ts +10 -0
  432. package/src/testing/steps/api/request.step.ts +358 -0
  433. package/src/testing/steps/assert/http.step.ts +33 -0
  434. package/src/testing/steps/assert/index.ts +12 -0
  435. package/src/testing/steps/assert/jsonpath.step.ts +50 -0
  436. package/src/testing/steps/assert/primitives.step.ts +69 -0
  437. package/src/testing/steps/test/index.ts +8 -0
  438. package/src/testing/steps/test/test-spec.step.ts +52 -0
  439. package/src/testing/steps/ui/actions.step.ts +36 -0
  440. package/src/testing/steps/ui/assertions.step.ts +54 -0
  441. package/src/testing/steps/ui/form.step.ts +39 -0
  442. package/src/testing/steps/ui/index.ts +12 -0
  443. package/src/testing/steps/ui/navigation.step.ts +53 -0
  444. package/src/testing/steps/util/index.ts +10 -0
  445. package/src/testing/steps/util/log.step.ts +19 -0
  446. package/src/testing/steps/util/require.step.ts +16 -0
  447. package/src/testing/steps/util/sleep.step.ts +15 -0
  448. package/tsconfig.json +35 -25
  449. package/tsconfig.tests.json +14 -0
  450. package/dist/passport-strategies/local.strategy.d.ts +0 -15
  451. package/dist/passport-strategies/local.strategy.d.ts.map +0 -1
  452. package/dist/passport-strategies/local.strategy.js +0 -44
  453. package/dist/passport-strategies/local.strategy.js.map +0 -1
  454. package/dist/seeders/email-template-seeder.service.d.ts +0 -10
  455. package/dist/seeders/email-template-seeder.service.d.ts.map +0 -1
  456. package/dist/seeders/email-template-seeder.service.js +0 -84
  457. package/dist/seeders/email-template-seeder.service.js.map +0 -1
  458. package/dist/seeders/sms-template-seeder.service.d.ts +0 -10
  459. package/dist/seeders/sms-template-seeder.service.d.ts.map +0 -1
  460. package/dist/seeders/sms-template-seeder.service.js.map +0 -1
  461. package/dist/seeders/user-seeder.service.d.ts +0 -10
  462. package/dist/seeders/user-seeder.service.d.ts.map +0 -1
  463. package/dist/seeders/user-seeder.service.js +0 -44
  464. package/dist/seeders/user-seeder.service.js.map +0 -1
  465. package/dist/tsconfig.tsbuildinfo +0 -1
  466. package/src/passport-strategies/local.strategy.ts +0 -28
  467. package/src/seeders/email-template-seeder.service.ts +0 -49
  468. package/src/seeders/sms-template-seeder.service.ts +0 -50
  469. package/src/seeders/user-seeder.service.ts +0 -33
@@ -18,9 +18,12 @@ import { ChatterMessageDetails } from '../entities/chatter-message-details.entit
18
18
  import { ChatterMessage } from '../entities/chatter-message.entity';
19
19
  import { getMediaStorageProvider } from './mediaStorageProviders';
20
20
  import { RequestContextService } from './request-context.service';
21
- import { take } from 'rxjs';
21
+ import { Logger } from '@nestjs/common';
22
+
22
23
  @Injectable()
23
24
  export class ChatterMessageService extends CRUDService<ChatterMessage> {
25
+ private readonly _logger = new Logger(ChatterMessageService.name);
26
+
24
27
  constructor(
25
28
  @InjectEntityManager()
26
29
  readonly entityManager: EntityManager,
@@ -502,16 +505,23 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
502
505
  return populatedEntity;
503
506
  }
504
507
 
508
+ private logHeapUsed(label: string) {
509
+ const mb = () => Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
510
+ this._logger.log(`heapUsedMB(${label}): ${mb()}`);
511
+ }
512
+
505
513
  // [2026-02-05T23:31:21.025Z] INFO: [200 OK]
506
514
  // GET /api/chatter-message/getChatterMessages/216/mswipeBoomboxBulkUpload?populateMedia[0]=messageAttachments&populate[0]=user&populate[1]=chatterMessageDetails&limit=25 22747ms
507
515
  async getChatterMessages(entityId: number, entityName: string, query: any) {
508
516
  const { limit = 25, offset = 0, populate = [], populateMedia = [], filters } = query;
517
+ this.logHeapUsed('getChatterMessages-start');
509
518
 
510
519
  const model = await this.modelMetadataRepo.findOne({
511
520
  where: {
512
521
  singularName: entityName
513
522
  },
514
523
  });
524
+ this.logHeapUsed('getChatterMessages-modelLoaded');
515
525
  const oneToManyFields = await this.fieldMetadataRepo.find({
516
526
  where: {
517
527
  model: { id: model.id },
@@ -519,11 +529,13 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
519
529
  relationType: 'one-to-many'
520
530
  }
521
531
  });
532
+ this.logHeapUsed('getChatterMessages-oneToManyFieldsLoaded');
522
533
 
523
534
  const relatedEntitiesMap = new Map<string, number[]>();
524
535
 
525
536
  for (const field of oneToManyFields) {
526
537
  if (field.enableAuditTracking === false) {
538
+ this._logger.log(`Skipping field ${field.name} for chatter message retrieval because audit tracking is disabled`);
527
539
  continue
528
540
  }
529
541
  const coModelName = field.relationCoModelSingularName;
@@ -552,8 +564,10 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
552
564
  relatedEntitiesMap.set(field.name, relatedIds);
553
565
  }
554
566
  }
567
+ this.logHeapUsed('getChatterMessages-relatedEntitiesLoaded');
555
568
 
556
569
  const qb = await this.repo.createSecurityRuleAwareQueryBuilder('entity');
570
+ this.logHeapUsed('getChatterMessages-queryBuilderReady');
557
571
 
558
572
  const orConditions: string[] = [];
559
573
  const parameters: any = {};
@@ -602,10 +616,13 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
602
616
  qb.skip(offset).take(limit);
603
617
 
604
618
  const [entities, count] = await qb.getManyAndCount();
619
+ this.logHeapUsed('getChatterMessages-entitiesLoaded');
605
620
 
606
621
  if (populateMedia && populateMedia.length > 0) {
607
622
  const normalizedPopulateMedia = this.crudHelperService.normalize(populateMedia);
623
+ this.logHeapUsed('getChatterMessages-beforePopulateMedia');
608
624
  await this['handlePopulateMedia'](normalizedPopulateMedia, entities);
625
+ this.logHeapUsed('getChatterMessages-afterPopulateMedia');
609
626
  }
610
627
 
611
628
  const currentPage = Math.floor(offset / limit) + 1;
@@ -3,7 +3,7 @@ import { BasicFilterDto } from "../dtos/basic-filters.dto";
3
3
  import { classify } from "@angular-devkit/core/src/utils/strings";
4
4
  import { ActiveUserData } from "src/interfaces/active-user-data.interface";
5
5
  import { SolidRegistry } from "src/helpers/solid-registry";
6
- import { Logger } from "@nestjs/common";
6
+ import { BadRequestException, Logger } from "@nestjs/common";
7
7
  import { ERROR_MESSAGES } from "src/constants/error-messages";
8
8
 
9
9
  export enum FilterCombinator {
@@ -64,88 +64,102 @@ export class CrudHelperService {
64
64
  const primaryFilterObj = normalizedFilters[key];
65
65
  const normalizedPrimaryFilterObj = this.normalizeObjectKeys(primaryFilterObj);
66
66
 
67
+ const [rawField, funcAlias] = key.split(':');
68
+
67
69
  // Get the operator or field from the key
68
70
  const operatorOrField = Object.keys(normalizedPrimaryFilterObj)[0];
69
71
  // if the key is an operator, then build the query based on the operator
70
72
  if (operatorOrField.startsWith('$')) {
71
73
  const operator = operatorOrField;
72
- this.buildOperatorQuery(qb, alias, key, normalizedPrimaryFilterObj, operator);
74
+ let columnExpression: string | undefined;
75
+ if (funcAlias) {
76
+ try {
77
+ columnExpression = this.buildDateGranularityExpression(this.getDriver(selectQb), `${alias}.${rawField}`, funcAlias);
78
+ } catch {
79
+ throw new BadRequestException(`Unsupported field function '${funcAlias}'. Supported functions are: day, week, month, year.`);
80
+ }
81
+ }
82
+ this.buildOperatorQuery(qb, alias, rawField, normalizedPrimaryFilterObj, operator, columnExpression);
73
83
  return;
74
84
  }
75
85
  else { // Recursively call the applyFilters method to handle nested conditions
76
- const joinField = `${alias}.${key}`;
77
- if (!this.isRelationJoined(selectQb, joinField)) selectQb.leftJoin(joinField, key);
78
- this.applyFilters(qb, primaryFilterObj, key, selectQb);
86
+ if (funcAlias) {
87
+ throw new BadRequestException(`Function alias ':${funcAlias}' is not valid on relation field '${rawField}'. It can only be applied to scalar fields.`);
88
+ }
89
+ const joinField = `${alias}.${rawField}`;
90
+ if (!this.isRelationJoined(selectQb, joinField)) selectQb.leftJoin(joinField, rawField);
91
+ this.applyFilters(qb, primaryFilterObj, rawField, selectQb);
79
92
  }
80
93
  });
81
94
  }
82
95
  }
83
96
 
84
- private buildOperatorQuery(qb: any, alias: string, field: string, normalizedPrimaryOperatorObj: any, operator: string) {
97
+ private buildOperatorQuery(qb: any, alias: string, field: string, normalizedPrimaryOperatorObj: any, operator: string, columnExpression?: string) {
85
98
  const uniqueFieldAlias = `${alias}_${field}_${Math.floor(Math.random() * 1000)}`;
99
+ const colExpr = columnExpression ?? `${alias}.${field}`;
86
100
  switch (operator) {
87
101
  case '$eq':
88
- qb.andWhere(`${alias}.${field} = :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$eq });
102
+ qb.andWhere(`${colExpr} = :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$eq });
89
103
  break;
90
104
  case '$eqi':
91
- qb.andWhere(`LOWER(${alias}.${field}) = :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$eqi.toLowerCase() });
105
+ qb.andWhere(`LOWER(${colExpr}) = :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$eqi.toLowerCase() });
92
106
  break;
93
107
  case '$ne':
94
- qb.andWhere(`${alias}.${field} != :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$ne });
108
+ qb.andWhere(`${colExpr} != :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$ne });
95
109
  break;
96
110
  case '$nei':
97
- qb.andWhere(`LOWER(${alias}.${field}) != :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$nei.toLowerCase() });
111
+ qb.andWhere(`LOWER(${colExpr}) != :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$nei.toLowerCase() });
98
112
  break;
99
113
  case '$gt':
100
- qb.andWhere(`${alias}.${field} > :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$gt });
114
+ qb.andWhere(`${colExpr} > :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$gt });
101
115
  break;
102
116
  case '$gte':
103
- qb.andWhere(`${alias}.${field} >= :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$gte });
117
+ qb.andWhere(`${colExpr} >= :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$gte });
104
118
  break;
105
119
  case '$lt':
106
- qb.andWhere(`${alias}.${field} < :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$lt });
120
+ qb.andWhere(`${colExpr} < :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$lt });
107
121
  break;
108
122
  case '$lte':
109
- qb.andWhere(`${alias}.${field} <= :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$lte });
123
+ qb.andWhere(`${colExpr} <= :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$lte });
110
124
  break;
111
125
  case '$in':
112
- qb.andWhere(`${alias}.${field} IN (:...${uniqueFieldAlias})`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$in });
126
+ qb.andWhere(`${colExpr} IN (:...${uniqueFieldAlias})`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$in });
113
127
  break;
114
128
  case '$notIn':
115
- qb.andWhere(`${alias}.${field} NOT IN (:...${uniqueFieldAlias})`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$notIn });
129
+ qb.andWhere(`${colExpr} NOT IN (:...${uniqueFieldAlias})`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$notIn });
116
130
  break;
117
131
  case '$contains':
118
- qb.andWhere(`${alias}.${field} LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$contains}%` });
132
+ qb.andWhere(`${colExpr} LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$contains}%` });
119
133
  break;
120
134
  case '$notContains':
121
- qb.andWhere(`${alias}.${field} NOT LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$notContains}%` });
135
+ qb.andWhere(`${colExpr} NOT LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$notContains}%` });
122
136
  break;
123
137
  case '$containsi':
124
- qb.andWhere(`LOWER(${alias}.${field}) LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$containsi.toLowerCase()}%` });
138
+ qb.andWhere(`LOWER(${colExpr}) LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$containsi.toLowerCase()}%` });
125
139
  break;
126
140
  case '$notContainsi':
127
- qb.andWhere(`LOWER(${alias}.${field}) NOT LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$notContainsi.toLowerCase()}%` });
141
+ qb.andWhere(`LOWER(${colExpr}) NOT LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$notContainsi.toLowerCase()}%` });
128
142
  break;
129
143
  case '$null':
130
- qb.andWhere(`${alias}.${field} IS NULL`);
144
+ qb.andWhere(`${colExpr} IS NULL`);
131
145
  break;
132
146
  case '$notNull':
133
- qb.andWhere(`${alias}.${field} IS NOT NULL`);
147
+ qb.andWhere(`${colExpr} IS NOT NULL`);
134
148
  break;
135
149
  case '$between':
136
- qb.andWhere(`${alias}.${field} BETWEEN :${uniqueFieldAlias}0 AND :${uniqueFieldAlias}1`, { [`${uniqueFieldAlias}0`]: normalizedPrimaryOperatorObj.$between[0], [`${uniqueFieldAlias}1`]: normalizedPrimaryOperatorObj.$between[1] });
150
+ qb.andWhere(`${colExpr} BETWEEN :${uniqueFieldAlias}0 AND :${uniqueFieldAlias}1`, { [`${uniqueFieldAlias}0`]: normalizedPrimaryOperatorObj.$between[0], [`${uniqueFieldAlias}1`]: normalizedPrimaryOperatorObj.$between[1] });
137
151
  break;
138
152
  case '$startsWith':
139
- qb.andWhere(`${alias}.${field} LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `${normalizedPrimaryOperatorObj.$startsWith}%` });
153
+ qb.andWhere(`${colExpr} LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `${normalizedPrimaryOperatorObj.$startsWith}%` });
140
154
  break;
141
155
  case '$startsWithi':
142
- qb.andWhere(`LOWER(${alias}.${field}) LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `${normalizedPrimaryOperatorObj.$startsWithi.toLowerCase()}%` });
156
+ qb.andWhere(`LOWER(${colExpr}) LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `${normalizedPrimaryOperatorObj.$startsWithi.toLowerCase()}%` });
143
157
  break;
144
158
  case '$endsWith':
145
- qb.andWhere(`${alias}.${field} LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$endsWith}` });
159
+ qb.andWhere(`${colExpr} LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$endsWith}` });
146
160
  break;
147
161
  case '$endsWithi':
148
- qb.andWhere(`LOWER(${alias}.${field}) LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$endsWithi.toLowerCase()}` });
162
+ qb.andWhere(`LOWER(${colExpr}) LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$endsWithi.toLowerCase()}` });
149
163
  break;
150
164
  default:
151
165
  throw new Error(`Operator ${operator} is not supported`);
@@ -588,6 +602,27 @@ export class CrudHelperService {
588
602
  }
589
603
  }
590
604
 
605
+ private getGroupFieldValues(
606
+ group: any,
607
+ groupByFields: string[],
608
+ groupAliasMap: Record<string, string>
609
+ ): Array<{ rawVal: any; alias: string; granularity?: string }> {
610
+ return groupByFields
611
+ .map(field => {
612
+ const parts = field.split(':');
613
+ const granularity = parts[1];
614
+ const alias = groupAliasMap[field] ?? this.sanitizeAlias(field.replace(/\./g, '_'));
615
+ const rawVal = group[alias] ?? group[field] ?? group[field.replace(/\./g, '_')];
616
+ return { rawVal, alias, granularity };
617
+ })
618
+ .filter(({ rawVal }) => rawVal !== undefined && rawVal !== null);
619
+ }
620
+
621
+ private normalizeGroupValue(value: any, granularity?: string): any {
622
+ if (!granularity) return value;
623
+ return this.formatGroupValue(value, 'YYYY-MM-DD');
624
+ }
625
+
591
626
  getGroupName(
592
627
  group: any,
593
628
  aggregateAliases: Set<string>,
@@ -595,22 +630,28 @@ export class CrudHelperService {
595
630
  groupAliasMap: Record<string, string>,
596
631
  groupFormatMap: Record<string, string | undefined>
597
632
  ): 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);
633
+ const fieldValues = this.getGroupFieldValues(group, groupByFields, groupAliasMap);
605
634
 
606
- if (orderedValues.length === 0) {
635
+ if (fieldValues.length === 0) {
607
636
  return Object.keys(group)
608
637
  .filter(key => !this.isAggregateFieldKey(key, aggregateAliases))
609
638
  .map(key => group[key])
610
639
  .join('_');
611
640
  }
612
641
 
613
- return orderedValues.join('_');
642
+ return fieldValues
643
+ .map(({ rawVal, alias }) => this.formatGroupValue(rawVal, groupFormatMap[alias]))
644
+ .join('_');
645
+ }
646
+
647
+ getGroupValue(
648
+ group: any,
649
+ groupByFields: string[],
650
+ groupAliasMap: Record<string, string>
651
+ ): any {
652
+ const fieldValues = this.getGroupFieldValues(group, groupByFields, groupAliasMap);
653
+ if (fieldValues.length === 1) return this.normalizeGroupValue(fieldValues[0].rawVal, fieldValues[0].granularity);
654
+ return fieldValues.map(({ rawVal, granularity }) => this.normalizeGroupValue(rawVal, granularity)).join('_');
614
655
  }
615
656
 
616
657
  createGroupRecords(group: any, aggregateAliases: Set<string>, groupData: any, groupByFields: string[], groupAliasMap: Record<string, string>, groupFormatMap: Record<string, string | undefined>) {
@@ -622,6 +663,7 @@ export class CrudHelperService {
622
663
  }
623
664
  createGroupMeta(group: any, aggregateAliases: Set<string>, groupByFields: string[], groupAliasMap: Record<string, string>, groupFormatMap: Record<string, string | undefined>) {
624
665
  const groupName = this.getGroupName(group, aggregateAliases, groupByFields, groupAliasMap, groupFormatMap);
666
+ const groupValue = this.getGroupValue(group, groupByFields, groupAliasMap);
625
667
  const groupAggregateValues = {}
626
668
  for (const key in group) {
627
669
  if (group.hasOwnProperty(key) && this.isAggregateFieldKey(key, aggregateAliases)) {
@@ -631,6 +673,7 @@ export class CrudHelperService {
631
673
  }
632
674
  return {
633
675
  groupName,
676
+ groupValue,
634
677
  ...groupAggregateValues
635
678
  };
636
679
  }
@@ -67,6 +67,7 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
67
67
  protected get discoveryService(): DiscoveryService {
68
68
  return this._discoveryService ??= this.moduleRef.get(DiscoveryService, { strict: false });
69
69
  }
70
+
70
71
  protected get settingService(): SettingService {
71
72
  return this._settingService ??= this.moduleRef.get(SettingService, { strict: false });
72
73
  }
@@ -128,8 +129,8 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
128
129
  });
129
130
  }
130
131
 
131
- private async validateAndTransformDto(field: FieldMetadata, dto: any, files: Express.Multer.File[], hasMediaFields: boolean, isPartialUpdate: boolean = false, isUpdate: boolean = false) {
132
- const fieldManager: FieldCrudManager = await this.fieldCrudManager(field, this.entityManager, isPartialUpdate, isUpdate);
132
+ private async validateAndTransformDto(field: FieldMetadata, dto: any, files: Express.Multer.File[], hasMediaFields: boolean, isPartialUpdate: boolean = false, isUpdate: boolean = false, entityId?: number) {
133
+ const fieldManager: FieldCrudManager = await this.fieldCrudManager(field, this.entityManager, isPartialUpdate, isUpdate, entityId);
133
134
  const validationErrors = fieldManager.validate(dto, files);
134
135
  const errors = (validationErrors instanceof Promise) ? await validationErrors : validationErrors;
135
136
  if (errors.length > 0) {
@@ -210,7 +211,7 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
210
211
  // 2. Loop through the fields with a switch statement
211
212
  // 3. Handle the fields based on field type
212
213
  for (const field of fieldsToProcess) {
213
- const transformed = await this.validateAndTransformDto(field, updateDto, files, hasMediaFields, isPartialUpdate, isUpdate);
214
+ const transformed = await this.validateAndTransformDto(field, updateDto, files, hasMediaFields, isPartialUpdate, isUpdate, id);
214
215
  updateDto = transformed.dto;
215
216
  hasMediaFields = transformed.hasMediaFields;
216
217
  }
@@ -220,6 +221,8 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
220
221
  const mergedEntity = this.repo.merge(entity, updateDto);
221
222
  const savedEntity = await this.repo.save(mergedEntity) as T;
222
223
 
224
+ //FIXME: Skip the many-to-many, and instead fire differential updates and avoid loading the entire association graph for the ids
225
+
223
226
  // 6. Save the media
224
227
  if (hasMediaFields) {
225
228
  await this.saveMedia(model, files, savedEntity);
@@ -286,7 +289,7 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
286
289
  }
287
290
  }
288
291
 
289
- private async fieldCrudManager(fieldMetadata: FieldMetadata, entityManager: EntityManager, isPartialUpdate: boolean = false, isUpdate: boolean = false) {
292
+ private async fieldCrudManager(fieldMetadata: FieldMetadata, entityManager: EntityManager, isPartialUpdate: boolean = false, isUpdate: boolean = false, entityId?: number) {
290
293
  const commonOptions = { required: fieldMetadata.required && !isPartialUpdate, fieldName: fieldMetadata.name, isUpdate };
291
294
  switch (fieldMetadata.type) {
292
295
  case SolidFieldType.shortText: {
@@ -377,6 +380,7 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
377
380
  entityManager,
378
381
  inverseFieldName: fieldMetadata.relationCoModelFieldName,
379
382
  inverseRelationCoModelFieldName: fieldMetadata.name,
383
+ entityId,
380
384
  }
381
385
  return new OneToManyRelationFieldCrudManager(oneToManyOptions);
382
386
  }
@@ -389,6 +393,7 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
389
393
  isInverseSide: false,
390
394
  entityManager,
391
395
  fieldName: fieldMetadata.name,
396
+ entityId,
392
397
  }
393
398
  return new ManyToManyRelationFieldCrudManager(manyToManyOptions);
394
399
  }
@@ -401,6 +406,7 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
401
406
  entityManager,
402
407
  fieldName: fieldMetadata.relationCoModelFieldName,
403
408
  relationCoModelFieldName: fieldMetadata.name,
409
+ entityId,
404
410
  }
405
411
  return new ManyToManyRelationFieldCrudManager(inverseManyToManyOptions);
406
412
  }
@@ -1290,77 +1290,6 @@ export class FieldMetadataService implements OnApplicationBootstrap {
1290
1290
  return fieldObject;
1291
1291
  }
1292
1292
 
1293
- // async resolveS3Url(resolveS3UrlDto: ResolveS3UrlDto) {
1294
-
1295
- // const { modelName, fieldName, fieldValue, s3KeyFieldName } = resolveS3UrlDto;
1296
-
1297
- // // ------------------------------------------------
1298
- // // 1. Load model metadata
1299
- // // ------------------------------------------------
1300
- // const modelRepo = this.dataSource.getRepository(ModelMetadata);
1301
- // const model = await modelRepo.findOne({
1302
- // where: { singularName: modelName },
1303
- // relations: ['fields']
1304
- // });
1305
-
1306
- // if (!model) {
1307
- // throw new NotFoundException(`Model ${modelName} not found`);
1308
- // }
1309
-
1310
- // // ------------------------------------------------
1311
- // // 2. Validate the field we are filtering by
1312
- // // ------------------------------------------------
1313
- // const filterFieldMeta = model.fields.find(f => f.name === fieldName);
1314
- // if (!filterFieldMeta) {
1315
- // throw new NotFoundException(
1316
- // `Field ${fieldName} not found in model ${modelName}`
1317
- // );
1318
- // }
1319
-
1320
- // // ------------------------------------------------
1321
- // // 3. Load the actual entity repository
1322
- // // ------------------------------------------------
1323
- // const entityRepo = this.dataSource.getRepository(model.singularName);
1324
-
1325
- // // ------------------------------------------------
1326
- // // 4. Query using fieldName = fieldValue
1327
- // // ------------------------------------------------
1328
- // const record = await entityRepo.findOne({
1329
- // where: { [fieldName]: fieldValue }
1330
- // });
1331
-
1332
- // if (!record) {
1333
- // throw new NotFoundException(
1334
- // `${modelName} record not found for ${fieldName}="${fieldValue}"`
1335
- // );
1336
- // }
1337
-
1338
- // // ------------------------------------------------
1339
- // // 5. Extract S3 key from s3KeyFieldName
1340
- // // ------------------------------------------------
1341
- // const s3Key = record[s3KeyFieldName];
1342
-
1343
- // if (!s3Key) {
1344
- // throw new NotFoundException(
1345
- // `Field "${s3KeyFieldName}" has no value in ${modelName}.${fieldName}="${fieldValue}"`
1346
- // );
1347
- // }
1348
-
1349
- // // ------------------------------------------------
1350
- // // 6. Generate signed or public URL
1351
- // // ------------------------------------------------
1352
- // let url = "";
1353
-
1354
- // // TODO - get
1355
- // if (resolveS3UrlDto.isPrivate == "true") {
1356
- // const expiryInSeconds = 60 * 60;
1357
- // url = await this.fileService.getSignedUrl(s3Key, expiryInSeconds, resolveS3UrlDto.bucketName);
1358
- // } else {
1359
- // url = `https://${resolveS3UrlDto.bucketName}.s3.${this.configService.get('S3_AWS_REGION_NAME')}.amazonaws.com/${s3Key}`;
1360
- // }
1361
- // return { url: url }
1362
- // }
1363
-
1364
1293
  async resolveS3Url(resolveS3UrlDto: ResolveS3UrlDto) {
1365
1294
  let url = "";
1366
1295
  const normalizedKey = this.normalizeS3Key(resolveS3UrlDto.s3Key);
@@ -9,25 +9,17 @@ import { IFileService, WriteOptions, CopyOptions, UrlOptions } from './file-serv
9
9
  /**
10
10
  * Disk-based implementation of IFileService.
11
11
  * Handles file operations on the local filesystem.
12
+ * Callers are responsible for providing complete paths (use DiskStoragePathBuilder for primary storage paths).
12
13
  */
13
14
  @Injectable()
14
15
  export class DiskFileService implements IFileService {
15
16
  private readonly logger = new Logger(DiskFileService.name);
16
- private readonly fileStorageDir: string;
17
17
  private readonly baseUrl: string;
18
18
 
19
19
  constructor() {
20
- this.fileStorageDir = process.env.AB_MEDIA_FILE_STORAGE_DIR || 'uploads';
21
20
  this.baseUrl = process.env.BASE_URL || '';
22
21
  }
23
22
 
24
- private resolvePath(filePath: string): string {
25
- if (path.isAbsolute(filePath)) {
26
- return filePath;
27
- }
28
- return `${this.fileStorageDir}/${filePath}`;
29
- }
30
-
31
23
  /**
32
24
  * Read file contents as Buffer
33
25
  */
@@ -50,10 +42,9 @@ export class DiskFileService implements IFileService {
50
42
  * @returns Public URL of the written file
51
43
  */
52
44
  async write(filePath: string, data: Buffer | string, options?: WriteOptions): Promise<string> {
53
- const resolvedPath = this.resolvePath(filePath);
54
- await this.ensureDirectoryExists(resolvedPath);
55
- await fsPromises.writeFile(resolvedPath, data);
56
- return `${this.baseUrl}/${resolvedPath}`;
45
+ await this.ensureDirectoryExists(filePath);
46
+ await fsPromises.writeFile(filePath, data);
47
+ return `${this.baseUrl}/${filePath}`;
57
48
  }
58
49
 
59
50
  /**
@@ -62,12 +53,11 @@ export class DiskFileService implements IFileService {
62
53
  * @returns Public URL of the written file
63
54
  */
64
55
  async writeStream(filePath: string, stream: Readable, options?: WriteOptions): Promise<string> {
65
- const resolvedPath = this.resolvePath(filePath);
66
- await this.ensureDirectoryExists(resolvedPath);
67
- const writeStream = fs.createWriteStream(resolvedPath);
56
+ await this.ensureDirectoryExists(filePath);
57
+ const writeStream = fs.createWriteStream(filePath);
68
58
  await pipeline(stream, writeStream);
69
- this.logger.debug(`File saved via stream: ${resolvedPath}`);
70
- return `${this.baseUrl}/${resolvedPath}`;
59
+ this.logger.debug(`File saved via stream: ${filePath}`);
60
+ return `${this.baseUrl}/${filePath}`;
71
61
  }
72
62
 
73
63
  /**
@@ -2,3 +2,4 @@ export * from './file-service.interface';
2
2
  export * from './disk-file.service';
3
3
  export * from './s3-file.service';
4
4
  export * from './file-service.factory';
5
+ export * from './storage-path-builder';
@@ -0,0 +1,56 @@
1
+ import { Injectable, Provider } from '@nestjs/common';
2
+ import * as path from 'path';
3
+ import { DEFAULT_MEDIA_FILE_STORAGE_DIR } from '../settings/default-settings-provider.service';
4
+
5
+ export const FILE_STORAGE_PATH_BUILDER = Symbol('FILE_STORAGE_PATH_BUILDER');
6
+
7
+ export interface IStoragePathBuilder {
8
+ build(fileName: string): string;
9
+ }
10
+
11
+ @Injectable()
12
+ export class DiskStoragePathBuilder implements IStoragePathBuilder {
13
+ private readonly base: string;
14
+
15
+ constructor() {
16
+ this.base = process.env.AB_MEDIA_FILE_STORAGE_DIR || DEFAULT_MEDIA_FILE_STORAGE_DIR;
17
+ }
18
+
19
+ build(fileName: string): string {
20
+ if (path.isAbsolute(fileName) || fileName.startsWith(`${this.base}/`)) {
21
+ return fileName;
22
+ }
23
+ return `${this.base}/${fileName}`;
24
+ }
25
+ }
26
+
27
+ @Injectable()
28
+ export class S3StoragePathBuilder implements IStoragePathBuilder {
29
+ private readonly bucket: string;
30
+
31
+ constructor() {
32
+ this.bucket = process.env.S3_DEFAULT_BUCKET ?? '';
33
+ }
34
+
35
+ build(fileName: string): string {
36
+ if (fileName.includes(':')) {
37
+ return fileName;
38
+ }
39
+ return `${this.bucket}:${fileName}`;
40
+ }
41
+ }
42
+
43
+ export const StoragePathBuilderFactory: Provider = {
44
+ provide: FILE_STORAGE_PATH_BUILDER,
45
+ useFactory: (disk: DiskStoragePathBuilder, s3: S3StoragePathBuilder): IStoragePathBuilder => {
46
+ const defaultService = process.env.DEFAULT_FILE_SERVICE ?? 'disk';
47
+ switch (defaultService.toLowerCase()) {
48
+ case 's3':
49
+ return s3;
50
+ case 'disk':
51
+ default:
52
+ return disk;
53
+ }
54
+ },
55
+ inject: [DiskStoragePathBuilder, S3StoragePathBuilder],
56
+ };
@@ -2,6 +2,8 @@ import { forwardRef, Inject, Injectable, NotFoundException } from '@nestjs/commo
2
2
  import { ModuleRef } from "@nestjs/core";
3
3
  import { InjectEntityManager } from '@nestjs/typeorm';
4
4
  import { EntityManager, In } from 'typeorm';
5
+ import * as path from 'path';
6
+ import { DEFAULT_MEDIA_FILE_STORAGE_DIR } from "src/services/settings/default-settings-provider.service";
5
7
  import type { SolidCoreSetting } from "src/services/settings/default-settings-provider.service";
6
8
 
7
9
  import { ConfigService } from '@nestjs/config';
@@ -52,7 +54,7 @@ export class MediaService extends CRUDService<Media> {
52
54
 
53
55
  for (const media of data.records) {
54
56
  if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.Filesystem) {
55
- media.relativeUri = `${this.settingService.getConfigValue<SolidCoreSetting>("baseUrl")}/${await this.getFileSysytemFullFilePath(media.relativeUri)}`;
57
+ media.relativeUri = `${this.settingService.getConfigValue<SolidCoreSetting>("baseUrl")}/${this.getFullFilePathForDisk(media.relativeUri)}`;
56
58
  } else if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.AwsS3) {
57
59
  media.relativeUri = this.getAwsS3FullFilePath(
58
60
  media.relativeUri,
@@ -63,7 +65,7 @@ export class MediaService extends CRUDService<Media> {
63
65
  }
64
66
  // data.records.forEach((media: Media) => {
65
67
  // if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.Filesystem) {
66
- // media.relativeUri = `${process.env.BASE_URL}/${await this.getFileSysytemFullFilePath(media.relativeUri)}`;
68
+ // media.relativeUri = `${process.env.BASE_URL}/${this.getFileSysytemFullFilePath(media.relativeUri)}`;
67
69
  // } else if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.AwsS3) {
68
70
  // media.relativeUri = this.getAwsS3FullFilePath(
69
71
  // media.relativeUri,
@@ -78,7 +80,7 @@ export class MediaService extends CRUDService<Media> {
78
80
  for (const group of data.groupRecords) {
79
81
  for (const media of group.groupData.records) {
80
82
  if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.Filesystem) {
81
- media.relativeUri = `${this.settingService.getConfigValue<SolidCoreSetting>("baseUrl")}/${await this.getFileSysytemFullFilePath(media.relativeUri)}`;
83
+ media.relativeUri = `${this.settingService.getConfigValue<SolidCoreSetting>("baseUrl")}/${this.getFullFilePathForDisk(media.relativeUri)}`;
82
84
  }
83
85
  else if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.AwsS3) {
84
86
  media.relativeUri = this.getAwsS3FullFilePath(media.relativeUri, media.mediaStorageProviderMetadata.bucketName, media.mediaStorageProviderMetadata.region);
@@ -129,7 +131,7 @@ export class MediaService extends CRUDService<Media> {
129
131
 
130
132
  switch (createDto.mediaStorageProviderMetadata.type) {
131
133
  case MediaStorageProviderType.Filesystem:
132
- const fileStoragePath = await this.getFileSysytemFullFilePath(this.getFileName(file));
134
+ const fileStoragePath = this.getFullFilePathForDisk(this.getFileName(file));
133
135
  await this.diskFileService.copy(file.path, fileStoragePath);
134
136
  createDto['relativeUri'] = this.getFileName(file);
135
137
  break;
@@ -189,9 +191,13 @@ export class MediaService extends CRUDService<Media> {
189
191
  }
190
192
  //TODO: Move this to a app builder config
191
193
 
192
- private async getFileSysytemFullFilePath(fileName: string): Promise<string> {
193
- const fileStorageDir = this.settingService.getConfigValue<SolidCoreSetting>("fileStorageDir");
194
- return `${fileStorageDir}/${fileName}`;
194
+ private getFullFilePathForDisk(fileName: string): string {
195
+ const base = this.settingService.getConfigValue<SolidCoreSetting>("fileStorageDir")
196
+ || DEFAULT_MEDIA_FILE_STORAGE_DIR;
197
+ if (path.isAbsolute(fileName) || fileName.startsWith(`${base}/`)) {
198
+ return fileName;
199
+ }
200
+ return `${base}/${fileName}`;
195
201
  }
196
202
 
197
203
  private getAwsS3FullFilePath(awsMediaurl: string, bucketName: string, regionName: string): string {
@@ -43,7 +43,7 @@ export class FileS3StorageProvider<T> implements MediaStorageProvider<T> {
43
43
  const region = this.getEffectiveRegion(storageMeta.region);
44
44
  if (storageMeta.isPublic === false) {
45
45
  // Generate signed URL
46
- const expiryInSeconds = (storageMeta.signedUrlExpiry ?? 60) * 60; // default 5 min
46
+ const expiryInSeconds = (storageMeta.signedUrlExpiry ?? 60) * 60;
47
47
  m['_full_url'] = await this.s3FileService.getUrl(`${storageMeta?.bucketName}:${m.relativeUri}`, { expiresIn: expiryInSeconds, region });
48
48
  } else {
49
49
  // Public S3 or local filesystem: use normal URL