@quazardous/qdadm 1.19.3

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 (291) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -0
  3. package/package.json +101 -0
  4. package/src/assets/logo.svg +6 -0
  5. package/src/auth/SessionAuthAdapter.ts +459 -0
  6. package/src/auth/index.ts +15 -0
  7. package/src/chain/ActiveStack.ts +154 -0
  8. package/src/chain/StackHydrator.ts +292 -0
  9. package/src/chain/index.ts +25 -0
  10. package/src/chain/useActiveStack.ts +120 -0
  11. package/src/chain/useStackHydrator.ts +155 -0
  12. package/src/components/BoolCell.vue +34 -0
  13. package/src/components/InfoBanner.vue +73 -0
  14. package/src/components/QdadmRoot.vue +52 -0
  15. package/src/components/SeverityTag.vue +86 -0
  16. package/src/components/dialogs/BulkStatusDialog.vue +60 -0
  17. package/src/components/dialogs/MultiStepDialog.vue +331 -0
  18. package/src/components/dialogs/SimpleDialog.vue +122 -0
  19. package/src/components/dialogs/UnsavedChangesDialog.vue +80 -0
  20. package/src/components/display/BannerZone.vue +65 -0
  21. package/src/components/display/CardsGrid.vue +72 -0
  22. package/src/components/display/CopyableId.vue +84 -0
  23. package/src/components/display/EmptyState.vue +62 -0
  24. package/src/components/display/IntensityBar.vue +190 -0
  25. package/src/components/display/RichCardsGrid.vue +101 -0
  26. package/src/components/edit/FormActions.vue +78 -0
  27. package/src/components/edit/FormField.vue +82 -0
  28. package/src/components/edit/FormInput.vue +158 -0
  29. package/src/components/edit/FormPage.vue +260 -0
  30. package/src/components/edit/FormTab.vue +217 -0
  31. package/src/components/edit/FormTabs.vue +120 -0
  32. package/src/components/edit/LookupField.vue +406 -0
  33. package/src/components/edit/LookupPickerDialog.vue +230 -0
  34. package/src/components/editors/JsonEditorFoldable.vue +470 -0
  35. package/src/components/editors/JsonStructuredField.vue +264 -0
  36. package/src/components/editors/JsonViewer.vue +51 -0
  37. package/src/components/editors/KeyValueEditor.vue +314 -0
  38. package/src/components/editors/LanguageEditor.vue +259 -0
  39. package/src/components/editors/PermissionEditor.vue +558 -0
  40. package/src/components/editors/ScopeEditor.vue +372 -0
  41. package/src/components/editors/VanillaJsonEditor.vue +183 -0
  42. package/src/components/index.ts +83 -0
  43. package/src/components/item/FieldGroups.vue +392 -0
  44. package/src/components/layout/AppLayout.vue +1032 -0
  45. package/src/components/layout/BaseLayout.vue +190 -0
  46. package/src/components/layout/Breadcrumb.vue +55 -0
  47. package/src/components/layout/DashboardLayout.vue +104 -0
  48. package/src/components/layout/FormLayout.vue +256 -0
  49. package/src/components/layout/ListLayout.vue +278 -0
  50. package/src/components/layout/PageHeader.vue +156 -0
  51. package/src/components/layout/PageLayout.vue +126 -0
  52. package/src/components/layout/PageNav.vue +192 -0
  53. package/src/components/layout/SidebarBox.vue +87 -0
  54. package/src/components/layout/Zone.vue +182 -0
  55. package/src/components/layout/defaults/DefaultBreadcrumb.vue +149 -0
  56. package/src/components/layout/defaults/DefaultFooter.vue +89 -0
  57. package/src/components/layout/defaults/DefaultFormActions.vue +67 -0
  58. package/src/components/layout/defaults/DefaultHeader.vue +78 -0
  59. package/src/components/layout/defaults/DefaultMenu.vue +188 -0
  60. package/src/components/layout/defaults/DefaultPagination.vue +75 -0
  61. package/src/components/layout/defaults/DefaultTable.vue +137 -0
  62. package/src/components/layout/defaults/DefaultUserInfo.vue +111 -0
  63. package/src/components/layout/defaults/index.ts +16 -0
  64. package/src/components/lists/ActionButtons.vue +49 -0
  65. package/src/components/lists/ActionColumn.vue +40 -0
  66. package/src/components/lists/FilterBar.vue +54 -0
  67. package/src/components/lists/ListPage.vue +467 -0
  68. package/src/components/pages/LoginPage.vue +334 -0
  69. package/src/components/pages/NotFoundPage.vue +98 -0
  70. package/src/components/show/ShowDisplay.vue +343 -0
  71. package/src/components/show/ShowField.vue +67 -0
  72. package/src/components/show/ShowPage.vue +431 -0
  73. package/src/composables/index.ts +155 -0
  74. package/src/composables/useApp.ts +67 -0
  75. package/src/composables/useAuth.ts +98 -0
  76. package/src/composables/useBareForm.ts +319 -0
  77. package/src/composables/useBreadcrumb.ts +252 -0
  78. package/src/composables/useChildPage.ts +113 -0
  79. package/src/composables/useCurrentEntity.ts +60 -0
  80. package/src/composables/useDeferred.ts +94 -0
  81. package/src/composables/useDirtyState.ts +138 -0
  82. package/src/composables/useEntityItemFormPage.ts +942 -0
  83. package/src/composables/useEntityItemFormPage.types.ts +451 -0
  84. package/src/composables/useEntityItemPage.ts +438 -0
  85. package/src/composables/useEntityItemShowPage.ts +722 -0
  86. package/src/composables/useFieldManager.ts +692 -0
  87. package/src/composables/useForm.ts +544 -0
  88. package/src/composables/useGuardStore.ts +53 -0
  89. package/src/composables/useHooks.ts +56 -0
  90. package/src/composables/useInfoBanner.ts +217 -0
  91. package/src/composables/useJsonSyntax.ts +122 -0
  92. package/src/composables/useLayoutResolver.ts +329 -0
  93. package/src/composables/useListPage.ts +1535 -0
  94. package/src/composables/useListPage.types.ts +437 -0
  95. package/src/composables/useListPage.utils.ts +101 -0
  96. package/src/composables/useNavContext.ts +439 -0
  97. package/src/composables/useNavigation.ts +216 -0
  98. package/src/composables/useOptionsLookup.ts +323 -0
  99. package/src/composables/usePageTitle.ts +84 -0
  100. package/src/composables/useSSEBridge.ts +142 -0
  101. package/src/composables/useSemanticBreadcrumb.ts +222 -0
  102. package/src/composables/useSidebarState.ts +24 -0
  103. package/src/composables/useSignals.ts +52 -0
  104. package/src/composables/useUnsavedChangesGuard.ts +155 -0
  105. package/src/composables/useUserImpersonator.ts +317 -0
  106. package/src/composables/useZoneRegistry.ts +252 -0
  107. package/src/core/bundles.ts +558 -0
  108. package/src/core/decorator.ts +420 -0
  109. package/src/core/extension.ts +470 -0
  110. package/src/core/index.ts +59 -0
  111. package/src/deferred/DeferredRegistry.ts +353 -0
  112. package/src/deferred/index.ts +15 -0
  113. package/src/editors/index.ts +12 -0
  114. package/src/entity/EntityManager.cache.ts +530 -0
  115. package/src/entity/EntityManager.crud.ts +615 -0
  116. package/src/entity/EntityManager.interface.ts +78 -0
  117. package/src/entity/EntityManager.query.ts +221 -0
  118. package/src/entity/EntityManager.relations.ts +151 -0
  119. package/src/entity/EntityManager.ts +944 -0
  120. package/src/entity/EntityManager.types.ts +288 -0
  121. package/src/entity/auth/CompositeAuthAdapter.ts +194 -0
  122. package/src/entity/auth/EntityAuthAdapter.ts +119 -0
  123. package/src/entity/auth/PermissiveAdapter.ts +43 -0
  124. package/src/entity/auth/RoleHierarchy.ts +128 -0
  125. package/src/entity/auth/SecurityChecker.ts +202 -0
  126. package/src/entity/auth/factory.ts +203 -0
  127. package/src/entity/auth/index.ts +47 -0
  128. package/src/entity/factory.test.js +189 -0
  129. package/src/entity/factory.ts +146 -0
  130. package/src/entity/index.ts +49 -0
  131. package/src/entity/storage/ApiStorage.ts +228 -0
  132. package/src/entity/storage/IStorage.ts +100 -0
  133. package/src/entity/storage/LocalStorage.ts +200 -0
  134. package/src/entity/storage/MemoryStorage.ts +184 -0
  135. package/src/entity/storage/MockApiStorage.ts +318 -0
  136. package/src/entity/storage/SdkStorage.ts +377 -0
  137. package/src/entity/storage/factory.test.js +159 -0
  138. package/src/entity/storage/factory.ts +209 -0
  139. package/src/entity/storage/index.ts +75 -0
  140. package/src/gen/FieldMapper.ts +122 -0
  141. package/src/gen/StorageProfileFactory.ts +93 -0
  142. package/src/gen/connectors/BaseConnector.ts +150 -0
  143. package/src/gen/connectors/ManualConnector.test.js +499 -0
  144. package/src/gen/connectors/ManualConnector.ts +414 -0
  145. package/src/gen/connectors/OpenAPIConnector.test.js +836 -0
  146. package/src/gen/connectors/OpenAPIConnector.ts +693 -0
  147. package/src/gen/connectors/__fixtures__/sample-openapi.json +311 -0
  148. package/src/gen/connectors/index.ts +27 -0
  149. package/src/gen/createManagers.ts +245 -0
  150. package/src/gen/decorators.ts +134 -0
  151. package/src/gen/generateManagers.test.js +540 -0
  152. package/src/gen/generateManagers.ts +398 -0
  153. package/src/gen/index.ts +66 -0
  154. package/src/gen/schema.ts +248 -0
  155. package/src/gen/vite-plugin.ts +114 -0
  156. package/src/generated/managers/testManager.js +45 -0
  157. package/src/generated/managers/testManager.ts +46 -0
  158. package/src/hooks/HookRegistry.ts +16 -0
  159. package/src/hooks/index.ts +15 -0
  160. package/src/i18n/I18n.ts +344 -0
  161. package/src/i18n/IncrementalDomainProvider.ts +153 -0
  162. package/src/i18n/InlineTranslationProvider.ts +4 -0
  163. package/src/i18n/LazyTranslationProvider.ts +102 -0
  164. package/src/i18n/MessagesRegistry.ts +4 -0
  165. package/src/i18n/Resolver.ts +4 -0
  166. package/src/i18n/__tests__/I18n.test.ts +169 -0
  167. package/src/i18n/__tests__/IncrementalDomainProvider.test.ts +146 -0
  168. package/src/i18n/__tests__/LazyTranslationProvider.test.ts +100 -0
  169. package/src/i18n/__tests__/Resolver.test.ts +271 -0
  170. package/src/i18n/defaults/DefaultCoreProvider.ts +28 -0
  171. package/src/i18n/defaults/core.en.yml +55 -0
  172. package/src/i18n/defaults/core.fr.yml +55 -0
  173. package/src/i18n/index.ts +55 -0
  174. package/src/i18n/loaders/raw-modules.d.ts +15 -0
  175. package/src/i18n/loaders/yaml.ts +35 -0
  176. package/src/i18n/strategies.ts +4 -0
  177. package/src/i18n/types.ts +34 -0
  178. package/src/i18n/useI18n.ts +34 -0
  179. package/src/index.ts +453 -0
  180. package/src/kernel/EventRouter.ts +23 -0
  181. package/src/kernel/Kernel.api.ts +79 -0
  182. package/src/kernel/Kernel.auth.ts +120 -0
  183. package/src/kernel/Kernel.i18n.ts +29 -0
  184. package/src/kernel/Kernel.modules.ts +170 -0
  185. package/src/kernel/Kernel.registries.ts +237 -0
  186. package/src/kernel/Kernel.routing.ts +334 -0
  187. package/src/kernel/Kernel.ts +460 -0
  188. package/src/kernel/Kernel.types.ts +263 -0
  189. package/src/kernel/Kernel.vue.ts +304 -0
  190. package/src/kernel/KernelContext.entities.ts +80 -0
  191. package/src/kernel/KernelContext.events.ts +57 -0
  192. package/src/kernel/KernelContext.i18n.ts +37 -0
  193. package/src/kernel/KernelContext.permissions.ts +38 -0
  194. package/src/kernel/KernelContext.routing.ts +280 -0
  195. package/src/kernel/KernelContext.ts +252 -0
  196. package/src/kernel/KernelContext.types.ts +173 -0
  197. package/src/kernel/KernelContext.zones.ts +54 -0
  198. package/src/kernel/Module.ts +169 -0
  199. package/src/kernel/ModuleLoader.ts +645 -0
  200. package/src/kernel/SSEBridge.ts +11 -0
  201. package/src/kernel/SignalBus.ts +48 -0
  202. package/src/kernel/index.ts +56 -0
  203. package/src/module/index.ts +33 -0
  204. package/src/module/moduleRegistry.ts +492 -0
  205. package/src/modules/debug/AuthCollector.ts +501 -0
  206. package/src/modules/debug/Collector.ts +20 -0
  207. package/src/modules/debug/DebugBridge.ts +14 -0
  208. package/src/modules/debug/DebugModule.ts +220 -0
  209. package/src/modules/debug/EntitiesCollector.ts +839 -0
  210. package/src/modules/debug/ErrorCollector.ts +4 -0
  211. package/src/modules/debug/I18nCollector.ts +9 -0
  212. package/src/modules/debug/LocalStorageAdapter.ts +7 -0
  213. package/src/modules/debug/RouterCollector.ts +449 -0
  214. package/src/modules/debug/SignalCollector.ts +4 -0
  215. package/src/modules/debug/ToastCollector.ts +4 -0
  216. package/src/modules/debug/ZonesCollector.ts +447 -0
  217. package/src/modules/debug/components/DebugBar.vue +31 -0
  218. package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
  219. package/src/modules/debug/components/index.ts +9 -0
  220. package/src/modules/debug/components/panels/AuthPanel.vue +267 -0
  221. package/src/modules/debug/components/panels/EntitiesPanel.vue +572 -0
  222. package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
  223. package/src/modules/debug/components/panels/RouterPanel.vue +440 -0
  224. package/src/modules/debug/components/panels/ZonesPanel.vue +133 -0
  225. package/src/modules/debug/components/panels/index.ts +16 -0
  226. package/src/modules/debug/index.ts +58 -0
  227. package/src/modules/debug/styles.scss +2671 -0
  228. package/src/notifications/NotificationBadge.vue +35 -0
  229. package/src/notifications/NotificationListener.vue +71 -0
  230. package/src/notifications/NotificationModule.ts +76 -0
  231. package/src/notifications/NotificationPanel.vue +478 -0
  232. package/src/notifications/NotificationStore.ts +288 -0
  233. package/src/notifications/index.ts +42 -0
  234. package/src/notifications/styles.scss +32 -0
  235. package/src/orchestrator/Orchestrator.ts +427 -0
  236. package/src/orchestrator/index.ts +8 -0
  237. package/src/orchestrator/useOrchestrator.ts +80 -0
  238. package/src/plugin.ts +251 -0
  239. package/src/query/FilterQuery.ts +306 -0
  240. package/src/query/QueryExecutor.ts +341 -0
  241. package/src/query/index.ts +16 -0
  242. package/src/security/EntityRolesProvider.ts +385 -0
  243. package/src/security/PermissionMatcher.ts +152 -0
  244. package/src/security/PermissionRegistry.ts +273 -0
  245. package/src/security/PersistableRolesProvider.ts +584 -0
  246. package/src/security/RolesManager.ts +136 -0
  247. package/src/security/RolesProvider.ts +167 -0
  248. package/src/security/RolesProviderStorage.ts +211 -0
  249. package/src/security/SecurityModule.ts +94 -0
  250. package/src/security/StaticRolesProvider.ts +117 -0
  251. package/src/security/UsersManager.ts +163 -0
  252. package/src/security/index.ts +77 -0
  253. package/src/security/pages/RoleForm.vue +276 -0
  254. package/src/security/pages/RoleList.vue +123 -0
  255. package/src/styles/_alerts.scss +95 -0
  256. package/src/styles/_buttons.scss +391 -0
  257. package/src/styles/_cards.scss +241 -0
  258. package/src/styles/_code.scss +78 -0
  259. package/src/styles/_dialogs.scss +55 -0
  260. package/src/styles/_filter-bar.scss +70 -0
  261. package/src/styles/_forms.scss +434 -0
  262. package/src/styles/_helpers.scss +153 -0
  263. package/src/styles/_lists.scss +225 -0
  264. package/src/styles/_main.scss +356 -0
  265. package/src/styles/_markdown.scss +88 -0
  266. package/src/styles/_responsive.scss +255 -0
  267. package/src/styles/_show-pages.scss +186 -0
  268. package/src/styles/_sidebar.scss +380 -0
  269. package/src/styles/_states.scss +115 -0
  270. package/src/styles/_stats.scss +47 -0
  271. package/src/styles/_variables.scss +224 -0
  272. package/src/styles/index.scss +38 -0
  273. package/src/styles/theme/_components.scss +331 -0
  274. package/src/styles/theme/_index.scss +10 -0
  275. package/src/styles/theme/_tokens.scss +147 -0
  276. package/src/styles/theme/_utilities.scss +175 -0
  277. package/src/toast/ToastBridgeModule.ts +68 -0
  278. package/src/toast/ToastListener.vue +55 -0
  279. package/src/toast/index.ts +26 -0
  280. package/src/toast/useSignalToast.ts +154 -0
  281. package/src/types/index.ts +285 -0
  282. package/src/types/pluralize.d.ts +19 -0
  283. package/src/types/quarkernel.d.ts +50 -0
  284. package/src/utils/formatters.ts +174 -0
  285. package/src/utils/index.ts +35 -0
  286. package/src/utils/transformers.ts +131 -0
  287. package/src/vite/qdadmDebugPlugin.ts +401 -0
  288. package/src/vite-env.d.ts +50 -0
  289. package/src/zones/ZoneRegistry.ts +934 -0
  290. package/src/zones/index.ts +29 -0
  291. package/src/zones/zones.ts +191 -0
