tsense 0.2.0-next.5 → 0.2.0-next.7

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.
@@ -1,5 +1,6 @@
1
+ import type { RelativeDate } from "../types.js";
1
2
  import type { FilterDescriptor } from "./index.js";
2
- export type FilterValue = string | string[] | number | boolean | Date | [FilterValue | undefined, FilterValue | undefined] | undefined;
3
+ export type FilterValue = string | string[] | number | boolean | Date | RelativeDate | [FilterValue | undefined, FilterValue | undefined] | undefined;
3
4
  export type FilterRow = {
4
5
  id: number;
5
6
  field?: string;
@@ -8,6 +9,7 @@ export type FilterRow = {
8
9
  };
9
10
  export type FilterState = {
10
11
  rows: FilterRow[];
12
+ manualRowIds: Set<number>;
11
13
  };
12
14
  export declare function createInitialState(): FilterState;
13
15
  export declare function addRow(state: FilterState): FilterState;
@@ -16,8 +18,9 @@ export declare function removeRow(state: FilterState, index: number): FilterStat
16
18
  export declare function setRowField(state: FilterState, index: number, field: string): FilterState;
17
19
  export declare function setRowCondition(state: FilterState, index: number, condition: string): FilterState;
18
20
  export declare function setRowValue(state: FilterState, index: number, value: FilterValue): FilterState;
21
+ export declare function restoreRows(rows: Omit<FilterRow, "id">[]): FilterState;
19
22
  export declare function clearState(): FilterState;
20
- export declare function applyPreset<T>(state: FilterState, descriptor: FilterDescriptor<T>, field: string, name: string): FilterState;
23
+ export declare function hasManualRows(state: FilterState): boolean;
21
24
  export declare function conditionsFor<T>(descriptor: FilterDescriptor<T>, field: string): FilterDescriptor<T>["columns"][number]["conditions"];
22
25
  export declare function columnFor<T>(descriptor: FilterDescriptor<T>, field: string): FilterDescriptor<T>["columns"][number];
23
26
  export declare function buildResult(state: FilterState): Record<string, unknown>;
@@ -11,94 +11,65 @@ const conditionToOperator = {
11
11
  };
12
12
  let nextRowId = 0;
13
13
  export function createInitialState() {
14
- return { rows: [] };
14
+ return { rows: [], manualRowIds: new Set() };
15
15
  }
16
16
  export function addRow(state) {
17
- return { rows: [...state.rows, { id: ++nextRowId }] };
17
+ const id = ++nextRowId;
18
+ return {
19
+ rows: [...state.rows, { id }],
20
+ manualRowIds: new Set([...state.manualRowIds, id]),
21
+ };
18
22
  }
19
23
  export function addRowWithField(state, field) {
20
- return { rows: [...state.rows, { id: ++nextRowId, field }] };
24
+ const id = ++nextRowId;
25
+ return {
26
+ rows: [...state.rows, { id, field }],
27
+ manualRowIds: new Set([...state.manualRowIds, id]),
28
+ };
21
29
  }
22
30
  export function removeRow(state, index) {
23
- return { rows: state.rows.filter((_, i) => i !== index) };
31
+ const removed = state.rows[index];
32
+ const manualRowIds = new Set(state.manualRowIds);
33
+ if (removed) {
34
+ manualRowIds.delete(removed.id);
35
+ }
36
+ return {
37
+ rows: state.rows.filter((_, i) => i !== index),
38
+ manualRowIds,
39
+ };
24
40
  }
25
41
  export function setRowField(state, index, field) {
26
42
  return {
27
43
  rows: state.rows.map((row, i) => i === index ? { id: row.id, field } : row),
44
+ manualRowIds: state.manualRowIds,
28
45
  };
29
46
  }
30
47
  export function setRowCondition(state, index, condition) {
31
48
  return {
32
49
  rows: state.rows.map((row, i) => i === index ? { ...row, condition, value: undefined } : row),
50
+ manualRowIds: state.manualRowIds,
33
51
  };
34
52
  }
35
53
  export function setRowValue(state, index, value) {
36
54
  return {
37
55
  rows: state.rows.map((row, i) => (i === index ? { ...row, value } : row)),
56
+ manualRowIds: state.manualRowIds,
38
57
  };
39
58
  }
40
- export function clearState() {
41
- return { rows: [] };
42
- }
43
- function filterValueToRow(field, filterValue) {
44
- if (filterValue == null) {
45
- return null;
46
- }
47
- if (Array.isArray(filterValue)) {
48
- return {
49
- id: ++nextRowId,
50
- field,
51
- condition: "is_in",
52
- value: filterValue,
53
- };
54
- }
55
- if (typeof filterValue !== "object" || filterValue instanceof Date) {
56
- return {
57
- id: ++nextRowId,
58
- field,
59
- condition: "equals",
60
- value: filterValue,
61
- };
62
- }
63
- const operators = filterValue;
64
- const keys = Object.keys(operators);
65
- if (keys.includes("gte") && keys.includes("lte")) {
66
- return {
67
- id: ++nextRowId,
68
- field,
69
- condition: "between",
70
- value: [operators.gte, operators.lte],
71
- };
72
- }
73
- if (!keys[0]) {
74
- return null;
75
- }
59
+ export function restoreRows(rows) {
76
60
  return {
77
- id: ++nextRowId,
78
- field,
79
- condition: keys[0],
80
- value: operators[keys[0]],
61
+ rows: rows.map((row) => ({ ...row, id: ++nextRowId })),
62
+ manualRowIds: new Set(),
81
63
  };
82
64
  }
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 presetRows = [];
90
- for (const [key, value] of Object.entries(preset.filter)) {
91
- const row = filterValueToRow(key, value);
92
- if (row) {
93
- presetRows.push(row);
94
- }
95
- }
96
- if (!presetRows.length) {
97
- return state;
98
- }
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] };
65
+ export function clearState() {
66
+ return { rows: [], manualRowIds: new Set() };
67
+ }
68
+ export function hasManualRows(state) {
69
+ return state.rows.some((r) => state.manualRowIds.has(r.id) &&
70
+ r.field != null &&
71
+ r.condition != null &&
72
+ r.value != null);
102
73
  }
