@techfinityedge/koolbase-react-native 2.0.0 → 2.2.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 +46 -0
- package/dist/database-errors.d.ts +23 -0
- package/dist/database-errors.js +31 -0
- package/dist/database.d.ts +29 -1
- package/dist/database.js +63 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -162,6 +162,52 @@ await Koolbase.db.update('record-id', { title: 'Updated' });
|
|
|
162
162
|
await Koolbase.db.delete('record-id');
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
+
### Handling unique-constraint conflicts
|
|
166
|
+
|
|
167
|
+
A write that would violate a unique constraint throws `KoolbaseConflictError`:
|
|
168
|
+
|
|
169
|
+
\`\`\`ts
|
|
170
|
+
try {
|
|
171
|
+
await koolbase.db.upsert('users', { email }, { name });
|
|
172
|
+
} catch (e) {
|
|
173
|
+
if (e instanceof KoolbaseConflictError) {
|
|
174
|
+
showError('That email is already registered.');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
\`\`\`
|
|
178
|
+
|
|
179
|
+
### Upsert
|
|
180
|
+
|
|
181
|
+
Insert a record, or update the existing one matching a filter.
|
|
182
|
+
|
|
183
|
+
\`\`\`ts
|
|
184
|
+
const result = await Koolbase.db.upsert(
|
|
185
|
+
'profiles',
|
|
186
|
+
{ user_id: userId },
|
|
187
|
+
{ weightKg: 70 }
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
console.log(result.created); // true if inserted, false if updated
|
|
191
|
+
console.log(result.record.id);
|
|
192
|
+
\`\`\`
|
|
193
|
+
|
|
194
|
+
> Online-only: needs the server's view to decide insert vs update, so unlike
|
|
195
|
+
> `insert` it isn't queued offline and throws on network failure.
|
|
196
|
+
|
|
197
|
+
### Delete where
|
|
198
|
+
|
|
199
|
+
Bulk-delete every record matching a filter. Returns the number deleted.
|
|
200
|
+
|
|
201
|
+
\`\`\`ts
|
|
202
|
+
const deleted = await Koolbase.db.deleteWhere('sessions', {
|
|
203
|
+
user_id: userId,
|
|
204
|
+
status: 'expired',
|
|
205
|
+
});
|
|
206
|
+
\`\`\`
|
|
207
|
+
|
|
208
|
+
> A non-empty filter is required. The collection's delete rule applies; for
|
|
209
|
+
> `owner`/`scoped` rules the delete is scoped to your own records. Online-only.
|
|
210
|
+
|
|
165
211
|
### Offline-first
|
|
166
212
|
|
|
167
213
|
```typescript
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when a write is rejected because the value would violate a
|
|
3
|
+
* collection's unique constraint — the server responds with 409 Conflict.
|
|
4
|
+
* Catch it to handle duplicates, e.g. an email or username already in use.
|
|
5
|
+
*
|
|
6
|
+
* Currently surfaced by `upsert` (the online-only write). `insert` and
|
|
7
|
+
* `update` are optimistic/offline-first: they accept the write locally and
|
|
8
|
+
* sync in the background, so a constraint conflict on those paths is a
|
|
9
|
+
* sync-time concern rather than a thrown error.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* try {
|
|
13
|
+
* await koolbase.db.upsert('users', { email }, { name });
|
|
14
|
+
* } catch (e) {
|
|
15
|
+
* if (e instanceof KoolbaseConflictError) {
|
|
16
|
+
* showError('That email is already registered.');
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
*/
|
|
20
|
+
export declare class KoolbaseConflictError extends Error {
|
|
21
|
+
code: string;
|
|
22
|
+
constructor(message?: string);
|
|
23
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KoolbaseConflictError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Thrown when a write is rejected because the value would violate a
|
|
6
|
+
* collection's unique constraint — the server responds with 409 Conflict.
|
|
7
|
+
* Catch it to handle duplicates, e.g. an email or username already in use.
|
|
8
|
+
*
|
|
9
|
+
* Currently surfaced by `upsert` (the online-only write). `insert` and
|
|
10
|
+
* `update` are optimistic/offline-first: they accept the write locally and
|
|
11
|
+
* sync in the background, so a constraint conflict on those paths is a
|
|
12
|
+
* sync-time concern rather than a thrown error.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* try {
|
|
16
|
+
* await koolbase.db.upsert('users', { email }, { name });
|
|
17
|
+
* } catch (e) {
|
|
18
|
+
* if (e instanceof KoolbaseConflictError) {
|
|
19
|
+
* showError('That email is already registered.');
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
class KoolbaseConflictError extends Error {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super(message ?? 'Value violates a unique constraint');
|
|
26
|
+
this.code = 'unique_violation';
|
|
27
|
+
this.name = 'KoolbaseConflictError';
|
|
28
|
+
Object.setPrototypeOf(this, KoolbaseConflictError.prototype);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.KoolbaseConflictError = KoolbaseConflictError;
|
package/dist/database.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { KoolbaseConfig, KoolbaseRecord, QueryOptions, QueryResult } from './types';
|
|
1
|
+
import { KoolbaseConfig, KoolbaseRecord, QueryOptions, QueryResult, UpsertResult } from './types';
|
|
2
2
|
export declare class KoolbaseDatabase {
|
|
3
3
|
private config;
|
|
4
4
|
private getUserId;
|
|
@@ -9,6 +9,34 @@ export declare class KoolbaseDatabase {
|
|
|
9
9
|
private runQuery;
|
|
10
10
|
query(collection: string, options?: QueryOptions): Promise<QueryResult>;
|
|
11
11
|
insert(collection: string, data: Record<string, unknown>): Promise<KoolbaseRecord>;
|
|
12
|
+
/**
|
|
13
|
+
* Insert a record, or update the existing one matching `match`.
|
|
14
|
+
*
|
|
15
|
+
* The server decides: exactly one match updates it, no match inserts a new
|
|
16
|
+
* record (seeded with the `match` fields), more than one match is an error.
|
|
17
|
+
* Returns the resulting record and a `created` flag (true = inserted, false
|
|
18
|
+
* = updated).
|
|
19
|
+
*
|
|
20
|
+
* Online-only by design. Unlike `insert`, an upsert is NOT queued offline:
|
|
21
|
+
* the insert-vs-update decision needs the server's authoritative view of
|
|
22
|
+
* what already exists, so deferring it could create a duplicate or apply a
|
|
23
|
+
* wrong update on later sync. It throws on network failure instead. A raw
|
|
24
|
+
* fetch is used (not `request`) so the status code is readable: 201 =
|
|
25
|
+
* created, 200 = updated.
|
|
26
|
+
*/
|
|
27
|
+
upsert(collection: string, match: Record<string, unknown>, data: Record<string, unknown>): Promise<UpsertResult>;
|
|
28
|
+
/**
|
|
29
|
+
* Bulk-delete every record in `collection` matching `filters`.
|
|
30
|
+
*
|
|
31
|
+
* The server applies the collection's delete rule (scoping to the caller for
|
|
32
|
+
* owner/scoped rules) and returns the number of records deleted.
|
|
33
|
+
*
|
|
34
|
+
* Online-only by design — like upsert, this is NOT queued offline: a bulk
|
|
35
|
+
* delete needs the server's authoritative view of what matches, so it throws
|
|
36
|
+
* on network failure rather than risk deleting the wrong set on later sync.
|
|
37
|
+
* The collection cache is invalidated on success.
|
|
38
|
+
*/
|
|
39
|
+
deleteWhere(collection: string, filters: Record<string, unknown>): Promise<number>;
|
|
12
40
|
get(recordId: string): Promise<KoolbaseRecord>;
|
|
13
41
|
update(recordId: string, data: Record<string, unknown>): Promise<KoolbaseRecord>;
|
|
14
42
|
delete(recordId: string): Promise<void>;
|
package/dist/database.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.KoolbaseDatabase = void 0;
|
|
|
4
4
|
const cache_store_1 = require("./cache-store");
|
|
5
5
|
const sync_engine_1 = require("./sync-engine");
|
|
6
6
|
const record_1 = require("./record");
|
|
7
|
+
const database_errors_1 = require("./database-errors");
|
|
7
8
|
function generateId() {
|
|
8
9
|
return 'local_' + Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
9
10
|
}
|
|
@@ -97,6 +98,68 @@ class KoolbaseDatabase {
|
|
|
97
98
|
});
|
|
98
99
|
return optimisticRecord;
|
|
99
100
|
}
|
|
101
|
+
// ─── Upsert (online-only) ─────────────────────────────────────────────────
|
|
102
|
+
/**
|
|
103
|
+
* Insert a record, or update the existing one matching `match`.
|
|
104
|
+
*
|
|
105
|
+
* The server decides: exactly one match updates it, no match inserts a new
|
|
106
|
+
* record (seeded with the `match` fields), more than one match is an error.
|
|
107
|
+
* Returns the resulting record and a `created` flag (true = inserted, false
|
|
108
|
+
* = updated).
|
|
109
|
+
*
|
|
110
|
+
* Online-only by design. Unlike `insert`, an upsert is NOT queued offline:
|
|
111
|
+
* the insert-vs-update decision needs the server's authoritative view of
|
|
112
|
+
* what already exists, so deferring it could create a duplicate or apply a
|
|
113
|
+
* wrong update on later sync. It throws on network failure instead. A raw
|
|
114
|
+
* fetch is used (not `request`) so the status code is readable: 201 =
|
|
115
|
+
* created, 200 = updated.
|
|
116
|
+
*/
|
|
117
|
+
async upsert(collection, match, data) {
|
|
118
|
+
const res = await fetch(`${this.config.baseUrl}/v1/sdk/db/upsert`, {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
headers: this.headers,
|
|
121
|
+
body: JSON.stringify({ collection, match, data }),
|
|
122
|
+
});
|
|
123
|
+
const body = await res.json();
|
|
124
|
+
if (!res.ok) {
|
|
125
|
+
if (res.status === 409) {
|
|
126
|
+
throw new database_errors_1.KoolbaseConflictError(body.error);
|
|
127
|
+
}
|
|
128
|
+
throw new Error(body.error ?? `Upsert failed: ${res.status}`);
|
|
129
|
+
}
|
|
130
|
+
const created = res.status === 201;
|
|
131
|
+
const record = (0, record_1.recordFromWire)(body);
|
|
132
|
+
// Keep the cache fresh, same intent as insert's post-success invalidate.
|
|
133
|
+
const userId = this.getUserId() ?? 'anonymous';
|
|
134
|
+
await (0, cache_store_1.invalidateCache)(userId, collection);
|
|
135
|
+
return { record, created };
|
|
136
|
+
}
|
|
137
|
+
// ─── Delete where (online-only) ─────────────────────────────────────────────
|
|
138
|
+
/**
|
|
139
|
+
* Bulk-delete every record in `collection` matching `filters`.
|
|
140
|
+
*
|
|
141
|
+
* The server applies the collection's delete rule (scoping to the caller for
|
|
142
|
+
* owner/scoped rules) and returns the number of records deleted.
|
|
143
|
+
*
|
|
144
|
+
* Online-only by design — like upsert, this is NOT queued offline: a bulk
|
|
145
|
+
* delete needs the server's authoritative view of what matches, so it throws
|
|
146
|
+
* on network failure rather than risk deleting the wrong set on later sync.
|
|
147
|
+
* The collection cache is invalidated on success.
|
|
148
|
+
*/
|
|
149
|
+
async deleteWhere(collection, filters) {
|
|
150
|
+
const res = await fetch(`${this.config.baseUrl}/v1/sdk/db/delete-where`, {
|
|
151
|
+
method: 'POST',
|
|
152
|
+
headers: this.headers,
|
|
153
|
+
body: JSON.stringify({ collection, filters }),
|
|
154
|
+
});
|
|
155
|
+
const body = await res.json();
|
|
156
|
+
if (!res.ok) {
|
|
157
|
+
throw new Error(body.error ?? `Delete failed: ${res.status}`);
|
|
158
|
+
}
|
|
159
|
+
const userId = this.getUserId() ?? 'anonymous';
|
|
160
|
+
await (0, cache_store_1.invalidateCache)(userId, collection);
|
|
161
|
+
return body.deleted ?? 0;
|
|
162
|
+
}
|
|
100
163
|
// ─── Get single record ──────────────────────────────────────────────────────
|
|
101
164
|
async get(recordId) {
|
|
102
165
|
const raw = await this.request('GET', `/v1/sdk/db/records/${recordId}`);
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { KoolbaseStorage } from './storage';
|
|
|
18
18
|
import { KoolbaseConfig, VersionCheckResult } from './types';
|
|
19
19
|
export * from './types';
|
|
20
20
|
export * from './auth-errors';
|
|
21
|
+
export * from './database-errors';
|
|
21
22
|
export { KoolbaseAuth, KoolbaseDatabase, KoolbaseFlags, KoolbaseFunctions, KoolbaseRealtime, KoolbaseStorage };
|
|
22
23
|
export declare const Koolbase: {
|
|
23
24
|
initialize(config: KoolbaseConfig): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -45,6 +45,7 @@ const storage_1 = require("./storage");
|
|
|
45
45
|
Object.defineProperty(exports, "KoolbaseStorage", { enumerable: true, get: function () { return storage_1.KoolbaseStorage; } });
|
|
46
46
|
__exportStar(require("./types"), exports);
|
|
47
47
|
__exportStar(require("./auth-errors"), exports);
|
|
48
|
+
__exportStar(require("./database-errors"), exports);
|
|
48
49
|
let _auth = null;
|
|
49
50
|
let _db = null;
|
|
50
51
|
let _storage = null;
|
package/dist/types.d.ts
CHANGED
|
@@ -130,6 +130,10 @@ export interface QueryResult {
|
|
|
130
130
|
total: number;
|
|
131
131
|
isFromCache?: boolean;
|
|
132
132
|
}
|
|
133
|
+
export interface UpsertResult {
|
|
134
|
+
record: KoolbaseRecord;
|
|
135
|
+
created: boolean;
|
|
136
|
+
}
|
|
133
137
|
export interface PendingWrite {
|
|
134
138
|
id: string;
|
|
135
139
|
type: 'insert' | 'update' | 'delete';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@techfinityedge/koolbase-react-native",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.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",
|