@pulseindex/sdk 4.0.1 → 5.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,105 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.0.0
4
+
5
+ Needs a PulseIndex engine at v2.0.0 or later. The wire contract is a
6
+ breaking change: an SDK at 4.x cannot talk to a v2 engine, and this cannot
7
+ talk to a v1 one.
8
+
9
+ ### A radius that means what it says, and "the nearest K"
10
+
11
+ A circle used to be a union of geohash cells, and a union of cells is a superset
12
+ of the circle. Measured against a million entities, a 1 km search returned
13
+ **2,479** rows where **1,241** were really inside — and nothing in the answer
14
+ said which. You hydrated all 2,479 from your own store and measured them again.
15
+
16
+ Send a position with the record and the engine settles the edge itself:
17
+
18
+ points: { where: { lat: 41.0369, lon: 28.9850 } }
19
+
20
+ Then `withinRadius({ ..., field: 'where' })` narrows on the cells and measures
21
+ the true distance, or `within('where', lat, lon, km)` measures without them.
22
+ Degrees go on the wire and the engine packs them: a representation split between
23
+ this package and the engine, with nothing comparing the two, is how the geo
24
+ defects in 4.0.0 happened.
25
+
26
+ `nearest('where', lat, lon)` orders by distance, nearest first. That was not a
27
+ question you could ask before at any price. It needs no radius guessed to make
28
+ it quick, and combines with every other filter, so "the closest ten available
29
+ drivers with room" is one request.
30
+
31
+ Ordering is to the centimetre, which is the precision a stored position has.
32
+
33
+
34
+ ### The result count now says whether it is the whole count
35
+
36
+ A paged search stops as soon as the page is full, so the `totalMatches` it
37
+ carried was only what the engine had reached by then. Nothing said so, and it
38
+ does not look partial: measured on 500,000 records that all matched, a page of
39
+ 20 reported **65,536**; a harder query reported **48,105** against a true total
40
+ of **333,895**. Any interface printing "N results" from that was wrong by
41
+ several times over and looked fine.
42
+
43
+ `totalIsExact` now comes from the engine instead of being guessed from
44
+ `limit === 0`, and `searchWithTotal()` is **one request instead of two** — the
45
+ wire can ask for a page and a true count together. A page whose matches all fit
46
+ inside it is reported exact, which the old guess got wrong.
47
+
48
+
49
+ ### A record carries whatever numbers you name, and no field the engine chose
50
+
51
+ An entity used to be forced through one `uint32` called `price` and one `uint64`
52
+ bitfield called `locationPrefix`. That is a schema this SDK had no business
53
+ imposing: one number per record, under a name we picked, with no negatives, and
54
+ nothing above 4,294,967,295. A rating, a capacity, a timestamp, an elevation and
55
+ a balance were all the same field or no field at all.
56
+
57
+ Both are gone. A record now carries `numbers`, under your own names:
58
+
59
+ client.index('1001', {
60
+ categories: ['feature:pool'],
61
+ numbers: { price_cents: 45000, bedrooms: 3, built_at: 1712000000 },
62
+ })
63
+
64
+ Any name, any 64-bit integer, any number of them, and every one is filterable
65
+ through `range()` and orderable through `sortBy()`. A name means nothing to the
66
+ engine beyond its hash.
67
+
68
+ ### Three things this fixes that were losing data quietly
69
+
70
+ **A fraction was floored without a word.** `4.3` was sent as `4`, `0.5` as `0`,
71
+ `199.99` as `199` — measured, not inferred. The engine's column is a 64-bit
72
+ integer, so a fraction is refused now, with the scaling it needs named. Keep the
73
+ scale on your side: a price in cents, a rating out of 100.
74
+
75
+ **A field of your own called `price` disappeared.** Sixteen key names were
76
+ reserved out of your attributes, and anything under one was dropped: measured,
77
+ `{price: 250, rating: 4.3, lat: 41, kind: 'villa'}` came out as
78
+ `["rating:4.3", "kind:villa"]` — `price` and `lat` gone, no error. Numbers go
79
+ through `numbers` now, so nothing in your own object is swallowed.
80
+
81
+ **A range or an order on a field nothing carries was answered, not refused.**
82
+ It excluded every entity, or left the page in insertion order and reported it as
83
+ sorted. At ten million records `bedrooms 3..6` returned 0 while the tag
84
+ `bedrooms:3` returned 1,666,667. The engine refuses it by name now — but only
85
+ when the tenant holds entities and none of them carries that field, because an
86
+ empty tenant has nothing to be wrong about.
87
+
88
+ ### Migrating
89
+
90
+ - `index(id, {price: N})` → `index(id, {numbers: {price: N}})`. A top-level
91
+ `price` is no longer special; it becomes the tag `price:N` like any other
92
+ scalar attribute.
93
+ - `indexEntity(id, categories, price, locationPrefix, tenantId)` →
94
+ `indexEntity(id, categories, numbers, tenantId)`.
95
+ - `.location(prefix)` on the query builder is gone. Nothing ever sent it: both
96
+ SDKs passed 0 on every request.
97
+ - A range bound may now be negative or past 4,294,967,295.
98
+
99
+ This needs an engine built from the same commit. The proto is a breaking change:
100
+ field numbers 2 and 3 on `IndexEntityRequest`, and 1 on `SearchQueryRequest`, are
101
+ reserved rather than reused.
102
+
3
103
  ## 4.0.1
