tsense 0.0.2 → 0.0.4
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 +32 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/tsense.d.ts +34 -0
- package/dist/tsense.js +215 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -38,6 +38,12 @@ export const UsersCollection = new TSense("users", {
|
|
|
38
38
|
type: "string",
|
|
39
39
|
sort: true,
|
|
40
40
|
},
|
|
41
|
+
company: {
|
|
42
|
+
type: "string",
|
|
43
|
+
override: {} as "netflix" | "google",
|
|
44
|
+
facet: true,
|
|
45
|
+
optional: true,
|
|
46
|
+
},
|
|
41
47
|
work_history: {
|
|
42
48
|
// object and object[] auto-infers enable_nested_fields
|
|
43
49
|
type: "object[]",
|
|
@@ -91,4 +97,29 @@ const results = await UsersCollection.searchDocuments({
|
|
|
91
97
|
],
|
|
92
98
|
},
|
|
93
99
|
});
|
|
94
|
-
|
|
100
|
+
|
|
101
|
+
/*
|
|
102
|
+
typed as
|
|
103
|
+
count: number;
|
|
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
|
+
{
|
|
118
|
+
search: "john",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
facet_by: "company",
|
|
122
|
+
enable_facet_total: true,
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { TSense } from "./
|
|
1
|
+
export { TSense } from "./tsense.js";
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { TSense } from "./
|
|
1
|
+
export { TSense } from "./tsense.js";
|
package/dist/tsense.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Client } from "typesense";
|
|
2
|
+
import type { CustomCollectionField, Filters, InferCollectionTypes } from "./types/core.js";
|
|
3
|
+
import type { Simplify } from "./types/helpers.js";
|
|
4
|
+
export declare class TSense<Options extends CustomCollectionField, Fields extends Record<string, Options>, Inferred = InferCollectionTypes<Fields>> {
|
|
5
|
+
private name;
|
|
6
|
+
private data;
|
|
7
|
+
infer: Inferred;
|
|
8
|
+
constructor(name: string, data: {
|
|
9
|
+
fields: Fields;
|
|
10
|
+
client: Client;
|
|
11
|
+
default_search_field?: NoInfer<keyof Inferred>;
|
|
12
|
+
default_sorting_field?: NoInfer<keyof Inferred>;
|
|
13
|
+
batch_size?: number;
|
|
14
|
+
enable_nested_fields?: boolean;
|
|
15
|
+
});
|
|
16
|
+
private checkNested;
|
|
17
|
+
private buildObjectFilter;
|
|
18
|
+
private buildSort;
|
|
19
|
+
private buildFilter;
|
|
20
|
+
private maybeArray;
|
|
21
|
+
delete(): Promise<void>;
|
|
22
|
+
create(): Promise<this>;
|
|
23
|
+
searchDocuments<FacetBy extends keyof Inferred, EnableFacetTotal extends boolean | undefined = undefined>(data: Filters<Inferred>, facet?: {
|
|
24
|
+
facet_by?: FacetBy;
|
|
25
|
+
enable_facet_total?: EnableFacetTotal;
|
|
26
|
+
}): Promise<{
|
|
27
|
+
count: number;
|
|
28
|
+
data: Inferred[];
|
|
29
|
+
facet: FacetBy extends keyof Inferred ? Simplify<Record<NonNullable<Inferred[FacetBy]> extends string ? NonNullable<Inferred[FacetBy]> : never, number> & (EnableFacetTotal extends true ? {
|
|
30
|
+
total: number;
|
|
31
|
+
} : {})> : never;
|
|
32
|
+
}>;
|
|
33
|
+
upsertDocuments(items: Inferred | Inferred[]): Promise<any[] | undefined>;
|
|
34
|
+
}
|
package/dist/tsense.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
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
|
+
for (const [name, field] of Object.entries(this.data.fields)) {
|
|
120
|
+
const isSimpleField = typeof field === "string";
|
|
121
|
+
if (isSimpleField) {
|
|
122
|
+
const isOptional = field[field.length - 1] === "?";
|
|
123
|
+
const realType = isOptional ? field.slice(0, -1) : field;
|
|
124
|
+
fields.push({
|
|
125
|
+
name,
|
|
126
|
+
type: realType,
|
|
127
|
+
optional: isOptional,
|
|
128
|
+
});
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
fields.push({
|
|
132
|
+
name,
|
|
133
|
+
type: field.type,
|
|
134
|
+
optional: field.optional,
|
|
135
|
+
facet: field.facet,
|
|
136
|
+
index: field.index,
|
|
137
|
+
sort: field.sort,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
this.checkNested(fields);
|
|
141
|
+
yield this.data.client.collections().create({
|
|
142
|
+
name: this.name,
|
|
143
|
+
fields,
|
|
144
|
+
default_sorting_field: this.data.default_sorting_field,
|
|
145
|
+
enable_nested_fields: this.data.enable_nested_fields,
|
|
146
|
+
});
|
|
147
|
+
return this;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
searchDocuments(data, facet) {
|
|
151
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
152
|
+
var _a, _b, _c, _d, _e;
|
|
153
|
+
const res = yield this.data.client
|
|
154
|
+
.collections(this.name)
|
|
155
|
+
.documents()
|
|
156
|
+
.search({
|
|
157
|
+
q: (_a = data.search) !== null && _a !== void 0 ? _a : "",
|
|
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: facet === null || facet === void 0 ? void 0 : facet.facet_by,
|
|
164
|
+
});
|
|
165
|
+
const facet_result = (facet === null || facet === void 0 ? void 0 : facet.facet_by)
|
|
166
|
+
? facet.enable_facet_total
|
|
167
|
+
? { total: 0 }
|
|
168
|
+
: {}
|
|
169
|
+
: undefined;
|
|
170
|
+
if ((_d = (_c = res.facet_counts) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.counts) {
|
|
171
|
+
for (const iter of res.facet_counts[0].counts) {
|
|
172
|
+
facet_result[iter.value] = iter.count;
|
|
173
|
+
if (facet === null || facet === void 0 ? void 0 : facet.enable_facet_total)
|
|
174
|
+
facet_result.total += iter.count;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const result = [];
|
|
178
|
+
for (const hit of (_e = res.hits) !== null && _e !== void 0 ? _e : []) {
|
|
179
|
+
if (data.highlight) {
|
|
180
|
+
for (const [key, value] of Object.entries(hit.highlight)) {
|
|
181
|
+
if (value) {
|
|
182
|
+
// @ts-expect-error
|
|
183
|
+
hit.document[key] = value.snippet;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
result.push(hit.document);
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
data: result,
|
|
191
|
+
count: res.found,
|
|
192
|
+
facet: facet_result,
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
upsertDocuments(items) {
|
|
197
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
198
|
+
const parsed = [];
|
|
199
|
+
for (const item of this.maybeArray(items)) {
|
|
200
|
+
parsed.push(JSON.stringify(item));
|
|
201
|
+
}
|
|
202
|
+
if (!parsed.length) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const res = yield this.data.client
|
|
206
|
+
.collections(this.name)
|
|
207
|
+
.documents()
|
|
208
|
+
.import(parsed.join("\n"), {
|
|
209
|
+
action: "upsert",
|
|
210
|
+
batch_size: this.data.batch_size,
|
|
211
|
+
});
|
|
212
|
+
return res.split("\n").map((v) => JSON.parse(v));
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tsense",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"dist"
|
|
14
14
|
],
|
|
15
|
+
"sideEffects": false,
|
|
15
16
|
"description": "Opinionated, fully typed typesense client",
|
|
16
17
|
"license": "MIT",
|
|
17
18
|
"keywords": [
|