@bagelink/vue 1.7.80 → 1.7.86
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/components/Filter.vue.d.ts +28 -0
- package/dist/components/Filter.vue.d.ts.map +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/index.cjs +25 -25
- package/dist/index.mjs +4529 -4233
- package/dist/style.css +1 -1
- package/dist/utils/index.d.ts +3 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/queryFilter.d.ts +177 -0
- package/dist/utils/queryFilter.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/Filter.vue +267 -0
- package/src/components/index.ts +1 -0
- package/src/styles/appearance.css +24 -0
- package/src/utils/index.ts +4 -1
- package/src/utils/queryFilter.ts +366 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SCIM 2.0 Query Builder
|
|
3
|
+
*
|
|
4
|
+
* Type-safe query builder for SCIM 2.0 filtering.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* // Array-based (recommended for UI)
|
|
9
|
+
* const filters: QueryConditions<Person> = [
|
|
10
|
+
* { field: 'first_name', op: 'eq', value: 'John' },
|
|
11
|
+
* { field: 'age', op: 'gt', value: 18, connector: 'and' },
|
|
12
|
+
* ]
|
|
13
|
+
* buildQuery(filters) // → 'first_name eq "John" and age gt 18'
|
|
14
|
+
*
|
|
15
|
+
* // Range query (same field twice)
|
|
16
|
+
* const rangeFilters: QueryConditions<Donation> = [
|
|
17
|
+
* { field: 'amount', op: 'ge', value: 100 },
|
|
18
|
+
* { field: 'amount', op: 'le', value: 500, connector: 'and' },
|
|
19
|
+
* ]
|
|
20
|
+
* buildQuery(rangeFilters) // → 'amount ge 100 and amount le 500'
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
type Primitive = string | number | boolean | null
|
|
25
|
+
|
|
26
|
+
type DeepKeyOf<T> = T extends Primitive
|
|
27
|
+
? never
|
|
28
|
+
: {
|
|
29
|
+
[K in keyof T & string]: K | `${K}.${DeepKeyOf<T[K]>}`;
|
|
30
|
+
}[keyof T & string]
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* SCIM 2.0 comparison operators
|
|
34
|
+
*/
|
|
35
|
+
export type ComparisonOperator =
|
|
36
|
+
| 'eq' // equal
|
|
37
|
+
| 'ne' // not equal
|
|
38
|
+
| 'gt' // greater than
|
|
39
|
+
| 'ge' // greater than or equal
|
|
40
|
+
| 'lt' // less than
|
|
41
|
+
| 'le' // less than or equal
|
|
42
|
+
| 'co' // contains
|
|
43
|
+
| 'sw' // starts with
|
|
44
|
+
| 'ew' // ends with
|
|
45
|
+
| 'pr' // present (has value)
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* SCIM 2.0 logical operators
|
|
49
|
+
*/
|
|
50
|
+
export type LogicalOperator = 'and' | 'or'
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A single filter condition
|
|
54
|
+
*/
|
|
55
|
+
export interface FilterCondition<T extends Record<string, any> = Record<string, any>> {
|
|
56
|
+
field: DeepKeyOf<T> | string
|
|
57
|
+
op: ComparisonOperator
|
|
58
|
+
value?: Primitive // optional for 'pr' operator
|
|
59
|
+
connector?: LogicalOperator // before this condition, ignored for first
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Array of filter conditions - the primary way to define queries
|
|
64
|
+
*/
|
|
65
|
+
export type QueryConditions<T extends Record<string, any> = Record<string, any>> = FilterCondition<T>[]
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Format a value for SCIM 2.0 query
|
|
69
|
+
*/
|
|
70
|
+
function formatValue(value: Primitive): string {
|
|
71
|
+
if (value === null) return 'null'
|
|
72
|
+
if (typeof value === 'string') {
|
|
73
|
+
const escaped = value.replace(/"/g, '\\"')
|
|
74
|
+
return `"${escaped}"`
|
|
75
|
+
}
|
|
76
|
+
if (typeof value === 'boolean') return value.toString()
|
|
77
|
+
return String(value)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Build a SCIM 2.0 query string from conditions array
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* buildQuery<Person>([
|
|
86
|
+
* { field: 'name', op: 'eq', value: 'John' },
|
|
87
|
+
* { field: 'age', op: 'gt', value: 18, connector: 'and' },
|
|
88
|
+
* ])
|
|
89
|
+
* // → 'name eq "John" and age gt 18'
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export function buildQuery<T extends Record<string, any>>(
|
|
93
|
+
conditions: QueryConditions<T>
|
|
94
|
+
): string {
|
|
95
|
+
if (!conditions || conditions.length === 0) return ''
|
|
96
|
+
|
|
97
|
+
const parts: string[] = []
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i < conditions.length; i++) {
|
|
100
|
+
const { field, op, value, connector } = conditions[i]
|
|
101
|
+
|
|
102
|
+
// Add connector (except for first condition)
|
|
103
|
+
if (i > 0 && connector) {
|
|
104
|
+
parts.push(connector)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Build the condition
|
|
108
|
+
if (op === 'pr') {
|
|
109
|
+
parts.push(`${field} pr`)
|
|
110
|
+
} else {
|
|
111
|
+
parts.push(`${field} ${op} ${formatValue(value ?? null)}`)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return parts.join(' ')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Query Builder for SCIM 2.0 filtering
|
|
120
|
+
*/
|
|
121
|
+
export class QueryFilter<T extends Record<string, any>> {
|
|
122
|
+
private parts: string[] = []
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Equal comparison
|
|
126
|
+
*/
|
|
127
|
+
eq<K extends DeepKeyOf<T>>(field: K, value: Primitive): this {
|
|
128
|
+
this.parts.push(`${field as string} eq ${this.formatValue(value)}`)
|
|
129
|
+
return this
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Not equal comparison
|
|
134
|
+
*/
|
|
135
|
+
ne<K extends DeepKeyOf<T>>(field: K, value: Primitive): this {
|
|
136
|
+
this.parts.push(`${field as string} ne ${this.formatValue(value)}`)
|
|
137
|
+
return this
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Greater than comparison
|
|
142
|
+
*/
|
|
143
|
+
gt<K extends DeepKeyOf<T>>(field: K, value: number | string): this {
|
|
144
|
+
this.parts.push(`${field as string} gt ${this.formatValue(value)}`)
|
|
145
|
+
return this
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Greater than or equal comparison
|
|
150
|
+
*/
|
|
151
|
+
ge<K extends DeepKeyOf<T>>(field: K, value: number | string): this {
|
|
152
|
+
this.parts.push(`${field as string} ge ${this.formatValue(value)}`)
|
|
153
|
+
return this
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Less than comparison
|
|
158
|
+
*/
|
|
159
|
+
lt<K extends DeepKeyOf<T>>(field: K, value: number | string): this {
|
|
160
|
+
this.parts.push(`${field as string} lt ${this.formatValue(value)}`)
|
|
161
|
+
return this
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Less than or equal comparison
|
|
166
|
+
*/
|
|
167
|
+
le<K extends DeepKeyOf<T>>(field: K, value: number | string): this {
|
|
168
|
+
this.parts.push(`${field as string} le ${this.formatValue(value)}`)
|
|
169
|
+
return this
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Contains comparison (for strings)
|
|
174
|
+
*/
|
|
175
|
+
co<K extends DeepKeyOf<T>>(field: K, value: string): this {
|
|
176
|
+
this.parts.push(`${field as string} co ${this.formatValue(value)}`)
|
|
177
|
+
return this
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Starts with comparison (for strings)
|
|
182
|
+
*/
|
|
183
|
+
sw<K extends DeepKeyOf<T>>(field: K, value: string): this {
|
|
184
|
+
this.parts.push(`${field as string} sw ${this.formatValue(value)}`)
|
|
185
|
+
return this
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Ends with comparison (for strings)
|
|
190
|
+
*/
|
|
191
|
+
ew<K extends DeepKeyOf<T>>(field: K, value: string): this {
|
|
192
|
+
this.parts.push(`${field as string} ew ${this.formatValue(value)}`)
|
|
193
|
+
return this
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Present check (field has a value)
|
|
198
|
+
*/
|
|
199
|
+
pr<K extends DeepKeyOf<T>>(field: K): this {
|
|
200
|
+
this.parts.push(`${field as string} pr`)
|
|
201
|
+
return this
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* AND logical operator
|
|
206
|
+
*/
|
|
207
|
+
and(): this {
|
|
208
|
+
this.parts.push('and')
|
|
209
|
+
return this
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* OR logical operator
|
|
214
|
+
*/
|
|
215
|
+
or(): this {
|
|
216
|
+
this.parts.push('or')
|
|
217
|
+
return this
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* NOT logical operator
|
|
222
|
+
*/
|
|
223
|
+
not(): this {
|
|
224
|
+
this.parts.push('not')
|
|
225
|
+
return this
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Group expressions with parentheses
|
|
230
|
+
*/
|
|
231
|
+
group(builderFn: (qb: QueryFilter<T>) => QueryFilter<T>): this {
|
|
232
|
+
const groupBuilder = new QueryFilter<T>()
|
|
233
|
+
builderFn(groupBuilder)
|
|
234
|
+
const groupQuery = groupBuilder.build()
|
|
235
|
+
if (groupQuery) {
|
|
236
|
+
this.parts.push(`(${groupQuery})`)
|
|
237
|
+
}
|
|
238
|
+
return this
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Add raw query string (use with caution)
|
|
243
|
+
*/
|
|
244
|
+
raw(query: string): this {
|
|
245
|
+
this.parts.push(query)
|
|
246
|
+
return this
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Clear the query
|
|
251
|
+
*/
|
|
252
|
+
clear(): this {
|
|
253
|
+
this.parts = []
|
|
254
|
+
return this
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Build the final query string
|
|
259
|
+
*/
|
|
260
|
+
build(): string {
|
|
261
|
+
return this.parts.join(' ')
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Convert to string automatically (allows using builder without .build())
|
|
266
|
+
*/
|
|
267
|
+
toString(): string {
|
|
268
|
+
return this.build()
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Allow implicit string conversion
|
|
273
|
+
*/
|
|
274
|
+
[Symbol.toPrimitive](hint: string): string | number {
|
|
275
|
+
if (hint === 'string') {
|
|
276
|
+
return this.build()
|
|
277
|
+
}
|
|
278
|
+
return this.parts.length
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Format value for SCIM 2.0 query
|
|
283
|
+
*/
|
|
284
|
+
private formatValue(value: Primitive): string {
|
|
285
|
+
if (value === null) {
|
|
286
|
+
return 'null'
|
|
287
|
+
}
|
|
288
|
+
if (typeof value === 'string') {
|
|
289
|
+
// Escape quotes in strings
|
|
290
|
+
const escaped = value.replace(/"/g, '\\"')
|
|
291
|
+
return `"${escaped}"`
|
|
292
|
+
}
|
|
293
|
+
if (typeof value === 'boolean') {
|
|
294
|
+
return value.toString()
|
|
295
|
+
}
|
|
296
|
+
return String(value)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Build a query string from conditions
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```typescript
|
|
305
|
+
* // From conditions array
|
|
306
|
+
* query<Person>([
|
|
307
|
+
* { field: 'first_name', op: 'eq', value: 'John' },
|
|
308
|
+
* { field: 'age', op: 'gt', value: 18, connector: 'and' },
|
|
309
|
+
* ])
|
|
310
|
+
* // → 'first_name eq "John" and age gt 18'
|
|
311
|
+
*
|
|
312
|
+
* // Empty returns empty string
|
|
313
|
+
* query([]) // → ''
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
export function query<T extends Record<string, any>>(
|
|
317
|
+
conditions: QueryConditions<T>
|
|
318
|
+
): string {
|
|
319
|
+
return buildQuery(conditions)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Create a query that checks if any of the fields match the value
|
|
324
|
+
*/
|
|
325
|
+
export function anyOf<T extends Record<string, any>>(
|
|
326
|
+
fields: Array<DeepKeyOf<T> | string>,
|
|
327
|
+
value: Primitive
|
|
328
|
+
): string {
|
|
329
|
+
const conditions: QueryConditions<T> = fields.map((field, i) => ({
|
|
330
|
+
field,
|
|
331
|
+
op: 'eq' as const,
|
|
332
|
+
value,
|
|
333
|
+
connector: i > 0 ? 'or' as const : undefined,
|
|
334
|
+
}))
|
|
335
|
+
return buildQuery(conditions)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Create a range query (field >= min and field <= max)
|
|
340
|
+
*/
|
|
341
|
+
export function range<T extends Record<string, any>>(
|
|
342
|
+
field: DeepKeyOf<T> | string,
|
|
343
|
+
min: number | string,
|
|
344
|
+
max: number | string
|
|
345
|
+
): string {
|
|
346
|
+
return buildQuery<T>([
|
|
347
|
+
{ field, op: 'ge', value: min },
|
|
348
|
+
{ field, op: 'le', value: max, connector: 'and' },
|
|
349
|
+
])
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Create a search query across multiple string fields (OR)
|
|
354
|
+
*/
|
|
355
|
+
export function search<T extends Record<string, any>>(
|
|
356
|
+
fields: Array<DeepKeyOf<T> | string>,
|
|
357
|
+
searchTerm: string
|
|
358
|
+
): string {
|
|
359
|
+
const conditions: QueryConditions<T> = fields.map((field, i) => ({
|
|
360
|
+
field,
|
|
361
|
+
op: 'co' as const,
|
|
362
|
+
value: searchTerm,
|
|
363
|
+
connector: i > 0 ? 'or' as const : undefined,
|
|
364
|
+
}))
|
|
365
|
+
return buildQuery(conditions)
|
|
366
|
+
}
|