4
104
 
5
105
  ### The covering threshold, corrected against a real app
package/dist/index.d.mts CHANGED
@@ -27,19 +27,38 @@ interface SortSpec {
27
27
  field: string;
28
28
  /** Largest first when true; smallest first otherwise. */
29
29
  descending: boolean;
30
+ /**
31
+ * Order by distance from the query's geo predicate instead of by a field
32
+ * value. `field` is ignored.
33
+ *
34
+ * Ordering is to the centimetre, which is the precision a stored position
35
+ * has. Rows closer together than that tie, and a tie breaks on the entity id
36
+ * so the same query returns the same page.
37
+ */
38
+ byDistance?: boolean;
30
39
  }
31
40
  interface RangePredicate {
32
41
  field: string;
33
42
  minVal: number;
34
43
  maxVal: number;
35
44
  }
45
+ /** A circle, and the position field to measure it against. */
46
+ interface GeoPredicate {
47
+ field: string;
48
+ lat: number;
49
+ lon: number;
50
+ /** Inclusive. 0 means no radius bound, only an origin to measure from. */
51
+ radiusKm: number;
52
+ }
36
53
  interface SearchQueryRequest {
37
- locationPrefix: string;
38
54
  filters: FilterPredicate[];
55
+ geo?: GeoPredicate;
39
56
  ranges: RangePredicate[];
40
57
  limit: number;
41
58
  offset: number;
42
59
  tenantId: string;
60
+ /** Count every match rather than stopping when the page is full. */
61
+ exactTotal: boolean;
43
62
  /** Absent returns matches in entity-id order. */
44
63
  sort?: SortSpec;
45
64
  }
@@ -61,8 +80,8 @@ interface SearchResponse {
61
80
  }
62
81
  interface IndexEntityRequest {
63
82
  entityId: string;
64
- locationPrefix: string;
65
- price: number;
83
+ numbers: Record<string, number>;
84
+ points: Record<string, GeoPoint>;
66
85
  categories: string[];
67
86
  tenantId: string;
68
87
  }
@@ -88,10 +107,21 @@ interface RadiusOptions {
88
107
  lon?: number;
89
108
  radiusKm: number;
90
109
  precision?: number;
110
+ /**
111
+ * The position field to measure against, as named in `points` when indexing.
112
+ *
113
+ * Given one, the engine narrows on the geohash cells and then measures the
114
+ * true distance, so the answer holds only what is really inside the circle.
115
+ * Without it the cells are the whole answer, and a union of cells is a
116
+ * superset: measured at a million entities, a 1 km search returned 2,479
117
+ * rows where 1,241 were inside.
118
+ */
119
+ field?: string;
91
120
  }
