@contember/bindx 0.1.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 (234) hide show
  1. package/dist/adapter/ContemberAdapter.d.ts +40 -0
  2. package/dist/adapter/ContemberAdapter.d.ts.map +1 -0
  3. package/dist/adapter/MockAdapter.d.ts +81 -0
  4. package/dist/adapter/MockAdapter.d.ts.map +1 -0
  5. package/dist/adapter/MockQueryEngine.d.ts +43 -0
  6. package/dist/adapter/MockQueryEngine.d.ts.map +1 -0
  7. package/dist/adapter/index.d.ts +5 -0
  8. package/dist/adapter/index.d.ts.map +1 -0
  9. package/dist/adapter/types.d.ts +184 -0
  10. package/dist/adapter/types.d.ts.map +1 -0
  11. package/dist/brand/ComponentBrand.d.ts +25 -0
  12. package/dist/brand/ComponentBrand.d.ts.map +1 -0
  13. package/dist/contember/index.d.ts +10 -0
  14. package/dist/contember/index.d.ts.map +1 -0
  15. package/dist/core/ActionDispatcher.d.ts +64 -0
  16. package/dist/core/ActionDispatcher.d.ts.map +1 -0
  17. package/dist/core/Disposable.d.ts +57 -0
  18. package/dist/core/Disposable.d.ts.map +1 -0
  19. package/dist/core/EntityLoader.d.ts +80 -0
  20. package/dist/core/EntityLoader.d.ts.map +1 -0
  21. package/dist/core/PersistenceManager.d.ts +126 -0
  22. package/dist/core/PersistenceManager.d.ts.map +1 -0
  23. package/dist/core/SelectionResolver.d.ts +21 -0
  24. package/dist/core/SelectionResolver.d.ts.map +1 -0
  25. package/dist/core/actionClassification.d.ts +48 -0
  26. package/dist/core/actionClassification.d.ts.map +1 -0
  27. package/dist/core/actions.d.ts +301 -0
  28. package/dist/core/actions.d.ts.map +1 -0
  29. package/dist/core/index.d.ts +4 -0
  30. package/dist/core/index.d.ts.map +1 -0
  31. package/dist/dataview/filterHandlers.d.ts +18 -0
  32. package/dist/dataview/filterHandlers.d.ts.map +1 -0
  33. package/dist/dataview/index.d.ts +3 -0
  34. package/dist/dataview/index.d.ts.map +1 -0
  35. package/dist/dataview/types.d.ts +164 -0
  36. package/dist/dataview/types.d.ts.map +1 -0
  37. package/dist/errors/UnfetchedFieldError.d.ts +11 -0
  38. package/dist/errors/UnfetchedFieldError.d.ts.map +1 -0
  39. package/dist/errors/errorPathResolver.d.ts +52 -0
  40. package/dist/errors/errorPathResolver.d.ts.map +1 -0
  41. package/dist/errors/index.d.ts +5 -0
  42. package/dist/errors/index.d.ts.map +1 -0
  43. package/dist/errors/pathMapper.d.ts +42 -0
  44. package/dist/errors/pathMapper.d.ts.map +1 -0
  45. package/dist/errors/types.d.ts +131 -0
  46. package/dist/errors/types.d.ts.map +1 -0
  47. package/dist/events/EventEmitter.d.ts +97 -0
  48. package/dist/events/EventEmitter.d.ts.map +1 -0
  49. package/dist/events/eventFactory.d.ts +40 -0
  50. package/dist/events/eventFactory.d.ts.map +1 -0
  51. package/dist/events/index.d.ts +15 -0
  52. package/dist/events/index.d.ts.map +1 -0
  53. package/dist/events/types.d.ts +264 -0
  54. package/dist/events/types.d.ts.map +1 -0
  55. package/dist/handles/BaseHandle.d.ts +64 -0
  56. package/dist/handles/BaseHandle.d.ts.map +1 -0
  57. package/dist/handles/EntityHandle.d.ts +208 -0
  58. package/dist/handles/EntityHandle.d.ts.map +1 -0
  59. package/dist/handles/FieldHandle.d.ts +111 -0
  60. package/dist/handles/FieldHandle.d.ts.map +1 -0
  61. package/dist/handles/HasManyListHandle.d.ts +166 -0
  62. package/dist/handles/HasManyListHandle.d.ts.map +1 -0
  63. package/dist/handles/HasOneHandle.d.ts +168 -0
  64. package/dist/handles/HasOneHandle.d.ts.map +1 -0
  65. package/dist/handles/PlaceholderHandle.d.ts +117 -0
  66. package/dist/handles/PlaceholderHandle.d.ts.map +1 -0
  67. package/dist/handles/index.d.ts +8 -0
  68. package/dist/handles/index.d.ts.map +1 -0
  69. package/dist/handles/proxyFactory.d.ts +66 -0
  70. package/dist/handles/proxyFactory.d.ts.map +1 -0
  71. package/dist/handles/types.d.ts +430 -0
  72. package/dist/handles/types.d.ts.map +1 -0
  73. package/dist/index.d.ts +53 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/internal.d.ts +11 -0
  76. package/dist/internal.d.ts.map +1 -0
  77. package/dist/persistence/BatchPersister.d.ts +142 -0
  78. package/dist/persistence/BatchPersister.d.ts.map +1 -0
  79. package/dist/persistence/ChangeRegistry.d.ts +87 -0
  80. package/dist/persistence/ChangeRegistry.d.ts.map +1 -0
  81. package/dist/persistence/ContemberSchemaMutationAdapter.d.ts +16 -0
  82. package/dist/persistence/ContemberSchemaMutationAdapter.d.ts.map +1 -0
  83. package/dist/persistence/MutationCollector.d.ts +96 -0
  84. package/dist/persistence/MutationCollector.d.ts.map +1 -0
  85. package/dist/persistence/MutationSchemaProvider.d.ts +30 -0
  86. package/dist/persistence/MutationSchemaProvider.d.ts.map +1 -0
  87. package/dist/persistence/index.d.ts +7 -0
  88. package/dist/persistence/index.d.ts.map +1 -0
  89. package/dist/persistence/types.d.ts +189 -0
  90. package/dist/persistence/types.d.ts.map +1 -0
  91. package/dist/roles/RoleSchemaRegistry.d.ts +77 -0
  92. package/dist/roles/RoleSchemaRegistry.d.ts.map +1 -0
  93. package/dist/roles/index.d.ts +11 -0
  94. package/dist/roles/index.d.ts.map +1 -0
  95. package/dist/roles/types.d.ts +242 -0
  96. package/dist/roles/types.d.ts.map +1 -0
  97. package/dist/schema/ContemberSchema.d.ts +98 -0
  98. package/dist/schema/ContemberSchema.d.ts.map +1 -0
  99. package/dist/schema/SchemaLoader.d.ts +28 -0
  100. package/dist/schema/SchemaLoader.d.ts.map +1 -0
  101. package/dist/schema/SchemaRegistry.d.ts +95 -0
  102. package/dist/schema/SchemaRegistry.d.ts.map +1 -0
  103. package/dist/schema/index.d.ts +6 -0
  104. package/dist/schema/index.d.ts.map +1 -0
  105. package/dist/schema/roles.d.ts +62 -0
  106. package/dist/schema/roles.d.ts.map +1 -0
  107. package/dist/schema/types.d.ts +179 -0
  108. package/dist/schema/types.d.ts.map +1 -0
  109. package/dist/selection/SelectionMetaCollector.d.ts +32 -0
  110. package/dist/selection/SelectionMetaCollector.d.ts.map +1 -0
  111. package/dist/selection/SelectionScope.d.ts +77 -0
  112. package/dist/selection/SelectionScope.d.ts.map +1 -0
  113. package/dist/selection/buildQuery.d.ts +45 -0
  114. package/dist/selection/buildQuery.d.ts.map +1 -0
  115. package/dist/selection/createFragment.d.ts +29 -0
  116. package/dist/selection/createFragment.d.ts.map +1 -0
  117. package/dist/selection/createSelectionBuilder.d.ts +18 -0
  118. package/dist/selection/createSelectionBuilder.d.ts.map +1 -0
  119. package/dist/selection/index.d.ts +8 -0
  120. package/dist/selection/index.d.ts.map +1 -0
  121. package/dist/selection/queryTypes.d.ts +84 -0
  122. package/dist/selection/queryTypes.d.ts.map +1 -0
  123. package/dist/selection/types.d.ts +238 -0
  124. package/dist/selection/types.d.ts.map +1 -0
  125. package/dist/store/DirtyTracker.d.ts +26 -0
  126. package/dist/store/DirtyTracker.d.ts.map +1 -0
  127. package/dist/store/EntityMetaStore.d.ts +52 -0
  128. package/dist/store/EntityMetaStore.d.ts.map +1 -0
  129. package/dist/store/EntitySnapshotStore.d.ts +63 -0
  130. package/dist/store/EntitySnapshotStore.d.ts.map +1 -0
  131. package/dist/store/ErrorStore.d.ts +82 -0
  132. package/dist/store/ErrorStore.d.ts.map +1 -0
  133. package/dist/store/RelationStore.d.ts +175 -0
  134. package/dist/store/RelationStore.d.ts.map +1 -0
  135. package/dist/store/SnapshotStore.d.ts +143 -0
  136. package/dist/store/SnapshotStore.d.ts.map +1 -0
  137. package/dist/store/SubscriptionManager.d.ts +90 -0
  138. package/dist/store/SubscriptionManager.d.ts.map +1 -0
  139. package/dist/store/TouchedStore.d.ts +24 -0
  140. package/dist/store/TouchedStore.d.ts.map +1 -0
  141. package/dist/store/entityId.d.ts +29 -0
  142. package/dist/store/entityId.d.ts.map +1 -0
  143. package/dist/store/index.d.ts +1 -0
  144. package/dist/store/index.d.ts.map +1 -0
  145. package/dist/store/snapshots.d.ts +144 -0
  146. package/dist/store/snapshots.d.ts.map +1 -0
  147. package/dist/undo/UndoManager.d.ts +120 -0
  148. package/dist/undo/UndoManager.d.ts.map +1 -0
  149. package/dist/undo/index.d.ts +3 -0
  150. package/dist/undo/index.d.ts.map +1 -0
  151. package/dist/undo/types.d.ts +74 -0
  152. package/dist/undo/types.d.ts.map +1 -0
  153. package/dist/utils/aliasGenerator.d.ts +25 -0
  154. package/dist/utils/aliasGenerator.d.ts.map +1 -0
  155. package/dist/utils/deepEqual.d.ts +6 -0
  156. package/dist/utils/deepEqual.d.ts.map +1 -0
  157. package/dist/utils/index.d.ts +3 -0
  158. package/dist/utils/index.d.ts.map +1 -0
  159. package/package.json +29 -0
  160. package/src/adapter/ContemberAdapter.ts +239 -0
  161. package/src/adapter/MockAdapter.ts +394 -0
  162. package/src/adapter/MockQueryEngine.ts +236 -0
  163. package/src/adapter/index.ts +21 -0
  164. package/src/adapter/types.ts +199 -0
  165. package/src/brand/ComponentBrand.ts +28 -0
  166. package/src/contember/index.ts +17 -0
  167. package/src/core/ActionDispatcher.ts +450 -0
  168. package/src/core/Disposable.ts +126 -0
  169. package/src/core/EntityLoader.ts +169 -0
  170. package/src/core/SelectionResolver.ts +78 -0
  171. package/src/core/actionClassification.ts +196 -0
  172. package/src/core/actions.ts +491 -0
  173. package/src/core/index.ts +20 -0
  174. package/src/dataview/filterHandlers.ts +443 -0
  175. package/src/dataview/index.ts +36 -0
  176. package/src/dataview/types.ts +220 -0
  177. package/src/errors/UnfetchedFieldError.ts +18 -0
  178. package/src/errors/errorPathResolver.ts +239 -0
  179. package/src/errors/index.ts +40 -0
  180. package/src/errors/pathMapper.ts +40 -0
  181. package/src/errors/types.ts +223 -0
  182. package/src/events/EventEmitter.ts +447 -0
  183. package/src/events/eventFactory.ts +430 -0
  184. package/src/events/index.ts +89 -0
  185. package/src/events/types.ts +374 -0
  186. package/src/handles/BaseHandle.ts +100 -0
  187. package/src/handles/EntityHandle.ts +538 -0
  188. package/src/handles/FieldHandle.ts +268 -0
  189. package/src/handles/HasManyListHandle.ts +500 -0
  190. package/src/handles/HasOneHandle.ts +500 -0
  191. package/src/handles/PlaceholderHandle.ts +321 -0
  192. package/src/handles/index.ts +37 -0
  193. package/src/handles/proxyFactory.ts +218 -0
  194. package/src/handles/types.ts +743 -0
  195. package/src/index.ts +384 -0
  196. package/src/internal.ts +19 -0
  197. package/src/persistence/BatchPersister.ts +911 -0
  198. package/src/persistence/ChangeRegistry.ts +150 -0
  199. package/src/persistence/ContemberSchemaMutationAdapter.ts +57 -0
  200. package/src/persistence/MutationCollector.ts +567 -0
  201. package/src/persistence/MutationSchemaProvider.ts +33 -0
  202. package/src/persistence/index.ts +23 -0
  203. package/src/persistence/types.ts +220 -0
  204. package/src/schema/ContemberSchema.ts +132 -0
  205. package/src/schema/SchemaLoader.ts +93 -0
  206. package/src/schema/SchemaRegistry.ts +286 -0
  207. package/src/schema/index.ts +51 -0
  208. package/src/schema/roles.ts +83 -0
  209. package/src/schema/types.ts +238 -0
  210. package/src/selection/SelectionMetaCollector.ts +122 -0
  211. package/src/selection/SelectionScope.ts +208 -0
  212. package/src/selection/buildQuery.ts +134 -0
  213. package/src/selection/createFragment.ts +50 -0
  214. package/src/selection/createSelectionBuilder.ts +283 -0
  215. package/src/selection/index.ts +31 -0
  216. package/src/selection/queryTypes.ts +135 -0
  217. package/src/selection/types.ts +320 -0
  218. package/src/store/DirtyTracker.ts +117 -0
  219. package/src/store/EntityMetaStore.ts +143 -0
  220. package/src/store/EntitySnapshotStore.ts +260 -0
  221. package/src/store/ErrorStore.ts +251 -0
  222. package/src/store/RelationStore.ts +696 -0
  223. package/src/store/SnapshotStore.ts +765 -0
  224. package/src/store/SubscriptionManager.ts +241 -0
  225. package/src/store/TouchedStore.ts +42 -0
  226. package/src/store/entityId.ts +43 -0
  227. package/src/store/index.ts +2 -0
  228. package/src/store/snapshots.ts +203 -0
  229. package/src/undo/UndoManager.ts +418 -0
  230. package/src/undo/index.ts +9 -0
  231. package/src/undo/types.ts +84 -0
  232. package/src/utils/aliasGenerator.ts +56 -0
  233. package/src/utils/deepEqual.ts +33 -0
  234. package/src/utils/index.ts +2 -0
