tsense 0.0.14 → 0.0.15
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 +52 -2
- package/dist/index.d.ts +1 -1
- package/dist/tsense.d.ts +8 -3
- package/dist/tsense.js +77 -16
- package/dist/types.d.ts +17 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -89,8 +89,8 @@ type("string").configure({
|
|
|
89
89
|
|
|
90
90
|
### Collection Methods
|
|
91
91
|
|
|
92
|
-
| Method | Description |
|
|
93
|
-
|
|
|
92
|
+
| Method/Property | Description |
|
|
93
|
+
| --------------- | ----------- |
|
|
94
94
|
| `create()` | Creates the collection in Typesense |
|
|
95
95
|
| `drop()` | Deletes the collection |
|
|
96
96
|
| `get(id)` | Retrieves a document by ID |
|
|
@@ -100,7 +100,57 @@ type("string").configure({
|
|
|
100
100
|
| `updateMany(filter, data)` | Updates documents matching filter |
|
|
101
101
|
| `upsert(docs)` | Inserts or updates documents |
|
|
102
102
|
| `search(options)` | Searches the collection |
|
|
103
|
+
| `syncSchema()` | Syncs schema (creates/patches collection) |
|
|
104
|
+
| `syncData(options)` | Syncs data from external source |
|
|
105
|
+
| `fields` | Array of generated field schemas |
|
|
103
106
|
|
|
107
|
+
### Schema Sync
|
|
108
|
+
|
|
109
|
+
Automatically sync schema before the first operation:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
const Collection = new TSense({
|
|
113
|
+
// ...
|
|
114
|
+
autoSyncSchema: true,
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Or manually:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
await Collection.syncSchema();
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Data Sync
|
|
125
|
+
|
|
126
|
+
Sync documents from an external source (database, API, etc.):
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const Collection = new TSense({
|
|
130
|
+
// ...
|
|
131
|
+
dataSync: {
|
|
132
|
+
getAllIds: async () => {
|
|
133
|
+
return db.selectFrom("users").select("id").execute().then(rows => rows.map(r => r.id));
|
|
134
|
+
},
|
|
135
|
+
getItems: async (ids) => {
|
|
136
|
+
return db.selectFrom("users").where("id", "in", ids).execute();
|
|
137
|
+
},
|
|
138
|
+
chunkSize: 100, // optional, default 500
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Full sync
|
|
143
|
+
await Collection.syncData();
|
|
144
|
+
|
|
145
|
+
// Partial sync (specific IDs)
|
|
146
|
+
await Collection.syncData({ ids: ["id1", "id2"] });
|
|
147
|
+
|
|
148
|
+
// Full sync + remove orphan documents
|
|
149
|
+
await Collection.syncData({ purge: true });
|
|
150
|
+
|
|
151
|
+
// Override chunk size
|
|
152
|
+
await Collection.syncData({ chunkSize: 50 });
|
|
153
|
+
```
|
|
104
154
|
|
|
105
155
|
### Filter Syntax
|
|
106
156
|
|
package/dist/index.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ export { DateTransformer } from "./transformers/date.js";
|
|
|
3
3
|
export { defaultTransformers } from "./transformers/defaults.js";
|
|
4
4
|
export type { FieldTransformer } from "./transformers/types.js";
|
|
5
5
|
export { TSense } from "./tsense.js";
|
|
6
|
-
export type { ConnectionConfig, DeleteResult, FilterFor, HighlightOptions, SearchListOptions, SearchListResult, SearchOptions, SearchResult, TsenseOptions, UpdateResult, UpsertResult, } from "./types.js";
|
|
6
|
+
export type { ConnectionConfig, DeleteResult, FilterFor, HighlightOptions, SearchListOptions, SearchListResult, SearchOptions, SearchResult, SyncConfig, SyncOptions, SyncResult, TsenseOptions, UpdateResult, UpsertResult, } from "./types.js";
|
package/dist/tsense.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Type } from "arktype";
|
|
2
2
|
import redaxios from "redaxios";
|
|
3
|
-
import type { DeleteResult, FilterFor, SearchListOptions, SearchListResult, SearchOptions, SearchResult, TsenseOptions, UpdateResult, UpsertResult } from "./types.js";
|
|
3
|
+
import type { DeleteResult, FieldSchema, FilterFor, SearchListOptions, SearchListResult, SearchOptions, SearchResult, SyncOptions, SyncResult, TsenseOptions, UpdateResult, UpsertResult } from "./types.js";
|
|
4
4
|
declare const redaxiosInstance: {
|
|
5
5
|
<T>(urlOrConfig: string | redaxios.Options, config?: redaxios.Options | undefined, _method?: any, data?: any, _undefined?: undefined): Promise<redaxios.Response<T>>;
|
|
6
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>>);
|
|
@@ -59,11 +59,13 @@ declare const redaxiosInstance: {
|
|
|
59
59
|
export type AxiosInstance = ReturnType<typeof redaxiosInstance.create>;
|
|
60
60
|
export declare class TSense<T extends Type> {
|
|
61
61
|
private options;
|
|
62
|
-
|
|
62
|
+
fields: FieldSchema[];
|
|
63
63
|
private axios;
|
|
64
64
|
private synced;
|
|
65
65
|
private fieldTransformers;
|
|
66
|
+
private dataSyncConfig?;
|
|
66
67
|
infer: T["infer"];
|
|
68
|
+
getFields(): FieldSchema[];
|
|
67
69
|
constructor(options: TsenseOptions<T>);
|
|
68
70
|
private getBaseType;
|
|
69
71
|
private inferType;
|
|
@@ -72,7 +74,7 @@ export declare class TSense<T extends Type> {
|
|
|
72
74
|
private serializeFilterValue;
|
|
73
75
|
private extractFields;
|
|
74
76
|
private ensureSynced;
|
|
75
|
-
|
|
77
|
+
syncSchema(): Promise<void>;
|
|
76
78
|
private buildObjectFilter;
|
|
77
79
|
private buildFilter;
|
|
78
80
|
private buildSort;
|
|
@@ -87,5 +89,8 @@ export declare class TSense<T extends Type> {
|
|
|
87
89
|
searchList(options: SearchListOptions<T["infer"]>): Promise<SearchListResult<T["infer"]>>;
|
|
88
90
|
count(filter?: FilterFor<T["infer"]>): Promise<number>;
|
|
89
91
|
upsert(docs: T["infer"] | T["infer"][]): Promise<UpsertResult[]>;
|
|
92
|
+
syncData(options?: SyncOptions): Promise<SyncResult>;
|
|
93
|
+
private purgeOrphans;
|
|
94
|
+
private exportIds;
|
|
90
95
|
}
|
|
91
96
|
export {};
|
package/dist/tsense.js
CHANGED
|
@@ -11,17 +11,28 @@ var _a;
|
|
|
11
11
|
import redaxios from "redaxios";
|
|
12
12
|
import { TSenseMigrator } from "./migrator.js";
|
|
13
13
|
import { defaultTransformers } from "./transformers/defaults.js";
|
|
14
|
+
function chunkArray(arr, size) {
|
|
15
|
+
const chunks = [];
|
|
16
|
+
for (let i = 0; i < arr.length; i += size) {
|
|
17
|
+
chunks.push(arr.slice(i, i + size));
|
|
18
|
+
}
|
|
19
|
+
return chunks;
|
|
20
|
+
}
|
|
14
21
|
const redaxiosInstance = (_a = redaxios.default) !== null && _a !== void 0 ? _a : redaxios;
|
|
15
22
|
const arkToTsense = {
|
|
16
23
|
string: "string",
|
|
17
24
|
number: "float",
|
|
18
25
|
"number.integer": "int64",
|
|
26
|
+
"number % 1": "int64",
|
|
19
27
|
boolean: "bool",
|
|
20
28
|
"string[]": "string[]",
|
|
21
29
|
"number[]": "float[]",
|
|
22
30
|
"boolean[]": "bool[]",
|
|
23
31
|
};
|
|
24
32
|
export class TSense {
|
|
33
|
+
getFields() {
|
|
34
|
+
return this.fields;
|
|
35
|
+
}
|
|
25
36
|
constructor(options) {
|
|
26
37
|
var _a;
|
|
27
38
|
this.options = options;
|
|
@@ -34,6 +45,7 @@ export class TSense {
|
|
|
34
45
|
headers: { "X-TYPESENSE-API-KEY": options.connection.apiKey },
|
|
35
46
|
});
|
|
36
47
|
this.extractFields((_a = options.transformers) !== null && _a !== void 0 ? _a : defaultTransformers);
|
|
48
|
+
this.dataSyncConfig = options.dataSync;
|
|
37
49
|
}
|
|
38
50
|
getBaseType(expression, domain) {
|
|
39
51
|
if (domain && domain !== "undefined")
|
|
@@ -94,14 +106,15 @@ export class TSense {
|
|
|
94
106
|
return transformer.serialize(value);
|
|
95
107
|
}
|
|
96
108
|
extractFields(transformers) {
|
|
97
|
-
var _a;
|
|
109
|
+
var _a, _b, _c, _d;
|
|
98
110
|
const internal = this.options.schema;
|
|
99
111
|
for (const prop of internal.structure.props) {
|
|
100
|
-
const
|
|
112
|
+
const innerType = (_b = (_a = prop.value.branches) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : prop.value;
|
|
113
|
+
const meta = ((_c = innerType.meta) !== null && _c !== void 0 ? _c : prop.value.meta);
|
|
101
114
|
const expression = String(prop.value.expression);
|
|
102
115
|
const domain = prop.value.domain;
|
|
103
116
|
const baseType = this.getBaseType(expression, domain);
|
|
104
|
-
const transformer = transformers.find((t) => t.match(baseType, domain));
|
|
117
|
+
const transformer = transformers.find((t) => t.match(expression, domain) || t.match(baseType, domain));
|
|
105
118
|
if (transformer) {
|
|
106
119
|
this.fieldTransformers.set(prop.key, transformer);
|
|
107
120
|
this.fields.push({
|
|
@@ -114,7 +127,7 @@ export class TSense {
|
|
|
114
127
|
});
|
|
115
128
|
continue;
|
|
116
129
|
}
|
|
117
|
-
const type = (
|
|
130
|
+
const type = (_d = meta === null || meta === void 0 ? void 0 : meta.type) !== null && _d !== void 0 ? _d : this.inferType(baseType);
|
|
118
131
|
this.fields.push({
|
|
119
132
|
name: prop.key,
|
|
120
133
|
type,
|
|
@@ -127,13 +140,13 @@ export class TSense {
|
|
|
127
140
|
}
|
|
128
141
|
ensureSynced(force) {
|
|
129
142
|
return __awaiter(this, void 0, void 0, function* () {
|
|
130
|
-
if (!force && (this.synced || !this.options.
|
|
143
|
+
if (!force && (this.synced || !this.options.autoSyncSchema))
|
|
131
144
|
return;
|
|
132
145
|
yield new TSenseMigrator(this.options.name, this.fields, this.options.defaultSortingField, this.axios).sync();
|
|
133
146
|
this.synced = true;
|
|
134
147
|
});
|
|
135
148
|
}
|
|
136
|
-
|
|
149
|
+
syncSchema() {
|
|
137
150
|
return __awaiter(this, void 0, void 0, function* () {
|
|
138
151
|
yield this.ensureSynced(true);
|
|
139
152
|
});
|
|
@@ -224,7 +237,6 @@ export class TSense {
|
|
|
224
237
|
}
|
|
225
238
|
drop() {
|
|
226
239
|
return __awaiter(this, void 0, void 0, function* () {
|
|
227
|
-
console.log("dropping");
|
|
228
240
|
yield this.axios({
|
|
229
241
|
method: "DELETE",
|
|
230
242
|
url: `/collections/${this.options.name}`,
|
|
@@ -309,9 +321,7 @@ export class TSense {
|
|
|
309
321
|
yield this.ensureSynced();
|
|
310
322
|
const params = {
|
|
311
323
|
q: (_a = options.query) !== null && _a !== void 0 ? _a : "",
|
|
312
|
-
query_by: ((_b = options.queryBy) !== null && _b !== void 0 ? _b : [
|
|
313
|
-
this.options.defaultSearchField,
|
|
314
|
-
]).join(","),
|
|
324
|
+
query_by: ((_b = options.queryBy) !== null && _b !== void 0 ? _b : [this.options.defaultSearchField]).join(","),
|
|
315
325
|
};
|
|
316
326
|
const sortBy = this.buildSort(options);
|
|
317
327
|
if (sortBy)
|
|
@@ -394,9 +404,7 @@ export class TSense {
|
|
|
394
404
|
const page = options.cursor ? Number(options.cursor) : 1;
|
|
395
405
|
const params = {
|
|
396
406
|
q: (_b = options.query) !== null && _b !== void 0 ? _b : "",
|
|
397
|
-
query_by: ((_c = options.queryBy) !== null && _c !== void 0 ? _c : [
|
|
398
|
-
this.options.defaultSearchField,
|
|
399
|
-
]).join(","),
|
|
407
|
+
query_by: ((_c = options.queryBy) !== null && _c !== void 0 ? _c : [this.options.defaultSearchField]).join(","),
|
|
400
408
|
per_page: limit,
|
|
401
409
|
page,
|
|
402
410
|
sort_by: `${field}:${options.sort.direction}`,
|
|
@@ -456,9 +464,7 @@ export class TSense {
|
|
|
456
464
|
this.options.schema.assert(item);
|
|
457
465
|
}
|
|
458
466
|
}
|
|
459
|
-
const payload = items
|
|
460
|
-
.map((item) => JSON.stringify(this.serializeDoc(item)))
|
|
461
|
-
.join("\n");
|
|
467
|
+
const payload = items.map((item) => JSON.stringify(this.serializeDoc(item))).join("\n");
|
|
462
468
|
const params = { action: "upsert" };
|
|
463
469
|
if (this.options.batchSize) {
|
|
464
470
|
params.batch_size = this.options.batchSize;
|
|
@@ -476,4 +482,59 @@ export class TSense {
|
|
|
476
482
|
return [data];
|
|
477
483
|
});
|
|
478
484
|
}
|
|
485
|
+
syncData(options) {
|
|
486
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
487
|
+
var _a, _b, _c;
|
|
488
|
+
if (!this.dataSyncConfig) {
|
|
489
|
+
throw new Error("DATA_SYNC_NOT_CONFIGURED");
|
|
490
|
+
}
|
|
491
|
+
const chunkSize = (_b = (_a = options === null || options === void 0 ? void 0 : options.chunkSize) !== null && _a !== void 0 ? _a : this.dataSyncConfig.chunkSize) !== null && _b !== void 0 ? _b : 500;
|
|
492
|
+
const ids = (_c = options === null || options === void 0 ? void 0 : options.ids) !== null && _c !== void 0 ? _c : (yield this.dataSyncConfig.getAllIds());
|
|
493
|
+
let upserted = 0;
|
|
494
|
+
let failed = 0;
|
|
495
|
+
for (const chunk of chunkArray(ids, chunkSize)) {
|
|
496
|
+
const items = yield this.dataSyncConfig.getItems(chunk);
|
|
497
|
+
const results = yield this.upsert(items);
|
|
498
|
+
for (const r of results) {
|
|
499
|
+
if (r.success)
|
|
500
|
+
upserted++;
|
|
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);
|
|
508
|
+
}
|
|
509
|
+
return { upserted, deleted, failed };
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
purgeOrphans(validIds, chunkSize) {
|
|
513
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
514
|
+
const validSet = new Set(validIds);
|
|
515
|
+
const remoteIds = yield this.exportIds();
|
|
516
|
+
const orphans = remoteIds.filter((id) => !validSet.has(id));
|
|
517
|
+
if (!orphans.length)
|
|
518
|
+
return 0;
|
|
519
|
+
let deleted = 0;
|
|
520
|
+
for (const chunk of chunkArray(orphans, chunkSize)) {
|
|
521
|
+
const result = yield this.deleteMany({ id: chunk });
|
|
522
|
+
deleted += result.deleted;
|
|
523
|
+
}
|
|
524
|
+
return deleted;
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
exportIds() {
|
|
528
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
529
|
+
const { data } = yield this.axios({
|
|
530
|
+
method: "GET",
|
|
531
|
+
url: `/collections/${this.options.name}/documents/export`,
|
|
532
|
+
params: { include_fields: "id" },
|
|
533
|
+
});
|
|
534
|
+
return data
|
|
535
|
+
.split("\n")
|
|
536
|
+
.filter((line) => line.length)
|
|
537
|
+
.map((line) => JSON.parse(line).id);
|
|
538
|
+
});
|
|
539
|
+
}
|
|
479
540
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -42,8 +42,9 @@ export type TsenseOptions<T extends Type> = {
|
|
|
42
42
|
defaultSortingField?: keyof T["infer"];
|
|
43
43
|
batchSize?: number;
|
|
44
44
|
validateOnUpsert?: boolean;
|
|
45
|
-
|
|
45
|
+
autoSyncSchema?: boolean;
|
|
46
46
|
transformers?: FieldTransformer[];
|
|
47
|
+
dataSync?: SyncConfig<T["infer"]>;
|
|
47
48
|
};
|
|
48
49
|
type SingleFilter<T> = Partial<{
|
|
49
50
|
[K in keyof T]: BaseIfArray<T[K]> | NonNullable<BaseIfArray<T[K]>>[] | {
|
|
@@ -116,4 +117,19 @@ export type SearchListResult<T> = {
|
|
|
116
117
|
nextCursor: string | null;
|
|
117
118
|
total: number;
|
|
118
119
|
};
|
|
120
|
+
export type SyncConfig<T> = {
|
|
121
|
+
getAllIds: () => Promise<string[]>;
|
|
122
|
+
getItems: (ids: string[]) => Promise<T[]>;
|
|
123
|
+
chunkSize?: number;
|
|
124
|
+
};
|
|
125
|
+
export type SyncOptions = {
|
|
126
|
+
ids?: string[];
|
|
127
|
+
purge?: boolean;
|
|
128
|
+
chunkSize?: number;
|
|
129
|
+
};
|
|
130
|
+
export type SyncResult = {
|
|
131
|
+
upserted: number;
|
|
132
|
+
deleted: number;
|
|
133
|
+
failed: number;
|
|
134
|
+
};
|
|
119
135
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tsense",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Opinionated, fully typed typesense client",
|
|
6
6
|
"keywords": [
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"@changesets/cli": "^2.29.7",
|
|
43
43
|
"@types/bun": "latest",
|
|
44
44
|
"arktype": "^2.1.29",
|
|
45
|
-
"oxfmt": "^0.
|
|
46
|
-
"oxlint": "^1.
|
|
45
|
+
"oxfmt": "^0.28.0",
|
|
46
|
+
"oxlint": "^1.43.0"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"arktype": "^2.1.29",
|