@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.
Files changed (59) hide show
  1. package/CHANGELOG.md +1342 -0
  2. package/README.md +462 -511
  3. package/dist/{auth-storage.d.ts → cjs/auth-storage.d.ts} +1 -1
  4. package/dist/cjs/index.d.ts +19 -0
  5. package/dist/cjs/index.js +125 -0
  6. package/dist/cjs/package.json +3 -0
  7. package/dist/cjs/platform.d.ts +2 -0
  8. package/dist/cjs/platform.js +43 -0
  9. package/dist/esm/auth-storage.d.ts +26 -0
  10. package/dist/esm/auth-storage.js +100 -0
  11. package/dist/esm/index.d.ts +19 -0
  12. package/dist/esm/index.js +106 -0
  13. package/dist/esm/package.json +3 -0
  14. package/dist/esm/platform.d.ts +2 -0
  15. package/dist/esm/platform.js +37 -0
  16. package/package.json +30 -24
  17. package/dist/analytics.d.ts +0 -24
  18. package/dist/analytics.js +0 -114
  19. package/dist/apple-auth.d.ts +0 -22
  20. package/dist/apple-auth.js +0 -74
  21. package/dist/auth-errors.d.ts +0 -117
  22. package/dist/auth-errors.js +0 -250
  23. package/dist/auth.d.ts +0 -199
  24. package/dist/auth.js +0 -794
  25. package/dist/cache-store.d.ts +0 -11
  26. package/dist/cache-store.js +0 -136
  27. package/dist/code-push.d.ts +0 -59
  28. package/dist/code-push.js +0 -255
  29. package/dist/database-errors.d.ts +0 -95
  30. package/dist/database-errors.js +0 -173
  31. package/dist/database.d.ts +0 -208
  32. package/dist/database.js +0 -508
  33. package/dist/device-id.d.ts +0 -1
  34. package/dist/device-id.js +0 -60
  35. package/dist/device-metadata.d.ts +0 -36
  36. package/dist/device-metadata.js +0 -102
  37. package/dist/flags.d.ts +0 -15
  38. package/dist/flags.js +0 -76
  39. package/dist/functions.d.ts +0 -8
  40. package/dist/functions.js +0 -70
  41. package/dist/index.d.ts +0 -45
  42. package/dist/index.js +0 -193
  43. package/dist/logic-engine.d.ts +0 -17
  44. package/dist/logic-engine.js +0 -193
  45. package/dist/messaging.d.ts +0 -13
  46. package/dist/messaging.js +0 -36
  47. package/dist/realtime.d.ts +0 -19
  48. package/dist/realtime.js +0 -148
  49. package/dist/record.d.ts +0 -2
  50. package/dist/record.js +0 -20
  51. package/dist/storage-errors.d.ts +0 -163
  52. package/dist/storage-errors.js +0 -249
  53. package/dist/storage.d.ts +0 -184
  54. package/dist/storage.js +0 -438
  55. package/dist/sync-engine.d.ts +0 -16
  56. package/dist/sync-engine.js +0 -86
  57. package/dist/types.d.ts +0 -470
  58. package/dist/types.js +0 -40
  59. /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;
@@ -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
- }