tsense 0.2.0-next.0 → 0.2.0-next.2

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.
@@ -11,6 +11,7 @@ export type FilterState = {
11
11
  };
12
12
  export declare function createInitialState(): FilterState;
13
13
  export declare function addRow(state: FilterState): FilterState;
14
+ export declare function addRowWithField(state: FilterState, field: string): FilterState;
14
15
  export declare function removeRow(state: FilterState, index: number): FilterState;
15
16
  export declare function setRowField(state: FilterState, index: number, field: string): FilterState;
16
17
  export declare function setRowCondition(state: FilterState, index: number, condition: string): FilterState;
@@ -11,11 +11,14 @@ const conditionToOperator = {
11
11
  };
12
12
  let nextRowId = 0;
13
13
  export function createInitialState() {
14
- return { rows: [{ id: ++nextRowId }] };
14
+ return { rows: [] };
15
15
  }
16
16
  export function addRow(state) {
17
17
  return { rows: [...state.rows, { id: ++nextRowId }] };
18
18
  }
19
+ export function addRowWithField(state, field) {
20
+ return { rows: [...state.rows, { id: ++nextRowId, field }] };
21
+ }
19
22
  export function removeRow(state, index) {
20
23
  return { rows: state.rows.filter((_, i) => i !== index) };
21
24
  }
@@ -37,54 +40,58 @@ export function setRowValue(state, index, value) {
37
40
  export function clearState() {
38
41
  return { rows: [] };
39
42
  }
40
- export function applyPreset(state, descriptor, field, name) {
41
- const column = descriptor.columns.find((c) => c.key === field);
42
- const preset = column?.presets?.find((p) => p.name === name);
43
- if (!preset)
44
- return state;
45
- const filterValue = preset.filter[field];
46
- if (filterValue == null)
47
- return state;
43
+ function filterValueToRow(field, filterValue) {
44
+ if (filterValue == null) {
45
+ return null;
46
+ }
48
47
  if (Array.isArray(filterValue)) {
49
48
  return {
50
- rows: [
51
- ...state.rows,
52
- { id: ++nextRowId, field, condition: "is_in", value: filterValue },
53
- ],
49
+ id: ++nextRowId,
50
+ field,
51
+ condition: "is_in",
52
+ value: filterValue,
54
53
  };
55
54
  }
56
55
  if (typeof filterValue !== "object" || filterValue instanceof Date) {
57
56
  return {
58
- rows: [
59
- ...state.rows,
60
- { id: ++nextRowId, field, condition: "equals", value: filterValue },
61
- ],
57
+ id: ++nextRowId,
58
+ field,
59
+ condition: "equals",
60
+ value: filterValue,
62
61
  };
63
62
  }
64
63
  const ops = filterValue;
65
64
  const keys = Object.keys(ops);
66
65
  if (keys.includes("gte") && keys.includes("lte")) {
67
66
  return {
68
- rows: [
69
- ...state.rows,
70
- {
71
- id: ++nextRowId,
72
- field,
73
- condition: "between",
74
- value: [ops.gte, ops.lte],
75
- },
76
- ],
67
+ id: ++nextRowId,
68
+ field,
69
+ condition: "between",
70
+ value: [ops.gte, ops.lte],
77
71
  };
78
72
  }
79
73
  if (keys[0]) {
80
- return {
81
- rows: [
82
- ...state.rows,
83
- { id: ++nextRowId, field, condition: keys[0], value: ops[keys[0]] },
84
- ],
85
- };
74
+ return { id: ++nextRowId, field, condition: keys[0], value: ops[keys[0]] };
75
+ }
76
+ return null;
77
+ }
78
+ export function applyPreset(state, descriptor, field, name) {
79
+ const column = descriptor.columns.find((c) => c.key === field);
80
+ const preset = column?.presets?.find((p) => p.name === name);
81
+ if (!preset) {
82
+ return state;
83
+ }
84
+ const rows = [];
85
+ for (const [key, value] of Object.entries(preset.filter)) {
86
+ const row = filterValueToRow(key, value);
87
+ if (row) {
88
+ rows.push(row);
89
+ }
90
+ }
91
+ if (!rows.length) {
92
+ return state;
86
93
  }
87
- return state;
94
+ return { rows };
88
95
  }
89
96
  export function conditionsFor(descriptor, field) {
90
97
  const column = descriptor.columns.find((c) => c.key === field);
@@ -58,11 +58,23 @@ export function createFilterBuilder(collection, config, options) {
58
58
  "not?": "string",
59
59
  "notIn?": "string[]",
60
60
  });
61
+ const dateInput = "number | string | Date";
62
+ const dateOps = type.raw({
63
+ "not?": dateInput,
64
+ "gt?": dateInput,
65
+ "gte?": dateInput,
66
+ "lt?": dateInput,
67
+ "lte?": dateInput,
68
+ "notIn?": `(${dateInput})[]`,
69
+ });
61
70
  const fieldSchemas = {
62
71
  number: type.raw("number").or(type.raw("number[]")).or(numberOps),
63
72
  string: type.raw("string").or(type.raw("string[]")).or(stringOps),
64
73
  boolean: type.raw("boolean"),
65
- date: type.raw("number").or(type.raw("number[]")).or(numberOps),
74
+ date: type
75
+ .raw(dateInput)
76
+ .or(type.raw(`(${dateInput})[]`))
77
+ .or(dateOps),
66
78
  };
67
79
  const descriptor = this.describe();
68
80
  const filterDef = {};
package/dist/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, } 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, WithNull, } from "./types.js";
@@ -1,12 +1,7 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Fragment, useEffect, useRef } from "react";
3
3
  import { useFilterBuilder } from "./use-filter-builder.js";
