tsense 0.0.17 → 0.2.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,93 @@
1
+ const ARRAY_OPERATORS = new Set(["notIn"]);
2
+ function serializeValue(value) {
3
+ if (value instanceof Date) {
4
+ return value.toISOString();
5
+ }
6
+ return String(value);
7
+ }
8
+ function coerceValue(raw, columnType) {
9
+ if (columnType === "number") {
10
+ return Number(raw);
11
+ }
12
+ if (columnType === "boolean") {
13
+ return raw === "true";
14
+ }
15
+ if (columnType === "date") {
16
+ return new Date(raw);
17
+ }
18
+ return raw;
19
+ }
20
+ export function serializeFilter(filter, descriptor) {
21
+ const params = new URLSearchParams();
22
+ const columnMap = new Map(descriptor.columns.map((c) => [c.key, c]));
23
+ for (const [key, value] of Object.entries(filter)) {
24
+ const column = columnMap.get(key);
25
+ if (!column || value == null) {
26
+ continue;
27
+ }
28
+ if (Array.isArray(value)) {
29
+ if (value.length === 1) {
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
+ }
35
+ continue;
36
+ }
37
+ if (typeof value === "object" && !(value instanceof Date)) {
38
+ for (const [op, opValue] of Object.entries(value)) {
39
+ if (opValue == null) {
40
+ continue;
41
+ }
42
+ if (Array.isArray(opValue)) {
43
+ params.set(`${key}.${op}`, opValue.map((v) => serializeValue(v)).join(","));
44
+ }
45
+ else {
46
+ params.set(`${key}.${op}`, serializeValue(opValue));
47
+ }
48
+ }
49
+ continue;
50
+ }
51
+ params.set(key, serializeValue(value));
52
+ }
53
+ return params;
54
+ }
55
+ export function deserializeFilter(params, descriptor) {
56
+ const result = {};
57
+ const columnMap = new Map(descriptor.columns.map((c) => [c.key, c]));
58
+ for (const [paramKey, rawValue] of params.entries()) {
59
+ const dotIndex = paramKey.indexOf(".");
60
+ if (dotIndex !== -1) {
61
+ const field = paramKey.slice(0, dotIndex);
62
+ const operator = paramKey.slice(dotIndex + 1);
63
+ const column = columnMap.get(field);
64
+ if (!column) {
65
+ continue;
66
+ }
67
+ const existing = (result[field] ?? {});
68
+ if (ARRAY_OPERATORS.has(operator)) {
69
+ existing[operator] = rawValue
70
+ .split(",")
71
+ .map((v) => coerceValue(v, column.type));
72
+ }
73
+ else {
74
+ existing[operator] = coerceValue(rawValue, column.type);
75
+ }
76
+ result[field] = existing;
77
+ continue;
78
+ }
79
+ const column = columnMap.get(paramKey);
80
+ if (!column) {
81
+ continue;
82
+ }
83
+ if (rawValue.includes(",")) {
84
+ result[paramKey] = rawValue
85
+ .split(",")
86
+ .map((v) => coerceValue(v, column.type));
87
+ }
88
+ else {
89
+ result[paramKey] = coerceValue(rawValue, column.type);
90
+ }
91
+ }
92
+ return result;
93
+ }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export type { TsenseFieldMeta, TsenseFieldType } from "./env.js";
2
+ export { rank } from "./rank.js";
2
3
  export { DateTransformer } from "./transformers/date.js";
3
4
  export { defaultTransformers } from "./transformers/defaults.js";
4
5
  export type { FieldTransformer } from "./transformers/types.js";
5
6
  export { TSense } from "./tsense.js";
