@techfinityedge/koolbase-react-native 5.5.0 → 6.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/README.md CHANGED
@@ -379,6 +379,66 @@ When the device is offline, these writes are queued and synced automatically whe
379
379
 
380
380
  ---
381
381
 
382
+ ### Semantic search
383
+
384
+ Find records by meaning, not just by field equality. Store an embedding
385
+ vector on a record, then search the collection for nearest neighbors to a
386
+ query vector — useful for similarity search over user-supplied embeddings.
387
+
388
+ Declare a vector field on the collection from the dashboard or CLI first
389
+ (picking a dimension; v1 supports 384, 768, 1024, and 1536). Then write
390
+ and search from the SDK:
391
+
392
+ ```typescript
393
+ // Set a vector for one record
394
+ await Koolbase.db.setVector(
395
+ articleId,
396
+ 'embedding',
397
+ await myEmbeddingModel.encode(article.content),
398
+ );
399
+
400
+ // Read it back
401
+ const v = await Koolbase.db.getVector(articleId, 'embedding');
402
+ console.log(`${v.vector.length}-dim, updated ${v.updatedAt}`);
403
+
404
+ // Search the collection for nearest neighbors
405
+ const result = await Koolbase.db.searchSemantic({
406
+ collection: 'articles',
407
+ field: 'embedding',
408
+ queryVector: await myEmbeddingModel.encode(userQuery),
409
+ limit: 10,
410
+ });
411
+
412
+ for (const hit of result.hits) {
413
+ // hit.distance is cosine distance: lower = more similar
414
+ // (0 = identical direction, 1 ≈ orthogonal, 2 = opposite)
415
+ console.log(hit.record.data.title, hit.distance.toFixed(3));
416
+ }
417
+
418
+ // Optionally scope the search with an equality filter
419
+ const scoped = await Koolbase.db.searchSemantic({
420
+ collection: 'articles',
421
+ field: 'embedding',
422
+ queryVector: queryEmbedding,
423
+ limit: 10,
424
+ where: { category: 'tech' },
425
+ });
426
+
427
+ // Remove a record's vector when you no longer need it
428
+ await Koolbase.db.deleteVector(articleId, 'embedding');
429
+ ```
430
+
431
+ A few behaviors worth knowing:
432
+
433
+ - **Vector length must match the declared dimension.** Mismatches throw `KoolbaseVectorDimensionMismatchError` with the expected and actual dimensions in the message.
434
+ - **Online-only.** Vector operations are not cached locally or queued offline — HNSW similarity search has no useful offline semantics, so deferred writes could corrupt your view of what's stored.
435
+ - **Read rule applies post-search.** Semantic search respects the collection's read rule the same way `query()` does: `owner`/`scoped`/`conditional` records are filtered to the caller after the HNSW lookup, so strict rules may return fewer than `limit` results.
436
+ - **Higher dimensions coming.** OpenAI's `text-embedding-3-large` ships at 3072 dimensions, supported in a future release once pgvector is upgraded. In the meantime, use your model's `dimensions=1536` parameter (Matryoshka truncation) for full compatibility.
437
+
438
+ See [Semantic search docs](https://docs.koolbase.com/database/vectors) for setup, dimension guidance, and embedding model recommendations.
439
+
440
+ ---
441
+
382
442
  ## Storage
383
443
 
384
444
  Upload and serve files via presigned URLs to Cloudflare R2. Uploads are
@@ -755,13 +815,13 @@ handling doesn't depend on message text.
755
815
  All data-layer failures extend `KoolbaseDataError` (which extends `Error`):
756
816
 
757
817
  | Error | When |
758
- | `KoolbaseStorageConflictError` | An upload targets a path that's already taken and `overwrite: false` (409, code `PATH_CONFLICT`). Exposes `.path` — the colliding path. |
759
- | `KoolbaseStorageNotFoundError` | The bucket or object doesn't exist (404). |
760
- | `KoolbaseStorageValidationError` | The request was rejected as invalid — bad path, missing field (400). |
761
- | `KoolbaseStoragePermissionError` | The caller is not allowed to perform the operation (403). |
762
- | `KoolbaseStorageQuotaError` | An upload would push the bucket past its `max_size_bytes` cap (409, code `QUOTA_EXCEEDED`). |
763
- | `KoolbaseStorageFileTooLargeError` | A single file exceeds the bucket's `max_file_size_bytes` cap (413, code `FILE_TOO_LARGE`). |
764
- | `KoolbaseStorageMimeTypeError` | The upload's content-type isn't in the bucket's `allowed_mime_types` allowlist (415, code `MIME_NOT_ALLOWED`). |
818
+ |---|---|
819
+ | `KoolbaseConflictError` | A write violates a unique constraint (409). Exposes `.field` — the field that collided, when the server reports it. |
820
+ | `KoolbaseNotFoundError` | The record or collection doesn't exist (404). |
821
+ | `KoolbaseValidationError` | The request was rejected as invalid (400). |
822
+ | `KoolbasePermissionError` | An access rule denied the operation (403). |
823
+ | `KoolbaseRateLimitError` | The caller is being rate-limited (429). |
824
+ | `KoolbaseVectorDimensionMismatchError` | A vector's length doesn't match the field's declared dimension (400, code `vector_dimension_mismatch`). |
765
825
 
766
826
  ```ts
767
827
  import { KoolbaseConflictError, KoolbaseDataError } from '@techfinityedge/koolbase-react-native';
@@ -828,7 +888,7 @@ try {
828
888
  ## What's included
829
889
 
830
890
  - Authentication: email + password, Apple Sign-In, Google Sign-In, phone + OTP
831
- - Database with offline-first cache, realtime subscriptions, and populate
891
+ - Database with offline-first cache, realtime subscriptions, populate for related records, semantic search over vectors
832
892
  - Storage with presigned uploads and downloads, safe-by-default conflict handling, image transforms, object versioning (history + restore + soft-delete)
833
893
  - Realtime subscriptions over WebSocket
834
894
  - Authenticated functions (`ctx.auth` exposes the caller automatically)
@@ -69,6 +69,24 @@ export declare class KoolbasePermissionError extends KoolbaseDataError {
69
69
  export declare class KoolbaseRateLimitError extends KoolbaseDataError {
70
70
  constructor(message?: string);
71
71
  }
72
+ /**
73
+ * Thrown when the supplied vector's length does not match the dimension
74
+ * declared on the collection's vector field — the server responds with
75
+ * 400 and code `vector_dimension_mismatch`. The message includes both
76
+ * the expected and actual dimensions so you can surface a precise error.
77
+ *
78
+ * @example
79
+ * try {
80
+ * await koolbase.db.setVector(id, 'embedding', [0.1, 0.2]); // 2 dims
81
+ * } catch (e) {
82
+ * if (e instanceof KoolbaseVectorDimensionMismatchError) {
83
+ * showError(e.message); // "expected 1536, got 2"
84
+ * }
85
+ * }
86
+ */
87
+ export declare class KoolbaseVectorDimensionMismatchError extends KoolbaseDataError {
88
+ constructor(message?: string);
89
+ }
72
90
  /**
73
91
  * Maps a non-2xx data-layer response to a typed {@link KoolbaseDataError},
74
92
  * preferring the server's stable `code` and falling back to the HTTP status
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.KoolbaseRateLimitError = exports.KoolbasePermissionError = exports.KoolbaseValidationError = exports.KoolbaseNotFoundError = exports.KoolbaseConflictError = exports.KoolbaseDataError = void 0;
3
+ exports.KoolbaseVectorDimensionMismatchError = exports.KoolbaseRateLimitError = exports.KoolbasePermissionError = exports.KoolbaseValidationError = exports.KoolbaseNotFoundError = exports.KoolbaseConflictError = exports.KoolbaseDataError = void 0;
4
4
  exports.koolbaseDataError = koolbaseDataError;
5
5
  /**
6
6
  * Base class for errors surfaced by the Koolbase data layer (database reads
@@ -103,6 +103,29 @@ class KoolbaseRateLimitError extends KoolbaseDataError {
103
103
  }
104
104
  }
105
105
  exports.KoolbaseRateLimitError = KoolbaseRateLimitError;
106
+ /**
107
+ * Thrown when the supplied vector's length does not match the dimension
108
+ * declared on the collection's vector field — the server responds with
109
+ * 400 and code `vector_dimension_mismatch`. The message includes both
110
+ * the expected and actual dimensions so you can surface a precise error.
111
+ *
112
+ * @example
113
+ * try {
114
+ * await koolbase.db.setVector(id, 'embedding', [0.1, 0.2]); // 2 dims
115
+ * } catch (e) {
116
+ * if (e instanceof KoolbaseVectorDimensionMismatchError) {
117
+ * showError(e.message); // "expected 1536, got 2"
118
+ * }
119
+ * }
120
+ */
121
+ class KoolbaseVectorDimensionMismatchError extends KoolbaseDataError {
122
+ constructor(message) {
123
+ super(message ?? 'Vector dimension does not match field declaration', 'vector_dimension_mismatch');
124
+ this.name = 'KoolbaseVectorDimensionMismatchError';
125
+ Object.setPrototypeOf(this, KoolbaseVectorDimensionMismatchError.prototype);
126
+ }
127
+ }
128
+ exports.KoolbaseVectorDimensionMismatchError = KoolbaseVectorDimensionMismatchError;
106
129
  /**
107
130
  * Maps a non-2xx data-layer response to a typed {@link KoolbaseDataError},
108
131
  * preferring the server's stable `code` and falling back to the HTTP status
@@ -119,13 +142,19 @@ function koolbaseDataError(status, body, fallbackMessage = 'Request failed') {
119
142
  case 'not_found':
120
143
  case 'record_not_found':
121
144
  case 'collection_not_found':
145
+ case 'vector_not_found':
146
+ case 'vector_field_not_found':
122
147
  return new KoolbaseNotFoundError(message);
123
148
  case 'permission_denied':
124
149
  return new KoolbasePermissionError(message);
125
150
  case 'rate_limit':
126
151
  return new KoolbaseRateLimitError(message);
127
152
  case 'validation_error':
153
+ case 'vector_collection_mismatch':
154
+ case 'unsupported_dimension':
128
155
  return new KoolbaseValidationError(message);
156
+ case 'vector_dimension_mismatch':
157
+ return new KoolbaseVectorDimensionMismatchError(message);
129
158
  }
130
159
  // ─── status fallback (pre-code servers) ───
131
160
  switch (status) {
@@ -1,4 +1,4 @@
1
- import { KoolbaseConfig, KoolbaseRecord, QueryOptions, QueryResult, UpsertResult, BatchOp, BatchResult } from './types';
1
+ import { KoolbaseConfig, KoolbaseRecord, QueryOptions, QueryResult, UpsertResult, BatchOp, BatchResult, KoolbaseVector, SemanticSearchResult } from './types';
2
2
  export declare class KoolbaseDatabase {
3
3
  private config;
4
4
  private getUserId;
@@ -90,5 +90,83 @@ export declare class KoolbaseDatabase {
90
90
  */
91
91
  update(recordId: string, data: Record<string, unknown>): Promise<KoolbaseRecord>;
92
92
  delete(recordId: string): Promise<void>;
93
+ /**
94
+ * Write (or replace) a vector for a record on the named `field`.
95
+ *
96
+ * The field must already be declared on the collection via the dashboard
97
+ * or CLI. `vector.length` must match the field's declared dimension;
98
+ * otherwise throws `KoolbaseVectorDimensionMismatchError`.
99
+ *
100
+ * Online-only — vectors are not cached locally or queued offline because
101
+ * HNSW similarity search has no useful offline semantics.
102
+ *
103
+ * @example
104
+ * await Koolbase.db.setVector(
105
+ * articleId,
106
+ * 'embedding',
107
+ * await myEmbeddingModel.encode(article.content),
108
+ * );
109
+ */
110
+ setVector(recordId: string, field: string, vector: number[]): Promise<void>;
111
+ /**
112
+ * Read a record's stored vector on the named `field`.
113
+ *
114
+ * Throws `KoolbaseNotFoundError` if either the field is not declared or
115
+ * no vector has been set for this record on this field. Throws
116
+ * `KoolbasePermissionError` if the caller cannot read this record per
117
+ * the collection's read rule.
118
+ *
119
+ * Online-only.
120
+ *
121
+ * @example
122
+ * const v = await Koolbase.db.getVector(articleId, 'embedding');
123
+ * console.log(`${v.vector.length}-dim, updated ${v.updatedAt}`);
124
+ */
125
+ getVector(recordId: string, field: string): Promise<KoolbaseVector>;
126
+ /**
127
+ * Remove a record's stored vector on the named `field`.
128
+ *
129
+ * Online-only. Throws `KoolbaseNotFoundError` if no vector is set for
130
+ * `(recordId, field)`; throws `KoolbasePermissionError` if the caller
131
+ * cannot write this record per the collection's write rule.
132
+ *
133
+ * Note: this removes the vector from the dimension table but does NOT
134
+ * remove the field declaration itself — the field stays on the
135
+ * collection and is still settable on other records.
136
+ */
137
+ deleteVector(recordId: string, field: string): Promise<void>;
138
+ /**
139
+ * Semantic search via HNSW vector similarity.
140
+ *
141
+ * Ranks records in `collection` by cosine distance between the supplied
142
+ * `queryVector` and each record's stored vector on `field`. Returns up
143
+ * to `limit` (default 20) nearest hits, with the collection's read rule
144
+ * applied — owner/scoped/conditional records are filtered to the caller.
145
+ *
146
+ * `where` is an optional equality filter map on record `data` fields,
147
+ * applied AFTER the HNSW lookup, so very strict filters may return
148
+ * fewer than `limit` results.
149
+ *
150
+ * Online-only.
151
+ *
152
+ * @example
153
+ * const result = await Koolbase.db.searchSemantic({
154
+ * collection: 'articles',
155
+ * field: 'embedding',
156
+ * queryVector: await myEmbeddingModel.encode(userQuery),
157
+ * limit: 10,
158
+ * where: { category: 'tech' },
159
+ * });
160
+ * for (const hit of result.hits) {
161
+ * console.log(hit.record.data.title, hit.distance);
162
+ * }
163
+ */
164
+ searchSemantic(opts: {
165
+ collection: string;
166
+ field: string;
167
+ queryVector: number[];
168
+ limit?: number;
169
+ where?: Record<string, unknown>;
170
+ }): Promise<SemanticSearchResult>;
93
171
  syncPendingWrites(): Promise<void>;
94
172
  }
package/dist/database.js CHANGED
@@ -319,6 +319,126 @@ class KoolbaseDatabase {
319
319
  // Queued for sync — will retry when online
320
320
  }
321
321
  }
322
+ // ─── Vectors ────────────────────────────────────────────────────────────────
323
+ /**
324
+ * Write (or replace) a vector for a record on the named `field`.
325
+ *
326
+ * The field must already be declared on the collection via the dashboard
327
+ * or CLI. `vector.length` must match the field's declared dimension;
328
+ * otherwise throws `KoolbaseVectorDimensionMismatchError`.
329
+ *
330
+ * Online-only — vectors are not cached locally or queued offline because
331
+ * HNSW similarity search has no useful offline semantics.
332
+ *
333
+ * @example
334
+ * await Koolbase.db.setVector(
335
+ * articleId,
336
+ * 'embedding',
337
+ * await myEmbeddingModel.encode(article.content),
338
+ * );
339
+ */
340
+ async setVector(recordId, field, vector) {
341
+ const res = await fetch(`${this.config.baseUrl}/v1/sdk/db/set-vector`, {
342
+ method: 'POST',
343
+ headers: await this.buildHeaders(),
344
+ body: JSON.stringify({ record_id: recordId, field, vector }),
345
+ });
346
+ if (res.status !== 204) {
347
+ const body = await res.json().catch(() => ({}));
348
+ throw (0, database_errors_1.koolbaseDataError)(res.status, body, 'Set vector failed');
349
+ }
350
+ }
351
+ /**
352
+ * Read a record's stored vector on the named `field`.
353
+ *
354
+ * Throws `KoolbaseNotFoundError` if either the field is not declared or
355
+ * no vector has been set for this record on this field. Throws
356
+ * `KoolbasePermissionError` if the caller cannot read this record per
357
+ * the collection's read rule.
358
+ *
359
+ * Online-only.
360
+ *
361
+ * @example
362
+ * const v = await Koolbase.db.getVector(articleId, 'embedding');
363
+ * console.log(`${v.vector.length}-dim, updated ${v.updatedAt}`);
364
+ */
365
+ async getVector(recordId, field) {
366
+ const raw = await this.request('POST', '/v1/sdk/db/get-vector', { record_id: recordId, field });
367
+ return {
368
+ recordId: raw.record_id,
369
+ fieldName: raw.field_name,
370
+ vector: raw.vector,
371
+ createdAt: raw.created_at,
372
+ updatedAt: raw.updated_at,
373
+ };
374
+ }
375
+ /**
376
+ * Remove a record's stored vector on the named `field`.
377
+ *
378
+ * Online-only. Throws `KoolbaseNotFoundError` if no vector is set for
379
+ * `(recordId, field)`; throws `KoolbasePermissionError` if the caller
380
+ * cannot write this record per the collection's write rule.
381
+ *
382
+ * Note: this removes the vector from the dimension table but does NOT
383
+ * remove the field declaration itself — the field stays on the
384
+ * collection and is still settable on other records.
385
+ */
386
+ async deleteVector(recordId, field) {
387
+ const res = await fetch(`${this.config.baseUrl}/v1/sdk/db/delete-vector`, {
388
+ method: 'POST',
389
+ headers: await this.buildHeaders(),
390
+ body: JSON.stringify({ record_id: recordId, field }),
391
+ });
392
+ if (res.status !== 204) {
393
+ const body = await res.json().catch(() => ({}));
394
+ throw (0, database_errors_1.koolbaseDataError)(res.status, body, 'Delete vector failed');
395
+ }
396
+ }
397
+ /**
398
+ * Semantic search via HNSW vector similarity.
399
+ *
400
+ * Ranks records in `collection` by cosine distance between the supplied
401
+ * `queryVector` and each record's stored vector on `field`. Returns up
402
+ * to `limit` (default 20) nearest hits, with the collection's read rule
403
+ * applied — owner/scoped/conditional records are filtered to the caller.
404
+ *
405
+ * `where` is an optional equality filter map on record `data` fields,
406
+ * applied AFTER the HNSW lookup, so very strict filters may return
407
+ * fewer than `limit` results.
408
+ *
409
+ * Online-only.
410
+ *
411
+ * @example
412
+ * const result = await Koolbase.db.searchSemantic({
413
+ * collection: 'articles',
414
+ * field: 'embedding',
415
+ * queryVector: await myEmbeddingModel.encode(userQuery),
416
+ * limit: 10,
417
+ * where: { category: 'tech' },
418
+ * });
419
+ * for (const hit of result.hits) {
420
+ * console.log(hit.record.data.title, hit.distance);
421
+ * }
422
+ */
423
+ async searchSemantic(opts) {
424
+ const body = {
425
+ collection: opts.collection,
426
+ field: opts.field,
427
+ query_vector: opts.queryVector,
428
+ limit: opts.limit ?? 20,
429
+ };
430
+ if (opts.where && Object.keys(opts.where).length > 0) {
431
+ body.where = opts.where;
432
+ }
433
+ const raw = await this.request('POST', '/v1/sdk/db/search-semantic', body);
434
+ return {
435
+ hits: (raw.results ?? []).map(r => ({
436
+ record: (0, record_1.recordFromWire)(r.record),
437
+ distance: r.distance,
438
+ })),
439
+ total: raw.total ?? (raw.results ?? []).length,
440
+ };
441
+ }
322
442
  // ─── Manual sync ────────────────────────────────────────────────────────────
323
443
  async syncPendingWrites() {
324
444
  await this.syncEngine.flush();
package/dist/types.d.ts CHANGED
@@ -147,6 +147,41 @@ export interface PendingWrite {
147
147
  retries: number;
148
148
  createdAt: string;
149
149
  }
150
+ /**
151
+ * A stored vector retrieved by `KoolbaseDatabase.getVector()`. The `vector`
152
+ * field carries the float values exactly as stored on the server; the
153
+ * `recordId` + `fieldName` pair identifies which slot they came from.
154
+ */
155
+ export interface KoolbaseVector {
156
+ recordId: string;
157
+ fieldName: string;
158
+ vector: number[];
159
+ /** ISO 8601 timestamp from the server. */
160
+ createdAt: string;
161
+ /** ISO 8601 timestamp from the server. */
162
+ updatedAt: string;
163
+ }
164
+ /**
165
+ * One ranked hit from `KoolbaseDatabase.searchSemantic()`. `record` is
166
+ * the full record (same wire shape as a record returned by query/get).
167
+ * `distance` is the cosine distance between the query vector and the
168
+ * stored vector — lower means more similar. Range: 0 (identical
169
+ * direction) to 2 (opposite direction).
170
+ */
171
+ export interface KoolbaseSemanticHit {
172
+ record: KoolbaseRecord;
173
+ distance: number;
174
+ }
175
+ /**
176
+ * Result of `KoolbaseDatabase.searchSemantic()`. `hits` is the ranked
177
+ * list of nearest neighbors (best match first); `total` is the count of
178
+ * hits returned (matches `hits.length` in v1 — preserved as a separate
179
+ * field for future pagination).
180
+ */
181
+ export interface SemanticSearchResult {
182
+ hits: KoolbaseSemanticHit[];
183
+ total: number;
184
+ }
150
185
  export interface UploadOptions {
151
186
  bucket: string;
152
187
  path: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techfinityedge/koolbase-react-native",
3
- "version": "5.5.0",
3
+ "version": "6.0.0",
4
4
  "description": "React Native SDK for Koolbase — auth, database, storage, realtime, feature flags, and functions in one package.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",