@bagelink/vue 1.15.323 → 1.15.331
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/FilterQuery.types.d.ts +2 -0
- package/dist/components/FilterQuery.types.d.ts.map +1 -1
- package/dist/components/FilterQuery.vue.d.ts.map +1 -1
- package/dist/i18n/index.d.ts +12 -0
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/index.cjs +73 -73
- package/dist/index.mjs +5357 -5309
- package/dist/utils/queryFilter.d.ts +5 -1
- package/dist/utils/queryFilter.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/FilterQuery.types.ts +2 -0
- package/src/components/FilterQuery.vue +38 -22
- package/src/form-flow/FormFlow.vue +1 -1
- package/src/i18n/locales/en.json +3 -1
- package/src/i18n/locales/es.json +3 -1
- package/src/i18n/locales/fr.json +3 -1
- package/src/i18n/locales/he.json +2 -0
- package/src/i18n/locales/it.json +3 -1
- package/src/i18n/locales/ru.json +2 -0
- package/src/utils/queryFilter.ts +25 -4
|
@@ -27,7 +27,7 @@ type DeepKeyOf<T> = T extends Primitive ? never : {
|
|
|
27
27
|
/**
|
|
28
28
|
* SCIM 2.0 comparison operators
|
|
29
29
|
*/
|
|
30
|
-
export type ComparisonOperator = 'eq' | 'ne' | 'gt' | 'ge' | 'lt' | 'le' | 'co' | 'sw' | 'ew' | 'pr' | 'in' | 'nin' | 'between';
|
|
30
|
+
export type ComparisonOperator = 'eq' | 'ne' | 'gt' | 'ge' | 'lt' | 'le' | 'co' | 'sw' | 'sw_any' | 'ew' | 'pr' | 'is_null' | 'in' | 'nin' | 'between';
|
|
31
31
|
/**
|
|
32
32
|
* SCIM 2.0 logical operators
|
|
33
33
|
*/
|
|
@@ -95,6 +95,10 @@ export declare class QueryFilter<T extends Record<string, any>> {
|
|
|
95
95
|
* Starts with comparison (for strings)
|
|
96
96
|
*/
|
|
97
97
|
sw<K extends DeepKeyOf<T>>(field: K, value: string): this;
|
|
98
|
+
/**
|
|
99
|
+
* Starts with any listed prefix.
|
|
100
|
+
*/
|
|
101
|
+
swAny<K extends DeepKeyOf<T>>(field: K, values: string[]): this;
|
|
98
102
|
/**
|
|
99
103
|
* Ends with comparison (for strings)
|
|
100
104
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queryFilter.d.ts","sourceRoot":"","sources":["../../src/utils/queryFilter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,KAAK,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAA;AAEjD,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,SAAS,SAAS,GACpC,KAAK,GACL;KACA,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;CACtD,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAA;AAEpB;;GAEG;AACH,MAAM,MAAM,kBAAkB,GACzB,IAAI,GACL,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,KAAK,GACL,SAAS,CAAA;AAEb;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,IAAI,CAAA;AAE1C;;GAEG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IACnF,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,MAAM,CAAA;IAC5B,EAAE,EAAE,kBAAkB,CAAA;IACtB,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,EAAE,CAAA;IAC/B,SAAS,CAAC,EAAE,eAAe,CAAA;CAC3B;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,eAAe,CAAC,CAAC,CAAC,EAAE,CAAA;AAuBvG;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACvD,UAAU,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC,GAC7B,MAAM,
|
|
1
|
+
{"version":3,"file":"queryFilter.d.ts","sourceRoot":"","sources":["../../src/utils/queryFilter.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -18,4 +18,6 @@ export interface FilterField {
|
|
|
18
18
|
/** Preset query options — each option injects a raw SCIM query. Mutually exclusive with `options`. */
|
|
19
19
|
queryOptions?: QueryOption[]
|
|
20
20
|
multiple?: boolean
|
|
21
|
+
/** Additional operators intentionally supported by this filter backend. */
|
|
22
|
+
additionalOperators?: Array<'sw_any'>
|
|
21
23
|
}
|
|
@@ -57,15 +57,15 @@ const dropdownProps = computed(() => props.inline
|
|
|
57
57
|
placement: props.placement,
|
|
58
58
|
icon: 'filter_alt',
|
|
59
59
|
value: filterLabel.value,
|
|
60
|
-
|
|
60
|
+
sm: true,
|
|
61
61
|
})
|
|
62
62
|
|
|
63
63
|
function isComplete(c: { field: string, op: ComparisonOperator, value?: any }) {
|
|
64
64
|
if (!c.field || !c.op) return false
|
|
65
65
|
// queryOption presets are complete if they have a selected value
|
|
66
66
|
if (isQueryOptionField(c.field as string)) return c.value !== '' && c.value != null
|
|
67
|
-
if (c.op === 'pr') return true
|
|
68
|
-
if (c.op === 'in' || c.op === 'nin') return Array.isArray(c.value) && c.value.length > 0
|
|
67
|
+
if (c.op === 'pr' || c.op === 'is_null') return true
|
|
68
|
+
if (c.op === 'in' || c.op === 'nin' || c.op === 'sw_any') return Array.isArray(c.value) && c.value.length > 0
|
|
69
69
|
if (c.op === 'between') {
|
|
70
70
|
return Array.isArray(c.value) && c.value.length === 2
|
|
71
71
|
&& c.value[0] !== '' && c.value[0] != null
|
|
@@ -103,8 +103,10 @@ const currentTexts = computed(() => ({
|
|
|
103
103
|
le: $t('filter.operators.le'),
|
|
104
104
|
co: $t('filter.operators.co'),
|
|
105
105
|
sw: $t('filter.operators.sw'),
|
|
106
|
+
sw_any: $t('filter.operators.swAny'),
|
|
106
107
|
ew: $t('filter.operators.ew'),
|
|
107
108
|
pr: $t('filter.operators.pr'),
|
|
109
|
+
is_null: $t('filter.operators.isNull'),
|
|
108
110
|
in: $t('filter.operators.in'),
|
|
109
111
|
nin: $t('filter.operators.nin'),
|
|
110
112
|
between: $t('filter.operators.between'),
|
|
@@ -162,29 +164,35 @@ const allOperatorOptions = computed(() => [
|
|
|
162
164
|
{ label: currentTexts.value.operators.le, value: 'le' as ComparisonOperator },
|
|
163
165
|
{ label: currentTexts.value.operators.co, value: 'co' as ComparisonOperator },
|
|
164
166
|
{ label: currentTexts.value.operators.sw, value: 'sw' as ComparisonOperator },
|
|
167
|
+
{ label: currentTexts.value.operators.sw_any, value: 'sw_any' as ComparisonOperator },
|
|
165
168
|
{ label: currentTexts.value.operators.ew, value: 'ew' as ComparisonOperator },
|
|
166
169
|
{ label: currentTexts.value.operators.pr, value: 'pr' as ComparisonOperator },
|
|
170
|
+
{ label: currentTexts.value.operators.is_null, value: 'is_null' as ComparisonOperator },
|
|
167
171
|
{ label: currentTexts.value.operators.in, value: 'in' as ComparisonOperator },
|
|
168
172
|
{ label: currentTexts.value.operators.nin, value: 'nin' as ComparisonOperator },
|
|
169
173
|
{ label: currentTexts.value.operators.between, value: 'between' as ComparisonOperator },
|
|
170
174
|
])
|
|
171
175
|
|
|
172
|
-
function getOperatorsForType(fieldType: string, hasOptions: boolean, isMultiple: boolean) {
|
|
176
|
+
function getOperatorsForType(fieldType: string, hasOptions: boolean, isMultiple: boolean, fieldValue: string) {
|
|
173
177
|
const allow = (ops: string[]) => allOperatorOptions.value.filter(o => ops.includes(o.value))
|
|
178
|
+
const withAdditional = (ops: string[]) => [
|
|
179
|
+
...ops,
|
|
180
|
+
...(getFieldDef(fieldValue)?.additionalOperators ?? []),
|
|
181
|
+
]
|
|
174
182
|
if (isMultiple && hasOptions) {
|
|
175
|
-
return allow(['in', 'nin', 'pr'])
|
|
183
|
+
return allow(withAdditional(['in', 'nin', 'pr', 'is_null']))
|
|
176
184
|
}
|
|
177
185
|
if (hasOptions) {
|
|
178
|
-
return allow(['eq', 'ne', 'in', 'nin', 'pr'])
|
|
186
|
+
return allow(withAdditional(['eq', 'ne', 'in', 'nin', 'pr', 'is_null']))
|
|
179
187
|
}
|
|
180
188
|
switch (fieldType) {
|
|
181
189
|
case 'boolean':
|
|
182
|
-
return allow(['eq', 'ne', 'pr'])
|
|
190
|
+
return allow(withAdditional(['eq', 'ne', 'pr', 'is_null']))
|
|
183
191
|
case 'number':
|
|
184
192
|
case 'date':
|
|
185
|
-
return allow(['eq', 'ne', 'gt', 'ge', 'lt', 'le', 'between', 'in', 'nin', 'pr'])
|
|
193
|
+
return allow(withAdditional(['eq', 'ne', 'gt', 'ge', 'lt', 'le', 'between', 'in', 'nin', 'pr', 'is_null']))
|
|
186
194
|
default:
|
|
187
|
-
return allow(['eq', 'ne', 'co', 'sw', 'ew', 'in', 'nin', 'pr'])
|
|
195
|
+
return allow(withAdditional(['eq', 'ne', 'co', 'sw', 'ew', 'in', 'nin', 'pr', 'is_null']))
|
|
188
196
|
}
|
|
189
197
|
}
|
|
190
198
|
|
|
@@ -346,11 +354,11 @@ function getFieldType(fieldValue: string): string {
|
|
|
346
354
|
}
|
|
347
355
|
|
|
348
356
|
function needsValue(operator: ComparisonOperator): boolean {
|
|
349
|
-
return operator !== 'pr'
|
|
357
|
+
return operator !== 'pr' && operator !== 'is_null'
|
|
350
358
|
}
|
|
351
359
|
|
|
352
360
|
function isMultiOperator(operator: ComparisonOperator): boolean {
|
|
353
|
-
return operator === 'in' || operator === 'nin'
|
|
361
|
+
return operator === 'in' || operator === 'nin' || operator === 'sw_any'
|
|
354
362
|
}
|
|
355
363
|
|
|
356
364
|
function isBetweenOperator(operator: ComparisonOperator): boolean {
|
|
@@ -367,6 +375,14 @@ function listToText(value: UICondition['value']): string {
|
|
|
367
375
|
return Array.isArray(value) ? value.join(', ') : String(value ?? '')
|
|
368
376
|
}
|
|
369
377
|
|
|
378
|
+
function selectModelValue(value: UICondition['value']): any {
|
|
379
|
+
return value ?? undefined
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function selectMultiModelValue(value: UICondition['value']): any[] {
|
|
383
|
+
return Array.isArray(value) ? value.filter(item => item != null) : []
|
|
384
|
+
}
|
|
385
|
+
|
|
370
386
|
/** Split comma-separated free-list text into a trimmed string[]. */
|
|
371
387
|
function textToList(text: string): string[] {
|
|
372
388
|
return text.split(',').map(s => s.trim()).filter(s => s !== '')
|
|
@@ -402,7 +418,7 @@ function onFieldChange(id: string, newField: string) {
|
|
|
402
418
|
return
|
|
403
419
|
}
|
|
404
420
|
const multi = !!(fieldDef?.multiple && fieldDef?.options)
|
|
405
|
-
const validOps = getOperatorsForType(fieldDef?.type || 'string', !!fieldDef?.options, multi)
|
|
421
|
+
const validOps = getOperatorsForType(fieldDef?.type || 'string', !!fieldDef?.options, multi, newField)
|
|
406
422
|
const defaultOp = (multi ? 'in' : validOps[0]?.value ?? 'eq') as ComparisonOperator
|
|
407
423
|
updateConditionAtIndex(index, {
|
|
408
424
|
field: newField,
|
|
@@ -419,9 +435,9 @@ function onOperatorChange(id: string, operator: ComparisonOperator) {
|
|
|
419
435
|
const updates: Parameters<typeof updateConditionAtIndex>[1] = { op: operator }
|
|
420
436
|
|
|
421
437
|
// Reset the value shape when the operator's expected value type changes.
|
|
422
|
-
const wasList = prevOp === 'in' || prevOp === 'nin'
|
|
438
|
+
const wasList = prevOp === 'in' || prevOp === 'nin' || prevOp === 'sw_any'
|
|
423
439
|
const wasBetween = prevOp === 'between'
|
|
424
|
-
const isList = operator === 'in' || operator === 'nin'
|
|
440
|
+
const isList = operator === 'in' || operator === 'nin' || operator === 'sw_any'
|
|
425
441
|
const isBetween = operator === 'between'
|
|
426
442
|
|
|
427
443
|
if (isBetween && !wasBetween) updates.value = ['', '']
|
|
@@ -518,7 +534,7 @@ function onConnectorChange(id: string, connector: LogicalOperator) {
|
|
|
518
534
|
<!-- Operator selector -->
|
|
519
535
|
<SelectInput
|
|
520
536
|
:model-value="condition.operator"
|
|
521
|
-
:options="getOperatorsForType(getFieldType(condition.field), !!getFieldOptions(condition.field), isMultipleField(condition.field))"
|
|
537
|
+
:options="getOperatorsForType(getFieldType(condition.field), !!getFieldOptions(condition.field), isMultipleField(condition.field), condition.field)"
|
|
522
538
|
class="m-0 light-input borderHover"
|
|
523
539
|
@update:model-value="(v: ComparisonOperator) => onOperatorChange(condition.id, v)"
|
|
524
540
|
/>
|
|
@@ -563,7 +579,7 @@ function onConnectorChange(id: string, connector: LogicalOperator) {
|
|
|
563
579
|
<SelectInput
|
|
564
580
|
v-else-if="getFieldOptions(condition.field) && isMultiOperator(condition.operator)"
|
|
565
581
|
:key="`${condition.id}-${condition.field}-multi`"
|
|
566
|
-
:model-value="
|
|
582
|
+
:model-value="selectMultiModelValue(condition.value)"
|
|
567
583
|
:options="getFieldOptions(condition.field)!"
|
|
568
584
|
:placeholder="currentTexts.placeholders.selectOption"
|
|
569
585
|
class="m-0 light-input borderHover" searchable multiselect
|
|
@@ -581,7 +597,7 @@ function onConnectorChange(id: string, connector: LogicalOperator) {
|
|
|
581
597
|
<SelectInput
|
|
582
598
|
v-else-if="getFieldOptions(condition.field)"
|
|
583
599
|
:key="`${condition.id}-${condition.field}-single`"
|
|
584
|
-
:model-value="condition.value"
|
|
600
|
+
:model-value="selectModelValue(condition.value)"
|
|
585
601
|
:options="getFieldOptions(condition.field)!"
|
|
586
602
|
:placeholder="currentTexts.placeholders.selectOption"
|
|
587
603
|
class="m-0 light-input borderHover" searchable
|
|
@@ -590,7 +606,7 @@ function onConnectorChange(id: string, connector: LogicalOperator) {
|
|
|
590
606
|
<SelectInput
|
|
591
607
|
v-else-if="getFieldType(condition.field) === 'boolean'"
|
|
592
608
|
:key="`${condition.id}-${condition.field}-bool`"
|
|
593
|
-
:model-value="condition.value" :options="booleanOptions"
|
|
609
|
+
:model-value="selectModelValue(condition.value)" :options="booleanOptions"
|
|
594
610
|
:placeholder="currentTexts.placeholders.selectOption"
|
|
595
611
|
class="m-0 light-input borderHover"
|
|
596
612
|
@update:model-value="(v: boolean) => onValueChange(condition.id, v)"
|
|
@@ -620,7 +636,7 @@ function onConnectorChange(id: string, connector: LogicalOperator) {
|
|
|
620
636
|
|
|
621
637
|
<!-- Remove button -->
|
|
622
638
|
<Btn
|
|
623
|
-
icon="close"
|
|
639
|
+
icon="close" sm flat size="sm"
|
|
624
640
|
:class="{ 'm_bg-gray-40 m_px-3 m_mx-auto': !props.inline && !props.desktopLayoutOnMobile }"
|
|
625
641
|
@click="removeCondition(index)"
|
|
626
642
|
/>
|
|
@@ -629,14 +645,14 @@ function onConnectorChange(id: string, connector: LogicalOperator) {
|
|
|
629
645
|
</div>
|
|
630
646
|
|
|
631
647
|
<div class="flex gap-1 space-between m_flex-wrap">
|
|
632
|
-
<Btn icon="add" flat
|
|
648
|
+
<Btn icon="add" flat sm :value="currentTexts.buttons.addCondition" @click="addCondition" />
|
|
633
649
|
<div class="flex gap-05">
|
|
634
650
|
<Btn
|
|
635
|
-
v-if="conditions.length > 0" icon="delete_sweep" flat
|
|
651
|
+
v-if="conditions.length > 0" icon="delete_sweep" flat sm color="gray"
|
|
636
652
|
:value="currentTexts.buttons.clearAll" @click="clearAll"
|
|
637
653
|
/>
|
|
638
654
|
<Btn
|
|
639
|
-
v-if="hasSaveListener" :disabled="conditions.length === 0" class="color-green" icon="save"
|
|
655
|
+
v-if="hasSaveListener" :disabled="conditions.length === 0" class="color-green" icon="save" sm
|
|
640
656
|
color="green-light" :value="$t('filter.buttons.save')"
|
|
641
657
|
@click="emit('save', internalModel.filter(isComplete))"
|
|
642
658
|
/>
|
|
@@ -230,7 +230,7 @@ function updateField(key: string, value: any, field: FieldBuilder) {
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
function isFieldEmpty(value: any, field: FieldBuilder): boolean {
|
|
233
|
-
return value == null || value === ''
|
|
233
|
+
return value == null || value === '' || value === false
|
|
234
234
|
|| (Array.isArray(value) && value.length === 0)
|
|
235
235
|
|| (field._type === 'tel' && typeof value === 'string' && /^\+\d+$/.test(value))
|
|
236
236
|
}
|
package/src/i18n/locales/en.json
CHANGED
|
@@ -75,8 +75,10 @@
|
|
|
75
75
|
"le": "Less or equal",
|
|
76
76
|
"co": "Contains",
|
|
77
77
|
"sw": "Starts with",
|
|
78
|
+
"swAny": "Starts with any",
|
|
78
79
|
"ew": "Ends with",
|
|
79
|
-
"pr": "
|
|
80
|
+
"pr": "Is present",
|
|
81
|
+
"isNull": "Is not present",
|
|
80
82
|
"in": "In list",
|
|
81
83
|
"nin": "Not in list",
|
|
82
84
|
"between": "Between"
|
package/src/i18n/locales/es.json
CHANGED
|
@@ -74,8 +74,10 @@
|
|
|
74
74
|
"le": "Menor o igual",
|
|
75
75
|
"co": "Contiene",
|
|
76
76
|
"sw": "Empieza con",
|
|
77
|
+
"swAny": "Empieza con cualquiera",
|
|
77
78
|
"ew": "Termina con",
|
|
78
|
-
"pr": "
|
|
79
|
+
"pr": "Está presente",
|
|
80
|
+
"isNull": "No está presente",
|
|
79
81
|
"in": "En lista",
|
|
80
82
|
"nin": "No en lista",
|
|
81
83
|
"between": "Entre"
|
package/src/i18n/locales/fr.json
CHANGED
|
@@ -74,8 +74,10 @@
|
|
|
74
74
|
"le": "Inférieur ou égal",
|
|
75
75
|
"co": "Contient",
|
|
76
76
|
"sw": "Commence par",
|
|
77
|
+
"swAny": "Commence par l’un des préfixes",
|
|
77
78
|
"ew": "Se termine par",
|
|
78
|
-
"pr": "
|
|
79
|
+
"pr": "Est présent",
|
|
80
|
+
"isNull": "N’est pas présent",
|
|
79
81
|
"in": "Dans la liste",
|
|
80
82
|
"nin": "Pas dans la liste",
|
|
81
83
|
"between": "Entre"
|
package/src/i18n/locales/he.json
CHANGED
package/src/i18n/locales/it.json
CHANGED
|
@@ -74,8 +74,10 @@
|
|
|
74
74
|
"le": "Minore o uguale",
|
|
75
75
|
"co": "Contiene",
|
|
76
76
|
"sw": "Inizia con",
|
|
77
|
+
"swAny": "Inizia con uno qualsiasi",
|
|
77
78
|
"ew": "Finisce con",
|
|
78
|
-
"pr": "
|
|
79
|
+
"pr": "È presente",
|
|
80
|
+
"isNull": "Non è presente",
|
|
79
81
|
"in": "Nella lista",
|
|
80
82
|
"nin": "Non nella lista",
|
|
81
83
|
"between": "Tra"
|
package/src/i18n/locales/ru.json
CHANGED
|
@@ -74,8 +74,10 @@
|
|
|
74
74
|
"le": "Меньше или равно",
|
|
75
75
|
"co": "Содержит",
|
|
76
76
|
"sw": "Начинается с",
|
|
77
|
+
"swAny": "Начинается с любого",
|
|
77
78
|
"ew": "Заканчивается на",
|
|
78
79
|
"pr": "Присутствует",
|
|
80
|
+
"isNull": "Отсутствует",
|
|
79
81
|
"in": "В списке",
|
|
80
82
|
"nin": "Не в списке",
|
|
81
83
|
"between": "Между"
|
package/src/utils/queryFilter.ts
CHANGED
|
@@ -41,8 +41,10 @@ export type ComparisonOperator
|
|
|
41
41
|
| 'le' // less than or equal
|
|
42
42
|
| 'co' // contains
|
|
43
43
|
| 'sw' // starts with
|
|
44
|
+
| 'sw_any' // starts with any value in a list
|
|
44
45
|
| 'ew' // ends with
|
|
45
46
|
| 'pr' // present (has value)
|
|
47
|
+
| 'is_null' // missing or explicitly null; serialized as `eq null`
|
|
46
48
|
| 'in' // in list — native: field in ["a","b"] (scalar membership OR array overlap)
|
|
47
49
|
| 'nin' // not in list — native: field nin ["a","b"]
|
|
48
50
|
| 'between' // inclusive range — field between [lo, hi] (numbers or dates)
|
|
@@ -118,7 +120,9 @@ export function buildQuery<T extends Record<string, any>>(
|
|
|
118
120
|
// Build the condition
|
|
119
121
|
if (op === 'pr') {
|
|
120
122
|
parts.push(`${field} pr`)
|
|
121
|
-
} else if (op === '
|
|
123
|
+
} else if (op === 'is_null') {
|
|
124
|
+
parts.push(`${field} eq null`)
|
|
125
|
+
} else if (op === 'in' || op === 'nin' || op === 'sw_any') {
|
|
122
126
|
// Native list membership / array-overlap: field in ["a","b"].
|
|
123
127
|
const values = Array.isArray(value) ? value : (value != null ? [value] : [])
|
|
124
128
|
if (values.length === 0) continue
|
|
@@ -206,6 +210,14 @@ export class QueryFilter<T extends Record<string, any>> {
|
|
|
206
210
|
return this
|
|
207
211
|
}
|
|
208
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Starts with any listed prefix.
|
|
215
|
+
*/
|
|
216
|
+
swAny<K extends DeepKeyOf<T>>(field: K, values: string[]): this {
|
|
217
|
+
this.parts.push(`${field as string} sw_any ${formatList(values)}`)
|
|
218
|
+
return this
|
|
219
|
+
}
|
|
220
|
+
|
|
209
221
|
/**
|
|
210
222
|
* Ends with comparison (for strings)
|
|
211
223
|
*/
|
|
@@ -451,8 +463,11 @@ export function evaluateQuery(query: string, data: Record<string, any>): boolean
|
|
|
451
463
|
case 'le': match = value != null && fieldValue <= value; break
|
|
452
464
|
case 'co': match = value != null && String(fieldValue).includes(String(value)); break
|
|
453
465
|
case 'sw': match = value != null && String(fieldValue).startsWith(String(value)); break
|
|
466
|
+
case 'sw_any': match = fieldValue != null && Array.isArray(value)
|
|
467
|
+
&& value.some(prefix => String(fieldValue).startsWith(String(prefix))); break
|
|
454
468
|
case 'ew': match = value != null && String(fieldValue).endsWith(String(value)); break
|
|
455
469
|
case 'pr': match = fieldValue != null; break
|
|
470
|
+
case 'is_null': match = fieldValue == null; break
|
|
456
471
|
case 'in': match = matchesIn(fieldValue, value); break
|
|
457
472
|
case 'nin': match = !matchesIn(fieldValue, value); break
|
|
458
473
|
case 'between': match = matchesBetween(fieldValue, value); break
|
|
@@ -498,10 +513,10 @@ function matchesBetween(fieldValue: any, value: Primitive | Primitive[] | undefi
|
|
|
498
513
|
return String(fieldValue) >= String(lo) && String(fieldValue) <= String(hi)
|
|
499
514
|
}
|
|
500
515
|
|
|
501
|
-
const OPERATORS: ComparisonOperator[] = ['eq', 'ne', 'gt', 'ge', 'lt', 'le', 'co', 'sw', 'ew', 'pr', 'in', 'nin', 'between']
|
|
516
|
+
const OPERATORS: ComparisonOperator[] = ['eq', 'ne', 'gt', 'ge', 'lt', 'le', 'co', 'sw', 'sw_any', 'ew', 'pr', 'in', 'nin', 'between']
|
|
502
517
|
const CONNECTORS: LogicalOperator[] = ['and', 'or']
|
|
503
518
|
/** Operators whose value is a bracket list literal. */
|
|
504
|
-
const LIST_OPERATORS: ComparisonOperator[] = ['in', 'nin', 'between']
|
|
519
|
+
const LIST_OPERATORS: ComparisonOperator[] = ['in', 'nin', 'between', 'sw_any']
|
|
505
520
|
|
|
506
521
|
/**
|
|
507
522
|
* Parse a value string into a Primitive
|
|
@@ -651,7 +666,13 @@ function parseTokens<T extends Record<string, any>>(
|
|
|
651
666
|
value = LIST_OPERATORS.includes(op) ? parseListLiteral(raw) : parseValue(raw)
|
|
652
667
|
}
|
|
653
668
|
|
|
654
|
-
const
|
|
669
|
+
const normalizedOp: ComparisonOperator
|
|
670
|
+
= op === 'eq' && value === null
|
|
671
|
+
? 'is_null'
|
|
672
|
+
: op === 'ne' && value === null
|
|
673
|
+
? 'pr'
|
|
674
|
+
: op
|
|
675
|
+
const condition: FilterCondition<T> = { field, op: normalizedOp, value }
|
|
655
676
|
if (connector) condition.connector = connector
|
|
656
677
|
conditions.push(condition)
|
|
657
678
|
}
|