@koolbase/react-native 9.0.0 → 9.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.
@@ -1,3 +1,4 @@
1
+ import { KoolbaseError } from './errors';
1
2
  /**
2
3
  * Base class for errors surfaced by the Koolbase data layer (database reads
3
4
  * and writes). Every data error carries a `message` and, when the server
@@ -7,8 +8,13 @@
7
8
  * Catch this to handle any data-layer failure generically, or catch a
8
9
  * specific subclass to branch on the kind of failure.
9
10
  */
10
- export declare class KoolbaseDataError extends Error {
11
- code?: string;
11
+ export declare class KoolbaseDataError extends KoolbaseError {
12
+ /**
13
+ * Structured payload from the server's error body, when it sent one — e.g. a
14
+ * revision_mismatch 409 carries {expected_revision, current_revision,
15
+ * record}. Attached by the factory; absent when the body had none.
16
+ */
17
+ details?: Record<string, unknown>;
12
18
  constructor(message: string, code?: string);
13
19
  }
14
20
  /**
@@ -92,4 +98,4 @@ export declare class KoolbaseVectorDimensionMismatchError extends KoolbaseDataEr
92
98
  * preferring the server's stable `code` and falling back to the HTTP status
93
99
  * for older or uncoded responses. Always returns an error to throw.
94
100
  */
95
- export declare function koolbaseDataError(status: number, body: any, fallbackMessage?: string): KoolbaseDataError;
101
+ export declare function koolbaseDataError(status: number, body: any, fallbackMessage?: string): KoolbaseError;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.KoolbaseVectorDimensionMismatchError = exports.KoolbaseRateLimitError = exports.KoolbasePermissionError = exports.KoolbaseValidationError = exports.KoolbaseNotFoundError = exports.KoolbaseConflictError = exports.KoolbaseDataError = void 0;
4
4
  exports.koolbaseDataError = koolbaseDataError;
5
+ const errors_1 = require("./errors");
5
6
  /**
6
7
  * Base class for errors surfaced by the Koolbase data layer (database reads
7
8
  * and writes). Every data error carries a `message` and, when the server
@@ -11,10 +12,9 @@ exports.koolbaseDataError = koolbaseDataError;
11
12
  * Catch this to handle any data-layer failure generically, or catch a
12
13
  * specific subclass to branch on the kind of failure.
13
14
  */
14
- class KoolbaseDataError extends Error {
15
+ class KoolbaseDataError extends errors_1.KoolbaseError {
15
16
  constructor(message, code) {
16
- super(message);
17
- this.code = code;
17
+ super(message, code);
18
18
  this.name = 'KoolbaseDataError';
19
19
  Object.setPrototypeOf(this, KoolbaseDataError.prototype);
20
20
  }
@@ -135,39 +135,66 @@ function koolbaseDataError(status, body, fallbackMessage = 'Request failed') {
135
135
  const code = body?.code;
136
136
  const message = body?.error ?? fallbackMessage;
137
137
  const field = body?.details?.field;
138
+ const attach = (err) => {
139
+ // The body's structured details ride along on data errors — a
140
+ // revision_mismatch 409 carries the current revision and record, and
141
+ // discarding them here is how a refused conflict-resolution became
142
+ // permanently unresolvable: the information arrived and died in this file.
143
+ if (err instanceof KoolbaseDataError && body?.details) {
144
+ err.details = body.details;
145
+ }
146
+ return err;
147
+ };
148
+ // Status-first for auth: a 401 means the credentials were not accepted,
149
+ // whatever code the body claims. Trusting a mislabelled body here bypasses
150
+ // session-clearing and strands the app signed-in with dead credentials.
151
+ if (status === 401) {
152
+ return new errors_1.KoolbaseUnauthenticatedError(message);
153
+ }
138
154
  // ─── code-first ───
139
155
  switch (code) {
140
156
  case 'unique_violation':
141
- return new KoolbaseConflictError(message, field);
157
+ return attach(new KoolbaseConflictError(message, field));
142
158
  case 'not_found':
143
159
  case 'record_not_found':
144
160
  case 'collection_not_found':
145
161
  case 'vector_not_found':
146
162
  case 'vector_field_not_found':
147
- return new KoolbaseNotFoundError(message);
163
+ return attach(new KoolbaseNotFoundError(message));
164
+ case 'unauthenticated':
165
+ case 'session_expired':
166
+ case 'invalid_token':
167
+ return attach(new errors_1.KoolbaseUnauthenticatedError(message));
148
168
  case 'permission_denied':
149
- return new KoolbasePermissionError(message);
169
+ return attach(new KoolbasePermissionError(message));
150
170
  case 'rate_limit':
151
- return new KoolbaseRateLimitError(message);
171
+ return attach(new KoolbaseRateLimitError(message));
152
172
  case 'validation_error':
153
173
  case 'vector_collection_mismatch':
154
174
  case 'unsupported_dimension':
155
- return new KoolbaseValidationError(message);
175
+ return attach(new KoolbaseValidationError(message));
156
176
  case 'vector_dimension_mismatch':
157
- return new KoolbaseVectorDimensionMismatchError(message);
177
+ return attach(new KoolbaseVectorDimensionMismatchError(message));
158
178
  }
159
179
  // ─── status fallback (pre-code servers) ───
160
180
  switch (status) {
161
181
  case 409:
162
- return new KoolbaseConflictError(message);
182
+ return attach(new KoolbaseConflictError(message));
163
183
  case 404:
164
- return new KoolbaseNotFoundError(message);
184
+ return attach(new KoolbaseNotFoundError(message));
185
+ case 401:
186
+ // The status carries the meaning: every 401 from this server reports the
187
+ // same code, so it cannot say whether the session expired, the key was
188
+ // revoked, or the header was malformed. Safe to treat uniformly because a
189
+ // permission failure is 403 — a 401 means the credentials were not
190
+ // accepted, not that this caller may not proceed.
191
+ return attach(new errors_1.KoolbaseUnauthenticatedError(message));
165
192
  case 403:
166
- return new KoolbasePermissionError(message);
193
+ return attach(new KoolbasePermissionError(message));
167
194
  case 429:
168
- return new KoolbaseRateLimitError(message);
195
+ return attach(new KoolbaseRateLimitError(message));
169
196
  case 400:
170
- return new KoolbaseValidationError(message);
197
+ return attach(new KoolbaseValidationError(message));
171
198
  }
172
- return new KoolbaseDataError(message, code);
199
+ return attach(new KoolbaseDataError(message, code));
173
200
  }
@@ -1,13 +1,50 @@
1
+ import { KoolbaseConflict } from './conflict';
2
+ import { PendingWrite } from './pending-write';
1
3
  import { KoolbaseConfig, KoolbaseRecord, QueryOptions, QueryResult, UpsertResult, BatchOp, BatchResult, KoolbaseVector, SemanticSearchResult, SearchMode } from './types';
2
4
  export declare class KoolbaseDatabase {
3
5
  private config;
4
6
  private getUserId;
5
7
  private getToken;
8
+ /**
9
+ * Called when the server rejects the caller's credentials.
10
+ *
11
+ * A session stops working for the whole SDK at once, so it is cleared before
12
+ * the error reaches the caller — otherwise the app keeps believing it is
13
+ * signed in and every subsequent call fails the same way, with no path back
14
+ * to login.
15
+ */
16
+ private onSessionExpired?;
6
17
  private syncEngine;
7
- constructor(config: KoolbaseConfig, getUserId: () => string | null, getToken: () => Promise<string | null>);
18
+ constructor(config: KoolbaseConfig, getUserId: () => string | null, getToken: () => Promise<string | null>, onSessionExpired?: () => Promise<void>);
8
19
  private buildHeaders;
9
20
  private request;
21
+ /**
22
+ * Like [request], but returns the status alongside the body.
23
+ *
24
+ * Several operations need it — upsert distinguishes create from update by a
25
+ * 201, batch reports per-operation outcomes — and needing it was why they
26
+ * hand-rolled their own fetch, each mapping errors slightly differently and
27
+ * none of them clearing a rejected session. One path, two shapes of result.
28
+ */
29
+ private requestWithStatus;
10
30
  private runQuery;
31
+ /**
32
+ * Query records, cache-first (stale-while-revalidate).
33
+ *
34
+ * A cache hit is returned immediately with `isFromCache: true`, and a
35
+ * background refresh updates the cache for the next call — so a repeat
36
+ * query converges on the server's state one call behind it. Only a cache
37
+ * miss awaits the network (`isFromCache: false`).
38
+ *
39
+ * Two consequences worth designing for: results can be one refresh stale,
40
+ * even online — re-query if you need convergence after a known write; and
41
+ * background refresh failures are swallowed by design (the cached result
42
+ * has already been returned), so a dead network looks identical to a slow
43
+ * refresh. Check `isFromCache` when the difference matters.
44
+ *
45
+ * The cache is per-user and persisted; it doubles as the offline baseline
46
+ * store for `update`/`delete`.
47
+ */
11
48
  query(collection: string, options?: QueryOptions): Promise<QueryResult>;
12
49
  /**
13
50
  * Insert a new record into a collection.
@@ -75,6 +112,46 @@ export declare class KoolbaseDatabase {
75
112
  */
76
113
  batch(operations: BatchOp[]): Promise<BatchResult[]>;
77
114
  get(recordId: string): Promise<KoolbaseRecord>;
115
+ /**
116
+ * Writes that could not be applied, waiting for a decision.
117
+ *
118
+ * Held rather than discarded, and surviving restarts. An app that never reads
119
+ * these accumulates them invisibly, with the changes they hold never applied —
120
+ * so if you support offline editing, surface them somewhere.
121
+ */
122
+ /**
123
+ * Changes made offline, waiting to be sent. Oldest first.
124
+ *
125
+ * For sync indicators ("3 changes waiting") and for warning a user who is
126
+ * about to log out with unsynced edits — see [PendingWrite] for why that
127
+ * moment matters. Snapshot, not a live handle; per-user.
128
+ */
129
+ pendingWrites(): Promise<PendingWrite[]>;
130
+ conflicts(): Promise<KoolbaseConflict[]>;
131
+ /**
132
+ * Resolves by id, reloading the stored conflict first.
133
+ *
134
+ * A conflict object handed to a UI can sit there while someone decides, and a
135
+ * sync pass may resolve it or another write supersede it meanwhile. Acting on
136
+ * values captured when the object was built would write against a state that
137
+ * no longer exists.
138
+ */
139
+ private readonly conflictResolver;
140
+ /**
141
+ * Per-user state demands a user. Signed out, "no answer" must not be
142
+ * disguised as "empty" — tonight's fake-zero: the display read the anonymous
143
+ * bucket while a signed-in user's writes sat unseen in theirs.
144
+ */
145
+ private requireUserId;
146
+ private requireConflict;
147
+ private dropConflict;
148
+ /**
149
+ * Issues the resolving write, conditional on the revision the refusal
150
+ * reported, and clears the conflict only once the server accepts it.
151
+ *
152
+ * Clearing first would lose the change if the write then failed.
153
+ */
154
+ private applyResolution;
78
155
  /**
79
156
  * Update a record's fields by id.
80
157
  *
@@ -88,6 +165,19 @@ export declare class KoolbaseDatabase {
88
165
  * optimistic record is returned so the UI can re-render the new fields
89
166
  * immediately.
90
167
  */
168
+ /**
169
+ * The record's state as the SDK last knew it, for composing an offline
170
+ * mutation against.
171
+ *
172
+ * Two sources, in order. A record created offline is not in the cache as a
173
+ * server record, but its queued insert holds the state a later edit builds on
174
+ * — insert-then-correct is the ordinary offline sequence. Otherwise the cached
175
+ * copy, with the revision it was read at.
176
+ *
177
+ * Null when neither exists: never seen on this device, or a queued delete has
178
+ * already removed it locally.
179
+ */
180
+ private resolveBaseline;
91
181
  update(recordId: string, data: Record<string, unknown>): Promise<KoolbaseRecord>;
92
182
  delete(recordId: string): Promise<void>;
93
183
  /**