adminizer 4.6.0 → 4.7.0-build.221

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 (335) hide show
  1. package/Readme.md +12 -0
  2. package/assets/RelationDialogStack-CnHFOYyR.css +1 -0
  3. package/assets/RelationDialogStack-DI2bZxQX.js +20 -0
  4. package/assets/add-4213XScp.js +1 -0
  5. package/assets/add-group--F1XxfJ1.js +1 -0
  6. package/assets/add-group-form-DKRXC2n-.js +1 -0
  7. package/assets/add-user-b9HfMq7z.js +1 -0
  8. package/assets/add-user-form-UXAToWQd.js +1 -0
  9. package/assets/app-Clx8f5hd.css +1 -0
  10. package/assets/app-layout-C02ja4OI.js +1 -0
  11. package/assets/app-shell-D8wtkECO.js +1 -0
  12. package/assets/app-sidebar-BR5_zwxj.js +1 -0
  13. package/assets/app-sidebar-header-BZbIBQjq.js +1 -0
  14. package/assets/app.js +59 -7990
  15. package/assets/breadcrumbs-CODaqVkB.js +1 -0
  16. package/assets/catalog-6ZZjWdFs.js +6 -0
  17. package/assets/dashboard-CftAdKiH.js +3 -0
  18. package/assets/date-picker-DSOkNiNo.js +1 -0
  19. package/assets/del-modal-Cd4M7af8.js +1 -0
  20. package/assets/dynamic-controls-BsrJhPDY.js +1 -0
  21. package/assets/geo-json-CzR5JBXL.js +1 -0
  22. package/assets/handsontable-BI98OF5R.js +336 -0
  23. package/assets/history-DLHSU7Ak.js +1 -0
  24. package/assets/home-NcQ7nnU0.js +2 -0
  25. package/assets/icon-C2JNaLxH.js +1 -0
  26. package/assets/init-user-D2HSyXup.js +1 -0
  27. package/assets/input-error-CH2caTtO.js +1 -0
  28. package/assets/jsoneditor-DVNQmA6G.js +3869 -0
  29. package/assets/list-CFU0-TgN.js +4 -0
  30. package/assets/login-DFSLEO17.js +1 -0
  31. package/assets/manifest.json +246 -273
  32. package/assets/material-icon-Bx9pGQiQ.js +1 -0
  33. package/assets/media-manager-WcKe1EvD.js +6 -0
  34. package/assets/module-CT7PC6pc.js +1 -0
  35. package/assets/monaco-Bl4zdWCn.js +11 -0
  36. package/assets/nav-footer-BiSQkPt4.js +1 -0
  37. package/assets/nav-main-rTH0PfDw.js +1 -0
  38. package/assets/nav-user-Bj6cWs0a.js +1 -0
  39. package/assets/notification-Bi-PX87D.js +20 -0
  40. package/assets/pagination-render-AKJvTIRA.js +1 -0
  41. package/assets/register-BTP4uxsK.js +1 -0
  42. package/assets/ru-eW0WKkga.js +1 -0
  43. package/assets/theme-switcher-Dm8EOGzx.js +1 -0
  44. package/assets/toast-editor-DOg_gvSp.js +668 -0
  45. package/assets/user-filters-list-YUfR4XMI.js +1 -0
  46. package/assets/user-info-BgSbT02v.js +1 -0
  47. package/assets/user-menu-content-TQTXHIv6.js +1 -0
  48. package/assets/{vanilla-picker-l5rcX3cq.js → vanilla-picker-B3LgXoKV.js} +1 -7
  49. package/assets/welcome-vLwTu1R6.js +1 -0
  50. package/controllers/add.d.ts +1 -1
  51. package/controllers/add.js +20 -26
  52. package/controllers/addGroup.d.ts +1 -1
  53. package/controllers/addGroup.js +7 -15
  54. package/controllers/addUser.d.ts +1 -1
  55. package/controllers/addUser.js +10 -16
  56. package/controllers/catalog/Catalog.d.ts +1 -1
  57. package/controllers/catalog/Catalog.js +1 -10
  58. package/controllers/catalog/FrontendCatalogAdapter.d.ts +3 -3
  59. package/controllers/catalog/FrontendCatalogAdapter.js +0 -6
  60. package/controllers/catalog/FrontentCatalogAdapter.d.ts +3 -3
  61. package/controllers/catalog/FrontentCatalogAdapter.js +0 -4
  62. package/controllers/ckeditorUpload.js +2 -18
  63. package/controllers/dashboard.d.ts +1 -1
  64. package/controllers/dashboard.js +2 -5
  65. package/controllers/edit.js +24 -37
  66. package/controllers/editGroup.d.ts +1 -1
  67. package/controllers/editGroup.js +10 -21
  68. package/controllers/editUser.d.ts +1 -1
  69. package/controllers/editUser.js +11 -23
  70. package/controllers/exportData.d.ts +6 -0
  71. package/controllers/exportData.js +315 -0
  72. package/controllers/feed.d.ts +6 -0
  73. package/controllers/feed.js +73 -0
  74. package/controllers/feedback/feedbackController.d.ts +1 -1
  75. package/controllers/feedback/feedbackController.js +5 -7
  76. package/controllers/filter-fields/columns.d.ts +11 -0
  77. package/controllers/filter-fields/columns.js +106 -0
  78. package/controllers/filter-fields/filterFields.d.ts +10 -0
  79. package/controllers/filter-fields/filterFields.js +141 -0
  80. package/controllers/filter-fields/getAllUserFilters.d.ts +6 -0
  81. package/controllers/filter-fields/getAllUserFilters.js +115 -0
  82. package/controllers/filter-fields/groups.d.ts +5 -0
  83. package/controllers/filter-fields/groups.js +26 -0
  84. package/controllers/filter-fields/savedFilters.d.ts +25 -0
  85. package/controllers/filter-fields/savedFilters.js +317 -0
  86. package/controllers/getAllWidgets.d.ts +1 -1
  87. package/controllers/getAllWidgets.js +1 -9
  88. package/controllers/history-actions/HistoryController.js +6 -31
  89. package/controllers/initUser.js +6 -5
  90. package/controllers/inlineUpdate.d.ts +7 -0
  91. package/controllers/inlineUpdate.js +148 -0
  92. package/controllers/list.d.ts +1 -1
  93. package/controllers/list.js +383 -70
  94. package/controllers/listUserFilters.d.ts +5 -0
  95. package/controllers/listUserFilters.js +26 -0
  96. package/controllers/login.js +2 -2
  97. package/controllers/media-manager/mediaManagerApi.js +0 -9
  98. package/controllers/navigation/edit.js +3 -9
  99. package/controllers/notifications/NotificationController.d.ts +1 -1
  100. package/controllers/notifications/NotificationController.js +12 -36
  101. package/controllers/register.js +10 -9
  102. package/controllers/remove.js +10 -19
  103. package/controllers/timezones.d.ts +1 -1
  104. package/controllers/timezones.js +0 -6
  105. package/controllers/userApiKey.d.ts +7 -0
  106. package/controllers/userApiKey.js +36 -0
  107. package/controllers/view.js +14 -26
  108. package/controllers/welcome.d.ts +1 -1
  109. package/controllers/welcome.js +0 -4
  110. package/controllers/widgets/Action.d.ts +1 -1
  111. package/controllers/widgets/Action.js +1 -9
  112. package/controllers/widgets/Custom.d.ts +1 -1
  113. package/controllers/widgets/Custom.js +1 -9
  114. package/controllers/widgets/Info.d.ts +1 -1
  115. package/controllers/widgets/Info.js +1 -9
  116. package/controllers/widgets/filterInfo.d.ts +1 -0
  117. package/controllers/widgets/filterInfo.js +44 -0
  118. package/controllers/widgets/switch.d.ts +1 -1
  119. package/controllers/widgets/switch.js +1 -9
  120. package/controllers/widgetsDB.d.ts +1 -1
  121. package/controllers/widgetsDB.js +2 -8
  122. package/helpers/accessRightsHelper.d.ts +1 -1
  123. package/helpers/accessRightsHelper.js +5 -4
  124. package/helpers/apiKeyHelper.d.ts +5 -0
  125. package/helpers/apiKeyHelper.js +8 -0
  126. package/helpers/configHelper.js +12 -15
  127. package/helpers/controllerHelper.d.ts +14 -21
  128. package/helpers/controllerHelper.js +50 -83
  129. package/helpers/fieldsHelper.js +3 -3
  130. package/helpers/filterDatetimeHelper.d.ts +5 -0
  131. package/helpers/filterDatetimeHelper.js +84 -0
  132. package/helpers/inertiaActionsHelper.d.ts +2 -2
  133. package/helpers/inertiaActionsHelper.js +5 -4
  134. package/helpers/inertiaAddHelper.d.ts +3 -414
  135. package/helpers/inertiaAddHelper.js +39 -15
  136. package/helpers/inertiaGroupHelper.d.ts +2 -2
  137. package/helpers/inertiaGroupHelper.js +3 -3
  138. package/helpers/inertiaListHelper.d.ts +7 -3
  139. package/helpers/inertiaListHelper.js +26 -12
  140. package/helpers/inertiaMenuHelper.d.ts +1 -0
  141. package/helpers/inertiaMenuHelper.js +15 -3
  142. package/helpers/inertiaUserHelper.d.ts +3 -2
  143. package/helpers/inertiaUserHelper.js +7 -3
  144. package/helpers/menuHelper.d.ts +5 -2
  145. package/helpers/menuHelper.js +64 -12
  146. package/helpers/viteHelper.js +1 -2
  147. package/index.d.ts +6 -2
  148. package/index.js +6 -2
  149. package/interfaces/adminpanelConfig.d.ts +126 -39
  150. package/interfaces/internalModelAccess.d.ts +37 -0
  151. package/interfaces/internalModelAccess.js +1 -0
  152. package/interfaces/listQueryBuilder.d.ts +22 -0
  153. package/interfaces/listQueryBuilder.js +1 -0
  154. package/interfaces/queryCriteria.d.ts +54 -0
  155. package/interfaces/queryCriteria.js +1 -0
  156. package/interfaces/types.d.ts +1 -13
  157. package/lib/Adminizer.d.ts +4 -2
  158. package/lib/Adminizer.js +29 -10
  159. package/lib/DataAccessor.d.ts +8 -5
  160. package/lib/DataAccessor.js +89 -42
  161. package/lib/I18n.d.ts +5 -1
  162. package/lib/I18n.js +45 -19
  163. package/lib/MiddlewareManager.d.ts +8 -0
  164. package/lib/MiddlewareManager.js +53 -0
  165. package/lib/ai-assistant/OpenAiModelService.d.ts +3 -3
  166. package/lib/ai-assistant/OpenAiModelService.js +23 -24
  167. package/lib/catalog/Navigation.d.ts +1 -0
  168. package/lib/catalog/Navigation.js +12 -11
  169. package/lib/controls/jsoneditor/JsonEditor.d.ts +1 -2
  170. package/lib/controls/markdown/ToastUiEditor.d.ts +1 -2
  171. package/lib/feedback/FeedbackHandler.js +2 -2
  172. package/lib/filters/ConditionValidator.d.ts +107 -0
  173. package/lib/filters/ConditionValidator.js +483 -0
  174. package/lib/filters/CustomFilterHandler.d.ts +122 -0
  175. package/lib/filters/CustomFilterHandler.js +131 -0
  176. package/lib/filters/FilterCustomFieldHandler.d.ts +2 -0
  177. package/lib/filters/FilterCustomFieldHandler.js +2 -0
  178. package/lib/filters/FilterService.d.ts +82 -0
  179. package/lib/filters/FilterService.js +323 -0
  180. package/lib/filters/index.d.ts +6 -0
  181. package/lib/filters/index.js +4 -0
  182. package/lib/history-actions/AbstractHistoryAdapter.d.ts +5 -4
  183. package/lib/history-actions/AbstractHistoryAdapter.js +45 -30
  184. package/lib/history-actions/DefaultHistoryAdapter.d.ts +2 -0
  185. package/lib/history-actions/DefaultHistoryAdapter.js +12 -6
  186. package/lib/history-actions/HistoryHandler.js +1 -1
  187. package/lib/list-query-builder/ListQueryBuilder.d.ts +117 -0
  188. package/lib/list-query-builder/ListQueryBuilder.js +707 -0
  189. package/lib/media-manager/AbstractMediaManager.d.ts +9 -8
  190. package/lib/media-manager/DefaultMediaManager.d.ts +2 -0
  191. package/lib/media-manager/DefaultMediaManager.js +27 -15
  192. package/lib/media-manager/Items.d.ts +4 -1
  193. package/lib/media-manager/Items.js +62 -59
  194. package/lib/media-manager/MediaManagerHandler.js +1 -1
  195. package/lib/media-manager/helpers/MediaManagerHelper.d.ts +5 -1
  196. package/lib/media-manager/helpers/MediaManagerHelper.js +38 -4
  197. package/lib/model/AbstractModel.d.ts +33 -25
  198. package/lib/model/AbstractModel.js +54 -2
  199. package/lib/model/InternalModelAccessFactory.d.ts +18 -0
  200. package/lib/model/InternalModelAccessFactory.js +82 -0
  201. package/lib/model/ModelHandler.d.ts +6 -0
  202. package/lib/model/ModelHandler.js +14 -0
  203. package/lib/model/adapter/sequelize.d.ts +32 -10
  204. package/lib/model/adapter/sequelize.js +409 -62
  205. package/lib/model/adapter/typeorm.d.ts +62 -0
  206. package/lib/model/adapter/typeorm.js +777 -0
  207. package/lib/model/internalModelAccessToken.d.ts +1 -0
  208. package/lib/model/internalModelAccessToken.js +1 -0
  209. package/lib/model/internalModelRepository.d.ts +3 -0
  210. package/lib/model/internalModelRepository.js +4 -0
  211. package/lib/notifications/AbstractNotificationService.d.ts +3 -0
  212. package/lib/notifications/AbstractNotificationService.js +42 -36
  213. package/lib/notifications/GeneralNotificationService.js +37 -40
  214. package/lib/notifications/SystemNotificationService.js +33 -36
  215. package/lib/requestProcessor.js +0 -3
  216. package/lib/ui-i18n/getUiTranslations.d.ts +1 -0
  217. package/lib/ui-i18n/getUiTranslations.js +3 -0
  218. package/lib/ui-i18n/uiTranslationKeys.d.ts +6 -0
  219. package/lib/ui-i18n/uiTranslationKeys.js +268 -0
  220. package/lib/widgets/widgetHandler.d.ts +7 -2
  221. package/lib/widgets/widgetHandler.js +98 -18
  222. package/models/FilterAP.d.ts +116 -0
  223. package/models/FilterAP.js +68 -0
  224. package/models/FilterColumnAP.d.ts +32 -0
  225. package/models/FilterColumnAP.js +21 -0
  226. package/models/GroupAP.d.ts +1 -1
  227. package/models/HistoryActionsAP.d.ts +3 -3
  228. package/models/MediaManagerMetaAP.d.ts +1 -1
  229. package/models/NavigationAP.d.ts +1 -1
  230. package/models/UserAP.d.ts +6 -1
  231. package/models/UserAP.js +4 -0
  232. package/package.json +39 -32
  233. package/policies/authPolicies.d.ts +12 -0
  234. package/policies/authPolicies.js +104 -0
  235. package/policies/permissionResolvers.d.ts +12 -0
  236. package/policies/permissionResolvers.js +52 -0
  237. package/services/FeedService.d.ts +70 -0
  238. package/services/FeedService.js +334 -0
  239. package/system/Router.d.ts +1 -0
  240. package/system/Router.js +136 -59
  241. package/system/bindAccessRights.js +11 -30
  242. package/system/bindAuthorization.js +18 -18
  243. package/system/bindCors.js +16 -1
  244. package/system/bindCustomFilterHandlers.d.ts +2 -0
  245. package/system/bindCustomFilterHandlers.js +4 -0
  246. package/system/bindInertia.js +18 -61
  247. package/system/bindReqFunctions.js +10 -2
  248. package/system/bindTranslations.js +7 -3
  249. package/system/buildInternalModelAccess.d.ts +4 -0
  250. package/system/buildInternalModelAccess.js +59 -0
  251. package/system/defaults.js +18 -14
  252. package/system/routePolicies.d.ts +2 -0
  253. package/system/routePolicies.js +29 -0
  254. package/translations/en.json +151 -39
  255. package/translations/ru.json +211 -64
  256. package/ui/components/handsontable.js +13 -27
  257. package/ui/components/ui/dialog-stack.js +1 -3
  258. package/ui/components/ui/popover.js +1 -1
  259. package/ui/components/ui/sidebar.js +1 -1
  260. package/assets/RelationDialogStack-D9UVEF9s.js +0 -407
  261. package/assets/RelationDialogStack-DV8OITPR.css +0 -4
  262. package/assets/add-BlvzWQKF.js +0 -1
  263. package/assets/add-group-1Uoq9NWe.js +0 -1
  264. package/assets/add-group-form-B01gwDGu.js +0 -1
  265. package/assets/add-user-BQCAsaRy.js +0 -1
  266. package/assets/add-user-form-Cgwc9JkI.js +0 -1
  267. package/assets/app-DaOMy1nl.css +0 -72
  268. package/assets/app-layout-CAm60qoE.js +0 -1
  269. package/assets/app-shell-QWCtn9ye.js +0 -1
  270. package/assets/app-sidebar-CfaYbnSy.js +0 -1
  271. package/assets/app-sidebar-header-BsPFDhZ8.js +0 -1
  272. package/assets/breadcrumbs-CZZirDj8.js +0 -1
  273. package/assets/catalog-k5myk7fk.js +0 -6
  274. package/assets/dashboard-BkL9ybBw.js +0 -3
  275. package/assets/date-picker-B5AZ_7Q9.js +0 -1
  276. package/assets/del-modal-BoyXZloG.js +0 -1
  277. package/assets/dynamic-controls-k61T9W2P.js +0 -1
  278. package/assets/form-1WGY0YgG.js +0 -1
  279. package/assets/form-DJapG3qF.js +0 -1
  280. package/assets/geo-json-DsMUHo0l.js +0 -4
  281. package/assets/handsontable-PxYdjN8c.js +0 -298
  282. package/assets/history-CluyR-yJ.js +0 -1
  283. package/assets/home-Dug9zBzM.js +0 -2
  284. package/assets/icon-DfDLMG5f.js +0 -1
  285. package/assets/init-user-CF80MEnT.js +0 -1
  286. package/assets/input-error-DuuqFgZa.js +0 -1
  287. package/assets/jsoneditor-De1FvcYu.js +0 -3882
  288. package/assets/list-CuexshNt.js +0 -1
  289. package/assets/list-table-BsEWjTKY.js +0 -22
  290. package/assets/login-eDemYzN-.js +0 -1
  291. package/assets/material-icon-DMT8KdmE.js +0 -1
  292. package/assets/media-manager-DfYa_mT_.js +0 -14
  293. package/assets/module-Cd4Tm6uq.js +0 -1
  294. package/assets/monaco-BhBYtg3d.js +0 -11
  295. package/assets/nav-footer-D3olYxtz.js +0 -1
  296. package/assets/nav-main-BctqyzeV.js +0 -1
  297. package/assets/nav-user-Bn1Lt-BZ.js +0 -1
  298. package/assets/notification-D64qvni-.js +0 -27
  299. package/assets/pagination-render-DxD78ahJ.js +0 -1
  300. package/assets/register-349PwYbe.js +0 -1
  301. package/assets/theme-switcher-DfExsHW2.js +0 -1
  302. package/assets/toast-editor-ESK5iWPx.js +0 -845
  303. package/assets/user-info-DtBK5Kh8.js +0 -1
  304. package/assets/user-menu-content-0XYOCMaA.js +0 -1
  305. package/assets/welcome-PDp-gUei.js +0 -1
  306. package/controllers/form.d.ts +0 -1
  307. package/controllers/form.js +0 -86
  308. package/helpers/formHelper.d.ts +0 -6
  309. package/helpers/formHelper.js +0 -38
  310. package/helpers/inertiaFromHelper.d.ts +0 -11
  311. package/helpers/inertiaFromHelper.js +0 -98
  312. package/helpers/runtimeMode.d.ts +0 -1
  313. package/helpers/runtimeMode.js +0 -3
  314. package/lib/PolicyManager.d.ts +0 -8
  315. package/lib/PolicyManager.js +0 -53
  316. package/lib/datatable/NodeTable.d.ts +0 -49
  317. package/lib/datatable/NodeTable.js +0 -207
  318. package/lib/model/adapter/waterline.d.ts +0 -32
  319. package/lib/model/adapter/waterline.js +0 -180
  320. package/migrations/index.d.ts +0 -3
  321. package/migrations/index.js +0 -4
  322. package/migrations/knex/20250106000001_adminizer_init.d.ts +0 -8
  323. package/migrations/knex/20250106000001_adminizer_init.js +0 -164
  324. package/migrations/umzug/0001.d.ts +0 -7
  325. package/migrations/umzug/0001.js +0 -121
  326. package/migrations/umzugExports.d.ts +0 -2
  327. package/migrations/umzugExports.js +0 -9
  328. package/system/bindForms.d.ts +0 -2
  329. package/system/bindForms.js +0 -20
  330. /package/{policies → middlewares}/addMessages.d.ts +0 -0
  331. /package/{policies → middlewares}/addMessages.js +0 -0
  332. /package/{policies → middlewares}/checkAuth.d.ts +0 -0
  333. /package/{policies → middlewares}/checkAuth.js +0 -0
  334. /package/{policies → middlewares}/setLocale.d.ts +0 -0
  335. /package/{policies → middlewares}/setLocale.js +0 -0
