tsense 0.2.0-next.0 → 0.2.0-next.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/filters/filter-state.d.ts +1 -0
- package/dist/filters/filter-state.js +54 -39
- package/dist/filters/index.js +13 -1
- package/dist/filters/url.js +19 -17
- package/dist/react/filter-builder/filter-builder-defaults.d.ts +7 -0
- package/dist/react/filter-builder/filter-builder-defaults.js +86 -0
- package/dist/react/filter-builder/filter-builder-types.d.ts +58 -0
- package/dist/react/filter-builder/filter-builder-types.js +1 -0
- package/dist/react/filter-builder/filter-builder.d.ts +3 -0
- package/dist/react/filter-builder/filter-builder.js +40 -0
- package/dist/react/filter-builder.js +20 -8
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +1 -1
- package/dist/react/use-filter-builder.d.ts +1 -0
- package/dist/react/use-filter-builder.js +2 -1
- package/dist/transformers/date.js +9 -1
- package/dist/tsense.d.ts +8 -0
- package/dist/tsense.js +74 -23
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ export type FilterState = {
|
|
|
11
11
|
};
|
|
12
12
|
export declare function createInitialState(): FilterState;
|
|
13
13
|
export declare function addRow(state: FilterState): FilterState;
|
|
14
|
+
export declare function addRowWithField(state: FilterState, field: string): FilterState;
|
|
14
15
|
export declare function removeRow(state: FilterState, index: number): FilterState;
|
|
15
16
|
export declare function setRowField(state: FilterState, index: number, field: string): FilterState;
|
|
16
17
|
export declare function setRowCondition(state: FilterState, index: number, condition: string): FilterState;
|
|
@@ -11,11 +11,14 @@ const conditionToOperator = {
|
|
|
11
11
|
};
|
|
12
12
|
let nextRowId = 0;
|
|
13
13
|
export function createInitialState() {
|
|
14
|
-
return { rows: [
|
|
14
|
+
return { rows: [] };
|
|
15
15
|
}
|
|
16
16
|
export function addRow(state) {
|
|
17
17
|
return { rows: [...state.rows, { id: ++nextRowId }] };
|
|
18
18
|
}
|
|
19
|
+
export function addRowWithField(state, field) {
|
|
20
|
+
return { rows: [...state.rows, { id: ++nextRowId, field }] };
|
|
21
|
+
}
|
|
19
22
|
export function removeRow(state, index) {
|
|
20
23
|
return { rows: state.rows.filter((_, i) => i !== index) };
|
|
21
24
|
}
|
|
@@ -37,54 +40,63 @@ export function setRowValue(state, index, value) {
|
|
|
37
40
|
export function clearState() {
|
|
38
41
|
return { rows: [] };
|
|
39
42
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return state;
|
|
45
|
-
const filterValue = preset.filter[field];
|
|
46
|
-
if (filterValue == null)
|
|
47
|
-
return state;
|
|
43
|
+
function filterValueToRow(field, filterValue) {
|
|
44
|
+
if (filterValue == null) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
48
47
|
if (Array.isArray(filterValue)) {
|
|
49
48
|
return {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
id: ++nextRowId,
|
|
50
|
+
field,
|
|
51
|
+
condition: "is_in",
|
|
52
|
+
value: filterValue,
|
|
54
53
|
};
|
|
55
54
|
}
|
|
56
55
|
if (typeof filterValue !== "object" || filterValue instanceof Date) {
|
|
57
56
|
return {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
id: ++nextRowId,
|
|
58
|
+
field,
|
|
59
|
+
condition: "equals",
|
|
60
|
+
value: filterValue,
|
|
62
61
|
};
|
|
63
62
|
}
|
|
64
|
-
const
|
|
65
|
-
const keys = Object.keys(
|
|
63
|
+
const operators = filterValue;
|
|
64
|
+
const keys = Object.keys(operators);
|
|
66
65
|
if (keys.includes("gte") && keys.includes("lte")) {
|
|
67
66
|
return {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
field,
|
|
73
|
-
condition: "between",
|
|
74
|
-
value: [ops.gte, ops.lte],
|
|
75
|
-
},
|
|
76
|
-
],
|
|
67
|
+
id: ++nextRowId,
|
|
68
|
+
field,
|
|
69
|
+
condition: "between",
|
|
70
|
+
value: [operators.gte, operators.lte],
|
|
77
71
|
};
|
|
78
72
|
}
|
|
79
|
-
if (keys[0]) {
|
|
80
|
-
return
|
|
81
|
-
rows: [
|
|
82
|
-
...state.rows,
|
|
83
|
-
{ id: ++nextRowId, field, condition: keys[0], value: ops[keys[0]] },
|
|
84
|
-
],
|
|
85
|
-
};
|
|
73
|
+
if (!keys[0]) {
|
|
74
|
+
return null;
|
|
86
75
|
}
|
|
87
|
-
return
|
|
76
|
+
return {
|
|
77
|
+
id: ++nextRowId,
|
|
78
|
+
field,
|
|
79
|
+
condition: keys[0],
|
|
80
|
+
value: operators[keys[0]],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function applyPreset(state, descriptor, field, name) {
|
|
84
|
+
const column = descriptor.columns.find((c) => c.key === field);
|
|
85
|
+
const preset = column?.presets?.find((p) => p.name === name);
|
|
86
|
+
if (!preset) {
|
|
87
|
+
return state;
|
|
88
|
+
}
|
|
89
|
+
const rows = [];
|
|
90
|
+
for (const [key, value] of Object.entries(preset.filter)) {
|
|
91
|
+
const row = filterValueToRow(key, value);
|
|
92
|
+
if (row) {
|
|
93
|
+
rows.push(row);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (!rows.length) {
|
|
97
|
+
return state;
|
|
98
|
+
}
|
|
99
|
+
return { rows };
|
|
88
100
|
}
|
|
89
101
|
export function conditionsFor(descriptor, field) {
|
|
90
102
|
const column = descriptor.columns.find((c) => c.key === field);
|
|
@@ -98,8 +110,9 @@ export function columnFor(descriptor, field) {
|
|
|
98
110
|
return column;
|
|
99
111
|
}
|
|
100
112
|
function isRowComplete(row) {
|
|
101
|
-
if (!row.field || !row.condition || row.value == null)
|
|
113
|
+
if (!row.field || !row.condition || row.value == null) {
|
|
102
114
|
return false;
|
|
115
|
+
}
|
|
103
116
|
if (row.condition === "between") {
|
|
104
117
|
return (Array.isArray(row.value) && row.value[0] != null && row.value[1] != null);
|
|
105
118
|
}
|
|
@@ -111,8 +124,9 @@ function isRowComplete(row) {
|
|
|
111
124
|
export function buildResult(state) {
|
|
112
125
|
const result = {};
|
|
113
126
|
for (const row of state.rows) {
|
|
114
|
-
if (!isRowComplete(row))
|
|
127
|
+
if (!isRowComplete(row)) {
|
|
115
128
|
continue;
|
|
129
|
+
}
|
|
116
130
|
if (row.condition === "equals" || row.condition === "is_in") {
|
|
117
131
|
result[row.field] = row.value;
|
|
118
132
|
continue;
|
|
@@ -126,8 +140,9 @@ export function buildResult(state) {
|
|
|
126
140
|
continue;
|
|
127
141
|
}
|
|
128
142
|
const operator = conditionToOperator[row.condition];
|
|
129
|
-
if (!operator)
|
|
143
|
+
if (!operator) {
|
|
130
144
|
continue;
|
|
145
|
+
}
|
|
131
146
|
const existing = result[row.field];
|
|
132
147
|
if (typeof existing === "object" &&
|
|
133
148
|
existing !== null &&
|
package/dist/filters/index.js
CHANGED
|
@@ -58,11 +58,23 @@ export function createFilterBuilder(collection, config, options) {
|
|
|
58
58
|
"not?": "string",
|
|
59
59
|
"notIn?": "string[]",
|
|
60
60
|
});
|
|
61
|
+
const dateInput = "number | string | Date";
|
|
62
|
+
const dateOps = type.raw({
|
|
63
|
+
"not?": dateInput,
|
|
64
|
+
"gt?": dateInput,
|
|
65
|
+
"gte?": dateInput,
|
|
66
|
+
"lt?": dateInput,
|
|
67
|
+
"lte?": dateInput,
|
|
68
|
+
"notIn?": `(${dateInput})[]`,
|
|
69
|
+
});
|
|
61
70
|
const fieldSchemas = {
|
|
62
71
|
number: type.raw("number").or(type.raw("number[]")).or(numberOps),
|
|
63
72
|
string: type.raw("string").or(type.raw("string[]")).or(stringOps),
|
|
64
73
|
boolean: type.raw("boolean"),
|
|
65
|
-
date: type
|
|
74
|
+
date: type
|
|
75
|
+
.raw(dateInput)
|
|
76
|
+
.or(type.raw(`(${dateInput})[]`))
|
|
77
|
+
.or(dateOps),
|
|
66
78
|
};
|
|
67
79
|
const descriptor = this.describe();
|
|
68
80
|
const filterDef = {};
|
package/dist/filters/url.js
CHANGED
|
@@ -17,6 +17,11 @@ function coerceValue(raw, columnType) {
|
|
|
17
17
|
}
|
|
18
18
|
return raw;
|
|
19
19
|
}
|
|
20
|
+
function appendValues(params, key, values) {
|
|
21
|
+
for (const value of values) {
|
|
22
|
+
params.append(key, serializeValue(value));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
20
25
|
export function serializeFilter(filter, descriptor) {
|
|
21
26
|
const params = new URLSearchParams();
|
|
22
27
|
const columnMap = new Map(descriptor.columns.map((c) => [c.key, c]));
|
|
@@ -26,12 +31,7 @@ export function serializeFilter(filter, descriptor) {
|
|
|
26
31
|
continue;
|
|
27
32
|
}
|
|
28
33
|
if (Array.isArray(value)) {
|
|
29
|
-
|
|
30
|
-
params.set(key, serializeValue(value[0]));
|
|
31
|
-
}
|
|
32
|
-
else if (value.length > 1) {
|
|
33
|
-
params.set(key, value.map((v) => serializeValue(v)).join(","));
|
|
34
|
-
}
|
|
34
|
+
appendValues(params, key, value);
|
|
35
35
|
continue;
|
|
36
36
|
}
|
|
37
37
|
if (typeof value === "object" && !(value instanceof Date)) {
|
|
@@ -40,7 +40,7 @@ export function serializeFilter(filter, descriptor) {
|
|
|
40
40
|
continue;
|
|
41
41
|
}
|
|
42
42
|
if (Array.isArray(opValue)) {
|
|
43
|
-
params
|
|
43
|
+
appendValues(params, `${key}.${op}`, opValue);
|
|
44
44
|
}
|
|
45
45
|
else {
|
|
46
46
|
params.set(`${key}.${op}`, serializeValue(opValue));
|
|
@@ -55,7 +55,13 @@ export function serializeFilter(filter, descriptor) {
|
|
|
55
55
|
export function deserializeFilter(params, descriptor) {
|
|
56
56
|
const result = {};
|
|
57
57
|
const columnMap = new Map(descriptor.columns.map((c) => [c.key, c]));
|
|
58
|
-
|
|
58
|
+
const visited = new Set();
|
|
59
|
+
for (const paramKey of params.keys()) {
|
|
60
|
+
if (visited.has(paramKey)) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
visited.add(paramKey);
|
|
64
|
+
const rawValues = params.getAll(paramKey);
|
|
59
65
|
const dotIndex = paramKey.indexOf(".");
|
|
60
66
|
if (dotIndex !== -1) {
|
|
61
67
|
const field = paramKey.slice(0, dotIndex);
|
|
@@ -66,12 +72,10 @@ export function deserializeFilter(params, descriptor) {
|
|
|
66
72
|
}
|
|
67
73
|
const existing = (result[field] ?? {});
|
|
68
74
|
if (ARRAY_OPERATORS.has(operator)) {
|
|
69
|
-
existing[operator] =
|
|
70
|
-
.split(",")
|
|
71
|
-
.map((v) => coerceValue(v, column.type));
|
|
75
|
+
existing[operator] = rawValues.map((value) => coerceValue(value, column.type));
|
|
72
76
|
}
|
|
73
77
|
else {
|
|
74
|
-
existing[operator] = coerceValue(
|
|
78
|
+
existing[operator] = coerceValue(rawValues[0], column.type);
|
|
75
79
|
}
|
|
76
80
|
result[field] = existing;
|
|
77
81
|
continue;
|
|
@@ -80,13 +84,11 @@ export function deserializeFilter(params, descriptor) {
|
|
|
80
84
|
if (!column) {
|
|
81
85
|
continue;
|
|
82
86
|
}
|
|
83
|
-
if (
|
|
84
|
-
result[paramKey] =
|
|
85
|
-
.split(",")
|
|
86
|
-
.map((v) => coerceValue(v, column.type));
|
|
87
|
+
if (rawValues.length > 1) {
|
|
88
|
+
result[paramKey] = rawValues.map((value) => coerceValue(value, column.type));
|
|
87
89
|
}
|
|
88
90
|
else {
|
|
89
|
-
result[paramKey] = coerceValue(
|
|
91
|
+
result[paramKey] = coerceValue(rawValues[0], column.type);
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
94
|
return result;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AddButtonSlotProps, ConditionSelectSlotProps, FieldSelectSlotProps, PresetButtonSlotProps, RemoveButtonSlotProps, ValueInputSlotProps } from "./filter-builder-types.js";
|
|
2
|
+
export declare function defaultFieldSelect({ columns, value, onChange }: FieldSelectSlotProps): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
export declare function defaultConditionSelect({ conditions, value, onChange, disabled }: ConditionSelectSlotProps): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
export declare function defaultValueInput({ column, condition, value, onChange }: ValueInputSlotProps): import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
export declare function defaultRemoveButton({ onClick }: RemoveButtonSlotProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export declare function defaultAddButton({ onClick }: AddButtonSlotProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export declare function defaultPresetButton({ preset, onClick }: PresetButtonSlotProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
function formatDateForInput(date) {
|
|
3
|
+
if (!(date instanceof Date)) {
|
|
4
|
+
return "";
|
|
5
|
+
}
|
|
6
|
+
return date.toISOString().slice(0, 10);
|
|
7
|
+
}
|
|
8
|
+
function parseDateInput(value) {
|
|
9
|
+
if (!value) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
return new Date(value + "T00:00:00Z");
|
|
13
|
+
}
|
|
14
|
+
function parseNumberInput(value) {
|
|
15
|
+
if (!value) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
return Number(value);
|
|
19
|
+
}
|
|
20
|
+
function parseBooleanInput(value) {
|
|
21
|
+
if (!value) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
return value === "true";
|
|
25
|
+
}
|
|
26
|
+
function asTuple(value) {
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
return [value[0], value[1]];
|
|
29
|
+
}
|
|
30
|
+
return [undefined, undefined];
|
|
31
|
+
}
|
|
32
|
+
function renderSelectOptions(options, defaultValue) {
|
|
33
|
+
const allOptions = defaultValue
|
|
34
|
+
? [{ key: "", label: defaultValue }, ...options]
|
|
35
|
+
: options;
|
|
36
|
+
return allOptions.map((option) => (_jsx("option", { value: option.key, children: option.label }, option.key)));
|
|
37
|
+
}
|
|
38
|
+
export function defaultFieldSelect({ columns, value, onChange, }) {
|
|
39
|
+
return (_jsx("select", { className: "rounded border px-2 py-1", value: value ?? "", onChange: (event) => onChange(event.target.value), children: renderSelectOptions(columns, "Column") }));
|
|
40
|
+
}
|
|
41
|
+
export function defaultConditionSelect({ conditions, value, onChange, disabled, }) {
|
|
42
|
+
return (_jsx("select", { className: "rounded border px-2 py-1", value: value ?? "", onChange: (event) => onChange(event.target.value), disabled: disabled, children: renderSelectOptions(conditions, "Condition") }));
|
|
43
|
+
}
|
|
44
|
+
export function defaultValueInput({ column, condition, value, onChange, }) {
|
|
45
|
+
if (condition === "between" && column.type === "date") {
|
|
46
|
+
const tuple = asTuple(value);
|
|
47
|
+
return (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("input", { className: "rounded border px-2 py-1", type: "date", value: formatDateForInput(tuple[0]), onChange: (event) => onChange([parseDateInput(event.target.value), tuple[1]]) }), _jsx("span", { className: "text-sm text-gray-500", children: "and" }), _jsx("input", { className: "rounded border px-2 py-1", type: "date", value: formatDateForInput(tuple[1]), onChange: (event) => onChange([tuple[0], parseDateInput(event.target.value)]) })] }));
|
|
48
|
+
}
|
|
49
|
+
if (condition === "between") {
|
|
50
|
+
const tuple = asTuple(value);
|
|
51
|
+
return (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("input", { className: "w-24 rounded border px-2 py-1", type: "number", value: String(tuple[0] ?? ""), onChange: (event) => onChange([parseNumberInput(event.target.value), tuple[1]]) }), _jsx("span", { className: "text-sm text-gray-500", children: "and" }), _jsx("input", { className: "w-24 rounded border px-2 py-1", type: "number", value: String(tuple[1] ?? ""), onChange: (event) => onChange([tuple[0], parseNumberInput(event.target.value)]) })] }));
|
|
52
|
+
}
|
|
53
|
+
if (column.values) {
|
|
54
|
+
const selected = Array.isArray(value) ? value : [];
|
|
55
|
+
return (_jsx("div", { className: "flex flex-wrap gap-2", children: column.values.map((option) => {
|
|
56
|
+
const checked = selected.includes(option.value);
|
|
57
|
+
return (_jsxs("label", { className: "flex cursor-pointer select-none items-center gap-1 text-sm", children: [_jsx("input", { type: "checkbox", checked: checked, onChange: () => onChange(checked
|
|
58
|
+
? selected.filter((item) => item !== option.value)
|
|
59
|
+
: [...selected, option.value]) }), option.label] }, option.value));
|
|
60
|
+
}) }));
|
|
61
|
+
}
|
|
62
|
+
if (column.type === "date") {
|
|
63
|
+
return (_jsx("input", { className: "rounded border px-2 py-1", type: "date", value: formatDateForInput(value), onChange: (event) => onChange(parseDateInput(event.target.value)) }));
|
|
64
|
+
}
|
|
65
|
+
if (column.type === "number") {
|
|
66
|
+
return (_jsx("input", { className: "rounded border px-2 py-1", type: "number", value: String(value ?? ""), onChange: (event) => onChange(parseNumberInput(event.target.value)) }));
|
|
67
|
+
}
|
|
68
|
+
if (column.type === "boolean") {
|
|
69
|
+
const options = [
|
|
70
|
+
{ key: "", label: "Value" },
|
|
71
|
+
{ key: "true", label: "true" },
|
|
72
|
+
{ key: "false", label: "false" },
|
|
73
|
+
];
|
|
74
|
+
return (_jsx("select", { className: "rounded border px-2 py-1", value: value == null ? "" : String(value), onChange: (event) => onChange(parseBooleanInput(event.target.value)), children: renderSelectOptions(options) }));
|
|
75
|
+
}
|
|
76
|
+
return (_jsx("input", { className: "rounded border px-2 py-1", type: "text", value: typeof value === "string" ? value : "", onChange: (event) => onChange(event.target.value) }));
|
|
77
|
+
}
|
|
78
|
+
export function defaultRemoveButton({ onClick }) {
|
|
79
|
+
return (_jsx("button", { type: "button", className: "text-red-500 hover:text-red-700", onClick: onClick, children: "\u00D7" }));
|
|
80
|
+
}
|
|
81
|
+
export function defaultAddButton({ onClick }) {
|
|
82
|
+
return (_jsx("button", { type: "button", className: "text-blue-500 hover:text-blue-700", onClick: onClick, children: "+ Add filter" }));
|
|
83
|
+
}
|
|
84
|
+
export function defaultPresetButton({ preset, onClick, }) {
|
|
85
|
+
return (_jsx("button", { type: "button", className: "rounded bg-gray-100 px-2 py-1 text-sm hover:bg-gray-200", onClick: onClick, children: preset.name }));
|
|
86
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { FilterValue } from "../../filters/filter-state.js";
|
|
3
|
+
import type { FilterDescriptor } from "../../filters/index.js";
|
|
4
|
+
import type { FilterFor } from "../../types.js";
|
|
5
|
+
export type FieldSelectSlotProps = {
|
|
6
|
+
columns: FilterDescriptor["columns"];
|
|
7
|
+
value: string | undefined;
|
|
8
|
+
onChange: (field: string) => void;
|
|
9
|
+
};
|
|
10
|
+
export type ConditionSelectSlotProps = {
|
|
11
|
+
conditions: FilterDescriptor["columns"][number]["conditions"];
|
|
12
|
+
value: string | undefined;
|
|
13
|
+
onChange: (condition: string) => void;
|
|
14
|
+
disabled: boolean;
|
|
15
|
+
};
|
|
16
|
+
export type ValueInputSlotProps = {
|
|
17
|
+
column: FilterDescriptor["columns"][number];
|
|
18
|
+
condition: string;
|
|
19
|
+
value: FilterValue;
|
|
20
|
+
onChange: (value: FilterValue) => void;
|
|
21
|
+
};
|
|
22
|
+
export type RemoveButtonSlotProps = {
|
|
23
|
+
onClick: () => void;
|
|
24
|
+
};
|
|
25
|
+
export type AddButtonSlotProps = {
|
|
26
|
+
onClick: () => void;
|
|
27
|
+
};
|
|
28
|
+
export type PresetButtonSlotProps = {
|
|
29
|
+
preset: {
|
|
30
|
+
field: string;
|
|
31
|
+
name: string;
|
|
32
|
+
};
|
|
33
|
+
onClick: () => void;
|
|
34
|
+
};
|
|
35
|
+
export type RowSlotProps = {
|
|
36
|
+
index: number;
|
|
37
|
+
fieldSelect: ReactNode;
|
|
38
|
+
conditionSelect: ReactNode;
|
|
39
|
+
valueInput: ReactNode | null;
|
|
40
|
+
removeButton: ReactNode;
|
|
41
|
+
};
|
|
42
|
+
export type RootSlotProps = {
|
|
43
|
+
rows: ReactNode;
|
|
44
|
+
addButton: ReactNode;
|
|
45
|
+
presets: ReactNode | null;
|
|
46
|
+
};
|
|
47
|
+
export type FilterBuilderProps<T> = {
|
|
48
|
+
descriptor: FilterDescriptor<T>;
|
|
49
|
+
onChange?: (filter: FilterFor<T>) => void;
|
|
50
|
+
renderRoot?: (props: RootSlotProps) => ReactNode;
|
|
51
|
+
renderRow?: (props: RowSlotProps) => ReactNode;
|
|
52
|
+
renderFieldSelect?: (props: FieldSelectSlotProps) => ReactNode;
|
|
53
|
+
renderConditionSelect?: (props: ConditionSelectSlotProps) => ReactNode;
|
|
54
|
+
renderValueInput?: (props: ValueInputSlotProps) => ReactNode;
|
|
55
|
+
renderAddButton?: (props: AddButtonSlotProps) => ReactNode;
|
|
56
|
+
renderRemoveButton?: (props: RemoveButtonSlotProps) => ReactNode;
|
|
57
|
+
renderPresetButton?: (props: PresetButtonSlotProps) => ReactNode;
|
|
58
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { FilterBuilderProps } from "./filter-builder-types.js";
|
|
3
|
+
export declare function FilterBuilder<T>({ descriptor, onChange, renderRoot, renderRow, renderFieldSelect, renderConditionSelect, renderValueInput, renderAddButton, renderRemoveButton, renderPresetButton }: FilterBuilderProps<T>): ReactNode;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, useEffect, useRef } from "react";
|
|
3
|
+
import { useFilterBuilder } from "../use-filter-builder.js";
|
|
4
|
+
import { defaultAddButton, defaultConditionSelect, defaultFieldSelect, defaultPresetButton, defaultRemoveButton, defaultValueInput, } from "./filter-builder-defaults.js";
|
|
5
|
+
export function FilterBuilder({ descriptor, onChange, renderRoot, renderRow, renderFieldSelect, renderConditionSelect, renderValueInput, renderAddButton, renderRemoveButton, renderPresetButton, }) {
|
|
6
|
+
const filters = useFilterBuilder(descriptor);
|
|
7
|
+
const onChangeRef = useRef(onChange);
|
|
8
|
+
onChangeRef.current = onChange;
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
onChangeRef.current?.(filters.result);
|
|
11
|
+
}, [filters.result]);
|
|
12
|
+
const FieldSelect = renderFieldSelect ?? defaultFieldSelect;
|
|
13
|
+
const ConditionSelect = renderConditionSelect ?? defaultConditionSelect;
|
|
14
|
+
const ValueInput = renderValueInput ?? defaultValueInput;
|
|
15
|
+
const RemoveButton = renderRemoveButton ?? defaultRemoveButton;
|
|
16
|
+
const AddButton = renderAddButton ?? defaultAddButton;
|
|
17
|
+
const PresetButton = renderPresetButton ?? defaultPresetButton;
|
|
18
|
+
const rows = filters.rows.map((row, index) => {
|
|
19
|
+
const fieldSelect = (_jsx(FieldSelect, { columns: filters.columns, value: row.field, onChange: (field) => filters.setField(index, field) }));
|
|
20
|
+
const conditionSelect = (_jsx(ConditionSelect, { conditions: row.field ? filters.conditionsFor(row.field) : [], value: row.condition, onChange: (condition) => filters.setCondition(index, condition), disabled: !row.field }));
|
|
21
|
+
const valueInput = row.field && row.condition ? (_jsx(ValueInput, { column: filters.columnFor(row.field), condition: row.condition, value: row.value, onChange: (value) => filters.setValue(index, value) })) : null;
|
|
22
|
+
const removeButton = _jsx(RemoveButton, { onClick: () => filters.remove(index) });
|
|
23
|
+
if (renderRow) {
|
|
24
|
+
return (_jsx(Fragment, { children: renderRow({
|
|
25
|
+
index,
|
|
26
|
+
fieldSelect,
|
|
27
|
+
conditionSelect,
|
|
28
|
+
valueInput,
|
|
29
|
+
removeButton,
|
|
30
|
+
}) }, row.id));
|
|
31
|
+
}
|
|
32
|
+
return (_jsxs("div", { className: "flex items-center gap-2", children: [fieldSelect, conditionSelect, valueInput, removeButton] }, row.id));
|
|
33
|
+
});
|
|
34
|
+
const addButton = _jsx(AddButton, { onClick: filters.add });
|
|
35
|
+
const presets = filters.presets.length > 0 ? (_jsx("div", { className: "flex flex-wrap gap-1 border-t pt-2", children: filters.presets.map((preset) => (_jsx(Fragment, { children: _jsx(PresetButton, { preset: preset, onClick: () => filters.applyPreset(preset.field, preset.name) }) }, `${preset.field}-${preset.name}`))) })) : null;
|
|
36
|
+
if (renderRoot) {
|
|
37
|
+
return _jsx(_Fragment, { children: renderRoot({ rows: _jsx(_Fragment, { children: rows }), addButton, presets }) });
|
|
38
|
+
}
|
|
39
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [rows, addButton, presets] }));
|
|
40
|
+
}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Fragment, useEffect, useRef } from "react";
|
|
3
3
|
import { useFilterBuilder } from "./use-filter-builder.js";
|
|
4
|
-
|
|
5
|
-
return (_jsxs("select", { className: "rounded border px-2 py-1", value: value ?? "", onChange: (e) => onChange(e.target.value), children: [_jsx("option", { value: "", children: "Column" }), columns.map((col) => (_jsx("option", { value: col.key, children: col.label }, col.key)))] }));
|
|
6
|
-
}
|
|
7
|
-
function defaultConditionSelect({ conditions, value, onChange, disabled, }) {
|
|
8
|
-
return (_jsxs("select", { className: "rounded border px-2 py-1", value: value ?? "", onChange: (e) => onChange(e.target.value), disabled: disabled, children: [_jsx("option", { value: "", children: "Condition" }), conditions.map((c) => (_jsx("option", { value: c.key, children: c.label }, c.key)))] }));
|
|
9
|
-
}
|
|
4
|
+
// ======== Helpers ========
|
|
10
5
|
function formatDateForInput(date) {
|
|
11
6
|
if (!(date instanceof Date))
|
|
12
7
|
return "";
|
|
@@ -23,6 +18,18 @@ function asTuple(value) {
|
|
|
23
18
|
}
|
|
24
19
|
return [undefined, undefined];
|
|
25
20
|
}
|
|
21
|
+
function renderSelectOptions(options, defaultValue) {
|
|
22
|
+
const opts = defaultValue
|
|
23
|
+
? [{ key: "", label: defaultValue }, ...options]
|
|
24
|
+
: options;
|
|
25
|
+
return opts.map((o) => (_jsx("option", { value: o.key, children: o.label }, o.key)));
|
|
26
|
+
}
|
|
27
|
+
function defaultFieldSelect({ columns, value, onChange, }) {
|
|
28
|
+
return (_jsx("select", { className: "rounded border px-2 py-1", value: value ?? "", onChange: (e) => onChange(e.target.value), children: renderSelectOptions(columns, "Column") }));
|
|
29
|
+
}
|
|
30
|
+
function defaultConditionSelect({ conditions, value, onChange, disabled, }) {
|
|
31
|
+
return (_jsx("select", { className: "rounded border px-2 py-1", value: value ?? "", onChange: (e) => onChange(e.target.value), disabled: disabled, children: renderSelectOptions(conditions, "Condition") }));
|
|
32
|
+
}
|
|
26
33
|
function defaultValueInput({ column, condition, value, onChange, }) {
|
|
27
34
|
if (condition === "between" && column.type === "date") {
|
|
28
35
|
const tuple = asTuple(value);
|
|
@@ -36,7 +43,7 @@ function defaultValueInput({ column, condition, value, onChange, }) {
|
|
|
36
43
|
const selected = Array.isArray(value) ? value : [];
|
|
37
44
|
return (_jsx("div", { className: "flex flex-wrap gap-2", children: column.values.map((v) => {
|
|
38
45
|
const checked = selected.includes(v.value);
|
|
39
|
-
return (_jsxs("label", { className: "flex items-center gap-1 text-sm", children: [_jsx("input", { type: "checkbox", checked: checked, onChange: () => onChange(checked
|
|
46
|
+
return (_jsxs("label", { className: "flex cursor-pointer select-none items-center gap-1 text-sm", children: [_jsx("input", { type: "checkbox", checked: checked, onChange: () => onChange(checked
|
|
40
47
|
? selected.filter((s) => s !== v.value)
|
|
41
48
|
: [...selected, v.value]) }), v.label] }, v.value));
|
|
42
49
|
}) }));
|
|
@@ -48,7 +55,12 @@ function defaultValueInput({ column, condition, value, onChange, }) {
|
|
|
48
55
|
return (_jsx("input", { className: "rounded border px-2 py-1", type: "number", value: String(value ?? ""), onChange: (e) => onChange(Number(e.target.value)) }));
|
|
49
56
|
}
|
|
50
57
|
if (column.type === "boolean") {
|
|
51
|
-
|
|
58
|
+
const options = [
|
|
59
|
+
{ key: "", label: "Value" },
|
|
60
|
+
{ key: "true", label: "true" },
|
|
61
|
+
{ key: "false", label: "false" },
|
|
62
|
+
];
|
|
63
|
+
return (_jsx("select", { className: "rounded border px-2 py-1", value: value == null ? "" : String(value), onChange: (e) => onChange(e.target.value === "true"), children: renderSelectOptions(options) }));
|
|
52
64
|
}
|
|
53
65
|
return (_jsx("input", { className: "rounded border px-2 py-1", type: "text", value: typeof value === "string" ? value : "", onChange: (e) => onChange(e.target.value) }));
|
|
54
66
|
}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { FilterBuilder } from "./filter-builder.js";
|
|
2
|
-
export type { AddButtonSlotProps, ConditionSelectSlotProps, FieldSelectSlotProps, PresetButtonSlotProps, RemoveButtonSlotProps, RootSlotProps, RowSlotProps, ValueInputSlotProps, } from "./filter-builder.js";
|
|
1
|
+
export { FilterBuilder } from "./filter-builder/filter-builder.js";
|
|
2
|
+
export type { AddButtonSlotProps, ConditionSelectSlotProps, FieldSelectSlotProps, PresetButtonSlotProps, RemoveButtonSlotProps, RootSlotProps, RowSlotProps, ValueInputSlotProps, } from "./filter-builder/filter-builder-types.js";
|
|
3
3
|
export type { FilterValue } from "../filters/filter-state.js";
|
|
4
4
|
export { useFilterBuilder } from "./use-filter-builder.js";
|
package/dist/react/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { FilterBuilder } from "./filter-builder.js";
|
|
1
|
+
export { FilterBuilder } from "./filter-builder/filter-builder.js";
|
|
2
2
|
export { useFilterBuilder } from "./use-filter-builder.js";
|
|
@@ -8,6 +8,7 @@ type UseFilterBuilderReturn = {
|
|
|
8
8
|
columns: FilterDescriptor["columns"];
|
|
9
9
|
rows: FilterRow[];
|
|
10
10
|
add: () => void;
|
|
11
|
+
addWithField: (field: string) => void;
|
|
11
12
|
remove: (index: number) => void;
|
|
12
13
|
setField: (index: number, field: string) => void;
|
|
13
14
|
setCondition: (index: number, condition: string) => void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useMemo, useState } from "react";
|
|
2
|
-
import { addRow, applyPreset as applyPresetState, buildResult, clearState, columnFor, conditionsFor, createInitialState, removeRow, setRowCondition, setRowField, setRowValue, } from "../filters/filter-state.js";
|
|
2
|
+
import { addRow, addRowWithField, applyPreset as applyPresetState, buildResult, clearState, columnFor, conditionsFor, createInitialState, removeRow, setRowCondition, setRowField, setRowValue, } from "../filters/filter-state.js";
|
|
3
3
|
export function useFilterBuilder(descriptor) {
|
|
4
4
|
const [state, setState] = useState(createInitialState);
|
|
5
5
|
const presets = useMemo(() => descriptor.columns.flatMap((col) => (col.presets ?? []).map((p) => ({ field: col.key, name: p.name }))), [descriptor]);
|
|
@@ -8,6 +8,7 @@ export function useFilterBuilder(descriptor) {
|
|
|
8
8
|
columns: descriptor.columns,
|
|
9
9
|
rows: state.rows,
|
|
10
10
|
add: () => setState(addRow),
|
|
11
|
+
addWithField: (field) => setState((s) => addRowWithField(s, field)),
|
|
11
12
|
remove: (index) => setState((s) => removeRow(s, index)),
|
|
12
13
|
setField: (index, field) => setState((s) => setRowField(s, index, field)),
|
|
13
14
|
setCondition: (index, condition) => setState((s) => setRowCondition(s, index, condition)),
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
export const DateTransformer = {
|
|
2
2
|
match: (expr, domain) => expr === "Date" || domain === "Date",
|
|
3
3
|
storageType: "int64",
|
|
4
|
-
serialize: (date) =>
|
|
4
|
+
serialize: (date) => {
|
|
5
|
+
if (typeof date === "number") {
|
|
6
|
+
return date;
|
|
7
|
+
}
|
|
8
|
+
if (typeof date === "string") {
|
|
9
|
+
return new Date(date).getTime();
|
|
10
|
+
}
|
|
11
|
+
return date.getTime();
|
|
12
|
+
},
|
|
5
13
|
deserialize: (ts) => new Date(ts),
|
|
6
14
|
};
|
package/dist/tsense.d.ts
CHANGED
|
@@ -76,17 +76,25 @@ export declare class TSense<T extends Type> {
|
|
|
76
76
|
syncSchema(): Promise<void>;
|
|
77
77
|
private buildObjectFilter;
|
|
78
78
|
private buildFilter;
|
|
79
|
+
private validateFilterFields;
|
|
79
80
|
private validateFields;
|
|
81
|
+
private buildFilterExpression;
|
|
82
|
+
private combineFilterExpressions;
|
|
80
83
|
private buildSort;
|
|
81
84
|
create(): Promise<this>;
|
|
82
85
|
drop(): Promise<void>;
|
|
83
86
|
get(id: string): Promise<T["infer"] | null>;
|
|
84
87
|
delete(id: string): Promise<boolean>;
|
|
88
|
+
private deleteManyWithFilterBy;
|
|
85
89
|
deleteMany(filter: FilterFor<T["infer"]>): Promise<DeleteResult>;
|
|
86
90
|
update(id: string, data: Partial<T["infer"]>): Promise<T["infer"]>;
|
|
91
|
+
private updateManyWithFilterBy;
|
|
87
92
|
updateMany(filter: FilterFor<T["infer"]>, data: Partial<T["infer"]>): Promise<UpdateResult>;
|
|
93
|
+
private searchWithFilterBy;
|
|
88
94
|
search<const O extends SearchOptions<T["infer"]> = SearchOptionsPlain<T["infer"]>>(options: O): Promise<SearchResult<ProjectSearch<T["infer"], O>>>;
|
|
95
|
+
private searchListWithFilterBy;
|
|
89
96
|
searchList(options: SearchListOptions<T["infer"]>): Promise<SearchListResult<T["infer"]>>;
|
|
97
|
+
private countWithFilterBy;
|
|
90
98
|
count(filter?: FilterFor<T["infer"]>): Promise<number>;
|
|
91
99
|
upsert(docs: T["infer"] | T["infer"][]): Promise<UpsertResult[]>;
|
|
92
100
|
syncData(options?: SyncOptions): Promise<SyncResult>;
|
package/dist/tsense.js
CHANGED
|
@@ -72,6 +72,11 @@ export class TSense {
|
|
|
72
72
|
}
|
|
73
73
|
serializeDoc(doc) {
|
|
74
74
|
const result = { ...doc };
|
|
75
|
+
for (const key of Object.keys(result)) {
|
|
76
|
+
if (result[key] == null) {
|
|
77
|
+
delete result[key];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
75
80
|
for (const [field, transformer] of this.fieldTransformers) {
|
|
76
81
|
if (result[field] != null) {
|
|
77
82
|
result[field] = transformer.serialize(result[field]);
|
|
@@ -204,8 +209,14 @@ export class TSense {
|
|
|
204
209
|
const orParts = [];
|
|
205
210
|
for (const condition of rawValue) {
|
|
206
211
|
const inner = this.buildFilter(condition);
|
|
212
|
+
if (!inner.length) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
207
215
|
orParts.push(`(${inner.join("&&")})`);
|
|
208
216
|
}
|
|
217
|
+
if (!orParts.length) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
209
220
|
result.push(`(${orParts.join("||")})`);
|
|
210
221
|
continue;
|
|
211
222
|
}
|
|
@@ -227,6 +238,28 @@ export class TSense {
|
|
|
227
238
|
}
|
|
228
239
|
return result;
|
|
229
240
|
}
|
|
241
|
+
validateFilterFields(filter) {
|
|
242
|
+
if (!filter) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const fields = [];
|
|
246
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
247
|
+
if (value == null) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (key === "OR") {
|
|
251
|
+
for (const condition of value) {
|
|
252
|
+
this.validateFilterFields(condition);
|
|
253
|
+
}
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
fields.push(key);
|
|
257
|
+
}
|
|
258
|
+
if (!fields.length) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
this.validateFields(fields);
|
|
262
|
+
}
|
|
230
263
|
validateFields(fields) {
|
|
231
264
|
const valid = new Set(this.fields.map((f) => f.name));
|
|
232
265
|
for (const field of fields) {
|
|
@@ -235,6 +268,20 @@ export class TSense {
|
|
|
235
268
|
}
|
|
236
269
|
}
|
|
237
270
|
}
|
|
271
|
+
buildFilterExpression(filter) {
|
|
272
|
+
this.validateFilterFields(filter);
|
|
273
|
+
const parts = this.buildFilter(filter);
|
|
274
|
+
if (!parts.length) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
return `(${parts.join("&&")})`;
|
|
278
|
+
}
|
|
279
|
+
combineFilterExpressions(...filters) {
|
|
280
|
+
return filters
|
|
281
|
+
.map((filter) => this.buildFilterExpression(filter))
|
|
282
|
+
.filter((filter) => filter != null)
|
|
283
|
+
.join("&&");
|
|
284
|
+
}
|
|
238
285
|
buildSort(options) {
|
|
239
286
|
if (!options.sortBy)
|
|
240
287
|
return;
|
|
@@ -292,9 +339,8 @@ export class TSense {
|
|
|
292
339
|
});
|
|
293
340
|
return data != null;
|
|
294
341
|
}
|
|
295
|
-
async
|
|
342
|
+
async deleteManyWithFilterBy(filterBy) {
|
|
296
343
|
await this.ensureSynced();
|
|
297
|
-
const filterBy = this.buildFilter(filter).join("&&");
|
|
298
344
|
if (!filterBy) {
|
|
299
345
|
throw new Error("FILTER_REQUIRED");
|
|
300
346
|
}
|
|
@@ -305,6 +351,9 @@ export class TSense {
|
|
|
305
351
|
});
|
|
306
352
|
return { deleted: data.num_deleted };
|
|
307
353
|
}
|
|
354
|
+
async deleteMany(filter) {
|
|
355
|
+
return await this.deleteManyWithFilterBy(this.combineFilterExpressions(filter));
|
|
356
|
+
}
|
|
308
357
|
async update(id, data) {
|
|
309
358
|
await this.ensureSynced();
|
|
310
359
|
const serialized = this.serializeDoc(data);
|
|
@@ -315,9 +364,8 @@ export class TSense {
|
|
|
315
364
|
});
|
|
316
365
|
return this.deserializeDoc(updated);
|
|
317
366
|
}
|
|
318
|
-
async
|
|
367
|
+
async updateManyWithFilterBy(filterBy, data) {
|
|
319
368
|
await this.ensureSynced();
|
|
320
|
-
const filterBy = this.buildFilter(filter).join("&&");
|
|
321
369
|
if (!filterBy) {
|
|
322
370
|
throw new Error("FILTER_REQUIRED");
|
|
323
371
|
}
|
|
@@ -330,7 +378,10 @@ export class TSense {
|
|
|
330
378
|
});
|
|
331
379
|
return { updated: result.num_updated };
|
|
332
380
|
}
|
|
333
|
-
async
|
|
381
|
+
async updateMany(filter, data) {
|
|
382
|
+
return await this.updateManyWithFilterBy(this.combineFilterExpressions(filter), data);
|
|
383
|
+
}
|
|
384
|
+
async searchWithFilterBy(options, filterBy) {
|
|
334
385
|
await this.ensureSynced();
|
|
335
386
|
const queryByFields = options.queryBy ?? [
|
|
336
387
|
this.options.defaultSearchField,
|
|
@@ -352,7 +403,6 @@ export class TSense {
|
|
|
352
403
|
const sortBy = this.buildSort(options);
|
|
353
404
|
if (sortBy)
|
|
354
405
|
params.sort_by = sortBy;
|
|
355
|
-
const filterBy = this.buildFilter(options.filter).join("&&");
|
|
356
406
|
if (filterBy)
|
|
357
407
|
params.filter_by = filterBy;
|
|
358
408
|
if (options.page != null)
|
|
@@ -418,17 +468,19 @@ export class TSense {
|
|
|
418
468
|
scores,
|
|
419
469
|
};
|
|
420
470
|
}
|
|
421
|
-
async
|
|
471
|
+
async search(options) {
|
|
472
|
+
return await this.searchWithFilterBy(options, this.combineFilterExpressions(options.filter));
|
|
473
|
+
}
|
|
474
|
+
async searchListWithFilterBy(options, filterBy) {
|
|
422
475
|
const page = options.cursor ? Number(options.cursor) : 1;
|
|
423
476
|
const limit = Math.min(options.limit ?? 20, 100);
|
|
424
|
-
const result = await this.
|
|
477
|
+
const result = await this.searchWithFilterBy({
|
|
425
478
|
query: options.query,
|
|
426
479
|
queryBy: options.queryBy,
|
|
427
|
-
filter: options.filter,
|
|
428
480
|
sortBy: [options.sortBy],
|
|
429
481
|
page,
|
|
430
482
|
limit,
|
|
431
|
-
});
|
|
483
|
+
}, filterBy);
|
|
432
484
|
const hasMore = page * limit < result.count;
|
|
433
485
|
return {
|
|
434
486
|
data: result.data,
|
|
@@ -436,9 +488,11 @@ export class TSense {
|
|
|
436
488
|
total: result.count,
|
|
437
489
|
};
|
|
438
490
|
}
|
|
439
|
-
async
|
|
491
|
+
async searchList(options) {
|
|
492
|
+
return await this.searchListWithFilterBy(options, this.combineFilterExpressions(options.filter));
|
|
493
|
+
}
|
|
494
|
+
async countWithFilterBy(filterBy) {
|
|
440
495
|
await this.ensureSynced();
|
|
441
|
-
const filterBy = this.buildFilter(filter).join("&&");
|
|
442
496
|
if (!filterBy) {
|
|
443
497
|
const { data } = await this.axios({
|
|
444
498
|
method: "GET",
|
|
@@ -459,6 +513,9 @@ export class TSense {
|
|
|
459
513
|
});
|
|
460
514
|
return data.found;
|
|
461
515
|
}
|
|
516
|
+
async count(filter) {
|
|
517
|
+
return await this.countWithFilterBy(this.combineFilterExpressions(filter));
|
|
518
|
+
}
|
|
462
519
|
async upsert(docs) {
|
|
463
520
|
await this.ensureSynced();
|
|
464
521
|
const items = Array.isArray(docs) ? docs : [docs];
|
|
@@ -538,17 +595,11 @@ export class TSense {
|
|
|
538
595
|
}
|
|
539
596
|
scoped(baseFilter) {
|
|
540
597
|
return {
|
|
541
|
-
search: (options) => this.
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
...options,
|
|
547
|
-
filter: { ...options.filter, ...baseFilter },
|
|
548
|
-
}),
|
|
549
|
-
count: (filter) => this.count({ ...filter, ...baseFilter }),
|
|
550
|
-
deleteMany: (filter) => this.deleteMany({ ...filter, ...baseFilter }),
|
|
551
|
-
updateMany: (filter, data) => this.updateMany({ ...filter, ...baseFilter }, data),
|
|
598
|
+
search: (options) => this.searchWithFilterBy(options, this.combineFilterExpressions(baseFilter, options.filter)),
|
|
599
|
+
searchList: (options) => this.searchListWithFilterBy(options, this.combineFilterExpressions(baseFilter, options.filter)),
|
|
600
|
+
count: (filter) => this.countWithFilterBy(this.combineFilterExpressions(baseFilter, filter)),
|
|
601
|
+
deleteMany: (filter) => this.deleteManyWithFilterBy(this.combineFilterExpressions(baseFilter, filter)),
|
|
602
|
+
updateMany: (filter, data) => this.updateManyWithFilterBy(this.combineFilterExpressions(baseFilter, filter), data),
|
|
552
603
|
};
|
|
553
604
|
}
|
|
554
605
|
}
|