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 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
- 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>);
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
- sync(): Promise<void>;
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 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);
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 = (_a = meta === null || meta === void 0 ? void 0 : meta.type) !== null && _a !== void 0 ? _a : this.inferType(baseType);
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.autoSync))
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
- sync() {
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
- 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.14",
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.27.0",
46
- "oxlint": "^1.42.0"
45
+ "oxfmt": "^0.28.0",
46
+ "oxlint": "^1.43.0"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "arktype": "^2.1.29",