@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.
- package/dist/adapter/ContemberAdapter.d.ts +40 -0
- package/dist/adapter/ContemberAdapter.d.ts.map +1 -0
- package/dist/adapter/MockAdapter.d.ts +81 -0
- package/dist/adapter/MockAdapter.d.ts.map +1 -0
- package/dist/adapter/MockQueryEngine.d.ts +43 -0
- package/dist/adapter/MockQueryEngine.d.ts.map +1 -0
- package/dist/adapter/index.d.ts +5 -0
- package/dist/adapter/index.d.ts.map +1 -0
- package/dist/adapter/types.d.ts +184 -0
- package/dist/adapter/types.d.ts.map +1 -0
- package/dist/brand/ComponentBrand.d.ts +25 -0
- package/dist/brand/ComponentBrand.d.ts.map +1 -0
- package/dist/contember/index.d.ts +10 -0
- package/dist/contember/index.d.ts.map +1 -0
- package/dist/core/ActionDispatcher.d.ts +64 -0
- package/dist/core/ActionDispatcher.d.ts.map +1 -0
- package/dist/core/Disposable.d.ts +57 -0
- package/dist/core/Disposable.d.ts.map +1 -0
- package/dist/core/EntityLoader.d.ts +80 -0
- package/dist/core/EntityLoader.d.ts.map +1 -0
- package/dist/core/PersistenceManager.d.ts +126 -0
- package/dist/core/PersistenceManager.d.ts.map +1 -0
- package/dist/core/SelectionResolver.d.ts +21 -0
- package/dist/core/SelectionResolver.d.ts.map +1 -0
- package/dist/core/actionClassification.d.ts +48 -0
- package/dist/core/actionClassification.d.ts.map +1 -0
- package/dist/core/actions.d.ts +301 -0
- package/dist/core/actions.d.ts.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/dataview/filterHandlers.d.ts +18 -0
- package/dist/dataview/filterHandlers.d.ts.map +1 -0
- package/dist/dataview/index.d.ts +3 -0
- package/dist/dataview/index.d.ts.map +1 -0
- package/dist/dataview/types.d.ts +164 -0
- package/dist/dataview/types.d.ts.map +1 -0
- package/dist/errors/UnfetchedFieldError.d.ts +11 -0
- package/dist/errors/UnfetchedFieldError.d.ts.map +1 -0
- package/dist/errors/errorPathResolver.d.ts +52 -0
- package/dist/errors/errorPathResolver.d.ts.map +1 -0
- package/dist/errors/index.d.ts +5 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/pathMapper.d.ts +42 -0
- package/dist/errors/pathMapper.d.ts.map +1 -0
- package/dist/errors/types.d.ts +131 -0
- package/dist/errors/types.d.ts.map +1 -0
- package/dist/events/EventEmitter.d.ts +97 -0
- package/dist/events/EventEmitter.d.ts.map +1 -0
- package/dist/events/eventFactory.d.ts +40 -0
- package/dist/events/eventFactory.d.ts.map +1 -0
- package/dist/events/index.d.ts +15 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/types.d.ts +264 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/handles/BaseHandle.d.ts +64 -0
- package/dist/handles/BaseHandle.d.ts.map +1 -0
- package/dist/handles/EntityHandle.d.ts +208 -0
- package/dist/handles/EntityHandle.d.ts.map +1 -0
- package/dist/handles/FieldHandle.d.ts +111 -0
- package/dist/handles/FieldHandle.d.ts.map +1 -0
- package/dist/handles/HasManyListHandle.d.ts +166 -0
- package/dist/handles/HasManyListHandle.d.ts.map +1 -0
- package/dist/handles/HasOneHandle.d.ts +168 -0
- package/dist/handles/HasOneHandle.d.ts.map +1 -0
- package/dist/handles/PlaceholderHandle.d.ts +117 -0
- package/dist/handles/PlaceholderHandle.d.ts.map +1 -0
- package/dist/handles/index.d.ts +8 -0
- package/dist/handles/index.d.ts.map +1 -0
- package/dist/handles/proxyFactory.d.ts +66 -0
- package/dist/handles/proxyFactory.d.ts.map +1 -0
- package/dist/handles/types.d.ts +430 -0
- package/dist/handles/types.d.ts.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/internal.d.ts +11 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/persistence/BatchPersister.d.ts +142 -0
- package/dist/persistence/BatchPersister.d.ts.map +1 -0
- package/dist/persistence/ChangeRegistry.d.ts +87 -0
- package/dist/persistence/ChangeRegistry.d.ts.map +1 -0
- package/dist/persistence/ContemberSchemaMutationAdapter.d.ts +16 -0
- package/dist/persistence/ContemberSchemaMutationAdapter.d.ts.map +1 -0
- package/dist/persistence/MutationCollector.d.ts +96 -0
- package/dist/persistence/MutationCollector.d.ts.map +1 -0
- package/dist/persistence/MutationSchemaProvider.d.ts +30 -0
- package/dist/persistence/MutationSchemaProvider.d.ts.map +1 -0
- package/dist/persistence/index.d.ts +7 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/types.d.ts +189 -0
- package/dist/persistence/types.d.ts.map +1 -0
- package/dist/roles/RoleSchemaRegistry.d.ts +77 -0
- package/dist/roles/RoleSchemaRegistry.d.ts.map +1 -0
- package/dist/roles/index.d.ts +11 -0
- package/dist/roles/index.d.ts.map +1 -0
- package/dist/roles/types.d.ts +242 -0
- package/dist/roles/types.d.ts.map +1 -0
- package/dist/schema/ContemberSchema.d.ts +98 -0
- package/dist/schema/ContemberSchema.d.ts.map +1 -0
- package/dist/schema/SchemaLoader.d.ts +28 -0
- package/dist/schema/SchemaLoader.d.ts.map +1 -0
- package/dist/schema/SchemaRegistry.d.ts +95 -0
- package/dist/schema/SchemaRegistry.d.ts.map +1 -0
- package/dist/schema/index.d.ts +6 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/roles.d.ts +62 -0
- package/dist/schema/roles.d.ts.map +1 -0
- package/dist/schema/types.d.ts +179 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/selection/SelectionMetaCollector.d.ts +32 -0
- package/dist/selection/SelectionMetaCollector.d.ts.map +1 -0
- package/dist/selection/SelectionScope.d.ts +77 -0
- package/dist/selection/SelectionScope.d.ts.map +1 -0
- package/dist/selection/buildQuery.d.ts +45 -0
- package/dist/selection/buildQuery.d.ts.map +1 -0
- package/dist/selection/createFragment.d.ts +29 -0
- package/dist/selection/createFragment.d.ts.map +1 -0
- package/dist/selection/createSelectionBuilder.d.ts +18 -0
- package/dist/selection/createSelectionBuilder.d.ts.map +1 -0
- package/dist/selection/index.d.ts +8 -0
- package/dist/selection/index.d.ts.map +1 -0
- package/dist/selection/queryTypes.d.ts +84 -0
- package/dist/selection/queryTypes.d.ts.map +1 -0
- package/dist/selection/types.d.ts +238 -0
- package/dist/selection/types.d.ts.map +1 -0
- package/dist/store/DirtyTracker.d.ts +26 -0
- package/dist/store/DirtyTracker.d.ts.map +1 -0
- package/dist/store/EntityMetaStore.d.ts +52 -0
- package/dist/store/EntityMetaStore.d.ts.map +1 -0
- package/dist/store/EntitySnapshotStore.d.ts +63 -0
- package/dist/store/EntitySnapshotStore.d.ts.map +1 -0
- package/dist/store/ErrorStore.d.ts +82 -0
- package/dist/store/ErrorStore.d.ts.map +1 -0
- package/dist/store/RelationStore.d.ts +175 -0
- package/dist/store/RelationStore.d.ts.map +1 -0
- package/dist/store/SnapshotStore.d.ts +143 -0
- package/dist/store/SnapshotStore.d.ts.map +1 -0
- package/dist/store/SubscriptionManager.d.ts +90 -0
- package/dist/store/SubscriptionManager.d.ts.map +1 -0
- package/dist/store/TouchedStore.d.ts +24 -0
- package/dist/store/TouchedStore.d.ts.map +1 -0
- package/dist/store/entityId.d.ts +29 -0
- package/dist/store/entityId.d.ts.map +1 -0
- package/dist/store/index.d.ts +1 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/snapshots.d.ts +144 -0
- package/dist/store/snapshots.d.ts.map +1 -0
- package/dist/undo/UndoManager.d.ts +120 -0
- package/dist/undo/UndoManager.d.ts.map +1 -0
- package/dist/undo/index.d.ts +3 -0
- package/dist/undo/index.d.ts.map +1 -0
- package/dist/undo/types.d.ts +74 -0
- package/dist/undo/types.d.ts.map +1 -0
- package/dist/utils/aliasGenerator.d.ts +25 -0
- package/dist/utils/aliasGenerator.d.ts.map +1 -0
- package/dist/utils/deepEqual.d.ts +6 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/package.json +29 -0
- package/src/adapter/ContemberAdapter.ts +239 -0
- package/src/adapter/MockAdapter.ts +394 -0
- package/src/adapter/MockQueryEngine.ts +236 -0
- package/src/adapter/index.ts +21 -0
- package/src/adapter/types.ts +199 -0
- package/src/brand/ComponentBrand.ts +28 -0
- package/src/contember/index.ts +17 -0
- package/src/core/ActionDispatcher.ts +450 -0
- package/src/core/Disposable.ts +126 -0
- package/src/core/EntityLoader.ts +169 -0
- package/src/core/SelectionResolver.ts +78 -0
- package/src/core/actionClassification.ts +196 -0
- package/src/core/actions.ts +491 -0
- package/src/core/index.ts +20 -0
- package/src/dataview/filterHandlers.ts +443 -0
- package/src/dataview/index.ts +36 -0
- package/src/dataview/types.ts +220 -0
- package/src/errors/UnfetchedFieldError.ts +18 -0
- package/src/errors/errorPathResolver.ts +239 -0
- package/src/errors/index.ts +40 -0
- package/src/errors/pathMapper.ts +40 -0
- package/src/errors/types.ts +223 -0
- package/src/events/EventEmitter.ts +447 -0
- package/src/events/eventFactory.ts +430 -0
- package/src/events/index.ts +89 -0
- package/src/events/types.ts +374 -0
- package/src/handles/BaseHandle.ts +100 -0
- package/src/handles/EntityHandle.ts +538 -0
- package/src/handles/FieldHandle.ts +268 -0
- package/src/handles/HasManyListHandle.ts +500 -0
- package/src/handles/HasOneHandle.ts +500 -0
- package/src/handles/PlaceholderHandle.ts +321 -0
- package/src/handles/index.ts +37 -0
- package/src/handles/proxyFactory.ts +218 -0
- package/src/handles/types.ts +743 -0
- package/src/index.ts +384 -0
- package/src/internal.ts +19 -0
- package/src/persistence/BatchPersister.ts +911 -0
- package/src/persistence/ChangeRegistry.ts +150 -0
- package/src/persistence/ContemberSchemaMutationAdapter.ts +57 -0
- package/src/persistence/MutationCollector.ts +567 -0
- package/src/persistence/MutationSchemaProvider.ts +33 -0
- package/src/persistence/index.ts +23 -0
- package/src/persistence/types.ts +220 -0
- package/src/schema/ContemberSchema.ts +132 -0
- package/src/schema/SchemaLoader.ts +93 -0
- package/src/schema/SchemaRegistry.ts +286 -0
- package/src/schema/index.ts +51 -0
- package/src/schema/roles.ts +83 -0
- package/src/schema/types.ts +238 -0
- package/src/selection/SelectionMetaCollector.ts +122 -0
- package/src/selection/SelectionScope.ts +208 -0
- package/src/selection/buildQuery.ts +134 -0
- package/src/selection/createFragment.ts +50 -0
- package/src/selection/createSelectionBuilder.ts +283 -0
- package/src/selection/index.ts +31 -0
- package/src/selection/queryTypes.ts +135 -0
- package/src/selection/types.ts +320 -0
- package/src/store/DirtyTracker.ts +117 -0
- package/src/store/EntityMetaStore.ts +143 -0
- package/src/store/EntitySnapshotStore.ts +260 -0
- package/src/store/ErrorStore.ts +251 -0
- package/src/store/RelationStore.ts +696 -0
- package/src/store/SnapshotStore.ts +765 -0
- package/src/store/SubscriptionManager.ts +241 -0
- package/src/store/TouchedStore.ts +42 -0
- package/src/store/entityId.ts +43 -0
- package/src/store/index.ts +2 -0
- package/src/store/snapshots.ts +203 -0
- package/src/undo/UndoManager.ts +418 -0
- package/src/undo/index.ts +9 -0
- package/src/undo/types.ts +84 -0
- package/src/utils/aliasGenerator.ts +56 -0
- package/src/utils/deepEqual.ts +33 -0
- 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
|
+
}
|