6
- export type { ConnectionConfig, DeleteResult, FilterFor, HighlightOptions, ProjectSearch, SearchListOptions, SearchListResult, SearchOptions, SearchOptionsPlain, SearchOptionsWithOmit, SearchOptionsWithPick, SearchResult, SyncConfig, SyncOptions, SyncResult, TsenseOptions, UpdateResult, UpsertResult, } from "./types.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, } from "./types.js";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export { rank } from "./rank.js";
1
2
  export { DateTransformer } from "./transformers/date.js";
2
3
  export { defaultTransformers } from "./transformers/defaults.js";
3
4
  export { TSense } from "./tsense.js";
@@ -5,7 +5,7 @@ export declare class TSenseMigrator {
5
5
  private localFields;
6
6
  private defaultSortingField;
7
7
  private axios;
8
- constructor(collectionName: string, localFields: FieldSchema[], defaultSortingField: string | undefined, axios: AxiosInstance);
8
+ constructor(collectionName: string, localFields: readonly FieldSchema[], defaultSortingField: string | undefined, axios: AxiosInstance);
9
9
  sync(): Promise<void>;
10
10
  private exists;
11
11
  private getRemoteFields;
package/dist/migrator.js CHANGED
@@ -1,61 +1,50 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
1
  const COMPARABLE_KEYS = ["type", "facet", "sort", "index", "optional"];
11
- const NESTED_TYPES = ["object", "object[]"];
2
+ const NESTED_TYPES = new Set(["object", "object[]"]);
12
3
  export class TSenseMigrator {
4
+ collectionName;
5
+ localFields;
6
+ defaultSortingField;
7
+ axios;
13
8
  constructor(collectionName, localFields, defaultSortingField, axios) {
14
9
  this.collectionName = collectionName;
15
10
  this.localFields = localFields;
16
11
  this.defaultSortingField = defaultSortingField;
17
12
  this.axios = axios;
18
13
  }
19
- sync() {
20
- return __awaiter(this, void 0, void 0, function* () {
21
- const exists = yield this.exists();
22
- if (!exists) {
23
- return yield this.create();
24
- }
25
- const remoteFields = yield this.getRemoteFields();
26
- const diff = this.diff(remoteFields);
27
- if (!diff.toAdd.length && !diff.toRemove.length && !diff.toModify.length) {
28
- return;
29
- }
30
- const patched = yield this.patch(diff);
31
- if (patched)
32
- return;
33
- yield this.drop();
34
- yield this.create();
35
- });
14
+ async sync() {
15
+ const exists = await this.exists();
16
+ if (!exists) {
17
+ return await this.create();
18
+ }
19
+ const remoteFields = await this.getRemoteFields();
20
+ const diff = this.diff(remoteFields);
21
+ if (!diff.toAdd.length && !diff.toRemove.length && !diff.toModify.length) {
22
+ return;
23
+ }
24
+ const patched = await this.patch(diff);
25
+ if (patched)
26
+ return;
27
+ await this.drop();
28
+ await this.create();
36
29
  }
37
- exists() {
38
- return __awaiter(this, void 0, void 0, function* () {
39
- return yield this.axios({
40
- method: "GET",
41
- url: `/collections/${this.collectionName}`,
42
- })
43
- .then(() => true)
44
- .catch((e) => {
45
- if (e.status === 404)
46
- return false;
47
- throw e;
48
- });
30
+ async exists() {
31
+ return await this.axios({
32
+ method: "GET",
33
+ url: `/collections/${this.collectionName}`,
34
+ })
35
+ .then(() => true)
36
+ .catch((e) => {
37
+ if (e.status === 404)
38
+ return false;
39
+ throw e;
49
40
  });
50
41
  }
51
- getRemoteFields() {
52
- return __awaiter(this, void 0, void 0, function* () {
53
- const { data } = yield this.axios({
54
- method: "GET",
55
- url: `/collections/${this.collectionName}`,
56
- });
57
- return data.fields;
42
+ async getRemoteFields() {
43
+ const { data } = await this.axios({
44
+ method: "GET",
45
+ url: `/collections/${this.collectionName}`,
58
46
  });
47
+ return data.fields;
59
48
  }
60
49
  diff(remote) {
61
50
  const remoteByName = new Map(remote.map((f) => [f.name, f]));
@@ -85,57 +74,50 @@ export class TSenseMigrator {
85
74
  return { toAdd, toRemove, toModify };
86
75
  }
87
76
  fieldsMatch(local, remote) {
88
- var _a, _b;
89
77
  for (const key of COMPARABLE_KEYS) {
90
- if (((_a = local[key]) !== null && _a !== void 0 ? _a : undefined) !== ((_b = remote[key]) !== null && _b !== void 0 ? _b : undefined)) {
78
+ if ((local[key] ?? undefined) !== (remote[key] ?? undefined)) {
91
79
  return false;
92
80
  }
93
81
  }
94
82
  return true;
95
83
  }
96
- patch(diff) {
97
- return __awaiter(this, void 0, void 0, function* () {
98
- const fields = [];
99
- for (const field of diff.toRemove) {
100
- fields.push({ name: field.name, drop: true });
101
- }
102
- for (const field of diff.toModify) {
103
- fields.push({ name: field.name, drop: true });
104
- fields.push(field);
105
- }
106
- for (const field of diff.toAdd) {
107
- fields.push(field);
108
- }
109
- return yield this.axios({
110
- method: "PATCH",
111
- url: `/collections/${this.collectionName}`,
112
- data: { fields },
113
- })
114
- .then(() => true)
115
- .catch(() => false);
116
- });
84
+ async patch(diff) {
85
+ const fields = [];
86
+ for (const field of diff.toRemove) {
87
+ fields.push({ name: field.name, drop: true });
88
+ }
89
+ for (const field of diff.toModify) {
90
+ fields.push({ name: field.name, drop: true });
91
+ fields.push(field);
92
+ }
93
+ for (const field of diff.toAdd) {
94
+ fields.push(field);
95
+ }
96
+ return await this.axios({
97
+ method: "PATCH",
98
+ url: `/collections/${this.collectionName}`,
99
+ data: { fields },
100
+ })
101
+ .then(() => true)
102
+ .catch(() => false);
117
103
  }
118
- create() {
119
- return __awaiter(this, void 0, void 0, function* () {
120
- const enable_nested_fields = this.localFields.some((f) => NESTED_TYPES.includes(f.type));
121
- yield this.axios({
122
- method: "POST",
123
- url: "/collections",
124
- data: {
125
- name: this.collectionName,
126
- fields: this.localFields,
127
- default_sorting_field: this.defaultSortingField,
128
- enable_nested_fields,
129
- },
130
- });
104
+ async create() {
105
+ const enable_nested_fields = this.localFields.some((f) => NESTED_TYPES.has(f.type));
106
+ await this.axios({
107
+ method: "POST",
108
+ url: "/collections",
109
+ data: {
110
+ name: this.collectionName,
111
+ fields: this.localFields,
112
+ default_sorting_field: this.defaultSortingField,
113
+ enable_nested_fields,
114
+ },
131
115
  });
132
116
  }
133
- drop() {
134
- return __awaiter(this, void 0, void 0, function* () {
135
- yield this.axios({
136
- method: "DELETE",
137
- url: `/collections/${this.collectionName}`,
138
- });
117
+ async drop() {
118
+ await this.axios({
119
+ method: "DELETE",
120
+ url: `/collections/${this.collectionName}`,
139
121
  });
140
122
  }
141
123
  }
package/dist/rank.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export declare function rank<T>({ db, id_key, ts }: {
2
+ db: T[];
3
+ id_key: keyof T;
4
+ ts: {
5
+ id: string;
6
+ }[];
7
+ }): T[];
package/dist/rank.js ADDED
@@ -0,0 +1,12 @@
1
+ export function rank({ db, id_key, ts, }) {
2
+ const mapped = new Map(ts.map((t, i) => [t.id, i]));
3
+ const result = [];
4
+ for (const item of db) {
5
+ const position = mapped.get(String(item[id_key]));
6
+ if (position == null) {
7
+ continue;
8
+ }
9
+ result[position] = item;
10
+ }
11
+ return result.filter(Boolean);
12
+ }
@@ -0,0 +1,60 @@
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
+ 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
+ };
59
+ export declare function FilterBuilder<T>({ descriptor, onChange, renderRoot, renderRow, renderFieldSelect, renderConditionSelect, renderValueInput, renderAddButton, renderRemoveButton, renderPresetButton }: FilterBuilderProps<T>): ReactNode;
60
+ export {};
@@ -0,0 +1,102 @@
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
+ function defaultFieldSelect({ columns, value, onChange, }) {
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
+ }
10
+ function formatDateForInput(date) {
11
+ if (!(date instanceof Date))
12
+ return "";
13
+ return date.toISOString().slice(0, 10);
14
+ }
15
+ function parseDateInput(str) {
16
+ if (!str)
17
+ return undefined;
18
+ return new Date(str + "T00:00:00Z");
19
+ }
20
+ function asTuple(value) {
21
+ if (Array.isArray(value)) {
22
+ return [value[0], value[1]];
23
+ }
24
+ return [undefined, undefined];
25
+ }
26
+ function defaultValueInput({ column, condition, value, onChange, }) {
27
+ if (condition === "between" && column.type === "date") {
28
+ const tuple = asTuple(value);
29
+ 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: (e) => onChange([parseDateInput(e.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: (e) => onChange([tuple[0], parseDateInput(e.target.value)]) })] }));
30
+ }
31
+ if (condition === "between") {
32
+ const tuple = asTuple(value);
33
+ 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: (e) => onChange([Number(e.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: (e) => onChange([tuple[0], Number(e.target.value)]) })] }));
34
+ }
35
+ if (column.values) {
36
+ const selected = Array.isArray(value) ? value : [];
37
+ return (_jsx("div", { className: "flex flex-wrap gap-2", children: column.values.map((v) => {
38
+ 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
40
+ ? selected.filter((s) => s !== v.value)
41
+ : [...selected, v.value]) }), v.label] }, v.value));
42
+ }) }));
43
+ }
44
+ if (column.type === "date") {
45
+ return (_jsx("input", { className: "rounded border px-2 py-1", type: "date", value: formatDateForInput(value), onChange: (e) => onChange(parseDateInput(e.target.value)) }));
46
+ }
47
+ if (column.type === "number") {
48
+ return (_jsx("input", { className: "rounded border px-2 py-1", type: "number", value: String(value ?? ""), onChange: (e) => onChange(Number(e.target.value)) }));
49
+ }
50
+ if (column.type === "boolean") {
51
+ return (_jsxs("select", { className: "rounded border px-2 py-1", value: value == null ? "" : String(value), onChange: (e) => onChange(e.target.value === "true"), children: [_jsx("option", { value: "", children: "Value" }), _jsx("option", { value: "true", children: "true" }), _jsx("option", { value: "false", children: "false" })] }));
52
+ }
53
+ return (_jsx("input", { className: "rounded border px-2 py-1", type: "text", value: typeof value === "string" ? value : "", onChange: (e) => onChange(e.target.value) }));
54
+ }
55
+ function defaultRemoveButton({ onClick }) {
56
+ return (_jsx("button", { className: "text-red-500 hover:text-red-700", onClick: onClick, children: "\u00D7" }));
57
+ }
58
+ function defaultAddButton({ onClick }) {
59
+ return (_jsx("button", { className: "text-blue-500 hover:text-blue-700", onClick: onClick, children: "+ Add filter" }));
60
+ }
61
+ function defaultPresetButton({ preset, onClick }) {
62
+ return (_jsx("button", { className: "rounded bg-gray-100 px-2 py-1 text-sm hover:bg-gray-200", onClick: onClick, children: preset.name }));
63
+ }
64
+ export function FilterBuilder({ descriptor, onChange, renderRoot, renderRow, renderFieldSelect, renderConditionSelect, renderValueInput, renderAddButton, renderRemoveButton, renderPresetButton, }) {
65
+ const filters = useFilterBuilder(descriptor);
66
+ const onChangeRef = useRef(onChange);
67
+ onChangeRef.current = onChange;
68
+ useEffect(() => {
69
+ onChangeRef.current?.(filters.result);
70
+ }, [filters.result]);
71
+ const FieldSelect = renderFieldSelect ?? defaultFieldSelect;
72
+ const ConditionSelect = renderConditionSelect ?? defaultConditionSelect;
73
+ const ValueInput = renderValueInput ?? defaultValueInput;
74
+ const RemoveButton = renderRemoveButton ?? defaultRemoveButton;
75
+ const AddButton = renderAddButton ?? defaultAddButton;
76
+ const PresetButton = renderPresetButton ?? defaultPresetButton;
77
+ const rows = filters.rows.map((row, i) => {
78
+ const fieldSelect = (_jsx(FieldSelect, { columns: filters.columns, value: row.field, onChange: (field) => filters.setField(i, field) }));
79
+ const conditionSelect = (_jsx(ConditionSelect, { conditions: row.field ? filters.conditionsFor(row.field) : [], value: row.condition, onChange: (condition) => filters.setCondition(i, condition), disabled: !row.field }));
80
+ const column = row.field
81
+ ? filters.columns.find((c) => c.key === row.field)
82
+ : undefined;
83
+ const valueInput = row.field && row.condition && column ? (_jsx(ValueInput, { column: column, condition: row.condition, value: row.value, onChange: (v) => filters.setValue(i, v) })) : null;
84
+ const removeButton = _jsx(RemoveButton, { onClick: () => filters.remove(i) });
85
+ if (renderRow) {
86
+ return (_jsx(Fragment, { children: renderRow({
87
+ index: i,
88
+ fieldSelect,
89
+ conditionSelect,
90
+ valueInput,
91
+ removeButton,
92
+ }) }, row.id));
93
+ }
94
+ return (_jsxs("div", { className: "flex items-center gap-2", children: [fieldSelect, conditionSelect, valueInput, removeButton] }, row.id));
95
+ });
96
+ const addButton = _jsx(AddButton, { onClick: filters.add });
97
+ 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;
98
+ if (renderRoot) {
99
+ return _jsx(_Fragment, { children: renderRoot({ rows: _jsx(_Fragment, { children: rows }), addButton, presets }) });
100
+ }
101
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [rows, addButton, presets] }));
102
+ }
@@ -0,0 +1,4 @@
1
+ export { FilterBuilder } from "./filter-builder.js";
2
+ export type { AddButtonSlotProps, ConditionSelectSlotProps, FieldSelectSlotProps, PresetButtonSlotProps, RemoveButtonSlotProps, RootSlotProps, RowSlotProps, ValueInputSlotProps, } from "./filter-builder.js";
3
+ export type { FilterValue } from "../filters/filter-state.js";
4
+ export { useFilterBuilder } from "./use-filter-builder.js";
@@ -0,0 +1,2 @@
1
+ export { FilterBuilder } from "./filter-builder.js";
2
+ export { useFilterBuilder } from "./use-filter-builder.js";
@@ -0,0 +1,23 @@
1
+ import { type FilterRow, type FilterValue } from "../filters/filter-state.js";
2
+ import type { FilterDescriptor } from "../filters/index.js";
3
+ type Preset = {
4
+ field: string;
5
+ name: string;
6
+ };
7
+ type UseFilterBuilderReturn = {
8
+ columns: FilterDescriptor["columns"];
9
+ rows: FilterRow[];
10
+ add: () => void;
11
+ remove: (index: number) => void;
12
+ setField: (index: number, field: string) => void;
13
+ setCondition: (index: number, condition: string) => void;
14
+ setValue: (index: number, value: FilterValue) => void;
15
+ clear: () => void;
16
+ conditionsFor: (field: string) => FilterDescriptor["columns"][number]["conditions"];
17
+ columnFor: (field: string) => FilterDescriptor["columns"][number];
18
+ presets: Preset[];
19
+ applyPreset: (field: string, name: string) => void;
20
+ result: Record<string, unknown>;
21
+ };
22
+ export declare function useFilterBuilder<T>(descriptor: FilterDescriptor<T>): UseFilterBuilderReturn;
23
+ export {};
@@ -0,0 +1,22 @@
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";
3
+ export function useFilterBuilder(descriptor) {
4
+ const [state, setState] = useState(createInitialState);
5
+ const presets = useMemo(() => descriptor.columns.flatMap((col) => (col.presets ?? []).map((p) => ({ field: col.key, name: p.name }))), [descriptor]);
6
+ const result = useMemo(() => buildResult(state), [state]);
7
+ return {
8
+ columns: descriptor.columns,
9
+ rows: state.rows,
10
+ add: () => setState(addRow),
11
+ remove: (index) => setState((s) => removeRow(s, index)),
12
+ setField: (index, field) => setState((s) => setRowField(s, index, field)),
13
+ setCondition: (index, condition) => setState((s) => setRowCondition(s, index, condition)),
14
+ setValue: (index, value) => setState((s) => setRowValue(s, index, value)),
15
+ clear: () => setState(clearState),
16
+ conditionsFor: (field) => conditionsFor(descriptor, field),
17
+ columnFor: (field) => columnFor(descriptor, field),
18
+ presets,
19
+ applyPreset: (field, name) => setState((s) => applyPresetState(s, descriptor, field, name)),
20
+ result,
21
+ };
22
+ }
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, SearchListOptions, SearchListResult, SearchOptions, SearchOptionsPlain, SearchResult, SyncOptions, SyncResult, TsenseOptions, UpdateResult, UpsertResult } from "./types.js";
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>>);
@@ -53,19 +53,18 @@ declare const redaxiosInstance: {
53
53
  spread<Args, R>(fn: (...args: Args[]) => R): (array: Args[]) => R;
54
54
  CancelToken: AbortController;
55
55
  defaults: redaxios.Options;
56
- create: any;
56
+ create: /*elided*/ any;
57
57
  };
