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 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
- private fields;
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
- sync(): Promise<void>;
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 meta = prop.value.meta;
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 transformer = transformers.find((t) => t.match(expression, domain));
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 = (_a = meta === null || meta === void 0 ? void 0 : meta.type) !== null && _a !== void 0 ? _a : this.inferType(domain !== null && domain !== void 0 ? domain : expression);
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.autoSync))
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
- sync() {
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
- autoSync?: boolean;
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.13",
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.23.0",
46
- "oxlint": "^1.38.0"
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
+ }