@@ -0,0 +1,707 @@
1
+ /**
2
+ * Security limits for filter conditions
3
+ */
4
+ export const FILTER_SECURITY_LIMITS = {
5
+ MAX_DEPTH: 10, // Maximum nesting depth
6
+ MAX_IN_VALUES: 1000, // Maximum elements in IN operator
7
+ MAX_CONDITIONS_PER_GROUP: 100, // Maximum conditions in one group
8
+ MAX_TOTAL_CONDITIONS: 500, // Maximum conditions in entire filter tree
9
+ MAX_STRING_LENGTH: 10000 // Maximum string length in filter value
10
+ };
11
+ /**
12
+ * ListQueryBuilder - replaces legacy NodeTable
13
+ *
14
+ * Key improvements:
15
+ * - Promise-based API (no callbacks)
16
+ * - Clean interface without DataTables.js format
17
+ * - Direct integration with FilterCondition
18
+ * - Support for CustomFilterHandler
19
+ * - TypeScript type safety
20
+ * - Full operator support (eq, neq, gt, gte, lt, lte, like, ilike, in, between, isNull, regex, custom)
21
+ * - Nested AND/OR/NOT groups
22
+ */
23
+ export class ListQueryBuilder {
24
+ model;
25
+ fields;
26
+ dataAccessor;
27
+ customFilterHandler;
28
+ fieldsArray = ['actions'];
29
+ constructor(model, fields, dataAccessor, customFilterHandler) {
30
+ this.model = model;
31
+ this.fields = fields;
32
+ this.dataAccessor = dataAccessor;
33
+ this.customFilterHandler = customFilterHandler;
34
+ this.fieldsArray = this.fieldsArray.concat(Object.keys(this.fields));
35
+ }
36
+ /**
37
+ * Public method to build WHERE clause without executing query
38
+ * Useful for external execution (e.g., direct adapter calls)
39
+ */
40
+ buildWhereClause(params) {
41
+ return this.buildWhere(params);
42
+ }
43
+ /**
44
+ * Execute query with Promise API
45
+ */
46
+ async execute(params) {
47
+ const whereClause = this.buildWhere(params);
48
+ const orderClause = this.buildOrder(params);
49
+ const offset = (params.page - 1) * params.limit;
50
+ // Execute queries in parallel
51
+ const [data, total, filtered] = await Promise.all([
52
+ this.model.find({
53
+ where: whereClause,
54
+ sort: orderClause,
55
+ limit: params.limit,
56
+ skip: offset,
57
+ select: this.buildSelect(params.fields)
58
+ }, this.dataAccessor),
59
+ this.model.count({}, this.dataAccessor),
60
+ this.model.count({ where: whereClause }, this.dataAccessor)
61
+ ]);
62
+ return {
63
+ data: this.mapData(data),
64
+ total,
65
+ filtered,
66
+ page: params.page,
67
+ limit: params.limit,
68
+ pages: Math.ceil(filtered / params.limit)
69
+ };
70
+ }
71
+ /**
72
+ * Build WHERE clause from FilterCondition[]
73
+ * Supports nested AND/OR/NOT groups
74
+ */
75
+ buildWhere(params) {
76
+ const conditions = [];
77
+ // 1. Filters from FilterCondition
78
+ if (params.filters && params.filters.length > 0) {
79
+ const filterCondition = this.buildConditionGroup(params.filters, 'AND');
80
+ // Check if filterCondition is not empty (including null values)
81
+ const hasKeys = Object.keys(filterCondition).length > 0;
82
+ const hasNullValue = Object.values(filterCondition).some(v => v === null);
83
+ if (hasKeys || hasNullValue) {
84
+ conditions.push(filterCondition);
85
+ }
86
+ }
87
+ // 2. Global search (for compatibility)
88
+ if (params.globalSearch) {
89
+ const searchConditions = this.buildGlobalSearch(params.globalSearch);
90
+ if (searchConditions && Object.keys(searchConditions).length > 0) {
91
+ conditions.push(searchConditions);
92
+ }
93
+ }
94
+ // Combine with AND
95
+ if (conditions.length === 0) {
96
+ return {};
97
+ }
98
+ if (conditions.length === 1) {
99
+ return conditions[0];
100
+ }
101
+ return { and: conditions };
102
+ }
103
+ /**
104
+ * Recursive condition group builder
105
+ * Key method for complex filter support
106
+ */
107
+ buildConditionGroup(conditions, logic = 'AND', depth = 0) {
108
+ // Security: check depth
109
+ if (depth > FILTER_SECURITY_LIMITS.MAX_DEPTH) {
110
+ throw new Error(`Filter nesting too deep (max ${FILTER_SECURITY_LIMITS.MAX_DEPTH})`);
111
+ }
112
+ // Security: check conditions count
113
+ if (conditions.length > FILTER_SECURITY_LIMITS.MAX_CONDITIONS_PER_GROUP) {
114
+ throw new Error(`Too many conditions in group (max ${FILTER_SECURITY_LIMITS.MAX_CONDITIONS_PER_GROUP})`);
115
+ }
116
+ const clauses = conditions
117
+ .filter(cond => this.isValidCondition(cond, depth))
118
+ .map(cond => {
119
+ // Recursion for nested groups
120
+ if (cond.children && cond.children.length > 0) {
121
+ return this.buildConditionGroup(cond.children, cond.logic || 'AND', depth + 1);
122
+ }
123
+ // Simple condition
124
+ return this.buildSingleCondition(cond);
125
+ })
126
+ .filter(c => {
127
+ if (!c)
128
+ return false;
129
+ // Check for regular keys
130
+ const keys = Object.keys(c);
131
+ if (keys.length > 0) {
132
+ for (const key of keys) {
133
+ const val = c[key];
134
+ // null is a valid value (IS NULL condition)
135
+ if (val === null)
136
+ return true;
137
+ if (val && typeof val === 'object') {
138
+ if (Object.keys(val).length > 0)
139
+ return true;
140
+ }
141
+ else if (val !== undefined) {
142
+ return true;
143
+ }
144
+ }
145
+ }
146
+ return false;
147
+ });
148
+ if (clauses.length === 0) {
149
+ return {};
150
+ }
151
+ if (clauses.length === 1) {
152
+ // NOT operator
153
+ if (logic === 'NOT') {
154
+ return { not: clauses[0] };
155
+ }
156
+ return clauses[0];
157
+ }
158
+ // NOT operator requires exactly one condition
159
+ if (logic === 'NOT') {
160
+ throw new Error('NOT operator requires exactly one condition');
161
+ }
162
+ return logic === 'OR'
163
+ ? { or: clauses }
164
+ : { and: clauses };
165
+ }
166
+ /**
167
+ * Validate condition
168
+ */
169
+ isValidCondition(cond, _depth = 0) {
170
+ // Group with children
171
+ if (cond.children && cond.children.length > 0) {
172
+ return true;
173
+ }
174
+ const hasRelation = Boolean(cond.relation && cond.relationField);
175
+ // Regular condition
176
+ if (!cond.field && !hasRelation && !cond.customHandler && !cond.rawSQL) {
177
+ return false;
178
+ }
179
+ if (!cond.operator) {
180
+ return false;
181
+ }
182
+ // Operators without value
183
+ if (cond.operator === 'isNull' || cond.operator === 'isNotNull' || cond.operator === 'today') {
184
+ return true;
185
+ }
186
+ // Custom handlers may have their own validation
187
+ if (cond.customHandler || cond.rawSQL) {
188
+ return true;
189
+ }
190
+ // Relation conditions must have value (except NULL checks handled above)
191
+ if (hasRelation) {
192
+ return cond.value !== undefined && cond.value !== '';
193
+ }
194
+ // Others require value
195
+ return cond.value !== undefined && cond.value !== '';
196
+ }
197
+ /**
198
+ * Build single condition
199
+ */
200
+ buildSingleCondition(cond) {
201
+ // 1. Raw SQL is intentionally not part of QueryCriteria.
202
+ if (cond.rawSQL) {
203
+ throw new Error("Raw SQL filters are not supported by QueryCriteria");
204
+ }
205
+ // 2. Custom handler
206
+ if (cond.customHandler) {
207
+ return this.handleCustomCondition(cond);
208
+ }
209
+ // 3. Relation condition
210
+ if (cond.relation && cond.relationField) {
211
+ return this.buildRelationCondition(cond);
212
+ }
213
+ // 4. Standard field condition
214
+ const { field, operator, value } = cond;
215
+ // Ensure field and operator exist
216
+ if (!field || !operator) {
217
+ return {};
218
+ }
219
+ // Get field config to handle special cases
220
+ const fieldConfig = this.fields[field];
221
+ const fieldType = fieldConfig?.model?.type;
222
+ const fieldConfigType = typeof fieldConfig?.config === 'object' && fieldConfig?.config !== null
223
+ ? fieldConfig.config?.type
224
+ : undefined;
225
+ // Special handling for select-many (JSON array stored in DB) with in/notIn/eq operators
226
+ // select-many widget stores data as JSON array: ["value1","value2"]
227
+ if ((fieldType === 'json' || fieldConfigType === 'select-many') &&
228
+ Array.isArray(value)) {
229
+ if (operator === 'eq') {
230
+ return { [field]: { jsonContains: value } };
231
+ }
232
+ if (operator === 'in' || operator === 'notIn') {
233
+ const clauses = value.map((item) => ({ [field]: { jsonContains: item } }));
234
+ return operator === 'in'
235
+ ? { or: clauses }
236
+ : { not: { or: clauses } };
237
+ }
238
+ }
239
+ // Special handling for isNull - return { field: null }
240
+ if (operator === 'isNull') {
241
+ return { [field]: null };
242
+ }
243
+ // Special handling for isNotNull - return { not: { field: null } }
244
+ if (operator === 'isNotNull') {
245
+ return { [field]: { isNotNull: true } };
246
+ }
247
+ // Validate operator-value combination
248
+ this.validateOperatorValue(operator, value);
249
+ // Map operator to ORM condition (ignore value for isNull/isNotNull)
250
+ const condition = this.mapOperatorToCondition(operator, value);
251
+ // Skip if condition is undefined/null (invalid value)
252
+ if (condition === undefined || condition === null) {
253
+ return {};
254
+ }
255
+ return { [field]: condition };
256
+ }
257
+ /**
258
+ * Map filter operators to adapter-neutral criteria operators.
259
+ */
260
+ mapOperatorToCondition(operator, value) {
261
+ const operatorsWithoutValue = ['isNull', 'isNotNull', 'today'];
262
+ // Return undefined for invalid values (will be filtered out)
263
+ // Note: false is a valid boolean value and should NOT be filtered out
264
+ if (!operatorsWithoutValue.includes(operator) && (value === undefined || value === null || value === '')) {
265
+ return undefined;
266
+ }
267
+ switch (operator) {
268
+ case 'eq':
269
+ // Convert string 'true'/'false' to actual boolean values
270
+ // For boolean fields, return value directly.
271
+ if (value === 'true')
272
+ return true;
273
+ if (value === 'false')
274
+ return false;
275
+ if (typeof value === 'boolean')
276
+ return value;
277
+ return value;
278
+ case 'neq':
279
+ // Convert string 'true'/'false' to actual boolean values
280
+ const neqValue = value === 'true' ? true : value === 'false' ? false : value;
281
+ return { ne: neqValue };
282
+ case 'gt':
283
+ return { gt: value };
284
+ case 'gte':
285
+ return { gte: value };
286
+ case 'lt':
287
+ return { lt: value };
288
+ case 'lte':
289
+ return { lte: value };
290
+ case 'like':
291
+ if (typeof value === 'string' && value.startsWith('%') && value.endsWith('%')) {
292
+ return { contains: value.slice(1, -1) };
293
+ }
294
+ return { contains: value };
295
+ case 'ilike':
296
+ // Case-insensitive LIKE - works on all dialects
297
+ if (typeof value === 'string' && value.startsWith('%') && value.endsWith('%')) {
298
+ return { contains: value.slice(1, -1) };
299
+ }
300
+ return { contains: value };
301
+ case 'startsWith':
302
+ return { startsWith: value };
303
+ case 'endsWith':
304
+ return { endsWith: value };
305
+ case 'regex':
306
+ return { regex: value };
307
+ case 'in':
308
+ // Convert string 'true'/'false' to actual boolean values in array
309
+ const inValues = Array.isArray(value) ? value : [value];
310
+ const convertedInValues = inValues.map((v) => v === 'true' ? true : v === 'false' ? false : v);
311
+ return { in: convertedInValues };
312
+ case 'notIn':
313
+ // Convert string 'true'/'false' to actual boolean values in array
314
+ const notInValues = Array.isArray(value) ? value : [value];
315
+ const convertedNotInValues = notInValues.map((v) => v === 'true' ? true : v === 'false' ? false : v);
316
+ return { notIn: convertedNotInValues };
317
+ case 'between':
318
+ if (Array.isArray(value) && value.length === 2) {
319
+ return { between: value };
320
+ }
321
+ return value;
322
+ case 'today': {
323
+ const [from, to] = this.getDayRange(new Date());
324
+ return { between: [from, to] };
325
+ }
326
+ case 'month': {
327
+ const range = this.getMonthRangeByValue(value);
328
+ return range ? { between: range } : undefined;
329
+ }
330
+ case 'year': {
331
+ const range = this.getYearRangeByValue(value);
332
+ return range ? { between: range } : undefined;
333
+ }
334
+ case 'monthBetween': {
335
+ const range = this.getMonthRangeBetween(value);
336
+ return range ? { between: range } : undefined;
337
+ }
338
+ case 'yearBetween': {
339
+ const range = this.getYearRangeBetween(value);
340
+ return range ? { between: range } : undefined;
341
+ }
342
+ case 'custom':
343
+ // Custom conditions are handled separately
344
+ return value;
345
+ default:
346
+ return value;
347
+ }
348
+ }
349
+ /**
350
+ * Validate operator-value combination
351
+ */
352
+ validateOperatorValue(operator, value) {
353
+ const operatorsWithoutValue = ['isNull', 'isNotNull', 'today'];
354
+ // Skip validation for undefined/null/empty values (will be filtered out)
355
+ if (!operatorsWithoutValue.includes(operator) && (value === undefined || value === null || value === '')) {
356
+ return;
357
+ }
358
+ switch (operator) {
359
+ case 'in':
360
+ case 'notIn':
361
+ if (!Array.isArray(value)) {
362
+ throw new Error(`Operator '${operator}' requires array value`);
363
+ }
364
+ if (value.length > FILTER_SECURITY_LIMITS.MAX_IN_VALUES) {
365
+ throw new Error(`Too many values in IN operator (max ${FILTER_SECURITY_LIMITS.MAX_IN_VALUES})`);
366
+ }
367
+ break;
368
+ case 'between':
369
+ if (!Array.isArray(value) || value.length !== 2) {
370
+ throw new Error('BETWEEN operator requires array of 2 values');
371
+ }
372
+ break;
373
+ case 'month':
374
+ if (typeof value !== 'string' || !/^\d{4}-\d{2}$/.test(value)) {
375
+ throw new Error('MONTH operator requires YYYY-MM value');
376
+ }
377
+ break;
378
+ case 'year':
379
+ if (!((typeof value === 'string' && /^\d{4}$/.test(value)) ||
380
+ (typeof value === 'number' && Number.isInteger(value)))) {
381
+ throw new Error('YEAR operator requires YYYY value');
382
+ }
383
+ break;
384
+ case 'monthBetween':
385
+ if (!Array.isArray(value) || value.length !== 2) {
386
+ throw new Error('MONTH BETWEEN operator requires array of 2 YYYY-MM values');
387
+ }
388
+ if (typeof value[0] !== 'string' || !/^\d{4}-\d{2}$/.test(value[0]) ||
389
+ typeof value[1] !== 'string' || !/^\d{4}-\d{2}$/.test(value[1])) {
390
+ throw new Error('MONTH BETWEEN operator requires array of 2 YYYY-MM values');
391
+ }
392
+ break;
393
+ case 'yearBetween':
394
+ if (!Array.isArray(value) || value.length !== 2) {
395
+ throw new Error('YEAR BETWEEN operator requires array of 2 YYYY values');
396
+ }
397
+ if (!this.isYearValue(value[0]) || !this.isYearValue(value[1])) {
398
+ throw new Error('YEAR BETWEEN operator requires array of 2 YYYY values');
399
+ }
400
+ break;
401
+ case 'regex':
402
+ if (typeof value !== 'string') {
403
+ throw new Error('Regex operator requires string pattern');
404
+ }
405
+ try {
406
+ new RegExp(value);
407
+ }
408
+ catch (e) {
409
+ throw new Error(`Invalid regex pattern: ${value}`);
410
+ }
411
+ break;
412
+ case 'like':
413
+ case 'ilike':
414
+ case 'startsWith':
415
+ case 'endsWith':
416
+ if (typeof value === 'string' && value.length > FILTER_SECURITY_LIMITS.MAX_STRING_LENGTH) {
417
+ throw new Error(`String value too long (max ${FILTER_SECURITY_LIMITS.MAX_STRING_LENGTH})`);
418
+ }
419
+ break;
420
+ }
421
+ }
422
+ getDayRange(date) {
423
+ const from = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
424
+ const to = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999);
425
+ return [from, to];
426
+ }
427
+ getMonthRangeByValue(value) {
428
+ if (typeof value !== 'string' || !/^\d{4}-\d{2}$/.test(value)) {
429
+ return null;
430
+ }
431
+ const [year, month] = value.split('-').map(Number);
432
+ if (!year || !month || month < 1 || month > 12) {
433
+ return null;
434
+ }
435
+ const from = new Date(year, month - 1, 1, 0, 0, 0, 0);
436
+ const to = new Date(year, month, 0, 23, 59, 59, 999);
437
+ return [from, to];
438
+ }
439
+ getYearRangeByValue(value) {
440
+ const year = this.parseYearValue(value);
441
+ if (year === null) {
442
+ return null;
443
+ }
444
+ const from = new Date(year, 0, 1, 0, 0, 0, 0);
445
+ const to = new Date(year, 11, 31, 23, 59, 59, 999);
446
+ return [from, to];
447
+ }
448
+ getMonthRangeBetween(value) {
449
+ if (!Array.isArray(value) || value.length !== 2) {
450
+ return null;
451
+ }
452
+ const fromRange = this.getMonthRangeByValue(value[0]);
453
+ const toRange = this.getMonthRangeByValue(value[1]);
454
+ if (!fromRange || !toRange) {
455
+ return null;
456
+ }
457
+ const start = fromRange[0] <= toRange[0] ? fromRange[0] : toRange[0];
458
+ const end = fromRange[1] <= toRange[1] ? toRange[1] : fromRange[1];
459
+ return [start, end];
460
+ }
461
+ getYearRangeBetween(value) {
462
+ if (!Array.isArray(value) || value.length !== 2) {
463
+ return null;
464
+ }
465
+ const fromRange = this.getYearRangeByValue(value[0]);
466
+ const toRange = this.getYearRangeByValue(value[1]);
467
+ if (!fromRange || !toRange) {
468
+ return null;
469
+ }
470
+ const start = fromRange[0] <= toRange[0] ? fromRange[0] : toRange[0];
471
+ const end = fromRange[1] <= toRange[1] ? toRange[1] : fromRange[1];
472
+ return [start, end];
473
+ }
474
+ isYearValue(value) {
475
+ return this.parseYearValue(value) !== null;
476
+ }
477
+ parseYearValue(value) {
478
+ if (typeof value === 'number' && Number.isInteger(value)) {
479
+ return value;
480
+ }
481
+ if (typeof value === 'string' && /^\d{4}$/.test(value)) {
482
+ return Number(value);
483
+ }
484
+ return null;
485
+ }
486
+ /**
487
+ * Build relation condition
488
+ */
489
+ buildRelationCondition(cond) {
490
+ const { relation, relationField, operator, value } = cond;
491
+ // Ensure operator and safe relation path parts exist
492
+ if (!relation || !relationField || !operator) {
493
+ return {};
494
+ }
495
+ if (!/^[A-Za-z0-9_]+$/.test(relation) || !/^[A-Za-z0-9_]+$/.test(relationField)) {
496
+ return {};
497
+ }
498
+ const relationPath = `$${relation}.${relationField}$`;
499
+ if (operator === 'isNull') {
500
+ return { [relationPath]: null };
501
+ }
502
+ if (operator === 'isNotNull') {
503
+ return { [relationPath]: { isNotNull: true } };
504
+ }
505
+ this.validateOperatorValue(operator, value);
506
+ const relationCondition = this.mapOperatorToCondition(operator, value);
507
+ if (relationCondition === undefined || relationCondition === null) {
508
+ return {};
509
+ }
510
+ return { [relationPath]: relationCondition };
511
+ }
512
+ /**
513
+ * Handle custom condition
514
+ */
515
+ handleCustomCondition(cond) {
516
+ const handler = this.customFilterHandler?.get(cond.customHandler || '');
517
+ if (handler) {
518
+ const condition = handler.buildCondition(cond.value, cond.customHandlerParams);
519
+ // rawSQL returned
520
+ if (condition.rawSQL) {
521
+ throw new Error("Raw SQL filters are not supported by QueryCriteria");
522
+ }
523
+ // in-memory function returned
524
+ if (condition.inMemory) {
525
+ throw new Error("In-memory filters are not supported by QueryCriteria");
526
+ }
527
+ // Standard criteria returned
528
+ if (condition.criteria) {
529
+ return condition.criteria;
530
+ }
531
+ return condition;
532
+ }
533
+ return {};
534
+ }
535
+ /**
536
+ * Build global search condition
537
+ * Searches in string/text/ref fields (same behavior as NodeTable)
538
+ * Date/time fields are excluded from global search to avoid moment.js warnings
539
+ */
540
+ buildGlobalSearch(searchStr) {
541
+ // Skip if empty
542
+ if (!searchStr || searchStr.trim() === '') {
543
+ return null;
544
+ }
545
+ const searchConditions = [];
546
+ for (const [fieldName, fieldConfig] of Object.entries(this.fields)) {
547
+ // Skip non-visible fields (same behavior as NodeTable)
548
+ const config = typeof fieldConfig.config === 'object' && fieldConfig.config !== null
549
+ ? fieldConfig.config
550
+ : null;
551
+ if (config && config.visible === false) {
552
+ continue;
553
+ }
554
+ const fieldType = fieldConfig.model?.type;
555
+ // Skip date/datetime fields - they can't be searched with LIKE
556
+ // This avoids moment.js warnings when string values are passed to date fields
557
+ if (fieldType === 'date' || fieldType === 'datetime') {
558
+ continue;
559
+ }
560
+ // Global search in string/text/ref fields
561
+ if (fieldType === 'string' || fieldType === 'text' || fieldType === 'ref') {
562
+ searchConditions.push({
563
+ [fieldName]: { contains: searchStr }
564
+ });
565
+ }
566
+ }
567
+ if (searchConditions.length === 0) {
568
+ return null;
569
+ }
570
+ if (searchConditions.length === 1) {
571
+ return searchConditions[0];
572
+ }
573
+ return { or: searchConditions };
574
+ }
575
+ /**
576
+ * Build ORDER clause
577
+ */
578
+ buildOrder(params) {
579
+ const sortField = params.sort || 'createdAt';
580
+ const sortDirection = this.sanitizeSortDirection(params.sortDirection);
581
+ // Validate sort field exists
582
+ if (sortField !== 'createdAt' && sortField !== 'updatedAt') {
583
+ const fieldExists = sortField in this.fields ||
584
+ sortField === this.model.primaryKey ||
585
+ sortField === 'id';
586
+ if (!fieldExists) {
587
+ // Fallback to createdAt if field doesn't exist
588
+ return 'createdAt DESC';
589
+ }
590
+ }
591
+ return `${sortField} ${sortDirection}`;
592
+ }
593
+ sanitizeSortDirection(sortDirection) {
594
+ if (!sortDirection) {
595
+ return 'DESC';
596
+ }
597
+ const normalizedDirection = sortDirection.toUpperCase();
598
+ if (normalizedDirection !== 'ASC' && normalizedDirection !== 'DESC') {
599
+ return 'DESC';
600
+ }
601
+ return normalizedDirection;
602
+ }
603
+ /**
604
+ * Map data for output
605
+ */
606
+ mapData(data) {
607
+ const out = [];
608
+ data.forEach((elem) => {
609
+ const row = {};
610
+ // Add primary key first
611
+ const pk = this.model.primaryKey ?? 'id';
612
+ row[pk] = elem[pk];
613
+ // Process each field
614
+ Object.keys(this.fields).forEach((key) => {
615
+ const fieldConfig = this.fields[key].config;
616
+ const fieldModel = this.fields[key].model;
617
+ const modelConfig = this.fields[key].modelConfig;
618
+ const displayField = modelConfig?.titleField ??
619
+ (typeof elem['title'] !== 'undefined' ? 'title' :
620
+ typeof elem['name'] !== 'undefined' ? 'name' :
621
+ modelConfig?.identifierField ?? 'id');
622
+ if (fieldConfig.displayModifier) {
623
+ // Custom display modifier
624
+ row[key] = fieldConfig.displayModifier(elem[key]);
625
+ }
626
+ else if (fieldModel && fieldModel.model) {
627
+ // Handle "belongsTo" relationships
628
+ if (!elem[key]) {
629
+ row[key] = null;
630
+ }
631
+ else {
632
+ row[key] = elem[key][displayField];
633
+ }
634
+ }
635
+ else if (fieldModel?.type === 'association-many' || fieldModel?.type === 'association') {
636
+ // Handle "hasMany" relationships
637
+ if (!elem[key] || elem[key].length === 0) {
638
+ row[key] = null;
639
+ }
640
+ else {
641
+ const displayValues = [];
642
+ elem[key].forEach((item) => {
643
+ if (item[displayField]) {
644
+ displayValues.push(item[displayField]);
645
+ }
646
+ else if (fieldConfig.identifierField && item[fieldConfig.identifierField]) {
647
+ displayValues.push(item[fieldConfig.identifierField]);
648
+ }
649
+ });
650
+ row[key] = displayValues.join(', ');
651
+ }
652
+ }
653
+ else if (fieldModel?.type === 'json') {
654
+ // Handle JSON fields
655
+ const str = JSON.stringify(elem[key]);
656
+ row[key] = str === '{}' ? '' : str;
657
+ }
658
+ else {
659
+ // Regular field
660
+ row[key] = elem[key];
661
+ }
662
+ });
663
+ out.push(row);
664
+ });
665
+ return out;
666
+ }
667
+ buildSelect(fields) {
668
+ if (!fields?.length) {
669
+ return undefined;
670
+ }
671
+ const selectableFields = fields.filter((field) => {
672
+ const modelField = this.model.attributes[field];
673
+ if (!modelField) {
674
+ return false;
675
+ }
676
+ return !modelField.model
677
+ && !modelField.collection
678
+ && modelField.type !== "association"
679
+ && modelField.type !== "association-many";
680
+ });
681
+ if (this.model.primaryKey && !selectableFields.includes(this.model.primaryKey)) {
682
+ selectableFields.unshift(this.model.primaryKey);
683
+ }
684
+ return selectableFields.length ? selectableFields : undefined;
685
+ }
686
+ /**
687
+ * Get allowed operators for field type
688
+ */
689
+ static getOperatorsForFieldType(fieldType) {
690
+ const operatorsByType = {
691
+ string: ['eq', 'neq', 'like', 'ilike', 'startsWith', 'endsWith', 'in', 'notIn', 'isNull', 'isNotNull', 'regex'],
692
+ text: ['eq', 'neq', 'like', 'ilike', 'startsWith', 'endsWith', 'isNull', 'isNotNull'],
693
+ number: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between', 'in', 'notIn', 'isNull', 'isNotNull'],
694
+ integer: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between', 'in', 'notIn', 'isNull', 'isNotNull'],
695
+ float: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between', 'in', 'notIn', 'isNull', 'isNotNull'],
696
+ boolean: ['eq', 'neq', 'isNull', 'isNotNull'],
697
+ date: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between', 'today', 'month', 'year', 'monthBetween', 'yearBetween', 'isNull', 'isNotNull'],
698
+ datetime: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between', 'today', 'month', 'year', 'monthBetween', 'yearBetween', 'isNull', 'isNotNull'],
699
+ select: ['eq', 'neq', 'in', 'notIn', 'isNull', 'isNotNull'],
700
+ json: ['isNull', 'isNotNull', 'custom'],
701
+ association: ['eq', 'neq', 'in', 'notIn', 'isNull', 'isNotNull'],
702
+ 'association-many': ['in', 'notIn', 'isNull', 'isNotNull']
703
+ };
704
+ return operatorsByType[fieldType] || operatorsByType.string;
705
+ }
706
+ }
707
+ export { ListQueryBuilder as ModernListQueryBuilder };