@@ -0,0 +1,443 @@
1
+ /**
2
+ * Built-in filter handler implementations.
3
+ *
4
+ * Each handler converts a typed filter artifact into a Contember-style
5
+ * EntityWhere clause for use with the backend adapter.
6
+ */
7
+
8
+ import type {
9
+ FilterHandler,
10
+ TextFilterArtifact,
11
+ NumberFilterArtifact,
12
+ NumberRangeFilterArtifact,
13
+ DateFilterArtifact,
14
+ BooleanFilterArtifact,
15
+ EnumFilterArtifact,
16
+ EnumListFilterArtifact,
17
+ RelationFilterArtifact,
18
+ IsDefinedFilterArtifact,
19
+ } from './types.js'
20
+
21
+ // ============================================================================
22
+ // Text Filter
23
+ // ============================================================================
24
+
25
+ export function createTextFilterHandler(fieldPath: string): FilterHandler<TextFilterArtifact> {
26
+ return {
27
+ defaultArtifact(): TextFilterArtifact {
28
+ return { mode: 'contains', query: '' }
29
+ },
30
+
31
+ isActive(artifact: TextFilterArtifact): boolean {
32
+ return artifact.query.length > 0 || artifact.nullCondition !== undefined
33
+ },
34
+
35
+ toWhere(artifact: TextFilterArtifact): Record<string, unknown> | undefined {
36
+ if (!this.isActive(artifact)) return undefined
37
+
38
+ const conditions: Record<string, unknown>[] = []
39
+
40
+ if (artifact.query.length > 0) {
41
+ const fieldCondition = buildTextCondition(artifact.mode, artifact.query)
42
+ conditions.push(buildNestedWhere(fieldPath, fieldCondition))
43
+ }
44
+
45
+ if (artifact.nullCondition !== undefined) {
46
+ conditions.push(buildNestedWhere(fieldPath, { isNull: artifact.nullCondition }))
47
+ }
48
+
49
+ if (conditions.length === 0) return undefined
50
+ if (conditions.length === 1) return conditions[0]
51
+ return { and: conditions }
52
+ },
53
+ }
54
+ }
55
+
56
+ function buildTextCondition(mode: TextFilterArtifact['mode'], query: string): Record<string, unknown> {
57
+ switch (mode) {
58
+ case 'contains':
59
+ return { containsCI: query }
60
+ case 'startsWith':
61
+ return { startsWithCI: query }
62
+ case 'endsWith':
63
+ return { endsWithCI: query }
64
+ case 'equals':
65
+ return { eq: query }
66
+ case 'notContains':
67
+ return { not: { containsCI: query } }
68
+ }
69
+ }
70
+
71
+ // ============================================================================
72
+ // Full Text Filter (across multiple fields)
73
+ // ============================================================================
74
+
75
+ export function createFullTextFilterHandler(fieldPaths: readonly string[]): FilterHandler<TextFilterArtifact> {
76
+ return {
77
+ defaultArtifact(): TextFilterArtifact {
78
+ return { mode: 'contains', query: '' }
79
+ },
80
+
81
+ isActive(artifact: TextFilterArtifact): boolean {
82
+ return artifact.query.length > 0
83
+ },
84
+
85
+ toWhere(artifact: TextFilterArtifact): Record<string, unknown> | undefined {
86
+ if (!this.isActive(artifact)) return undefined
87
+
88
+ const conditions = fieldPaths.map(fieldPath => {
89
+ const fieldCondition = buildTextCondition(artifact.mode, artifact.query)
90
+ return buildNestedWhere(fieldPath, fieldCondition)
91
+ })
92
+
93
+ if (conditions.length === 0) return undefined
94
+ if (conditions.length === 1) return conditions[0]
95
+ return { or: conditions }
96
+ },
97
+ }
98
+ }
99
+
100
+ // ============================================================================
101
+ // Number Filter
102
+ // ============================================================================
103
+
104
+ export function createNumberFilterHandler(fieldPath: string): FilterHandler<NumberFilterArtifact> {
105
+ return {
106
+ defaultArtifact(): NumberFilterArtifact {
107
+ return { mode: 'eq', value: null }
108
+ },
109
+
110
+ isActive(artifact: NumberFilterArtifact): boolean {
111
+ return artifact.value !== null || artifact.nullCondition !== undefined
112
+ },
113
+
114
+ toWhere(artifact: NumberFilterArtifact): Record<string, unknown> | undefined {
115
+ if (!this.isActive(artifact)) return undefined
116
+
117
+ const conditions: Record<string, unknown>[] = []
118
+
119
+ if (artifact.value !== null) {
120
+ conditions.push(buildNestedWhere(fieldPath, { [artifact.mode]: artifact.value }))
121
+ }
122
+
123
+ if (artifact.nullCondition !== undefined) {
124
+ conditions.push(buildNestedWhere(fieldPath, { isNull: artifact.nullCondition }))
125
+ }
126
+
127
+ if (conditions.length === 0) return undefined
128
+ if (conditions.length === 1) return conditions[0]
129
+ return { and: conditions }
130
+ },
131
+ }
132
+ }
133
+
134
+ // ============================================================================
135
+ // Number Range Filter
136
+ // ============================================================================
137
+
138
+ export function createNumberRangeFilterHandler(fieldPath: string): FilterHandler<NumberRangeFilterArtifact> {
139
+ return {
140
+ defaultArtifact(): NumberRangeFilterArtifact {
141
+ return { min: null, max: null }
142
+ },
143
+
144
+ isActive(artifact: NumberRangeFilterArtifact): boolean {
145
+ return artifact.min !== null || artifact.max !== null || artifact.nullCondition !== undefined
146
+ },
147
+
148
+ toWhere(artifact: NumberRangeFilterArtifact): Record<string, unknown> | undefined {
149
+ if (!this.isActive(artifact)) return undefined
150
+
151
+ const conditions: Record<string, unknown>[] = []
152
+
153
+ if (artifact.min !== null) {
154
+ conditions.push(buildNestedWhere(fieldPath, { gte: artifact.min }))
155
+ }
156
+
157
+ if (artifact.max !== null) {
158
+ conditions.push(buildNestedWhere(fieldPath, { lte: artifact.max }))
159
+ }
160
+
161
+ if (artifact.nullCondition !== undefined) {
162
+ conditions.push(buildNestedWhere(fieldPath, { isNull: artifact.nullCondition }))
163
+ }
164
+
165
+ if (conditions.length === 0) return undefined
166
+ if (conditions.length === 1) return conditions[0]
167
+ return { and: conditions }
168
+ },
169
+ }
170
+ }
171
+
172
+ // ============================================================================
173
+ // Date Filter
174
+ // ============================================================================
175
+
176
+ export function createDateFilterHandler(fieldPath: string): FilterHandler<DateFilterArtifact> {
177
+ return {
178
+ defaultArtifact(): DateFilterArtifact {
179
+ return { start: null, end: null }
180
+ },
181
+
182
+ isActive(artifact: DateFilterArtifact): boolean {
183
+ return artifact.start !== null || artifact.end !== null || artifact.nullCondition !== undefined
184
+ },
185
+
186
+ toWhere(artifact: DateFilterArtifact): Record<string, unknown> | undefined {
187
+ if (!this.isActive(artifact)) return undefined
188
+
189
+ const conditions: Record<string, unknown>[] = []
190
+
191
+ if (artifact.start !== null) {
192
+ const normalized = new Date(artifact.start + 'T00:00:00')
193
+ if (!isNaN(normalized.getTime())) {
194
+ conditions.push(buildNestedWhere(fieldPath, { gte: toLocalIsoString(normalized) }))
195
+ }
196
+ }
197
+
198
+ if (artifact.end !== null) {
199
+ const normalized = new Date(artifact.end + 'T00:00:00')
200
+ if (!isNaN(normalized.getTime())) {
201
+ // End date is inclusive — advance to start of next day
202
+ normalized.setDate(normalized.getDate() + 1)
203
+ conditions.push(buildNestedWhere(fieldPath, { lt: toLocalIsoString(normalized) }))
204
+ }
205
+ }
206
+
207
+ if (artifact.nullCondition !== undefined) {
208
+ conditions.push(buildNestedWhere(fieldPath, { isNull: artifact.nullCondition }))
209
+ }
210
+
211
+ if (conditions.length === 0) return undefined
212
+ if (conditions.length === 1) return conditions[0]
213
+ return { and: conditions }
214
+ },
215
+ }
216
+ }
217
+
218
+ // ============================================================================
219
+ // Boolean Filter
220
+ // ============================================================================
221
+
222
+ export function createBooleanFilterHandler(fieldPath: string): FilterHandler<BooleanFilterArtifact> {
223
+ return {
224
+ defaultArtifact(): BooleanFilterArtifact {
225
+ return {}
226
+ },
227
+
228
+ isActive(artifact: BooleanFilterArtifact): boolean {
229
+ return artifact.includeTrue === true || artifact.includeFalse === true || artifact.nullCondition !== undefined
230
+ },
231
+
232
+ toWhere(artifact: BooleanFilterArtifact): Record<string, unknown> | undefined {
233
+ if (!this.isActive(artifact)) return undefined
234
+
235
+ const conditions: Record<string, unknown>[] = []
236
+
237
+ if (artifact.includeTrue && artifact.includeFalse) {
238
+ // Both selected — matches all, no field condition needed
239
+ } else if (artifact.includeTrue) {
240
+ conditions.push(buildNestedWhere(fieldPath, { eq: true }))
241
+ } else if (artifact.includeFalse) {
242
+ conditions.push(buildNestedWhere(fieldPath, { eq: false }))
243
+ }
244
+
245
+ if (artifact.nullCondition !== undefined) {
246
+ conditions.push(buildNestedWhere(fieldPath, { isNull: artifact.nullCondition }))
247
+ }
248
+
249
+ if (conditions.length === 0) return undefined
250
+ if (conditions.length === 1) return conditions[0]
251
+ return { and: conditions }
252
+ },
253
+ }
254
+ }
255
+
256
+ // ============================================================================
257
+ // Enum Filter
258
+ // ============================================================================
259
+
260
+ export function createEnumFilterHandler(fieldPath: string): FilterHandler<EnumFilterArtifact> {
261
+ return {
262
+ defaultArtifact(): EnumFilterArtifact {
263
+ return {}
264
+ },
265
+
266
+ isActive(artifact: EnumFilterArtifact): boolean {
267
+ return (artifact.values !== undefined && artifact.values.length > 0)
268
+ || (artifact.notValues !== undefined && artifact.notValues.length > 0)
269
+ || artifact.nullCondition !== undefined
270
+ },
271
+
272
+ toWhere(artifact: EnumFilterArtifact): Record<string, unknown> | undefined {
273
+ if (!this.isActive(artifact)) return undefined
274
+
275
+ const conditions: Record<string, unknown>[] = []
276
+
277
+ if (artifact.values !== undefined && artifact.values.length > 0) {
278
+ conditions.push(buildNestedWhere(fieldPath, { in: artifact.values }))
279
+ }
280
+
281
+ if (artifact.notValues !== undefined && artifact.notValues.length > 0) {
282
+ conditions.push(buildNestedWhere(fieldPath, { notIn: artifact.notValues }))
283
+ }
284
+
285
+ if (artifact.nullCondition !== undefined) {
286
+ conditions.push(buildNestedWhere(fieldPath, { isNull: artifact.nullCondition }))
287
+ }
288
+
289
+ if (conditions.length === 0) return undefined
290
+ if (conditions.length === 1) return conditions[0]
291
+ return { and: conditions }
292
+ },
293
+ }
294
+ }
295
+
296
+ // ============================================================================
297
+ // Enum List Filter (for array enum fields)
298
+ // ============================================================================
299
+
300
+ export function createEnumListFilterHandler(fieldPath: string): FilterHandler<EnumListFilterArtifact> {
301
+ return {
302
+ defaultArtifact(): EnumListFilterArtifact {
303
+ return {}
304
+ },
305
+
306
+ isActive(artifact: EnumListFilterArtifact): boolean {
307
+ return (artifact.values !== undefined && artifact.values.length > 0)
308
+ || (artifact.notValues !== undefined && artifact.notValues.length > 0)
309
+ || artifact.nullCondition !== undefined
310
+ },
311
+
312
+ toWhere(artifact: EnumListFilterArtifact): Record<string, unknown> | undefined {
313
+ if (!this.isActive(artifact)) return undefined
314
+
315
+ const conditions: Record<string, unknown>[] = []
316
+
317
+ if (artifact.values !== undefined && artifact.values.length > 0) {
318
+ const valueConds = artifact.values.map(v =>
319
+ buildNestedWhere(fieldPath, { includes: v }),
320
+ )
321
+ conditions.push(valueConds.length === 1 ? valueConds[0]! : { or: valueConds })
322
+ }
323
+
324
+ if (artifact.notValues !== undefined && artifact.notValues.length > 0) {
325
+ const valueConds = artifact.notValues.map(v =>
326
+ buildNestedWhere(fieldPath, { includes: v }),
327
+ )
328
+ const inner = valueConds.length === 1 ? valueConds[0]! : { or: valueConds }
329
+ conditions.push({ not: inner })
330
+ }
331
+
332
+ if (artifact.nullCondition !== undefined) {
333
+ conditions.push(buildNestedWhere(fieldPath, { isNull: artifact.nullCondition }))
334
+ }
335
+
336
+ if (conditions.length === 0) return undefined
337
+ if (conditions.length === 1) return conditions[0]
338
+ return { and: conditions }
339
+ },
340
+ }
341
+ }
342
+
343
+ // ============================================================================
344
+ // Relation Filter (HasOne / HasMany)
345
+ // ============================================================================
346
+
347
+ export function createRelationFilterHandler(fieldPath: string): FilterHandler<RelationFilterArtifact> {
348
+ return {
349
+ defaultArtifact(): RelationFilterArtifact {
350
+ return {}
351
+ },
352
+
353
+ isActive(artifact: RelationFilterArtifact): boolean {
354
+ return (artifact.id !== undefined && artifact.id.length > 0)
355
+ || (artifact.notId !== undefined && artifact.notId.length > 0)
356
+ || artifact.nullCondition !== undefined
357
+ },
358
+
359
+ toWhere(artifact: RelationFilterArtifact): Record<string, unknown> | undefined {
360
+ if (!this.isActive(artifact)) return undefined
361
+
362
+ const conditions: Record<string, unknown>[] = []
363
+
364
+ if (artifact.id !== undefined && artifact.id.length > 0) {
365
+ const idCondition = artifact.id.length === 1
366
+ ? { eq: artifact.id[0] }
367
+ : { in: artifact.id }
368
+ conditions.push(buildNestedWhere(fieldPath, { id: idCondition }))
369
+ }
370
+
371
+ if (artifact.notId !== undefined && artifact.notId.length > 0) {
372
+ const idCondition = artifact.notId.length === 1
373
+ ? { eq: artifact.notId[0] }
374
+ : { in: artifact.notId }
375
+ conditions.push({ not: buildNestedWhere(fieldPath, { id: idCondition }) })
376
+ }
377
+
378
+ if (artifact.nullCondition !== undefined) {
379
+ conditions.push(buildNestedWhere(fieldPath, { id: { isNull: artifact.nullCondition } }))
380
+ }
381
+
382
+ if (conditions.length === 0) return undefined
383
+ if (conditions.length === 1) return conditions[0]
384
+ return { and: conditions }
385
+ },
386
+ }
387
+ }
388
+
389
+ // ============================================================================
390
+ // IsDefined Filter
391
+ // ============================================================================
392
+
393
+ export function createIsDefinedFilterHandler(fieldPath: string): FilterHandler<IsDefinedFilterArtifact> {
394
+ return {
395
+ defaultArtifact(): IsDefinedFilterArtifact {
396
+ return { defined: null }
397
+ },
398
+
399
+ isActive(artifact: IsDefinedFilterArtifact): boolean {
400
+ return artifact.defined !== null
401
+ },
402
+
403
+ toWhere(artifact: IsDefinedFilterArtifact): Record<string, unknown> | undefined {
404
+ if (artifact.defined === null) return undefined
405
+ return buildNestedWhere(fieldPath, { isNull: !artifact.defined })
406
+ },
407
+ }
408
+ }
409
+
410
+ // ============================================================================
411
+ // Helpers
412
+ // ============================================================================
413
+
414
+ /**
415
+ * Format a Date as ISO string with local timezone offset.
416
+ * e.g. 2024-01-15T00:00:00+01:00
417
+ */
418
+ function toLocalIsoString(date: Date): string {
419
+ const tzo = -date.getTimezoneOffset()
420
+ const sign = tzo >= 0 ? '+' : '-'
421
+ const pad = (n: number, len = 2): string => String(n).padStart(len, '0')
422
+
423
+ return (
424
+ pad(date.getFullYear(), 4) + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate()) +
425
+ 'T' + pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds()) +
426
+ sign + pad(Math.floor(Math.abs(tzo) / 60)) + ':' + pad(Math.abs(tzo) % 60)
427
+ )
428
+ }
429
+
430
+ /**
431
+ * Builds a nested where clause from a dot-separated field path.
432
+ * e.g., "author.name" → { author: { name: condition } }
433
+ */
434
+ function buildNestedWhere(fieldPath: string, condition: Record<string, unknown>): Record<string, unknown> {
435
+ const parts = fieldPath.split('.')
436
+ let result = condition
437
+
438
+ for (let i = parts.length - 1; i >= 0; i--) {
439
+ result = { [parts[i]!]: result }
440
+ }
441
+
442
+ return result
443
+ }
@@ -0,0 +1,36 @@
1
+ export type {
2
+ TextFilterArtifact,
3
+ NumberFilterArtifact,
4
+ NumberRangeFilterArtifact,
5
+ DateFilterArtifact,
6
+ BooleanFilterArtifact,
7
+ EnumFilterArtifact,
8
+ EnumListFilterArtifact,
9
+ RelationFilterArtifact,
10
+ IsDefinedFilterArtifact,
11
+ FilterArtifact,
12
+ FilterHandler,
13
+ ColumnType,
14
+ ColumnDefinition,
15
+ SortingDirections,
16
+ SortingDirectionAction,
17
+ SortingState,
18
+ PagingState,
19
+ PagingInfo,
20
+ DataViewLayout,
21
+ SelectionValues,
22
+ SelectionState,
23
+ } from './types.js'
24
+
25
+ export {
26
+ createTextFilterHandler,
27
+ createFullTextFilterHandler,
28
+ createNumberFilterHandler,
29
+ createNumberRangeFilterHandler,
30
+ createDateFilterHandler,
31
+ createBooleanFilterHandler,
32
+ createEnumFilterHandler,
33
+ createEnumListFilterHandler,
34
+ createRelationFilterHandler,
35
+ createIsDefinedFilterHandler,
36
+ } from './filterHandlers.js'
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Core types for the DataView/DataGrid system.
3
+ *
4
+ * These types are framework-agnostic and define the filter artifact system,
5
+ * filter handlers, sorting state, and paging state.
6
+ */
7
+
8
+ import type { ReactNode } from 'react'
9
+ import type { EntityWhere, EntityOrderBy, OrderDirection } from '../selection/queryTypes.js'
10
+
11
+ // ============================================================================
12
+ // Filter Artifact Types
13
+ // ============================================================================
14
+
15
+ /**
16
+ * Text filter artifact - stored as user's filter state
17
+ */
18
+ export interface TextFilterArtifact {
19
+ readonly mode: 'contains' | 'startsWith' | 'endsWith' | 'equals' | 'notContains'
20
+ readonly query: string
21
+ readonly nullCondition?: boolean
22
+ }
23
+
24
+ /**
25
+ * Number filter artifact
26
+ */
27
+ export interface NumberFilterArtifact {
28
+ readonly mode: 'eq' | 'gt' | 'gte' | 'lt' | 'lte'
29
+ readonly value: number | null
30
+ readonly nullCondition?: boolean
31
+ }
32
+
33
+ /**
34
+ * Number range filter artifact
35
+ */
36
+ export interface NumberRangeFilterArtifact {
37
+ readonly min: number | null
38
+ readonly max: number | null
39
+ readonly nullCondition?: boolean
40
+ }
41
+
42
+ /**
43
+ * Date filter artifact
44
+ */
45
+ export interface DateFilterArtifact {
46
+ readonly start: string | null
47
+ readonly end: string | null
48
+ readonly nullCondition?: boolean
49
+ }
50
+
51
+ /**
52
+ * Boolean filter artifact
53
+ */
54
+ export interface BooleanFilterArtifact {
55
+ readonly includeTrue?: boolean
56
+ readonly includeFalse?: boolean
57
+ readonly nullCondition?: boolean
58
+ }
59
+
60
+ /**
61
+ * Enum filter artifact
62
+ */
63
+ export interface EnumFilterArtifact {
64
+ readonly values?: readonly string[]
65
+ readonly notValues?: readonly string[]
66
+ readonly nullCondition?: boolean
67
+ }
68
+
69
+ /**
70
+ * Relation filter artifact (hasOne/hasMany)
71
+ */
72
+ export interface RelationFilterArtifact {
73
+ readonly id?: readonly string[]
74
+ readonly notId?: readonly string[]
75
+ readonly nullCondition?: boolean
76
+ }
77
+
78
+ /**
79
+ * Enum list filter artifact - for array enum fields (uses 'includes' condition)
80
+ */
81
+ export interface EnumListFilterArtifact {
82
+ readonly values?: readonly string[]
83
+ readonly notValues?: readonly string[]
84
+ readonly nullCondition?: boolean
85
+ }
86
+
87
+ /**
88
+ * IsDefined filter artifact
89
+ */
90
+ export interface IsDefinedFilterArtifact {
91
+ readonly defined: boolean | null
92
+ }
93
+
94
+ /**
95
+ * Union of all filter artifact types
96
+ */
97
+ export type FilterArtifact =
98
+ | TextFilterArtifact
99
+ | NumberFilterArtifact
100
+ | NumberRangeFilterArtifact
101
+ | DateFilterArtifact
102
+ | BooleanFilterArtifact
103
+ | EnumFilterArtifact
104
+ | EnumListFilterArtifact
105
+ | RelationFilterArtifact
106
+ | IsDefinedFilterArtifact
107
+
108
+ // ============================================================================
109
+ // Filter Handler
110
+ // ============================================================================
111
+
112
+ /**
113
+ * Filter handler converts a filter artifact into an EntityWhere clause.
114
+ * Returns undefined when the filter is not active (artifact is empty/default).
115
+ */
116
+ export interface FilterHandler<TArtifact = FilterArtifact> {
117
+ /** Convert artifact to where clause */
118
+ toWhere(artifact: TArtifact): Record<string, unknown> | undefined
119
+
120
+ /** Check if artifact represents an active filter */
121
+ isActive(artifact: TArtifact): boolean
122
+
123
+ /** Create default (empty/inactive) artifact */
124
+ defaultArtifact(): TArtifact
125
+ }
126
+
127
+ // ============================================================================
128
+ // Column Definition Types
129
+ // ============================================================================
130
+
131
+ /**
132
+ * Column type discriminator
133
+ */
134
+ export type ColumnType = 'text' | 'number' | 'date' | 'dateTime' | 'boolean' | 'enum' | 'enumList' | 'uuid' | 'isDefined' | 'hasOne' | 'hasMany' | 'action' | 'custom'
135
+
136
+ /**
137
+ * Base column definition - framework-agnostic metadata
138
+ */
139
+ export interface ColumnDefinition {
140
+ /** Column type */
141
+ readonly type: ColumnType
142
+ /** Field name on the entity (null for action columns) */
143
+ readonly field: string | null
144
+ /** Whether this column supports sorting */
145
+ readonly sortable: boolean
146
+ /** Filter handler for this column (null if no filtering) */
147
+ readonly filterHandler: FilterHandler | null
148
+ /** Initial filter artifact (null if no filtering) */
149
+ readonly filterArtifact: FilterArtifact | null
150
+ }
151
+
152
+ // ============================================================================
153
+ // Sorting State
154
+ // ============================================================================
155
+
156
+ /**
157
+ * Sorting directions - map of field → direction.
158
+ * Supports multi-column sorting (order of keys = sort priority).
159
+ */
160
+ export type SortingDirections = Readonly<Record<string, OrderDirection>>
161
+
162
+ /**
163
+ * Sorting direction action for triggers
164
+ */
165
+ export type SortingDirectionAction = OrderDirection | 'next' | 'toggleAsc' | 'toggleDesc' | 'clear'
166
+
167
+ /**
168
+ * Sorting state - supports multiple sorted columns
169
+ */
170
+ export interface SortingState {
171
+ readonly directions: SortingDirections
172
+ }
173
+
174
+ // ============================================================================
175
+ // Selection State (Column Visibility + Layout)
176
+ // ============================================================================
177
+
178
+ /**
179
+ * Layout definition for DataView
180
+ */
181
+ export interface DataViewLayout {
182
+ readonly name: string
183
+ readonly label?: ReactNode
184
+ }
185
+
186
+ /**
187
+ * Selection values - current layout + column visibility
188
+ */
189
+ export interface SelectionValues {
190
+ readonly layout?: string
191
+ readonly visibility: Readonly<Record<string, boolean>>
192
+ }
193
+
194
+ /**
195
+ * Selection state - includes values + available layouts
196
+ */
197
+ export interface SelectionState {
198
+ readonly values: SelectionValues
199
+ readonly layouts: readonly DataViewLayout[]
200
+ }
201
+
202
+ // ============================================================================
203
+ // Paging State
204
+ // ============================================================================
205
+
206
+ /**
207
+ * Paging state
208
+ */
209
+ export interface PagingState {
210
+ readonly pageIndex: number
211
+ readonly itemsPerPage: number
212
+ }
213
+
214
+ /**
215
+ * Paging info (computed from state + data)
216
+ */
217
+ export interface PagingInfo {
218
+ readonly totalCount: number | null
219
+ readonly totalPages: number | null
220
+ }