103
74
  export function conditionsFor(descriptor, field) {
104
75
  const column = descriptor.columns.find((c) => c.key === field);
@@ -1,14 +1,14 @@
1
- export { isRelativeDate, resolveRelativeDate } from './relative-dates.js';
2
- export { deserializeFilter, serializeFilter } from './url.js';
3
- import type { Type } from 'arktype';
4
- import type { TSense } from '../tsense.js';
5
- import type { FilterFor, SearchInput } from '../types.js';
1
+ export { isRelativeDate, resolveRelativeDate } from "./relative-dates.js";
2
+ export { deserializeFilter, serializeFilter } from "./url.js";
3
+ import type { Type } from "arktype";
4
+ import type { TSense } from "../tsense.js";
5
+ import type { FilterFor, SearchInput } from "../types.js";
6
6
  export type FilterDescriptor<T = Record<string, unknown>> = {
7
7
  infer: FilterFor<T>;
8
8
  columns: {
9
9
  key: keyof T & string;
10
10
  label: string;
11
- type: 'string' | 'number' | 'boolean' | 'date';
11
+ type: "string" | "number" | "boolean" | "date";
12
12
  conditions: {
13
13
  key: string;
14
14
  label: string;
@@ -17,25 +17,20 @@ export type FilterDescriptor<T = Record<string, unknown>> = {
17
17
  value: string;
18
18
  label: string;
19
19
  }[];
20
- presets?: {
21
- name: string;
22
- filter: Record<string, unknown>;
23
- }[];
24
20
  }[];
25
21
  };
26
- type FilterBuilderFieldConfig<T> = {
22
+ type FilterBuilderFieldConfig = {
27
23
  label: string;
28
24
  labels?: Record<string, string>;
29
- presets?: Record<string, FilterFor<T> | (() => FilterFor<T>)>;
30
25
  };
31
26
  type FilterBuilderReturn<T> = {
32
27
  describe(): FilterDescriptor<T>;
33
28
  schema(): Type<SearchInput<T>>;
34
29
  };
35
- type ColumnType = FilterDescriptor['columns'][number]['type'];
30
+ type ColumnType = FilterDescriptor["columns"][number]["type"];
36
31
  type FilterBuilderOptions = {
37
- conditionLabels?: Partial<Record<ColumnType | 'enum', Partial<Record<string, string>>>>;
32
+ conditionLabels?: Partial<Record<ColumnType | "enum", Partial<Record<string, string>>>>;
38
33
  };
39
34
  export declare function createFilterBuilder<T extends Type>(collection: TSense<T>, config: {
40
- [K in keyof T['infer']]?: FilterBuilderFieldConfig<T['infer']>;
41
- }, options?: FilterBuilderOptions): FilterBuilderReturn<T['infer']>;
35
+ [K in keyof T["infer"]]?: FilterBuilderFieldConfig;
36
+ }, options?: FilterBuilderOptions): FilterBuilderReturn<T["infer"]>;
@@ -1,46 +1,46 @@
1
- export { isRelativeDate, resolveRelativeDate } from './relative-dates.js';
2
- export { deserializeFilter, serializeFilter } from './url.js';
3
- import { type } from 'arktype';
1
+ export { isRelativeDate, resolveRelativeDate } from "./relative-dates.js";
2
+ export { deserializeFilter, serializeFilter } from "./url.js";
3
+ import { type } from "arktype";
4
4
  const tsenseTypeMap = {
5
- string: 'string',
6
- 'string*': 'string',
7
- 'string[]': 'string',
8
- int32: 'number',
9
- int64: 'number',
10
- float: 'number',
11
- 'int32[]': 'number',
12
- 'int64[]': 'number',
13
- 'float[]': 'number',
14
- bool: 'boolean',
15
- 'bool[]': 'boolean',
16
- auto: 'string',
17
- image: 'string',
5
+ string: "string",
6
+ "string*": "string",
7
+ "string[]": "string",
8
+ int32: "number",
9
+ int64: "number",
10
+ float: "number",
11
+ "int32[]": "number",
12
+ "int64[]": "number",
13
+ "float[]": "number",
14
+ bool: "boolean",
15
+ "bool[]": "boolean",
16
+ auto: "string",
17
+ image: "string",
18
18
  };
19
19
  const enumConditions = [
20
- { key: 'is_in', label: 'is in' },
21
- { key: 'is_not_in', label: 'is not in' },
20
+ { key: "is_in", label: "is in" },
21
+ { key: "is_not_in", label: "is not in" },
22
22
  ];
23
23
  const conditionsByType = {
24
24
  string: [
25
- { key: 'equals', label: 'equals' },
26
- { key: 'not_equals', label: 'not equals' },
25
+ { key: "equals", label: "equals" },
26
+ { key: "not_equals", label: "not equals" },
27
27
  ],
28
28
  number: [
29
- { key: 'equals', label: 'equals' },
30
- { key: 'not_equals', label: 'not equals' },
31
- { key: 'gt', label: 'greater than' },
32
- { key: 'gte', label: 'greater than or equal' },
33
- { key: 'lt', label: 'less than' },
34
- { key: 'lte', label: 'less than or equal' },
35
- { key: 'between', label: 'between' },
29
+ { key: "equals", label: "equals" },
30
+ { key: "not_equals", label: "not equals" },
31
+ { key: "gt", label: "greater than" },
32
+ { key: "gte", label: "greater than or equal" },
33
+ { key: "lt", label: "less than" },
34
+ { key: "lte", label: "less than or equal" },
35
+ { key: "between", label: "between" },
36
36
  ],
37
- boolean: [{ key: 'equals', label: 'equals' }],
37
+ boolean: [{ key: "equals", label: "equals" }],
38
38
  date: [
39
- { key: 'equals', label: 'equals' },
40
- { key: 'not_equals', label: 'not equals' },
41
- { key: 'gt', label: 'after' },
42
- { key: 'lt', label: 'before' },
43
- { key: 'between', label: 'between' },
39
+ { key: "equals", label: "equals" },
40
+ { key: "not_equals", label: "not equals" },
41
+ { key: "gt", label: "after" },
42
+ { key: "lt", label: "before" },
43
+ { key: "between", label: "between" },
44
44
  ],
45
45
  };
46
46
  export function createFilterBuilder(collection, config, options) {
@@ -48,36 +48,36 @@ export function createFilterBuilder(collection, config, options) {
48
48
  return {
49
49
  schema() {
50
50
  const numberOps = type.raw({
51
- 'not?': 'number',
52
- 'gt?': 'number',
53
- 'gte?': 'number',
54
- 'lt?': 'number',
55
- 'lte?': 'number',
56
- 'notIn?': 'number[]',
51
+ "not?": "number",
52
+ "gt?": "number",
53
+ "gte?": "number",
54
+ "lt?": "number",
55
+ "lte?": "number",
56
+ "notIn?": "number[]",
57
57
  });
58
58
  const stringOps = type.raw({
59
- 'not?': 'string',
60
- 'notIn?': 'string[]',
59
+ "not?": "string",
60
+ "notIn?": "string[]",
61
61
  });
62
62
  const relativeDateUnit = "'day' | 'week' | 'month'";
63
63
  const relativeDate = type
64
64
  .raw({ startOf: relativeDateUnit })
65
65
  .or(type.raw({ endOf: relativeDateUnit }));
66
- const concreteDateInput = type.raw('number | string | Date');
66
+ const concreteDateInput = type.raw("number | string | Date");
67
67
  const dateInput = concreteDateInput.or(relativeDate);
68
68
  const dateArrayInput = dateInput.array();
69
69
  const dateOps = type.raw({
70
- 'not?': dateInput,
71
- 'gt?': dateInput,
72
- 'gte?': dateInput,
73
- 'lt?': dateInput,
74
- 'lte?': dateInput,
75
- 'notIn?': dateArrayInput,
70
+ "not?": dateInput,
71
+ "gt?": dateInput,
72
+ "gte?": dateInput,
73
+ "lt?": dateInput,
74
+ "lte?": dateInput,
75
+ "notIn?": dateArrayInput,
76
76
  });
77
77
  const fieldSchemas = {
78
- number: type.raw('number').or(type.raw('number[]')).or(numberOps),
79
- string: type.raw('string').or(type.raw('string[]')).or(stringOps),
80
- boolean: type.raw('boolean'),
78
+ number: type.raw("number").or(type.raw("number[]")).or(numberOps),
79
+ string: type.raw("string").or(type.raw("string[]")).or(stringOps),
80
+ boolean: type.raw("boolean"),
81
81
  date: dateInput.or(dateArrayInput).or(dateOps),
82
82
  };
83
83
  const descriptor = this.describe();
@@ -87,10 +87,10 @@ export function createFilterBuilder(collection, config, options) {
87
87
  }
88
88
  return type
89
89
  .raw({
90
- 'query?': 'string',
91
- 'filter?': type.raw(filterDef),
92
- 'page?': 'number',
93
- 'limit?': 'number',
90
+ "query?": "string",
91
+ "filter?": type.raw(filterDef),
92
+ "page?": "number",
93
+ "limit?": "number",
94
94
  })
95
95
  .as();
96
96
  },
@@ -110,8 +110,8 @@ export function createFilterBuilder(collection, config, options) {
110
110
  const fieldConfig = config[field.name];
111
111
  if (!fieldConfig)
112
112
  continue;
113
- const columnType = field.sourceExpression === 'Date'
114
- ? 'date'
113
+ const columnType = field.sourceExpression === "Date"
114
+ ? "date"
115
115
  : tsenseTypeMap[field.type];
116
116
  if (!columnType)
117
117
  continue;
@@ -126,15 +126,7 @@ export function createFilterBuilder(collection, config, options) {
126
126
  value: v,
127
127
  label: fieldConfig.labels?.[v] ?? v,
128
128
  }));
129
- column.conditions = withLabels(enumConditions, 'enum');
130
- }
131
- if (fieldConfig.presets) {
132
- column.presets = Object.entries(fieldConfig.presets).map(([name, filterOrFn]) => ({
133
- name,
134
- filter: (typeof filterOrFn === 'function'
135
- ? filterOrFn()
136
- : filterOrFn),
137
- }));
129
+ column.conditions = withLabels(enumConditions, "enum");
138
130
  }
139
131
  columns.push(column);
140
132
  }
@@ -1,3 +1,3 @@
1
- import type { RelativeDate } from '../types.js';
1
+ import type { RelativeDate } from "../types.js";
2
2
  export declare function isRelativeDate(value: unknown): value is RelativeDate;
3
3
  export declare function resolveRelativeDate(expr: RelativeDate, tz: string, now?: Date): Date;
@@ -1,19 +1,19 @@
1
- import dayjs from 'dayjs';
2
- import timezone from 'dayjs/plugin/timezone.js';
3
- import utc from 'dayjs/plugin/utc.js';
1
+ import dayjs from "dayjs";
2
+ import timezone from "dayjs/plugin/timezone.js";
3
+ import utc from "dayjs/plugin/utc.js";
4
4
  dayjs.extend(utc);
5
5
  dayjs.extend(timezone);
6
6
  export function isRelativeDate(value) {
7
- if (typeof value !== 'object' || value === null || value instanceof Date) {
7
+ if (typeof value !== "object" || value === null || value instanceof Date) {
8
8
  return false;
9
9
  }
10
10
  const obj = value;
11
- return (('startOf' in obj && typeof obj.startOf === 'string') ||
12
- ('endOf' in obj && typeof obj.endOf === 'string'));
11
+ return (("startOf" in obj && typeof obj.startOf === "string") ||
12
+ ("endOf" in obj && typeof obj.endOf === "string"));
13
13
  }
14
14
  export function resolveRelativeDate(expr, tz, now) {
15
15
  const base = now ? dayjs(now).tz(tz) : dayjs().tz(tz);
16
- if ('startOf' in expr) {
16
+ if ("startOf" in expr) {
17
17
  return base.startOf(expr.startOf).toDate();
18
18
  }
19
19
  return base.endOf(expr.endOf).toDate();
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- export type { TsenseFieldMeta, TsenseFieldType } from './env.js';
2
- export { rank } from './rank.js';
3
- export { DateTransformer } from './transformers/date.js';
4
- export { defaultTransformers } from './transformers/defaults.js';
5
- export type { FieldTransformer } from './transformers/types.js';
6
- export { TSense } from './tsense.js';
7
- export { isRelativeDate, resolveRelativeDate, } from './filters/relative-dates.js';
8
- export type { ConnectionConfig, DeleteResult, FilterFor, HighlightOptions, NumberFilter, ProjectSearch, RelativeDate, RelativeDateUnit, SearchListOptions, SearchListResult, SearchInput, SearchOptions, ScopedCollection, SearchOptionsPlain, SearchOptionsWithOmit, SearchOptionsWithPick, SearchResult, StringFilter, SyncConfig, SyncOptions, SyncResult, TsenseOptions, UpdateResult, UpsertResult, } from './types.js';
1
+ export type { TsenseFieldMeta, TsenseFieldType } from "./env.js";
2
+ export { rank } from "./rank.js";
3
+ export { DateTransformer } from "./transformers/date.js";
4
+ export { defaultTransformers } from "./transformers/defaults.js";
5
+ export type { FieldTransformer } from "./transformers/types.js";
6
+ export { TSense } from "./tsense.js";
7
+ export { isRelativeDate, resolveRelativeDate, } from "./filters/relative-dates.js";
8
+ export type { ConnectionConfig, DeleteResult, FilterFor, HighlightOptions, NumberFilter, ProjectSearch, RelativeDate, RelativeDateUnit, 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,5 +1,5 @@
1
- export { rank } from './rank.js';
2
- export { DateTransformer } from './transformers/date.js';
3
- export { defaultTransformers } from './transformers/defaults.js';
4
- export { TSense } from './tsense.js';
5
- export { isRelativeDate, resolveRelativeDate, } from './filters/relative-dates.js';
1
+ export { rank } from "./rank.js";
2
+ export { DateTransformer } from "./transformers/date.js";
3
+ export { defaultTransformers } from "./transformers/defaults.js";
4
+ export { TSense } from "./tsense.js";
5
+ export { isRelativeDate, resolveRelativeDate, } from "./filters/relative-dates.js";
@@ -1,7 +1,6 @@
1
- import type { AddButtonSlotProps, ConditionSelectSlotProps, FieldSelectSlotProps, PresetButtonSlotProps, RemoveButtonSlotProps, ValueInputSlotProps } from "./filter-builder-types.js";
1
+ import type { AddButtonSlotProps, ConditionSelectSlotProps, FieldSelectSlotProps, RemoveButtonSlotProps, ValueInputSlotProps } from "./filter-builder-types.js";
2
2
  export declare function defaultFieldSelect({ columns, value, onChange }: FieldSelectSlotProps): import("react/jsx-runtime").JSX.Element;
3
3
  export declare function defaultConditionSelect({ conditions, value, onChange, disabled }: ConditionSelectSlotProps): import("react/jsx-runtime").JSX.Element;
4
4
  export declare function defaultValueInput({ column, condition, value, onChange }: ValueInputSlotProps): import("react/jsx-runtime").JSX.Element;
5
5
  export declare function defaultRemoveButton({ onClick }: RemoveButtonSlotProps): import("react/jsx-runtime").JSX.Element;
6
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;
@@ -81,6 +81,3 @@ export function defaultRemoveButton({ onClick }) {
81
81
  export function defaultAddButton({ onClick }) {
82
82
  return (_jsx("button", { type: "button", className: "text-blue-500 hover:text-blue-700", onClick: onClick, children: "+ Add filter" }));
83
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
- }
@@ -25,13 +25,6 @@ export type RemoveButtonSlotProps = {
25
25
  export type AddButtonSlotProps = {
26
26
  onClick: () => void;
27
27
  };
28
- export type PresetButtonSlotProps = {
29
- preset: {
30
- field: string;
31
- name: string;
32
- };
33
- onClick: () => void;
34
- };
35
28
  export type RowSlotProps = {
36
29
  index: number;
37
30
  fieldSelect: ReactNode;
@@ -42,7 +35,6 @@ export type RowSlotProps = {
42
35
  export type RootSlotProps = {
43
36
  rows: ReactNode;
44
37
  addButton: ReactNode;
45
- presets: ReactNode | null;
46
38
  };
47
39
  export type FilterBuilderProps<T> = {
48
40
  descriptor: FilterDescriptor<T>;
@@ -54,5 +46,4 @@ export type FilterBuilderProps<T> = {
54
46
  renderValueInput?: (props: ValueInputSlotProps) => ReactNode;
55
47
  renderAddButton?: (props: AddButtonSlotProps) => ReactNode;
56
48
  renderRemoveButton?: (props: RemoveButtonSlotProps) => ReactNode;
57
- renderPresetButton?: (props: PresetButtonSlotProps) => ReactNode;
58
49
  };
@@ -1,3 +1,3 @@
1
1
  import { type ReactNode } from "react";
2
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;
3
+ export declare function FilterBuilder<T>({ descriptor, onChange, renderRoot, renderRow, renderFieldSelect, renderConditionSelect, renderValueInput, renderAddButton, renderRemoveButton }: FilterBuilderProps<T>): ReactNode;
@@ -1,8 +1,8 @@
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
- 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, }) {
4
+ import { defaultAddButton, defaultConditionSelect, defaultFieldSelect, defaultRemoveButton, defaultValueInput, } from "./filter-builder-defaults.js";
5
+ export function FilterBuilder({ descriptor, onChange, renderRoot, renderRow, renderFieldSelect, renderConditionSelect, renderValueInput, renderAddButton, renderRemoveButton, }) {
6
6
  const filters = useFilterBuilder(descriptor);
7
7
  const onChangeRef = useRef(onChange);
8
8
  onChangeRef.current = onChange;
@@ -14,7 +14,6 @@ export function FilterBuilder({ descriptor, onChange, renderRoot, renderRow, ren
14
14
  const ValueInput = renderValueInput ?? defaultValueInput;
15
15
  const RemoveButton = renderRemoveButton ?? defaultRemoveButton;
16
16
  const AddButton = renderAddButton ?? defaultAddButton;
17
- const PresetButton = renderPresetButton ?? defaultPresetButton;
18
17
  const rows = filters.rows.map((row, index) => {
19
18
  const fieldSelect = (_jsx(FieldSelect, { columns: filters.columns, value: row.field, onChange: (field) => filters.setField(index, field) }));
20
19
  const conditionSelect = (_jsx(ConditionSelect, { conditions: row.field ? filters.conditionsFor(row.field) : [], value: row.condition, onChange: (condition) => filters.setCondition(index, condition), disabled: !row.field }));
@@ -32,9 +31,8 @@ export function FilterBuilder({ descriptor, onChange, renderRoot, renderRow, ren
32
31
  return (_jsxs("div", { className: "flex items-center gap-2", children: [fieldSelect, conditionSelect, valueInput, removeButton] }, row.id));
33
32
  });
34
33
  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
34
  if (renderRoot) {
37
- return _jsx(_Fragment, { children: renderRoot({ rows: _jsx(_Fragment, { children: rows }), addButton, presets }) });
35
+ return _jsx(_Fragment, { children: renderRoot({ rows: _jsx(_Fragment, { children: rows }), addButton }) });
38
36
  }
39
- return (_jsxs("div", { className: "flex flex-col gap-2", children: [rows, addButton, presets] }));
37
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [rows, addButton] }));
40
38
  }
@@ -1,4 +1,4 @@
1
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";
2
+ export type { AddButtonSlotProps, ConditionSelectSlotProps, FieldSelectSlotProps, 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";
@@ -1,12 +1,9 @@
1
1
  import { type FilterRow, type FilterValue } from "../filters/filter-state.js";
2
2
  import type { FilterDescriptor } from "../filters/index.js";
3
- type Preset = {
4
- field: string;
5
- name: string;
6
- };
7
3
  type UseFilterBuilderReturn = {
8
4
  columns: FilterDescriptor["columns"];
9
5
  rows: FilterRow[];
6
+ hasManualRows: boolean;
10
7
  add: () => void;
11
8
  addWithField: (field: string) => void;
12
9
  remove: (index: number) => void;
@@ -14,10 +11,9 @@ type UseFilterBuilderReturn = {
14
11
  setCondition: (index: number, condition: string) => void;
15
12
  setValue: (index: number, value: FilterValue) => void;
16
13
  clear: () => void;
14
+ restore: (rows: Omit<FilterRow, "id">[]) => void;
17
15
  conditionsFor: (field: string) => FilterDescriptor["columns"][number]["conditions"];
18
16
  columnFor: (field: string) => FilterDescriptor["columns"][number];
19
- presets: Preset[];
20
- applyPreset: (field: string, name: string) => void;
21
17
  result: Record<string, unknown>;
22
18
  };
23
19
  export declare function useFilterBuilder<T>(descriptor: FilterDescriptor<T>): UseFilterBuilderReturn;
@@ -1,12 +1,12 @@
1
1
  import { useMemo, useState } from "react";
2
- import { addRow, addRowWithField, applyPreset as applyPresetState, buildResult, clearState, columnFor, conditionsFor, createInitialState, removeRow, setRowCondition, setRowField, setRowValue, } from "../filters/filter-state.js";
2
+ import { addRow, addRowWithField, buildResult, clearState, columnFor, conditionsFor, createInitialState, hasManualRows as hasManualRowsState, removeRow, restoreRows, setRowCondition, setRowField, setRowValue, } from "../filters/filter-state.js";
3
3
  export function useFilterBuilder(descriptor) {
4
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
5
  const result = useMemo(() => buildResult(state), [state]);
7
6
  return {
8
7
  columns: descriptor.columns,
9
8
  rows: state.rows,
9
+ hasManualRows: hasManualRowsState(state),
10
10
  add: () => setState(addRow),
11
11
  addWithField: (field) => setState((s) => addRowWithField(s, field)),
12
12
  remove: (index) => setState((s) => removeRow(s, index)),
@@ -14,10 +14,9 @@ export function useFilterBuilder(descriptor) {
14
14
  setCondition: (index, condition) => setState((s) => setRowCondition(s, index, condition)),
15
15
  setValue: (index, value) => setState((s) => setRowValue(s, index, value)),
16
16
  clear: () => setState(clearState),
17
+ restore: (rows) => setState(() => restoreRows(rows)),
17
18
  conditionsFor: (field) => conditionsFor(descriptor, field),
18
19
  columnFor: (field) => columnFor(descriptor, field),
19
- presets,
20
- applyPreset: (field, name) => setState((s) => applyPresetState(s, descriptor, field, name)),
21
20
  result,
22
21
  };
23
22
  }
package/dist/tsense.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { Type } from 'arktype';
2
- import redaxios from 'redaxios';
3
- import type { DeleteResult, FieldSchema, FilterFor, ProjectSearch, ScopedCollection, SearchListOptions, SearchListResult, SearchOptions, SearchOptionsPlain, SearchResult, SyncOptions, SyncResult, TsenseOptions, UpdateResult, UpsertResult } from './types.js';
1
+ import type { Type } from "arktype";
2
+ import redaxios from "redaxios";
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>>);
@@ -64,7 +64,7 @@ export declare class TSense<T extends Type> {
64
64
  private synced;
65
65
  private fieldTransformers;
66
66
  private dataSyncConfig?;
67
- infer: T['infer'];
67
+ infer: T["infer"];
68
68
  constructor(options: TsenseOptions<T>);
69
69
  private getBaseType;
70
70
  private inferType;
@@ -85,23 +85,23 @@ export declare class TSense<T extends Type> {
85
85
  private buildSort;
86
86
  create(): Promise<this>;
87
87
  drop(): Promise<void>;
88
- get(id: string): Promise<T['infer'] | null>;
88
+ get(id: string): Promise<T["infer"] | null>;
89
89
  delete(id: string): Promise<boolean>;
90
90
  private deleteManyWithFilterBy;
91
- deleteMany(filter: FilterFor<T['infer']>): Promise<DeleteResult>;
92
- update(id: string, data: Partial<T['infer']>): Promise<T['infer']>;
91
+ deleteMany(filter: FilterFor<T["infer"]>): Promise<DeleteResult>;
92
+ update(id: string, data: Partial<T["infer"]>): Promise<T["infer"]>;
93
93
  private updateManyWithFilterBy;
94
- updateMany(filter: FilterFor<T['infer']>, data: Partial<T['infer']>): Promise<UpdateResult>;
94
+ updateMany(filter: FilterFor<T["infer"]>, data: Partial<T["infer"]>): Promise<UpdateResult>;
95
95
  private searchWithFilterBy;
96
- search<const O extends SearchOptions<T['infer']> = SearchOptionsPlain<T['infer']>>(options: O): Promise<SearchResult<ProjectSearch<T['infer'], O>>>;
96
+ search<const O extends SearchOptions<T["infer"]> = SearchOptionsPlain<T["infer"]>>(options: O): Promise<SearchResult<ProjectSearch<T["infer"], O>>>;
97
97
  private searchListWithFilterBy;
98
- searchList(options: SearchListOptions<T['infer']>): Promise<SearchListResult<T['infer']>>;
98
+ searchList(options: SearchListOptions<T["infer"]>): Promise<SearchListResult<T["infer"]>>;
99
99
  private countWithFilterBy;
100
- count(filter?: FilterFor<T['infer']>): Promise<number>;
101
- upsert(docs: T['infer'] | T['infer'][]): Promise<UpsertResult[]>;
100
+ count(filter?: FilterFor<T["infer"]>): Promise<number>;
101
+ upsert(docs: T["infer"] | T["infer"][]): Promise<UpsertResult[]>;
102
102
  syncData(options?: SyncOptions): Promise<SyncResult>;
103
103
  private purgeOrphans;
104
104
  exportIds(): Promise<string[]>;
105
- scoped(baseFilter: FilterFor<T['infer']>): ScopedCollection<T['infer']>;
105
+ scoped(baseFilter: FilterFor<T["infer"]>): ScopedCollection<T["infer"]>;
106
106
  }
107
107
  export {};
package/dist/tsense.js CHANGED
@@ -1,7 +1,7 @@
1
- import redaxios from 'redaxios';
2
- import { isRelativeDate, resolveRelativeDate, } from './filters/relative-dates.js';
3
- import { TSenseMigrator } from './migrator.js';
4
- import { defaultTransformers } from './transformers/defaults.js';
1
+ import redaxios from "redaxios";
2
+ import { isRelativeDate, resolveRelativeDate, } from "./filters/relative-dates.js";
3
+ import { TSenseMigrator } from "./migrator.js";
4
+ import { defaultTransformers } from "./transformers/defaults.js";
5
5
  function chunkArray(arr, size) {
6
6
  const chunks = [];
7
7
  for (let i = 0; i < arr.length; i += size) {
@@ -11,8 +11,8 @@ function chunkArray(arr, size) {
11
11
  }
12
12
  const redaxiosInstance = redaxios.default ?? redaxios;
13
13
  function escapeFilterValue(value) {
14
- if (typeof value === 'string') {
15
- return `\`${value.replaceAll('`', '')}\``;
14
+ if (typeof value === "string") {
15
+ return `\`${value.replaceAll("`", "")}\``;
16
16
  }
17
17
  if (Array.isArray(value)) {
18
18
  return value.map(escapeFilterValue);
@@ -25,17 +25,17 @@ const filterOperators = {
25
25
  gte: (k, v) => `${k}:>=${v}`,
26
26
  lt: (k, v) => `${k}:<${v}`,
27
27
  lte: (k, v) => `${k}:<=${v}`,
28
- notIn: (k, v) => `${k}:!=[${v.join(',')}]`,
28
+ notIn: (k, v) => `${k}:!=[${v.join(",")}]`,
29
29
  };
30
30
  const arkToTsense = {
31
- string: 'string',
32
- number: 'float',
33
- 'number.integer': 'int64',
34
- 'number % 1': 'int64',
35
- boolean: 'bool',
36
- 'string[]': 'string[]',
37
- 'number[]': 'float[]',
38
- 'boolean[]': 'bool[]',
31
+ string: "string",
32
+ number: "float",
33
+ "number.integer": "int64",
34
+ "number % 1": "int64",
35
+ boolean: "bool",
36
+ "string[]": "string[]",
37
+ "number[]": "float[]",
38
+ "boolean[]": "bool[]",
39
39
  };
40
40
  export class TSense {
41
41
  options;
@@ -49,27 +49,27 @@ export class TSense {
49
49
  this.options = options;
50
50
  this.axios = redaxiosInstance.create({
51
51
  baseURL: `${options.connection.protocol}://${options.connection.host}:${options.connection.port}`,
52
- headers: { 'X-TYPESENSE-API-KEY': options.connection.apiKey },
52
+ headers: { "X-TYPESENSE-API-KEY": options.connection.apiKey },
53
53
  });
54
54
  this.fields = this.extractFields(options.transformers ?? defaultTransformers);
55
55
  this.dataSyncConfig = options.dataSync;
56
56
  }
57
57
  getBaseType(expression, domain) {
58
- if (domain && domain !== 'undefined')
58
+ if (domain && domain !== "undefined")
59
59
  return domain;
60
- return expression.replace(/ \| undefined$/, '');
60
+ return expression.replace(/ \| undefined$/, "");
61
61
  }
62
62
  inferType(arkType) {
63
63
  const direct = arkToTsense[arkType];
64
64
  if (direct)
65
65
  return direct;
66
- if (arkType.includes('[]'))
67
- return 'object[]';
66
+ if (arkType.includes("[]"))
67
+ return "object[]";
68
68
  if (arkType.includes("'") || arkType.includes('"'))
69
- return 'string';
70
- if (arkType.includes('{') || arkType.includes('|'))
71
- return 'object';
72
- return 'string';
69
+ return "string";
70
+ if (arkType.includes("{") || arkType.includes("|"))
71
+ return "object";
72
+ return "string";
73
73
  }
74
74
  serializeDoc(doc) {
75
75
  const result = { ...doc };
@@ -100,7 +100,7 @@ export class TSense {
100
100
  if (Array.isArray(value)) {
101
101
  return value.map((v) => transformer.serialize(v));
102
102
  }
103
- if (typeof value === 'object' &&
103
+ if (typeof value === "object" &&
104
104
  value !== null &&
105
105
  Object.getPrototypeOf(value) === Object.prototype) {
106
106
  const result = {};
@@ -127,7 +127,7 @@ export class TSense {
127
127
  const branches = prop.value.branches ?? [];
128
128
  const enumValues = [];
129
129
  for (const branch of branches) {
130
- if (typeof branch.unit === 'string') {
130
+ if (typeof branch.unit === "string") {
131
131
  enumValues.push(branch.unit);
132
132
  }
133
133
  }
@@ -143,7 +143,7 @@ export class TSense {
143
143
  name: prop.key,
144
144
  type: transformer.storageType,
145
145
  sourceExpression: expression,
146
- optional: prop.kind === 'optional',
146
+ optional: prop.kind === "optional",
147
147
  facet: meta?.facet,
148
148
  sort: meta?.sort,
149
149
  index: meta?.index,
@@ -156,7 +156,7 @@ export class TSense {
156
156
  name: prop.key,
157
157
  type,
158
158
  sourceExpression: expression,
159
- optional: prop.kind === 'optional',
159
+ optional: prop.kind === "optional",
160
160
  facet: meta?.facet,
161
161
  sort: meta?.sort,
162
162
  index: meta?.index,
@@ -180,7 +180,7 @@ export class TSense {
180
180
  const escapedLte = escapeFilterValue(value.lte);
181
181
  const parts = [`${key}:[${escaped}..${escapedLte}]`];
182
182
  for (const [op, opValue] of Object.entries(value)) {
183
- if (op === 'gte' || op === 'lte' || opValue == null)
183
+ if (op === "gte" || op === "lte" || opValue == null)
184
184
  continue;
185
185
  const builder = filterOperators[op];
186
186
  if (builder) {
@@ -206,34 +206,34 @@ export class TSense {
206
206
  const [key, rawValue] = entry;
207
207
  if (rawValue == null)
208
208
  continue;
209
- if (key === 'OR') {
209
+ if (key === "OR") {
210
210
  const orParts = [];
211
211
  for (const condition of rawValue) {
212
212
  const inner = this.buildFilter(condition);
213
213
  if (!inner.length) {
214
214
  continue;
215
215
  }
216
- orParts.push(`(${inner.join('&&')})`);
216
+ orParts.push(`(${inner.join("&&")})`);
217
217
  }
218
218
  if (!orParts.length) {
219
219
  continue;
220
220
  }
221
- result.push(`(${orParts.join('||')})`);
221
+ result.push(`(${orParts.join("||")})`);
222
222
  continue;
223
223
  }
224
224
  const value = this.serializeFilterValue(key, rawValue);
225
225
  const escaped = escapeFilterValue(value);
226
- if (typeof escaped === 'string' ||
227
- typeof escaped === 'number' ||
228
- typeof escaped === 'boolean') {
226
+ if (typeof escaped === "string" ||
227
+ typeof escaped === "number" ||
228
+ typeof escaped === "boolean") {
229
229
  result.push(`${key}:=${escaped}`);
230
230
  continue;
231
231
  }
232
232
  if (Array.isArray(escaped)) {
233
- result.push(`${key}:[${escaped.join(',')}]`);
233
+ result.push(`${key}:[${escaped.join(",")}]`);
234
234
  continue;
235
235
  }
236
- if (typeof value === 'object' && value !== null) {
236
+ if (typeof value === "object" && value !== null) {
237
237
  result.push(...this.buildObjectFilter(key, value));
238
238
  }
239
239
  }
@@ -248,7 +248,7 @@ export class TSense {
248
248
  if (value == null) {
249
249
  continue;
250
250
  }
251
- if (key === 'OR') {
251
+ if (key === "OR") {
252
252
  for (const condition of value) {
253
253
  this.validateFilterFields(condition);
254
254
  }
@@ -264,7 +264,7 @@ export class TSense {
264
264
  validateFields(fields) {
265
265
  const valid = new Set(this.fields.map((f) => f.name));
266
266
  for (const field of fields) {
267
- if (field !== 'score' && !valid.has(field)) {
267
+ if (field !== "score" && !valid.has(field)) {
268
268
  throw new Error(`INVALID_FIELD: ${field}`);
269
269
  }
270
270
  }
@@ -276,7 +276,7 @@ export class TSense {
276
276
  if (Array.isArray(value)) {
277
277
  return value.map((v) => isRelativeDate(v) ? resolveRelativeDate(v, this.options.timezone) : v);
278
278
  }
279
- if (typeof value === 'object' &&
279
+ if (typeof value === "object" &&
280
280
  value !== null &&
281
281
  !(value instanceof Date)) {
282
282
  const result = {};
@@ -296,7 +296,7 @@ export class TSense {
296
296
  if (value == null) {
297
297
  continue;
298
298
  }
299
- if (key === 'OR') {
299
+ if (key === "OR") {
300
300
  result.OR = value.map((f) => this.resolveFilterDates(f));
301
301
  continue;
302
302
  }
@@ -311,32 +311,32 @@ export class TSense {
311
311
  if (!parts.length) {
312
312
  return;
313
313
  }
314
- return `(${parts.join('&&')})`;
314
+ return `(${parts.join("&&")})`;
315
315
  }
316
316
  combineFilterExpressions(...filters) {
317
317
  return filters
318
318
  .map((filter) => this.buildFilterExpression(filter))
319
319
  .filter((filter) => filter != null)
320
- .join('&&');
320
+ .join("&&");
321
321
  }
322
322
  buildSort(options) {
323
323
  if (!options.sortBy)
324
324
  return;
325
325
  const result = [];
326
326
  for (const item of options.sortBy) {
327
- const [field, direction] = item.split(':');
328
- if (field === 'undefined')
327
+ const [field, direction] = item.split(":");
328
+ if (field === "undefined")
329
329
  continue;
330
- const realField = field === 'score' ? '_text_match' : field;
330
+ const realField = field === "score" ? "_text_match" : field;
331
331
  result.push(`${realField}:${direction}`);
332
332
  }
333
- return result.join(',');
333
+ return result.join(",");
334
334
  }
335
335
  async create() {
336
- const enableNested = this.fields.some((f) => f.type === 'object' || f.type === 'object[]');
336
+ const enableNested = this.fields.some((f) => f.type === "object" || f.type === "object[]");
337
337
  await this.axios({
338
- method: 'POST',
339
- url: '/collections',
338
+ method: "POST",
339
+ url: "/collections",
340
340
  data: {
341
341
  name: this.options.name,
342
342
  fields: this.fields,
@@ -348,14 +348,14 @@ export class TSense {
348
348
  }
349
349
  async drop() {
350
350
  await this.axios({
351
- method: 'DELETE',
351
+ method: "DELETE",
352
352
  url: `/collections/${this.options.name}`,
353
353
  });
354
354
  }
355
355
  async get(id) {
356
356
  await this.ensureSynced();
357
357
  const { data } = await this.axios({
358
- method: 'GET',
358
+ method: "GET",
359
359
  url: `/collections/${this.options.name}/documents/${id}`,
360
360
  }).catch((e) => {
361
361
  if (e.status === 404)
@@ -367,7 +367,7 @@ export class TSense {
367
367
  async delete(id) {
368
368
  await this.ensureSynced();
369
369
  const { data } = await this.axios({
370
- method: 'DELETE',
370
+ method: "DELETE",
371
371
  url: `/collections/${this.options.name}/documents/${id}`,
372
372
  }).catch((e) => {
373
373
  if (e.status === 404)
@@ -379,10 +379,10 @@ export class TSense {
379
379
  async deleteManyWithFilterBy(filterBy) {
380
380
  await this.ensureSynced();
381
381
  if (!filterBy) {
382
- throw new Error('FILTER_REQUIRED');
382
+ throw new Error("FILTER_REQUIRED");
383
383
  }
384
384
  const { data } = await this.axios({
385
- method: 'DELETE',
385
+ method: "DELETE",
386
386
  url: `/collections/${this.options.name}/documents`,
387
387
  params: { filter_by: filterBy },
388
388
  });
@@ -395,7 +395,7 @@ export class TSense {
395
395
  await this.ensureSynced();
396
396
  const serialized = this.serializeDoc(data);
397
397
  const { data: updated } = await this.axios({
398
- method: 'PATCH',
398
+ method: "PATCH",
399
399
  url: `/collections/${this.options.name}/documents/${id}`,
400
400
  data: serialized,
401
401
  });
@@ -404,11 +404,11 @@ export class TSense {
404
404
  async updateManyWithFilterBy(filterBy, data) {
405
405
  await this.ensureSynced();
406
406
  if (!filterBy) {
407
- throw new Error('FILTER_REQUIRED');
407
+ throw new Error("FILTER_REQUIRED");
408
408
  }
409
409
  const serialized = this.serializeDoc(data);
410
410
  const { data: result } = await this.axios({
411
- method: 'PATCH',
411
+ method: "PATCH",
412
412
  url: `/collections/${this.options.name}/documents`,
413
413
  params: { filter_by: filterBy },
414
414
  data: serialized,
@@ -426,15 +426,15 @@ export class TSense {
426
426
  this.validateFields(queryByFields);
427
427
  if (options.sortBy) {
428
428
  this.validateFields(options.sortBy
429
- .map((s) => s.split(':')[0])
430
- .filter((f) => f !== 'undefined'));
429
+ .map((s) => s.split(":")[0])
430
+ .filter((f) => f !== "undefined"));
431
431
  }
432
432
  if (options.facetBy) {
433
433
  this.validateFields(options.facetBy);
434
434
  }
435
- const queryBy = queryByFields.join(',');
435
+ const queryBy = queryByFields.join(",");
436
436
  const params = {
437
- q: options.query ?? '*',
437
+ q: options.query ?? "*",
438
438
  query_by: queryBy,
439
439
  };
440
440
  const sortBy = this.buildSort(options);
@@ -446,20 +446,20 @@ export class TSense {
446
446
  params.page = options.page;
447
447
  if (options.limit != null)
448
448
  params.per_page = options.limit;
449
- const facetBy = options.facetBy?.join(',');
449
+ const facetBy = options.facetBy?.join(",");
450
450
  if (facetBy)
451
451
  params.facet_by = facetBy;
452
- if ('pick' in options && options.pick) {
453
- params.include_fields = options.pick.join(',');
452
+ if ("pick" in options && options.pick) {
453
+ params.include_fields = options.pick.join(",");
454
454
  }
455
- if ('omit' in options && options.omit) {
456
- params.exclude_fields = options.omit.join(',');
455
+ if ("omit" in options && options.omit) {
456
+ params.exclude_fields = options.omit.join(",");
457
457
  }
458
458
  const highlight = options.highlight;
459
- const highlightOpts = typeof highlight === 'object' ? highlight : undefined;
459
+ const highlightOpts = typeof highlight === "object" ? highlight : undefined;
460
460
  if (highlightOpts) {
461
461
  if (highlightOpts.fields) {
462
- params.highlight_fields = highlightOpts.fields.join(',');
462
+ params.highlight_fields = highlightOpts.fields.join(",");
463
463
  }
464
464
  if (highlightOpts.startTag) {
465
465
  params.highlight_start_tag = highlightOpts.startTag;
@@ -469,7 +469,7 @@ export class TSense {
469
469
  }
470
470
  }
471
471
  const { data: res } = await this.axios({
472
- method: 'GET',
472
+ method: "GET",
473
473
  url: `/collections/${this.options.name}/documents/search`,
474
474
  params,
475
475
  });
@@ -532,19 +532,19 @@ export class TSense {
532
532
  await this.ensureSynced();
533
533
  if (!filterBy) {
534
534
  const { data } = await this.axios({
535
- method: 'GET',
535
+ method: "GET",
536
536
  url: `/collections/${this.options.name}`,
537
537
  });
538
538
  return data.num_documents;
539
539
  }
540
540
  const params = {
541
- q: '*',
541
+ q: "*",
542
542
  query_by: this.options.defaultSearchField,
543
543
  per_page: 0,
544
544
  filter_by: filterBy,
545
545
  };
546
546
  const { data } = await this.axios({
547
- method: 'GET',
547
+ method: "GET",
548
548
  url: `/collections/${this.options.name}/documents/search`,
549
549
  params,
550
550
  });
@@ -565,26 +565,26 @@ export class TSense {
565
565
  }
566
566
  const payload = items
567
567
  .map((item) => JSON.stringify(this.serializeDoc(item)))
568
- .join('\n');
569
- const params = { action: 'upsert' };
568
+ .join("\n");
569
+ const params = { action: "upsert" };
570
570
  if (this.options.batchSize) {
571
571
  params.batch_size = this.options.batchSize;
572
572
  }
573
573
  const { data } = await this.axios({
574
- method: 'POST',
574
+ method: "POST",
575
575
  url: `/collections/${this.options.name}/documents/import`,
576
- headers: { 'Content-Type': 'text/plain' },
576
+ headers: { "Content-Type": "text/plain" },
577
577
  params,
578
578
  data: payload,
579
579
  });
580
- if (typeof data === 'string') {
581
- return data.split('\n').map((v) => JSON.parse(v));
580
+ if (typeof data === "string") {
581
+ return data.split("\n").map((v) => JSON.parse(v));
582
582
  }
583
583
  return [data];
584
584
  }
585
585
  async syncData(options) {
586
586
  if (!this.dataSyncConfig) {
587
- throw new Error('DATA_SYNC_NOT_CONFIGURED');
587
+ throw new Error("DATA_SYNC_NOT_CONFIGURED");
588
588
  }
589
589
  const chunkSize = options?.chunkSize ?? this.dataSyncConfig.chunkSize ?? 500;
590
590
  const ids = options?.ids ?? (await this.dataSyncConfig.getAllIds());
@@ -621,12 +621,12 @@ export class TSense {
621
621
  }
622
622
  async exportIds() {
623
623
  const { data } = await this.axios({
624
- method: 'GET',
624
+ method: "GET",
625
625
  url: `/collections/${this.options.name}/documents/export`,
626
- params: { include_fields: 'id' },
626
+ params: { include_fields: "id" },
627
627
  });
628
628
  return data
629
- .split('\n')
629
+ .split("\n")
630
630
  .filter((line) => line.length)
631
631
  .map((line) => JSON.parse(line).id);
632
632
  }
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { Type } from 'arktype';
2
- import type { FieldTransformer } from './transformers/types.js';
1
+ import type { Type } from "arktype";
2
+ import type { FieldTransformer } from "./transformers/types.js";
3
3
  type BaseIfArray<T> = T extends (infer Q)[] ? Q : T;
4
4
  export type FieldSchema = {
5
5
  name: string;
@@ -32,7 +32,7 @@ export type SearchApiResponse<T> = {
32
32
  export type ConnectionConfig = {
33
33
  host: string;
34
34
  port: number;
35
- protocol: 'http' | 'https';
35
+ protocol: "http" | "https";
36
36
  apiKey: string;
37
37
  timeout?: number;
38
38
  };
@@ -40,16 +40,16 @@ export type TsenseOptions<T extends Type> = {
40
40
  name: string;
41
41
  schema: T;
42
42
  connection: ConnectionConfig;
43
- defaultSearchField?: keyof T['infer'];
44
- defaultSortingField?: keyof T['infer'];
43
+ defaultSearchField?: keyof T["infer"];
44
+ defaultSortingField?: keyof T["infer"];
45
45
  batchSize?: number;
46
46
  validateOnUpsert?: boolean;
47
47
  autoSyncSchema?: boolean;
48
48
  timezone?: string;
49
49
  transformers?: FieldTransformer[];
50
- dataSync?: SyncConfig<T['infer']>;
50
+ dataSync?: SyncConfig<T["infer"]>;
51
51
  };
52
- export type RelativeDateUnit = 'day' | 'week' | 'month';
52
+ export type RelativeDateUnit = "day" | "week" | "month";
53
53
  export type RelativeDate = {
54
54
  startOf: RelativeDateUnit;
55
55
  } | {
@@ -88,12 +88,12 @@ export type HighlightOptions<T> = {
88
88
  startTag?: string;
89
89
  endTag?: string;
90
90
  };
91
- type SortableField<T> = Extract<keyof T, string> | 'score';
91
+ type SortableField<T> = Extract<keyof T, string> | "score";
92
92
  export type BaseSearchOptions<T> = {
93
93
  query?: string;
94
94
  queryBy?: (keyof T)[];
95
95
  filter?: FilterFor<T>;
96
- sortBy?: `${SortableField<T>}:${'asc' | 'desc'}`[];
96
+ sortBy?: `${SortableField<T>}:${"asc" | "desc"}`[];
97
97
  facetBy?: (keyof T)[];
98
98
  page?: number;
99
99
  limit?: number;
@@ -144,7 +144,7 @@ export type SearchListOptions<T> = {
144
144
  query?: string;
145
145
  queryBy?: (keyof T)[];
146
146
  filter?: FilterFor<T>;
147
- sortBy: `${Extract<keyof T, string>}:${'asc' | 'desc'}`;
147
+ sortBy: `${Extract<keyof T, string>}:${"asc" | "desc"}`;
148
148
  limit?: number;
149
149
  cursor?: string;
150
150
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsense",
3
- "version": "0.2.0-next.5",
3
+ "version": "0.2.0-next.7",
4
4
  "private": false,
5
5
  "description": "Opinionated, fully typed typesense client",
6
6
  "keywords": [