@techfinityedge/koolbase-react-native 1.11.0 → 2.1.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 +39 -2
- package/dist/database.d.ts +30 -1
- package/dist/database.js +77 -32
- package/dist/realtime.js +9 -1
- package/dist/record.d.ts +2 -0
- package/dist/record.js +20 -0
- package/dist/types.d.ts +5 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,9 +16,9 @@ Auth, database, storage, realtime, functions, feature flags, remote config, vers
|
|
|
16
16
|
3. Add the SDK:
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
npm install @techfinityedge/koolbase-react-native@^
|
|
19
|
+
npm install @techfinityedge/koolbase-react-native@^2.0.0
|
|
20
20
|
# or
|
|
21
|
-
yarn add @techfinityedge/koolbase-react-native@^
|
|
21
|
+
yarn add @techfinityedge/koolbase-react-native@^2.0.0
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
**4. Initialize at app startup:**
|
|
@@ -147,6 +147,11 @@ const { records } = await Koolbase.db.query('posts', {
|
|
|
147
147
|
orderDesc: true,
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
+
// Read fields off a record
|
|
151
|
+
const post = records[0];
|
|
152
|
+
console.log(post.data.title); // your fields live under .data
|
|
153
|
+
console.log(post.id, post.collection); // metadata
|
|
154
|
+
|
|
150
155
|
// Populate related records
|
|
151
156
|
const { records: postsWithAuthor } = await Koolbase.db.query('posts', {
|
|
152
157
|
populate: ['author_id:users'],
|
|
@@ -157,6 +162,38 @@ await Koolbase.db.update('record-id', { title: 'Updated' });
|
|
|
157
162
|
await Koolbase.db.delete('record-id');
|
|
158
163
|
```
|
|
159
164
|
|
|
165
|
+
### Upsert
|
|
166
|
+
|
|
167
|
+
Insert a record, or update the existing one matching a filter.
|
|
168
|
+
|
|
169
|
+
\`\`\`ts
|
|
170
|
+
const result = await Koolbase.db.upsert(
|
|
171
|
+
'profiles',
|
|
172
|
+
{ user_id: userId },
|
|
173
|
+
{ weightKg: 70 }
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
console.log(result.created); // true if inserted, false if updated
|
|
177
|
+
console.log(result.record.id);
|
|
178
|
+
\`\`\`
|
|
179
|
+
|
|
180
|
+
> Online-only: needs the server's view to decide insert vs update, so unlike
|
|
181
|
+
> `insert` it isn't queued offline and throws on network failure.
|
|
182
|
+
|
|
183
|
+
### Delete where
|
|
184
|
+
|
|
185
|
+
Bulk-delete every record matching a filter. Returns the number deleted.
|
|
186
|
+
|
|
187
|
+
\`\`\`ts
|
|
188
|
+
const deleted = await Koolbase.db.deleteWhere('sessions', {
|
|
189
|
+
user_id: userId,
|
|
190
|
+
status: 'expired',
|
|
191
|
+
});
|
|
192
|
+
\`\`\`
|
|
193
|
+
|
|
194
|
+
> A non-empty filter is required. The collection's delete rule applies; for
|
|
195
|
+
> `owner`/`scoped` rules the delete is scoped to your own records. Online-only.
|
|
196
|
+
|
|
160
197
|
### Offline-first
|
|
161
198
|
|
|
162
199
|
```typescript
|
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;
|
|
@@ -6,8 +6,37 @@ export declare class KoolbaseDatabase {
|
|
|
6
6
|
constructor(config: KoolbaseConfig, getUserId: () => string | null);
|
|
7
7
|
private get headers();
|
|
8
8
|
private request;
|
|
9
|
+
private runQuery;
|
|
9
10
|
query(collection: string, options?: QueryOptions): Promise<QueryResult>;
|
|
10
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>;
|
|
11
40
|
get(recordId: string): Promise<KoolbaseRecord>;
|
|
12
41
|
update(recordId: string, data: Record<string, unknown>): Promise<KoolbaseRecord>;
|
|
13
42
|
delete(recordId: string): Promise<void>;
|
package/dist/database.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.KoolbaseDatabase = void 0;
|
|
4
4
|
const cache_store_1 = require("./cache-store");
|
|
5
5
|
const sync_engine_1 = require("./sync-engine");
|
|
6
|
+
const record_1 = require("./record");
|
|
6
7
|
function generateId() {
|
|
7
8
|
return 'local_' + Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
8
9
|
}
|
|
@@ -34,13 +35,8 @@ class KoolbaseDatabase {
|
|
|
34
35
|
return data;
|
|
35
36
|
}
|
|
36
37
|
// ─── Query (cache-first) ───────────────────────────────────────────────────
|
|
37
|
-
async
|
|
38
|
-
const
|
|
39
|
-
const queryHash = (0, cache_store_1.hashQuery)(collection, options);
|
|
40
|
-
// 1. Return cached data immediately if available
|
|
41
|
-
const cached = await (0, cache_store_1.getCached)(userId, collection, queryHash);
|
|
42
|
-
// 2. Fetch from network in background
|
|
43
|
-
this.request('POST', '/v1/sdk/db/query', {
|
|
38
|
+
async runQuery(collection, options) {
|
|
39
|
+
const raw = await this.request('POST', '/v1/sdk/db/query', {
|
|
44
40
|
collection,
|
|
45
41
|
filters: options.filters ?? {},
|
|
46
42
|
limit: options.limit ?? 20,
|
|
@@ -48,26 +44,22 @@ class KoolbaseDatabase {
|
|
|
48
44
|
order_by: options.orderBy,
|
|
49
45
|
order_desc: options.orderDesc ?? false,
|
|
50
46
|
populate: options.populate ?? [],
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
});
|
|
48
|
+
return { records: raw.records.map(record_1.recordFromWire), total: raw.total };
|
|
49
|
+
}
|
|
50
|
+
async query(collection, options = {}) {
|
|
51
|
+
const userId = this.getUserId() ?? 'anonymous';
|
|
52
|
+
const queryHash = (0, cache_store_1.hashQuery)(collection, options);
|
|
53
|
+
const cached = await (0, cache_store_1.getCached)(userId, collection, queryHash);
|
|
54
|
+
this.runQuery(collection, options)
|
|
55
|
+
.then(result => (0, cache_store_1.setCached)(userId, collection, queryHash, result))
|
|
55
56
|
.catch(() => {
|
|
56
|
-
// Network unavailable — cached data
|
|
57
|
+
// Network unavailable — cached data already returned
|
|
57
58
|
});
|
|
58
59
|
if (cached) {
|
|
59
60
|
return { ...cached, isFromCache: true };
|
|
60
61
|
}
|
|
61
|
-
|
|
62
|
-
const result = await this.request('POST', '/v1/sdk/db/query', {
|
|
63
|
-
collection,
|
|
64
|
-
filters: options.filters ?? {},
|
|
65
|
-
limit: options.limit ?? 20,
|
|
66
|
-
offset: options.offset ?? 0,
|
|
67
|
-
order_by: options.orderBy,
|
|
68
|
-
order_desc: options.orderDesc ?? false,
|
|
69
|
-
populate: options.populate ?? [],
|
|
70
|
-
});
|
|
62
|
+
const result = await this.runQuery(collection, options);
|
|
71
63
|
await (0, cache_store_1.setCached)(userId, collection, queryHash, result);
|
|
72
64
|
return { ...result, isFromCache: false };
|
|
73
65
|
}
|
|
@@ -77,8 +69,6 @@ class KoolbaseDatabase {
|
|
|
77
69
|
// Build optimistic record
|
|
78
70
|
const optimisticRecord = {
|
|
79
71
|
id: generateId(),
|
|
80
|
-
projectId: '',
|
|
81
|
-
collectionId: '',
|
|
82
72
|
createdBy: userId,
|
|
83
73
|
data,
|
|
84
74
|
createdAt: new Date().toISOString(),
|
|
@@ -107,31 +97,86 @@ class KoolbaseDatabase {
|
|
|
107
97
|
});
|
|
108
98
|
return optimisticRecord;
|
|
109
99
|
}
|
|
100
|
+
// ─── Upsert (online-only) ─────────────────────────────────────────────────
|
|
101
|
+
/**
|
|
102
|
+
* Insert a record, or update the existing one matching `match`.
|
|
103
|
+
*
|
|
104
|
+
* The server decides: exactly one match updates it, no match inserts a new
|
|
105
|
+
* record (seeded with the `match` fields), more than one match is an error.
|
|
106
|
+
* Returns the resulting record and a `created` flag (true = inserted, false
|
|
107
|
+
* = updated).
|
|
108
|
+
*
|
|
109
|
+
* Online-only by design. Unlike `insert`, an upsert is NOT queued offline:
|
|
110
|
+
* the insert-vs-update decision needs the server's authoritative view of
|
|
111
|
+
* what already exists, so deferring it could create a duplicate or apply a
|
|
112
|
+
* wrong update on later sync. It throws on network failure instead. A raw
|
|
113
|
+
* fetch is used (not `request`) so the status code is readable: 201 =
|
|
114
|
+
* created, 200 = updated.
|
|
115
|
+
*/
|
|
116
|
+
async upsert(collection, match, data) {
|
|
117
|
+
const res = await fetch(`${this.config.baseUrl}/v1/sdk/db/upsert`, {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers: this.headers,
|
|
120
|
+
body: JSON.stringify({ collection, match, data }),
|
|
121
|
+
});
|
|
122
|
+
const body = await res.json();
|
|
123
|
+
if (!res.ok) {
|
|
124
|
+
throw new Error(body.error ?? `Upsert failed: ${res.status}`);
|
|
125
|
+
}
|
|
126
|
+
const created = res.status === 201;
|
|
127
|
+
const record = (0, record_1.recordFromWire)(body);
|
|
128
|
+
// Keep the cache fresh, same intent as insert's post-success invalidate.
|
|
129
|
+
const userId = this.getUserId() ?? 'anonymous';
|
|
130
|
+
await (0, cache_store_1.invalidateCache)(userId, collection);
|
|
131
|
+
return { record, created };
|
|
132
|
+
}
|
|
133
|
+
// ─── Delete where (online-only) ─────────────────────────────────────────────
|
|
134
|
+
/**
|
|
135
|
+
* Bulk-delete every record in `collection` matching `filters`.
|
|
136
|
+
*
|
|
137
|
+
* The server applies the collection's delete rule (scoping to the caller for
|
|
138
|
+
* owner/scoped rules) and returns the number of records deleted.
|
|
139
|
+
*
|
|
140
|
+
* Online-only by design — like upsert, this is NOT queued offline: a bulk
|
|
141
|
+
* delete needs the server's authoritative view of what matches, so it throws
|
|
142
|
+
* on network failure rather than risk deleting the wrong set on later sync.
|
|
143
|
+
* The collection cache is invalidated on success.
|
|
144
|
+
*/
|
|
145
|
+
async deleteWhere(collection, filters) {
|
|
146
|
+
const res = await fetch(`${this.config.baseUrl}/v1/sdk/db/delete-where`, {
|
|
147
|
+
method: 'POST',
|
|
148
|
+
headers: this.headers,
|
|
149
|
+
body: JSON.stringify({ collection, filters }),
|
|
150
|
+
});
|
|
151
|
+
const body = await res.json();
|
|
152
|
+
if (!res.ok) {
|
|
153
|
+
throw new Error(body.error ?? `Delete failed: ${res.status}`);
|
|
154
|
+
}
|
|
155
|
+
const userId = this.getUserId() ?? 'anonymous';
|
|
156
|
+
await (0, cache_store_1.invalidateCache)(userId, collection);
|
|
157
|
+
return body.deleted ?? 0;
|
|
158
|
+
}
|
|
110
159
|
// ─── Get single record ──────────────────────────────────────────────────────
|
|
111
160
|
async get(recordId) {
|
|
112
|
-
|
|
161
|
+
const raw = await this.request('GET', `/v1/sdk/db/records/${recordId}`);
|
|
162
|
+
return (0, record_1.recordFromWire)(raw);
|
|
113
163
|
}
|
|
114
164
|
// ─── Update ─────────────────────────────────────────────────────────────────
|
|
115
165
|
async update(recordId, data) {
|
|
116
166
|
const userId = this.getUserId() ?? 'anonymous';
|
|
117
|
-
// Add to write queue
|
|
118
167
|
await (0, cache_store_1.addToWriteQueue)(userId, {
|
|
119
168
|
id: generateId(),
|
|
120
169
|
type: 'update',
|
|
121
170
|
recordId,
|
|
122
171
|
data,
|
|
123
172
|
});
|
|
124
|
-
// Try network
|
|
125
173
|
try {
|
|
126
|
-
const
|
|
127
|
-
return
|
|
174
|
+
const raw = await this.request('PATCH', `/v1/sdk/db/records/${recordId}`, { data });
|
|
175
|
+
return (0, record_1.recordFromWire)(raw);
|
|
128
176
|
}
|
|
129
177
|
catch {
|
|
130
|
-
// Return optimistic version
|
|
131
178
|
return {
|
|
132
179
|
id: recordId,
|
|
133
|
-
projectId: '',
|
|
134
|
-
collectionId: '',
|
|
135
180
|
data,
|
|
136
181
|
createdAt: '',
|
|
137
182
|
updatedAt: new Date().toISOString(),
|
package/dist/realtime.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.KoolbaseRealtime = void 0;
|
|
4
|
+
const record_1 = require("./record");
|
|
4
5
|
class KoolbaseRealtime {
|
|
5
6
|
constructor(config) {
|
|
6
7
|
this.ws = null;
|
|
@@ -31,7 +32,14 @@ class KoolbaseRealtime {
|
|
|
31
32
|
this.ws = new WebSocket(`${wsUrl}/v1/sdk/realtime?key=${this.config.publicKey}`);
|
|
32
33
|
this.ws.onmessage = (event) => {
|
|
33
34
|
try {
|
|
34
|
-
const
|
|
35
|
+
const raw = JSON.parse(event.data);
|
|
36
|
+
if (!raw || !raw.record)
|
|
37
|
+
return;
|
|
38
|
+
const msg = {
|
|
39
|
+
type: raw.type,
|
|
40
|
+
collection: raw.collection,
|
|
41
|
+
record: (0, record_1.recordFromWire)(raw.record),
|
|
42
|
+
};
|
|
35
43
|
const callbacks = this.listeners.get(msg.collection) ?? [];
|
|
36
44
|
callbacks.forEach((cb) => cb(msg));
|
|
37
45
|
}
|
package/dist/record.d.ts
ADDED
package/dist/record.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.recordFromWire = recordFromWire;
|
|
4
|
+
// Converts the flat public wire shape into a KoolbaseRecord.
|
|
5
|
+
// Server sends: { $id, $createdAt, $updatedAt, $collection, $createdBy?, ...fields }
|
|
6
|
+
function recordFromWire(raw) {
|
|
7
|
+
const data = {};
|
|
8
|
+
for (const key of Object.keys(raw)) {
|
|
9
|
+
if (!key.startsWith('$'))
|
|
10
|
+
data[key] = raw[key];
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
id: raw['$id'],
|
|
14
|
+
collection: raw['$collection'],
|
|
15
|
+
createdBy: raw['$createdBy'],
|
|
16
|
+
data,
|
|
17
|
+
createdAt: raw['$createdAt'],
|
|
18
|
+
updatedAt: raw['$updatedAt'],
|
|
19
|
+
};
|
|
20
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -111,8 +111,7 @@ export interface PhoneVerifyResult {
|
|
|
111
111
|
}
|
|
112
112
|
export interface KoolbaseRecord {
|
|
113
113
|
id: string;
|
|
114
|
-
|
|
115
|
-
collectionId: string;
|
|
114
|
+
collection?: string;
|
|
116
115
|
createdBy?: string;
|
|
117
116
|
data: Record<string, unknown>;
|
|
118
117
|
createdAt: string;
|
|
@@ -131,6 +130,10 @@ export interface QueryResult {
|
|
|
131
130
|
total: number;
|
|
132
131
|
isFromCache?: boolean;
|
|
133
132
|
}
|
|
133
|
+
export interface UpsertResult {
|
|
134
|
+
record: KoolbaseRecord;
|
|
135
|
+
created: boolean;
|
|
136
|
+
}
|
|
134
137
|
export interface PendingWrite {
|
|
135
138
|
id: string;
|
|
136
139
|
type: 'insert' | 'update' | 'delete';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@techfinityedge/koolbase-react-native",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.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",
|