58
58
  };
59
59
  export type AxiosInstance = ReturnType<typeof redaxiosInstance.create>;
60
60
  export declare class TSense<T extends Type> {
61
61
  private options;
62
- fields: FieldSchema[];
62
+ readonly fields: readonly FieldSchema[];
63
63
  private axios;
64
64
  private synced;
65
65
  private fieldTransformers;
66
66
  private dataSyncConfig?;
67
67
  infer: T["infer"];
68
- getFields(): FieldSchema[];
69
68
  constructor(options: TsenseOptions<T>);
70
69
  private getBaseType;
71
70
  private inferType;
@@ -77,6 +76,7 @@ export declare class TSense<T extends Type> {
77
76
  syncSchema(): Promise<void>;
78
77
  private buildObjectFilter;
79
78
  private buildFilter;
79
+ private validateFields;
80
80
  private buildSort;
81
81
  create(): Promise<this>;
82
82
  drop(): Promise<void>;
@@ -92,5 +92,6 @@ export declare class TSense<T extends Type> {
92
92
  syncData(options?: SyncOptions): Promise<SyncResult>;
93
93
  private purgeOrphans;
94
94
  exportIds(): Promise<string[]>;
95
+ scoped(baseFilter: FilterFor<T["infer"]>): ScopedCollection<T["infer"]>;
95
96
  }
96
97
  export {};