@pulseindex/sdk 2.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 +102 -0
- package/LICENSE +21 -0
- package/README.md +326 -0
- package/dist/index.d.mts +324 -0
- package/dist/index.d.ts +324 -0
- package/dist/index.js +1195 -0
- package/dist/index.mjs +1158 -0
- package/package.json +69 -0
- package/proto/engine.proto +191 -0
- package/proto/health.proto +32 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Breaking: three methods removed
|
|
6
|
+
|
|
7
|
+
`createSnapshot()`, `getRecoveryState()` and `setCdcOffset()` are gone, along
|
|
8
|
+
with the `RecoveryState`, `CreateSnapshotResponse` and `SetCdcOffsetResponse`
|
|
9
|
+
types and the RPCs behind them in the bundled proto.
|
|
10
|
+
|
|
11
|
+
No key issued from the dashboard could call them — every attempt returned a
|
|
12
|
+
permission error — so nothing that worked before stops working. If you were
|
|
13
|
+
calling them and getting errors, that is the code to delete.
|
|
14
|
+
|
|
15
|
+
**Checking readiness:** use `health()`, or `servingStatus()` when you need to
|
|
16
|
+
tell "not answering" apart from "not reachable". Both work with any key.
|
|
17
|
+
|
|
18
|
+
### The bundled proto now describes only the client's contract
|
|
19
|
+
|
|
20
|
+
Four RPCs: `IndexEntity`, `BatchIndexEntities`, `DeleteEntity`, `Search`. The
|
|
21
|
+
drift check that keeps this file honest was changed to match: it verifies every
|
|
22
|
+
declaration here exists identically in the service, and no longer requires the
|
|
23
|
+
two to be identical.
|
|
24
|
+
|
|
25
|
+
## 1.1.4
|
|
26
|
+
|
|
27
|
+
Documentation only.
|
|
28
|
+
|
|
29
|
+
The README documented three methods no ordinary API key can call, and explained
|
|
30
|
+
that they need an elevated one. That is operator surface, and it was dead
|
|
31
|
+
reading for anyone the README is written for. It also named the server-side
|
|
32
|
+
environment variable that holds API keys, which is not the client's business.
|
|
33
|
+
|
|
34
|
+
`health()` is now described by what it tells you rather than by the protocol
|
|
35
|
+
underneath it, and the production example uses a real endpoint.
|
|
36
|
+
|
|
37
|
+
The methods still exist on the client; they are simply no longer presented as
|
|
38
|
+
part of the product.
|
|
39
|
+
|
|
40
|
+
## 1.1.3
|
|
41
|
+
|
|
42
|
+
No code change. The package now ships only what is needed to use it.
|
|
43
|
+
|
|
44
|
+
- Source maps are no longer published. They embedded the complete TypeScript
|
|
45
|
+
source of every bundled file and were 59% of the tarball. The shipped
|
|
46
|
+
JavaScript is unminified, so stack traces still land somewhere readable.
|
|
47
|
+
- Build, release and proto-sync notes moved out of the README; they described
|
|
48
|
+
how the SDK is maintained, not how to call it.
|
|
49
|
+
- The RPC table lists the calls a normal API key can make. The three
|
|
50
|
+
operator-only ones are noted rather than tabulated.
|
|
51
|
+
|
|
52
|
+
318 KB to 130 KB.
|
|
53
|
+
|
|
54
|
+
## 1.1.2
|
|
55
|
+
|
|
56
|
+
Documentation only; the code is identical to 1.1.0.
|
|
57
|
+
|
|
58
|
+
The vendored `engine.proto` still described how the service works rather than
|
|
59
|
+
how to call it. Found by reading the file rather than searching it for known
|
|
60
|
+
words — which is the only method that finds what you did not already know to
|
|
61
|
+
look for.
|
|
62
|
+
|
|
63
|
+
## 1.1.1
|
|
64
|
+
|
|
65
|
+
Documentation only; the code is identical to 1.1.0.
|
|
66
|
+
|
|
67
|
+
1.1.0 shipped internal maintainer comments in its type declarations and source
|
|
68
|
+
maps — `tsup` emits JSDoc into `.d.ts` and embeds the whole TypeScript source
|
|
69
|
+
into `.map`, and both are in the tarball. 1.1.0 has been unpublished.
|
|
70
|
+
|
|
71
|
+
## 1.1.0
|
|
72
|
+
|
|
73
|
+
### `health()` no longer reports false for every key
|
|
74
|
+
|
|
75
|
+
`health()` used an operator-only call that customer API keys are not permitted to
|
|
76
|
+
make. The permission error was caught and turned into `false`, so the method
|
|
77
|
+
reported an unusable service no matter how healthy it actually was.
|
|
78
|
+
|
|
79
|
+
It now uses the standard `grpc.health.v1.Health` protocol, which requires no
|
|
80
|
+
particular scope. The signature is unchanged. If you were working around this by
|
|
81
|
+
ignoring `health()`, you can stop.
|
|
82
|
+
|
|
83
|
+
### Added
|
|
84
|
+
|
|
85
|
+
- `client.servingStatus(service?)` — the raw serving status, for telling
|
|
86
|
+
"reachable but not serving" apart from "no answer at all". Defaults to `''`,
|
|
87
|
+
the overall-server name from the health spec.
|
|
88
|
+
- `SERVING_STATUS` — the status constants, exported from the package root.
|
|
89
|
+
- `healthProtoPath` on the client config, for the rare case of overriding the
|
|
90
|
+
bundled `health.proto`.
|
|
91
|
+
|
|
92
|
+
`proto/health.proto` now ships with the package. It is the standard health
|
|
93
|
+
protocol, vendored rather than pulled in as a dependency.
|
|
94
|
+
|
|
95
|
+
### Compatibility
|
|
96
|
+
|
|
97
|
+
Against a service deployed before this release, the health protocol answers but
|
|
98
|
+
always reports `SERVING`. `health()` is then equivalent to a reachability check.
|
|
99
|
+
|
|
100
|
+
## 1.0.0
|
|
101
|
+
|
|
102
|
+
Initial release.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PulseIndex
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# PulseIndex JavaScript / TypeScript SDK
|
|
2
|
+
|
|
3
|
+
Official Node.js & TypeScript client for **PulseIndex** — hosted search and filtering for large entity sets.
|
|
4
|
+
|
|
5
|
+
You send attributes to index and queries to run; PulseIndex returns matching entity IDs, which you hydrate from your own database. Your records stay in your primary store — the service holds only what it needs to answer queries.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@pulseindex/sdk)
|
|
8
|
+
[](#installation)
|
|
9
|
+
[](./LICENSE)
|
|
10
|
+
|
|
11
|
+
## Key features
|
|
12
|
+
|
|
13
|
+
- Dual ESM / CommonJS build for Node.js 18+
|
|
14
|
+
- Typed fluent `QueryBuilder` matching `pulseindex-php`
|
|
15
|
+
- Zero-dependency GeoHash radius coverage (`geo:{precision}:{hash}`)
|
|
16
|
+
- Connection pooling, deadlines, and `x-api-key` / `Authorization: Bearer` metadata
|
|
17
|
+
- Attribute flattening so plain objects index without a schema (categories, flags, geo tags, price)
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @pulseindex/sdk
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm add @pulseindex/sdk
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
ESM:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import PulseIndex, { GeoHash } from '@pulseindex/sdk';
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
CommonJS:
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
const { PulseIndex, GeoHash } = require('@pulseindex/sdk');
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Requires Node.js 18 or later. The engine gRPC endpoint defaults to `localhost:50051`.
|
|
42
|
+
|
|
43
|
+
## Quickstart
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import PulseIndex, { GeoHash } from '@pulseindex/sdk';
|
|
47
|
+
|
|
48
|
+
const client = new PulseIndex({
|
|
49
|
+
endpoint: 'localhost:50051',
|
|
50
|
+
apiKey: process.env.PULSEINDEX_API_KEY,
|
|
51
|
+
tenantId: 'acme_corp',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await client.index('1001', {
|
|
55
|
+
categories: ['features:swimming_pool'],
|
|
56
|
+
category: 'villa',
|
|
57
|
+
status: 'listed',
|
|
58
|
+
price: 250000,
|
|
59
|
+
lat: 41.0082,
|
|
60
|
+
lng: 28.9784,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const result = await client.search(
|
|
64
|
+
PulseIndex.query()
|
|
65
|
+
.tenant('acme_corp')
|
|
66
|
+
.must('features:swimming_pool')
|
|
67
|
+
.should(['category:villa', 'category:apartment'])
|
|
68
|
+
.mustNot('status:sold')
|
|
69
|
+
.range('price', 100000, 500000)
|
|
70
|
+
.withinRadius({ lat: 41.0082, lng: 28.9784, radiusKm: 5 })
|
|
71
|
+
.limit(50),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const ids = result.matchedEntityIds;
|
|
75
|
+
await client.close();
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Index-time geo tags should use the same dual precision as radius queries:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
const tags = GeoHash.encodeMultiTags(41.0082, 28.9784);
|
|
82
|
+
// ['geo:5:sxk97', 'geo:6:sxk976'] (example)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## gRPC configuration
|
|
86
|
+
|
|
87
|
+
Create a client against any PulseIndex Engine endpoint. Production customer gRPC should set `ssl: true` (or `PULSEINDEX_SSL=true`). The default is plaintext for local development.
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
const client = PulseIndex.create(
|
|
91
|
+
'api.usepulseindex.com:50051',
|
|
92
|
+
process.env.PULSEINDEX_API_KEY,
|
|
93
|
+
true,
|
|
94
|
+
);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
| Option | Env var | Default | Description |
|
|
98
|
+
| --- | --- | --- | --- |
|
|
99
|
+
| `endpoint` / `host` | `PULSEINDEX_ENDPOINT`, `PULSEINDEX_HOST` | `localhost:50051` | Engine `host:port` |
|
|
100
|
+
| `apiKey` | `PULSEINDEX_API_KEY` | — | Sent as `x-api-key` and `Authorization: Bearer` |
|
|
101
|
+
| `authorization` | `PULSEINDEX_AUTHORIZATION` | — | Overrides the Bearer token when set |
|
|
102
|
+
| `tenantId` | `PULSEINDEX_TENANT_ID` | `''` (engine uses `default`) | Default tenant for index / search / delete |
|
|
103
|
+
| `timeoutMs` | — | `5000` | Per-RPC deadline |
|
|
104
|
+
| `ssl` | `PULSEINDEX_SSL` | `false` | Enable TLS |
|
|
105
|
+
| `rootCerts` / `privateKey` / `certChain` | — | — | Optional custom TLS materials |
|
|
106
|
+
| `poolSize` | — | `1` | Number of multiplexed gRPC clients |
|
|
107
|
+
| `protoPath` | — | packaged `proto/engine.proto` | Override the proto file |
|
|
108
|
+
| `channelOptions` | — | keepalive defaults | Extra `@grpc/grpc-js` channel options |
|
|
109
|
+
|
|
110
|
+
The client sends your key as `x-api-key`, and also as `Authorization: Bearer` — either is accepted. Every indexing and query call carries `tenant_id`; an empty value means `"default"`.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
const client = new PulseIndex({
|
|
114
|
+
endpoint: process.env.PULSEINDEX_HOST,
|
|
115
|
+
apiKey: process.env.PULSEINDEX_API_KEY,
|
|
116
|
+
tenantId: 'acme_corp',
|
|
117
|
+
timeoutMs: 5000,
|
|
118
|
+
ssl: true,
|
|
119
|
+
poolSize: 2,
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Indexing
|
|
124
|
+
|
|
125
|
+
`index()` accepts a string/number entity id plus a flat attribute object. Reserved keys (`price`, `tenantId`, `lat` / `lng`, `categories`, …) map onto dedicated fields; every other key becomes a namespaced term (`status:listed`, `amenities:parking`) you can filter on. Coordinates automatically add `geo:5:…` and `geo:6:…` tags.
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
await client.index('1001', {
|
|
129
|
+
categories: ['feature:pool'],
|
|
130
|
+
amenities: ['parking', 'gym'],
|
|
131
|
+
furnished: true,
|
|
132
|
+
price: 1500,
|
|
133
|
+
lat: 24.7136,
|
|
134
|
+
lng: 46.6753,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
await client.batchIndex([
|
|
138
|
+
{ id: '1002', attributes: { categories: ['feature:garden'], price: 900 } },
|
|
139
|
+
{ entityId: 1003, categories: ['feature:pool'], price: 2000 },
|
|
140
|
+
]);
|
|
141
|
+
|
|
142
|
+
await client.delete('1001');
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Low-level PHP-compatible helper:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
await client.indexEntity(1001, ['feature:pool', 'amenity:parking'], 1500, 0, 'acme');
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`entity_id` is a proto `uint64`. Pass a string when the id may exceed `Number.MAX_SAFE_INTEGER`.
|
|
152
|
+
|
|
153
|
+
## QueryBuilder
|
|
154
|
+
|
|
155
|
+
The engine evaluates MUST (AND), SHOULD (OR group, then AND), MUST_NOT, optional `price` ranges, and returns ids only. `QueryBuilder` is immutable: each chained call returns a new builder.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
const query = client
|
|
159
|
+
.query()
|
|
160
|
+
.tenant('acme_corp')
|
|
161
|
+
.must('feature:pool')
|
|
162
|
+
.should(['category:villa', 'category:apartment'])
|
|
163
|
+
.mustNot('status:sold')
|
|
164
|
+
.range('price', 1000, 5000)
|
|
165
|
+
.withinRadius({ lat: 24.7136, lng: 46.6753, radiusKm: 5 })
|
|
166
|
+
.limit(50)
|
|
167
|
+
.offset(0);
|
|
168
|
+
|
|
169
|
+
const page = await query.execute();
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Equivalent object form:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
await client.search({
|
|
176
|
+
tenantId: 'acme_corp',
|
|
177
|
+
must: 'feature:pool',
|
|
178
|
+
should: ['category:villa', 'category:apartment'],
|
|
179
|
+
mustNot: 'status:sold',
|
|
180
|
+
ranges: [{ field: 'price', min: 100000, max: 500000 }],
|
|
181
|
+
withinRadius: { lat: 41.0082, lng: 28.9784, radiusKm: 5 },
|
|
182
|
+
limit: 50,
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
| Method | Effect |
|
|
187
|
+
| --- | --- |
|
|
188
|
+
| `tenant(id)` | Set `tenant_id` |
|
|
189
|
+
| `must(attr \| attr[])` | MUST filters |
|
|
190
|
+
| `should(attr \| attr[])` | SHOULD filters (OR group) |
|
|
191
|
+
| `mustNot(attr \| attr[])` | MUST_NOT filters |
|
|
192
|
+
| `range(field, min, max)` | Numeric range (currently `price`) |
|
|
193
|
+
| `withinRadius(lat, lon, km)` / `withinRadius({ lat, lng, radiusKm })` | SHOULD geo covering |
|
|
194
|
+
| `whereGeoHash(hash)` / `inGeoHash(hash)` | MUST exact geo cell |
|
|
195
|
+
| `location(prefix)` | Coarse `location_prefix` |
|
|
196
|
+
| `limit(n)` / `offset(n)` | Pagination (`0` = unlimited) |
|
|
197
|
+
| `toRequest()` | Compile the proto-shaped payload |
|
|
198
|
+
| `execute()` | Search via the bound client |
|
|
199
|
+
|
|
200
|
+
## GeoHash usage
|
|
201
|
+
|
|
202
|
+
Precision is chosen from radius, then covering cells are emitted as SHOULD `geo:{precision}:{hash}` tags:
|
|
203
|
+
|
|
204
|
+
| Radius | Precision | Approximate cell |
|
|
205
|
+
| --- | --- | --- |
|
|
206
|
+
| ≤ 1.5 km | 6 | ~1.2 km × 0.6 km |
|
|
207
|
+
| ≤ 8.0 km | 5 | ~4.9 km × 4.9 km |
|
|
208
|
+
| > 8.0 km | 4 | ~39 km × 19 km |
|
|
209
|
+
|
|
210
|
+
`GeoHash.neighborhood3x3()` returns the centre cell plus eight neighbors. `withinRadius()` uses intersecting covering cells (same algorithm as `pulseindex-php`) so oversized neighbors are not OR'd in.
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
import { GeoHash } from '@pulseindex/sdk';
|
|
214
|
+
|
|
215
|
+
GeoHash.encode(42.6, -5.6, 5); // 'ezs42'
|
|
216
|
+
GeoHash.tag('ezs42'); // 'geo:5:ezs42'
|
|
217
|
+
GeoHash.encodeMultiTags(41.0082, 28.9784);
|
|
218
|
+
GeoHash.neighborhood3x3('ezs42'); // centre + 8 neighbors
|
|
219
|
+
query.whereGeoHash('ezs42'); // MUST geo:5:ezs42
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Also available: `decode`, `decodeBounds`, `neighbor`, `neighbors`, `neighborhoodTags`, `optimalPrecisionForRadius`, `getCoveringHashes`, `encodeTag`, `haversineKm`.
|
|
223
|
+
|
|
224
|
+
## Error handling
|
|
225
|
+
|
|
226
|
+
All RPC failures wrap gRPC status codes. Catch the typed subclass that matches the failure mode:
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
import {
|
|
230
|
+
PulseIndexAuthError,
|
|
231
|
+
PulseIndexConnectionError,
|
|
232
|
+
PulseIndexQueryError,
|
|
233
|
+
} from '@pulseindex/sdk';
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
await client.search(PulseIndex.query().must('feature:pool').limit(20));
|
|
237
|
+
} catch (error) {
|
|
238
|
+
if (error instanceof PulseIndexAuthError) {
|
|
239
|
+
// UNAUTHENTICATED / PERMISSION_DENIED — check x-api-key
|
|
240
|
+
} else if (error instanceof PulseIndexConnectionError) {
|
|
241
|
+
// UNAVAILABLE / DEADLINE_EXCEEDED — engine down or timeout
|
|
242
|
+
} else if (error instanceof PulseIndexQueryError) {
|
|
243
|
+
// INVALID_ARGUMENT / RESOURCE_EXHAUSTED — bad query or capacity
|
|
244
|
+
} else {
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
| Class | Typical gRPC statuses |
|
|
251
|
+
| --- | --- |
|
|
252
|
+
| `PulseIndexError` | Base class (`code`, `grpcStatusCode`, `grpcDetails`) |
|
|
253
|
+
| `PulseIndexConnectionError` | `UNAVAILABLE`, `DEADLINE_EXCEEDED`, `CANCELLED`, `ABORTED` |
|
|
254
|
+
| `PulseIndexAuthError` | `UNAUTHENTICATED`, `PERMISSION_DENIED` |
|
|
255
|
+
| `PulseIndexQueryError` | `INVALID_ARGUMENT`, `FAILED_PRECONDITION`, `RESOURCE_EXHAUSTED`, … |
|
|
256
|
+
|
|
257
|
+
`client.health()` returns `false` instead of throwing when the channel is not ready.
|
|
258
|
+
|
|
259
|
+
### Health
|
|
260
|
+
|
|
261
|
+
`client.health()` is `true` only when the service can **serve reads**: the channel
|
|
262
|
+
is ready and the standard health protocol reports `SERVING`. A reachable service
|
|
263
|
+
that cannot currently answer queries reports `false`, so reachability alone is not
|
|
264
|
+
treated as health.
|
|
265
|
+
|
|
266
|
+
It returns `false` rather than throwing, which means unreachable and unavailable
|
|
267
|
+
look the same. Use `servingStatus()` when you need to tell them apart:
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
import { SERVING_STATUS } from '@pulseindex/sdk';
|
|
271
|
+
|
|
272
|
+
const status = await client.servingStatus();
|
|
273
|
+
status === SERVING_STATUS.SERVING; // ready for queries
|
|
274
|
+
status === SERVING_STATUS.NOT_SERVING; // reachable, not currently serving
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
`health()` needs no particular scope on your API key — it does not send one.
|
|
278
|
+
|
|
279
|
+
If `health()` stays `false` for more than a few minutes, retry with backoff rather
|
|
280
|
+
than failing your own requests immediately; if it persists, contact support.
|
|
281
|
+
|
|
282
|
+
## API reference
|
|
283
|
+
|
|
284
|
+
### `PulseIndex` / `PulseIndexClient`
|
|
285
|
+
|
|
286
|
+
| Method | Returns | Description |
|
|
287
|
+
| --- | --- | --- |
|
|
288
|
+
| `new PulseIndex(config)` | client | Create a pooled gRPC client |
|
|
289
|
+
| `PulseIndex.create(host, apiKey?, ssl?)` | client | Convenience constructor |
|
|
290
|
+
| `PulseIndex.query()` | `QueryBuilder` | Unbound fluent query |
|
|
291
|
+
| `client.query()` | `QueryBuilder` | Bound builder (`execute()` calls `search`) |
|
|
292
|
+
| `client.search(query \| options)` | `SearchResponse` | Run `Search` |
|
|
293
|
+
| `client.index(id, attributes)` | `{ success }` | Upsert one entity |
|
|
294
|
+
| `client.batchIndex(entities)` | `{ indexedCount }` | Batch upsert |
|
|
295
|
+
| `client.delete(id)` | `{ success }` | Soft-delete an entity |
|
|
296
|
+
| `client.health()` | `boolean` | Whether the service is ready to answer queries |
|
|
297
|
+
| `client.servingStatus()` | `number` | Readiness as a status code, when you need more than a boolean |
|
|
298
|
+
| `client.close()` | `void` | Shut down the channel pool |
|
|
299
|
+
|
|
300
|
+
`SearchResponse`:
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
{
|
|
304
|
+
matchedEntityIds: string[];
|
|
305
|
+
totalMatches: number;
|
|
306
|
+
executionTimeUs: number;
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## gRPC contract
|
|
311
|
+
|
|
312
|
+
Service: `pulseindex.engine.v1.SearchEngineService`
|
|
313
|
+
|
|
314
|
+
| RPC | Request | Response |
|
|
315
|
+
| --- | --- | --- |
|
|
316
|
+
| `IndexEntity` | `IndexEntityRequest` | `IndexEntityResponse` |
|
|
317
|
+
| `BatchIndexEntities` | `BatchIndexEntitiesRequest` | `BatchIndexEntitiesResponse` |
|
|
318
|
+
| `DeleteEntity` | `DeleteEntityRequest` | `DeleteEntityResponse` |
|
|
319
|
+
| `Search` | `SearchQueryRequest` | `SearchQueryResponse` |
|
|
320
|
+
|
|
321
|
+
`health()` reports whether the service is ready to answer queries. It needs no
|
|
322
|
+
particular scope, so it works with any key.
|
|
323
|
+
|
|
324
|
+
## License
|
|
325
|
+
|
|
326
|
+
MIT
|