4
- 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
- }
4
+ // ======== Helpers ========
10
5
  function formatDateForInput(date) {
11
6
  if (!(date instanceof Date))
12
7
  return "";
@@ -23,6 +18,18 @@ function asTuple(value) {
23
18
  }
24
19
  return [undefined, undefined];
25
20
  }
21
+ function renderSelectOptions(options, defaultValue) {
22
+ const opts = defaultValue
23
+ ? [{ key: "", label: defaultValue }, ...options]
24
+ : options;
25
+ return opts.map((o) => (_jsx("option", { value: o.key, children: o.label }, o.key)));
26
+ }
27
+ function defaultFieldSelect({ columns, value, onChange, }) {
28
+ return (_jsx("select", { className: "rounded border px-2 py-1", value: value ?? "", onChange: (e) => onChange(e.target.value), children: renderSelectOptions(columns, "Column") }));
29
+ }
30
+ function defaultConditionSelect({ conditions, value, onChange, disabled, }) {
31
+ return (_jsx("select", { className: "rounded border px-2 py-1", value: value ?? "", onChange: (e) => onChange(e.target.value), disabled: disabled, children: renderSelectOptions(conditions, "Condition") }));
32
+ }
26
33
  function defaultValueInput({ column, condition, value, onChange, }) {
27
34
  if (condition === "between" && column.type === "date") {
28
35
  const tuple = asTuple(value);
@@ -36,7 +43,7 @@ function defaultValueInput({ column, condition, value, onChange, }) {
36
43
  const selected = Array.isArray(value) ? value : [];
37
44
  return (_jsx("div", { className: "flex flex-wrap gap-2", children: column.values.map((v) => {
38
45
  const checked = selected.includes(v.value);
39
- return (_jsxs("label", { className: "flex items-center gap-1 text-sm", children: [_jsx("input", { type: "checkbox", checked: checked, onChange: () => onChange(checked
46
+ return (_jsxs("label", { className: "flex cursor-pointer select-none items-center gap-1 text-sm", children: [_jsx("input", { type: "checkbox", checked: checked, onChange: () => onChange(checked
40
47
  ? selected.filter((s) => s !== v.value)
41
48
  : [...selected, v.value]) }), v.label] }, v.value));
42
49
  }) }));
@@ -48,7 +55,12 @@ function defaultValueInput({ column, condition, value, onChange, }) {
48
55
  return (_jsx("input", { className: "rounded border px-2 py-1", type: "number", value: String(value ?? ""), onChange: (e) => onChange(Number(e.target.value)) }));
49
56
  }
50
57
  if (column.type === "boolean") {
51
- 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" })] }));
58
+ const options = [
59
+ { key: "", label: "Value" },
60
+ { key: "true", label: "true" },
61
+ { key: "false", label: "false" },
62
+ ];
63
+ return (_jsx("select", { className: "rounded border px-2 py-1", value: value == null ? "" : String(value), onChange: (e) => onChange(e.target.value === "true"), children: renderSelectOptions(options) }));
52
64
  }
53
65
  return (_jsx("input", { className: "rounded border px-2 py-1", type: "text", value: typeof value === "string" ? value : "", onChange: (e) => onChange(e.target.value) }));
54
66
  }
@@ -8,6 +8,7 @@ type UseFilterBuilderReturn = {
8
8
  columns: FilterDescriptor["columns"];
9
9
  rows: FilterRow[];
10
10
  add: () => void;
11
+ addWithField: (field: string) => void;
11
12
  remove: (index: number) => void;
12
13
  setField: (index: number, field: string) => void;
13
14
  setCondition: (index: number, condition: string) => void;
@@ -1,5 +1,5 @@
1
1
  import { useMemo, useState } from "react";
2
- import { addRow, applyPreset as applyPresetState, buildResult, clearState, columnFor, conditionsFor, createInitialState, removeRow, setRowCondition, setRowField, setRowValue, } from "../filters/filter-state.js";
2
+ import { addRow, addRowWithField, applyPreset as applyPresetState, buildResult, clearState, columnFor, conditionsFor, createInitialState, removeRow, setRowCondition, setRowField, setRowValue, } from "../filters/filter-state.js";
3
3
  export function useFilterBuilder(descriptor) {
4
4
  const [state, setState] = useState(createInitialState);
5
5
  const presets = useMemo(() => descriptor.columns.flatMap((col) => (col.presets ?? []).map((p) => ({ field: col.key, name: p.name }))), [descriptor]);
@@ -8,6 +8,7 @@ export function useFilterBuilder(descriptor) {
8
8
  columns: descriptor.columns,
9
9
  rows: state.rows,
10
10
  add: () => setState(addRow),
11
+ addWithField: (field) => setState((s) => addRowWithField(s, field)),
11
12
  remove: (index) => setState((s) => removeRow(s, index)),
12
13
  setField: (index, field) => setState((s) => setRowField(s, index, field)),
13
14
  setCondition: (index, condition) => setState((s) => setRowCondition(s, index, condition)),
@@ -1,6 +1,14 @@
1
1
  export const DateTransformer = {
2
2
  match: (expr, domain) => expr === "Date" || domain === "Date",
3
3
  storageType: "int64",
4
- serialize: (date) => date.getTime(),
4
+ serialize: (date) => {
5
+ if (typeof date === "number") {
6
+ return date;
7
+ }
8
+ if (typeof date === "string") {
9
+ return new Date(date).getTime();
10
+ }
11
+ return date.getTime();
12
+ },
5
13
  deserialize: (ts) => new Date(ts),
6
14
  };
package/dist/tsense.d.ts CHANGED
@@ -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 } from "./types.js";
3
+ import type { DeleteResult, FieldSchema, FilterFor, ProjectSearch, ScopedCollection, SearchListOptions, SearchListResult, SearchOptions, SearchOptionsPlain, SearchResult, SyncOptions, SyncResult, TsenseOptions, UpdateResult, UpsertResult, WithNull } 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>>);
@@ -88,7 +88,7 @@ export declare class TSense<T extends Type> {
88
88
  search<const O extends SearchOptions<T["infer"]> = SearchOptionsPlain<T["infer"]>>(options: O): Promise<SearchResult<ProjectSearch<T["infer"], O>>>;
89
89
  searchList(options: SearchListOptions<T["infer"]>): Promise<SearchListResult<T["infer"]>>;
90
90
  count(filter?: FilterFor<T["infer"]>): Promise<number>;
91
- upsert(docs: T["infer"] | T["infer"][]): Promise<UpsertResult[]>;
91
+ upsert(docs: WithNull<T["infer"]> | WithNull<T["infer"]>[]): Promise<UpsertResult[]>;
92
92
  syncData(options?: SyncOptions): Promise<SyncResult>;
93
93
  private purgeOrphans;
94
94
  exportIds(): Promise<string[]>;
package/dist/tsense.js CHANGED
@@ -72,6 +72,11 @@ export class TSense {
72
72
  }
73
73
  serializeDoc(doc) {
74
74
  const result = { ...doc };
75
+ for (const key of Object.keys(result)) {
76
+ if (result[key] == null) {
77
+ delete result[key];
78
+ }
79
+ }
75
80
  for (const [field, transformer] of this.fieldTransformers) {
76
81
  if (result[field] != null) {
77
82
  result[field] = transformer.serialize(result[field]);
package/dist/types.d.ts CHANGED
@@ -1,6 +1,9 @@
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
+ };
4
7
  export type FieldSchema = {
5
8
  name: string;
6
9
  type: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsense",
3
- "version": "0.2.0-next.0",
3
+ "version": "0.2.0-next.2",
4
4
  "private": false,
5
5
  "description": "Opinionated, fully typed typesense client",
6
6
  "keywords": [