@techfinityedge/koolbase-react-native 3.1.0 → 4.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 +22 -0
- package/dist/database-errors.d.ts +5 -4
- package/dist/database-errors.js +5 -4
- package/dist/database.d.ts +25 -0
- package/dist/database.js +74 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -257,6 +257,28 @@ const results = await Koolbase.db.batch([
|
|
|
257
257
|
|
|
258
258
|
---
|
|
259
259
|
|
|
260
|
+
### Handling write conflicts
|
|
261
|
+
|
|
262
|
+
`insert`, `update`, and `upsert` are online-first: when the server is reachable they throw a typed error on rejection. Catch `KoolbaseConflictError` to handle unique-constraint violations (e.g. a duplicate email):
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
import { KoolbaseConflictError } from '@techfinityedge/koolbase-react-native';
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
await Koolbase.db.insert('users', { email, name });
|
|
269
|
+
} catch (e) {
|
|
270
|
+
if (e instanceof KoolbaseConflictError) {
|
|
271
|
+
showError(`That ${e.field ?? 'value'} is already in use.`);
|
|
272
|
+
} else {
|
|
273
|
+
throw e;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
When the device is offline, these writes are queued and synced automatically when connectivity returns.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
260
282
|
## Storage
|
|
261
283
|
|
|
262
284
|
```typescript
|
|
@@ -20,10 +20,11 @@ export declare class KoolbaseDataError extends Error {
|
|
|
20
20
|
* (`details.field`) — useful when a collection has more than one unique
|
|
21
21
|
* constraint and you need to know which value clashed.
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
23
|
+
* Surfaced by `insert`, `update`, and `upsert` whenever the server is
|
|
24
|
+
* reachable and rejects the write with a 409. These writes are online-first:
|
|
25
|
+
* a server-side conflict throws immediately. Only a genuine network failure
|
|
26
|
+
* falls back to the offline queue, where a conflict that surfaces at sync
|
|
27
|
+
* time is handled by the sync engine rather than thrown here.
|
|
27
28
|
*
|
|
28
29
|
* @example
|
|
29
30
|
* try {
|
package/dist/database-errors.js
CHANGED
|
@@ -29,10 +29,11 @@ exports.KoolbaseDataError = KoolbaseDataError;
|
|
|
29
29
|
* (`details.field`) — useful when a collection has more than one unique
|
|
30
30
|
* constraint and you need to know which value clashed.
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
32
|
+
* Surfaced by `insert`, `update`, and `upsert` whenever the server is
|
|
33
|
+
* reachable and rejects the write with a 409. These writes are online-first:
|
|
34
|
+
* a server-side conflict throws immediately. Only a genuine network failure
|
|
35
|
+
* falls back to the offline queue, where a conflict that surfaces at sync
|
|
36
|
+
* time is handled by the sync engine rather than thrown here.
|
|
36
37
|
*
|
|
37
38
|
* @example
|
|
38
39
|
* try {
|
package/dist/database.d.ts
CHANGED
|
@@ -9,6 +9,18 @@ export declare class KoolbaseDatabase {
|
|
|
9
9
|
private request;
|
|
10
10
|
private runQuery;
|
|
11
11
|
query(collection: string, options?: QueryOptions): Promise<QueryResult>;
|
|
12
|
+
/**
|
|
13
|
+
* Insert a new record into a collection.
|
|
14
|
+
*
|
|
15
|
+
* Online-first: awaits the server so a server-side rejection (unique
|
|
16
|
+
* violation, validation error, permission denial) surfaces as the typed
|
|
17
|
+
* `KoolbaseDataError` subclass — `insert` now throws `KoolbaseConflictError`
|
|
18
|
+
* with the offending field on a 409, matching `upsert` and `update`.
|
|
19
|
+
*
|
|
20
|
+
* On genuine network failure (server unreachable, timeout) the write is
|
|
21
|
+
* accepted optimistically: saved to the local cache and queued for sync
|
|
22
|
+
* when connectivity returns.
|
|
23
|
+
*/
|
|
12
24
|
insert(collection: string, data: Record<string, unknown>): Promise<KoolbaseRecord>;
|
|
13
25
|
/**
|
|
14
26
|
* Insert a record, or update the existing one matching `match`.
|
|
@@ -63,6 +75,19 @@ export declare class KoolbaseDatabase {
|
|
|
63
75
|
*/
|
|
64
76
|
batch(operations: BatchOp[]): Promise<BatchResult[]>;
|
|
65
77
|
get(recordId: string): Promise<KoolbaseRecord>;
|
|
78
|
+
/**
|
|
79
|
+
* Update a record's fields by id.
|
|
80
|
+
*
|
|
81
|
+
* Online-first: awaits the server so a server-side rejection (unique
|
|
82
|
+
* violation, not found, permission denial) surfaces as the typed
|
|
83
|
+
* `KoolbaseDataError` subclass. An update that would violate a unique
|
|
84
|
+
* constraint now throws `KoolbaseConflictError` with the offending field —
|
|
85
|
+
* same shape as `insert` and `upsert`.
|
|
86
|
+
*
|
|
87
|
+
* On genuine network failure the update is queued for sync and a partial
|
|
88
|
+
* optimistic record is returned so the UI can re-render the new fields
|
|
89
|
+
* immediately.
|
|
90
|
+
*/
|
|
66
91
|
update(recordId: string, data: Record<string, unknown>): Promise<KoolbaseRecord>;
|
|
67
92
|
delete(recordId: string): Promise<void>;
|
|
68
93
|
syncPendingWrites(): Promise<void>;
|
package/dist/database.js
CHANGED
|
@@ -84,39 +84,56 @@ class KoolbaseDatabase {
|
|
|
84
84
|
await (0, cache_store_1.setCached)(userId, collection, queryHash, result);
|
|
85
85
|
return { ...result, isFromCache: false };
|
|
86
86
|
}
|
|
87
|
-
// ─── Insert (
|
|
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
|
+
*/
|
|
88
100
|
async insert(collection, data) {
|
|
89
101
|
const userId = this.getUserId() ?? 'anonymous';
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
data
|
|
95
|
-
|
|
96
|
-
updatedAt: new Date().toISOString(),
|
|
97
|
-
};
|
|
98
|
-
// Write to local cache immediately
|
|
99
|
-
await (0, cache_store_1.optimisticallyInsert)(userId, collection, optimisticRecord);
|
|
100
|
-
// Add to write queue
|
|
101
|
-
await (0, cache_store_1.addToWriteQueue)(userId, {
|
|
102
|
-
id: generateId(),
|
|
103
|
-
type: 'insert',
|
|
104
|
-
collection,
|
|
105
|
-
data,
|
|
106
|
-
});
|
|
107
|
-
// Try network in background
|
|
108
|
-
this.request('POST', '/v1/sdk/db/insert', {
|
|
109
|
-
collection,
|
|
110
|
-
data,
|
|
111
|
-
})
|
|
112
|
-
.then(async (serverRecord) => {
|
|
113
|
-
// Invalidate cache so next query gets real data
|
|
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);
|
|
114
108
|
await (0, cache_store_1.invalidateCache)(userId, collection);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
+
}
|
|
120
137
|
}
|
|
121
138
|
// ─── Upsert (online-only) ─────────────────────────────────────────────────
|
|
122
139
|
/**
|
|
@@ -246,20 +263,39 @@ class KoolbaseDatabase {
|
|
|
246
263
|
const raw = await this.request('GET', `/v1/sdk/db/records/${recordId}`);
|
|
247
264
|
return (0, record_1.recordFromWire)(raw);
|
|
248
265
|
}
|
|
249
|
-
// ─── Update
|
|
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
|
+
*/
|
|
250
280
|
async update(recordId, data) {
|
|
251
281
|
const userId = this.getUserId() ?? 'anonymous';
|
|
252
|
-
await (0, cache_store_1.addToWriteQueue)(userId, {
|
|
253
|
-
id: generateId(),
|
|
254
|
-
type: 'update',
|
|
255
|
-
recordId,
|
|
256
|
-
data,
|
|
257
|
-
});
|
|
258
282
|
try {
|
|
259
283
|
const raw = await this.request('PATCH', `/v1/sdk/db/records/${recordId}`, { data });
|
|
260
284
|
return (0, record_1.recordFromWire)(raw);
|
|
261
285
|
}
|
|
262
|
-
catch {
|
|
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
|
+
});
|
|
263
299
|
return {
|
|
264
300
|
id: recordId,
|
|
265
301
|
data,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@techfinityedge/koolbase-react-native",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "React Native SDK for Koolbase \u2014 auth, database, storage, realtime, feature flags, and functions in one package.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|