@koolbase/react-native 9.1.0 → 10.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 +1342 -0
- package/README.md +462 -511
- package/dist/{auth-storage.d.ts → cjs/auth-storage.d.ts} +1 -1
- package/dist/cjs/index.d.ts +19 -0
- package/dist/cjs/index.js +125 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/platform.d.ts +2 -0
- package/dist/cjs/platform.js +43 -0
- package/dist/esm/auth-storage.d.ts +26 -0
- package/dist/esm/auth-storage.js +100 -0
- package/dist/esm/index.d.ts +19 -0
- package/dist/esm/index.js +106 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/platform.d.ts +2 -0
- package/dist/esm/platform.js +37 -0
- package/package.json +30 -24
- package/dist/analytics.d.ts +0 -24
- package/dist/analytics.js +0 -114
- package/dist/apple-auth.d.ts +0 -22
- package/dist/apple-auth.js +0 -74
- package/dist/auth-errors.d.ts +0 -117
- package/dist/auth-errors.js +0 -250
- package/dist/auth.d.ts +0 -199
- package/dist/auth.js +0 -794
- package/dist/cache-store.d.ts +0 -11
- package/dist/cache-store.js +0 -136
- package/dist/code-push.d.ts +0 -59
- package/dist/code-push.js +0 -255
- package/dist/database-errors.d.ts +0 -95
- package/dist/database-errors.js +0 -173
- package/dist/database.d.ts +0 -208
- package/dist/database.js +0 -508
- package/dist/device-id.d.ts +0 -1
- package/dist/device-id.js +0 -60
- package/dist/device-metadata.d.ts +0 -36
- package/dist/device-metadata.js +0 -102
- package/dist/flags.d.ts +0 -15
- package/dist/flags.js +0 -76
- package/dist/functions.d.ts +0 -8
- package/dist/functions.js +0 -70
- package/dist/index.d.ts +0 -45
- package/dist/index.js +0 -193
- package/dist/logic-engine.d.ts +0 -17
- package/dist/logic-engine.js +0 -193
- package/dist/messaging.d.ts +0 -13
- package/dist/messaging.js +0 -36
- package/dist/realtime.d.ts +0 -19
- package/dist/realtime.js +0 -148
- package/dist/record.d.ts +0 -2
- package/dist/record.js +0 -20
- package/dist/storage-errors.d.ts +0 -163
- package/dist/storage-errors.js +0 -249
- package/dist/storage.d.ts +0 -184
- package/dist/storage.js +0 -438
- package/dist/sync-engine.d.ts +0 -16
- package/dist/sync-engine.js +0 -86
- package/dist/types.d.ts +0 -470
- package/dist/types.js +0 -40
- /package/dist/{auth-storage.js → cjs/auth-storage.js} +0 -0
package/dist/database.js
DELETED
|
@@ -1,508 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.KoolbaseDatabase = void 0;
|
|
4
|
-
const cache_store_1 = require("./cache-store");
|
|
5
|
-
const sync_engine_1 = require("./sync-engine");
|
|
6
|
-
const record_1 = require("./record");
|
|
7
|
-
const database_errors_1 = require("./database-errors");
|
|
8
|
-
function generateId() {
|
|
9
|
-
return 'local_' + Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
10
|
-
}
|
|
11
|
-
function batchOpToWire(op) {
|
|
12
|
-
switch (op.type) {
|
|
13
|
-
case 'insert':
|
|
14
|
-
return { type: 'insert', collection: op.collection, data: op.data };
|
|
15
|
-
case 'update':
|
|
16
|
-
return { type: 'update', record_id: op.recordId, data: op.data };
|
|
17
|
-
case 'delete':
|
|
18
|
-
return { type: 'delete', record_id: op.recordId };
|
|
19
|
-
case 'upsert':
|
|
20
|
-
return {
|
|
21
|
-
type: 'upsert',
|
|
22
|
-
collection: op.collection,
|
|
23
|
-
match: op.match,
|
|
24
|
-
data: op.data,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
class KoolbaseDatabase {
|
|
29
|
-
constructor(config, getUserId, getToken) {
|
|
30
|
-
this.config = config;
|
|
31
|
-
this.getUserId = getUserId;
|
|
32
|
-
this.getToken = getToken;
|
|
33
|
-
this.syncEngine = new sync_engine_1.SyncEngine(config, getUserId, getToken);
|
|
34
|
-
this.syncEngine.start();
|
|
35
|
-
}
|
|
36
|
-
// getUserId is kept only for local cache keys / offline metadata; request
|
|
37
|
-
// identity now comes solely from the verified access token.
|
|
38
|
-
async buildHeaders() {
|
|
39
|
-
const token = await this.getToken();
|
|
40
|
-
return {
|
|
41
|
-
'Content-Type': 'application/json',
|
|
42
|
-
'x-api-key': this.config.publicKey,
|
|
43
|
-
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
async request(method, path, body) {
|
|
47
|
-
const res = await fetch(`${this.config.baseUrl}${path}`, {
|
|
48
|
-
method,
|
|
49
|
-
headers: await this.buildHeaders(),
|
|
50
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
51
|
-
});
|
|
52
|
-
const data = await res.json();
|
|
53
|
-
if (!res.ok) {
|
|
54
|
-
throw (0, database_errors_1.koolbaseDataError)(res.status, data, `Request failed: ${res.status}`);
|
|
55
|
-
}
|
|
56
|
-
return data;
|
|
57
|
-
}
|
|
58
|
-
// ─── Query (cache-first) ───────────────────────────────────────────────────
|
|
59
|
-
async runQuery(collection, options) {
|
|
60
|
-
const raw = await this.request('POST', '/v1/sdk/db/query', {
|
|
61
|
-
collection,
|
|
62
|
-
filters: options.filters ?? {},
|
|
63
|
-
limit: options.limit ?? 20,
|
|
64
|
-
offset: options.offset ?? 0,
|
|
65
|
-
order_by: options.orderBy,
|
|
66
|
-
order_desc: options.orderDesc ?? false,
|
|
67
|
-
populate: options.populate ?? [],
|
|
68
|
-
});
|
|
69
|
-
return { records: raw.records.map(record_1.recordFromWire), total: raw.total };
|
|
70
|
-
}
|
|
71
|
-
async query(collection, options = {}) {
|
|
72
|
-
const userId = this.getUserId() ?? 'anonymous';
|
|
73
|
-
const queryHash = (0, cache_store_1.hashQuery)(collection, options);
|
|
74
|
-
const cached = await (0, cache_store_1.getCached)(userId, collection, queryHash);
|
|
75
|
-
this.runQuery(collection, options)
|
|
76
|
-
.then(result => (0, cache_store_1.setCached)(userId, collection, queryHash, result))
|
|
77
|
-
.catch(() => {
|
|
78
|
-
// Network unavailable — cached data already returned
|
|
79
|
-
});
|
|
80
|
-
if (cached) {
|
|
81
|
-
return { ...cached, isFromCache: true };
|
|
82
|
-
}
|
|
83
|
-
const result = await this.runQuery(collection, options);
|
|
84
|
-
await (0, cache_store_1.setCached)(userId, collection, queryHash, result);
|
|
85
|
-
return { ...result, isFromCache: false };
|
|
86
|
-
}
|
|
87
|
-
// ─── Insert (online-first with offline fallback) ───────────────────────────
|
|
88
|
-
/**
|
|
89
|
-
* Insert a new record into a collection.
|
|
90
|
-
*
|
|
91
|
-
* Online-first: awaits the server so a server-side rejection (unique
|
|
92
|
-
* violation, validation error, permission denial) surfaces as the typed
|
|
93
|
-
* `KoolbaseDataError` subclass — `insert` now throws `KoolbaseConflictError`
|
|
94
|
-
* with the offending field on a 409, matching `upsert` and `update`.
|
|
95
|
-
*
|
|
96
|
-
* On genuine network failure (server unreachable, timeout) the write is
|
|
97
|
-
* accepted optimistically: saved to the local cache and queued for sync
|
|
98
|
-
* when connectivity returns.
|
|
99
|
-
*/
|
|
100
|
-
async insert(collection, data) {
|
|
101
|
-
const userId = this.getUserId() ?? 'anonymous';
|
|
102
|
-
try {
|
|
103
|
-
// Online path: await the server and return the authoritative record
|
|
104
|
-
// (with the server-assigned id). Refresh the collection cache so the
|
|
105
|
-
// next query sees real data instead of a stale optimistic copy.
|
|
106
|
-
const raw = await this.request('POST', '/v1/sdk/db/insert', { collection, data });
|
|
107
|
-
const record = (0, record_1.recordFromWire)(raw);
|
|
108
|
-
await (0, cache_store_1.invalidateCache)(userId, collection);
|
|
109
|
-
return record;
|
|
110
|
-
}
|
|
111
|
-
catch (e) {
|
|
112
|
-
// Server-reachable rejection: the server saw the request and refused.
|
|
113
|
-
// Surface to the caller without writing optimistic state or queuing —
|
|
114
|
-
// the server has already decided it will not accept this write, and
|
|
115
|
-
// queuing it would just spin SyncEngine until max retries.
|
|
116
|
-
if (e instanceof database_errors_1.KoolbaseDataError)
|
|
117
|
-
throw e;
|
|
118
|
-
// Genuine network failure → offline path: save to local cache and
|
|
119
|
-
// queue for SyncEngine to retry when online. Return the optimistic
|
|
120
|
-
// record so the UI has something to render in the meantime.
|
|
121
|
-
const optimisticRecord = {
|
|
122
|
-
id: generateId(),
|
|
123
|
-
createdBy: userId,
|
|
124
|
-
data,
|
|
125
|
-
createdAt: new Date().toISOString(),
|
|
126
|
-
updatedAt: new Date().toISOString(),
|
|
127
|
-
};
|
|
128
|
-
await (0, cache_store_1.optimisticallyInsert)(userId, collection, optimisticRecord);
|
|
129
|
-
await (0, cache_store_1.addToWriteQueue)(userId, {
|
|
130
|
-
id: generateId(),
|
|
131
|
-
type: 'insert',
|
|
132
|
-
collection,
|
|
133
|
-
data,
|
|
134
|
-
});
|
|
135
|
-
return optimisticRecord;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
// ─── Upsert (online-only) ─────────────────────────────────────────────────
|
|
139
|
-
/**
|
|
140
|
-
* Insert a record, or update the existing one matching `match`.
|
|
141
|
-
*
|
|
142
|
-
* The server decides: exactly one match updates it, no match inserts a new
|
|
143
|
-
* record (seeded with the `match` fields), more than one match is an error.
|
|
144
|
-
* Returns the resulting record and a `created` flag (true = inserted, false
|
|
145
|
-
* = updated).
|
|
146
|
-
*
|
|
147
|
-
* Online-only by design. Unlike `insert`, an upsert is NOT queued offline:
|
|
148
|
-
* the insert-vs-update decision needs the server's authoritative view of
|
|
149
|
-
* what already exists, so deferring it could create a duplicate or apply a
|
|
150
|
-
* wrong update on later sync. It throws on network failure instead. A raw
|
|
151
|
-
* fetch is used (not `request`) so the status code is readable: 201 =
|
|
152
|
-
* created, 200 = updated.
|
|
153
|
-
*/
|
|
154
|
-
async upsert(collection, match, data) {
|
|
155
|
-
const res = await fetch(`${this.config.baseUrl}/v1/sdk/db/upsert`, {
|
|
156
|
-
method: 'POST',
|
|
157
|
-
headers: await this.buildHeaders(),
|
|
158
|
-
body: JSON.stringify({ collection, match, data }),
|
|
159
|
-
});
|
|
160
|
-
const body = await res.json();
|
|
161
|
-
if (!res.ok) {
|
|
162
|
-
throw (0, database_errors_1.koolbaseDataError)(res.status, body, `Upsert failed: ${res.status}`);
|
|
163
|
-
}
|
|
164
|
-
const created = res.status === 201;
|
|
165
|
-
const record = (0, record_1.recordFromWire)(body);
|
|
166
|
-
// Keep the cache fresh, same intent as insert's post-success invalidate.
|
|
167
|
-
const userId = this.getUserId() ?? 'anonymous';
|
|
168
|
-
await (0, cache_store_1.invalidateCache)(userId, collection);
|
|
169
|
-
return { record, created };
|
|
170
|
-
}
|
|
171
|
-
// ─── Delete where (online-only) ─────────────────────────────────────────────
|
|
172
|
-
/**
|
|
173
|
-
* Bulk-delete every record in `collection` matching `filters`.
|
|
174
|
-
*
|
|
175
|
-
* The server applies the collection's delete rule (scoping to the caller for
|
|
176
|
-
* owner/scoped rules) and returns the number of records deleted.
|
|
177
|
-
*
|
|
178
|
-
* Online-only by design — like upsert, this is NOT queued offline: a bulk
|
|
179
|
-
* delete needs the server's authoritative view of what matches, so it throws
|
|
180
|
-
* on network failure rather than risk deleting the wrong set on later sync.
|
|
181
|
-
* The collection cache is invalidated on success.
|
|
182
|
-
*/
|
|
183
|
-
async deleteWhere(collection, filters) {
|
|
184
|
-
const res = await fetch(`${this.config.baseUrl}/v1/sdk/db/delete-where`, {
|
|
185
|
-
method: 'POST',
|
|
186
|
-
headers: await this.buildHeaders(),
|
|
187
|
-
body: JSON.stringify({ collection, filters }),
|
|
188
|
-
});
|
|
189
|
-
const body = await res.json();
|
|
190
|
-
if (!res.ok) {
|
|
191
|
-
throw (0, database_errors_1.koolbaseDataError)(res.status, body, `Delete failed: ${res.status}`);
|
|
192
|
-
}
|
|
193
|
-
const userId = this.getUserId() ?? 'anonymous';
|
|
194
|
-
await (0, cache_store_1.invalidateCache)(userId, collection);
|
|
195
|
-
return body.deleted ?? 0;
|
|
196
|
-
}
|
|
197
|
-
// ─── Batch (atomic, online-only) ────────────────────────────────────────────
|
|
198
|
-
/**
|
|
199
|
-
* Run multiple writes as a single atomic transaction.
|
|
200
|
-
*
|
|
201
|
-
* All `operations` commit together or none are applied — the server runs
|
|
202
|
-
* them in one database transaction and rolls back entirely on any failure.
|
|
203
|
-
* Operations apply in order and may span multiple collections.
|
|
204
|
-
*
|
|
205
|
-
* Online-only by design (like `upsert` and `deleteWhere`): atomicity needs
|
|
206
|
-
* the server's authoritative view, so a batch is never queued offline — it
|
|
207
|
-
* throws on network failure. A server-side rejection throws a
|
|
208
|
-
* `KoolbaseDataException` whose message identifies which operation failed;
|
|
209
|
-
* nothing was persisted.
|
|
210
|
-
*
|
|
211
|
-
* Returns one `BatchResult` per operation, in order.
|
|
212
|
-
*
|
|
213
|
-
* @example
|
|
214
|
-
* const results = await Koolbase.db.batch([
|
|
215
|
-
* BatchOp.insert('orders', { total: 50 }),
|
|
216
|
-
* BatchOp.update(inventoryId, { stock: 9 }),
|
|
217
|
-
* BatchOp.upsert('counters', { match: { name: 'orders' }, data: { value: 1 } }),
|
|
218
|
-
* BatchOp.delete(cartItemId),
|
|
219
|
-
* ]);
|
|
220
|
-
*/
|
|
221
|
-
async batch(operations) {
|
|
222
|
-
if (operations.length === 0) {
|
|
223
|
-
throw new Error('batch requires at least one operation');
|
|
224
|
-
}
|
|
225
|
-
const res = await fetch(`${this.config.baseUrl}/v1/sdk/db/batch`, {
|
|
226
|
-
method: 'POST',
|
|
227
|
-
headers: await this.buildHeaders(),
|
|
228
|
-
body: JSON.stringify({
|
|
229
|
-
operations: operations.map(batchOpToWire),
|
|
230
|
-
}),
|
|
231
|
-
});
|
|
232
|
-
const body = await res.json();
|
|
233
|
-
if (!res.ok) {
|
|
234
|
-
throw (0, database_errors_1.koolbaseDataError)(res.status, body, `Batch failed: ${res.status}`);
|
|
235
|
-
}
|
|
236
|
-
const results = (body.results ?? []).map(r => ({
|
|
237
|
-
type: r.type ?? '',
|
|
238
|
-
record: r.record
|
|
239
|
-
? (0, record_1.recordFromWire)(r.record)
|
|
240
|
-
: undefined,
|
|
241
|
-
created: r.created,
|
|
242
|
-
deleted: r.deleted ?? false,
|
|
243
|
-
}));
|
|
244
|
-
// Keep the cache consistent with what committed. Insert/upsert carry the
|
|
245
|
-
// collection in the input op; update/delete address records by id, so we
|
|
246
|
-
// don't know the collection at this layer — those refresh naturally on
|
|
247
|
-
// the next query for the affected collection.
|
|
248
|
-
const userId = this.getUserId() ?? 'anonymous';
|
|
249
|
-
const touched = new Set();
|
|
250
|
-
for (const op of operations) {
|
|
251
|
-
if (op.type === 'insert' || op.type === 'upsert') {
|
|
252
|
-
touched.add(op.collection);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
for (const col of touched) {
|
|
256
|
-
await (0, cache_store_1.invalidateCache)(userId, col);
|
|
257
|
-
}
|
|
258
|
-
return results;
|
|
259
|
-
}
|
|
260
|
-
// ─── Get single record ──────────────────────────────────────────────────────
|
|
261
|
-
// ─── Get single record ──────────────────────────────────────────────────────
|
|
262
|
-
async get(recordId) {
|
|
263
|
-
const raw = await this.request('GET', `/v1/sdk/db/records/${recordId}`);
|
|
264
|
-
return (0, record_1.recordFromWire)(raw);
|
|
265
|
-
}
|
|
266
|
-
// ─── Update (online-first with offline fallback) ───────────────────────────
|
|
267
|
-
/**
|
|
268
|
-
* Update a record's fields by id.
|
|
269
|
-
*
|
|
270
|
-
* Online-first: awaits the server so a server-side rejection (unique
|
|
271
|
-
* violation, not found, permission denial) surfaces as the typed
|
|
272
|
-
* `KoolbaseDataError` subclass. An update that would violate a unique
|
|
273
|
-
* constraint now throws `KoolbaseConflictError` with the offending field —
|
|
274
|
-
* same shape as `insert` and `upsert`.
|
|
275
|
-
*
|
|
276
|
-
* On genuine network failure the update is queued for sync and a partial
|
|
277
|
-
* optimistic record is returned so the UI can re-render the new fields
|
|
278
|
-
* immediately.
|
|
279
|
-
*/
|
|
280
|
-
async update(recordId, data) {
|
|
281
|
-
const userId = this.getUserId() ?? 'anonymous';
|
|
282
|
-
try {
|
|
283
|
-
const raw = await this.request('PATCH', `/v1/sdk/db/records/${recordId}`, { data });
|
|
284
|
-
return (0, record_1.recordFromWire)(raw);
|
|
285
|
-
}
|
|
286
|
-
catch (e) {
|
|
287
|
-
// Server-reachable rejection: surface to caller without queuing — the
|
|
288
|
-
// server already refused the write and will refuse it again on retry.
|
|
289
|
-
if (e instanceof database_errors_1.KoolbaseDataError)
|
|
290
|
-
throw e;
|
|
291
|
-
// Genuine network failure → queue for sync and return an optimistic
|
|
292
|
-
// partial record so the UI reflects the update immediately.
|
|
293
|
-
await (0, cache_store_1.addToWriteQueue)(userId, {
|
|
294
|
-
id: generateId(),
|
|
295
|
-
type: 'update',
|
|
296
|
-
recordId,
|
|
297
|
-
data,
|
|
298
|
-
});
|
|
299
|
-
return {
|
|
300
|
-
id: recordId,
|
|
301
|
-
data,
|
|
302
|
-
createdAt: '',
|
|
303
|
-
updatedAt: new Date().toISOString(),
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
// ─── Delete ─────────────────────────────────────────────────────────────────
|
|
308
|
-
async delete(recordId) {
|
|
309
|
-
const userId = this.getUserId() ?? 'anonymous';
|
|
310
|
-
// Add to write queue
|
|
311
|
-
await (0, cache_store_1.addToWriteQueue)(userId, {
|
|
312
|
-
id: generateId(),
|
|
313
|
-
type: 'delete',
|
|
314
|
-
recordId,
|
|
315
|
-
});
|
|
316
|
-
// Try network
|
|
317
|
-
const res = await fetch(`${this.config.baseUrl}/v1/sdk/db/records/${recordId}`, { method: 'DELETE', headers: await this.buildHeaders(), });
|
|
318
|
-
if (!res.ok && res.status !== 204) {
|
|
319
|
-
// Queued for sync — will retry when online
|
|
320
|
-
}
|
|
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
|
-
* Queue an embedding job for a record's vector field. The server's
|
|
399
|
-
* embedding worker picks it up within ~1 second.
|
|
400
|
-
*
|
|
401
|
-
* If `text` is omitted, the vector field's configured `source_field`
|
|
402
|
-
* value on the record is used.
|
|
403
|
-
*
|
|
404
|
-
* @example
|
|
405
|
-
* await Koolbase.db.embedText({
|
|
406
|
-
* collection: 'articles',
|
|
407
|
-
* recordId: article.$id,
|
|
408
|
-
* vectorField: 'content_embedding',
|
|
409
|
-
* });
|
|
410
|
-
*/
|
|
411
|
-
async embedText(opts) {
|
|
412
|
-
const body = {
|
|
413
|
-
collection: opts.collection,
|
|
414
|
-
record_id: opts.recordId,
|
|
415
|
-
vector_field: opts.vectorField,
|
|
416
|
-
};
|
|
417
|
-
if (opts.text && opts.text.length > 0) {
|
|
418
|
-
body.text = opts.text;
|
|
419
|
-
}
|
|
420
|
-
await this.request('POST', '/v1/sdk/db/embed-text', body);
|
|
421
|
-
}
|
|
422
|
-
/**
|
|
423
|
-
* Search for records based on their semantic similarity to a query.
|
|
424
|
-
*
|
|
425
|
-
* @example
|
|
426
|
-
* // Server-side embedding — most common:
|
|
427
|
-
* const result = await Koolbase.db.searchSemantic({
|
|
428
|
-
* collection: 'articles',
|
|
429
|
-
* field: 'content_embedding',
|
|
430
|
-
* queryText: 'how do I configure CI/CD?',
|
|
431
|
-
* limit: 10,
|
|
432
|
-
* });
|
|
433
|
-
*
|
|
434
|
-
* // Client-side embedding:
|
|
435
|
-
* const result = await Koolbase.db.searchSemantic({
|
|
436
|
-
* collection: 'articles',
|
|
437
|
-
* field: 'content_embedding',
|
|
438
|
-
* queryVector: precomputed,
|
|
439
|
-
* limit: 10,
|
|
440
|
-
* });
|
|
441
|
-
*
|
|
442
|
-
* // Hybrid search (vector + BM25, RRF-fused):
|
|
443
|
-
* const result = await Koolbase.db.searchSemantic({
|
|
444
|
-
* collection: 'articles',
|
|
445
|
-
* field: 'content_embedding',
|
|
446
|
-
* queryText: 'how do I configure CI/CD?',
|
|
447
|
-
* mode: 'hybrid',
|
|
448
|
-
* minSimilarity: 70,
|
|
449
|
-
* });
|
|
450
|
-
*
|
|
451
|
-
* `mode` selects the retrieval strategy:
|
|
452
|
-
* - `'semantic'` (default) — pure vector search via HNSW
|
|
453
|
-
* - `'lexical'` — pure BM25 over the field's source text
|
|
454
|
-
* - `'hybrid'` — vector + lexical, RRF-fused (k=60)
|
|
455
|
-
*
|
|
456
|
-
* `minSimilarity` (0..100, optional) filters out results below the
|
|
457
|
-
* given similarity percentage server-side. Saves bandwidth on weak
|
|
458
|
-
* matches. Only valid for semantic and hybrid; rejected by the
|
|
459
|
-
* server on lexical mode.
|
|
460
|
-
*/
|
|
461
|
-
async searchSemantic(opts) {
|
|
462
|
-
const hasVector = Array.isArray(opts.queryVector) && opts.queryVector.length > 0;
|
|
463
|
-
const hasText = typeof opts.queryText === 'string' && opts.queryText.trim().length > 0;
|
|
464
|
-
if (!hasVector && !hasText) {
|
|
465
|
-
throw new Error('searchSemantic: provide either queryVector or queryText.');
|
|
466
|
-
}
|
|
467
|
-
if (hasVector && hasText) {
|
|
468
|
-
throw new Error('searchSemantic: provide only one of queryVector or queryText.');
|
|
469
|
-
}
|
|
470
|
-
if (opts.minSimilarity !== undefined &&
|
|
471
|
-
(opts.minSimilarity < 0 || opts.minSimilarity > 100)) {
|
|
472
|
-
throw new Error(`searchSemantic: minSimilarity must be between 0 and 100, got ${opts.minSimilarity}.`);
|
|
473
|
-
}
|
|
474
|
-
const body = {
|
|
475
|
-
collection: opts.collection,
|
|
476
|
-
field: opts.field,
|
|
477
|
-
limit: opts.limit ?? 20,
|
|
478
|
-
// Always send mode so the server uses the SDK's intent rather
|
|
479
|
-
// than its own default. Omitting for 'semantic' would also work
|
|
480
|
-
// (server defaults to semantic) but explicit is safer if the
|
|
481
|
-
// server's default ever shifts.
|
|
482
|
-
mode: opts.mode ?? 'semantic',
|
|
483
|
-
};
|
|
484
|
-
if (hasVector)
|
|
485
|
-
body.query_vector = opts.queryVector;
|
|
486
|
-
if (hasText)
|
|
487
|
-
body.query_text = opts.queryText;
|
|
488
|
-
if (opts.where && Object.keys(opts.where).length > 0) {
|
|
489
|
-
body.where = opts.where;
|
|
490
|
-
}
|
|
491
|
-
if (opts.minSimilarity !== undefined) {
|
|
492
|
-
body.min_similarity = opts.minSimilarity;
|
|
493
|
-
}
|
|
494
|
-
const raw = await this.request('POST', '/v1/sdk/db/search-semantic', body);
|
|
495
|
-
return {
|
|
496
|
-
hits: (raw.results ?? []).map(r => ({
|
|
497
|
-
record: (0, record_1.recordFromWire)(r.record),
|
|
498
|
-
distance: r.distance,
|
|
499
|
-
})),
|
|
500
|
-
total: raw.total ?? (raw.results ?? []).length,
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
// ─── Manual sync ────────────────────────────────────────────────────────────
|
|
504
|
-
async syncPendingWrites() {
|
|
505
|
-
await this.syncEngine.flush();
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
exports.KoolbaseDatabase = KoolbaseDatabase;
|
package/dist/device-id.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function getOrCreateDeviceId(): Promise<string>;
|
package/dist/device-id.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getOrCreateDeviceId = getOrCreateDeviceId;
|
|
7
|
-
const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
|
|
8
|
-
// Single source of the anonymous device identifier for the whole SDK.
|
|
9
|
-
// Generated once, persisted, and shared by messaging (registration keying),
|
|
10
|
-
// feature flags (rollout bucketing: stableHash(deviceId + ":" + key) % 100),
|
|
11
|
-
// code push (targeting), and analytics. Previously each subsystem was handed a
|
|
12
|
-
// hardcoded 'rn-device' literal, so every RN device collided on one messaging
|
|
13
|
-
// registration row and bucketed identically for every rollout — a 10% flag was
|
|
14
|
-
// on for everyone or no one, never 10%.
|
|
15
|
-
//
|
|
16
|
-
// The id is anonymous, not a secret: what matters is uniform DISTRIBUTION so
|
|
17
|
-
// hash(id) % 100 is even, not unpredictability. We use crypto.getRandomValues
|
|
18
|
-
// when the runtime provides it (best distribution, no modulo bias) and fall
|
|
19
|
-
// back to Math.random otherwise — mirroring the runtime-guarded crypto use
|
|
20
|
-
// already in code-push.ts, and adding no dependency (per the SDK's stated
|
|
21
|
-
// preference against a crypto-grade UUID dependency for non-security ids).
|
|
22
|
-
const DEVICE_ID_KEY = 'koolbase:device_id';
|
|
23
|
-
let _cached = null;
|
|
24
|
-
async function getOrCreateDeviceId() {
|
|
25
|
-
if (_cached)
|
|
26
|
-
return _cached;
|
|
27
|
-
try {
|
|
28
|
-
const existing = await async_storage_1.default.getItem(DEVICE_ID_KEY);
|
|
29
|
-
if (existing) {
|
|
30
|
-
_cached = existing;
|
|
31
|
-
return existing;
|
|
32
|
-
}
|
|
33
|
-
const newId = generateUUID();
|
|
34
|
-
await async_storage_1.default.setItem(DEVICE_ID_KEY, newId);
|
|
35
|
-
_cached = newId;
|
|
36
|
-
return newId;
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
// Storage unavailable — return an ephemeral (unpersisted) id so the SDK
|
|
40
|
-
// stays functional rather than throwing. Not stable across launches.
|
|
41
|
-
return generateUUID();
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
// UUID v4. Uses crypto.getRandomValues where available for uniform,
|
|
45
|
-
// modulo-bias-free bytes; Math.random fallback keeps it dependency-free.
|
|
46
|
-
function generateUUID() {
|
|
47
|
-
const bytes = new Uint8Array(16);
|
|
48
|
-
const c = typeof crypto !== 'undefined' ? crypto : undefined;
|
|
49
|
-
if (c && typeof c.getRandomValues === 'function') {
|
|
50
|
-
c.getRandomValues(bytes);
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
for (let i = 0; i < 16; i++)
|
|
54
|
-
bytes[i] = (Math.random() * 256) | 0;
|
|
55
|
-
}
|
|
56
|
-
bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4
|
|
57
|
-
bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant
|
|
58
|
-
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
59
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
60
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Koolbase React Native SDK version. Sent in the `x-koolbase-sdk-version`
|
|
3
|
-
* header on every authenticated request so the server can route
|
|
4
|
-
* version-conditional logic (deprecation warnings, schema migrations,
|
|
5
|
-
* feature flags). Must match the `version` field in package.json.
|
|
6
|
-
*/
|
|
7
|
-
export declare const koolbaseSdkVersion = "1.11.0";
|
|
8
|
-
/**
|
|
9
|
-
* Builds device-identifying headers attached to every Koolbase auth
|
|
10
|
-
* request. Mirrors the Flutter SDK's `DeviceMetadata` for parity. Apps
|
|
11
|
-
* with privacy concerns can swap in a custom storage adapter to avoid
|
|
12
|
-
* persisting the device label.
|
|
13
|
-
*
|
|
14
|
-
* Headers emitted:
|
|
15
|
-
* - User-Agent: koolbase-react-native/<sdk> (<platform> <version>)
|
|
16
|
-
* - x-koolbase-sdk: react-native
|
|
17
|
-
* - x-koolbase-sdk-version: <koolbaseSdkVersion>
|
|
18
|
-
* - x-koolbase-platform: ios | android | web | etc.
|
|
19
|
-
* - x-koolbase-platform-version: numeric SDK level or OS version string
|
|
20
|
-
* - x-koolbase-app-version: from KoolbaseConfig.appVersion or 'unknown'
|
|
21
|
-
* - x-koolbase-device-label: persistent UUID per install
|
|
22
|
-
*/
|
|
23
|
-
export declare class DeviceMetadata {
|
|
24
|
-
private cached;
|
|
25
|
-
private ephemeralLabel;
|
|
26
|
-
private readonly appVersion;
|
|
27
|
-
constructor(appVersion?: string);
|
|
28
|
-
/**
|
|
29
|
-
* Build (or return cached) device headers. The first call may perform
|
|
30
|
-
* an async keychain read to look up the persisted device label;
|
|
31
|
-
* subsequent calls return the in-memory cache synchronously via the
|
|
32
|
-
* returned Promise.
|
|
33
|
-
*/
|
|
34
|
-
build(): Promise<Record<string, string>>;
|
|
35
|
-
private getOrCreateDeviceLabel;
|
|
36
|
-
}
|