tsense 0.0.5 → 0.0.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.
- package/README.md +140 -108
- package/dist/env.d.ts +12 -0
- package/dist/index.d.ts +2 -0
- package/dist/tsense.d.ts +21 -25
- package/dist/tsense.js +266 -135
- package/dist/types.d.ts +99 -0
- package/package.json +28 -26
- package/dist/collection.d.ts +0 -27
- package/dist/collection.js +0 -200
- package/dist/types/core.d.ts +0 -74
- package/dist/types/helpers.d.ts +0 -11
- /package/dist/{types/core.js → env.js} +0 -0
- /package/dist/{types/helpers.js → types.js} +0 -0
package/README.md
CHANGED
|
@@ -1,121 +1,153 @@
|
|
|
1
|
-
|
|
1
|
+
# TSense
|
|
2
2
|
|
|
3
|
-
Opinionated, fully-typed
|
|
3
|
+
Opinionated, fully-typed Typesense client powered by [Arktype](https://arktype.io/)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add tsense arktype
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Example
|
|
11
12
|
|
|
12
|
-
# Example
|
|
13
13
|
```typescript
|
|
14
|
-
import {
|
|
14
|
+
import { type } from "arktype";
|
|
15
15
|
import { TSense } from "tsense";
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
const UsersCollection = new TSense({
|
|
18
|
+
name: "users",
|
|
19
|
+
schema: type({
|
|
20
|
+
"id?": "string",
|
|
21
|
+
email: "string",
|
|
22
|
+
age: type("number.integer").configure({ type: "int32", sort: true }),
|
|
23
|
+
"company?": type.enumerated("netflix", "google").configure({ facet: true }),
|
|
24
|
+
"phone?": "string",
|
|
25
|
+
name: type("string").configure({ sort: true }),
|
|
26
|
+
"work_history?": type({
|
|
27
|
+
company: "string",
|
|
28
|
+
date: "string",
|
|
29
|
+
})
|
|
30
|
+
.array()
|
|
31
|
+
.configure({ type: "object[]", index: false }),
|
|
32
|
+
}),
|
|
33
|
+
connection: {
|
|
34
|
+
host: "127.0.0.1",
|
|
35
|
+
port: 8108,
|
|
36
|
+
protocol: "http",
|
|
37
|
+
apiKey: "123",
|
|
38
|
+
},
|
|
39
|
+
defaultSearchField: "name",
|
|
40
|
+
validateOnUpsert: true,
|
|
27
41
|
});
|
|
28
42
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
},
|
|
47
|
-
work_history: {
|
|
48
|
-
// object and object[] auto-infers enable_nested_fields
|
|
49
|
-
type: "object[]",
|
|
50
|
-
index: false,
|
|
51
|
-
optional: true,
|
|
52
|
-
override: {} as {
|
|
53
|
-
company: string;
|
|
54
|
-
date: string;
|
|
55
|
-
}[],
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
default_search_field: "name",
|
|
43
|
+
type User = typeof UsersCollection.infer;
|
|
44
|
+
|
|
45
|
+
await UsersCollection.create();
|
|
46
|
+
|
|
47
|
+
await UsersCollection.upsert([
|
|
48
|
+
{ id: "1", email: "john@example.com", age: 30, name: "John Doe", company: "netflix" },
|
|
49
|
+
{ id: "2", email: "jane@example.com", age: 25, name: "Jane Smith", company: "google" },
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
const results = await UsersCollection.search({
|
|
53
|
+
query: "john",
|
|
54
|
+
queryBy: ["name", "email"],
|
|
55
|
+
sortBy: ["age:desc", "name:asc"],
|
|
56
|
+
filter: {
|
|
57
|
+
age: { min: 20 },
|
|
58
|
+
OR: [{ company: "google" }, { company: "netflix" }],
|
|
59
|
+
},
|
|
59
60
|
});
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
{
|
|
65
|
-
id?: string | undefined;
|
|
66
|
-
phone?: string | null | undefined;
|
|
67
|
-
work_history?: {
|
|
68
|
-
company: string;
|
|
69
|
-
date: string;
|
|
70
|
-
}[] | null | undefined;
|
|
71
|
-
email: string;
|
|
72
|
-
age: number;
|
|
73
|
-
name: string;
|
|
74
|
-
}
|
|
75
|
-
*/
|
|
76
|
-
|
|
77
|
-
const results = await UsersCollection.searchDocuments({
|
|
78
|
-
search: "john",
|
|
79
|
-
search_keys: ["name"],
|
|
80
|
-
// can sort multiple fields
|
|
81
|
-
order_by: ["age desc", "name asc"],
|
|
82
|
-
// compiles into
|
|
83
|
-
// age:>=20&&((email:=@google.com)||(email:=@netflix.com))
|
|
84
|
-
filter: {
|
|
85
|
-
// min and max range on numbers
|
|
86
|
-
age: {
|
|
87
|
-
min: 20,
|
|
88
|
-
},
|
|
89
|
-
// OR syntax similar to prisma
|
|
90
|
-
OR: [
|
|
91
|
-
{
|
|
92
|
-
email: "@google.com",
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
email: "@netflix.com",
|
|
96
|
-
},
|
|
97
|
-
],
|
|
98
|
-
},
|
|
62
|
+
const faceted = await UsersCollection.search({
|
|
63
|
+
query: "john",
|
|
64
|
+
facetBy: ["company"],
|
|
99
65
|
});
|
|
100
66
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
data: {
|
|
105
|
-
id?: string | undefined;
|
|
106
|
-
phone?: string | null | undefined;
|
|
107
|
-
...
|
|
108
|
-
}[];
|
|
109
|
-
facet: {
|
|
110
|
-
netflix: number;
|
|
111
|
-
google: number;
|
|
112
|
-
// enabled by enable_facet_total
|
|
113
|
-
total: number;
|
|
114
|
-
};
|
|
115
|
-
*/
|
|
116
|
-
const faceted = await UsersCollection.searchDocuments({
|
|
117
|
-
search: "john",
|
|
118
|
-
facet_by: "company",
|
|
119
|
-
enable_facet_total: true,
|
|
67
|
+
const highlighted = await UsersCollection.search({
|
|
68
|
+
query: "john",
|
|
69
|
+
highlight: true,
|
|
120
70
|
});
|
|
121
|
-
|
|
71
|
+
|
|
72
|
+
await UsersCollection.drop();
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## API Reference
|
|
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
|
+
### Schema Configuration
|
|
101
|
+
|
|
102
|
+
Use `.configure()` to set Typesense field options:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
type("string").configure({
|
|
106
|
+
type: "string",
|
|
107
|
+
facet: true,
|
|
108
|
+
sort: true,
|
|
109
|
+
index: true,
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Collection Methods
|
|
114
|
+
|
|
115
|
+
| Method | Description |
|
|
116
|
+
| ------ | ----------- |
|
|
117
|
+
| `create()` | Creates the collection in Typesense |
|
|
118
|
+
| `drop()` | Deletes the collection |
|
|
119
|
+
| `get(id)` | Retrieves a document by ID |
|
|
120
|
+
| `delete(id)` | Deletes a document by ID |
|
|
121
|
+
| `deleteMany(filter)` | Deletes documents matching filter |
|
|
122
|
+
| `update(id, data)` | Updates a document by ID |
|
|
123
|
+
| `updateMany(filter, data)` | Updates documents matching filter |
|
|
124
|
+
| `upsert(docs)` | Inserts or updates documents |
|
|
125
|
+
| `search(options)` | Searches the collection |
|
|
126
|
+
|
|
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
|
+
|
|
144
|
+
### Filter Syntax
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
filter: { name: "John" } // Exact match
|
|
148
|
+
filter: { age: 30 } // Numeric match
|
|
149
|
+
filter: { age: [25, 30, 35] } // IN
|
|
150
|
+
filter: { age: { min: 20, max: 40 } } // Range
|
|
151
|
+
filter: { name: { not: "John" } } // Not equal
|
|
152
|
+
filter: { OR: [{ age: 25 }, { age: 30 }] } // OR conditions
|
|
153
|
+
```
|
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type TsenseFieldType = "string" | "int32" | "int64" | "float" | "bool" | "image" | "string[]" | "int32[]" | "int64[]" | "float[]" | "bool[]" | "geopoint" | "geopoint[]" | "object" | "object[]" | "auto" | "string*";
|
|
2
|
+
export type TsenseFieldMeta = {
|
|
3
|
+
type?: TsenseFieldType;
|
|
4
|
+
facet?: boolean;
|
|
5
|
+
sort?: boolean;
|
|
6
|
+
index?: boolean;
|
|
7
|
+
};
|
|
8
|
+
declare global {
|
|
9
|
+
interface ArkEnv {
|
|
10
|
+
meta(): TsenseFieldMeta;
|
|
11
|
+
}
|
|
12
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/tsense.d.ts
CHANGED
|
@@ -1,29 +1,25 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
private
|
|
6
|
-
private
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
batch_size?: number;
|
|
14
|
-
enable_nested_fields?: boolean;
|
|
15
|
-
});
|
|
16
|
-
private checkNested;
|
|
1
|
+
import type { Type } from "arktype";
|
|
2
|
+
import type { DeleteResult, FilterFor, SearchOptions, SearchResult, TsenseOptions, UpdateResult, UpsertResult } from "./types.js";
|
|
3
|
+
export declare class TSense<T extends Type> {
|
|
4
|
+
private options;
|
|
5
|
+
private fields;
|
|
6
|
+
private enableNested;
|
|
7
|
+
private baseURL;
|
|
8
|
+
private headers;
|
|
9
|
+
infer: T["infer"];
|
|
10
|
+
constructor(options: TsenseOptions<T>);
|
|
11
|
+
private inferType;
|
|
12
|
+
private extractFields;
|
|
17
13
|
private buildObjectFilter;
|
|
18
|
-
private buildSort;
|
|
19
14
|
private buildFilter;
|
|
20
|
-
private
|
|
21
|
-
delete(): Promise<void>;
|
|
15
|
+
private buildSort;
|
|
22
16
|
create(): Promise<this>;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
drop(): Promise<void>;
|
|
18
|
+
get(id: string): Promise<T["infer"] | null>;
|
|
19
|
+
delete(id: string): Promise<boolean>;
|
|
20
|
+
deleteMany(filter: FilterFor<T["infer"]>): Promise<DeleteResult>;
|
|
21
|
+
update(id: string, data: Partial<T["infer"]>): Promise<T["infer"]>;
|
|
22
|
+
updateMany(filter: FilterFor<T["infer"]>, data: Partial<T["infer"]>): Promise<UpdateResult>;
|
|
23
|
+
search(options: SearchOptions<T["infer"]>): Promise<SearchResult<T["infer"]>>;
|
|
24
|
+
upsert(docs: T["infer"] | T["infer"][]): Promise<UpsertResult[]>;
|
|
29
25
|
}
|
package/dist/tsense.js
CHANGED
|
@@ -7,32 +7,74 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
+
var _a;
|
|
11
|
+
import redaxios from "redaxios";
|
|
12
|
+
const axios = (_a = redaxios.default) !== null && _a !== void 0 ? _a : redaxios;
|
|
10
13
|
const requiresNested = ["object", "object[]"];
|
|
14
|
+
const arkToTsense = {
|
|
15
|
+
string: "string",
|
|
16
|
+
number: "float",
|
|
17
|
+
"number.integer": "int64",
|
|
18
|
+
boolean: "bool",
|
|
19
|
+
"string[]": "string[]",
|
|
20
|
+
"number[]": "float[]",
|
|
21
|
+
"boolean[]": "bool[]",
|
|
22
|
+
};
|
|
11
23
|
export class TSense {
|
|
12
|
-
constructor(
|
|
13
|
-
this.
|
|
14
|
-
this.
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this.options = options;
|
|
26
|
+
this.fields = [];
|
|
27
|
+
this.enableNested = false;
|
|
15
28
|
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();
|
|
16
33
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return;
|
|
34
|
+
inferType(arkType) {
|
|
35
|
+
const direct = arkToTsense[arkType];
|
|
36
|
+
if (direct)
|
|
37
|
+
return direct;
|
|
38
|
+
if (arkType.includes("[]"))
|
|
39
|
+
return "object[]";
|
|
40
|
+
if (arkType.includes("{") || arkType.includes("|"))
|
|
41
|
+
return "object";
|
|
42
|
+
if (arkType.startsWith("'") || arkType.includes("'"))
|
|
43
|
+
return "string";
|
|
44
|
+
return "string";
|
|
45
|
+
}
|
|
46
|
+
extractFields() {
|
|
47
|
+
var _a;
|
|
48
|
+
const internal = this.options.schema;
|
|
49
|
+
for (const prop of internal.structure.props) {
|
|
50
|
+
const meta = prop.value.meta;
|
|
51
|
+
const expression = String(prop.value.expression);
|
|
52
|
+
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;
|
|
56
|
+
}
|
|
57
|
+
this.fields.push({
|
|
58
|
+
name: prop.key,
|
|
59
|
+
type: tsType,
|
|
60
|
+
optional: prop.kind === "optional",
|
|
61
|
+
facet: meta === null || meta === void 0 ? void 0 : meta.facet,
|
|
62
|
+
sort: meta === null || meta === void 0 ? void 0 : meta.sort,
|
|
63
|
+
index: meta === null || meta === void 0 ? void 0 : meta.index,
|
|
64
|
+
});
|
|
25
65
|
}
|
|
26
66
|
}
|
|
27
67
|
buildObjectFilter(key, value) {
|
|
68
|
+
var _a, _b;
|
|
28
69
|
if (Array.isArray(value)) {
|
|
29
70
|
return `(${key}:[${value.join(",")}])`;
|
|
30
71
|
}
|
|
31
|
-
|
|
32
|
-
|
|
72
|
+
const v = value;
|
|
73
|
+
if ("not" in v) {
|
|
74
|
+
return `${key}:!=${v.not}`;
|
|
33
75
|
}
|
|
34
|
-
const min =
|
|
35
|
-
const max =
|
|
76
|
+
const min = (_a = v.min) !== null && _a !== void 0 ? _a : undefined;
|
|
77
|
+
const max = (_b = v.max) !== null && _b !== void 0 ? _b : undefined;
|
|
36
78
|
if (min != null && max != null) {
|
|
37
79
|
return `${key}:[${min}..${max}]`;
|
|
38
80
|
}
|
|
@@ -43,174 +85,263 @@ export class TSense {
|
|
|
43
85
|
return `${key}:>=${min}`;
|
|
44
86
|
}
|
|
45
87
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
const order = [];
|
|
52
|
-
for (const key of this.maybeArray(data.order_by)) {
|
|
53
|
-
const splitted = key.split(" ");
|
|
54
|
-
let direction = splitted.at(-1);
|
|
55
|
-
if (direction !== "asc" && direction !== "desc") {
|
|
56
|
-
direction = (_a = data.direction) !== null && _a !== void 0 ? _a : "desc";
|
|
57
|
-
}
|
|
58
|
-
// safeguard
|
|
59
|
-
if (splitted[0] === "undefined") {
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
if (splitted[0] === "score") {
|
|
63
|
-
splitted[0] = "_text_match";
|
|
64
|
-
}
|
|
65
|
-
order.push(`${splitted[0]}:${direction}`);
|
|
66
|
-
}
|
|
67
|
-
return order.join(",");
|
|
68
|
-
}
|
|
69
|
-
buildFilter(data) {
|
|
70
|
-
var _a;
|
|
71
|
-
const filter = [];
|
|
72
|
-
for (const entry of Object.entries((_a = data.filter) !== null && _a !== void 0 ? _a : {})) {
|
|
88
|
+
buildFilter(filter) {
|
|
89
|
+
const result = [];
|
|
90
|
+
for (const entry of Object.entries(filter !== null && filter !== void 0 ? filter : {})) {
|
|
73
91
|
const [key, value] = entry;
|
|
74
92
|
if (value == null)
|
|
75
93
|
continue;
|
|
76
94
|
if (key === "OR") {
|
|
77
95
|
const orFilter = [];
|
|
78
96
|
for (const condition of value) {
|
|
79
|
-
const
|
|
80
|
-
orFilter.push(`(${
|
|
97
|
+
const inner = this.buildFilter(condition);
|
|
98
|
+
orFilter.push(`(${inner.join("||")})`);
|
|
81
99
|
}
|
|
82
|
-
|
|
100
|
+
result.push(`(${orFilter.join("||")})`);
|
|
83
101
|
continue;
|
|
84
102
|
}
|
|
85
103
|
switch (typeof value) {
|
|
86
104
|
case "string":
|
|
87
105
|
case "number":
|
|
88
106
|
case "boolean":
|
|
89
|
-
|
|
107
|
+
result.push(`${key}:=${value}`);
|
|
90
108
|
break;
|
|
91
109
|
case "object": {
|
|
92
110
|
const built = this.buildObjectFilter(key, value);
|
|
93
|
-
if (built)
|
|
94
|
-
|
|
95
|
-
}
|
|
111
|
+
if (built)
|
|
112
|
+
result.push(built);
|
|
96
113
|
break;
|
|
97
114
|
}
|
|
98
|
-
default:
|
|
115
|
+
default:
|
|
99
116
|
break;
|
|
100
|
-
}
|
|
101
117
|
}
|
|
102
118
|
}
|
|
103
|
-
return
|
|
119
|
+
return result;
|
|
104
120
|
}
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
return
|
|
121
|
+
buildSort(options) {
|
|
122
|
+
if (!options.sortBy)
|
|
123
|
+
return;
|
|
124
|
+
const result = [];
|
|
125
|
+
for (const item of options.sortBy) {
|
|
126
|
+
const [field, direction] = item.split(":");
|
|
127
|
+
if (field === "undefined")
|
|
128
|
+
continue;
|
|
129
|
+
const realField = field === "score" ? "_text_match" : field;
|
|
130
|
+
result.push(`${realField}:${direction}`);
|
|
108
131
|
}
|
|
109
|
-
return
|
|
132
|
+
return result.join(",");
|
|
110
133
|
}
|
|
111
|
-
|
|
134
|
+
create() {
|
|
112
135
|
return __awaiter(this, void 0, void 0, function* () {
|
|
113
|
-
yield
|
|
136
|
+
yield axios({
|
|
137
|
+
method: "POST",
|
|
138
|
+
baseURL: this.baseURL,
|
|
139
|
+
url: "/collections",
|
|
140
|
+
headers: this.headers,
|
|
141
|
+
data: {
|
|
142
|
+
name: this.options.name,
|
|
143
|
+
fields: this.fields,
|
|
144
|
+
default_sorting_field: this.options.defaultSortingField,
|
|
145
|
+
enable_nested_fields: this.enableNested,
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
return this;
|
|
114
149
|
});
|
|
115
150
|
}
|
|
116
|
-
|
|
151
|
+
drop() {
|
|
117
152
|
return __awaiter(this, void 0, void 0, function* () {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
153
|
+
yield axios({
|
|
154
|
+
method: "DELETE",
|
|
155
|
+
baseURL: this.baseURL,
|
|
156
|
+
url: `/collections/${this.options.name}`,
|
|
157
|
+
headers: this.headers,
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
get(id) {
|
|
162
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
163
|
+
const { data } = yield axios({
|
|
164
|
+
method: "GET",
|
|
165
|
+
baseURL: this.baseURL,
|
|
166
|
+
url: `/collections/${this.options.name}/documents/${id}`,
|
|
167
|
+
headers: this.headers,
|
|
168
|
+
}).catch((e) => {
|
|
169
|
+
if (e.status === 404)
|
|
170
|
+
return { data: null };
|
|
171
|
+
throw e;
|
|
172
|
+
});
|
|
173
|
+
return data;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
delete(id) {
|
|
177
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
178
|
+
const { data } = yield axios({
|
|
179
|
+
method: "DELETE",
|
|
180
|
+
baseURL: this.baseURL,
|
|
181
|
+
url: `/collections/${this.options.name}/documents/${id}`,
|
|
182
|
+
headers: this.headers,
|
|
183
|
+
}).catch((e) => {
|
|
184
|
+
if (e.status === 404)
|
|
185
|
+
return { data: null };
|
|
186
|
+
throw e;
|
|
187
|
+
});
|
|
188
|
+
return data != null;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
deleteMany(filter) {
|
|
192
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
193
|
+
const filterBy = this.buildFilter(filter).join("&&");
|
|
194
|
+
if (!filterBy) {
|
|
195
|
+
throw new Error("FILTER_REQUIRED");
|
|
139
196
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
197
|
+
const { data } = yield axios({
|
|
198
|
+
method: "DELETE",
|
|
199
|
+
baseURL: this.baseURL,
|
|
200
|
+
url: `/collections/${this.options.name}/documents`,
|
|
201
|
+
headers: this.headers,
|
|
202
|
+
params: { filter_by: filterBy },
|
|
146
203
|
});
|
|
147
|
-
return
|
|
204
|
+
return { deleted: data.num_deleted };
|
|
148
205
|
});
|
|
149
206
|
}
|
|
150
|
-
|
|
207
|
+
update(id, data) {
|
|
151
208
|
return __awaiter(this, void 0, void 0, function* () {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
.documents
|
|
156
|
-
.
|
|
157
|
-
|
|
158
|
-
query_by: (_b = data.search_keys) !== null && _b !== void 0 ? _b : [this.data.default_search_field],
|
|
159
|
-
sort_by: this.buildSort(data),
|
|
160
|
-
filter_by: this.buildFilter(data).join("&&"),
|
|
161
|
-
page: data.page,
|
|
162
|
-
limit: data.limit,
|
|
163
|
-
facet_by: data === null || data === void 0 ? void 0 : data.facet_by,
|
|
209
|
+
const { data: updated } = yield axios({
|
|
210
|
+
method: "PATCH",
|
|
211
|
+
baseURL: this.baseURL,
|
|
212
|
+
url: `/collections/${this.options.name}/documents/${id}`,
|
|
213
|
+
headers: this.headers,
|
|
214
|
+
data,
|
|
164
215
|
});
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
216
|
+
return updated;
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
updateMany(filter, data) {
|
|
220
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
221
|
+
const filterBy = this.buildFilter(filter).join("&&");
|
|
222
|
+
if (!filterBy) {
|
|
223
|
+
throw new Error("FILTER_REQUIRED");
|
|
224
|
+
}
|
|
225
|
+
const { data: result } = yield axios({
|
|
226
|
+
method: "PATCH",
|
|
227
|
+
baseURL: this.baseURL,
|
|
228
|
+
url: `/collections/${this.options.name}/documents`,
|
|
229
|
+
headers: this.headers,
|
|
230
|
+
params: { filter_by: filterBy },
|
|
231
|
+
data,
|
|
232
|
+
});
|
|
233
|
+
return { updated: result.num_updated };
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
search(options) {
|
|
237
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
238
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
239
|
+
const params = {
|
|
240
|
+
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(","),
|
|
244
|
+
};
|
|
245
|
+
const sortBy = this.buildSort(options);
|
|
246
|
+
if (sortBy)
|
|
247
|
+
params.sort_by = sortBy;
|
|
248
|
+
const filterBy = this.buildFilter(options.filter).join("&&");
|
|
249
|
+
if (filterBy)
|
|
250
|
+
params.filter_by = filterBy;
|
|
251
|
+
if (options.page != null)
|
|
252
|
+
params.page = options.page;
|
|
253
|
+
if (options.limit != null)
|
|
254
|
+
params.per_page = options.limit;
|
|
255
|
+
const facetBy = (_c = options.facetBy) === null || _c === void 0 ? void 0 : _c.join(",");
|
|
256
|
+
if (facetBy)
|
|
257
|
+
params.facet_by = facetBy;
|
|
258
|
+
if ("pick" in options && options.pick) {
|
|
259
|
+
params.include_fields = options.pick.join(",");
|
|
260
|
+
}
|
|
261
|
+
if ("omit" in options && options.omit) {
|
|
262
|
+
params.exclude_fields = options.omit.join(",");
|
|
263
|
+
}
|
|
264
|
+
const highlight = options.highlight;
|
|
265
|
+
const highlightEnabled = !!highlight;
|
|
266
|
+
let highlightOpts;
|
|
267
|
+
if (typeof highlight === "object") {
|
|
268
|
+
highlightOpts = highlight;
|
|
269
|
+
if (highlightOpts.fields) {
|
|
270
|
+
params.highlight_fields = highlightOpts.fields.join(",");
|
|
271
|
+
}
|
|
272
|
+
if (highlightOpts.startTag) {
|
|
273
|
+
params.highlight_start_tag = highlightOpts.startTag;
|
|
274
|
+
}
|
|
275
|
+
if (highlightOpts.endTag) {
|
|
276
|
+
params.highlight_end_tag = highlightOpts.endTag;
|
|
176
277
|
}
|
|
177
278
|
}
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
279
|
+
const { data: res } = yield axios({
|
|
280
|
+
method: "GET",
|
|
281
|
+
baseURL: this.baseURL,
|
|
282
|
+
url: `/collections/${this.options.name}/documents/search`,
|
|
283
|
+
headers: this.headers,
|
|
284
|
+
params,
|
|
285
|
+
});
|
|
286
|
+
const data = [];
|
|
287
|
+
const scores = [];
|
|
288
|
+
for (const hit of (_d = res.hits) !== null && _d !== void 0 ? _d : []) {
|
|
289
|
+
if (highlightEnabled) {
|
|
290
|
+
const fieldsToHighlight = highlightOpts === null || highlightOpts === void 0 ? void 0 : highlightOpts.fields;
|
|
291
|
+
for (const [key, value] of Object.entries((_e = hit.highlight) !== null && _e !== void 0 ? _e : {})) {
|
|
292
|
+
if (!(value === null || value === void 0 ? void 0 : value.snippet))
|
|
293
|
+
continue;
|
|
294
|
+
if (fieldsToHighlight && !fieldsToHighlight.includes(key))
|
|
295
|
+
continue;
|
|
296
|
+
hit.document[key] = value.snippet;
|
|
186
297
|
}
|
|
187
298
|
}
|
|
188
|
-
|
|
299
|
+
data.push(hit.document);
|
|
300
|
+
scores.push((_f = hit.text_match) !== null && _f !== void 0 ? _f : 0);
|
|
301
|
+
}
|
|
302
|
+
const facets = {};
|
|
303
|
+
for (const facetCount of (_g = res.facet_counts) !== null && _g !== void 0 ? _g : []) {
|
|
304
|
+
const fieldName = facetCount.field_name;
|
|
305
|
+
facets[fieldName] = {};
|
|
306
|
+
for (const item of facetCount.counts) {
|
|
307
|
+
facets[fieldName][item.value] = item.count;
|
|
308
|
+
}
|
|
189
309
|
}
|
|
190
310
|
return {
|
|
191
|
-
data: result,
|
|
192
311
|
count: res.found,
|
|
193
|
-
|
|
312
|
+
data,
|
|
313
|
+
facets,
|
|
314
|
+
scores,
|
|
194
315
|
};
|
|
195
316
|
});
|
|
196
317
|
}
|
|
197
|
-
|
|
318
|
+
upsert(docs) {
|
|
198
319
|
return __awaiter(this, void 0, void 0, function* () {
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
320
|
+
const items = Array.isArray(docs) ? docs : [docs];
|
|
321
|
+
if (!items.length)
|
|
322
|
+
return [];
|
|
323
|
+
if (this.options.validateOnUpsert) {
|
|
324
|
+
for (const item of items) {
|
|
325
|
+
this.options.schema.assert(item);
|
|
326
|
+
}
|
|
202
327
|
}
|
|
203
|
-
|
|
204
|
-
|
|
328
|
+
const payload = items.map((item) => JSON.stringify(item)).join("\n");
|
|
329
|
+
const params = { action: "upsert" };
|
|
330
|
+
if (this.options.batchSize) {
|
|
331
|
+
params.batch_size = this.options.batchSize;
|
|
205
332
|
}
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
.
|
|
209
|
-
.import
|
|
210
|
-
|
|
211
|
-
|
|
333
|
+
const { data } = yield axios({
|
|
334
|
+
method: "POST",
|
|
335
|
+
baseURL: this.baseURL,
|
|
336
|
+
url: `/collections/${this.options.name}/documents/import`,
|
|
337
|
+
headers: Object.assign(Object.assign({}, this.headers), { "Content-Type": "text/plain" }),
|
|
338
|
+
params,
|
|
339
|
+
data: payload,
|
|
212
340
|
});
|
|
213
|
-
|
|
341
|
+
if (typeof data === "string") {
|
|
342
|
+
return data.split("\n").map((v) => JSON.parse(v));
|
|
343
|
+
}
|
|
344
|
+
return [data];
|
|
214
345
|
});
|
|
215
346
|
}
|
|
216
347
|
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { Type } from "arktype";
|
|
2
|
+
type BaseIfArray<T> = T extends (infer Q)[] ? Q : T;
|
|
3
|
+
export type FieldSchema = {
|
|
4
|
+
name: string;
|
|
5
|
+
type: string;
|
|
6
|
+
facet?: boolean;
|
|
7
|
+
sort?: boolean;
|
|
8
|
+
index?: boolean;
|
|
9
|
+
optional?: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type SearchHit<T> = {
|
|
12
|
+
document: T;
|
|
13
|
+
highlight?: Record<string, {
|
|
14
|
+
snippet?: string;
|
|
15
|
+
}>;
|
|
16
|
+
text_match?: number;
|
|
17
|
+
};
|
|
18
|
+
export type SearchApiResponse<T> = {
|
|
19
|
+
found: number;
|
|
20
|
+
hits?: SearchHit<T>[];
|
|
21
|
+
facet_counts?: {
|
|
22
|
+
field_name: string;
|
|
23
|
+
counts: {
|
|
24
|
+
value: string;
|
|
25
|
+
count: number;
|
|
26
|
+
}[];
|
|
27
|
+
}[];
|
|
28
|
+
};
|
|
29
|
+
export type ConnectionConfig = {
|
|
30
|
+
host: string;
|
|
31
|
+
port: number;
|
|
32
|
+
protocol: "http" | "https";
|
|
33
|
+
apiKey: string;
|
|
34
|
+
timeout?: number;
|
|
35
|
+
};
|
|
36
|
+
export type TsenseOptions<T extends Type> = {
|
|
37
|
+
name: string;
|
|
38
|
+
schema: T;
|
|
39
|
+
connection: ConnectionConfig;
|
|
40
|
+
defaultSearchField?: keyof T["infer"];
|
|
41
|
+
defaultSortingField?: keyof T["infer"];
|
|
42
|
+
batchSize?: number;
|
|
43
|
+
validateOnUpsert?: boolean;
|
|
44
|
+
};
|
|
45
|
+
type SingleFilter<T> = Partial<{
|
|
46
|
+
[K in keyof T]: BaseIfArray<T[K]> | NonNullable<BaseIfArray<T[K]>>[] | {
|
|
47
|
+
not?: BaseIfArray<T[K]>;
|
|
48
|
+
} | (NonNullable<T[K]> extends number ? {
|
|
49
|
+
min?: number;
|
|
50
|
+
max?: number;
|
|
51
|
+
} : never);
|
|
52
|
+
}>;
|
|
53
|
+
export type FilterFor<T> = SingleFilter<T> & {
|
|
54
|
+
OR?: FilterFor<T>[];
|
|
55
|
+
};
|
|
56
|
+
export type HighlightOptions<T> = {
|
|
57
|
+
fields?: (keyof T)[];
|
|
58
|
+
startTag?: string;
|
|
59
|
+
endTag?: string;
|
|
60
|
+
};
|
|
61
|
+
type SortableField<T> = Extract<keyof T, string> | "score";
|
|
62
|
+
type BaseSearchOptions<T> = {
|
|
63
|
+
query?: string;
|
|
64
|
+
queryBy?: (keyof T)[];
|
|
65
|
+
filter?: FilterFor<T>;
|
|
66
|
+
sortBy?: `${SortableField<T>}:${"asc" | "desc"}`[];
|
|
67
|
+
facetBy?: (keyof T)[];
|
|
68
|
+
page?: number;
|
|
69
|
+
limit?: number;
|
|
70
|
+
highlight?: boolean | HighlightOptions<T>;
|
|
71
|
+
};
|
|
72
|
+
export type SearchOptions<T> = BaseSearchOptions<T> & ({
|
|
73
|
+
pick?: (keyof T)[];
|
|
74
|
+
omit?: never;
|
|
75
|
+
} | {
|
|
76
|
+
omit?: (keyof T)[];
|
|
77
|
+
pick?: never;
|
|
78
|
+
} | {
|
|
79
|
+
pick?: never;
|
|
80
|
+
omit?: never;
|
|
81
|
+
});
|
|
82
|
+
export type SearchResult<T> = {
|
|
83
|
+
count: number;
|
|
84
|
+
data: T[];
|
|
85
|
+
facets: Record<string, Record<string, number>>;
|
|
86
|
+
scores: number[];
|
|
87
|
+
};
|
|
88
|
+
export type DeleteResult = {
|
|
89
|
+
deleted: number;
|
|
90
|
+
};
|
|
91
|
+
export type UpdateResult = {
|
|
92
|
+
updated: number;
|
|
93
|
+
};
|
|
94
|
+
export type UpsertResult = {
|
|
95
|
+
success: boolean;
|
|
96
|
+
error?: string;
|
|
97
|
+
document?: unknown;
|
|
98
|
+
};
|
|
99
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tsense",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Opinionated, fully typed typesense client",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"typesense"
|
|
8
|
+
],
|
|
9
|
+
"homepage": "https://github.com/lobomfz/tsense",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"sideEffects": false,
|
|
4
16
|
"main": "dist/index.js",
|
|
5
17
|
"types": "dist/index.d.ts",
|
|
6
18
|
"exports": {
|
|
@@ -9,33 +21,9 @@
|
|
|
9
21
|
"import": "./dist/index.js"
|
|
10
22
|
}
|
|
11
23
|
},
|
|
12
|
-
"files": [
|
|
13
|
-
"dist"
|
|
14
|
-
],
|
|
15
|
-
"sideEffects": false,
|
|
16
|
-
"description": "Opinionated, fully typed typesense client",
|
|
17
|
-
"license": "MIT",
|
|
18
|
-
"keywords": [
|
|
19
|
-
"typesense"
|
|
20
|
-
],
|
|
21
24
|
"publishConfig": {
|
|
22
25
|
"access": "public"
|
|
23
26
|
},
|
|
24
|
-
"homepage": "https://github.com/lobomfz/tsense",
|
|
25
|
-
"type": "module",
|
|
26
|
-
"private": false,
|
|
27
|
-
"devDependencies": {
|
|
28
|
-
"@arethetypeswrong/cli": "^0.18.2",
|
|
29
|
-
"@biomejs/biome": "2.1.4",
|
|
30
|
-
"@changesets/cli": "^2.29.7",
|
|
31
|
-
"@types/bun": "latest"
|
|
32
|
-
},
|
|
33
|
-
"peerDependencies": {
|
|
34
|
-
"typescript": "^5"
|
|
35
|
-
},
|
|
36
|
-
"dependencies": {
|
|
37
|
-
"typesense": "^2.1.0"
|
|
38
|
-
},
|
|
39
27
|
"scripts": {
|
|
40
28
|
"build": "tsc",
|
|
41
29
|
"ci": "bun run build && bun run format && bun run check-exports",
|
|
@@ -44,5 +32,19 @@
|
|
|
44
32
|
"check-exports": "attw --pack . --ignore-rules=cjs-resolves-to-esm",
|
|
45
33
|
"local-release": "changeset version && changeset publish",
|
|
46
34
|
"prepublishOnly": "bun run ci"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"redaxios": "^0.5.1"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@arethetypeswrong/cli": "^0.18.2",
|
|
41
|
+
"@biomejs/biome": "2.1.4",
|
|
42
|
+
"@changesets/cli": "^2.29.7",
|
|
43
|
+
"@types/bun": "latest",
|
|
44
|
+
"arktype": "^2.1.29"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"arktype": "^2.1.29",
|
|
48
|
+
"typescript": "^5"
|
|
47
49
|
}
|
|
48
|
-
}
|
|
50
|
+
}
|
package/dist/collection.d.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { Client } from "typesense";
|
|
2
|
-
import type { CustomCollectionField, Filters, InferCollectionTypes } from "./types/core.js";
|
|
3
|
-
export declare class TSense<Options extends CustomCollectionField, Fields extends Record<string, Options>, Inferred = InferCollectionTypes<Fields>> {
|
|
4
|
-
private name;
|
|
5
|
-
private data;
|
|
6
|
-
infer: Inferred;
|
|
7
|
-
constructor(name: string, data: {
|
|
8
|
-
fields: Fields;
|
|
9
|
-
client: Client;
|
|
10
|
-
default_search_field?: NoInfer<keyof Inferred>;
|
|
11
|
-
default_sorting_field?: NoInfer<keyof Inferred>;
|
|
12
|
-
batch_size?: number;
|
|
13
|
-
enable_nested_fields?: boolean;
|
|
14
|
-
});
|
|
15
|
-
private checkNested;
|
|
16
|
-
private buildObjectFilter;
|
|
17
|
-
private buildSort;
|
|
18
|
-
private buildFilter;
|
|
19
|
-
private maybeArray;
|
|
20
|
-
delete(): Promise<void>;
|
|
21
|
-
create(): Promise<this>;
|
|
22
|
-
searchDocuments(data: Filters<Inferred>): Promise<{
|
|
23
|
-
count: number;
|
|
24
|
-
data: Inferred[];
|
|
25
|
-
}>;
|
|
26
|
-
upsertDocuments(items: Inferred | Inferred[]): Promise<any[] | undefined>;
|
|
27
|
-
}
|
package/dist/collection.js
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
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 requiresNested = ["object", "object[]"];
|
|
11
|
-
export class TSense {
|
|
12
|
-
constructor(name, data) {
|
|
13
|
-
this.name = name;
|
|
14
|
-
this.data = data;
|
|
15
|
-
this.infer = undefined;
|
|
16
|
-
}
|
|
17
|
-
checkNested(fields) {
|
|
18
|
-
if (this.data.enable_nested_fields) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
for (const field of fields) {
|
|
22
|
-
requiresNested.includes(field.type);
|
|
23
|
-
this.data.enable_nested_fields = true;
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
buildObjectFilter(key, value) {
|
|
28
|
-
if (Array.isArray(value)) {
|
|
29
|
-
return `(${key}:[${value.join(",")}])`;
|
|
30
|
-
}
|
|
31
|
-
if ("not" in value) {
|
|
32
|
-
return `${key}:!=${value.not}`;
|
|
33
|
-
}
|
|
34
|
-
const min = value.min != null ? value.min : undefined;
|
|
35
|
-
const max = value.max != null ? value.max : undefined;
|
|
36
|
-
if (min != null && max != null) {
|
|
37
|
-
return `${key}:[${min}..${max}]`;
|
|
38
|
-
}
|
|
39
|
-
if (max != null) {
|
|
40
|
-
return `${key}:<=${max}`;
|
|
41
|
-
}
|
|
42
|
-
if (min != null) {
|
|
43
|
-
return `${key}:>=${min}`;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
buildSort(data) {
|
|
47
|
-
var _a;
|
|
48
|
-
if (!data.order_by) {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
const order = [];
|
|
52
|
-
for (const key of this.maybeArray(data.order_by)) {
|
|
53
|
-
const splitted = key.split(" ");
|
|
54
|
-
let direction = splitted.at(-1);
|
|
55
|
-
if (direction !== "asc" && direction !== "desc") {
|
|
56
|
-
direction = (_a = data.direction) !== null && _a !== void 0 ? _a : "desc";
|
|
57
|
-
}
|
|
58
|
-
// safeguard
|
|
59
|
-
if (splitted[0] === "undefined") {
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
if (splitted[0] === "score") {
|
|
63
|
-
splitted[0] = "_text_match";
|
|
64
|
-
}
|
|
65
|
-
order.push(`${splitted[0]}:${direction}`);
|
|
66
|
-
}
|
|
67
|
-
return order.join(",");
|
|
68
|
-
}
|
|
69
|
-
buildFilter(data) {
|
|
70
|
-
var _a;
|
|
71
|
-
const filter = [];
|
|
72
|
-
for (const entry of Object.entries((_a = data.filter) !== null && _a !== void 0 ? _a : {})) {
|
|
73
|
-
const [key, value] = entry;
|
|
74
|
-
if (value == null)
|
|
75
|
-
continue;
|
|
76
|
-
if (key === "OR") {
|
|
77
|
-
const orFilter = [];
|
|
78
|
-
for (const condition of value) {
|
|
79
|
-
const filter = this.buildFilter({ filter: condition });
|
|
80
|
-
orFilter.push(`(${filter.join("||")})`);
|
|
81
|
-
}
|
|
82
|
-
filter.push(`(${orFilter.join("||")})`);
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
switch (typeof value) {
|
|
86
|
-
case "string":
|
|
87
|
-
case "number":
|
|
88
|
-
case "boolean":
|
|
89
|
-
filter.push(`${key}:=${value}`);
|
|
90
|
-
break;
|
|
91
|
-
case "object": {
|
|
92
|
-
const built = this.buildObjectFilter(key, value);
|
|
93
|
-
if (built) {
|
|
94
|
-
filter.push(built);
|
|
95
|
-
}
|
|
96
|
-
break;
|
|
97
|
-
}
|
|
98
|
-
default: {
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return filter;
|
|
104
|
-
}
|
|
105
|
-
maybeArray(d) {
|
|
106
|
-
if (Array.isArray(d)) {
|
|
107
|
-
return d;
|
|
108
|
-
}
|
|
109
|
-
return [d];
|
|
110
|
-
}
|
|
111
|
-
delete() {
|
|
112
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
113
|
-
yield this.data.client.collections(this.name).delete();
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
create() {
|
|
117
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
118
|
-
const fields = [
|
|
119
|
-
{
|
|
120
|
-
name: "id",
|
|
121
|
-
type: "int32",
|
|
122
|
-
},
|
|
123
|
-
];
|
|
124
|
-
for (const [name, field] of Object.entries(this.data.fields)) {
|
|
125
|
-
const isSimpleField = typeof field === "string";
|
|
126
|
-
if (isSimpleField) {
|
|
127
|
-
const isOptional = field[field.length - 1] === "?";
|
|
128
|
-
const realType = isOptional ? field.slice(0, -1) : field;
|
|
129
|
-
fields.push({
|
|
130
|
-
name,
|
|
131
|
-
type: realType,
|
|
132
|
-
optional: isOptional,
|
|
133
|
-
});
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
fields.push({
|
|
137
|
-
name,
|
|
138
|
-
type: field.type,
|
|
139
|
-
optional: field.optional,
|
|
140
|
-
facet: field.facet,
|
|
141
|
-
index: field.index,
|
|
142
|
-
sort: field.sort,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
this.checkNested(fields);
|
|
146
|
-
yield this.data.client.collections().create({
|
|
147
|
-
name: this.name,
|
|
148
|
-
fields,
|
|
149
|
-
default_sorting_field: this.data.default_sorting_field,
|
|
150
|
-
enable_nested_fields: this.data.enable_nested_fields,
|
|
151
|
-
});
|
|
152
|
-
return this;
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
searchDocuments(data) {
|
|
156
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
157
|
-
var _a, _b, _c;
|
|
158
|
-
const res = yield this.data.client
|
|
159
|
-
.collections(this.name)
|
|
160
|
-
.documents()
|
|
161
|
-
.search({
|
|
162
|
-
q: (_a = data.search) !== null && _a !== void 0 ? _a : "",
|
|
163
|
-
query_by: (_b = data.search_keys) !== null && _b !== void 0 ? _b : [this.data.default_search_field],
|
|
164
|
-
sort_by: this.buildSort(data),
|
|
165
|
-
filter_by: this.buildFilter(data).join("&&"),
|
|
166
|
-
page: data.page,
|
|
167
|
-
limit: data.limit,
|
|
168
|
-
});
|
|
169
|
-
const result = [];
|
|
170
|
-
if ((_c = res.hits) === null || _c === void 0 ? void 0 : _c.length) {
|
|
171
|
-
for (const hit of res.hits) {
|
|
172
|
-
result.push(hit.document);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
return {
|
|
176
|
-
data: result,
|
|
177
|
-
count: res.found,
|
|
178
|
-
};
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
upsertDocuments(items) {
|
|
182
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
183
|
-
const parsed = [];
|
|
184
|
-
for (const item of this.maybeArray(items)) {
|
|
185
|
-
parsed.push(JSON.stringify(item));
|
|
186
|
-
}
|
|
187
|
-
if (!parsed.length) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
const res = yield this.data.client
|
|
191
|
-
.collections(this.name)
|
|
192
|
-
.documents()
|
|
193
|
-
.import(parsed.join("\n"), {
|
|
194
|
-
action: "upsert",
|
|
195
|
-
batch_size: this.data.batch_size,
|
|
196
|
-
});
|
|
197
|
-
return res.split("\n").map((v) => JSON.parse(v));
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
}
|
package/dist/types/core.d.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import type { FieldType } from "typesense/lib/Typesense/Collection.js";
|
|
2
|
-
import type { BaseIfArray, Simplify, UndefinedToOptional } from "./helpers.js";
|
|
3
|
-
export type CustomCollectionField = FieldType | `${FieldType}?` | {
|
|
4
|
-
type: FieldType;
|
|
5
|
-
optional?: boolean;
|
|
6
|
-
facet?: boolean;
|
|
7
|
-
index?: boolean;
|
|
8
|
-
override?: any;
|
|
9
|
-
sort?: boolean;
|
|
10
|
-
};
|
|
11
|
-
type TypesenseToTS = {
|
|
12
|
-
string: string;
|
|
13
|
-
int32: number;
|
|
14
|
-
int64: number;
|
|
15
|
-
float: number;
|
|
16
|
-
bool: boolean;
|
|
17
|
-
image: string;
|
|
18
|
-
"string[]": string[];
|
|
19
|
-
"int32[]": number[];
|
|
20
|
-
"int64[]": number[];
|
|
21
|
-
"float[]": number[];
|
|
22
|
-
"bool[]": boolean[];
|
|
23
|
-
geopoint: unknown;
|
|
24
|
-
"geopoint[]": unknown[];
|
|
25
|
-
object: unknown;
|
|
26
|
-
"object[]": unknown[];
|
|
27
|
-
auto: unknown;
|
|
28
|
-
"string*": unknown;
|
|
29
|
-
};
|
|
30
|
-
type ValidFieldType = keyof TypesenseToTS;
|
|
31
|
-
type MaybeOptional<Field, Result> = Field extends {
|
|
32
|
-
optional: true;
|
|
33
|
-
} ? Result | null | undefined : Result;
|
|
34
|
-
export type InferCollectionTypes<Fields> = Simplify<{
|
|
35
|
-
id?: string;
|
|
36
|
-
} & UndefinedToOptional<{
|
|
37
|
-
[K in keyof Fields]: MaybeOptional<Fields[K], Fields[K] extends {
|
|
38
|
-
override: infer Override;
|
|
39
|
-
} ? Override : Fields[K] extends ValidFieldType ? TypesenseToTS[Fields[K]] : Fields[K] extends {
|
|
40
|
-
type: infer T;
|
|
41
|
-
} ? T extends ValidFieldType ? TypesenseToTS[T] : never : Fields[K] extends `${infer RealType}?` ? RealType extends ValidFieldType ? TypesenseToTS[RealType] | null | undefined : never : never>;
|
|
42
|
-
}>>;
|
|
43
|
-
type OrderBy<Inferred, Options = keyof Inferred | "score"> = Options | Options[] | (Options extends string ? `${Options} ${"asc" | "desc"}` : never)[];
|
|
44
|
-
type SingleFilter<Inferred> = Partial<{
|
|
45
|
-
[K in keyof Inferred]: BaseIfArray<Inferred[K]> | NonNullable<BaseIfArray<Inferred[K]>>[] | {
|
|
46
|
-
not?: string | string[] | boolean | null;
|
|
47
|
-
} | (NonNullable<Inferred[K]> extends number ? {
|
|
48
|
-
min?: number;
|
|
49
|
-
max?: number;
|
|
50
|
-
} : never);
|
|
51
|
-
}>;
|
|
52
|
-
type RecursiveFilter<Inferred> = SingleFilter<Inferred> & {
|
|
53
|
-
OR?: RecursiveFilter<Inferred>[];
|
|
54
|
-
};
|
|
55
|
-
export type SearchFilters<Inferred> = {
|
|
56
|
-
search?: string;
|
|
57
|
-
filter?: RecursiveFilter<Inferred>;
|
|
58
|
-
order_by?: OrderBy<Inferred> | undefined;
|
|
59
|
-
direction?: "asc" | "desc";
|
|
60
|
-
page?: number;
|
|
61
|
-
limit?: number;
|
|
62
|
-
search_keys?: (keyof Inferred)[];
|
|
63
|
-
highlight?: boolean;
|
|
64
|
-
facet_by?: NoInfer<keyof Inferred>;
|
|
65
|
-
enable_facet_total?: boolean;
|
|
66
|
-
};
|
|
67
|
-
export type InferFacetResponse<Inferred, Filter> = Filter extends {
|
|
68
|
-
facet_by: infer FacetBy;
|
|
69
|
-
} ? FacetBy extends keyof Inferred ? Record<NonNullable<Inferred[FacetBy]> extends string ? NonNullable<Inferred[FacetBy]> : never, number> & (Filter extends {
|
|
70
|
-
enable_facet_total: true;
|
|
71
|
-
} ? {
|
|
72
|
-
total: number;
|
|
73
|
-
} : {}) : never : never;
|
|
74
|
-
export {};
|
package/dist/types/helpers.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export type UndefinedToOptional<T> = {
|
|
2
|
-
[K in keyof T as undefined extends T[K] ? K : never]?: Exclude<T[K], undefined>;
|
|
3
|
-
} & {
|
|
4
|
-
[K in keyof T as undefined extends T[K] ? never : K]: T[K];
|
|
5
|
-
};
|
|
6
|
-
export type BaseIfArray<T> = T extends (infer Q)[] ? Q : T;
|
|
7
|
-
type DrainOuterGeneric<T> = [T] extends [unknown] ? T : never;
|
|
8
|
-
export type Simplify<T> = DrainOuterGeneric<{
|
|
9
|
-
[K in keyof T]: T[K];
|
|
10
|
-
} & {}>;
|
|
11
|
-
export {};
|
|
File without changes
|
|
File without changes
|