92
121
  interface SearchRequestOptions {
93
122
  tenantId?: string;
94
- locationPrefix?: EntityId;
123
+ /** Count every match rather than stopping when the page is full. */
124
+ exactTotal?: boolean;
95
125
  must?: string | string[];
96
126
  should?: string | string[];
97
127
  mustNot?: string | string[];
@@ -118,9 +148,7 @@ interface SearchRequestOptions {
118
148
  interface EntityAttributes {
119
149
  categories?: unknown;
120
150
  tags?: unknown;
121
- price?: unknown;
122
- locationPrefix?: unknown;
123
- location_prefix?: unknown;
151
+ numbers?: unknown;
124
152
  tenantId?: unknown;
125
153
  tenant_id?: unknown;
126
154
  latitude?: unknown;
@@ -137,9 +165,7 @@ interface EntityInput {
137
165
  attributes?: EntityAttributes;
138
166
  categories?: unknown;
139
167
  tags?: unknown;
140
- price?: unknown;
141
- locationPrefix?: unknown;
142
- location_prefix?: unknown;
168
+ numbers?: unknown;
143
169
  tenantId?: unknown;
144
170
  tenant_id?: unknown;
145
171
  latitude?: unknown;
@@ -156,11 +182,21 @@ interface BatchEntityInput {
156
182
  attributes?: EntityAttributes;
157
183
  [key: string]: unknown;
158
184
  }
185
+ /** One position in degrees. The engine packs it; this SDK does not. */
186
+ interface GeoPoint {
187
+ lat: number;
188
+ lon: number;
189
+ }
159
190
  interface EncodedEntity {
160
191
  entityId: string;
161
192
  categories: string[];
162
- price: number;
163
- locationPrefix: string;
193
+ /** Positions under your own names. */
194
+ points: Record<string, GeoPoint>;
195
+ /**
196
+ * Numeric fields under your own names. Any name, any integer, any number of
197
+ * them. This replaced a single `price` field the engine named for you.
198
+ */
199
+ numbers: Record<string, number>;
164
200
  tenantId: string;
165
201
  }
166
202
  interface PulseIndexClientConfig {
@@ -198,7 +234,15 @@ declare class QueryBuilder {
198
234
  private state;
199
235
  constructor(executor?: QueryExecutor | null);
200
236
  tenant(tenantId: string): QueryBuilder;
201
- location(locationPrefix: string | number | bigint): QueryBuilder;
237
+ /**
238
+ * Count every match instead of stopping as soon as the page is full.
239
+ *
240
+ * A paged search stops early, so the `totalMatches` it carries is only what
241
+ * the engine had counted by then — a lower bound, and one that does not look
242
+ * like one. This makes the count exact in the same request; `totalIsExact`
243
+ * on the response says which you got.
244
+ */
245
+ exactTotal(enabled?: boolean): QueryBuilder;
202
246
  must(attribute: string | string[]): QueryBuilder;
203
247
  /**
204
248
  * At least one of these has to match.
@@ -216,6 +260,15 @@ declare class QueryBuilder {
216
260
  inGeoHash(geohash: string): QueryBuilder;
217
261
  withinRadius(lat: number, lon: number, radiusKm: number, precision?: number): QueryBuilder;
218
262
  withinRadius(options: RadiusOptions): QueryBuilder;
263
+ /**
264
+ * Filter on a numeric field's inclusive range.
265
+ *
266
+ * `price` is the only number an entity carries. Naming any other field is
267
+ * refused by the engine rather than answered, because a field nothing carries
268
+ * can only match nothing, and an empty page looks exactly like a real one.
269
+ * Model any other number as a category token instead: `must('bedrooms:3')`,
270
+ * or several in one SHOULD group for a range of values.
271
+ */
219
272
  range(field: string, min: number, max: number): QueryBuilder;
220
273
  /**
221
274
  * How many ids to return. Zero asks the engine for the number of matches
@@ -234,11 +287,31 @@ declare class QueryBuilder {
234
287
  /** Order the page by a numeric field, largest first. */
235
288
  sortDesc(field: string): QueryBuilder;
236
289
  /**
237
- * Order the page by a numeric field. Rows carrying no value for it sort last
238
- * in both directions; they still count towards `totalMatches`, they simply
239
- * have nothing to be ordered by.
290
+ * Order the page by a numeric field.
291
+ *
292
+ * Bounded exactly as {@link range} is: `price` is the only field an entity
293
+ * carries, and any other name is refused rather than silently ignored. An
294
+ * order by a field nothing carries used to leave the page in insertion order
295
+ * and report it as sorted.
240
296
  */
241
297
  sortBy(field: string, descending?: boolean): QueryBuilder;
298
+ /**
299
+ * Keep only entities within `radiusKm` of the point, measured exactly.
300
+ *
301
+ * This is the circle on its own. {@link withinRadius} with a `field` adds
302
+ * the geohash cells too, which is what stops the engine opening every part
303
+ * of the index to find them.
304
+ */
305
+ within(field: string, lat: number, lon: number, radiusKm: number): QueryBuilder;
306
+ /**
307
+ * Order the page by distance from the point, nearest first.
308
+ *
309
+ * Without a radius this is "the nearest K of whatever else matched"; combine
310
+ * it with {@link within} or {@link withinRadius} to bound the search as well.
311
+ * It used to be impossible: a radius returned everything inside it unordered,
312
+ * so you hydrated every id from your own store before you could sort them.
313
+ */
314
+ nearest(field: string, lat: number, lon: number): QueryBuilder;
242
315
  toRequest(defaultTenantId?: string): SearchQueryRequest;
243
316
  toArray(defaultTenantId?: string): SearchQueryRequest;
244
317
  execute(): Promise<SearchResponse>;
@@ -318,12 +391,21 @@ declare class PulseIndexClient implements QueryExecutor {
318
391
  * reported 10,866 for a page of 100. Anything that prints "page 1 of N" from
319
392
  * that number is wrong by an order of magnitude and looks fine.
320
393
  *
321
- * This sends the count query as well, so it costs two round trips and returns
322
- * a total you can divide by a page size.
394
+ * One request. This used to run the whole query twice once for the page,
395
+ * once for the count because the wire had no way to ask for both. It does
396
+ * now, so this is the same round trip with `exactTotal` set, and the total
397
+ * you get back can be divided by a page size.
323
398
  */
324
399
  searchWithTotal(query: QueryBuilder | SearchRequestOptions): Promise<SearchResponse>;
325
400
  index(entityIdOrInput: EntityId | EntityInput, attributes?: EntityAttributes): Promise<IndexEntityResponse>;
326
- indexEntity(entityId: EntityId, categories?: string[], price?: number, locationPrefix?: EntityId, tenantId?: string): Promise<boolean>;
401
+ /**
402
+ * Index one record.
403
+ *
404
+ * `numbers` are yours to name: `{price_cents: 45000, bedrooms: 3}`. This
405
+ * used to take a single `price` and a `locationPrefix`, which was a schema
406
+ * this SDK had no business imposing.
407
+ */
408
+ indexEntity(entityId: EntityId, categories?: string[], numbers?: Record<string, number>, tenantId?: string): Promise<boolean>;
327
409
  batchIndex(entities: Array<EntityInput | BatchEntityInput>): Promise<BatchIndexResponse>;
328
410
  delete(entityId: EntityId, tenantId?: string): Promise<DeleteResponse>;
329
411
  deleteEntity(entityId: EntityId, tenantId?: string): Promise<boolean>;
package/dist/index.d.ts CHANGED
@@ -27,19 +27,38 @@ interface SortSpec {
27
27
  field: string;
28
28
  /** Largest first when true; smallest first otherwise. */
29
29
  descending: boolean;
30
+ /**
31
+ * Order by distance from the query's geo predicate instead of by a field
32
+ * value. `field` is ignored.
33
+ *
34
+ * Ordering is to the centimetre, which is the precision a stored position
35
+ * has. Rows closer together than that tie, and a tie breaks on the entity id
36
+ * so the same query returns the same page.
37
+ */
38
+ byDistance?: boolean;
30
39
  }
31
40
  interface RangePredicate {
32
41
  field: string;
33
42
  minVal: number;
34
43
  maxVal: number;
35
44
  }
45
+ /** A circle, and the position field to measure it against. */
46
+ interface GeoPredicate {
47
+ field: string;
48
+ lat: number;
49
+ lon: number;
50
+ /** Inclusive. 0 means no radius bound, only an origin to measure from. */
51
+ radiusKm: number;
52
+ }
36
53
  interface SearchQueryRequest {
37
- locationPrefix: string;
38
54
  filters: FilterPredicate[];
55
+ geo?: GeoPredicate;
39
56
  ranges: RangePredicate[];
40
57
  limit: number;
41
58
  offset: number;
42
59
  tenantId: string;
60
+ /** Count every match rather than stopping when the page is full. */
61
+ exactTotal: boolean;
43
62
  /** Absent returns matches in entity-id order. */
44
63
  sort?: SortSpec;
45
64
  }
@@ -61,8 +80,8 @@ interface SearchResponse {
61
80
  }
62
81
  interface IndexEntityRequest {
63
82
  entityId: string;
64
- locationPrefix: string;
65
- price: number;
83
+ numbers: Record<string, number>;
84
+ points: Record<string, GeoPoint>;
66
85
  categories: string[];
67
86
  tenantId: string;
68
87
  }
@@ -88,10 +107,21 @@ interface RadiusOptions {
88
107
  lon?: number;
89
108
  radiusKm: number;
90
109
  precision?: number;
110
+ /**
111
+ * The position field to measure against, as named in `points` when indexing.
112
+ *
113
+ * Given one, the engine narrows on the geohash cells and then measures the
114
+ * true distance, so the answer holds only what is really inside the circle.
115
+ * Without it the cells are the whole answer, and a union of cells is a
116
+ * superset: measured at a million entities, a 1 km search returned 2,479
117
+ * rows where 1,241 were inside.
118
+ */
119
+ field?: string;
91
120
  }
92
121
  interface SearchRequestOptions {
93
122
  tenantId?: string;
94
- locationPrefix?: EntityId;
123
+ /** Count every match rather than stopping when the page is full. */
124
+ exactTotal?: boolean;
95
125
  must?: string | string[];
96
126
  should?: string | string[];
97
127
  mustNot?: string | string[];
@@ -118,9 +148,7 @@ interface SearchRequestOptions {
118
148
  interface EntityAttributes {
119
149
  categories?: unknown;
120
150
  tags?: unknown;
121
- price?: unknown;
122
- locationPrefix?: unknown;
123
- location_prefix?: unknown;
151
+ numbers?: unknown;
124
152
  tenantId?: unknown;
125
153
  tenant_id?: unknown;
126
154
  latitude?: unknown;
@@ -137,9 +165,7 @@ interface EntityInput {
137
165
  attributes?: EntityAttributes;
138
166
  categories?: unknown;
139
167
  tags?: unknown;
140
- price?: unknown;
141
- locationPrefix?: unknown;
142
- location_prefix?: unknown;
168
+ numbers?: unknown;
143
169
  tenantId?: unknown;
144
170
  tenant_id?: unknown;
145
171
  latitude?: unknown;
@@ -156,11 +182,21 @@ interface BatchEntityInput {
156
182
  attributes?: EntityAttributes;
157
183
  [key: string]: unknown;
158
184
  }
185
+ /** One position in degrees. The engine packs it; this SDK does not. */
186
+ interface GeoPoint {
187
+ lat: number;
188
+ lon: number;
189
+ }
159
190
  interface EncodedEntity {
160
191
  entityId: string;
161
192
  categories: string[];
162
- price: number;
163
- locationPrefix: string;
193
+ /** Positions under your own names. */
194
+ points: Record<string, GeoPoint>;
195
+ /**
196
+ * Numeric fields under your own names. Any name, any integer, any number of
197
+ * them. This replaced a single `price` field the engine named for you.
198
+ */
199
+ numbers: Record<string, number>;
164
200
  tenantId: string;
165
201
  }
166
202
  interface PulseIndexClientConfig {
@@ -198,7 +234,15 @@ declare class QueryBuilder {
198
234
  private state;
199
235
  constructor(executor?: QueryExecutor | null);
200
236
  tenant(tenantId: string): QueryBuilder;
201
- location(locationPrefix: string | number | bigint): QueryBuilder;
237
+ /**
238
+ * Count every match instead of stopping as soon as the page is full.
239
+ *
240
+ * A paged search stops early, so the `totalMatches` it carries is only what
241
+ * the engine had counted by then — a lower bound, and one that does not look
242
+ * like one. This makes the count exact in the same request; `totalIsExact`
243
+ * on the response says which you got.
244
+ */
245
+ exactTotal(enabled?: boolean): QueryBuilder;
202
246
  must(attribute: string | string[]): QueryBuilder;
203
247
  /**
204
248
  * At least one of these has to match.
@@ -216,6 +260,15 @@ declare class QueryBuilder {
216
260
  inGeoHash(geohash: string): QueryBuilder;
217
261
  withinRadius(lat: number, lon: number, radiusKm: number, precision?: number): QueryBuilder;
218
262
  withinRadius(options: RadiusOptions): QueryBuilder;
263
+ /**
264
+ * Filter on a numeric field's inclusive range.
265
+ *
266
+ * `price` is the only number an entity carries. Naming any other field is
267
+ * refused by the engine rather than answered, because a field nothing carries
268
+ * can only match nothing, and an empty page looks exactly like a real one.
269
+ * Model any other number as a category token instead: `must('bedrooms:3')`,
270
+ * or several in one SHOULD group for a range of values.
271
+ */
219
272
  range(field: string, min: number, max: number): QueryBuilder;
220
273
  /**
221
274
  * How many ids to return. Zero asks the engine for the number of matches
@@ -234,11 +287,31 @@ declare class QueryBuilder {
234
287
  /** Order the page by a numeric field, largest first. */
235
288
  sortDesc(field: string): QueryBuilder;
236
289
  /**
237
- * Order the page by a numeric field. Rows carrying no value for it sort last
238
- * in both directions; they still count towards `totalMatches`, they simply
239
- * have nothing to be ordered by.
290
+ * Order the page by a numeric field.
291
+ *
292
+ * Bounded exactly as {@link range} is: `price` is the only field an entity
293
+ * carries, and any other name is refused rather than silently ignored. An
294
+ * order by a field nothing carries used to leave the page in insertion order
295
+ * and report it as sorted.
240
296
  */
241
297
  sortBy(field: string, descending?: boolean): QueryBuilder;
298
+ /**
299
+ * Keep only entities within `radiusKm` of the point, measured exactly.
300
+ *
301
+ * This is the circle on its own. {@link withinRadius} with a `field` adds
302
+ * the geohash cells too, which is what stops the engine opening every part
303
+ * of the index to find them.
304
+ */
305
+ within(field: string, lat: number, lon: number, radiusKm: number): QueryBuilder;
306
+ /**
307
+ * Order the page by distance from the point, nearest first.
308
+ *
309
+ * Without a radius this is "the nearest K of whatever else matched"; combine
310
+ * it with {@link within} or {@link withinRadius} to bound the search as well.
311
+ * It used to be impossible: a radius returned everything inside it unordered,
312
+ * so you hydrated every id from your own store before you could sort them.
313
+ */
314
+ nearest(field: string, lat: number, lon: number): QueryBuilder;
242
315
  toRequest(defaultTenantId?: string): SearchQueryRequest;
243
316
  toArray(defaultTenantId?: string): SearchQueryRequest;
244
317
  execute(): Promise<SearchResponse>;
@@ -318,12 +391,21 @@ declare class PulseIndexClient implements QueryExecutor {
318
391
  * reported 10,866 for a page of 100. Anything that prints "page 1 of N" from
319
392
  * that number is wrong by an order of magnitude and looks fine.
320
393
  *
321
- * This sends the count query as well, so it costs two round trips and returns
322
- * a total you can divide by a page size.
394
+ * One request. This used to run the whole query twice once for the page,
395
+ * once for the count because the wire had no way to ask for both. It does
396
+ * now, so this is the same round trip with `exactTotal` set, and the total
397
+ * you get back can be divided by a page size.
323
398
  */
324
399
  searchWithTotal(query: QueryBuilder | SearchRequestOptions): Promise<SearchResponse>;
325
400
  index(entityIdOrInput: EntityId | EntityInput, attributes?: EntityAttributes): Promise<IndexEntityResponse>;
326
- indexEntity(entityId: EntityId, categories?: string[], price?: number, locationPrefix?: EntityId, tenantId?: string): Promise<boolean>;
401
+ /**
402
+ * Index one record.
403
+ *
404
+ * `numbers` are yours to name: `{price_cents: 45000, bedrooms: 3}`. This
405
+ * used to take a single `price` and a `locationPrefix`, which was a schema
406
+ * this SDK had no business imposing.
407
+ */
408
+ indexEntity(entityId: EntityId, categories?: string[], numbers?: Record<string, number>, tenantId?: string): Promise<boolean>;
327
409
  batchIndex(entities: Array<EntityInput | BatchEntityInput>): Promise<BatchIndexResponse>;
328
410
  delete(entityId: EntityId, tenantId?: string): Promise<DeleteResponse>;
329
411
  deleteEntity(entityId: EntityId, tenantId?: string): Promise<boolean>;