tsense 0.0.13 → 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 +10 -3
- package/dist/tsense.js +106 -8
- package/dist/types.d.ts +17 -1
- package/package.json +4 -4
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,19 +59,22 @@ 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>);
|
|
70
|
+
private getBaseType;
|
|
68
71
|
private inferType;
|
|
69
72
|
private serializeDoc;
|
|
70
73
|
private deserializeDoc;
|
|
71
74
|
private serializeFilterValue;
|
|
72
75
|
private extractFields;
|
|
73
76
|
private ensureSynced;
|
|
74
|
-
|
|
77
|
+
syncSchema(): Promise<void>;
|
|
75
78
|
private buildObjectFilter;
|
|
76
79
|
private buildFilter;
|
|
77
80
|
private buildSort;
|
|
@@ -84,6 +87,10 @@ export declare class TSense<T extends Type> {
|
|
|
84
87
|
updateMany(filter: FilterFor<T["infer"]>, data: Partial<T["infer"]>): Promise<UpdateResult>;
|
|
85
88
|
search(options: SearchOptions<T["infer"]>): Promise<SearchResult<T["infer"]>>;
|
|
86
89
|
searchList(options: SearchListOptions<T["infer"]>): Promise<SearchListResult<T["infer"]>>;
|
|
90
|
+
count(filter?: FilterFor<T["infer"]>): Promise<number>;
|
|
87
91
|
upsert(docs: T["infer"] | T["infer"][]): Promise<UpsertResult[]>;
|
|
92
|
+
syncData(options?: SyncOptions): Promise<SyncResult>;
|
|
93
|
+
private purgeOrphans;
|
|
94
|
+
private exportIds;
|
|
88
95
|
}
|
|
89
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,12 @@ 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;
|
|
49
|
+
}
|
|
50
|
+
getBaseType(expression, domain) {
|
|
51
|
+
if (domain && domain !== "undefined")
|
|
52
|
+
return domain;
|
|
53
|
+
return expression.replace(/ \| undefined$/, "");
|
|
37
54
|
}
|
|
38
55
|
inferType(arkType) {
|
|
39
56
|
const direct = arkToTsense[arkType];
|
|
@@ -41,10 +58,10 @@ export class TSense {
|
|
|
41
58
|
return direct;
|
|
42
59
|
if (arkType.includes("[]"))
|
|
43
60
|
return "object[]";
|
|
61
|
+
if (arkType.includes("'") || arkType.includes('"'))
|
|
62
|
+
return "string";
|
|
44
63
|
if (arkType.includes("{") || arkType.includes("|"))
|
|
45
64
|
return "object";
|
|
46
|
-
if (arkType.startsWith("'") || arkType.includes("'"))
|
|
47
|
-
return "string";
|
|
48
65
|
return "string";
|
|
49
66
|
}
|
|
50
67
|
serializeDoc(doc) {
|
|
@@ -89,13 +106,15 @@ export class TSense {
|
|
|
89
106
|
return transformer.serialize(value);
|
|
90
107
|
}
|
|
91
108
|
extractFields(transformers) {
|
|
92
|
-
var _a;
|
|
109
|
+
var _a, _b, _c, _d;
|
|
93
110
|
const internal = this.options.schema;
|
|
94
111
|
for (const prop of internal.structure.props) {
|
|
95
|
-
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);
|
|
96
114
|
const expression = String(prop.value.expression);
|
|
97
115
|
const domain = prop.value.domain;
|
|
98
|
-
const
|
|
116
|
+
const baseType = this.getBaseType(expression, domain);
|
|
117
|
+
const transformer = transformers.find((t) => t.match(expression, domain) || t.match(baseType, domain));
|
|
99
118
|
if (transformer) {
|
|
100
119
|
this.fieldTransformers.set(prop.key, transformer);
|
|
101
120
|
this.fields.push({
|
|
@@ -108,7 +127,7 @@ export class TSense {
|
|
|
108
127
|
});
|
|
109
128
|
continue;
|
|
110
129
|
}
|
|
111
|
-
const type = (
|
|
130
|
+
const type = (_d = meta === null || meta === void 0 ? void 0 : meta.type) !== null && _d !== void 0 ? _d : this.inferType(baseType);
|
|
112
131
|
this.fields.push({
|
|
113
132
|
name: prop.key,
|
|
114
133
|
type,
|
|
@@ -121,13 +140,13 @@ export class TSense {
|
|
|
121
140
|
}
|
|
122
141
|
ensureSynced(force) {
|
|
123
142
|
return __awaiter(this, void 0, void 0, function* () {
|
|
124
|
-
if (!force && (this.synced || !this.options.
|
|
143
|
+
if (!force && (this.synced || !this.options.autoSyncSchema))
|
|
125
144
|
return;
|
|
126
145
|
yield new TSenseMigrator(this.options.name, this.fields, this.options.defaultSortingField, this.axios).sync();
|
|
127
146
|
this.synced = true;
|
|
128
147
|
});
|
|
129
148
|
}
|
|
130
|
-
|
|
149
|
+
syncSchema() {
|
|
131
150
|
return __awaiter(this, void 0, void 0, function* () {
|
|
132
151
|
yield this.ensureSynced(true);
|
|
133
152
|
});
|
|
@@ -410,6 +429,30 @@ export class TSense {
|
|
|
410
429
|
return { data, nextCursor, total: res.found };
|
|
411
430
|
});
|
|
412
431
|
}
|
|
432
|
+
count(filter) {
|
|
433
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
434
|
+
yield this.ensureSynced();
|
|
435
|
+
const filterBy = this.buildFilter(filter).join("&&");
|
|
436
|
+
if (!filterBy) {
|
|
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({
|
|
444
|
+
method: "GET",
|
|
445
|
+
url: `/collections/${this.options.name}/documents/search`,
|
|
446
|
+
params: {
|
|
447
|
+
q: "*",
|
|
448
|
+
query_by: this.options.defaultSearchField,
|
|
449
|
+
per_page: 0,
|
|
450
|
+
filter_by: filterBy,
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
return data.found;
|
|
454
|
+
});
|
|
455
|
+
}
|
|
413
456
|
upsert(docs) {
|
|
414
457
|
return __awaiter(this, void 0, void 0, function* () {
|
|
415
458
|
yield this.ensureSynced();
|
|
@@ -439,4 +482,59 @@ export class TSense {
|
|
|
439
482
|
return [data];
|
|
440
483
|
});
|
|
441
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
|
+
}
|
|
442
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,11 +42,11 @@
|
|
|
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",
|
|
50
50
|
"typescript": "^5"
|
|
51
51
|
}
|
|
52
|
-
}
|
|
52
|
+
}
|