tsense 0.0.6 → 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
@@ -40,6 +40,8 @@ const UsersCollection = new TSense({
40
40
  validateOnUpsert: true,
41
41
  });
42
42
 
43
+ type User = typeof UsersCollection.infer;
44
+
43
45
  await UsersCollection.create();
44
46
 
45
47
  await UsersCollection.upsert([
@@ -72,29 +74,6 @@ await UsersCollection.drop();
72
74
 
73
75
  ## API Reference
74
76
 
75
- ### Constructor
76
-
77
- ```typescript
78
- new TSense({
79
- name: string,
80
- schema: Type,
81
- connection: ConnectionConfig,
82
- defaultSearchField?: keyof T,
83
- defaultSortingField?: keyof T,
84
- batchSize?: number,
85
- validateOnUpsert?: boolean,
86
- })
87
- ```
88
-
89
- ### ConnectionConfig
90
-
91
- | Option | Type | Description |
92
- | ---------- | --------------------- | --------------------- |
93
- | `host` | `string` | Typesense server host |
94
- | `port` | `number` | Typesense server port |
95
- | `protocol` | `"http"` \| `"https"` | Connection protocol |
96
- | `apiKey` | `string` | Typesense API key |
97
-
98
77
  ### Schema Configuration
99
78
 
100
79
  Use `.configure()` to set Typesense field options:
@@ -122,22 +101,6 @@ type("string").configure({
122
101
  | `upsert(docs)` | Inserts or updates documents |
123
102
  | `search(options)` | Searches the collection |
124
103
 
125
- ### Search Options
126
-
127
- | Option | Type | Description |
128
- | ----------- | --------------------------------- | ------------------------ |
129
- | `query` | `string` | Text search query |
130
- | `queryBy` | `(keyof T)[]` | Fields to search in |
131
- | `filter` | `FilterFor<T>` | Filter conditions |
132
- | `sortBy` | `"field:asc\|desc"[]` | Sort order |
133
- | `facetBy` | `(keyof T)[]` | Fields to facet by |
134
- | `page` | `number` | Page number |
135
- | `limit` | `number` | Results per page |
136
- | `pick` | `(keyof T)[]` | Only return these fields |
137
- | `omit` | `(keyof T)[]` | Exclude these fields |
138
- | `highlight` | `boolean \| HighlightOptions<T>` | Enable highlighting |
139
-
140
- Note: `pick` and `omit` are mutually exclusive.
141
104
 
142
105
  ### Filter Syntax
143
106
 
@@ -148,4 +111,4 @@ filter: { age: [25, 30, 35] } // IN
148
111
  filter: { age: { min: 20, max: 40 } } // Range
149
112
  filter: { name: { not: "John" } } // Not equal
150
113
  filter: { OR: [{ age: 25 }, { age: 30 }] } // OR conditions
151
- ```
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,14 +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;
66
+ infer: T["infer"];
9
67
  constructor(options: TsenseOptions<T>);
10
68
  private inferType;
69
+ private serializeDoc;
70
+ private deserializeDoc;
71
+ private serializeFilterValue;
11
72
  private extractFields;
73
+ private ensureSynced;
74
+ sync(): Promise<void>;
12
75
  private buildObjectFilter;
13
76
  private buildFilter;
14
77
  private buildSort;
@@ -20,5 +83,7 @@ export declare class TSense<T extends Type> {
20
83
  update(id: string, data: Partial<T["infer"]>): Promise<T["infer"]>;
21
84
  updateMany(filter: FilterFor<T["infer"]>, data: Partial<T["infer"]>): Promise<UpdateResult>;
22
85
  search(options: SearchOptions<T["infer"]>): Promise<SearchResult<T["infer"]>>;
86
+ searchList(options: SearchListOptions<T["infer"]>): Promise<SearchListResult<T["infer"]>>;
23
87
  upsert(docs: T["infer"] | T["infer"][]): Promise<UpsertResult[]>;
24
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,13 +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;
28
- const { connection } = options;
29
- this.baseURL = `${connection.protocol}://${connection.host}:${connection.port}`;
30
- this.headers = { "X-TYPESENSE-API-KEY": connection.apiKey };
31
- this.extractFields();
29
+ this.synced = false;
30
+ this.fieldTransformers = new Map();
31
+ this.infer = undefined;
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);
32
37
  }
33
38
  inferType(arkType) {
34
39
  const direct = arkToTsense[arkType];
@@ -42,20 +47,71 @@ export class TSense {
42
47
  return "string";
43
48
  return "string";
44
49
  }
45
- 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) {
46
92
  var _a;
47
93
  const internal = this.options.schema;
48
94
  for (const prop of internal.structure.props) {
49
95
  const meta = prop.value.meta;
50
96
  const expression = String(prop.value.expression);
51
97
  const domain = prop.value.domain;
52
- 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);
53
- if (requiresNested.includes(tsType)) {
54
- 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;
55
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);
56
112
  this.fields.push({
57
113
  name: prop.key,
58
- type: tsType,
114
+ type,
59
115
  optional: prop.kind === "optional",
60
116
  facet: meta === null || meta === void 0 ? void 0 : meta.facet,
61
117
  sort: meta === null || meta === void 0 ? void 0 : meta.sort,
@@ -63,6 +119,19 @@ export class TSense {
63
119
  });
64
120
  }
65
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
+ }
66
135
  buildObjectFilter(key, value) {
67
136
  var _a, _b;
68
137
  if (Array.isArray(value)) {
@@ -87,18 +156,19 @@ export class TSense {
87
156
  buildFilter(filter) {
88
157
  const result = [];
89
158
  for (const entry of Object.entries(filter !== null && filter !== void 0 ? filter : {})) {
90
- const [key, value] = entry;
91
- if (value == null)
159
+ const [key, rawValue] = entry;
160
+ if (rawValue == null)
92
161
  continue;
93
162
  if (key === "OR") {
94
163
  const orFilter = [];
95
- for (const condition of value) {
164
+ for (const condition of rawValue) {
96
165
  const inner = this.buildFilter(condition);
97
166
  orFilter.push(`(${inner.join("||")})`);
98
167
  }
99
168
  result.push(`(${orFilter.join("||")})`);
100
169
  continue;
101
170
  }
171
+ const value = this.serializeFilterValue(key, rawValue);
102
172
  switch (typeof value) {
103
173
  case "string":
104
174
  case "number":
@@ -132,16 +202,15 @@ export class TSense {
132
202
  }
133
203
  create() {
134
204
  return __awaiter(this, void 0, void 0, function* () {
135
- yield axios({
205
+ const enableNested = this.fields.some((f) => f.type === "object" || f.type === "object[]");
206
+ yield this.axios({
136
207
  method: "POST",
137
- baseURL: this.baseURL,
138
208
  url: "/collections",
139
- headers: this.headers,
140
209
  data: {
141
210
  name: this.options.name,
142
211
  fields: this.fields,
143
212
  default_sorting_field: this.options.defaultSortingField,
144
- enable_nested_fields: this.enableNested,
213
+ enable_nested_fields: enableNested,
145
214
  },
146
215
  });
147
216
  return this;
@@ -149,36 +218,32 @@ export class TSense {
149
218
  }
150
219
  drop() {
151
220
  return __awaiter(this, void 0, void 0, function* () {
152
- yield axios({
221
+ yield this.axios({
153
222
  method: "DELETE",
154
- baseURL: this.baseURL,
155
223
  url: `/collections/${this.options.name}`,
156
- headers: this.headers,
157
224
  });
158
225
  });
159
226
  }
160
227
  get(id) {
161
228
  return __awaiter(this, void 0, void 0, function* () {
162
- const { data } = yield axios({
229
+ yield this.ensureSynced();
230
+ const { data } = yield this.axios({
163
231
  method: "GET",
164
- baseURL: this.baseURL,
165
232
  url: `/collections/${this.options.name}/documents/${id}`,
166
- headers: this.headers,
167
233
  }).catch((e) => {
168
234
  if (e.status === 404)
169
235
  return { data: null };
170
236
  throw e;
171
237
  });
172
- return data;
238
+ return data ? this.deserializeDoc(data) : null;
173
239
  });
174
240
  }
175
241
  delete(id) {
176
242
  return __awaiter(this, void 0, void 0, function* () {
177
- const { data } = yield axios({
243
+ yield this.ensureSynced();
244
+ const { data } = yield this.axios({
178
245
  method: "DELETE",
179
- baseURL: this.baseURL,
180
246
  url: `/collections/${this.options.name}/documents/${id}`,
181
- headers: this.headers,
182
247
  }).catch((e) => {
183
248
  if (e.status === 404)
184
249
  return { data: null };
@@ -189,15 +254,14 @@ export class TSense {
189
254
  }
190
255
  deleteMany(filter) {
191
256
  return __awaiter(this, void 0, void 0, function* () {
257
+ yield this.ensureSynced();
192
258
  const filterBy = this.buildFilter(filter).join("&&");
193
259
  if (!filterBy) {
194
260
  throw new Error("FILTER_REQUIRED");
195
261
  }
196
- const { data } = yield axios({
262
+ const { data } = yield this.axios({
197
263
  method: "DELETE",
198
- baseURL: this.baseURL,
199
264
  url: `/collections/${this.options.name}/documents`,
200
- headers: this.headers,
201
265
  params: { filter_by: filterBy },
202
266
  });
203
267
  return { deleted: data.num_deleted };
@@ -205,29 +269,29 @@ export class TSense {
205
269
  }
206
270
  update(id, data) {
207
271
  return __awaiter(this, void 0, void 0, function* () {
208
- const { data: updated } = yield axios({
272
+ yield this.ensureSynced();
273
+ const serialized = this.serializeDoc(data);
274
+ const { data: updated } = yield this.axios({
209
275
  method: "PATCH",
210
- baseURL: this.baseURL,
211
276
  url: `/collections/${this.options.name}/documents/${id}`,
212
- headers: this.headers,
213
- data,
277
+ data: serialized,
214
278
  });
215
- return updated;
279
+ return this.deserializeDoc(updated);
216
280
  });
217
281
  }
218
282
  updateMany(filter, data) {
219
283
  return __awaiter(this, void 0, void 0, function* () {
284
+ yield this.ensureSynced();
220
285
  const filterBy = this.buildFilter(filter).join("&&");
221
286
  if (!filterBy) {
222
287
  throw new Error("FILTER_REQUIRED");
223
288
  }
224
- const { data: result } = yield axios({
289
+ const serialized = this.serializeDoc(data);
290
+ const { data: result } = yield this.axios({
225
291
  method: "PATCH",
226
- baseURL: this.baseURL,
227
292
  url: `/collections/${this.options.name}/documents`,
228
- headers: this.headers,
229
293
  params: { filter_by: filterBy },
230
- data,
294
+ data: serialized,
231
295
  });
232
296
  return { updated: result.num_updated };
233
297
  });
@@ -235,11 +299,10 @@ export class TSense {
235
299
  search(options) {
236
300
  return __awaiter(this, void 0, void 0, function* () {
237
301
  var _a, _b, _c, _d, _e, _f, _g;
302
+ yield this.ensureSynced();
238
303
  const params = {
239
304
  q: (_a = options.query) !== null && _a !== void 0 ? _a : "",
240
- query_by: ((_b = options.queryBy) !== null && _b !== void 0 ? _b : [
241
- this.options.defaultSearchField,
242
- ]).join(","),
305
+ query_by: ((_b = options.queryBy) !== null && _b !== void 0 ? _b : [this.options.defaultSearchField]).join(","),
243
306
  };
244
307
  const sortBy = this.buildSort(options);
245
308
  if (sortBy)
@@ -275,11 +338,9 @@ export class TSense {
275
338
  params.highlight_end_tag = highlightOpts.endTag;
276
339
  }
277
340
  }
278
- const { data: res } = yield axios({
341
+ const { data: res } = yield this.axios({
279
342
  method: "GET",
280
- baseURL: this.baseURL,
281
343
  url: `/collections/${this.options.name}/documents/search`,
282
- headers: this.headers,
283
344
  params,
284
345
  });
285
346
  const data = [];
@@ -295,7 +356,8 @@ export class TSense {
295
356
  hit.document[key] = value.snippet;
296
357
  }
297
358
  }
298
- data.push(hit.document);
359
+ const doc = this.deserializeDoc(hit.document);
360
+ data.push(doc);
299
361
  scores.push((_f = hit.text_match) !== null && _f !== void 0 ? _f : 0);
300
362
  }
301
363
  const facets = {};
@@ -314,8 +376,48 @@ export class TSense {
314
376
  };
315
377
  });
316
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
+ }
317
418
  upsert(docs) {
318
419
  return __awaiter(this, void 0, void 0, function* () {
420
+ yield this.ensureSynced();
319
421
  const items = Array.isArray(docs) ? docs : [docs];
320
422
  if (!items.length)
321
423
  return [];
@@ -324,16 +426,15 @@ export class TSense {
324
426
  this.options.schema.assert(item);
325
427
  }
326
428
  }
327
- const payload = items.map((item) => JSON.stringify(item)).join("\n");
429
+ const payload = items.map((item) => JSON.stringify(this.serializeDoc(item))).join("\n");
328
430
  const params = { action: "upsert" };
329
431
  if (this.options.batchSize) {
330
432
  params.batch_size = this.options.batchSize;
331
433
  }
332
- const { data } = yield axios({
434
+ const { data } = yield this.axios({
333
435
  method: "POST",
334
- baseURL: this.baseURL,
335
436
  url: `/collections/${this.options.name}/documents/import`,
336
- headers: Object.assign(Object.assign({}, this.headers), { "Content-Type": "text/plain" }),
437
+ headers: { "Content-Type": "text/plain" },
337
438
  params,
338
439
  data: payload,
339
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.6",
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",