@koolbase/react-native 9.2.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 +403 -568
- 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 -30
- 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 -213
- package/dist/auth.js +0 -810
- package/dist/cache-store.d.ts +0 -50
- package/dist/cache-store.js +0 -197
- package/dist/code-push.d.ts +0 -59
- package/dist/code-push.js +0 -255
- package/dist/conflict.d.ts +0 -80
- package/dist/conflict.js +0 -84
- package/dist/database-errors.d.ts +0 -101
- package/dist/database-errors.js +0 -200
- package/dist/database.d.ts +0 -298
- package/dist/database.js +0 -852
- 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/errors.d.ts +0 -64
- package/dist/errors.js +0 -85
- package/dist/flags.d.ts +0 -15
- package/dist/flags.js +0 -76
- package/dist/function-errors.d.ts +0 -51
- package/dist/function-errors.js +0 -103
- package/dist/functions.d.ts +0 -15
- package/dist/functions.js +0 -83
- package/dist/index.d.ts +0 -49
- package/dist/index.js +0 -204
- 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/offline-state.d.ts +0 -97
- package/dist/offline-state.js +0 -200
- package/dist/pending-write.d.ts +0 -47
- package/dist/pending-write.js +0 -22
- package/dist/realtime.d.ts +0 -44
- package/dist/realtime.js +0 -195
- package/dist/record.d.ts +0 -2
- package/dist/record.js +0 -23
- package/dist/storage-errors.d.ts +0 -163
- package/dist/storage-errors.js +0 -253
- package/dist/storage.d.ts +0 -198
- package/dist/storage.js +0 -451
- package/dist/sync-engine.d.ts +0 -30
- package/dist/sync-engine.js +0 -290
- package/dist/types.d.ts +0 -487
- package/dist/types.js +0 -40
- /package/dist/{auth-storage.js → cjs/auth-storage.js} +0 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,1342 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@koolbase/react-native` are documented
|
|
4
|
+
in this file. The format is based on [Keep a Changelog][kac], and this project
|
|
5
|
+
adheres to [Semantic Versioning][semver].
|
|
6
|
+
|
|
7
|
+
[kac]: https://keepachangelog.com/en/1.1.0/
|
|
8
|
+
[semver]: https://semver.org/
|
|
9
|
+
|
|
10
|
+
## 10.0.0
|
|
11
|
+
|
|
12
|
+
### Read before upgrading
|
|
13
|
+
|
|
14
|
+
**The package is now a thin layer over `@koolbase/core`.** The SDK's behaviour
|
|
15
|
+
— auth, database, offline queue, conflicts, realtime, storage, functions —
|
|
16
|
+
moved into a shared core that `@koolbase/react-native` and the new
|
|
17
|
+
`@koolbase/js` both compose. Your imports do not change: everything the core
|
|
18
|
+
exports is re-exported here. What changes is that the same sixty tests that
|
|
19
|
+
proved the offline path on a device now run against both hosts, so a fix in
|
|
20
|
+
one is a fix in the other.
|
|
21
|
+
|
|
22
|
+
**Code push is gone.** `Koolbase.codePush`, `codePushChannel`,
|
|
23
|
+
`onMandatoryUpdate`, `KoolbaseCodePush`, `BundleManifest` and `BundlePayload`
|
|
24
|
+
are removed. Code push is a Flutter feature, where it patches the Dart VM; the
|
|
25
|
+
React Native version pushed config and flag overrides, which Remote Config and
|
|
26
|
+
Feature Flags already do without a bundle. If you set `codePushChannel`, delete
|
|
27
|
+
the line; the flag and config reads keep working from the remote values.
|
|
28
|
+
|
|
29
|
+
**`Koolbase.executeFlow` and the logic engine are gone.** Flows only ever
|
|
30
|
+
arrived through code-push bundles, so with no bundle the method could only
|
|
31
|
+
return an empty result. Removed rather than left as a call that does nothing.
|
|
32
|
+
|
|
33
|
+
**`KoolbaseAppleAuth` is gone.** Deprecated since 1.9.0, throwing
|
|
34
|
+
`not_implemented` since then. `Koolbase.auth.signInWithApple(...)` is the
|
|
35
|
+
Apple flow and is unchanged.
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- **Host access goes through a platform adapter.** AsyncStorage, NetInfo,
|
|
40
|
+
AppState and Platform are consumed by one file, `platform.ts`, and nothing
|
|
41
|
+
else in the SDK imports a native module. `KoolbaseConfig.platform` accepts
|
|
42
|
+
a custom adapter — for tests, or for a host this package does not cover.
|
|
43
|
+
|
|
44
|
+
- **The default session storage is decided by the platform.** Keychain when
|
|
45
|
+
`react-native-keychain` is installed, as before; the choice is now made by
|
|
46
|
+
the adapter rather than hard-coded in auth.
|
|
47
|
+
|
|
48
|
+
- **The device label persists through the platform's storage** rather than a
|
|
49
|
+
direct keychain call. Behaviour is the same on a device with keychain; on
|
|
50
|
+
one without, the label now survives restarts where it used to be regenerated
|
|
51
|
+
each session.
|
|
52
|
+
|
|
53
|
+
### Removed
|
|
54
|
+
|
|
55
|
+
- `jszip` — code push was its only user. This package's core now has no
|
|
56
|
+
runtime dependencies.
|
|
57
|
+
|
|
58
|
+
### Migration
|
|
59
|
+
|
|
60
|
+
Delete any `codePushChannel` or `onMandatoryUpdate` from your `initialize()`
|
|
61
|
+
config, and any `Koolbase.codePush` or `Koolbase.executeFlow` calls. That is
|
|
62
|
+
the whole migration; everything else is source-compatible.
|
|
63
|
+
|
|
64
|
+
## 9.2.0
|
|
65
|
+
|
|
66
|
+
### Read before upgrading
|
|
67
|
+
|
|
68
|
+
**`delete()` can now fail.** It previously returned `Promise<void>` with no throw
|
|
69
|
+
path: a delete the server refused — no permission, wrong project, record already
|
|
70
|
+
gone — reported success to the caller, and nothing anywhere reported otherwise.
|
|
71
|
+
It now throws. Code that called it without a `catch` will surface an error where
|
|
72
|
+
it never did before, which is the point, and still a change.
|
|
73
|
+
|
|
74
|
+
**A rejected credential is no longer a `KoolbaseDataError`.** It is
|
|
75
|
+
`KoolbaseUnauthenticatedError`, a sibling under the new shared root. Code
|
|
76
|
+
catching `KoolbaseDataError` to handle a dead session will stop matching.
|
|
77
|
+
|
|
78
|
+
### Fixed
|
|
79
|
+
|
|
80
|
+
- **`delete()` queued the write before attempting it, and never removed it.** A
|
|
81
|
+
delete that succeeded stayed in the queue and replayed later — against a
|
|
82
|
+
record that may since have been recreated under the same id.
|
|
83
|
+
|
|
84
|
+
- **Five data methods could not clear a rejected session.** `upsert`,
|
|
85
|
+
`deleteWhere`, `batch`, `setVector`, and `deleteVector` each built their own
|
|
86
|
+
request, so whether a 401 signed you out depended on which method you called.
|
|
87
|
+
All now go through one path.
|
|
88
|
+
|
|
89
|
+
- **A 401 was treated as an unreachable network** by `update`, `delete`, and
|
|
90
|
+
`upsert`, so the write was queued and an optimistic record returned — telling
|
|
91
|
+
the app the change had succeeded. Anything the server answered with is now
|
|
92
|
+
surfaced, because it will be refused again on every retry.
|
|
93
|
+
|
|
94
|
+
- **A 401 carrying a body `code` did not clear the session.** Responses were
|
|
95
|
+
mapped by their body's `code` field before their status, so a server answering
|
|
96
|
+
a rejected credential with `code: 'validation_error'` produced a data error and
|
|
97
|
+
left the dead session in place — the app kept making calls that could only
|
|
98
|
+
fail. Status is now authoritative: a 401 is an authentication failure whatever
|
|
99
|
+
the body says.
|
|
100
|
+
|
|
101
|
+
- **Offline insert chains broke at the server boundary.** A record created
|
|
102
|
+
offline got a `local_` id, and any queued update or delete addressed that id —
|
|
103
|
+
which ceased to exist the moment the insert replayed and the server assigned a
|
|
104
|
+
real one. The follow-up writes failed against a record that was sitting right
|
|
105
|
+
there. Ids are now UUID v4 from the moment of creation, client-side, and the
|
|
106
|
+
id travels with the queued insert, so a chain of edits made offline replays
|
|
107
|
+
exactly as it was made.
|
|
108
|
+
|
|
109
|
+
- **Every cold `query()` hit the network twice.** The stale-while-revalidate
|
|
110
|
+
refresh was fired before the cache was consulted, so a query with nothing
|
|
111
|
+
cached issued the background request and the real one. Doubled reads on every
|
|
112
|
+
first load, against your quota.
|
|
113
|
+
|
|
114
|
+
- **Signed-out state read as empty state.** Per-user caches and queues fell back
|
|
115
|
+
to a shared anonymous bucket when no user was present, so a signed-out call
|
|
116
|
+
reported zero pending writes rather than refusing — a sync indicator could show
|
|
117
|
+
"all synced" over a queue that was merely out of reach. Per-user surfaces now
|
|
118
|
+
refuse without a user.
|
|
119
|
+
|
|
120
|
+
- **A replayed write left the cache it invalidated behind.** Replay updated the
|
|
121
|
+
server and stopped there: a record deleted offline stayed in every cached query
|
|
122
|
+
result after its delete succeeded, and an updated one kept its pre-edit values
|
|
123
|
+
until something else evicted it. Replay now maintains the cache at the point of
|
|
124
|
+
success.
|
|
125
|
+
|
|
126
|
+
- **A terminally rejected insert left its optimistic record standing.** When the
|
|
127
|
+
server refused a queued insert for good — no permission, validation — the write
|
|
128
|
+
left the queue but the optimistic record stayed in every cached result it had
|
|
129
|
+
been written into. The app displayed a record that does not exist and never
|
|
130
|
+
will. Terminal rejection now evicts it.
|
|
131
|
+
|
|
132
|
+
- **Realtime reconnected every three seconds forever**, with no backoff and no
|
|
133
|
+
ceiling. A device with no network, a wrong URL, or a dead session drained
|
|
134
|
+
battery and data invisibly. It now doubles to a minute and resets when a
|
|
135
|
+
connection opens.
|
|
136
|
+
|
|
137
|
+
- **`clearUserCache` deleted the write queue along with the cache.** Nothing
|
|
138
|
+
called it, which is the only reason it had not lost anyone's work.
|
|
139
|
+
|
|
140
|
+
### Added
|
|
141
|
+
|
|
142
|
+
- **One exception hierarchy.** `KoolbaseError` is the root; the data, storage,
|
|
143
|
+
auth, and Function families sit beneath it, so `catch (e) { if (e instanceof
|
|
144
|
+
KoolbaseError) }` covers any SDK failure.
|
|
145
|
+
|
|
146
|
+
- **`KoolbaseUnauthenticatedError`** — raised by any surface. A session stops
|
|
147
|
+
working for the whole SDK at once, not one subsystem at a time. Named for what
|
|
148
|
+
the server reports: a 401 covers an expired session, a revoked key, and
|
|
149
|
+
missing credentials, and it does not distinguish them.
|
|
150
|
+
|
|
151
|
+
- **`auth.clearStoredSession()`** — discards a session already known to be
|
|
152
|
+
unusable, without a server call.
|
|
153
|
+
|
|
154
|
+
- **Typed Function failures.** `FunctionNotFoundError`,
|
|
155
|
+
`FunctionPermissionError`, `FunctionValidationError`,
|
|
156
|
+
`FunctionQuotaExceededError`, `FunctionExecutionError`. Every failed
|
|
157
|
+
invocation used to be a bare `Error`, matchable only on message text.
|
|
158
|
+
|
|
159
|
+
- The package's first tests: 60, covering the paths above.
|
|
160
|
+
|
|
161
|
+
### Added — the queue is observable
|
|
162
|
+
|
|
163
|
+
- **`db.pendingWrites()`** — changes made offline, waiting to be sent, oldest
|
|
164
|
+
first. For sync indicators and for warning a user about to log out with
|
|
165
|
+
unsynced edits: queues are per-user and survive logout by design, so those
|
|
166
|
+
edits sync whenever that user next signs in on this device — possibly never.
|
|
167
|
+
`conflicts()` got this treatment; the queue, the same durable state one step
|
|
168
|
+
earlier, now has it too.
|
|
169
|
+
- The returned shape deliberately excludes replay internals (baselines,
|
|
170
|
+
revisions). What is public is what an app needs to display.
|
|
171
|
+
- **Renamed:** the old exported `PendingWrite` interface — the 9.1.x queue-entry
|
|
172
|
+
shape, which no API ever returned — is no longer public. The name now refers
|
|
173
|
+
to the observable queue entry above. The old shape survives internally only
|
|
174
|
+
for the legacy-queue migration.
|
|
175
|
+
|
|
176
|
+
### Added — offline editing that cannot overwrite silently
|
|
177
|
+
|
|
178
|
+
Offline `update` and `delete` used to be queued without recording what the change
|
|
179
|
+
was based on, so replay applied them blindly and overwrote anything that had
|
|
180
|
+
changed meanwhile. Now:
|
|
181
|
+
|
|
182
|
+
- A write is queued only when the SDK knows what the record looked like at the
|
|
183
|
+
time — from a query, a read, a realtime event, or a still-queued insert.
|
|
184
|
+
Otherwise it throws `KoolbaseOfflineBaselineUnavailableError` rather than
|
|
185
|
+
queueing something that cannot be replayed safely.
|
|
186
|
+
|
|
187
|
+
- Replay sends the revision the change was based on, so the server applies it
|
|
188
|
+
only if the record still carries that revision. Nothing can land between the
|
|
189
|
+
client deciding a write is safe and the server applying it.
|
|
190
|
+
|
|
191
|
+
- A refused write becomes a durable conflict rather than a retry, readable from
|
|
192
|
+
`Koolbase.db.conflicts()` and resolvable four ways. Conflicts survive
|
|
193
|
+
restarts, and do not expire: an app that never reads them accumulates them
|
|
194
|
+
invisibly, so surface them if you support offline editing.
|
|
195
|
+
|
|
196
|
+
- A refused insert is now a conflict you can resolve. It was recorded as an
|
|
197
|
+
`update`, and resolution had no branch for an insert at all, so the one class
|
|
198
|
+
of conflict that loses a record outright was the one class you could not act
|
|
199
|
+
on. A rejected insert now holds its operation, and resolving it retries the
|
|
200
|
+
insert, carrying the conflict id as the idempotency key so a retry cannot
|
|
201
|
+
double-write.
|
|
202
|
+
|
|
203
|
+
- A resolution the server refuses no longer disappears. The conflict is updated
|
|
204
|
+
with what the server returned and stands, rather than being cleared on the
|
|
205
|
+
assumption the resolution landed.
|
|
206
|
+
|
|
207
|
+
- Writes queued by an earlier version are migrated on first sync. Inserts replay
|
|
208
|
+
normally; updates and deletes have no baseline, so they are preserved as
|
|
209
|
+
conflicts marked `baseline_unavailable` rather than replayed blindly or
|
|
210
|
+
dropped. The migration never touches the network, so its outcome does not
|
|
211
|
+
depend on whether the device happened to be online at startup.
|
|
212
|
+
|
|
213
|
+
- Records reach a per-record cache from every path that returns one — queries,
|
|
214
|
+
reads, writes, batch results, search hits, and realtime events — so anything
|
|
215
|
+
the SDK has fully seen can be edited offline.
|
|
216
|
+
|
|
217
|
+
## 9.1.0
|
|
218
|
+
|
|
219
|
+
### Fixed
|
|
220
|
+
|
|
221
|
+
- **Device identity is now a single shared, persisted value across the SDK.**
|
|
222
|
+
Feature flags, code push, and messaging were each initialized with a
|
|
223
|
+
hardcoded `'rn-device'` string instead of a real device id. This silently
|
|
224
|
+
broke three things: every device collided on one messaging registration row
|
|
225
|
+
(only the last-registered device per project received push); every device
|
|
226
|
+
produced the same feature-flag rollout bucket (a 10% rollout was on for
|
|
227
|
+
everyone or no one, never 10%); and code-push targeting collided the same
|
|
228
|
+
way. A single `getOrCreateDeviceId()` now generates a persisted UUID v4 once
|
|
229
|
+
(crypto-backed where the runtime provides it) and all subsystems share it.
|
|
230
|
+
|
|
231
|
+
### Removed
|
|
232
|
+
|
|
233
|
+
- **`Koolbase.messaging.send()` and `SendOptions`.** Sending push notifications
|
|
234
|
+
is server-initiated only — it requires a secret `kb_live_` key and must run
|
|
235
|
+
on your backend or in a Koolbase Function, never in the app. The publishable
|
|
236
|
+
key the SDK holds ships in your bundle; a client that could send would let
|
|
237
|
+
anyone extracting it push to your users. The API already rejected
|
|
238
|
+
publishable-key sends with 401, so this method never delivered. Move sends to
|
|
239
|
+
your backend. `registerToken` is unchanged.
|
|
240
|
+
|
|
241
|
+
### Note
|
|
242
|
+
|
|
243
|
+
- On upgrade, devices are assigned a proper unique id and will re-register
|
|
244
|
+
with messaging once. No action needed. Feature-flag rollout buckets will
|
|
245
|
+
change (correctly) — a device that happened to fall in or out of a rollout
|
|
246
|
+
under the old constant behaviour may now flip, matching its true bucket.
|
|
247
|
+
|
|
248
|
+
## 9.0.0
|
|
249
|
+
|
|
250
|
+
### Breaking changes
|
|
251
|
+
|
|
252
|
+
- **Package renamed** from `@techfinityedge/koolbase-react-native` to `@koolbase/react-native` for brand consistency with the rest of the Koolbase SDKs and tooling. This is the only change in this release — the API surface, behavior, and exports are identical to 8.0.0.
|
|
253
|
+
- **Migration:** replace the dependency in `package.json` (`@techfinityedge/koolbase-react-native` → `@koolbase/react-native`) and update every import path accordingly. No code changes beyond the import specifier are required. The old package is deprecated on npm and will receive no further updates.
|
|
254
|
+
|
|
255
|
+
## 8.0.0
|
|
256
|
+
|
|
257
|
+
### Breaking changes
|
|
258
|
+
|
|
259
|
+
- None. `mode` and `minSimilarity` are both optional; existing
|
|
260
|
+
`searchSemantic` callers continue to work unchanged. Major bump
|
|
261
|
+
reflects the conceptual expansion of the search contract (three
|
|
262
|
+
retrieval modes instead of one), not API-breaking removals.
|
|
263
|
+
|
|
264
|
+
### Added
|
|
265
|
+
|
|
266
|
+
- `KoolbaseDatabase.searchSemantic` accepts a new `mode` parameter of
|
|
267
|
+
type `SearchMode`. Three retrieval strategies are supported:
|
|
268
|
+
- `'semantic'` (default) — pure vector search via HNSW on cosine
|
|
269
|
+
distance. Best for fuzzy / conceptual queries.
|
|
270
|
+
- `'lexical'` — pure BM25 over the field's source text via Postgres
|
|
271
|
+
`ts_rank_cd`. Best for exact terms, codes, names, acronyms.
|
|
272
|
+
- `'hybrid'` — vector + lexical fused with reciprocal rank fusion
|
|
273
|
+
(k=60). Generally the strongest default for production search.
|
|
274
|
+
- `KoolbaseDatabase.searchSemantic` accepts a new `minSimilarity`
|
|
275
|
+
parameter (0..100, optional). Server-side filter that drops results
|
|
276
|
+
below the given similarity percentage before they cross the wire.
|
|
277
|
+
Saves bandwidth on weak matches. Only valid for semantic and hybrid
|
|
278
|
+
modes; the server rejects it on lexical mode (BM25 ranks aren't
|
|
279
|
+
comparable to cosine similarity).
|
|
280
|
+
- New `SearchMode` type exported from `@koolbase/react-native`.
|
|
281
|
+
|
|
282
|
+
### Server requirements
|
|
283
|
+
|
|
284
|
+
- Requires Koolbase API release with hybrid search shipped (June 8 2026
|
|
285
|
+
or later).
|
|
286
|
+
- Lexical and hybrid modes require the vector field to have a
|
|
287
|
+
`source_field` configured. The lexical sidecar table populates
|
|
288
|
+
automatically on record write via the same hook that drives auto-embed.
|
|
289
|
+
|
|
290
|
+
## 7.0.0
|
|
291
|
+
|
|
292
|
+
### Breaking changes
|
|
293
|
+
|
|
294
|
+
- `KoolbaseDatabase.searchSemantic`: the `queryVector` parameter is now optional.
|
|
295
|
+
Existing callers continue to work unchanged — the breaking aspect is that
|
|
296
|
+
the SDK now validates that exactly one of `queryVector` / `queryText` is
|
|
297
|
+
supplied, and throws `Error` otherwise.
|
|
298
|
+
|
|
299
|
+
### Added
|
|
300
|
+
|
|
301
|
+
- `KoolbaseDatabase.searchSemantic` accepts a new `queryText` parameter. When
|
|
302
|
+
supplied, the server embeds it inline using the vector field's configured
|
|
303
|
+
provider (Gemini or OpenAI) before running HNSW lookup. No client-side
|
|
304
|
+
embedding model required for typical search use cases.
|
|
305
|
+
- `KoolbaseDatabase.embedText` queues an embedding job for a specific record's
|
|
306
|
+
vector field. Used for backfilling vectors on records that pre-date the
|
|
307
|
+
auto-embed hook, or for embedding text other than the record's
|
|
308
|
+
configured source field.
|
|
309
|
+
|
|
310
|
+
### Server requirements
|
|
311
|
+
|
|
312
|
+
- Requires Koolbase API release `771728d` or later (Phase 2 Stage A3a).
|
|
313
|
+
- Auto-embed on record write is automatic once a vector field has its
|
|
314
|
+
`embedding_provider`, `embedding_model`, and `source_field` configured
|
|
315
|
+
(see [docs](https://docs.koolbase.com/database/vectors)).
|
|
316
|
+
|
|
317
|
+
## 6.0.0
|
|
318
|
+
|
|
319
|
+
### Added — database
|
|
320
|
+
|
|
321
|
+
- **Semantic search via vector similarity.** Query records by meaning,
|
|
322
|
+
not just by field equality. Companion to the server-side vector
|
|
323
|
+
primitive shipped in Koolbase Phase 1 AI on June 6 2026.
|
|
324
|
+
- `KoolbaseDatabase.setVector(recordId, field, vector)` writes (or
|
|
325
|
+
replaces) a vector for a record on the named field. The field must
|
|
326
|
+
already be declared on the collection via the dashboard or CLI;
|
|
327
|
+
the vector's length must match the field's declared dimension.
|
|
328
|
+
- `KoolbaseDatabase.getVector(recordId, field)` reads a stored vector
|
|
329
|
+
back as `KoolbaseVector` — `{ recordId, fieldName, vector,
|
|
330
|
+
createdAt, updatedAt }`.
|
|
331
|
+
- `KoolbaseDatabase.deleteVector(recordId, field)` removes a record's
|
|
332
|
+
vector slot. Does NOT remove the field declaration itself — the
|
|
333
|
+
field stays settable on other records.
|
|
334
|
+
- `KoolbaseDatabase.searchSemantic({ collection, field, queryVector,
|
|
335
|
+
limit, where })` runs an HNSW similarity search ranking records by
|
|
336
|
+
cosine distance to the query vector. The collection's read rule is
|
|
337
|
+
applied after the lookup; `where` is an optional equality filter
|
|
338
|
+
map. Returns `SemanticSearchResult` — `{ hits, total }` where each
|
|
339
|
+
hit carries `record` and `distance`.
|
|
340
|
+
|
|
341
|
+
- New typed exports: `KoolbaseVector`, `KoolbaseSemanticHit`,
|
|
342
|
+
`SemanticSearchResult`, `KoolbaseVectorDimensionMismatchError`.
|
|
343
|
+
|
|
344
|
+
### Fixed — docs
|
|
345
|
+
|
|
346
|
+
- Database errors table in `README.md` previously listed storage error
|
|
347
|
+
rows (copy-paste bug). Now lists the actual database error subclasses.
|
|
348
|
+
|
|
349
|
+
### Notes
|
|
350
|
+
|
|
351
|
+
- Vector fields must be declared ahead of time via the Koolbase
|
|
352
|
+
dashboard or CLI; the React Native SDK does not declare schema
|
|
353
|
+
(mirrors how collections and storage buckets are declared).
|
|
354
|
+
- Supported dimensions in this release: 384, 768, 1024, 1536. Higher
|
|
355
|
+
dimensions (e.g. OpenAI text-embedding-3-large at 3072) will be
|
|
356
|
+
supported in a future release once pgvector is upgraded — in the
|
|
357
|
+
meantime, use the model's `dimensions=1536` parameter (Matryoshka
|
|
358
|
+
truncation) for full compatibility.
|
|
359
|
+
- Vector operations are online-only. They're not cached locally or
|
|
360
|
+
queued offline because HNSW similarity search has no useful offline
|
|
361
|
+
semantics, so deferred writes could corrupt the user's view of what's
|
|
362
|
+
persisted.
|
|
363
|
+
- Semantic search respects the collection's read rule the same way
|
|
364
|
+
`query()` does — `owner`/`scoped`/`conditional` records are filtered
|
|
365
|
+
to the caller after the HNSW lookup, so strict rules may return fewer
|
|
366
|
+
than `limit` results.
|
|
367
|
+
|
|
368
|
+
### Migration
|
|
369
|
+
|
|
370
|
+
Purely additive — no existing methods, types, or exports changed.
|
|
371
|
+
Upgrading from 5.x requires only `yarn upgrade
|
|
372
|
+
@koolbase/react-native` (or the equivalent npm/pnpm/bun
|
|
373
|
+
command) and rebuilding (`yarn build`).
|
|
374
|
+
|
|
375
|
+
### Added — storage
|
|
376
|
+
|
|
377
|
+
- Object versioning (Gap #6). When a bucket has versioning enabled (via
|
|
378
|
+
the dashboard or the buckets PATCH endpoint), every overwrite preserves
|
|
379
|
+
the prior bytes as a history version and deletes become soft —
|
|
380
|
+
recoverable until force-purged.
|
|
381
|
+
- `KoolbaseStorage.listVersions(bucket, path)` returns the full timeline
|
|
382
|
+
newest-first. Each `KoolbaseObjectVersion` carries `versionId`, `size`,
|
|
383
|
+
`etag`, `metadata`, `createdAt`, plus the flags `isCurrent` (the row
|
|
384
|
+
that lives in `storage_objects` right now) and `isDeleteMarker` (a
|
|
385
|
+
soft-delete tombstone with no fetchable bytes). Returns an empty array
|
|
386
|
+
for a path with no current row and no history.
|
|
387
|
+
- `KoolbaseStorage.getVersion(bucket, path, versionId)` fetches metadata
|
|
388
|
+
for a single version. Works against the current row or any history
|
|
389
|
+
row — check `isCurrent` to disambiguate.
|
|
390
|
+
- `KoolbaseStorage.getDownloadUrl(bucket, path, versionId?)` now accepts
|
|
391
|
+
an optional `versionId` argument. Omit to download the current bytes;
|
|
392
|
+
pass a `versionId` to download that historical version directly.
|
|
393
|
+
Throws for delete markers (no bytes exist).
|
|
394
|
+
- `KoolbaseStorage.restoreVersion(bucket, path, versionId)` brings a
|
|
395
|
+
history version back as the new current. The previously-current row
|
|
396
|
+
is snapshotted into history first, so restore is itself a versioned
|
|
397
|
+
event you can undo. The restored row gets a freshly-minted
|
|
398
|
+
`versionId`; the target stays in history at its original id.
|
|
399
|
+
- `KoolbaseStorage.purgeVersion(bucket, path, versionId)` hard-removes
|
|
400
|
+
a single history row plus its `.versions/` R2 bytes (or just the row,
|
|
401
|
+
for delete markers). Refuses the current version.
|
|
402
|
+
- `KoolbaseStorage.delete(bucket, path, forcePurge?)` now accepts an
|
|
403
|
+
optional `forcePurge` argument. With `forcePurge: true` against a
|
|
404
|
+
versioned bucket, wipes the entire timeline for a path — every row,
|
|
405
|
+
every R2 key. The default (`false`) is the soft-delete behavior:
|
|
406
|
+
snapshots current to history and records a delete marker.
|
|
407
|
+
|
|
408
|
+
- New typed export: `KoolbaseObjectVersion`.
|
|
409
|
+
|
|
410
|
+
### Notes
|
|
411
|
+
|
|
412
|
+
- Versioning is opt-in per bucket. Buckets created before the feature
|
|
413
|
+
shipped — and any bucket with versioning off — keep the legacy
|
|
414
|
+
hard-overwrite, hard-delete semantics. No behavioral change for
|
|
415
|
+
non-versioned buckets.
|
|
416
|
+
- Delete markers can appear in `listVersions` results. Filter
|
|
417
|
+
client-side (`v => !v.isDeleteMarker`) if your UI only wants
|
|
418
|
+
restorable versions.
|
|
419
|
+
- `restoreVersion` against the already-current version, or against a
|
|
420
|
+
delete marker, throws. `getDownloadUrl` with a delete marker's
|
|
421
|
+
`versionId` throws.
|
|
422
|
+
- Backwards-compatible: all new APIs are additive. Existing
|
|
423
|
+
`getDownloadUrl(bucket, path)` and `delete(bucket, path)` calls
|
|
424
|
+
produce identical wire requests as before.
|
|
425
|
+
- Pairs with `koolbase_flutter` v6.5.0 (published earlier today). Same
|
|
426
|
+
client surface (`listVersions` / `getVersion` / `getDownloadUrl` with
|
|
427
|
+
`versionId` / `restoreVersion` / `purgeVersion` / `delete` with
|
|
428
|
+
`forcePurge`), same semantics.
|
|
429
|
+
|
|
430
|
+
## 5.4.0
|
|
431
|
+
|
|
432
|
+
### Added — storage
|
|
433
|
+
|
|
434
|
+
- Edge image transforms (Gap #8). Two complementary forms:
|
|
435
|
+
- `KoolbaseStorage.publicUrl({ ..., transform })` accepts an
|
|
436
|
+
optional `KoolbaseImageTransform` object — width, height,
|
|
437
|
+
format, quality, fit, dpr, gravity. The resulting URL hits
|
|
438
|
+
Cloudflare's image pipeline at
|
|
439
|
+
`cdn.koolbase.com/cdn-cgi/image/<opts>/...` and serves a
|
|
440
|
+
resized, re-encoded copy of the source.
|
|
441
|
+
- `KoolbaseStorage.publicUrlWithPreset({ projectId, presetName,
|
|
442
|
+
bucket, path })` resolves a named preset stored server-side
|
|
443
|
+
(managed via the dashboard or REST API) at
|
|
444
|
+
`cdn.koolbase.com/p/{project_id}/{preset_name}/{bucket}/{path}`.
|
|
445
|
+
Edit the preset once on the server and every URL using it
|
|
446
|
+
updates as the edge cache rolls over.
|
|
447
|
+
- `KoolbaseStorage.publicUrlForObjectWithPreset(obj, bucket,
|
|
448
|
+
presetName)` — instance-style variant when you already have a
|
|
449
|
+
`KoolbaseObject` in hand.
|
|
450
|
+
- New typed exports: `KoolbaseImageTransform`, `KoolbaseImageFormat`
|
|
451
|
+
(`'auto' | 'webp' | 'avif' | 'jpeg' | 'png'`), `KoolbaseImageFit`
|
|
452
|
+
(`'scale-down' | 'contain' | 'cover' | 'crop' | 'pad'`),
|
|
453
|
+
`KoolbaseImageGravity` (10 anchor positions). Out-of-range numeric
|
|
454
|
+
values clamp silently to Cloudflare's valid ranges (width/height
|
|
455
|
+
1–2000, quality 1–100, dpr 1–3).
|
|
456
|
+
|
|
457
|
+
### Notes
|
|
458
|
+
|
|
459
|
+
Cloudflare bills unique transformations per calendar month; every
|
|
460
|
+
Koolbase account includes 5,000 free. Transformed responses are
|
|
461
|
+
edge-cached for 4 hours.
|
|
462
|
+
|
|
463
|
+
### Compatibility
|
|
464
|
+
|
|
465
|
+
No breaking changes. All new APIs are additive; existing `publicUrl`
|
|
466
|
+
calls without `transform` produce the exact same URL they did in 5.3.0.
|
|
467
|
+
|
|
468
|
+
## 5.3.0
|
|
469
|
+
|
|
470
|
+
### Added — storage
|
|
471
|
+
|
|
472
|
+
- Public bucket CDN URLs (Gap #2 SDK polish).
|
|
473
|
+
- `KoolbaseObject` gains an `r2Bucket: string` field identifying
|
|
474
|
+
which physical R2 bucket holds the object's bytes. Always
|
|
475
|
+
populated. `'koolbase-storage-public'` means the object has a
|
|
476
|
+
stable CDN URL; anything else (typically `'koolbase-storage'`)
|
|
477
|
+
means it's in private storage and reads go through a presigned
|
|
478
|
+
URL via `getDownloadUrl`.
|
|
479
|
+
- `KoolbaseStorage.publicUrl({ projectId, bucket, path })` — static
|
|
480
|
+
method that builds the CDN URL pattern unconditionally. Use for
|
|
481
|
+
build-time URL generation where you have the inputs but don't
|
|
482
|
+
need (or want) a check that the file is actually in a public
|
|
483
|
+
bucket.
|
|
484
|
+
- `KoolbaseStorage.publicUrlForObject(obj, bucket)` — static method
|
|
485
|
+
that returns the stable CDN URL when the object lives in the
|
|
486
|
+
public R2 bucket, `null` otherwise. Use this when you have a
|
|
487
|
+
`KoolbaseObject` instance and want a safe URL — returns `null`
|
|
488
|
+
rather than a URL that 404s for private or legacy public-bucket
|
|
489
|
+
files.
|
|
490
|
+
|
|
491
|
+
### Internal
|
|
492
|
+
|
|
493
|
+
- Storage object JSON mapper extended to surface the server's
|
|
494
|
+
`r2_bucket` field as `r2Bucket` on the typed `KoolbaseObject`.
|
|
495
|
+
Defaults to `'koolbase-storage'` when the field is absent (older
|
|
496
|
+
cached responses, non-Koolbase JSON) so existing code keeps
|
|
497
|
+
decoding without crashes.
|
|
498
|
+
|
|
499
|
+
### Compatibility
|
|
500
|
+
|
|
501
|
+
No breaking changes. `getDownloadUrl` already returns the CDN URL
|
|
502
|
+
for objects in public buckets since the server-side Gap #2 deploy on
|
|
503
|
+
Jun 2 2026 — this release just makes that URL constructible without
|
|
504
|
+
a network round-trip.
|
|
505
|
+
|
|
506
|
+
## 5.2.0
|
|
507
|
+
|
|
508
|
+
### Added — storage
|
|
509
|
+
|
|
510
|
+
- Custom object metadata. Attach arbitrary key/value pairs to stored
|
|
511
|
+
objects at upload time, mutate via merge semantics post-upload, read
|
|
512
|
+
alongside any `KoolbaseObject`.
|
|
513
|
+
- `KoolbaseStorage.upload({ metadata })` accepts an optional
|
|
514
|
+
`metadata: Record<string, string>` field on `UploadOptions`. Set
|
|
515
|
+
at confirm time; REPLACES prior metadata on the `overwrite: true`
|
|
516
|
+
path (matches GCS semantics — a new upload at a path produces a
|
|
517
|
+
new object, not a patch of the old).
|
|
518
|
+
- New `KoolbaseStorage.updateMetadata(bucket, path, metadata)`
|
|
519
|
+
method with merge semantics: keys with a non-null string value
|
|
520
|
+
are set/updated, keys with `null` are deleted, keys absent from
|
|
521
|
+
the payload are untouched. One call handles add, update, and
|
|
522
|
+
delete atomically.
|
|
523
|
+
- `KoolbaseObject` gains a `metadata: Record<string, string>` field.
|
|
524
|
+
Always non-null — empty object `{}` when no metadata is set —
|
|
525
|
+
so callers can treat it as a guaranteed record without null
|
|
526
|
+
checks. Defensive decode handles missing/null `metadata` field
|
|
527
|
+
gracefully so older cached responses don't crash the mapper.
|
|
528
|
+
- New `KoolbaseStorageMetadataInvalidError` (extends
|
|
529
|
+
`KoolbaseStorageError`) thrown for server-side validation
|
|
530
|
+
failures (HTTP 400, code `metadata_invalid`). Its `detail` field
|
|
531
|
+
names the failing key and rule (e.g. `key "bad key": must match
|
|
532
|
+
[a-z0-9_]+`, `exceeds 50 keys (got 53)`) so callers can surface
|
|
533
|
+
actionable errors without guessing what shape rule was violated.
|
|
534
|
+
- Mapper recognizes `metadata_invalid` and extracts `detail` from the
|
|
535
|
+
response body.
|
|
536
|
+
|
|
537
|
+
### Notes
|
|
538
|
+
|
|
539
|
+
- Validation rules (enforced server-side): ≤50 keys per object, ≤8KB
|
|
540
|
+
total (sum of all key + value lengths), keys 1–64 chars matching
|
|
541
|
+
`[a-z0-9_]+`, values ≤1024 chars, leading underscore reserved for
|
|
542
|
+
system keys.
|
|
543
|
+
- Backwards-compatible: pure additive surface. v5.1.1 → v5.2.0. Existing
|
|
544
|
+
`upload()` callers without `metadata` continue working unchanged;
|
|
545
|
+
catching `KoolbaseStorageError` still catches the new metadata error.
|
|
546
|
+
- Pairs with `koolbase_flutter` v6.2.0 (published earlier today). Same
|
|
547
|
+
client surface (`upload({ metadata })`, `updateMetadata`), same error
|
|
548
|
+
type semantics, same merge contract.
|
|
549
|
+
|
|
550
|
+
## 5.1.1
|
|
551
|
+
|
|
552
|
+
### Fixed — storage
|
|
553
|
+
|
|
554
|
+
- Storage error mapper now switches on lowercase wire codes
|
|
555
|
+
(`path_conflict`, `quota_exceeded`, `file_too_large`, `mime_not_allowed`)
|
|
556
|
+
after the server normalized storage codes to lowercase snake_case.
|
|
557
|
+
Without this patch, v5.1.0 customers see generic `KoolbaseStorageError`
|
|
558
|
+
instead of the typed subclass for storage limit errors. No semantic
|
|
559
|
+
changes beyond the case match. Pairs with `koolbase_flutter` v6.1.1.
|
|
560
|
+
|
|
561
|
+
## 5.1.0
|
|
562
|
+
|
|
563
|
+
### Added — storage
|
|
564
|
+
|
|
565
|
+
- Three new typed errors covering the bucket-limit failure modes
|
|
566
|
+
introduced server-side in Storage #2. All extend
|
|
567
|
+
`KoolbaseStorageError`, so existing `instanceof KoolbaseStorageError`
|
|
568
|
+
catch-all blocks continue to work; check the specific type to branch
|
|
569
|
+
on the kind of limit hit.
|
|
570
|
+
- `KoolbaseStorageQuotaError` — 409 + `QUOTA_EXCEEDED`, thrown when
|
|
571
|
+
an upload would push the bucket past its `max_size_bytes` cap.
|
|
572
|
+
- `KoolbaseStorageFileTooLargeError` — 413 + `FILE_TOO_LARGE`, thrown
|
|
573
|
+
when a single file exceeds the bucket's `max_file_size_bytes` cap.
|
|
574
|
+
- `KoolbaseStorageMimeTypeError` — 415 + `MIME_NOT_ALLOWED`, thrown
|
|
575
|
+
when an upload's content-type isn't in the bucket's
|
|
576
|
+
`allowed_mime_types` allowlist (supports `type/*` wildcards).
|
|
577
|
+
- Mapper (`koolbaseStorageError` / `koolbaseStorageErrorFromResponse`)
|
|
578
|
+
recognizes the new codes via code-first lookup and the new HTTP
|
|
579
|
+
statuses (413, 415) via status fallback.
|
|
580
|
+
|
|
581
|
+
### Notes
|
|
582
|
+
|
|
583
|
+
- Backwards-compatible: pure additive surface. v5.0.0 → v5.1.0.
|
|
584
|
+
- Status-fallback for 409 remains `KoolbaseStorageConflictError` (path
|
|
585
|
+
collisions are the more common case); modern servers always emit
|
|
586
|
+
`code`, so the ambiguity only affects very old API responses.
|
|
587
|
+
- Pairs with `koolbase_flutter` v6.1.0 (published earlier today). Same
|
|
588
|
+
three error types, same code-first mapper extension.
|
|
589
|
+
|
|
590
|
+
## 5.0.0
|
|
591
|
+
|
|
592
|
+
### Breaking — storage
|
|
593
|
+
|
|
594
|
+
- **Storage URLs realigned to current server contract.** The v3.0.0 security
|
|
595
|
+
audit updated auth headers but left storage calling the old pre-refactor
|
|
596
|
+
endpoints (`/v1/sdk/storage/{bucket}/upload`, `/download`, `/delete`),
|
|
597
|
+
which the server no longer routes. **Storage uploads have been
|
|
598
|
+
non-functional since v3.0.0.** v5.0.0 realigns to the current contract:
|
|
599
|
+
`/v1/sdk/storage/upload-url`, `/confirm`, `/download-url`, `/object`.
|
|
600
|
+
- **3-step upload flow.** `upload()` now does presign → R2 PUT (raw binary, not
|
|
601
|
+
multipart) → confirm, matching Koolbase Flutter SDK v6.0.0. Confirmation
|
|
602
|
+
records the object in `storage_objects`, populates `etag`/`size`, and
|
|
603
|
+
prevents the orphan reaper from deleting your file. Previous "uploads"
|
|
604
|
+
bypassed confirm entirely and would have been swept on the next reaper pass.
|
|
605
|
+
- **`upload()` return shape changed.** Returns `UploadResult { object, downloadUrl }`
|
|
606
|
+
instead of `{ url }`. `object` is the full `KoolbaseObject` metadata
|
|
607
|
+
(id, size, content type, timestamps, etc.).
|
|
608
|
+
- **Safe-by-default uploads.** `UploadOptions` now accepts an `overwrite?: boolean`
|
|
609
|
+
field, defaulting to `false`. Uploads to a path where an object already
|
|
610
|
+
exists are **rejected** with a new `KoolbaseStorageConflictError` instead
|
|
611
|
+
of silently overwriting. Pass `overwrite: true` to opt into the previous
|
|
612
|
+
replacing behavior.
|
|
613
|
+
- **Storage operations now throw typed `KoolbaseStorageError` subtypes**
|
|
614
|
+
instead of generic `Error` — catching `Error` still works, but catching
|
|
615
|
+
the specific subclasses (or the `KoolbaseStorageError` base) gives you
|
|
616
|
+
cleaner branching.
|
|
617
|
+
|
|
618
|
+
### Added
|
|
619
|
+
|
|
620
|
+
- `KoolbaseStorageError` — base class for all storage failures, mirroring
|
|
621
|
+
the `KoolbaseDataError` pattern from the database layer.
|
|
622
|
+
- `KoolbaseStorageConflictError` (`code: PATH_CONFLICT`) — thrown when an
|
|
623
|
+
upload would replace an existing object and `overwrite: false`. Exposes
|
|
624
|
+
the colliding `path` from the server response.
|
|
625
|
+
- `KoolbaseStorageNotFoundError`, `KoolbaseStorageValidationError`,
|
|
626
|
+
`KoolbaseStoragePermissionError` — typed errors for the other storage
|
|
627
|
+
error classes (404, 400, 403). Storage operations now throw these
|
|
628
|
+
instead of a generic `Error`.
|
|
629
|
+
- `koolbaseStorageError(status, body)` and
|
|
630
|
+
`koolbaseStorageErrorFromResponse(res)` — code-first response-to-error
|
|
631
|
+
mappers, matching the `database-errors` module pattern.
|
|
632
|
+
- `KoolbaseObject` and `UploadResult` types in `types.ts` — full object
|
|
633
|
+
metadata is now part of the public surface.
|
|
634
|
+
|
|
635
|
+
### Migration
|
|
636
|
+
|
|
637
|
+
**If your app uploads to deterministic paths** (e.g. `avatars/${userId}.png`)
|
|
638
|
+
**and relied on the upload silently replacing the previous file:**
|
|
639
|
+
|
|
640
|
+
```typescript
|
|
641
|
+
// Before — silent overwrite
|
|
642
|
+
await Koolbase.storage.upload({
|
|
643
|
+
bucket: 'avatars',
|
|
644
|
+
path: 'me.png',
|
|
645
|
+
file: { uri, name, type: 'image/png' },
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
// After — explicit overwrite
|
|
649
|
+
await Koolbase.storage.upload({
|
|
650
|
+
bucket: 'avatars',
|
|
651
|
+
path: 'me.png',
|
|
652
|
+
file: { uri, name, type: 'image/png' },
|
|
653
|
+
overwrite: true,
|
|
654
|
+
});
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
**If you want a conflict prompt** (recommended for user-supplied filenames):
|
|
658
|
+
|
|
659
|
+
```typescript
|
|
660
|
+
try {
|
|
661
|
+
await Koolbase.storage.upload({
|
|
662
|
+
bucket: 'documents',
|
|
663
|
+
path: filename,
|
|
664
|
+
file: { uri, name, type },
|
|
665
|
+
});
|
|
666
|
+
} catch (e) {
|
|
667
|
+
if (e instanceof KoolbaseStorageConflictError) {
|
|
668
|
+
const ok = await confirm(`${e.path} already exists. Overwrite?`);
|
|
669
|
+
if (ok) {
|
|
670
|
+
await Koolbase.storage.upload({
|
|
671
|
+
bucket: 'documents',
|
|
672
|
+
path: filename,
|
|
673
|
+
file: { uri, name, type },
|
|
674
|
+
overwrite: true,
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
} else {
|
|
678
|
+
throw e;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
**If you used `const { url } = await upload(...)`:**
|
|
684
|
+
|
|
685
|
+
```typescript
|
|
686
|
+
// Before
|
|
687
|
+
const { url } = await Koolbase.storage.upload({ ... });
|
|
688
|
+
|
|
689
|
+
// After
|
|
690
|
+
const { object, downloadUrl } = await Koolbase.storage.upload({ ... });
|
|
691
|
+
const url = downloadUrl; // if you only want the download URL
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
**If you catch generic `Error` from storage operations**, consider catching
|
|
695
|
+
`KoolbaseStorageError` (or specific subclasses) for cleaner branching:
|
|
696
|
+
|
|
697
|
+
```typescript
|
|
698
|
+
try {
|
|
699
|
+
await Koolbase.storage.upload({ ... });
|
|
700
|
+
} catch (e) {
|
|
701
|
+
if (e instanceof KoolbaseStorageConflictError) {
|
|
702
|
+
// Path already exists — prompt user
|
|
703
|
+
} else if (e instanceof KoolbaseStorageNotFoundError) {
|
|
704
|
+
// Bucket missing or deleted
|
|
705
|
+
} else if (e instanceof KoolbaseStoragePermissionError) {
|
|
706
|
+
// Caller not authorized
|
|
707
|
+
} else if (e instanceof KoolbaseStorageError) {
|
|
708
|
+
// Any other storage error
|
|
709
|
+
showError(e.message);
|
|
710
|
+
} else {
|
|
711
|
+
throw e;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
### Server requirements
|
|
717
|
+
|
|
718
|
+
- Requires a Koolbase server build with `PATH_CONFLICT` 409 support and the
|
|
719
|
+
`upload-url` / `confirm` / `download-url` / `object` routes (shipped
|
|
720
|
+
alongside this release).
|
|
721
|
+
|
|
722
|
+
### Verification recommended
|
|
723
|
+
|
|
724
|
+
Storage in v3.0.0–v4.2.1 was non-functional. v5.0.0 is the first working
|
|
725
|
+
upload path since the security audit. **Test uploads end-to-end on a real
|
|
726
|
+
iOS and Android device after upgrading** — RN's `fetch` Blob-PUT behavior
|
|
727
|
+
can vary subtly by platform.
|
|
728
|
+
|
|
729
|
+
## 4.2.1
|
|
730
|
+
|
|
731
|
+
### Fixed
|
|
732
|
+
|
|
733
|
+
- Realtime now delivers `deleted` events. v4.1.0 dropped them because `RealtimeEvent` required a `record`, which deletes don't carry. `record` is now optional and `recordId` is provided on deletes — bringing React Native to parity with Flutter.
|
|
734
|
+
|
|
735
|
+
## 4.2.0
|
|
736
|
+
|
|
737
|
+
### Fixed
|
|
738
|
+
|
|
739
|
+
- Realtime now works. The client previously hit the wrong endpoint, authenticated with the public key instead of the user session, never sent a subscribe message, and parsed the wrong event shape — so it delivered nothing. Rewritten to the real protocol: connects with the signed-in user's session, subscribes per collection, streams `created`/`updated` events, and reconnects automatically.
|
|
740
|
+
|
|
741
|
+
### Changed
|
|
742
|
+
|
|
743
|
+
- `Koolbase.realtime.subscribe(collection, cb)` no longer needs a project — it's derived from the user's session.
|
|
744
|
+
|
|
745
|
+
## 4.0.0
|
|
746
|
+
|
|
747
|
+
### Changed
|
|
748
|
+
|
|
749
|
+
- **BREAKING:** `insert` and `update` are now online-first with an offline fallback. When the server is reachable they await the response and throw typed errors on rejection — a unique-constraint conflict throws `KoolbaseConflictError` (with the offending `field`), matching `upsert`. Only a genuine network failure falls back to the optimistic local-cache + sync-queue path. Previously these methods swallowed all server errors and `insert` always returned a local-id optimistic record.
|
|
750
|
+
|
|
751
|
+
### Migration
|
|
752
|
+
|
|
753
|
+
- Wrap `insert`/`update` in `try/catch` to handle conflicts (`catch (e) { if (e instanceof KoolbaseConflictError) … }`). If you relied on the returned id beginning with `local_`, treat the returned record's `id` as authoritative instead — when online it is now the server id.
|
|
754
|
+
|
|
755
|
+
## 3.1.0
|
|
756
|
+
|
|
757
|
+
### Added
|
|
758
|
+
|
|
759
|
+
- `batch()` method on `KoolbaseDatabase` for atomic multi-operation writes — runs `insert` / `update` / `delete` / `upsert` in a single server-side transaction. Closes the parity gap with `koolbase_flutter` 5.0.0, where `batch()` shipped on the Flutter side only. Online-only by design; throws on network failure rather than queuing.
|
|
760
|
+
- `BatchOp` factory and `BatchResult` interface exported from `types`.
|
|
761
|
+
|
|
762
|
+
## 3.0.0
|
|
763
|
+
|
|
764
|
+
### BREAKING — security
|
|
765
|
+
|
|
766
|
+
- Data-plane requests (database, storage, functions, offline sync) now
|
|
767
|
+
authenticate with the signed-in user's access token (Authorization: Bearer)
|
|
768
|
+
instead of the x-user-id header. The header is no longer sent or trusted.
|
|
769
|
+
Requires the matching Koolbase server build.
|
|
770
|
+
- End-user identity flows automatically from the active session — nothing to
|
|
771
|
+
pass on db/storage calls. owner/authenticated collections require an active
|
|
772
|
+
session.
|
|
773
|
+
|
|
774
|
+
### Added
|
|
775
|
+
|
|
776
|
+
- KoolbaseAuth.validAccessToken() — returns a currently-valid token,
|
|
777
|
+
refreshing near expiry; the data-plane clients pull from it per request so
|
|
778
|
+
identity follows the live session.
|
|
779
|
+
|
|
780
|
+
### Fixed
|
|
781
|
+
|
|
782
|
+
- Offline writes are now replayed with the user's identity (previously synced
|
|
783
|
+
anonymously through the sync engine).
|
|
784
|
+
|
|
785
|
+
## 2.4.0
|
|
786
|
+
|
|
787
|
+
- **Code Push — mandatory bundles.** The SDK now honors a bundle's `mandatory` flag. When a mandatory bundle is staged:
|
|
788
|
+
- `Koolbase.codePush.hasMandatoryUpdate` returns `true` — read it on app resume to gate your UI.
|
|
789
|
+
- The optional `onMandatoryUpdate` callback on the config passed to `Koolbase.initialize()` fires with `{ version, bundleId }` so you can prompt the user to restart.
|
|
790
|
+
- No breaking changes.
|
|
791
|
+
|
|
792
|
+
## 2.3.0
|
|
793
|
+
|
|
794
|
+
- Auth errors are now selected from the server's stable error `code` (with
|
|
795
|
+
status/message fallback for older servers), retiring message string-matching.
|
|
796
|
+
- New typed data-layer errors — KoolbaseNotFoundError, KoolbaseValidationError,
|
|
797
|
+
KoolbasePermissionError, KoolbaseRateLimitError — plus a shared
|
|
798
|
+
KoolbaseDataError base. query/get/upsert/deleteWhere now throw these
|
|
799
|
+
(code-first) instead of a generic Error.
|
|
800
|
+
- KoolbaseConflictError now exposes the collided `field` and extends
|
|
801
|
+
KoolbaseDataError.
|
|
802
|
+
|
|
803
|
+
## 2.2.0
|
|
804
|
+
|
|
805
|
+
- Added `KoolbaseConflictError`, thrown by `upsert` on a unique-constraint violation (HTTP 409). insert/update are optimistic/offline-first and surface conflicts at sync time, not as a thrown error.
|
|
806
|
+
|
|
807
|
+
## 2.1.0
|
|
808
|
+
|
|
809
|
+
- Added `Koolbase.db.upsert(collection:, match:, data:)` — insert-or-update by a match filter; returns `KoolbaseUpsertResult { record, created }`. Online-only.
|
|
810
|
+
- Added `Koolbase.db.deleteWhere(collection:, filters:)` — bulk delete by filter; returns the number of records deleted. Online-only.
|
|
811
|
+
|
|
812
|
+
## 2.0.0
|
|
813
|
+
|
|
814
|
+
### Breaking
|
|
815
|
+
|
|
816
|
+
- **Flat record shape.** Records are no longer wrapped in a `data` envelope on
|
|
817
|
+
the wire — your fields come back top-level, with system metadata in a reserved
|
|
818
|
+
`$`-prefixed namespace (`$id`, `$createdAt`, `$updatedAt`, `$collection`,
|
|
819
|
+
`$createdBy`). The SDK maps this back into `KoolbaseRecord`, so you still read
|
|
820
|
+
fields via `record.data.<field>`.
|
|
821
|
+
- Removed `KoolbaseRecord.projectId` and `KoolbaseRecord.collectionId`.
|
|
822
|
+
- Requires a Koolbase server on the flat record contract (shipped alongside this
|
|
823
|
+
release). Older servers return the legacy envelope and are not compatible.
|
|
824
|
+
|
|
825
|
+
### Added
|
|
826
|
+
|
|
827
|
+
- `KoolbaseRecord.collection` — the record's collection name.
|
|
828
|
+
|
|
829
|
+
### Fixed
|
|
830
|
+
|
|
831
|
+
- `KoolbaseRecord.createdAt` / `updatedAt` are now reliably populated. Under the
|
|
832
|
+
previous raw cast they were silently `undefined` (snake_case wire vs camelCase).
|
|
833
|
+
|
|
834
|
+
### Changed
|
|
835
|
+
|
|
836
|
+
- Realtime events and populated/related records now use the same flat shape.
|
|
837
|
+
- Offline cache is forward-compatible: existing entries stay readable (`id` and
|
|
838
|
+
`data` are shape-stable) and refresh to the new shape on the next online read;
|
|
839
|
+
pending offline writes are preserved.
|
|
840
|
+
|
|
841
|
+
## 1.11.0
|
|
842
|
+
|
|
843
|
+
### Added
|
|
844
|
+
|
|
845
|
+
- **Sign in with Google** — production-ready end-user OAuth via
|
|
846
|
+
`Koolbase.auth.signInWithGoogle({idToken, nonce?})`. Routes to the
|
|
847
|
+
server endpoint at `/v1/sdk/auth/oauth/google` with RS256-only JWKS
|
|
848
|
+
verification against Google's certs endpoint, multi-audience support
|
|
849
|
+
(iOS / Android / web client IDs configured per environment),
|
|
850
|
+
15-minute replay defense, and optional nonce check.
|
|
851
|
+
- `SignInWithGoogleParams` interface in `types.ts`.
|
|
852
|
+
- Three new typed errors in `auth-errors.ts`:
|
|
853
|
+
`GoogleSignInNotConfiguredError`, `InvalidGoogleTokenError`,
|
|
854
|
+
`GoogleEmailRequiredError`. Reuses existing `OAuthEmailConflictError`
|
|
855
|
+
and `UserDisabledError`.
|
|
856
|
+
|
|
857
|
+
#### Example with `@react-native-google-signin/google-signin`
|
|
858
|
+
|
|
859
|
+
```typescript
|
|
860
|
+
import { GoogleSignin } from '@react-native-google-signin/google-signin';
|
|
861
|
+
|
|
862
|
+
GoogleSignin.configure({
|
|
863
|
+
webClientId: '<your-web-client-id>.apps.googleusercontent.com',
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
const userInfo = await GoogleSignin.signIn();
|
|
867
|
+
|
|
868
|
+
const session = await Koolbase.auth.signInWithGoogle({
|
|
869
|
+
idToken: userInfo.idToken!,
|
|
870
|
+
});
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
### Auto-link policy
|
|
874
|
+
|
|
875
|
+
Same as Apple Sign-In (v1.10.0). A new Google identity attaches to an
|
|
876
|
+
existing user only when BOTH the Google email AND the existing user's
|
|
877
|
+
email are verified, AND emails match (case-insensitive). Otherwise
|
|
878
|
+
sign-in either creates a new user (no email collision) or surfaces
|
|
879
|
+
`OAuthEmailConflictError`.
|
|
880
|
+
|
|
881
|
+
### Configuration required
|
|
882
|
+
|
|
883
|
+
Before users can sign in with Google, configure the provider for your
|
|
884
|
+
environment with the OAuth client IDs from Google Cloud Console (one
|
|
885
|
+
each for iOS, Android, and web). See the README for the SQL setup.
|
|
886
|
+
|
|
887
|
+
### Coming next
|
|
888
|
+
|
|
889
|
+
- **Dashboard UI** for OAuth config — replaces the SQL workflow
|
|
890
|
+
|
|
891
|
+
## 1.10.1
|
|
892
|
+
|
|
893
|
+
### Documentation
|
|
894
|
+
|
|
895
|
+
- README rewritten to accurately reflect the v1.10.0 SDK surface. No SDK
|
|
896
|
+
code changes; this release exists to refresh the README rendered on the
|
|
897
|
+
npmjs.com package page.
|
|
898
|
+
- Removed fictional `Koolbase.auth.signInWithGoogle` reference. Google
|
|
899
|
+
Sign-In is planned for v1.11.0 — noted explicitly in the OAuth section.
|
|
900
|
+
- Replaced the deprecated `KoolbaseAppleAuth.signIn(callback)` example
|
|
901
|
+
with the new `Koolbase.auth.signInWithApple({identityToken, nonce?, fullName?})`
|
|
902
|
+
v1.10.0 API using `@invertase/react-native-apple-authentication`.
|
|
903
|
+
- Added `Koolbase.auth.onAuthStateChange(listener)` example (v1.9.0 feature).
|
|
904
|
+
- Replaced the Firebase/Supabase comparison table with a Koolbase-only
|
|
905
|
+
feature inventory.
|
|
906
|
+
- Bumped install snippet from `^1.8.0` to `^1.10.0`.
|
|
907
|
+
|
|
908
|
+
## 1.10.0
|
|
909
|
+
|
|
910
|
+
### Added
|
|
911
|
+
|
|
912
|
+
- **Sign in with Apple** — production-ready end-user OAuth via
|
|
913
|
+
`koolbase.auth.signInWithApple({identityToken, nonce?, fullName?})`.
|
|
914
|
+
Routes to the new server endpoint at `/v1/sdk/auth/oauth/apple` with
|
|
915
|
+
RS256-only JWKS verification, audience bound to your project's iOS
|
|
916
|
+
Bundle ID, 15-minute replay defense, and optional nonce check.
|
|
917
|
+
- `AppleFullName` interface and `SignInWithAppleParams` interface in
|
|
918
|
+
`types.ts`.
|
|
919
|
+
- Four new typed errors in `auth-errors.ts`:
|
|
920
|
+
`AppleSignInNotConfiguredError`, `InvalidAppleTokenError`,
|
|
921
|
+
`AppleEmailRequiredError`, `OAuthEmailConflictError`.
|
|
922
|
+
|
|
923
|
+
#### Example with `@invertase/react-native-apple-authentication`
|
|
924
|
+
|
|
925
|
+
```typescript
|
|
926
|
+
import appleAuth from '@invertase/react-native-apple-authentication';
|
|
927
|
+
|
|
928
|
+
// Get credential from native Apple Sign-In
|
|
929
|
+
const appleResponse = await appleAuth.performRequest({
|
|
930
|
+
requestedOperation: appleAuth.Operation.LOGIN,
|
|
931
|
+
requestedScopes: [appleAuth.Scope.EMAIL, appleAuth.Scope.FULL_NAME],
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
// Pass to Koolbase
|
|
935
|
+
const session = await koolbase.auth.signInWithApple({
|
|
936
|
+
identityToken: appleResponse.identityToken!,
|
|
937
|
+
nonce: appleResponse.nonce,
|
|
938
|
+
fullName: appleResponse.fullName
|
|
939
|
+
? {
|
|
940
|
+
givenName: appleResponse.fullName.givenName ?? undefined,
|
|
941
|
+
familyName: appleResponse.fullName.familyName ?? undefined,
|
|
942
|
+
}
|
|
943
|
+
: undefined,
|
|
944
|
+
});
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
### Auto-link policy
|
|
948
|
+
|
|
949
|
+
A new Apple identity attaches to an existing user only when BOTH the
|
|
950
|
+
provider email AND the existing user's email are verified, AND emails
|
|
951
|
+
match (case-insensitive). Otherwise sign-in either creates a new user
|
|
952
|
+
(no email collision) or surfaces `OAuthEmailConflictError` — user signs
|
|
953
|
+
in with existing method, then links Apple from settings.
|
|
954
|
+
|
|
955
|
+
### Configuration required
|
|
956
|
+
|
|
957
|
+
Before users can sign in with Apple, configure the provider for your
|
|
958
|
+
environment via direct DB insert (dashboard UI lands in v1.10.x):
|
|
959
|
+
|
|
960
|
+
```sql
|
|
961
|
+
INSERT INTO project_oauth_configs (environment_id, provider, bundle_id, enabled)
|
|
962
|
+
VALUES ('<your-environment-id>', 'apple', 'com.yourapp.bundle', true);
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
The Bundle ID is the audience claim in identity tokens from native Apple
|
|
966
|
+
Sign-In and must match exactly.
|
|
967
|
+
|
|
968
|
+
### Still deprecated — `KoolbaseAppleAuth.signIn` and `oauthLogin`
|
|
969
|
+
|
|
970
|
+
These remain deprecated and throw `KoolbaseAuthError('not_implemented')`.
|
|
971
|
+
The v1.10.0 surface is `koolbase.auth.signInWithApple(...)` on the auth
|
|
972
|
+
instance — same place as all other auth methods.
|
|
973
|
+
|
|
974
|
+
### Coming next
|
|
975
|
+
|
|
976
|
+
- **Dashboard UI** for OAuth config (v1.10.x) — minimal Bundle-ID input,
|
|
977
|
+
enable/disable toggle.
|
|
978
|
+
- **Google Sign-In** (v1.11.0) — same endpoint pattern at
|
|
979
|
+
`/v1/sdk/auth/oauth/google`.
|
|
980
|
+
- **GitHub OAuth** (v1.12.0) — code-exchange flow.
|
|
981
|
+
|
|
982
|
+
## 1.9.0
|
|
983
|
+
|
|
984
|
+
### Fixed (critical)
|
|
985
|
+
|
|
986
|
+
v1.8.0 and earlier shipped with silent breakages on the SDK auth surface.
|
|
987
|
+
Anyone using `KoolbaseAuth` before v1.9.0 should upgrade immediately.
|
|
988
|
+
|
|
989
|
+
- **`x-api-key` header is now sent on every auth request.** Previously the
|
|
990
|
+
SDK sent only `Content-Type`, causing the server's caller middleware to
|
|
991
|
+
resolve every project-scoped call as anonymous — every `/v1/sdk/auth/*`
|
|
992
|
+
endpoint returned 401.
|
|
993
|
+
- **Password reset endpoints were targeting wrong paths.** Both
|
|
994
|
+
`forgotPassword` (was `/v1/sdk/auth/forgot-password`) and `resetPassword`
|
|
995
|
+
(was `/v1/sdk/auth/reset-password`) silently 404'd on the server. Now
|
|
996
|
+
corrected to `/password-reset` and `/password-reset/confirm`.
|
|
997
|
+
- **Session responses were not being mapped.** The server returns
|
|
998
|
+
`access_token` / `refresh_token` / `expires_at` (snake_case); the SDK cast
|
|
999
|
+
directly to camelCase types, so `session.accessToken` was `undefined` and
|
|
1000
|
+
the Authorization header silently sent `Bearer undefined`. Now mapped
|
|
1001
|
+
properly on every session-returning endpoint.
|
|
1002
|
+
- **`register()` was discarding the session** the server returned; only the
|
|
1003
|
+
user object was kept. Now persists the full session.
|
|
1004
|
+
|
|
1005
|
+
### Added
|
|
1006
|
+
|
|
1007
|
+
- **Persistent sessions.** New `SecureAuthStorage` default backed by
|
|
1008
|
+
`react-native-keychain` (iOS Keychain + Android Keystore-backed
|
|
1009
|
+
encryption). The peer dependency is **optional** — apps without it
|
|
1010
|
+
installed see a clear warning and operate without persistence rather than
|
|
1011
|
+
crashing. Apps with custom requirements (Expo Go, compliance encryption,
|
|
1012
|
+
in-memory test mocks) can implement the `KoolbaseAuthStorage` interface
|
|
1013
|
+
and inject it via `KoolbaseConfig.authStorage`.
|
|
1014
|
+
- **Offline-aware session restoration.** New `restoreSession()` method
|
|
1015
|
+
returning a `RestoreResult` enum:
|
|
1016
|
+
- `NoSession` → no persisted session, show login
|
|
1017
|
+
- `Restored` → ready, show authenticated UI
|
|
1018
|
+
- `Expired` → refresh token rejected, show login
|
|
1019
|
+
- `Offline` → network unreachable, optimistically authenticated
|
|
1020
|
+
|
|
1021
|
+
Optimistic state is populated from disk *before* any network call, so
|
|
1022
|
+
authenticated UI renders immediately at app launch with no round-trip.
|
|
1023
|
+
- **Auth state listener.** New `KoolbaseAuth.onAuthStateChange(listener)`
|
|
1024
|
+
API following the RN ecosystem convention (Firebase/Supabase style):
|
|
1025
|
+
fires immediately on subscribe with current state, then on every state
|
|
1026
|
+
change. Returns an unsubscribe function for cleanup.
|
|
1027
|
+
- **Single-flight token refresh.** Concurrent callers hitting a stale token
|
|
1028
|
+
share one underlying refresh and receive the same result. Prevents the
|
|
1029
|
+
race where parallel refreshes each rotate the refresh token,
|
|
1030
|
+
invalidating peers mid-flight.
|
|
1031
|
+
- **Typed error hierarchy.** 10 new typed errors for granular handling:
|
|
1032
|
+
`InvalidCredentialsError`, `EmailAlreadyInUseError`, `UserDisabledError`,
|
|
1033
|
+
`WeakPasswordError`, `SessionExpiredError`, `TokenRevokedError`,
|
|
1034
|
+
`AccountLockedError` (with forward-compatible `lockedUntil` field),
|
|
1035
|
+
`UnlockTokenInvalidError`, `RateLimitError`, `NetworkError`. All extend
|
|
1036
|
+
`KoolbaseAuthError` for generic catches.
|
|
1037
|
+
- **Account unlock.** New `KoolbaseAuth.unlock(token)` method consumes the
|
|
1038
|
+
one-shot token from a brute-force unlock email and restores login access.
|
|
1039
|
+
- **Device metadata.** Every auth request now carries seven identifying
|
|
1040
|
+
headers including a stable per-install UUID device label, SDK version,
|
|
1041
|
+
platform info, and app version. Helps server-side debugging and
|
|
1042
|
+
version-conditional logic.
|
|
1043
|
+
- **Configurable timeout.** `KoolbaseConfig.authTimeout` (default 10000ms)
|
|
1044
|
+
sets a per-request timeout via `AbortController`.
|
|
1045
|
+
- **Injectable fetch.** `KoolbaseConfig.fetch` accepts an alternate `fetch`
|
|
1046
|
+
implementation. Useful for testing (mock fetch), corporate proxies, or
|
|
1047
|
+
instrumented HTTP.
|
|
1048
|
+
- **`koolbaseSdkVersion` constant** exported for runtime SDK version
|
|
1049
|
+
introspection.
|
|
1050
|
+
|
|
1051
|
+
### Changed
|
|
1052
|
+
|
|
1053
|
+
- **`logout()` returns `Promise<boolean>`** — `true` if the server-side
|
|
1054
|
+
logout call succeeded, `false` otherwise. Local session is always cleared
|
|
1055
|
+
regardless. Apps that don't care about the server signal can continue to
|
|
1056
|
+
ignore the return value.
|
|
1057
|
+
- **`setSession()` is now async** (returns `Promise<void>`) so storage
|
|
1058
|
+
persistence completes before the call resolves. Source-compatible for
|
|
1059
|
+
callers that ignored the previous void return.
|
|
1060
|
+
- **`register()` validates password length client-side** (must be ≥ 8
|
|
1061
|
+
characters) before hitting the network. Throws `WeakPasswordError`.
|
|
1062
|
+
|
|
1063
|
+
### Deprecated
|
|
1064
|
+
|
|
1065
|
+
- **`KoolbaseAuth.oauthLogin()` and `KoolbaseAppleAuth.signIn()`** now
|
|
1066
|
+
throw `KoolbaseAuthError('not_implemented')`. The earlier implementations
|
|
1067
|
+
routed through `/v1/auth/oauth` — the dashboard developer OAuth endpoint,
|
|
1068
|
+
which never created project-scoped end-user sessions. Apple Sign-In has
|
|
1069
|
+
therefore never actually worked for SDK consumers since it was first
|
|
1070
|
+
introduced. Proper OAuth (Apple, Google, GitHub) will ship in v1.10.0
|
|
1071
|
+
against new server endpoints at `/v1/sdk/auth/oauth/{provider}`. Use
|
|
1072
|
+
email/password authentication in the meantime.
|
|
1073
|
+
|
|
1074
|
+
### Peer dependencies
|
|
1075
|
+
|
|
1076
|
+
- `react-native-keychain >= 8.0.0` (**optional** —
|
|
1077
|
+
`peerDependenciesMeta.optional = true`)
|
|
1078
|
+
|
|
1079
|
+
### Migration
|
|
1080
|
+
|
|
1081
|
+
Most apps work without code changes after upgrading. To opt into
|
|
1082
|
+
persistence, install the peer dependency:
|
|
1083
|
+
|
|
1084
|
+
```bash
|
|
1085
|
+
npm install react-native-keychain
|
|
1086
|
+
cd ios && pod install
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
Then call `restoreSession()` at app launch:
|
|
1090
|
+
|
|
1091
|
+
```typescript
|
|
1092
|
+
useEffect(() => {
|
|
1093
|
+
koolbase.auth.restoreSession().then((result) => {
|
|
1094
|
+
if (result === RestoreResult.Restored) {
|
|
1095
|
+
navigate('Home');
|
|
1096
|
+
} else {
|
|
1097
|
+
navigate('Login');
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
}, []);
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1103
|
+
For apps using Apple Sign-In: temporarily switch to email/password until
|
|
1104
|
+
v1.10.0 ships. The deprecated method now throws explicitly rather than
|
|
1105
|
+
silently failing.
|
|
1106
|
+
|
|
1107
|
+
## 1.8.0
|
|
1108
|
+
|
|
1109
|
+
### Added
|
|
1110
|
+
|
|
1111
|
+
- **Functions:** Authenticated invocations now forward the signed-in user's
|
|
1112
|
+
session automatically.
|
|
1113
|
+
- When a user is signed in via `Koolbase.auth`, calls to
|
|
1114
|
+
`Koolbase.functions.invoke()` include their access token in the
|
|
1115
|
+
request.
|
|
1116
|
+
- Functions receive caller identity via `ctx.auth` — an object with
|
|
1117
|
+
`user_id` (string or null) and `is_authenticated` (boolean).
|
|
1118
|
+
- Unauthenticated invokes continue to work; Functions decide whether
|
|
1119
|
+
they require auth and respond with `AUTH_REQUIRED` if needed.
|
|
1120
|
+
- Token refresh is handled transparently — the next invoke after a
|
|
1121
|
+
refresh uses the fresh token without any client-side wiring.
|
|
1122
|
+
|
|
1123
|
+
Backwards compatible: no breaking changes. Existing code paths continue
|
|
1124
|
+
to work.
|
|
1125
|
+
|
|
1126
|
+
## 1.7.0
|
|
1127
|
+
|
|
1128
|
+
### Added — Phone + OTP authentication
|
|
1129
|
+
|
|
1130
|
+
Sign users in with their phone number — for emerging markets and apps
|
|
1131
|
+
where email isn't the primary identifier.
|
|
1132
|
+
|
|
1133
|
+
New methods on `Koolbase.auth`:
|
|
1134
|
+
|
|
1135
|
+
- `sendOtp({ phoneNumber })` — sends a 6-digit OTP to an E.164 phone
|
|
1136
|
+
number, returns the expiry timestamp.
|
|
1137
|
+
- `verifyOtp({ phoneNumber, code })` — verifies the code and signs the
|
|
1138
|
+
user in (creates the account if new). Returns `PhoneVerifyResult` with
|
|
1139
|
+
an `isNewUser` flag for routing first-time users to onboarding.
|
|
1140
|
+
- `linkPhone({ phoneNumber, code })` — links a phone number to an
|
|
1141
|
+
already-authenticated user.
|
|
1142
|
+
|
|
1143
|
+
New types: `OtpSendResult`, `PhoneVerifyResult`, `SendOtpParams`,
|
|
1144
|
+
`VerifyOtpParams`, `LinkPhoneParams`.
|
|
1145
|
+
|
|
1146
|
+
`KoolbaseUser` now exposes `phoneNumber` and `phoneVerified` fields.
|
|
1147
|
+
|
|
1148
|
+
New errors (all extend `KoolbaseAuthError`): `InvalidPhoneNumberError`,
|
|
1149
|
+
`OtpExpiredError`, `OtpInvalidError`, `OtpMaxAttemptsError`,
|
|
1150
|
+
`OtpRateLimitError`, `PhoneAlreadyLinkedError`, `SmsConfigMissingError`.
|
|
1151
|
+
|
|
1152
|
+
Phone numbers must be in E.164 format (e.g. `+233244000000`). Configure
|
|
1153
|
+
your SMS provider (Twilio, Africa's Talking, or Hubtel) in the Koolbase
|
|
1154
|
+
dashboard before using.
|
|
1155
|
+
|
|
1156
|
+
## 1.6.1
|
|
1157
|
+
|
|
1158
|
+
### Changed
|
|
1159
|
+
|
|
1160
|
+
- README update — Logic Engine v2 operators.
|
|
1161
|
+
|
|
1162
|
+
## 1.6.0
|
|
1163
|
+
|
|
1164
|
+
### Added — Logic Engine v2
|
|
1165
|
+
|
|
1166
|
+
Richer conditions with new operators:
|
|
1167
|
+
|
|
1168
|
+
- `gte` — greater than or equals
|
|
1169
|
+
- `lte` — less than or equals
|
|
1170
|
+
- `contains` — string or list contains value
|
|
1171
|
+
- `starts_with` — string starts with
|
|
1172
|
+
- `ends_with` — string ends with
|
|
1173
|
+
- `in_list` — value is in a list
|
|
1174
|
+
- `not_in_list` — value is not in a list
|
|
1175
|
+
- `between` — numeric value in range `[min, max]`
|
|
1176
|
+
- `is_true` — value is boolean true
|
|
1177
|
+
- `is_false` — value is boolean false
|
|
1178
|
+
- `not_exists` — value is null or missing
|
|
1179
|
+
|
|
1180
|
+
All operators work with AND/OR condition groups.
|
|
1181
|
+
|
|
1182
|
+
## 1.5.0
|
|
1183
|
+
|
|
1184
|
+
### Added — Sign in with Apple
|
|
1185
|
+
|
|
1186
|
+
> **Note:** This functionality is deprecated as of v1.9.0 — it never
|
|
1187
|
+
> created project-scoped end-user sessions. See the v1.9.0 entry above.
|
|
1188
|
+
|
|
1189
|
+
- Added `KoolbaseAppleAuth.signIn()` — Sign in with Apple for React Native
|
|
1190
|
+
- Added `KoolbaseAuth.oauthLogin()` — unified OAuth login method
|
|
1191
|
+
- Apple identity token verified server-side using Apple's JWKS endpoint
|
|
1192
|
+
- Works with any Apple credential provider (bring your own apple-auth
|
|
1193
|
+
library)
|
|
1194
|
+
|
|
1195
|
+
#### Usage
|
|
1196
|
+
|
|
1197
|
+
```typescript
|
|
1198
|
+
import { KoolbaseAppleAuth } from 'koolbase-react-native';
|
|
1199
|
+
|
|
1200
|
+
const session = await KoolbaseAppleAuth.signIn(async () => {
|
|
1201
|
+
const credential = await appleAuth.performRequest({
|
|
1202
|
+
requestedOperation: appleAuth.Operation.LOGIN,
|
|
1203
|
+
requestedScopes: [appleAuth.Scope.EMAIL, appleAuth.Scope.FULL_NAME],
|
|
1204
|
+
});
|
|
1205
|
+
return credential;
|
|
1206
|
+
});
|
|
1207
|
+
```
|
|
1208
|
+
|
|
1209
|
+
#### Setup required
|
|
1210
|
+
|
|
1211
|
+
Install `@invertase/react-native-apple-authentication` and configure your
|
|
1212
|
+
App ID in the Apple Developer portal.
|
|
1213
|
+
|
|
1214
|
+
## 1.4.0
|
|
1215
|
+
|
|
1216
|
+
### Added — Koolbase Cloud Messaging
|
|
1217
|
+
|
|
1218
|
+
- Added `KoolbaseMessaging` — push notification delivery via FCM.
|
|
1219
|
+
- Added `Koolbase.messaging.registerToken({ token, platform, userId? })`
|
|
1220
|
+
— register FCM device token.
|
|
1221
|
+
- Added `Koolbase.messaging.send({ to, title, body, data? })` — send push
|
|
1222
|
+
notification to a specific device.
|
|
1223
|
+
- `KoolbaseConfig` extended with `messagingEnabled` parameter (default
|
|
1224
|
+
`true`).
|
|
1225
|
+
- Device ID automatically reused from analytics stable device ID
|
|
1226
|
+
(AsyncStorage).
|
|
1227
|
+
|
|
1228
|
+
#### Usage
|
|
1229
|
+
|
|
1230
|
+
```typescript
|
|
1231
|
+
// After obtaining FCM token from @react-native-firebase/messaging
|
|
1232
|
+
const fcmToken = await messaging().getToken();
|
|
1233
|
+
await Koolbase.messaging.registerToken({
|
|
1234
|
+
token: fcmToken,
|
|
1235
|
+
platform: 'android', // or 'ios'
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
// Send to a specific device
|
|
1239
|
+
await Koolbase.messaging.send({
|
|
1240
|
+
to: deviceToken,
|
|
1241
|
+
title: 'Your order is ready',
|
|
1242
|
+
body: 'Pick up at counter 3',
|
|
1243
|
+
data: { order_id: '123' },
|
|
1244
|
+
});
|
|
1245
|
+
```
|
|
1246
|
+
|
|
1247
|
+
#### Setup required
|
|
1248
|
+
|
|
1249
|
+
Add your FCM server key as a project secret named `FCM_SERVER_KEY` in the
|
|
1250
|
+
Koolbase dashboard.
|
|
1251
|
+
|
|
1252
|
+
## 1.3.1
|
|
1253
|
+
|
|
1254
|
+
### Changed
|
|
1255
|
+
|
|
1256
|
+
- Updated README — added Code Push, Analytics, Logic Engine sections,
|
|
1257
|
+
clearer get started guide.
|
|
1258
|
+
|
|
1259
|
+
## 1.3.0
|
|
1260
|
+
|
|
1261
|
+
### Added — Analytics
|
|
1262
|
+
|
|
1263
|
+
- Added `KoolbaseAnalytics` — event tracking with batched flush.
|
|
1264
|
+
- Added `Koolbase.analytics` — top-level accessor.
|
|
1265
|
+
- Added `Koolbase.analytics.track(eventName, properties)` — custom event
|
|
1266
|
+
tracking.
|
|
1267
|
+
- Added `Koolbase.analytics.screenView(screenName, properties)` — screen
|
|
1268
|
+
view tracking.
|
|
1269
|
+
- Added `Koolbase.analytics.identify(userId)` — attach authenticated user.
|
|
1270
|
+
- Added `Koolbase.analytics.setUserProperty(key, value)` — user property.
|
|
1271
|
+
- Added `Koolbase.analytics.setUserProperties(map)` — bulk user
|
|
1272
|
+
properties.
|
|
1273
|
+
- Added `Koolbase.analytics.reset()` — clear identity on logout.
|
|
1274
|
+
- Added `Koolbase.analytics.flush()` — manual flush.
|
|
1275
|
+
- Added `Koolbase.analytics.dispose()` — flush and shut down.
|
|
1276
|
+
- Auto events: `app_open`, `screen_view`, `session_end`.
|
|
1277
|
+
- Batch flush: every 30s, on app background, on close, or at 20 events.
|
|
1278
|
+
- Anonymous by default (stable `device_id` via AsyncStorage), attach
|
|
1279
|
+
`user_id` on `identify()`.
|
|
1280
|
+
- `KoolbaseConfig` extended with `analyticsEnabled` and `appVersion`
|
|
1281
|
+
parameters.
|
|
1282
|
+
|
|
1283
|
+
### Added — Logic Engine v1
|
|
1284
|
+
|
|
1285
|
+
- Added `Koolbase.executeFlow(flowId, context)` — evaluate named flow
|
|
1286
|
+
from active bundle.
|
|
1287
|
+
- Added `KoolbaseLogicEngine` — safe, deterministic flow evaluator.
|
|
1288
|
+
- Supported node types: `if`, `sequence`, `event` (terminal), `set`.
|
|
1289
|
+
- Supported operators: `eq`, `neq`, `gt`, `lt`, `and`, `or`, `exists`.
|
|
1290
|
+
- Supported data sources: `context` (app-provided), `config` (bundle),
|
|
1291
|
+
`flags` (bundle).
|
|
1292
|
+
- `BundlePayload` extended with `flows` and `screens` fields.
|
|
1293
|
+
- Never throws — returns safe `FlowResult` on any error.
|
|
1294
|
+
|
|
1295
|
+
#### Usage
|
|
1296
|
+
|
|
1297
|
+
```typescript
|
|
1298
|
+
// Analytics
|
|
1299
|
+
await Koolbase.initialize({
|
|
1300
|
+
publicKey: 'pk_live_xxx',
|
|
1301
|
+
baseUrl: 'https://api.koolbase.com',
|
|
1302
|
+
appVersion: '1.0.0',
|
|
1303
|
+
analyticsEnabled: true,
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
Koolbase.analytics.track('purchase', { value: 1200, currency: 'GHS' });
|
|
1307
|
+
Koolbase.analytics.screenView('checkout');
|
|
1308
|
+
Koolbase.analytics.identify(user.id);
|
|
1309
|
+
Koolbase.analytics.setUserProperty('plan', 'pro');
|
|
1310
|
+
|
|
1311
|
+
// Logic Engine
|
|
1312
|
+
const result = Koolbase.executeFlow('on_checkout_tap', { plan: user.plan });
|
|
1313
|
+
if (result.hasEvent) navigation.navigate(result.eventName!);
|
|
1314
|
+
```
|
|
1315
|
+
|
|
1316
|
+
## 1.1.0
|
|
1317
|
+
|
|
1318
|
+
### Added — Offline-first database
|
|
1319
|
+
|
|
1320
|
+
- Database: offline-first support powered by AsyncStorage.
|
|
1321
|
+
- Cache-first reads — returns local data instantly, refreshes from
|
|
1322
|
+
network in background.
|
|
1323
|
+
- Optimistic writes — inserts saved locally first, synced when online.
|
|
1324
|
+
- Auto-sync on network reconnect via NetInfo.
|
|
1325
|
+
- `Koolbase.db.syncPendingWrites()` — manually trigger sync.
|
|
1326
|
+
- `QueryResult.isFromCache` flag — know whether data came from cache or
|
|
1327
|
+
network.
|
|
1328
|
+
- Write queue with max 3 retries before dropping failed writes.
|
|
1329
|
+
- User-scoped cache — no cross-user data leakage on shared devices.
|
|
1330
|
+
- `PendingWrite` type exported from package.
|
|
1331
|
+
|
|
1332
|
+
## 1.0.0
|
|
1333
|
+
|
|
1334
|
+
### Initial release
|
|
1335
|
+
|
|
1336
|
+
- Auth — register, login, logout, current user.
|
|
1337
|
+
- Database — insert, query, get, update, delete, populate.
|
|
1338
|
+
- Storage — upload, download, delete.
|
|
1339
|
+
- Realtime — WebSocket subscriptions.
|
|
1340
|
+
- Functions — invoke deployed functions.
|
|
1341
|
+
- Feature flags and remote config.
|
|
1342
|
+
- Version enforcement.
|