tsense 0.2.0-next.2 → 0.2.0-next.4
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.js +23 -13
- package/dist/filters/url.js +19 -17
- package/dist/index.d.ts +1 -1
- 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/index.d.ts +2 -2
- package/dist/react/index.js +1 -1
- package/dist/tsense.d.ts +10 -2
- package/dist/tsense.js +69 -23
- package/dist/types.d.ts +0 -3
- package/package.json +1 -1
|
@@ -60,20 +60,25 @@ function filterValueToRow(field, filterValue) {
|
|
|
60
60
|
value: filterValue,
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
|
-
const
|
|
64
|
-
const keys = Object.keys(
|
|
63
|
+
const operators = filterValue;
|
|
64
|
+
const keys = Object.keys(operators);
|
|
65
65
|
if (keys.includes("gte") && keys.includes("lte")) {
|
|
66
66
|
return {
|
|
67
67
|
id: ++nextRowId,
|
|
68
68
|
field,
|
|
69
69
|
condition: "between",
|
|
70
|
-
value: [
|
|
70
|
+
value: [operators.gte, operators.lte],
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
|
-
if (keys[0]) {
|
|
74
|
-
return
|
|
73
|
+
if (!keys[0]) {
|
|
74
|
+
return null;
|
|
75
75
|
}
|
|
76
|
-
return
|
|
76
|
+
return {
|
|
77
|
+
id: ++nextRowId,
|
|
78
|
+
field,
|
|
79
|
+
condition: keys[0],
|
|
80
|
+
value: operators[keys[0]],
|
|
81
|
+
};
|
|
77
82
|
}
|
|
78
83
|
export function applyPreset(state, descriptor, field, name) {
|
|
79
84
|
const column = descriptor.columns.find((c) => c.key === field);
|
|
@@ -81,17 +86,19 @@ export function applyPreset(state, descriptor, field, name) {
|
|
|
81
86
|
if (!preset) {
|
|
82
87
|
return state;
|
|
83
88
|
}
|
|
84
|
-
const
|
|
89
|
+
const presetRows = [];
|
|
85
90
|
for (const [key, value] of Object.entries(preset.filter)) {
|
|
86
91
|
const row = filterValueToRow(key, value);
|
|
87
92
|
if (row) {
|
|
88
|
-
|
|
93
|
+
presetRows.push(row);
|
|
89
94
|
}
|
|
90
95
|
}
|
|
91
|
-
if (!
|
|
96
|
+
if (!presetRows.length) {
|
|
92
97
|
return state;
|
|
93
98
|
}
|
|
94
|
-
|
|
99
|
+
const presetFields = new Set(presetRows.map((r) => r.field));
|
|
100
|
+
const preserved = state.rows.filter((r) => !r.field || !presetFields.has(r.field));
|
|
101
|
+
return { rows: [...preserved, ...presetRows] };
|
|
95
102
|
}
|
|
96
103
|
export function conditionsFor(descriptor, field) {
|
|
97
104
|
const column = descriptor.columns.find((c) => c.key === field);
|
|
@@ -105,8 +112,9 @@ export function columnFor(descriptor, field) {
|
|
|
105
112
|
return column;
|
|
106
113
|
}
|
|
107
114
|
function isRowComplete(row) {
|
|
108
|
-
if (!row.field || !row.condition || row.value == null)
|
|
115
|
+
if (!row.field || !row.condition || row.value == null) {
|
|
109
116
|
return false;
|
|
117
|
+
}
|
|
110
118
|
if (row.condition === "between") {
|
|
111
119
|
return (Array.isArray(row.value) && row.value[0] != null && row.value[1] != null);
|
|
112
120
|
}
|
|
@@ -118,8 +126,9 @@ function isRowComplete(row) {
|
|
|
118
126
|
export function buildResult(state) {
|
|
119
127
|
const result = {};
|
|
120
128
|
for (const row of state.rows) {
|
|
121
|
-
if (!isRowComplete(row))
|
|
129
|
+
if (!isRowComplete(row)) {
|
|
122
130
|
continue;
|
|
131
|
+
}
|
|
123
132
|
if (row.condition === "equals" || row.condition === "is_in") {
|
|
124
133
|
result[row.field] = row.value;
|
|
125
134
|
continue;
|
|
@@ -133,8 +142,9 @@ export function buildResult(state) {
|
|
|
133
142
|
continue;
|
|
134
143
|
}
|
|
135
144
|
const operator = conditionToOperator[row.condition];
|
|
136
|
-
if (!operator)
|
|
145
|
+
if (!operator) {
|
|
137
146
|
continue;
|
|
147
|
+
}
|
|
138
148
|
const existing = result[row.field];
|
|
139
149
|
if (typeof existing === "object" &&
|
|
140
150
|
existing !== null &&
|
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;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,4 +4,4 @@ export { DateTransformer } from "./transformers/date.js";
|
|
|
4
4
|
export { defaultTransformers } from "./transformers/defaults.js";
|
|
5
5
|
export type { FieldTransformer } from "./transformers/types.js";
|
|
6
6
|
export { TSense } from "./tsense.js";
|
|
7
|
-
export type { ConnectionConfig, DeleteResult, FilterFor, HighlightOptions, NumberFilter, ProjectSearch, SearchListOptions, SearchListResult, SearchInput, SearchOptions, ScopedCollection, SearchOptionsPlain, SearchOptionsWithOmit, SearchOptionsWithPick, SearchResult, StringFilter, SyncConfig, SyncOptions, SyncResult, TsenseOptions, UpdateResult, UpsertResult,
|
|
7
|
+
export type { ConnectionConfig, DeleteResult, FilterFor, HighlightOptions, NumberFilter, ProjectSearch, SearchListOptions, SearchListResult, SearchInput, SearchOptions, ScopedCollection, SearchOptionsPlain, SearchOptionsWithOmit, SearchOptionsWithPick, SearchResult, StringFilter, SyncConfig, SyncOptions, SyncResult, TsenseOptions, UpdateResult, UpsertResult, } from "./types.js";
|
|
@@ -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
|
+
}
|
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";
|
package/dist/tsense.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Type } from "arktype";
|
|
2
2
|
import redaxios from "redaxios";
|
|
3
|
-
import type { DeleteResult, FieldSchema, FilterFor, ProjectSearch, ScopedCollection, SearchListOptions, SearchListResult, SearchOptions, SearchOptionsPlain, SearchResult, SyncOptions, SyncResult, TsenseOptions, UpdateResult, UpsertResult
|
|
3
|
+
import type { DeleteResult, FieldSchema, FilterFor, ProjectSearch, ScopedCollection, SearchListOptions, SearchListResult, SearchOptions, SearchOptionsPlain, SearchResult, SyncOptions, SyncResult, TsenseOptions, UpdateResult, UpsertResult } from "./types.js";
|
|
4
4
|
declare const redaxiosInstance: {
|
|
5
5
|
<T>(urlOrConfig: string | redaxios.Options, config?: redaxios.Options | undefined, _method?: any, data?: any, _undefined?: undefined): Promise<redaxios.Response<T>>;
|
|
6
6
|
request: (<T_1 = any>(config?: redaxios.Options | undefined) => Promise<redaxios.Response<T_1>>) | (<T_2 = any>(url: string, config?: redaxios.Options | undefined) => Promise<redaxios.Response<T_2>>);
|
|
@@ -76,19 +76,27 @@ 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
|
-
upsert(docs:
|
|
99
|
+
upsert(docs: T["infer"] | T["infer"][]): Promise<UpsertResult[]>;
|
|
92
100
|
syncData(options?: SyncOptions): Promise<SyncResult>;
|
|
93
101
|
private purgeOrphans;
|
|
94
102
|
exportIds(): Promise<string[]>;
|
package/dist/tsense.js
CHANGED
|
@@ -209,8 +209,14 @@ export class TSense {
|
|
|
209
209
|
const orParts = [];
|
|
210
210
|
for (const condition of rawValue) {
|
|
211
211
|
const inner = this.buildFilter(condition);
|
|
212
|
+
if (!inner.length) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
212
215
|
orParts.push(`(${inner.join("&&")})`);
|
|
213
216
|
}
|
|
217
|
+
if (!orParts.length) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
214
220
|
result.push(`(${orParts.join("||")})`);
|
|
215
221
|
continue;
|
|
216
222
|
}
|
|
@@ -232,6 +238,28 @@ export class TSense {
|
|
|
232
238
|
}
|
|
233
239
|
return result;
|
|
234
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
|
+
}
|
|
235
263
|
validateFields(fields) {
|
|
236
264
|
const valid = new Set(this.fields.map((f) => f.name));
|
|
237
265
|
for (const field of fields) {
|
|
@@ -240,6 +268,20 @@ export class TSense {
|
|
|
240
268
|
}
|
|
241
269
|
}
|
|
242
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
|
+
}
|
|
243
285
|
buildSort(options) {
|
|
244
286
|
if (!options.sortBy)
|
|
245
287
|
return;
|
|
@@ -297,9 +339,8 @@ export class TSense {
|
|
|
297
339
|
});
|
|
298
340
|
return data != null;
|
|
299
341
|
}
|
|
300
|
-
async
|
|
342
|
+
async deleteManyWithFilterBy(filterBy) {
|
|
301
343
|
await this.ensureSynced();
|
|
302
|
-
const filterBy = this.buildFilter(filter).join("&&");
|
|
303
344
|
if (!filterBy) {
|
|
304
345
|
throw new Error("FILTER_REQUIRED");
|
|
305
346
|
}
|
|
@@ -310,6 +351,9 @@ export class TSense {
|
|
|
310
351
|
});
|
|
311
352
|
return { deleted: data.num_deleted };
|
|
312
353
|
}
|
|
354
|
+
async deleteMany(filter) {
|
|
355
|
+
return await this.deleteManyWithFilterBy(this.combineFilterExpressions(filter));
|
|
356
|
+
}
|
|
313
357
|
async update(id, data) {
|
|
314
358
|
await this.ensureSynced();
|
|
315
359
|
const serialized = this.serializeDoc(data);
|
|
@@ -320,9 +364,8 @@ export class TSense {
|
|
|
320
364
|
});
|
|
321
365
|
return this.deserializeDoc(updated);
|
|
322
366
|
}
|
|
323
|
-
async
|
|
367
|
+
async updateManyWithFilterBy(filterBy, data) {
|
|
324
368
|
await this.ensureSynced();
|
|
325
|
-
const filterBy = this.buildFilter(filter).join("&&");
|
|
326
369
|
if (!filterBy) {
|
|
327
370
|
throw new Error("FILTER_REQUIRED");
|
|
328
371
|
}
|
|
@@ -335,7 +378,10 @@ export class TSense {
|
|
|
335
378
|
});
|
|
336
379
|
return { updated: result.num_updated };
|
|
337
380
|
}
|
|
338
|
-
async
|
|
381
|
+
async updateMany(filter, data) {
|
|
382
|
+
return await this.updateManyWithFilterBy(this.combineFilterExpressions(filter), data);
|
|
383
|
+
}
|
|
384
|
+
async searchWithFilterBy(options, filterBy) {
|
|
339
385
|
await this.ensureSynced();
|
|
340
386
|
const queryByFields = options.queryBy ?? [
|
|
341
387
|
this.options.defaultSearchField,
|
|
@@ -357,7 +403,6 @@ export class TSense {
|
|
|
357
403
|
const sortBy = this.buildSort(options);
|
|
358
404
|
if (sortBy)
|
|
359
405
|
params.sort_by = sortBy;
|
|
360
|
-
const filterBy = this.buildFilter(options.filter).join("&&");
|
|
361
406
|
if (filterBy)
|
|
362
407
|
params.filter_by = filterBy;
|
|
363
408
|
if (options.page != null)
|
|
@@ -423,17 +468,19 @@ export class TSense {
|
|
|
423
468
|
scores,
|
|
424
469
|
};
|
|
425
470
|
}
|
|
426
|
-
async
|
|
471
|
+
async search(options) {
|
|
472
|
+
return await this.searchWithFilterBy(options, this.combineFilterExpressions(options.filter));
|
|
473
|
+
}
|
|
474
|
+
async searchListWithFilterBy(options, filterBy) {
|
|
427
475
|
const page = options.cursor ? Number(options.cursor) : 1;
|
|
428
476
|
const limit = Math.min(options.limit ?? 20, 100);
|
|
429
|
-
const result = await this.
|
|
477
|
+
const result = await this.searchWithFilterBy({
|
|
430
478
|
query: options.query,
|
|
431
479
|
queryBy: options.queryBy,
|
|
432
|
-
filter: options.filter,
|
|
433
480
|
sortBy: [options.sortBy],
|
|
434
481
|
page,
|
|
435
482
|
limit,
|
|
436
|
-
});
|
|
483
|
+
}, filterBy);
|
|
437
484
|
const hasMore = page * limit < result.count;
|
|
438
485
|
return {
|
|
439
486
|
data: result.data,
|
|
@@ -441,9 +488,11 @@ export class TSense {
|
|
|
441
488
|
total: result.count,
|
|
442
489
|
};
|
|
443
490
|
}
|
|
444
|
-
async
|
|
491
|
+
async searchList(options) {
|
|
492
|
+
return await this.searchListWithFilterBy(options, this.combineFilterExpressions(options.filter));
|
|
493
|
+
}
|
|
494
|
+
async countWithFilterBy(filterBy) {
|
|
445
495
|
await this.ensureSynced();
|
|
446
|
-
const filterBy = this.buildFilter(filter).join("&&");
|
|
447
496
|
if (!filterBy) {
|
|
448
497
|
const { data } = await this.axios({
|
|
449
498
|
method: "GET",
|
|
@@ -464,6 +513,9 @@ export class TSense {
|
|
|
464
513
|
});
|
|
465
514
|
return data.found;
|
|
466
515
|
}
|
|
516
|
+
async count(filter) {
|
|
517
|
+
return await this.countWithFilterBy(this.combineFilterExpressions(filter));
|
|
518
|
+
}
|
|
467
519
|
async upsert(docs) {
|
|
468
520
|
await this.ensureSynced();
|
|
469
521
|
const items = Array.isArray(docs) ? docs : [docs];
|
|
@@ -543,17 +595,11 @@ export class TSense {
|
|
|
543
595
|
}
|
|
544
596
|
scoped(baseFilter) {
|
|
545
597
|
return {
|
|
546
|
-
search: (options) => this.
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
...options,
|
|
552
|
-
filter: { ...options.filter, ...baseFilter },
|
|
553
|
-
}),
|
|
554
|
-
count: (filter) => this.count({ ...filter, ...baseFilter }),
|
|
555
|
-
deleteMany: (filter) => this.deleteMany({ ...filter, ...baseFilter }),
|
|
556
|
-
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),
|
|
557
603
|
};
|
|
558
604
|
}
|
|
559
605
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import type { Type } from "arktype";
|
|
2
2
|
import type { FieldTransformer } from "./transformers/types.js";
|
|
3
3
|
type BaseIfArray<T> = T extends (infer Q)[] ? Q : T;
|
|
4
|
-
export type WithNull<T> = {
|
|
5
|
-
[K in keyof T]: undefined extends T[K] ? T[K] | null : T[K];
|
|
6
|
-
};
|
|
7
4
|
export type FieldSchema = {
|
|
8
5
|
name: string;
|
|
9
6
|
type: string;
|