@koolbase/core 12.7.0 → 12.8.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.
@@ -207,8 +207,19 @@ export declare class KoolbaseDatabase {
207
207
  * already removed it locally.
208
208
  */
209
209
  private resolveBaseline;
210
- update(recordId: string, data: Record<string, unknown>): Promise<KoolbaseRecord>;
211
- delete(recordId: string): Promise<void>;
210
+ update(recordId: string, data: Record<string, unknown>,
211
+ /** expectedRevision: apply only if the record still carries it; refused
212
+ * with KoolbaseRevisionMismatchError otherwise (as the Flutter SDK). */
213
+ options?: {
214
+ expectedRevision?: number;
215
+ }): Promise<KoolbaseRecord>;
216
+ delete(recordId: string,
217
+ /** expectedRevision: delete only if the record still carries it (as the
218
+ * Flutter SDK) -- removing a record someone changed a moment ago is the
219
+ * more destructive stale write. */
220
+ options?: {
221
+ expectedRevision?: number;
222
+ }): Promise<void>;
212
223
  /**
213
224
  * Write (or replace) a vector for a record on the named `field`.
214
225
  *
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.KoolbaseDatabase = void 0;
4
4
  const network_js_1 = require("./network.js");
5
5
  const query_refresh_js_1 = require("./query-refresh.js");
6
+ const database_errors_js_1 = require("./database-errors.js");
6
7
  const errors_js_1 = require("./errors.js");
7
8
  const cache_store_js_1 = require("./cache-store.js");
8
9
  const offline_state_js_1 = require("./offline-state.js");
@@ -11,7 +12,7 @@ const pending_write_js_1 = require("./pending-write.js");
11
12
  const cache_store_js_2 = require("./cache-store.js");
12
13
  const sync_engine_js_1 = require("./sync-engine.js");
13
14
  const record_js_1 = require("./record.js");
14
- const database_errors_js_1 = require("./database-errors.js");
15
+ const database_errors_js_2 = require("./database-errors.js");
15
16
  function generateWriteId() {
16
17
  return 'local_' + Math.random().toString(36).slice(2) + Date.now().toString(36);
17
18
  }
@@ -111,7 +112,7 @@ class KoolbaseDatabase {
111
112
  }
112
113
  }
113
114
  if (!res.ok) {
114
- const err = (0, database_errors_js_1.koolbaseDataError)(res.status, data ?? {}, `Request failed: ${res.status}`);
115
+ const err = (0, database_errors_js_2.koolbaseDataError)(res.status, data ?? {}, `Request failed: ${res.status}`);
115
116
  if (err instanceof errors_js_1.KoolbaseUnauthenticatedError) {
116
117
  await this.onSessionExpired?.();
117
118
  }
@@ -145,7 +146,7 @@ class KoolbaseDatabase {
145
146
  }
146
147
  }
147
148
  if (!res.ok) {
148
- const err = (0, database_errors_js_1.koolbaseDataError)(res.status, data ?? {}, `Request failed: ${res.status}`);
149
+ const err = (0, database_errors_js_2.koolbaseDataError)(res.status, data ?? {}, `Request failed: ${res.status}`);
149
150
  if (err instanceof errors_js_1.KoolbaseUnauthenticatedError) {
150
151
  await this.onSessionExpired?.();
151
152
  }
@@ -230,7 +231,7 @@ class KoolbaseDatabase {
230
231
  */
231
232
  async invalidate(collection) {
232
233
  await (0, cache_store_js_2.invalidateCache)(this.getUserId() ?? 'anonymous', collection);
233
- await (0, query_refresh_js_1.refreshCollectionQueries)(collection);
234
+ void (0, query_refresh_js_1.refreshCollectionQueries)(collection);
234
235
  }
235
236
  // ─── Insert (online-first with offline fallback) ───────────────────────────
236
237
  /**
@@ -254,7 +255,7 @@ class KoolbaseDatabase {
254
255
  const raw = await this.request('POST', '/v1/sdk/db/insert', { collection, data });
255
256
  const record = (0, record_js_1.recordFromWire)(raw);
256
257
  await (0, cache_store_js_2.invalidateCache)(userId, collection);
257
- await (0, query_refresh_js_1.refreshCollectionQueries)(collection);
258
+ void (0, query_refresh_js_1.refreshCollectionQueries)(collection);
258
259
  // The response carries a fresh revision, so caching it keeps the
259
260
  // baseline current for whatever edits this record next.
260
261
  await (0, cache_store_js_1.cacheRecord)(userId, collection, record.id, record.data, record.revision);
@@ -329,7 +330,7 @@ class KoolbaseDatabase {
329
330
  // Keep the cache fresh, same intent as insert's post-success invalidate.
330
331
  const userId = this.getUserId() ?? 'anonymous';
331
332
  await (0, cache_store_js_2.invalidateCache)(userId, collection);
332
- await (0, query_refresh_js_1.refreshCollectionQueries)(collection);
333
+ void (0, query_refresh_js_1.refreshCollectionQueries)(collection);
333
334
  await (0, cache_store_js_1.cacheRecord)(userId, collection, record.id, record.data, record.revision);
334
335
  return { record, created };
335
336
  }
@@ -349,7 +350,7 @@ class KoolbaseDatabase {
349
350
  const body = await this.request('POST', '/v1/sdk/db/delete-where', { collection, filters });
350
351
  const userId = this.getUserId() ?? 'anonymous';
351
352
  await (0, cache_store_js_2.invalidateCache)(userId, collection);
352
- await (0, query_refresh_js_1.refreshCollectionQueries)(collection);
353
+ void (0, query_refresh_js_1.refreshCollectionQueries)(collection);
353
354
  return body.deleted ?? 0;
354
355
  }
355
356
  // ─── Batch (atomic, online-only) ────────────────────────────────────────────
@@ -413,7 +414,7 @@ class KoolbaseDatabase {
413
414
  }
414
415
  for (const col of touched) {
415
416
  await (0, cache_store_js_2.invalidateCache)(userId, col);
416
- await (0, query_refresh_js_1.refreshCollectionQueries)(col);
417
+ void (0, query_refresh_js_1.refreshCollectionQueries)(col);
417
418
  }
418
419
  return results;
419
420
  }
@@ -535,7 +536,7 @@ class KoolbaseDatabase {
535
536
  const { conflicts } = await (0, offline_state_js_1.readOfflineState)(userId);
536
537
  const found = conflicts.find((c) => c.id === id);
537
538
  if (!found) {
538
- throw new database_errors_js_1.KoolbaseDataError('That conflict is no longer outstanding — it may already have been resolved.', 'conflict_not_found');
539
+ throw new database_errors_js_2.KoolbaseDataError('That conflict is no longer outstanding — it may already have been resolved.', 'conflict_not_found');
539
540
  }
540
541
  return found;
541
542
  }
@@ -584,7 +585,7 @@ class KoolbaseDatabase {
584
585
  // retry replays the stale condition and a conflict whose resolution
585
586
  // fails once is permanently unresolvable except by abandon —
586
587
  // device-proven: three identical refusals against an unchanged server.
587
- const details = e instanceof database_errors_js_1.KoolbaseDataError ? e.details : undefined;
588
+ const details = e instanceof database_errors_js_2.KoolbaseDataError ? e.details : undefined;
588
589
  const current = details?.current_revision;
589
590
  const record = details?.record;
590
591
  if (typeof current === 'number') {
@@ -598,14 +599,14 @@ class KoolbaseDatabase {
598
599
  if (record)
599
600
  stored.server = record;
600
601
  });
601
- throw new database_errors_js_1.KoolbaseDataError('The record has changed again while deciding. The conflict now ' +
602
+ throw new database_errors_js_2.KoolbaseDataError('The record has changed again while deciding. The conflict now ' +
602
603
  'reflects the server\'s current state — review and retry.', 'revision_mismatch');
603
604
  }
604
605
  throw e;
605
606
  }
606
607
  await this.dropConflict(c.id);
607
608
  await (0, cache_store_js_2.invalidateCache)(this.getUserId() ?? 'anonymous', c.collection);
608
- await (0, query_refresh_js_1.refreshCollectionQueries)(c.collection);
609
+ void (0, query_refresh_js_1.refreshCollectionQueries)(c.collection);
609
610
  }
610
611
  // ─── Update (online-first with offline fallback) ───────────────────────────
611
612
  /**
@@ -662,12 +663,15 @@ class KoolbaseDatabase {
662
663
  return null;
663
664
  return { baseline: cached.data, revision: cached.revision, collection: cached.collection };
664
665
  }
665
- async update(recordId, data) {
666
+ async update(recordId, data,
667
+ /** expectedRevision: apply only if the record still carries it; refused
668
+ * with KoolbaseRevisionMismatchError otherwise (as the Flutter SDK). */
669
+ options = {}) {
666
670
  const userId = this.getUserId() ?? 'anonymous';
667
671
  // Resolved before the request, so a network failure has somewhere to go.
668
672
  const base = await this.resolveBaseline(userId, recordId);
669
673
  try {
670
- const raw = await this.request('PATCH', `/v1/sdk/db/records/${recordId}`, { data });
674
+ const raw = await this.request('PATCH', `/v1/sdk/db/records/${recordId}`, { data, ...(options.expectedRevision !== undefined ? { expected_revision: options.expectedRevision } : {}) });
671
675
  const updated = (0, record_js_1.recordFromWire)(raw);
672
676
  // Its collection's live queries re-run (every one, if the record's
673
677
  // collection is not known).
@@ -675,7 +679,7 @@ class KoolbaseDatabase {
675
679
  if (updated.collection) {
676
680
  await (0, cache_store_js_1.cacheRecord)(userId, updated.collection, updated.id, updated.data, updated.revision);
677
681
  }
678
- await (0, query_refresh_js_1.refreshCollectionQueries)(refreshed);
682
+ void (0, query_refresh_js_1.refreshCollectionQueries)(refreshed);
679
683
  return updated;
680
684
  }
681
685
  catch (e) {
@@ -685,6 +689,10 @@ class KoolbaseDatabase {
685
689
  // credential — must not be queued: it will be refused again on every
686
690
  // retry. Checked against the root rather than the data family, because a
687
691
  // rejected credential belongs to no single surface.
692
+ // Refused as stale: the server holds newer data, so show it -- the
693
+ // collection's live queries re-run in the background (as Flutter).
694
+ if (e instanceof database_errors_js_1.KoolbaseRevisionMismatchError)
695
+ void (0, query_refresh_js_1.refreshCollectionQueries)(base?.collection);
688
696
  if (e instanceof errors_js_1.KoolbaseError)
689
697
  throw e;
690
698
  // Genuine network failure. Queueable only if the SDK knows what the
@@ -716,15 +724,19 @@ class KoolbaseDatabase {
716
724
  }
717
725
  }
718
726
  // ─── Delete ─────────────────────────────────────────────────────────────────
719
- async delete(recordId) {
727
+ async delete(recordId,
728
+ /** expectedRevision: delete only if the record still carries it (as the
729
+ * Flutter SDK) -- removing a record someone changed a moment ago is the
730
+ * more destructive stale write. */
731
+ options = {}) {
720
732
  const userId = this.getUserId() ?? 'anonymous';
721
733
  const base = await this.resolveBaseline(userId, recordId);
722
734
  try {
723
- await this.request('DELETE', `/v1/sdk/db/records/${recordId}`);
735
+ await this.request('DELETE', `/v1/sdk/db/records/${recordId}${options.expectedRevision !== undefined ? `?expected_revision=${options.expectedRevision}` : ''}`);
724
736
  await (0, cache_store_js_1.removeCachedRecord)(userId, recordId);
725
737
  // Its collection is known from the baseline when the record was seen;
726
738
  // otherwise every live query re-runs, rather than a guess.
727
- await (0, query_refresh_js_1.refreshCollectionQueries)(base?.collection);
739
+ void (0, query_refresh_js_1.refreshCollectionQueries)(base?.collection);
728
740
  }
729
741
  catch (e) {
730
742
  // A server that answered has refused: a permission denial or a missing
@@ -735,6 +747,10 @@ class KoolbaseDatabase {
735
747
  // credential — must not be queued: it will be refused again on every
736
748
  // retry. Checked against the root rather than the data family, because a
737
749
  // rejected credential belongs to no single surface.
750
+ // Refused as stale: the server holds newer data, so show it -- the
751
+ // collection's live queries re-run in the background (as Flutter).
752
+ if (e instanceof database_errors_js_1.KoolbaseRevisionMismatchError)
753
+ void (0, query_refresh_js_1.refreshCollectionQueries)(base?.collection);
738
754
  if (e instanceof errors_js_1.KoolbaseError)
739
755
  throw e;
740
756
  // Genuine network failure. Queued here rather than before the request,
@@ -207,8 +207,19 @@ export declare class KoolbaseDatabase {
207
207
  * already removed it locally.
208
208
  */
209
209
  private resolveBaseline;
210
- update(recordId: string, data: Record<string, unknown>): Promise<KoolbaseRecord>;
211
- delete(recordId: string): Promise<void>;
210
+ update(recordId: string, data: Record<string, unknown>,
211
+ /** expectedRevision: apply only if the record still carries it; refused
212
+ * with KoolbaseRevisionMismatchError otherwise (as the Flutter SDK). */
213
+ options?: {
214
+ expectedRevision?: number;
215
+ }): Promise<KoolbaseRecord>;
216
+ delete(recordId: string,
217
+ /** expectedRevision: delete only if the record still carries it (as the
218
+ * Flutter SDK) -- removing a record someone changed a moment ago is the
219
+ * more destructive stale write. */
220
+ options?: {
221
+ expectedRevision?: number;
222
+ }): Promise<void>;
212
223
  /**
213
224
  * Write (or replace) a vector for a record on the named `field`.
214
225
  *
@@ -1,5 +1,6 @@
1
1
  import { koolbaseFetch } from './network.js';
2
2
  import { refreshCollectionQueries } from './query-refresh.js';
3
+ import { KoolbaseRevisionMismatchError } from './database-errors.js';
3
4
  import { KoolbaseError, KoolbaseOfflineBaselineUnavailableError, KoolbaseUnauthenticatedError, } from './errors.js';
4
5
  import { cacheRecord, getCachedRecord, removeCachedRecord } from './cache-store.js';
5
6
  import { readOfflineState, mutateOfflineState, queueWrite, } from './offline-state.js';
@@ -227,7 +228,7 @@ export class KoolbaseDatabase {
227
228
  */
228
229
  async invalidate(collection) {
229
230
  await invalidateCache(this.getUserId() ?? 'anonymous', collection);
230
- await refreshCollectionQueries(collection);
231
+ void refreshCollectionQueries(collection);
231
232
  }
232
233
  // ─── Insert (online-first with offline fallback) ───────────────────────────
233
234
  /**
@@ -251,7 +252,7 @@ export class KoolbaseDatabase {
251
252
  const raw = await this.request('POST', '/v1/sdk/db/insert', { collection, data });
252
253
  const record = recordFromWire(raw);
253
254
  await invalidateCache(userId, collection);
254
- await refreshCollectionQueries(collection);
255
+ void refreshCollectionQueries(collection);
255
256
  // The response carries a fresh revision, so caching it keeps the
256
257
  // baseline current for whatever edits this record next.
257
258
  await cacheRecord(userId, collection, record.id, record.data, record.revision);
@@ -326,7 +327,7 @@ export class KoolbaseDatabase {
326
327
  // Keep the cache fresh, same intent as insert's post-success invalidate.
327
328
  const userId = this.getUserId() ?? 'anonymous';
328
329
  await invalidateCache(userId, collection);
329
- await refreshCollectionQueries(collection);
330
+ void refreshCollectionQueries(collection);
330
331
  await cacheRecord(userId, collection, record.id, record.data, record.revision);
331
332
  return { record, created };
332
333
  }
@@ -346,7 +347,7 @@ export class KoolbaseDatabase {
346
347
  const body = await this.request('POST', '/v1/sdk/db/delete-where', { collection, filters });
347
348
  const userId = this.getUserId() ?? 'anonymous';
348
349
  await invalidateCache(userId, collection);
349
- await refreshCollectionQueries(collection);
350
+ void refreshCollectionQueries(collection);
350
351
  return body.deleted ?? 0;
351
352
  }
352
353
  // ─── Batch (atomic, online-only) ────────────────────────────────────────────
@@ -410,7 +411,7 @@ export class KoolbaseDatabase {
410
411
  }
411
412
  for (const col of touched) {
412
413
  await invalidateCache(userId, col);
413
- await refreshCollectionQueries(col);
414
+ void refreshCollectionQueries(col);
414
415
  }
415
416
  return results;
416
417
  }
@@ -602,7 +603,7 @@ export class KoolbaseDatabase {
602
603
  }
603
604
  await this.dropConflict(c.id);
604
605
  await invalidateCache(this.getUserId() ?? 'anonymous', c.collection);
605
- await refreshCollectionQueries(c.collection);
606
+ void refreshCollectionQueries(c.collection);
606
607
  }
607
608
  // ─── Update (online-first with offline fallback) ───────────────────────────
608
609
  /**
@@ -659,12 +660,15 @@ export class KoolbaseDatabase {
659
660
  return null;
660
661
  return { baseline: cached.data, revision: cached.revision, collection: cached.collection };
661
662
  }
662
- async update(recordId, data) {
663
+ async update(recordId, data,
664
+ /** expectedRevision: apply only if the record still carries it; refused
665
+ * with KoolbaseRevisionMismatchError otherwise (as the Flutter SDK). */
666
+ options = {}) {
663
667
  const userId = this.getUserId() ?? 'anonymous';
664
668
  // Resolved before the request, so a network failure has somewhere to go.
665
669
  const base = await this.resolveBaseline(userId, recordId);
666
670
  try {
667
- const raw = await this.request('PATCH', `/v1/sdk/db/records/${recordId}`, { data });
671
+ const raw = await this.request('PATCH', `/v1/sdk/db/records/${recordId}`, { data, ...(options.expectedRevision !== undefined ? { expected_revision: options.expectedRevision } : {}) });
668
672
  const updated = recordFromWire(raw);
669
673
  // Its collection's live queries re-run (every one, if the record's
670
674
  // collection is not known).
@@ -672,7 +676,7 @@ export class KoolbaseDatabase {
672
676
  if (updated.collection) {
673
677
  await cacheRecord(userId, updated.collection, updated.id, updated.data, updated.revision);
674
678
  }
675
- await refreshCollectionQueries(refreshed);
679
+ void refreshCollectionQueries(refreshed);
676
680
  return updated;
677
681
  }
678
682
  catch (e) {
@@ -682,6 +686,10 @@ export class KoolbaseDatabase {
682
686
  // credential — must not be queued: it will be refused again on every
683
687
  // retry. Checked against the root rather than the data family, because a
684
688
  // rejected credential belongs to no single surface.
689
+ // Refused as stale: the server holds newer data, so show it -- the
690
+ // collection's live queries re-run in the background (as Flutter).
691
+ if (e instanceof KoolbaseRevisionMismatchError)
692
+ void refreshCollectionQueries(base?.collection);
685
693
  if (e instanceof KoolbaseError)
686
694
  throw e;
687
695
  // Genuine network failure. Queueable only if the SDK knows what the
@@ -713,15 +721,19 @@ export class KoolbaseDatabase {
713
721
  }
714
722
  }
715
723
  // ─── Delete ─────────────────────────────────────────────────────────────────
716
- async delete(recordId) {
724
+ async delete(recordId,
725
+ /** expectedRevision: delete only if the record still carries it (as the
726
+ * Flutter SDK) -- removing a record someone changed a moment ago is the
727
+ * more destructive stale write. */
728
+ options = {}) {
717
729
  const userId = this.getUserId() ?? 'anonymous';
718
730
  const base = await this.resolveBaseline(userId, recordId);
719
731
  try {
720
- await this.request('DELETE', `/v1/sdk/db/records/${recordId}`);
732
+ await this.request('DELETE', `/v1/sdk/db/records/${recordId}${options.expectedRevision !== undefined ? `?expected_revision=${options.expectedRevision}` : ''}`);
721
733
  await removeCachedRecord(userId, recordId);
722
734
  // Its collection is known from the baseline when the record was seen;
723
735
  // otherwise every live query re-runs, rather than a guess.
724
- await refreshCollectionQueries(base?.collection);
736
+ void refreshCollectionQueries(base?.collection);
725
737
  }
726
738
  catch (e) {
727
739
  // A server that answered has refused: a permission denial or a missing
@@ -732,6 +744,10 @@ export class KoolbaseDatabase {
732
744
  // credential — must not be queued: it will be refused again on every
733
745
  // retry. Checked against the root rather than the data family, because a
734
746
  // rejected credential belongs to no single surface.
747
+ // Refused as stale: the server holds newer data, so show it -- the
748
+ // collection's live queries re-run in the background (as Flutter).
749
+ if (e instanceof KoolbaseRevisionMismatchError)
750
+ void refreshCollectionQueries(base?.collection);
735
751
  if (e instanceof KoolbaseError)
736
752
  throw e;
737
753
  // Genuine network failure. Queued here rather than before the request,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koolbase/core",
3
- "version": "12.7.0",
3
+ "version": "12.8.0",
4
4
  "description": "Koolbase SDK core — shared behaviour behind @koolbase/react-native and @koolbase/js. Install one of those, not this.",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "types": "./dist/esm/index.d.ts",