tsense 0.0.17 → 0.1.0
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/dist/migrator.js +69 -87
- package/dist/tsense.js +282 -327
- package/package.json +7 -8
package/dist/migrator.js
CHANGED
|
@@ -1,61 +1,50 @@
|
|
|
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
1
|
const COMPARABLE_KEYS = ["type", "facet", "sort", "index", "optional"];
|
|
11
2
|
const NESTED_TYPES = ["object", "object[]"];
|
|
12
3
|
export class TSenseMigrator {
|
|
4
|
+
collectionName;
|
|
5
|
+
localFields;
|
|
6
|
+
defaultSortingField;
|
|
7
|
+
axios;
|
|
13
8
|
constructor(collectionName, localFields, defaultSortingField, axios) {
|
|
14
9
|
this.collectionName = collectionName;
|
|
15
10
|
this.localFields = localFields;
|
|
16
11
|
this.defaultSortingField = defaultSortingField;
|
|
17
12
|
this.axios = axios;
|
|
18
13
|
}
|
|
19
|
-
sync() {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
yield this.create();
|
|
35
|
-
});
|
|
14
|
+
async sync() {
|
|
15
|
+
const exists = await this.exists();
|
|
16
|
+
if (!exists) {
|
|
17
|
+
return await this.create();
|
|
18
|
+
}
|
|
19
|
+
const remoteFields = await this.getRemoteFields();
|
|
20
|
+
const diff = this.diff(remoteFields);
|
|
21
|
+
if (!diff.toAdd.length && !diff.toRemove.length && !diff.toModify.length) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const patched = await this.patch(diff);
|
|
25
|
+
if (patched)
|
|
26
|
+
return;
|
|
27
|
+
await this.drop();
|
|
28
|
+
await this.create();
|
|
36
29
|
}
|
|
37
|
-
exists() {
|
|
38
|
-
return
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
throw e;
|
|
48
|
-
});
|
|
30
|
+
async exists() {
|
|
31
|
+
return await this.axios({
|
|
32
|
+
method: "GET",
|
|
33
|
+
url: `/collections/${this.collectionName}`,
|
|
34
|
+
})
|
|
35
|
+
.then(() => true)
|
|
36
|
+
.catch((e) => {
|
|
37
|
+
if (e.status === 404)
|
|
38
|
+
return false;
|
|
39
|
+
throw e;
|
|
49
40
|
});
|
|
50
41
|
}
|
|
51
|
-
getRemoteFields() {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
url: `/collections/${this.collectionName}`,
|
|
56
|
-
});
|
|
57
|
-
return data.fields;
|
|
42
|
+
async getRemoteFields() {
|
|
43
|
+
const { data } = await this.axios({
|
|
44
|
+
method: "GET",
|
|
45
|
+
url: `/collections/${this.collectionName}`,
|
|
58
46
|
});
|
|
47
|
+
return data.fields;
|
|
59
48
|
}
|
|
60
49
|
diff(remote) {
|
|
61
50
|
const remoteByName = new Map(remote.map((f) => [f.name, f]));
|
|
@@ -85,57 +74,50 @@ export class TSenseMigrator {
|
|
|
85
74
|
return { toAdd, toRemove, toModify };
|
|
86
75
|
}
|
|
87
76
|
fieldsMatch(local, remote) {
|
|
88
|
-
var _a, _b;
|
|
89
77
|
for (const key of COMPARABLE_KEYS) {
|
|
90
|
-
if ((
|
|
78
|
+
if ((local[key] ?? undefined) !== (remote[key] ?? undefined)) {
|
|
91
79
|
return false;
|
|
92
80
|
}
|
|
93
81
|
}
|
|
94
82
|
return true;
|
|
95
83
|
}
|
|
96
|
-
patch(diff) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
.catch(() => false);
|
|
116
|
-
});
|
|
84
|
+
async patch(diff) {
|
|
85
|
+
const fields = [];
|
|
86
|
+
for (const field of diff.toRemove) {
|
|
87
|
+
fields.push({ name: field.name, drop: true });
|
|
88
|
+
}
|
|
89
|
+
for (const field of diff.toModify) {
|
|
90
|
+
fields.push({ name: field.name, drop: true });
|
|
91
|
+
fields.push(field);
|
|
92
|
+
}
|
|
93
|
+
for (const field of diff.toAdd) {
|
|
94
|
+
fields.push(field);
|
|
95
|
+
}
|
|
96
|
+
return await this.axios({
|
|
97
|
+
method: "PATCH",
|
|
98
|
+
url: `/collections/${this.collectionName}`,
|
|
99
|
+
data: { fields },
|
|
100
|
+
})
|
|
101
|
+
.then(() => true)
|
|
102
|
+
.catch(() => false);
|
|
117
103
|
}
|
|
118
|
-
create() {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
},
|
|
130
|
-
});
|
|
104
|
+
async create() {
|
|
105
|
+
const enable_nested_fields = this.localFields.some((f) => NESTED_TYPES.includes(f.type));
|
|
106
|
+
await this.axios({
|
|
107
|
+
method: "POST",
|
|
108
|
+
url: "/collections",
|
|
109
|
+
data: {
|
|
110
|
+
name: this.collectionName,
|
|
111
|
+
fields: this.localFields,
|
|
112
|
+
default_sorting_field: this.defaultSortingField,
|
|
113
|
+
enable_nested_fields,
|
|
114
|
+
},
|
|
131
115
|
});
|
|
132
116
|
}
|
|
133
|
-
drop() {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
url: `/collections/${this.collectionName}`,
|
|
138
|
-
});
|
|
117
|
+
async drop() {
|
|
118
|
+
await this.axios({
|
|
119
|
+
method: "DELETE",
|
|
120
|
+
url: `/collections/${this.collectionName}`,
|
|
139
121
|
});
|
|
140
122
|
}
|
|
141
123
|
}
|
package/dist/tsense.js
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
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
|
-
var _a;
|
|
11
1
|
import redaxios from "redaxios";
|
|
12
2
|
import { TSenseMigrator } from "./migrator.js";
|
|
13
3
|
import { defaultTransformers } from "./transformers/defaults.js";
|
|
@@ -18,7 +8,7 @@ function chunkArray(arr, size) {
|
|
|
18
8
|
}
|
|
19
9
|
return chunks;
|
|
20
10
|
}
|
|
21
|
-
const redaxiosInstance =
|
|
11
|
+
const redaxiosInstance = redaxios.default ?? redaxios;
|
|
22
12
|
const arkToTsense = {
|
|
23
13
|
string: "string",
|
|
24
14
|
number: "float",
|
|
@@ -30,21 +20,23 @@ const arkToTsense = {
|
|
|
30
20
|
"boolean[]": "bool[]",
|
|
31
21
|
};
|
|
32
22
|
export class TSense {
|
|
23
|
+
options;
|
|
24
|
+
fields = [];
|
|
25
|
+
axios;
|
|
26
|
+
synced = false;
|
|
27
|
+
fieldTransformers = new Map();
|
|
28
|
+
dataSyncConfig;
|
|
29
|
+
infer = undefined;
|
|
33
30
|
getFields() {
|
|
34
31
|
return this.fields;
|
|
35
32
|
}
|
|
36
33
|
constructor(options) {
|
|
37
|
-
var _a;
|
|
38
34
|
this.options = options;
|
|
39
|
-
this.fields = [];
|
|
40
|
-
this.synced = false;
|
|
41
|
-
this.fieldTransformers = new Map();
|
|
42
|
-
this.infer = undefined;
|
|
43
35
|
this.axios = redaxiosInstance.create({
|
|
44
36
|
baseURL: `${options.connection.protocol}://${options.connection.host}:${options.connection.port}`,
|
|
45
37
|
headers: { "X-TYPESENSE-API-KEY": options.connection.apiKey },
|
|
46
38
|
});
|
|
47
|
-
this.extractFields(
|
|
39
|
+
this.extractFields(options.transformers ?? defaultTransformers);
|
|
48
40
|
this.dataSyncConfig = options.dataSync;
|
|
49
41
|
}
|
|
50
42
|
getBaseType(expression, domain) {
|
|
@@ -65,7 +57,7 @@ export class TSense {
|
|
|
65
57
|
return "string";
|
|
66
58
|
}
|
|
67
59
|
serializeDoc(doc) {
|
|
68
|
-
const result =
|
|
60
|
+
const result = { ...doc };
|
|
69
61
|
for (const [field, transformer] of this.fieldTransformers) {
|
|
70
62
|
if (result[field] != null) {
|
|
71
63
|
result[field] = transformer.serialize(result[field]);
|
|
@@ -94,7 +86,7 @@ export class TSense {
|
|
|
94
86
|
if (!isFilterObject) {
|
|
95
87
|
return transformer.serialize(value);
|
|
96
88
|
}
|
|
97
|
-
const result =
|
|
89
|
+
const result = { ...v };
|
|
98
90
|
if ("min" in v && v.min != null)
|
|
99
91
|
result.min = transformer.serialize(v.min);
|
|
100
92
|
if ("max" in v && v.max != null)
|
|
@@ -106,11 +98,10 @@ export class TSense {
|
|
|
106
98
|
return transformer.serialize(value);
|
|
107
99
|
}
|
|
108
100
|
extractFields(transformers) {
|
|
109
|
-
var _a, _b, _c, _d;
|
|
110
101
|
const internal = this.options.schema;
|
|
111
102
|
for (const prop of internal.structure.props) {
|
|
112
|
-
const innerType =
|
|
113
|
-
const meta = (
|
|
103
|
+
const innerType = prop.value.branches?.[0] ?? prop.value;
|
|
104
|
+
const meta = (innerType.meta ?? prop.value.meta);
|
|
114
105
|
const expression = String(prop.value.expression);
|
|
115
106
|
const domain = prop.value.domain;
|
|
116
107
|
const baseType = this.getBaseType(expression, domain);
|
|
@@ -121,38 +112,33 @@ export class TSense {
|
|
|
121
112
|
name: prop.key,
|
|
122
113
|
type: transformer.storageType,
|
|
123
114
|
optional: prop.kind === "optional",
|
|
124
|
-
facet: meta
|
|
125
|
-
sort: meta
|
|
126
|
-
index: meta
|
|
115
|
+
facet: meta?.facet,
|
|
116
|
+
sort: meta?.sort,
|
|
117
|
+
index: meta?.index,
|
|
127
118
|
});
|
|
128
119
|
continue;
|
|
129
120
|
}
|
|
130
|
-
const type =
|
|
121
|
+
const type = meta?.type ?? this.inferType(baseType);
|
|
131
122
|
this.fields.push({
|
|
132
123
|
name: prop.key,
|
|
133
124
|
type,
|
|
134
125
|
optional: prop.kind === "optional",
|
|
135
|
-
facet: meta
|
|
136
|
-
sort: meta
|
|
137
|
-
index: meta
|
|
126
|
+
facet: meta?.facet,
|
|
127
|
+
sort: meta?.sort,
|
|
128
|
+
index: meta?.index,
|
|
138
129
|
});
|
|
139
130
|
}
|
|
140
131
|
}
|
|
141
|
-
ensureSynced(force) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
this.synced = true;
|
|
147
|
-
});
|
|
132
|
+
async ensureSynced(force) {
|
|
133
|
+
if (!force && (this.synced || !this.options.autoSyncSchema))
|
|
134
|
+
return;
|
|
135
|
+
await new TSenseMigrator(this.options.name, this.fields, this.options.defaultSortingField, this.axios).sync();
|
|
136
|
+
this.synced = true;
|
|
148
137
|
}
|
|
149
|
-
syncSchema() {
|
|
150
|
-
|
|
151
|
-
yield this.ensureSynced(true);
|
|
152
|
-
});
|
|
138
|
+
async syncSchema() {
|
|
139
|
+
await this.ensureSynced(true);
|
|
153
140
|
}
|
|
154
141
|
buildObjectFilter(key, value) {
|
|
155
|
-
var _a, _b;
|
|
156
142
|
if (Array.isArray(value)) {
|
|
157
143
|
return `(${key}:[${value.join(",")}])`;
|
|
158
144
|
}
|
|
@@ -160,8 +146,8 @@ export class TSense {
|
|
|
160
146
|
if ("not" in v) {
|
|
161
147
|
return `${key}:!=${v.not}`;
|
|
162
148
|
}
|
|
163
|
-
const min =
|
|
164
|
-
const max =
|
|
149
|
+
const min = v.min ?? undefined;
|
|
150
|
+
const max = v.max ?? undefined;
|
|
165
151
|
if (min != null && max != null) {
|
|
166
152
|
return `${key}:[${min}..${max}]`;
|
|
167
153
|
}
|
|
@@ -174,7 +160,7 @@ export class TSense {
|
|
|
174
160
|
}
|
|
175
161
|
buildFilter(filter) {
|
|
176
162
|
const result = [];
|
|
177
|
-
for (const entry of Object.entries(filter
|
|
163
|
+
for (const entry of Object.entries(filter ?? {})) {
|
|
178
164
|
const [key, rawValue] = entry;
|
|
179
165
|
if (rawValue == null)
|
|
180
166
|
continue;
|
|
@@ -219,322 +205,291 @@ export class TSense {
|
|
|
219
205
|
}
|
|
220
206
|
return result.join(",");
|
|
221
207
|
}
|
|
222
|
-
create() {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
},
|
|
234
|
-
});
|
|
235
|
-
return this;
|
|
208
|
+
async create() {
|
|
209
|
+
const enableNested = this.fields.some((f) => f.type === "object" || f.type === "object[]");
|
|
210
|
+
await this.axios({
|
|
211
|
+
method: "POST",
|
|
212
|
+
url: "/collections",
|
|
213
|
+
data: {
|
|
214
|
+
name: this.options.name,
|
|
215
|
+
fields: this.fields,
|
|
216
|
+
default_sorting_field: this.options.defaultSortingField,
|
|
217
|
+
enable_nested_fields: enableNested,
|
|
218
|
+
},
|
|
236
219
|
});
|
|
220
|
+
return this;
|
|
237
221
|
}
|
|
238
|
-
drop() {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
url: `/collections/${this.options.name}`,
|
|
243
|
-
});
|
|
222
|
+
async drop() {
|
|
223
|
+
await this.axios({
|
|
224
|
+
method: "DELETE",
|
|
225
|
+
url: `/collections/${this.options.name}`,
|
|
244
226
|
});
|
|
245
227
|
}
|
|
246
|
-
get(id) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
throw e;
|
|
256
|
-
});
|
|
257
|
-
return data ? this.deserializeDoc(data) : null;
|
|
228
|
+
async get(id) {
|
|
229
|
+
await this.ensureSynced();
|
|
230
|
+
const { data } = await this.axios({
|
|
231
|
+
method: "GET",
|
|
232
|
+
url: `/collections/${this.options.name}/documents/${id}`,
|
|
233
|
+
}).catch((e) => {
|
|
234
|
+
if (e.status === 404)
|
|
235
|
+
return { data: null };
|
|
236
|
+
throw e;
|
|
258
237
|
});
|
|
238
|
+
return data ? this.deserializeDoc(data) : null;
|
|
259
239
|
}
|
|
260
|
-
delete(id) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
throw e;
|
|
270
|
-
});
|
|
271
|
-
return data != null;
|
|
240
|
+
async delete(id) {
|
|
241
|
+
await this.ensureSynced();
|
|
242
|
+
const { data } = await this.axios({
|
|
243
|
+
method: "DELETE",
|
|
244
|
+
url: `/collections/${this.options.name}/documents/${id}`,
|
|
245
|
+
}).catch((e) => {
|
|
246
|
+
if (e.status === 404)
|
|
247
|
+
return { data: null };
|
|
248
|
+
throw e;
|
|
272
249
|
});
|
|
250
|
+
return data != null;
|
|
273
251
|
}
|
|
274
|
-
deleteMany(filter) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
params: { filter_by: filterBy },
|
|
285
|
-
});
|
|
286
|
-
return { deleted: data.num_deleted };
|
|
252
|
+
async deleteMany(filter) {
|
|
253
|
+
await this.ensureSynced();
|
|
254
|
+
const filterBy = this.buildFilter(filter).join("&&");
|
|
255
|
+
if (!filterBy) {
|
|
256
|
+
throw new Error("FILTER_REQUIRED");
|
|
257
|
+
}
|
|
258
|
+
const { data } = await this.axios({
|
|
259
|
+
method: "DELETE",
|
|
260
|
+
url: `/collections/${this.options.name}/documents`,
|
|
261
|
+
params: { filter_by: filterBy },
|
|
287
262
|
});
|
|
263
|
+
return { deleted: data.num_deleted };
|
|
288
264
|
}
|
|
289
|
-
update(id, data) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
data: serialized,
|
|
297
|
-
});
|
|
298
|
-
return this.deserializeDoc(updated);
|
|
265
|
+
async update(id, data) {
|
|
266
|
+
await this.ensureSynced();
|
|
267
|
+
const serialized = this.serializeDoc(data);
|
|
268
|
+
const { data: updated } = await this.axios({
|
|
269
|
+
method: "PATCH",
|
|
270
|
+
url: `/collections/${this.options.name}/documents/${id}`,
|
|
271
|
+
data: serialized,
|
|
299
272
|
});
|
|
273
|
+
return this.deserializeDoc(updated);
|
|
300
274
|
}
|
|
301
|
-
updateMany(filter, data) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
data: serialized,
|
|
314
|
-
});
|
|
315
|
-
return { updated: result.num_updated };
|
|
275
|
+
async updateMany(filter, data) {
|
|
276
|
+
await this.ensureSynced();
|
|
277
|
+
const filterBy = this.buildFilter(filter).join("&&");
|
|
278
|
+
if (!filterBy) {
|
|
279
|
+
throw new Error("FILTER_REQUIRED");
|
|
280
|
+
}
|
|
281
|
+
const serialized = this.serializeDoc(data);
|
|
282
|
+
const { data: result } = await this.axios({
|
|
283
|
+
method: "PATCH",
|
|
284
|
+
url: `/collections/${this.options.name}/documents`,
|
|
285
|
+
params: { filter_by: filterBy },
|
|
286
|
+
data: serialized,
|
|
316
287
|
});
|
|
288
|
+
return { updated: result.num_updated };
|
|
317
289
|
}
|
|
318
|
-
search(options) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
290
|
+
async search(options) {
|
|
291
|
+
await this.ensureSynced();
|
|
292
|
+
const params = {
|
|
293
|
+
q: options.query ?? "",
|
|
294
|
+
query_by: (options.queryBy ?? [this.options.defaultSearchField]).join(","),
|
|
295
|
+
};
|
|
296
|
+
const sortBy = this.buildSort(options);
|
|
297
|
+
if (sortBy)
|
|
298
|
+
params.sort_by = sortBy;
|
|
299
|
+
const filterBy = this.buildFilter(options.filter).join("&&");
|
|
300
|
+
if (filterBy)
|
|
301
|
+
params.filter_by = filterBy;
|
|
302
|
+
if (options.page != null)
|
|
303
|
+
params.page = options.page;
|
|
304
|
+
if (options.limit != null)
|
|
305
|
+
params.per_page = options.limit;
|
|
306
|
+
const facetBy = options.facetBy?.join(",");
|
|
307
|
+
if (facetBy)
|
|
308
|
+
params.facet_by = facetBy;
|
|
309
|
+
if ("pick" in options && options.pick) {
|
|
310
|
+
params.include_fields = options.pick.join(",");
|
|
311
|
+
}
|
|
312
|
+
if ("omit" in options && options.omit) {
|
|
313
|
+
params.exclude_fields = options.omit.join(",");
|
|
314
|
+
}
|
|
315
|
+
const highlight = options.highlight;
|
|
316
|
+
const highlightEnabled = !!highlight;
|
|
317
|
+
let highlightOpts;
|
|
318
|
+
if (typeof highlight === "object") {
|
|
319
|
+
highlightOpts = highlight;
|
|
320
|
+
if (highlightOpts.fields) {
|
|
321
|
+
params.highlight_fields = highlightOpts.fields.join(",");
|
|
341
322
|
}
|
|
342
|
-
if (
|
|
343
|
-
params.
|
|
323
|
+
if (highlightOpts.startTag) {
|
|
324
|
+
params.highlight_start_tag = highlightOpts.startTag;
|
|
344
325
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
let highlightOpts;
|
|
348
|
-
if (typeof highlight === "object") {
|
|
349
|
-
highlightOpts = highlight;
|
|
350
|
-
if (highlightOpts.fields) {
|
|
351
|
-
params.highlight_fields = highlightOpts.fields.join(",");
|
|
352
|
-
}
|
|
353
|
-
if (highlightOpts.startTag) {
|
|
354
|
-
params.highlight_start_tag = highlightOpts.startTag;
|
|
355
|
-
}
|
|
356
|
-
if (highlightOpts.endTag) {
|
|
357
|
-
params.highlight_end_tag = highlightOpts.endTag;
|
|
358
|
-
}
|
|
326
|
+
if (highlightOpts.endTag) {
|
|
327
|
+
params.highlight_end_tag = highlightOpts.endTag;
|
|
359
328
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
329
|
+
}
|
|
330
|
+
const { data: res } = await this.axios({
|
|
331
|
+
method: "GET",
|
|
332
|
+
url: `/collections/${this.options.name}/documents/search`,
|
|
333
|
+
params,
|
|
334
|
+
});
|
|
335
|
+
const data = [];
|
|
336
|
+
const scores = [];
|
|
337
|
+
for (const hit of res.hits ?? []) {
|
|
338
|
+
if (highlightEnabled) {
|
|
339
|
+
const fieldsToHighlight = highlightOpts?.fields;
|
|
340
|
+
for (const [key, value] of Object.entries(hit.highlight ?? {})) {
|
|
341
|
+
if (!value?.snippet)
|
|
342
|
+
continue;
|
|
343
|
+
if (fieldsToHighlight && !fieldsToHighlight.includes(key))
|
|
344
|
+
continue;
|
|
345
|
+
hit.document[key] = value.snippet;
|
|
377
346
|
}
|
|
378
|
-
const doc = this.deserializeDoc(hit.document);
|
|
379
|
-
data.push(doc);
|
|
380
|
-
scores.push((_f = hit.text_match) !== null && _f !== void 0 ? _f : 0);
|
|
381
347
|
}
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
348
|
+
const doc = this.deserializeDoc(hit.document);
|
|
349
|
+
data.push(doc);
|
|
350
|
+
scores.push(hit.text_match ?? 0);
|
|
351
|
+
}
|
|
352
|
+
const facets = {};
|
|
353
|
+
for (const facetCount of res.facet_counts ?? []) {
|
|
354
|
+
const fieldName = facetCount.field_name;
|
|
355
|
+
facets[fieldName] = {};
|
|
356
|
+
for (const item of facetCount.counts) {
|
|
357
|
+
facets[fieldName][item.value] = item.count;
|
|
389
358
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
count: res.found,
|
|
362
|
+
data,
|
|
363
|
+
facets,
|
|
364
|
+
scores,
|
|
365
|
+
};
|
|
397
366
|
}
|
|
398
|
-
searchList(options) {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
url: `/collections/${this.options.name}/documents/search`,
|
|
419
|
-
params,
|
|
420
|
-
});
|
|
421
|
-
const hits = (_d = res.hits) !== null && _d !== void 0 ? _d : [];
|
|
422
|
-
const data = [];
|
|
423
|
-
for (const hit of hits) {
|
|
424
|
-
const doc = this.deserializeDoc(hit.document);
|
|
425
|
-
data.push(doc);
|
|
426
|
-
}
|
|
427
|
-
const hasMore = page * limit < res.found;
|
|
428
|
-
const nextCursor = hasMore ? String(page + 1) : null;
|
|
429
|
-
return { data, nextCursor, total: res.found };
|
|
367
|
+
async searchList(options) {
|
|
368
|
+
await this.ensureSynced();
|
|
369
|
+
const limit = Math.min(options.limit ?? 20, 100);
|
|
370
|
+
const field = options.sort.field;
|
|
371
|
+
const page = options.cursor ? Number(options.cursor) : 1;
|
|
372
|
+
const params = {
|
|
373
|
+
q: options.query ?? "",
|
|
374
|
+
query_by: (options.queryBy ?? [this.options.defaultSearchField]).join(","),
|
|
375
|
+
per_page: limit,
|
|
376
|
+
page,
|
|
377
|
+
sort_by: `${field}:${options.sort.direction}`,
|
|
378
|
+
};
|
|
379
|
+
const filterParts = this.buildFilter(options.filter);
|
|
380
|
+
const filterBy = filterParts.join("&&");
|
|
381
|
+
if (filterBy)
|
|
382
|
+
params.filter_by = filterBy;
|
|
383
|
+
const { data: res } = await this.axios({
|
|
384
|
+
method: "GET",
|
|
385
|
+
url: `/collections/${this.options.name}/documents/search`,
|
|
386
|
+
params,
|
|
430
387
|
});
|
|
388
|
+
const hits = res.hits ?? [];
|
|
389
|
+
const data = [];
|
|
390
|
+
for (const hit of hits) {
|
|
391
|
+
const doc = this.deserializeDoc(hit.document);
|
|
392
|
+
data.push(doc);
|
|
393
|
+
}
|
|
394
|
+
const hasMore = page * limit < res.found;
|
|
395
|
+
const nextCursor = hasMore ? String(page + 1) : null;
|
|
396
|
+
return { data, nextCursor, total: res.found };
|
|
431
397
|
}
|
|
432
|
-
count(filter) {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
const { data } = yield this.axios({
|
|
438
|
-
method: "GET",
|
|
439
|
-
url: `/collections/${this.options.name}`,
|
|
440
|
-
});
|
|
441
|
-
return data.num_documents;
|
|
442
|
-
}
|
|
443
|
-
const { data } = yield this.axios({
|
|
398
|
+
async count(filter) {
|
|
399
|
+
await this.ensureSynced();
|
|
400
|
+
const filterBy = this.buildFilter(filter).join("&&");
|
|
401
|
+
if (!filterBy) {
|
|
402
|
+
const { data } = await this.axios({
|
|
444
403
|
method: "GET",
|
|
445
|
-
url: `/collections/${this.options.name}
|
|
446
|
-
params: {
|
|
447
|
-
q: "*",
|
|
448
|
-
query_by: this.options.defaultSearchField,
|
|
449
|
-
per_page: 0,
|
|
450
|
-
filter_by: filterBy,
|
|
451
|
-
},
|
|
404
|
+
url: `/collections/${this.options.name}`,
|
|
452
405
|
});
|
|
453
|
-
return data.
|
|
406
|
+
return data.num_documents;
|
|
407
|
+
}
|
|
408
|
+
const { data } = await this.axios({
|
|
409
|
+
method: "GET",
|
|
410
|
+
url: `/collections/${this.options.name}/documents/search`,
|
|
411
|
+
params: {
|
|
412
|
+
q: "*",
|
|
413
|
+
query_by: this.options.defaultSearchField,
|
|
414
|
+
per_page: 0,
|
|
415
|
+
filter_by: filterBy,
|
|
416
|
+
},
|
|
454
417
|
});
|
|
418
|
+
return data.found;
|
|
455
419
|
}
|
|
456
|
-
upsert(docs) {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
this.options.schema.assert(item);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
const payload = items.map((item) => JSON.stringify(this.serializeDoc(item))).join("\n");
|
|
468
|
-
const params = { action: "upsert" };
|
|
469
|
-
if (this.options.batchSize) {
|
|
470
|
-
params.batch_size = this.options.batchSize;
|
|
420
|
+
async upsert(docs) {
|
|
421
|
+
await this.ensureSynced();
|
|
422
|
+
const items = Array.isArray(docs) ? docs : [docs];
|
|
423
|
+
if (!items.length)
|
|
424
|
+
return [];
|
|
425
|
+
if (this.options.validateOnUpsert) {
|
|
426
|
+
for (const item of items) {
|
|
427
|
+
this.options.schema.assert(item);
|
|
471
428
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
}
|
|
482
|
-
|
|
429
|
+
}
|
|
430
|
+
const payload = items.map((item) => JSON.stringify(this.serializeDoc(item))).join("\n");
|
|
431
|
+
const params = { action: "upsert" };
|
|
432
|
+
if (this.options.batchSize) {
|
|
433
|
+
params.batch_size = this.options.batchSize;
|
|
434
|
+
}
|
|
435
|
+
const { data } = await this.axios({
|
|
436
|
+
method: "POST",
|
|
437
|
+
url: `/collections/${this.options.name}/documents/import`,
|
|
438
|
+
headers: { "Content-Type": "text/plain" },
|
|
439
|
+
params,
|
|
440
|
+
data: payload,
|
|
483
441
|
});
|
|
442
|
+
if (typeof data === "string") {
|
|
443
|
+
return data.split("\n").map((v) => JSON.parse(v));
|
|
444
|
+
}
|
|
445
|
+
return [data];
|
|
484
446
|
}
|
|
485
|
-
syncData(options) {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
else
|
|
502
|
-
failed++;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
let deleted = 0;
|
|
506
|
-
if (options === null || options === void 0 ? void 0 : options.purge) {
|
|
507
|
-
deleted = yield this.purgeOrphans(ids, chunkSize);
|
|
447
|
+
async syncData(options) {
|
|
448
|
+
if (!this.dataSyncConfig) {
|
|
449
|
+
throw new Error("DATA_SYNC_NOT_CONFIGURED");
|
|
450
|
+
}
|
|
451
|
+
const chunkSize = options?.chunkSize ?? this.dataSyncConfig.chunkSize ?? 500;
|
|
452
|
+
const ids = options?.ids ?? (await this.dataSyncConfig.getAllIds());
|
|
453
|
+
let upserted = 0;
|
|
454
|
+
let failed = 0;
|
|
455
|
+
for (const chunk of chunkArray(ids, chunkSize)) {
|
|
456
|
+
const items = await this.dataSyncConfig.getItems(chunk);
|
|
457
|
+
const results = await this.upsert(items);
|
|
458
|
+
for (const r of results) {
|
|
459
|
+
if (r.success)
|
|
460
|
+
upserted++;
|
|
461
|
+
else
|
|
462
|
+
failed++;
|
|
508
463
|
}
|
|
509
|
-
|
|
510
|
-
|
|
464
|
+
}
|
|
465
|
+
let deleted = 0;
|
|
466
|
+
if (options?.purge) {
|
|
467
|
+
deleted = await this.purgeOrphans(ids, chunkSize);
|
|
468
|
+
}
|
|
469
|
+
return { upserted, deleted, failed };
|
|
511
470
|
}
|
|
512
|
-
purgeOrphans(validIds, chunkSize) {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
return deleted;
|
|
525
|
-
});
|
|
471
|
+
async purgeOrphans(validIds, chunkSize) {
|
|
472
|
+
const validSet = new Set(validIds);
|
|
473
|
+
const remoteIds = await this.exportIds();
|
|
474
|
+
const orphans = remoteIds.filter((id) => !validSet.has(id));
|
|
475
|
+
if (!orphans.length)
|
|
476
|
+
return 0;
|
|
477
|
+
let deleted = 0;
|
|
478
|
+
for (const chunk of chunkArray(orphans, chunkSize)) {
|
|
479
|
+
const result = await this.deleteMany({ id: chunk });
|
|
480
|
+
deleted += result.deleted;
|
|
481
|
+
}
|
|
482
|
+
return deleted;
|
|
526
483
|
}
|
|
527
|
-
exportIds() {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
params: { include_fields: "id" },
|
|
533
|
-
});
|
|
534
|
-
return data
|
|
535
|
-
.split("\n")
|
|
536
|
-
.filter((line) => line.length)
|
|
537
|
-
.map((line) => JSON.parse(line).id);
|
|
484
|
+
async exportIds() {
|
|
485
|
+
const { data } = await this.axios({
|
|
486
|
+
method: "GET",
|
|
487
|
+
url: `/collections/${this.options.name}/documents/export`,
|
|
488
|
+
params: { include_fields: "id" },
|
|
538
489
|
});
|
|
490
|
+
return data
|
|
491
|
+
.split("\n")
|
|
492
|
+
.filter((line) => line.length)
|
|
493
|
+
.map((line) => JSON.parse(line).id);
|
|
539
494
|
}
|
|
540
495
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tsense",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Opinionated, fully typed typesense client",
|
|
6
6
|
"keywords": [
|
|
@@ -38,15 +38,14 @@
|
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
41
|
-
"@
|
|
42
|
-
"@changesets/cli": "^2.29.7",
|
|
41
|
+
"@changesets/cli": "^2.30.0",
|
|
43
42
|
"@types/bun": "latest",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
43
|
+
"@typescript/native-preview": "^7.0.0-dev.20260324.1",
|
|
44
|
+
"arktype": "^2.2.0",
|
|
45
|
+
"oxfmt": "^0.42.0",
|
|
46
|
+
"oxlint": "^1.57.0"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"arktype": "^2.1.29"
|
|
50
|
-
"typescript": "^5"
|
|
49
|
+
"arktype": "^2.1.29"
|
|
51
50
|
}
|
|
52
51
|
}
|