tsense 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -74,29 +74,6 @@ await UsersCollection.drop();
74
74
 
75
75
  ## API Reference
76
76
 
77
- ### Constructor
78
-
79
- ```typescript
80
- new TSense({
81
- name: string,
82
- schema: Type,
83
- connection: ConnectionConfig,
84
- defaultSearchField?: keyof T,
85
- defaultSortingField?: keyof T,
86
- batchSize?: number,
87
- validateOnUpsert?: boolean,
88
- })
89
- ```
90
-
91
- ### ConnectionConfig
92
-
93
- | Option | Type | Description |
94
- | ---------- | --------------------- | --------------------- |
95
- | `host` | `string` | Typesense server host |
96
- | `port` | `number` | Typesense server port |
97
- | `protocol` | `"http"` \| `"https"` | Connection protocol |
98
- | `apiKey` | `string` | Typesense API key |
99
-
100
77
  ### Schema Configuration
101
78
 
102
79
  Use `.configure()` to set Typesense field options:
@@ -124,22 +101,6 @@ type("string").configure({
124
101
  | `upsert(docs)` | Inserts or updates documents |
125
102
  | `search(options)` | Searches the collection |
126
103
 
127
- ### Search Options
128
-
129
- | Option | Type | Description |
130
- | ----------- | --------------------------------- | ------------------------ |
131
- | `query` | `string` | Text search query |
132
- | `queryBy` | `(keyof T)[]` | Fields to search in |
133
- | `filter` | `FilterFor<T>` | Filter conditions |
134
- | `sortBy` | `"field:asc\|desc"[]` | Sort order |
135
- | `facetBy` | `(keyof T)[]` | Fields to facet by |
136
- | `page` | `number` | Page number |
137
- | `limit` | `number` | Results per page |
138
- | `pick` | `(keyof T)[]` | Only return these fields |
139
- | `omit` | `(keyof T)[]` | Exclude these fields |
140
- | `highlight` | `boolean \| HighlightOptions<T>` | Enable highlighting |
141
-
142
- Note: `pick` and `omit` are mutually exclusive.
143
104
 
144
105
  ### Filter Syntax
145
106
 
@@ -150,4 +111,4 @@ filter: { age: [25, 30, 35] } // IN
150
111
  filter: { age: { min: 20, max: 40 } } // Range
151
112
  filter: { name: { not: "John" } } // Not equal
152
113
  filter: { OR: [{ age: 25 }, { age: 30 }] } // OR conditions
153
- ```
114
+ ```
package/dist/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
1
  export type { TsenseFieldMeta, TsenseFieldType } from "./env.js";
2
2
  export { TSense } from "./tsense.js";
3
- export type { ConnectionConfig, DeleteResult, FilterFor, HighlightOptions, SearchOptions, SearchResult, TsenseOptions, UpdateResult, UpsertResult, } from "./types.js";
3
+ export { defaultTransformers } from "./transformers/defaults.js";
4
+ export { DateTransformer } from "./transformers/date.js";
5
+ export type { FieldTransformer } from "./transformers/types.js";
6
+ export type { ConnectionConfig, DeleteResult, FilterFor, HighlightOptions, SearchListOptions, SearchListResult, SearchOptions, SearchResult, TsenseOptions, UpdateResult, UpsertResult, } from "./types.js";
package/dist/index.js CHANGED
@@ -1 +1,3 @@
1
1
  export { TSense } from "./tsense.js";
2
+ export { defaultTransformers } from "./transformers/defaults.js";
3
+ export { DateTransformer } from "./transformers/date.js";
@@ -0,0 +1,17 @@
1
+ import type { AxiosInstance } from "./tsense.js";
2
+ import type { FieldSchema } from "./types.js";
3
+ export declare class TSenseMigrator {
4
+ private collectionName;
5
+ private localFields;
6
+ private defaultSortingField;
7
+ private axios;
8
+ constructor(collectionName: string, localFields: FieldSchema[], defaultSortingField: string | undefined, axios: AxiosInstance);
9
+ sync(): Promise<void>;
10
+ private exists;
11
+ private getRemoteFields;
12
+ private diff;
13
+ private fieldsMatch;
14
+ private patch;
15
+ private create;
16
+ private drop;
17
+ }
@@ -0,0 +1,141 @@
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
+ const COMPARABLE_KEYS = ["type", "facet", "sort", "index", "optional"];
11
+ const NESTED_TYPES = ["object", "object[]"];
12
+ export class TSenseMigrator {
13
+ constructor(collectionName, localFields, defaultSortingField, axios) {
14
+ this.collectionName = collectionName;
15
+ this.localFields = localFields;
16
+ this.defaultSortingField = defaultSortingField;
17
+ this.axios = axios;
18
+ }
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
+ });
36
+ }
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
+ });
49
+ });
50
+ }
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;
58
+ });
59
+ }
60
+ diff(remote) {
61
+ const remoteByName = new Map(remote.map((f) => [f.name, f]));
62
+ const localByName = new Map(this.localFields.map((f) => [f.name, f]));
63
+ const toAdd = [];
64
+ const toRemove = [];
65
+ const toModify = [];
66
+ for (const local of this.localFields) {
67
+ if (local.name === "id")
68
+ continue;
69
+ const remoteField = remoteByName.get(local.name);
70
+ if (!remoteField) {
71
+ toAdd.push(local);
72
+ continue;
73
+ }
74
+ if (this.fieldsMatch(local, remoteField))
75
+ continue;
76
+ toModify.push(local);
77
+ }
78
+ for (const remoteField of remote) {
79
+ if (remoteField.name === "id")
80
+ continue;
81
+ if (localByName.has(remoteField.name))
82
+ continue;
83
+ toRemove.push(remoteField);
84
+ }
85
+ return { toAdd, toRemove, toModify };
86
+ }
87
+ fieldsMatch(local, remote) {
88
+ var _a, _b;
89
+ 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)) {
91
+ return false;
92
+ }
93
+ }
94
+ return true;
95
+ }
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
+ });
117
+ }
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
+ });
131
+ });
132
+ }
133
+ drop() {
134
+ return __awaiter(this, void 0, void 0, function* () {
135
+ yield this.axios({
136
+ method: "DELETE",
137
+ url: `/collections/${this.collectionName}`,
138
+ });
139
+ });
140
+ }
141
+ }
@@ -0,0 +1,2 @@
1
+ import type { FieldTransformer } from "./types.js";
2
+ export declare const DateTransformer: FieldTransformer<Date, number>;
@@ -0,0 +1,6 @@
1
+ export const DateTransformer = {
2
+ match: (expr, domain) => expr === "Date" || domain === "Date",
3
+ storageType: "int64",
4
+ serialize: (date) => date.getTime(),
5
+ deserialize: (ts) => new Date(ts),
6
+ };
@@ -0,0 +1,2 @@
1
+ import type { FieldTransformer } from "./types.js";
2
+ export declare const defaultTransformers: FieldTransformer[];
@@ -0,0 +1,2 @@
1
+ import { DateTransformer } from "./date.js";
2
+ export const defaultTransformers = [DateTransformer];
@@ -0,0 +1,7 @@
1
+ import type { TsenseFieldType } from "../env.js";
2
+ export interface FieldTransformer<TJs = unknown, TStorage = unknown> {
3
+ match: (expression: string, domain?: string) => boolean;
4
+ storageType: TsenseFieldType;
5
+ serialize: (value: TJs) => TStorage;
6
+ deserialize: (value: TStorage) => TJs;
7
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/tsense.d.ts CHANGED
@@ -1,15 +1,77 @@
1
1
  import type { Type } from "arktype";
2
- import type { DeleteResult, FilterFor, SearchOptions, SearchResult, TsenseOptions, UpdateResult, UpsertResult } from "./types.js";
2
+ import redaxios from "redaxios";
3
+ import type { DeleteResult, FilterFor, SearchListOptions, SearchListResult, SearchOptions, SearchResult, TsenseOptions, UpdateResult, UpsertResult } from "./types.js";
4
+ declare const redaxiosInstance: {
5
+ <T>(urlOrConfig: string | redaxios.Options, config?: redaxios.Options | undefined, _method?: any, data?: any, _undefined?: undefined): Promise<redaxios.Response<T>>;
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>>);
7
+ get<T_3 = any>(url: string, config?: redaxios.Options | undefined): Promise<redaxios.Response<T_3>>;
8
+ delete<T_3 = any>(url: string, config?: redaxios.Options | undefined): Promise<redaxios.Response<T_3>>;
9
+ head<T_3 = any>(url: string, config?: redaxios.Options | undefined): Promise<redaxios.Response<T_3>>;
10
+ options<T_3 = any>(url: string, config?: redaxios.Options | undefined): Promise<redaxios.Response<T_3>>;
11
+ post<T_4 = any>(url: string, body?: any, config?: redaxios.Options | undefined): Promise<redaxios.Response<T_4>>;
12
+ put<T_4 = any>(url: string, body?: any, config?: redaxios.Options | undefined): Promise<redaxios.Response<T_4>>;
13
+ patch<T_4 = any>(url: string, body?: any, config?: redaxios.Options | undefined): Promise<redaxios.Response<T_4>>;
14
+ all: {
15
+ <T_5>(values: Iterable<T_5 | PromiseLike<T_5>>): Promise<T_5[]>;
16
+ <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>, T10 | PromiseLike<T10>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>;
17
+ <T1_1, T2_1, T3_1, T4_1, T5_1, T6_1, T7_1, T8_1, T9_1>(values: readonly [T1_1 | PromiseLike<T1_1>, T2_1 | PromiseLike<T2_1>, T3_1 | PromiseLike<T3_1>, T4_1 | PromiseLike<T4_1>, T5_1 | PromiseLike<T5_1>, T6_1 | PromiseLike<T6_1>, T7_1 | PromiseLike<T7_1>, T8_1 | PromiseLike<T8_1>, T9_1 | PromiseLike<T9_1>]): Promise<[T1_1, T2_1, T3_1, T4_1, T5_1, T6_1, T7_1, T8_1, T9_1]>;
18
+ <T1_2, T2_2, T3_2, T4_2, T5_2, T6_2, T7_2, T8_2>(values: readonly [T1_2 | PromiseLike<T1_2>, T2_2 | PromiseLike<T2_2>, T3_2 | PromiseLike<T3_2>, T4_2 | PromiseLike<T4_2>, T5_2 | PromiseLike<T5_2>, T6_2 | PromiseLike<T6_2>, T7_2 | PromiseLike<T7_2>, T8_2 | PromiseLike<T8_2>]): Promise<[T1_2, T2_2, T3_2, T4_2, T5_2, T6_2, T7_2, T8_2]>;
19
+ <T1_3, T2_3, T3_3, T4_3, T5_3, T6_3, T7_3>(values: readonly [T1_3 | PromiseLike<T1_3>, T2_3 | PromiseLike<T2_3>, T3_3 | PromiseLike<T3_3>, T4_3 | PromiseLike<T4_3>, T5_3 | PromiseLike<T5_3>, T6_3 | PromiseLike<T6_3>, T7_3 | PromiseLike<T7_3>]): Promise<[T1_3, T2_3, T3_3, T4_3, T5_3, T6_3, T7_3]>;
20
+ <T1_4, T2_4, T3_4, T4_4, T5_4, T6_4>(values: readonly [T1_4 | PromiseLike<T1_4>, T2_4 | PromiseLike<T2_4>, T3_4 | PromiseLike<T3_4>, T4_4 | PromiseLike<T4_4>, T5_4 | PromiseLike<T5_4>, T6_4 | PromiseLike<T6_4>]): Promise<[T1_4, T2_4, T3_4, T4_4, T5_4, T6_4]>;
21
+ <T1_5, T2_5, T3_5, T4_5, T5_5>(values: readonly [T1_5 | PromiseLike<T1_5>, T2_5 | PromiseLike<T2_5>, T3_5 | PromiseLike<T3_5>, T4_5 | PromiseLike<T4_5>, T5_5 | PromiseLike<T5_5>]): Promise<[T1_5, T2_5, T3_5, T4_5, T5_5]>;
22
+ <T1_6, T2_6, T3_6, T4_6>(values: readonly [T1_6 | PromiseLike<T1_6>, T2_6 | PromiseLike<T2_6>, T3_6 | PromiseLike<T3_6>, T4_6 | PromiseLike<T4_6>]): Promise<[T1_6, T2_6, T3_6, T4_6]>;
23
+ <T1_7, T2_7, T3_7>(values: readonly [T1_7 | PromiseLike<T1_7>, T2_7 | PromiseLike<T2_7>, T3_7 | PromiseLike<T3_7>]): Promise<[T1_7, T2_7, T3_7]>;
24
+ <T1_8, T2_8>(values: readonly [T1_8 | PromiseLike<T1_8>, T2_8 | PromiseLike<T2_8>]): Promise<[T1_8, T2_8]>;
25
+ <T_6>(values: readonly (T_6 | PromiseLike<T_6>)[]): Promise<T_6[]>;
26
+ };
27
+ spread<Args, R>(fn: (...args: Args[]) => R): (array: Args[]) => R;
28
+ CancelToken: AbortController;
29
+ defaults: redaxios.Options;
30
+ create: (defaults?: redaxios.Options | undefined) => {
31
+ <T>(urlOrConfig: string | redaxios.Options, config?: redaxios.Options | undefined, _method?: any, data?: any, _undefined?: undefined): Promise<redaxios.Response<T>>;
32
+ 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>>);
33
+ get<T_3 = any>(url: string, config?: redaxios.Options | undefined): Promise<redaxios.Response<T_3>>;
34
+ delete<T_3 = any>(url: string, config?: redaxios.Options | undefined): Promise<redaxios.Response<T_3>>;
35
+ head<T_3 = any>(url: string, config?: redaxios.Options | undefined): Promise<redaxios.Response<T_3>>;
36
+ options<T_3 = any>(url: string, config?: redaxios.Options | undefined): Promise<redaxios.Response<T_3>>;
37
+ post<T_4 = any>(url: string, body?: any, config?: redaxios.Options | undefined): Promise<redaxios.Response<T_4>>;
38
+ put<T_4 = any>(url: string, body?: any, config?: redaxios.Options | undefined): Promise<redaxios.Response<T_4>>;
39
+ patch<T_4 = any>(url: string, body?: any, config?: redaxios.Options | undefined): Promise<redaxios.Response<T_4>>;
40
+ all: {
41
+ <T_5>(values: Iterable<T_5 | PromiseLike<T_5>>): Promise<T_5[]>;
42
+ <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>, T10 | PromiseLike<T10>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>;
43
+ <T1_1, T2_1, T3_1, T4_1, T5_1, T6_1, T7_1, T8_1, T9_1>(values: readonly [T1_1 | PromiseLike<T1_1>, T2_1 | PromiseLike<T2_1>, T3_1 | PromiseLike<T3_1>, T4_1 | PromiseLike<T4_1>, T5_1 | PromiseLike<T5_1>, T6_1 | PromiseLike<T6_1>, T7_1 | PromiseLike<T7_1>, T8_1 | PromiseLike<T8_1>, T9_1 | PromiseLike<T9_1>]): Promise<[T1_1, T2_1, T3_1, T4_1, T5_1, T6_1, T7_1, T8_1, T9_1]>;
44
+ <T1_2, T2_2, T3_2, T4_2, T5_2, T6_2, T7_2, T8_2>(values: readonly [T1_2 | PromiseLike<T1_2>, T2_2 | PromiseLike<T2_2>, T3_2 | PromiseLike<T3_2>, T4_2 | PromiseLike<T4_2>, T5_2 | PromiseLike<T5_2>, T6_2 | PromiseLike<T6_2>, T7_2 | PromiseLike<T7_2>, T8_2 | PromiseLike<T8_2>]): Promise<[T1_2, T2_2, T3_2, T4_2, T5_2, T6_2, T7_2, T8_2]>;
45
+ <T1_3, T2_3, T3_3, T4_3, T5_3, T6_3, T7_3>(values: readonly [T1_3 | PromiseLike<T1_3>, T2_3 | PromiseLike<T2_3>, T3_3 | PromiseLike<T3_3>, T4_3 | PromiseLike<T4_3>, T5_3 | PromiseLike<T5_3>, T6_3 | PromiseLike<T6_3>, T7_3 | PromiseLike<T7_3>]): Promise<[T1_3, T2_3, T3_3, T4_3, T5_3, T6_3, T7_3]>;
46
+ <T1_4, T2_4, T3_4, T4_4, T5_4, T6_4>(values: readonly [T1_4 | PromiseLike<T1_4>, T2_4 | PromiseLike<T2_4>, T3_4 | PromiseLike<T3_4>, T4_4 | PromiseLike<T4_4>, T5_4 | PromiseLike<T5_4>, T6_4 | PromiseLike<T6_4>]): Promise<[T1_4, T2_4, T3_4, T4_4, T5_4, T6_4]>;
47
+ <T1_5, T2_5, T3_5, T4_5, T5_5>(values: readonly [T1_5 | PromiseLike<T1_5>, T2_5 | PromiseLike<T2_5>, T3_5 | PromiseLike<T3_5>, T4_5 | PromiseLike<T4_5>, T5_5 | PromiseLike<T5_5>]): Promise<[T1_5, T2_5, T3_5, T4_5, T5_5]>;
48
+ <T1_6, T2_6, T3_6, T4_6>(values: readonly [T1_6 | PromiseLike<T1_6>, T2_6 | PromiseLike<T2_6>, T3_6 | PromiseLike<T3_6>, T4_6 | PromiseLike<T4_6>]): Promise<[T1_6, T2_6, T3_6, T4_6]>;
49
+ <T1_7, T2_7, T3_7>(values: readonly [T1_7 | PromiseLike<T1_7>, T2_7 | PromiseLike<T2_7>, T3_7 | PromiseLike<T3_7>]): Promise<[T1_7, T2_7, T3_7]>;
50
+ <T1_8, T2_8>(values: readonly [T1_8 | PromiseLike<T1_8>, T2_8 | PromiseLike<T2_8>]): Promise<[T1_8, T2_8]>;
51
+ <T_6>(values: readonly (T_6 | PromiseLike<T_6>)[]): Promise<T_6[]>;
52
+ };
53
+ spread<Args, R>(fn: (...args: Args[]) => R): (array: Args[]) => R;
54
+ CancelToken: AbortController;
55
+ defaults: redaxios.Options;
56
+ create: any;
57
+ };
58
+ };
59
+ export type AxiosInstance = ReturnType<typeof redaxiosInstance.create>;
3
60
  export declare class TSense<T extends Type> {
4
61
  private options;
5
62
  private fields;
6
- private enableNested;
7
- private baseURL;
8
- private headers;
63
+ private axios;
64
+ private synced;
65
+ private fieldTransformers;
9
66
  infer: T["infer"];
10
67
  constructor(options: TsenseOptions<T>);
11
68
  private inferType;
69
+ private serializeDoc;
70
+ private deserializeDoc;
71
+ private serializeFilterValue;
12
72
  private extractFields;
73
+ private ensureSynced;
74
+ sync(): Promise<void>;
13
75
  private buildObjectFilter;
14
76
  private buildFilter;
15
77
  private buildSort;
@@ -21,5 +83,7 @@ export declare class TSense<T extends Type> {
21
83
  update(id: string, data: Partial<T["infer"]>): Promise<T["infer"]>;
22
84
  updateMany(filter: FilterFor<T["infer"]>, data: Partial<T["infer"]>): Promise<UpdateResult>;
23
85
  search(options: SearchOptions<T["infer"]>): Promise<SearchResult<T["infer"]>>;
86
+ searchList(options: SearchListOptions<T["infer"]>): Promise<SearchListResult<T["infer"]>>;
24
87
  upsert(docs: T["infer"] | T["infer"][]): Promise<UpsertResult[]>;
25
88
  }
89
+ export {};
package/dist/tsense.js CHANGED
@@ -9,8 +9,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  var _a;
11
11
  import redaxios from "redaxios";
12
- const axios = (_a = redaxios.default) !== null && _a !== void 0 ? _a : redaxios;
13
- const requiresNested = ["object", "object[]"];
12
+ import { TSenseMigrator } from "./migrator.js";
13
+ import { defaultTransformers } from "./transformers/defaults.js";
14
+ const redaxiosInstance = (_a = redaxios.default) !== null && _a !== void 0 ? _a : redaxios;
14
15
  const arkToTsense = {
15
16
  string: "string",
16
17
  number: "float",
@@ -22,14 +23,17 @@ const arkToTsense = {
22
23
  };
23
24
  export class TSense {
24
25
  constructor(options) {
26
+ var _a;
25
27
  this.options = options;
26
28
  this.fields = [];
27
- this.enableNested = false;
29
+ this.synced = false;
30
+ this.fieldTransformers = new Map();
28
31
  this.infer = undefined;
29
- const { connection } = options;
30
- this.baseURL = `${connection.protocol}://${connection.host}:${connection.port}`;
31
- this.headers = { "X-TYPESENSE-API-KEY": connection.apiKey };
32
- this.extractFields();
32
+ this.axios = redaxiosInstance.create({
33
+ baseURL: `${options.connection.protocol}://${options.connection.host}:${options.connection.port}`,
34
+ headers: { "X-TYPESENSE-API-KEY": options.connection.apiKey },
35
+ });
36
+ this.extractFields((_a = options.transformers) !== null && _a !== void 0 ? _a : defaultTransformers);
33
37
  }
34
38
  inferType(arkType) {
35
39
  const direct = arkToTsense[arkType];
@@ -43,20 +47,71 @@ export class TSense {
43
47
  return "string";
44
48
  return "string";
45
49
  }
46
- extractFields() {
50
+ serializeDoc(doc) {
51
+ const result = Object.assign({}, doc);
52
+ for (const [field, transformer] of this.fieldTransformers) {
53
+ if (result[field] != null) {
54
+ result[field] = transformer.serialize(result[field]);
55
+ }
56
+ }
57
+ return result;
58
+ }
59
+ deserializeDoc(doc) {
60
+ for (const [field, transformer] of this.fieldTransformers) {
61
+ if (doc[field] != null) {
62
+ doc[field] = transformer.deserialize(doc[field]);
63
+ }
64
+ }
65
+ return doc;
66
+ }
67
+ serializeFilterValue(key, value) {
68
+ const transformer = this.fieldTransformers.get(key);
69
+ if (!transformer)
70
+ return value;
71
+ if (Array.isArray(value)) {
72
+ return value.map((v) => transformer.serialize(v));
73
+ }
74
+ if (typeof value === "object" && value !== null) {
75
+ const v = value;
76
+ const isFilterObject = "min" in v || "max" in v || "not" in v;
77
+ if (!isFilterObject) {
78
+ return transformer.serialize(value);
79
+ }
80
+ const result = Object.assign({}, v);
81
+ if ("min" in v && v.min != null)
82
+ result.min = transformer.serialize(v.min);
83
+ if ("max" in v && v.max != null)
84
+ result.max = transformer.serialize(v.max);
85
+ if ("not" in v && v.not != null)
86
+ result.not = transformer.serialize(v.not);
87
+ return result;
88
+ }
89
+ return transformer.serialize(value);
90
+ }
91
+ extractFields(transformers) {
47
92
  var _a;
48
93
  const internal = this.options.schema;
49
94
  for (const prop of internal.structure.props) {
50
95
  const meta = prop.value.meta;
51
96
  const expression = String(prop.value.expression);
52
97
  const domain = prop.value.domain;
53
- const tsType = (_a = meta === null || meta === void 0 ? void 0 : meta.type) !== null && _a !== void 0 ? _a : this.inferType(domain !== null && domain !== void 0 ? domain : expression);
54
- if (requiresNested.includes(tsType)) {
55
- this.enableNested = true;
98
+ const transformer = transformers.find((t) => t.match(expression, domain));
99
+ if (transformer) {
100
+ this.fieldTransformers.set(prop.key, transformer);
101
+ this.fields.push({
102
+ name: prop.key,
103
+ type: transformer.storageType,
104
+ optional: prop.kind === "optional",
105
+ facet: meta === null || meta === void 0 ? void 0 : meta.facet,
106
+ sort: meta === null || meta === void 0 ? void 0 : meta.sort,
107
+ index: meta === null || meta === void 0 ? void 0 : meta.index,
108
+ });
109
+ continue;
56
110
  }
111
+ const type = (_a = meta === null || meta === void 0 ? void 0 : meta.type) !== null && _a !== void 0 ? _a : this.inferType(domain !== null && domain !== void 0 ? domain : expression);
57
112
  this.fields.push({
58
113
  name: prop.key,
59
- type: tsType,
114
+ type,
60
115
  optional: prop.kind === "optional",
61
116
  facet: meta === null || meta === void 0 ? void 0 : meta.facet,
62
117
  sort: meta === null || meta === void 0 ? void 0 : meta.sort,
@@ -64,6 +119,19 @@ export class TSense {
64
119
  });
65
120
  }
66
121
  }
122
+ ensureSynced(force) {
123
+ return __awaiter(this, void 0, void 0, function* () {
124
+ if (!force && (this.synced || !this.options.autoSync))
125
+ return;
126
+ yield new TSenseMigrator(this.options.name, this.fields, this.options.defaultSortingField, this.axios).sync();
127
+ this.synced = true;
128
+ });
129
+ }
130
+ sync() {
131
+ return __awaiter(this, void 0, void 0, function* () {
132
+ yield this.ensureSynced(true);
133
+ });
134
+ }
67
135
  buildObjectFilter(key, value) {
68
136
  var _a, _b;
69
137
  if (Array.isArray(value)) {
@@ -88,18 +156,19 @@ export class TSense {
88
156
  buildFilter(filter) {
89
157
  const result = [];
90
158
  for (const entry of Object.entries(filter !== null && filter !== void 0 ? filter : {})) {
91
- const [key, value] = entry;
92
- if (value == null)
159
+ const [key, rawValue] = entry;
160
+ if (rawValue == null)
93
161
  continue;
94
162
  if (key === "OR") {
95
163
  const orFilter = [];
96
- for (const condition of value) {
164
+ for (const condition of rawValue) {
97
165
  const inner = this.buildFilter(condition);
98
166
  orFilter.push(`(${inner.join("||")})`);
99
167
  }
100
168
  result.push(`(${orFilter.join("||")})`);
101
169
  continue;
102
170
  }
171
+ const value = this.serializeFilterValue(key, rawValue);
103
172
  switch (typeof value) {
104
173
  case "string":
105
174
  case "number":
@@ -133,16 +202,15 @@ export class TSense {
133
202
  }
134
203
  create() {
135
204
  return __awaiter(this, void 0, void 0, function* () {
136
- yield axios({
205
+ const enableNested = this.fields.some((f) => f.type === "object" || f.type === "object[]");
206
+ yield this.axios({
137
207
  method: "POST",
138
- baseURL: this.baseURL,
139
208
  url: "/collections",
140
- headers: this.headers,
141
209
  data: {
142
210
  name: this.options.name,
143
211
  fields: this.fields,
144
212
  default_sorting_field: this.options.defaultSortingField,
145
- enable_nested_fields: this.enableNested,
213
+ enable_nested_fields: enableNested,
146
214
  },
147
215
  });
148
216
  return this;
@@ -150,36 +218,32 @@ export class TSense {
150
218
  }
151
219
  drop() {
152
220
  return __awaiter(this, void 0, void 0, function* () {
153
- yield axios({
221
+ yield this.axios({
154
222
  method: "DELETE",
155
- baseURL: this.baseURL,
156
223
  url: `/collections/${this.options.name}`,
157
- headers: this.headers,
158
224
  });
159
225
  });
160
226
  }
161
227
  get(id) {
162
228
  return __awaiter(this, void 0, void 0, function* () {
163
- const { data } = yield axios({
229
+ yield this.ensureSynced();
230
+ const { data } = yield this.axios({
164
231
  method: "GET",
165
- baseURL: this.baseURL,
166
232
  url: `/collections/${this.options.name}/documents/${id}`,
167
- headers: this.headers,
168
233
  }).catch((e) => {
169
234
  if (e.status === 404)
170
235
  return { data: null };
171
236
  throw e;
172
237
  });
173
- return data;
238
+ return data ? this.deserializeDoc(data) : null;
174
239
  });
175
240
  }
176
241
  delete(id) {
177
242
  return __awaiter(this, void 0, void 0, function* () {
178
- const { data } = yield axios({
243
+ yield this.ensureSynced();
244
+ const { data } = yield this.axios({
179
245
  method: "DELETE",
180
- baseURL: this.baseURL,
181
246
  url: `/collections/${this.options.name}/documents/${id}`,
182
- headers: this.headers,
183
247
  }).catch((e) => {
184
248
  if (e.status === 404)
185
249
  return { data: null };
@@ -190,15 +254,14 @@ export class TSense {
190
254
  }
191
255
  deleteMany(filter) {
192
256
  return __awaiter(this, void 0, void 0, function* () {
257
+ yield this.ensureSynced();
193
258
  const filterBy = this.buildFilter(filter).join("&&");
194
259
  if (!filterBy) {
195
260
  throw new Error("FILTER_REQUIRED");
196
261
  }
197
- const { data } = yield axios({
262
+ const { data } = yield this.axios({
198
263
  method: "DELETE",
199
- baseURL: this.baseURL,
200
264
  url: `/collections/${this.options.name}/documents`,
201
- headers: this.headers,
202
265
  params: { filter_by: filterBy },
203
266
  });
204
267
  return { deleted: data.num_deleted };
@@ -206,29 +269,29 @@ export class TSense {
206
269
  }
207
270
  update(id, data) {
208
271
  return __awaiter(this, void 0, void 0, function* () {
209
- const { data: updated } = yield axios({
272
+ yield this.ensureSynced();
273
+ const serialized = this.serializeDoc(data);
274
+ const { data: updated } = yield this.axios({
210
275
  method: "PATCH",
211
- baseURL: this.baseURL,
212
276
  url: `/collections/${this.options.name}/documents/${id}`,
213
- headers: this.headers,
214
- data,
277
+ data: serialized,
215
278
  });
216
- return updated;
279
+ return this.deserializeDoc(updated);
217
280
  });
218
281
  }
219
282
  updateMany(filter, data) {
220
283
  return __awaiter(this, void 0, void 0, function* () {
284
+ yield this.ensureSynced();
221
285
  const filterBy = this.buildFilter(filter).join("&&");
222
286
  if (!filterBy) {
223
287
  throw new Error("FILTER_REQUIRED");
224
288
  }
225
- const { data: result } = yield axios({
289
+ const serialized = this.serializeDoc(data);
290
+ const { data: result } = yield this.axios({
226
291
  method: "PATCH",
227
- baseURL: this.baseURL,
228
292
  url: `/collections/${this.options.name}/documents`,
229
- headers: this.headers,
230
293
  params: { filter_by: filterBy },
231
- data,
294
+ data: serialized,
232
295
  });
233
296
  return { updated: result.num_updated };
234
297
  });
@@ -236,11 +299,10 @@ export class TSense {
236
299
  search(options) {
237
300
  return __awaiter(this, void 0, void 0, function* () {
238
301
  var _a, _b, _c, _d, _e, _f, _g;
302
+ yield this.ensureSynced();
239
303
  const params = {
240
304
  q: (_a = options.query) !== null && _a !== void 0 ? _a : "",
241
- query_by: ((_b = options.queryBy) !== null && _b !== void 0 ? _b : [
242
- this.options.defaultSearchField,
243
- ]).join(","),
305
+ query_by: ((_b = options.queryBy) !== null && _b !== void 0 ? _b : [this.options.defaultSearchField]).join(","),
244
306
  };
245
307
  const sortBy = this.buildSort(options);
246
308
  if (sortBy)
@@ -276,11 +338,9 @@ export class TSense {
276
338
  params.highlight_end_tag = highlightOpts.endTag;
277
339
  }
278
340
  }
279
- const { data: res } = yield axios({
341
+ const { data: res } = yield this.axios({
280
342
  method: "GET",
281
- baseURL: this.baseURL,
282
343
  url: `/collections/${this.options.name}/documents/search`,
283
- headers: this.headers,
284
344
  params,
285
345
  });
286
346
  const data = [];
@@ -296,7 +356,8 @@ export class TSense {
296
356
  hit.document[key] = value.snippet;
297
357
  }
298
358
  }
299
- data.push(hit.document);
359
+ const doc = this.deserializeDoc(hit.document);
360
+ data.push(doc);
300
361
  scores.push((_f = hit.text_match) !== null && _f !== void 0 ? _f : 0);
301
362
  }
302
363
  const facets = {};
@@ -315,8 +376,48 @@ export class TSense {
315
376
  };
316
377
  });
317
378
  }
379
+ searchList(options) {
380
+ return __awaiter(this, void 0, void 0, function* () {
381
+ var _a, _b, _c, _d;
382
+ yield this.ensureSynced();
383
+ const limit = Math.min((_a = options.limit) !== null && _a !== void 0 ? _a : 20, 100);
384
+ const field = options.sort.field;
385
+ const params = {
386
+ q: (_b = options.query) !== null && _b !== void 0 ? _b : "",
387
+ query_by: ((_c = options.queryBy) !== null && _c !== void 0 ? _c : [this.options.defaultSearchField]).join(","),
388
+ per_page: limit,
389
+ sort_by: `${field}:${options.sort.direction}`,
390
+ };
391
+ const filterParts = this.buildFilter(options.filter);
392
+ if (options.cursor) {
393
+ const op = options.sort.direction === "asc" ? ">" : "<";
394
+ filterParts.push(`${field}:${op}${options.cursor}`);
395
+ }
396
+ const filterBy = filterParts.join("&&");
397
+ if (filterBy)
398
+ params.filter_by = filterBy;
399
+ const { data: res } = yield this.axios({
400
+ method: "GET",
401
+ url: `/collections/${this.options.name}/documents/search`,
402
+ params,
403
+ });
404
+ const hits = (_d = res.hits) !== null && _d !== void 0 ? _d : [];
405
+ const data = [];
406
+ for (const hit of hits) {
407
+ const doc = this.deserializeDoc(hit.document);
408
+ data.push(doc);
409
+ }
410
+ if (data.length < limit) {
411
+ return { data, nextCursor: null };
412
+ }
413
+ const lastHit = hits[hits.length - 1].document;
414
+ const nextCursor = String(lastHit[field]);
415
+ return { data, nextCursor };
416
+ });
417
+ }
318
418
  upsert(docs) {
319
419
  return __awaiter(this, void 0, void 0, function* () {
420
+ yield this.ensureSynced();
320
421
  const items = Array.isArray(docs) ? docs : [docs];
321
422
  if (!items.length)
322
423
  return [];
@@ -325,16 +426,15 @@ export class TSense {
325
426
  this.options.schema.assert(item);
326
427
  }
327
428
  }
328
- const payload = items.map((item) => JSON.stringify(item)).join("\n");
429
+ const payload = items.map((item) => JSON.stringify(this.serializeDoc(item))).join("\n");
329
430
  const params = { action: "upsert" };
330
431
  if (this.options.batchSize) {
331
432
  params.batch_size = this.options.batchSize;
332
433
  }
333
- const { data } = yield axios({
434
+ const { data } = yield this.axios({
334
435
  method: "POST",
335
- baseURL: this.baseURL,
336
436
  url: `/collections/${this.options.name}/documents/import`,
337
- headers: Object.assign(Object.assign({}, this.headers), { "Content-Type": "text/plain" }),
437
+ headers: { "Content-Type": "text/plain" },
338
438
  params,
339
439
  data: payload,
340
440
  });
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Type } from "arktype";
2
+ import type { FieldTransformer } from "./transformers/types.js";
2
3
  type BaseIfArray<T> = T extends (infer Q)[] ? Q : T;
3
4
  export type FieldSchema = {
4
5
  name: string;
@@ -41,14 +42,16 @@ export type TsenseOptions<T extends Type> = {
41
42
  defaultSortingField?: keyof T["infer"];
42
43
  batchSize?: number;
43
44
  validateOnUpsert?: boolean;
45
+ autoSync?: boolean;
46
+ transformers?: FieldTransformer[];
44
47
  };
45
48
  type SingleFilter<T> = Partial<{
46
49
  [K in keyof T]: BaseIfArray<T[K]> | NonNullable<BaseIfArray<T[K]>>[] | {
47
50
  not?: BaseIfArray<T[K]>;
48
- } | (NonNullable<T[K]> extends number ? {
49
- min?: number;
50
- max?: number;
51
- } : never);
51
+ } | (NonNullable<T[K]> extends number | Date ? NonNullable<T[K]> extends infer Type ? {
52
+ min?: Type;
53
+ max?: Type;
54
+ } : never : never);
52
55
  }>;
53
56
  export type FilterFor<T> = SingleFilter<T> & {
54
57
  OR?: FilterFor<T>[];
@@ -96,4 +99,20 @@ export type UpsertResult = {
96
99
  error?: string;
97
100
  document?: unknown;
98
101
  };
102
+ type SearchListSort<T> = {
103
+ field: keyof T;
104
+ direction: "asc" | "desc";
105
+ };
106
+ export type SearchListOptions<T> = {
107
+ query?: string;
108
+ queryBy?: (keyof T)[];
109
+ filter?: FilterFor<T>;
110
+ sort: SearchListSort<T>;
111
+ limit?: number;
112
+ cursor?: string;
113
+ };
114
+ export type SearchListResult<T> = {
115
+ data: T[];
116
+ nextCursor: string | null;
117
+ };
99
118
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsense",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "private": false,
5
5
  "description": "Opinionated, fully typed typesense client",
6
6
  "keywords": [
@@ -41,7 +41,9 @@
41
41
  "@biomejs/biome": "2.1.4",
42
42
  "@changesets/cli": "^2.29.7",
43
43
  "@types/bun": "latest",
44
- "arktype": "^2.1.29"
44
+ "arktype": "^2.1.29",
45
+ "oxfmt": "^0.23.0",
46
+ "oxlint": "^1.38.0"
45
47
  },
46
48
  "peerDependencies": {
47
49
  "arktype": "^2.1.29",