@@ -0,0 +1,341 @@
1
+ /**
2
+ * QueryExecutor - MongoDB-like query execution for arrays
3
+ *
4
+ * Provides client-side filtering with the same query syntax used for API calls.
5
+ * Pure functions, no Vue dependencies, null-safe operations.
6
+ *
7
+ * Supported operators:
8
+ * - Implicit: value → $eq, array → $in
9
+ * - Comparison: $eq, $ne, $gt, $gte, $lt, $lte
10
+ * - Set: $in, $nin, $like, $between
11
+ * - Logical: $or, $and
12
+ *
13
+ * @example
14
+ * // Simple equality (implicit $eq)
15
+ * QueryExecutor.execute(items, { status: 'active' })
16
+ *
17
+ * // Array membership (implicit $in)
18
+ * QueryExecutor.execute(items, { status: ['active', 'pending'] })
19
+ *
20
+ * // Nested field access
21
+ * QueryExecutor.execute(items, { 'author.name': 'John' })
22
+ */
23
+
24
+ /**
25
+ * Query condition can be a primitive, array, or operator object
26
+ */
27
+ export type QueryCondition =
28
+ | string
29
+ | number
30
+ | boolean
31
+ | null
32
+ | undefined
33
+ | unknown[]
34
+ | QueryOperators
35
+
36
+ /**
37
+ * Supported query operators
38
+ */
39
+ export interface QueryOperators {
40
+ $eq?: unknown
41
+ $ne?: unknown
42
+ $gt?: number | string | Date
43
+ $gte?: number | string | Date
44
+ $lt?: number | string | Date
45
+ $lte?: number | string | Date
46
+ $in?: unknown[]
47
+ $nin?: unknown[]
48
+ $like?: string
49
+ $between?: [unknown, unknown]
50
+ }
51
+
52
+ /**
53
+ * Query object with field conditions and logical operators
54
+ */
55
+ export interface QueryObject {
56
+ $or?: QueryObject[]
57
+ $and?: QueryObject[]
58
+ [field: string]: QueryCondition | QueryObject[] | undefined
59
+ }
60
+
61
+ /**
62
+ * Result of query execution
63
+ */
64
+ export interface QueryResult<T> {
65
+ items: T[]
66
+ total: number
67
+ }
68
+
69
+ /**
70
+ * Get nested value from object using dot notation
71
+ *
72
+ * @param path - Dot-separated path (e.g., 'author.name')
73
+ * @param obj - Source object
74
+ * @returns Value at path or undefined if not found
75
+ *
76
+ * @example
77
+ * getNestedValue('author.name', { author: { name: 'John' } }) // → 'John'
78
+ * getNestedValue('author.name', { author: null }) // → undefined
79
+ * getNestedValue('missing.path', {}) // → undefined
80
+ */
81
+ export function getNestedValue(path: string, obj: unknown): unknown {
82
+ if (obj === null || obj === undefined) return undefined
83
+ if (!path || typeof path !== 'string') return undefined
84
+
85
+ const keys = path.split('.')
86
+ let current: unknown = obj
87
+
88
+ for (const key of keys) {
89
+ if (current === null || current === undefined) return undefined
90
+ if (typeof current !== 'object') return undefined
91
+ current = (current as Record<string, unknown>)[key]
92
+ }
93
+
94
+ return current
95
+ }
96
+
97
+ /**
98
+ * Check if a value matches a condition with an operator
99
+ */
100
+ function matchCondition(itemValue: unknown, condition: QueryCondition): boolean {
101
+ // Null/undefined condition matches null/undefined values
102
+ if (condition === null || condition === undefined) {
103
+ return itemValue === condition
104
+ }
105
+
106
+ // Explicit operator object (e.g., { $eq: 'value' })
107
+ if (typeof condition === 'object' && !Array.isArray(condition)) {
108
+ return matchOperator(itemValue, condition as QueryOperators)
109
+ }
110
+
111
+ // Implicit $in for arrays
112
+ if (Array.isArray(condition)) {
113
+ return matchIn(itemValue, condition)
114
+ }
115
+
116
+ // Implicit $eq for primitive values
117
+ return itemValue === condition
118
+ }
119
+
120
+ /**
121
+ * Match a value against an operator object
122
+ */
123
+ function matchOperator(itemValue: unknown, operators: QueryOperators): boolean {
124
+ for (const [op, expected] of Object.entries(operators)) {
125
+ switch (op) {
126
+ case '$eq':
127
+ if (itemValue !== expected) return false
128
+ break
129
+
130
+ case '$ne':
131
+ if (itemValue === expected) return false
132
+ break
133
+
134
+ case '$gt':
135
+ if (itemValue === null || itemValue === undefined) return false
136
+ if (expected === null || expected === undefined) return false
137
+ if (typeof itemValue !== typeof expected) return false
138
+ if (!((itemValue as number | string) > (expected as number | string))) return false
139
+ break
140
+
141
+ case '$gte':
142
+ if (itemValue === null || itemValue === undefined) return false
143
+ if (expected === null || expected === undefined) return false
144
+ if (typeof itemValue !== typeof expected) return false
145
+ if (!((itemValue as number | string) >= (expected as number | string))) return false
146
+ break
147
+
148
+ case '$lt':
149
+ if (itemValue === null || itemValue === undefined) return false
150
+ if (expected === null || expected === undefined) return false
151
+ if (typeof itemValue !== typeof expected) return false
152
+ if (!((itemValue as number | string) < (expected as number | string))) return false
153
+ break
154
+
155
+ case '$lte':
156
+ if (itemValue === null || itemValue === undefined) return false
157
+ if (expected === null || expected === undefined) return false
158
+ if (typeof itemValue !== typeof expected) return false
159
+ if (!((itemValue as number | string) <= (expected as number | string))) return false
160
+ break
161
+
162
+ case '$in':
163
+ if (!matchIn(itemValue, expected as unknown[])) return false
164
+ break
165
+
166
+ case '$nin':
167
+ if (!matchNin(itemValue, expected as unknown[])) return false
168
+ break
169
+
170
+ case '$like':
171
+ if (!matchLike(itemValue, expected as string)) return false
172
+ break
173
+
174
+ case '$between':
175
+ if (!matchBetween(itemValue, expected as [unknown, unknown])) return false
176
+ break
177
+
178
+ default:
179
+ // Unknown operator - skip (fail-safe, return true for this check)
180
+ break
181
+ }
182
+ }
183
+ return true
184
+ }
185
+
186
+ /**
187
+ * Match $in operator - value in array
188
+ */
189
+ function matchIn(itemValue: unknown, values: unknown[]): boolean {
190
+ if (!Array.isArray(values)) return false
191
+ if (values.length === 0) return false
192
+ return values.includes(itemValue)
193
+ }
194
+
195
+ /**
196
+ * Match $nin operator - value not in array
197
+ */
198
+ function matchNin(itemValue: unknown, values: unknown[]): boolean {
199
+ if (!Array.isArray(values)) return true
200
+ if (values.length === 0) return true
201
+ return !values.includes(itemValue)
202
+ }
203
+
204
+ /**
205
+ * Match $like operator - case-insensitive substring match
206
+ */
207
+ function matchLike(itemValue: unknown, pattern: string): boolean {
208
+ if (itemValue === null || itemValue === undefined) return false
209
+ if (pattern === null || pattern === undefined) return false
210
+ const str = String(itemValue).toLowerCase()
211
+ const search = String(pattern).toLowerCase()
212
+ return str.includes(search)
213
+ }
214
+
215
+ /**
216
+ * Match $between operator - inclusive range check
217
+ */
218
+ function matchBetween(itemValue: unknown, range: [unknown, unknown]): boolean {
219
+ if (itemValue === null || itemValue === undefined) return false
220
+ if (!Array.isArray(range) || range.length !== 2) return false
221
+ const [min, max] = range
222
+ return (itemValue as number) >= (min as number) && (itemValue as number) <= (max as number)
223
+ }
224
+
225
+ /**
226
+ * Match an item against a query object
227
+ */
228
+ function matchQuery(item: unknown, query: QueryObject): boolean {
229
+ if (!query || typeof query !== 'object') return true
230
+ if (Object.keys(query).length === 0) return true
231
+
232
+ for (const [key, condition] of Object.entries(query)) {
233
+ // Logical operators
234
+ if (key === '$or') {
235
+ if (!matchOr(item, condition as QueryObject[])) return false
236
+ continue
237
+ }
238
+
239
+ if (key === '$and') {
240
+ if (!matchAnd(item, condition as QueryObject[])) return false
241
+ continue
242
+ }
243
+
244
+ // Field condition
245
+ const itemValue = getNestedValue(key, item)
246
+ if (!matchCondition(itemValue, condition as QueryCondition)) {
247
+ return false
248
+ }
249
+ }
250
+
251
+ return true
252
+ }
253
+
254
+ /**
255
+ * Match $or operator - at least one condition must match
256
+ */
257
+ function matchOr(item: unknown, conditions: QueryObject[]): boolean {
258
+ if (!Array.isArray(conditions)) return true
259
+ if (conditions.length === 0) return true
260
+ return conditions.some((cond) => matchQuery(item, cond))
261
+ }
262
+
263
+ /**
264
+ * Match $and operator - all conditions must match
265
+ */
266
+ function matchAnd(item: unknown, conditions: QueryObject[]): boolean {
267
+ if (!Array.isArray(conditions)) return true
268
+ if (conditions.length === 0) return true
269
+ return conditions.every((cond) => matchQuery(item, cond))
270
+ }
271
+
272
+ /**
273
+ * QueryExecutor - Static class for MongoDB-like array filtering
274
+ */
275
+ export class QueryExecutor {
276
+ /**
277
+ * Execute query on array of items
278
+ *
279
+ * @param items - Source items to filter
280
+ * @param query - MongoDB-like query object
281
+ * @returns Query result with filtered items and total count
282
+ *
283
+ * @example
284
+ * // Filter by status
285
+ * QueryExecutor.execute(books, { status: 'published' })
286
+ * // => { items: [...], total: 5 }
287
+ *
288
+ * // Filter by multiple values
289
+ * QueryExecutor.execute(books, { status: ['draft', 'published'] })
290
+ *
291
+ * // Empty query returns all items
292
+ * QueryExecutor.execute(books, {})
293
+ * // => { items: [...all books...], total: 100 }
294
+ */
295
+ static execute<T>(items: T[], query: QueryObject): QueryResult<T> {
296
+ // Guard against invalid input
297
+ if (!Array.isArray(items)) {
298
+ return { items: [], total: 0 }
299
+ }
300
+
301
+ // Empty query returns all items
302
+ if (!query || typeof query !== 'object' || Object.keys(query).length === 0) {
303
+ return { items: [...items], total: items.length }
304
+ }
305
+
306
+ try {
307
+ const filtered = items.filter((item) => matchQuery(item, query))
308
+ return { items: filtered, total: filtered.length }
309
+ } catch {
310
+ // Return empty for malformed queries (fail-safe)
311
+ return { items: [], total: 0 }
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Check if single item matches query
317
+ *
318
+ * @param item - Item to check
319
+ * @param query - Query object
320
+ * @returns True if item matches query
321
+ *
322
+ * @example
323
+ * const book = { title: 'Vue 3', status: 'published' }
324
+ * QueryExecutor.match(book, { status: 'published' }) // true
325
+ * QueryExecutor.match(book, { status: 'draft' }) // false
326
+ */
327
+ static match<T>(item: T, query: QueryObject): boolean {
328
+ if (item === null || item === undefined) return false
329
+
330
+ // Empty query matches everything
331
+ if (!query || typeof query !== 'object' || Object.keys(query).length === 0) {
332
+ return true
333
+ }
334
+
335
+ try {
336
+ return matchQuery(item, query)
337
+ } catch {
338
+ return false
339
+ }
340
+ }
341
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Query Module - MongoDB-like filtering for arrays
3
+ *
4
+ * Provides client-side filtering with the same query syntax used for API calls.
5
+ */
6
+
7
+ export { QueryExecutor, getNestedValue } from './QueryExecutor'
8
+ export type { QueryCondition, QueryOperators, QueryObject, QueryResult } from './QueryExecutor'
9
+
10
+ export { FilterQuery } from './FilterQuery'
11
+ export type {
12
+ FilterQuerySource,
13
+ FilterOption,
14
+ ValueResolver,
15
+ FilterQueryOptions,
16
+ } from './FilterQuery'