kaafil-js 0.1.0 → 0.3.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.
@@ -2583,6 +2583,11 @@ function createDrainer(options) {
2583
2583
  row: outcome.entity,
2584
2584
  ...outcome.serverTime !== void 0 ? { syncedAt: outcome.serverTime } : {}
2585
2585
  });
2586
+ const temporaryId = entity.temporaryId;
2587
+ if (temporaryId !== void 0 && temporaryId !== outcome.entity.id) {
2588
+ await snapshot.purgeEntity(op.tripRef, entity.listName, temporaryId);
2589
+ changed = true;
2590
+ }
2586
2591
  }
2587
2592
  if (changed && outcome.serverTime !== void 0) {
2588
2593
  events.emit("snapshot.updated", {
@@ -2682,6 +2687,16 @@ function createDrainer(options) {
2682
2687
  switch (result.action) {
2683
2688
  case "drop":
2684
2689
  await outbox.drop(op.id);
2690
+ if (op.entity?.temporaryId !== void 0) {
2691
+ await snapshot.purgeEntity(op.tripRef, op.entity.listName, op.entity.temporaryId);
2692
+ if (outcome.serverTime !== void 0) {
2693
+ events.emit("snapshot.updated", {
2694
+ tripRef: op.tripRef,
2695
+ lists: [op.entity.listName],
2696
+ serverTime: outcome.serverTime
2697
+ });
2698
+ }
2699
+ }
2685
2700
  return "terminal";
2686
2701
  case "reissue":
2687
2702
  await outbox.reissue(op.id, {
@@ -2962,7 +2977,35 @@ var SYNC_PULL_SECTIONS = [
2962
2977
  "itinerary",
2963
2978
  "closeout"
2964
2979
  ];
2965
- function extractSectionRows(data) {
2980
+ var SECTION_ROW_KEY = {
2981
+ // Delta-shaped `rooms[]`; `windows[]`/`unassigned[]` are derived
2982
+ // projections — the long note above is still exactly right about this one.
2983
+ rooming: "rooms",
2984
+ // The same decision in the same shape: `vehicles[]` is the feed,
2985
+ // `unassignedPool[]` the projection that must never be cached as rows.
2986
+ seating: "vehicles",
2987
+ expenses: "items",
2988
+ float: "data",
2989
+ // The one that was silently dead. `sections[]` is a grouping projection and
2990
+ // `availableTemplates[]` is agency-level catalogue; `items[]` is the trip's
2991
+ // own entity list and the only one of the three carrying tombstones.
2992
+ checklist: "items",
2993
+ // `days[]` is the day-by-day projection the engine recomputes per read;
2994
+ // `items[]` is the entity list.
2995
+ itinerary: "items",
2996
+ closeout: null
2997
+ // `stayWindows`, `pickups`, `bookings` and `collections` send a top-level
2998
+ // array and never reach this table.
2999
+ };
3000
+ function extractSectionRows(data, section) {
3001
+ if (section !== void 0 && section in SECTION_ROW_KEY) {
3002
+ const key = SECTION_ROW_KEY[section];
3003
+ if (key === null || key === void 0 || typeof data !== "object" || data === null) {
3004
+ return void 0;
3005
+ }
3006
+ const value = data[key];
3007
+ return Array.isArray(value) && isRowArray(value) ? value : void 0;
3008
+ }
2966
3009
  if (Array.isArray(data)) {
2967
3010
  return isRowArray(data) ? data : void 0;
2968
3011
  }
@@ -3032,7 +3075,7 @@ function createPullCoordinator(options) {
3032
3075
  absent.push(name);
3033
3076
  continue;
3034
3077
  }
3035
- const rows = extractSectionRows(section.data);
3078
+ const rows = extractSectionRows(section.data, name);
3036
3079
  if (rows === void 0) {
3037
3080
  unmapped.push(name);
3038
3081
  continue;
@@ -3069,6 +3112,49 @@ function createPullCoordinator(options) {
3069
3112
  };
3070
3113
  }
3071
3114
 
3115
+ // src/offline/reads.ts
3116
+ function createReadCacheStore(options) {
3117
+ const key = `${options.keyPrefix}:reads`;
3118
+ let reads = /* @__PURE__ */ new Map();
3119
+ async function persist() {
3120
+ const shape = { reads: Object.fromEntries(reads) };
3121
+ await options.storage.set(key, JSON.stringify(shape));
3122
+ }
3123
+ return {
3124
+ get(readKey) {
3125
+ return reads.get(readKey);
3126
+ },
3127
+ async set(readKey, value, serverTime) {
3128
+ reads.set(readKey, { value, syncedAt: serverTime });
3129
+ await persist();
3130
+ },
3131
+ async purge(readKey) {
3132
+ if (!reads.delete(readKey)) {
3133
+ return;
3134
+ }
3135
+ await persist();
3136
+ },
3137
+ async load() {
3138
+ reads = /* @__PURE__ */ new Map();
3139
+ const raw = await options.storage.get(key);
3140
+ if (raw === void 0) {
3141
+ return;
3142
+ }
3143
+ let parsed;
3144
+ try {
3145
+ parsed = JSON.parse(raw);
3146
+ } catch {
3147
+ return;
3148
+ }
3149
+ for (const [readKey, entry] of Object.entries(parsed.reads ?? {})) {
3150
+ if (entry !== null && typeof entry === "object" && typeof entry.syncedAt === "string") {
3151
+ reads.set(readKey, entry);
3152
+ }
3153
+ }
3154
+ }
3155
+ };
3156
+ }
3157
+
3072
3158
  // src/offline/share.ts
3073
3159
  var SHARE_WRITE_GRACE_MS = 7 * 24 * 60 * 60 * 1e3;
3074
3160
  var KaafilShareLinkExpiredError = class _KaafilShareLinkExpiredError extends Error {
@@ -3443,6 +3529,8 @@ function createOfflineEngine(options) {
3443
3529
  ...options.onEventHandlerError !== void 0 ? { onHandlerError: (error) => options.onEventHandlerError?.(error) } : {}
3444
3530
  });
3445
3531
  const snapshot = createSnapshotStore({ storage: options.storage, keyPrefix });
3532
+ const reads = createReadCacheStore({ storage: options.storage, keyPrefix });
3533
+ const mintEntityId = () => `tmp_${options.newId?.() ?? newIdempotencyKey()}`;
3446
3534
  const outbox = createOutbox({
3447
3535
  storage: options.storage,
3448
3536
  keyPrefix,
@@ -3491,6 +3579,13 @@ function createOfflineEngine(options) {
3491
3579
  let unbind;
3492
3580
  async function enqueue(input) {
3493
3581
  share?.assertWritable();
3582
+ let entity = input.entity;
3583
+ let temporaryId;
3584
+ if (input.creates === true && input.entity !== void 0) {
3585
+ const { temporaryId: _ignored, ...ref } = input.entity;
3586
+ temporaryId = mintEntityId();
3587
+ entity = { ...ref, id: temporaryId, temporaryId };
3588
+ }
3494
3589
  const op = await outbox.enqueue({
3495
3590
  tripRef: input.tripRef,
3496
3591
  method: input.method,
@@ -3498,16 +3593,25 @@ function createOfflineEngine(options) {
3498
3593
  operationId: input.operationId,
3499
3594
  ...input.body !== void 0 ? { body: input.body } : {},
3500
3595
  ...input.ifMatch !== void 0 ? { ifMatch: input.ifMatch } : {},
3501
- ...input.entity !== void 0 ? { entity: input.entity } : {},
3596
+ ...entity !== void 0 ? { entity } : {},
3502
3597
  ...input.idempotencyKey !== void 0 ? { idempotencyKey: input.idempotencyKey } : {},
3503
3598
  ...input.enqueuedAt !== void 0 ? { enqueuedAt: input.enqueuedAt } : {}
3504
3599
  });
3505
- if (input.optimistic !== void 0 && input.entity !== void 0) {
3600
+ if (input.optimistic !== void 0 && entity !== void 0) {
3506
3601
  await snapshot.upsert({
3507
3602
  tripRef: input.tripRef,
3508
- listName: input.entity.listName,
3509
- row: input.optimistic
3603
+ listName: entity.listName,
3604
+ // A create's row is keyed on the minted id rather than on whatever
3605
+ // `id` the caller happened to put in the optimistic row — those two
3606
+ // disagreeing is what would leave an orphan the drainer cannot find.
3607
+ row: temporaryId === void 0 ? input.optimistic : { ...input.optimistic, id: temporaryId }
3510
3608
  });
3609
+ if (temporaryId !== void 0) {
3610
+ events.emit("snapshot.updated", {
3611
+ tripRef: input.tripRef,
3612
+ lists: [entity.listName]
3613
+ });
3614
+ }
3511
3615
  }
3512
3616
  drainer.kick();
3513
3617
  return op;
@@ -3515,17 +3619,39 @@ function createOfflineEngine(options) {
3515
3619
  return {
3516
3620
  events,
3517
3621
  snapshot,
3622
+ reads,
3518
3623
  outbox,
3519
3624
  drainer,
3520
3625
  pull,
3521
3626
  blobs,
3522
3627
  share,
3523
3628
  async open() {
3524
- await Promise.all([snapshot.load(), outbox.load(), blobs?.load() ?? Promise.resolve()]);
3629
+ await Promise.all([
3630
+ snapshot.load(),
3631
+ reads.load(),
3632
+ outbox.load(),
3633
+ blobs?.load() ?? Promise.resolve()
3634
+ ]);
3525
3635
  unbind = drainer.bindOpportunisticTriggers();
3526
3636
  drainer.kick();
3527
3637
  },
3528
3638
  enqueue,
3639
+ async discard(opId) {
3640
+ const op = outbox.get(opId);
3641
+ if (op === void 0) {
3642
+ return;
3643
+ }
3644
+ await outbox.drop(opId);
3645
+ const temporaryId = op.entity?.temporaryId;
3646
+ if (op.entity === void 0 || temporaryId === void 0) {
3647
+ return;
3648
+ }
3649
+ await snapshot.purgeEntity(op.tripRef, op.entity.listName, temporaryId);
3650
+ events.emit("snapshot.updated", {
3651
+ tripRef: op.tripRef,
3652
+ lists: [op.entity.listName]
3653
+ });
3654
+ },
3529
3655
  async enqueueWithBlob(input) {
3530
3656
  const { blob, ...rest } = input;
3531
3657
  share?.assertWritable();
@@ -7411,6 +7537,6 @@ var KaafilClient = class {
7411
7537
  }
7412
7538
  };
7413
7539
 
7414
- export { ACTIVE_TREK_REF, BATCH_THRESHOLD_OPS, BlobStatus, BoardStatus, BookingStatus, CONSOLIDATED_PULL_THRESHOLD_LISTS, ChecklistAudience, ChecklistGate, ChecklistItemStatus, ChecklistPhase, ChecklistPullTemplateMode, ClaimStatusIngest, CloseoutPackSection, CollectionMode, CompressionOutcome, Currency, ERROR_CODE_TABLE, EventType, ExpenseCategory, ExpensePaymentMode, FALLBACK_GLYPH, FileContentType, FilePurpose, FloatDirection, Gender, HEURISTIC_SEVERITY, ItineraryItemStatusUpdate, ItineraryItemType, KaafilAbortError, KaafilApiError, KaafilCapabilityUnavailableError, KaafilClient, KaafilClientAlreadyOpenError, KaafilClientNotOpenError, KaafilDeltaCursorInFlightError, KaafilEntitlementError, KaafilError, KaafilErrorCode, KaafilIndexedDbUnavailableError, KaafilInvalidRequestError, KaafilLockedError, KaafilNetworkError, KaafilNotFoundError, KaafilNotImplementedError, KaafilPaginationError, KaafilPaginationExhaustedError, KaafilPaginationInFlightError, KaafilRateLimitedError, KaafilReadOnlyRoleError, KaafilShareBatchUnauthenticatedError, KaafilShareLinkExpiredError, KaafilShareTokenExpiredError, KaafilShareTokenRevokedError, KaafilTimeoutError, KaafilTransportError, KaafilUnauthenticatedError, KaafilValidationError, KaafilVersionConflictError, MAX_BATCH_OPS, ManagerRole, ManifestMode, OutboxClass, OutboxRetryCause, OutboxStatus, PartyKind, PickupKind, Retryability, RoomType, RoomingFillOrder, RoomingGenderPolicy, RoomingHeuristicKey, SHADES_PER_FAMILY, SHARE_WRITE_GRACE_MS, SYNC_PULL_SECTIONS, SeatingFillOrder, SeatingGenderAdjacency, SeatingHeuristicKey, SyncOpMethod, TONE_FAMILIES, TriggerAnchor, TripMode, TripStatus, UNKNOWN_TONE_FAMILY, UnsatisfiableSchemeError, VehicleLayout, VehicleType, compressImage, convertImageLossless, createBlobLane, createDeltaCursor, createDrainer, createHttpOfflineTransport, createInMemoryStorageAdapter, createIndexedDbStorageAdapter, createOfflineEngine, createOfflineEvents, createOutbox, createPullCoordinator, createShareGuard, createSnapshotStore, createSyncResource, extractSectionRows, isCompressibleType, isImageCompressionSupported, isKaafilError, isRetryable, isRoomingHeuristicKey, isShareBatchPushAvailable, isTombstone, mergeDeltaRows, parseRoomingTone, readCurrentVersion, reconcileConflict, roomingHeuristicSeverity, scaledDimensions, shouldUseBatchTransport };
7540
+ export { ACTIVE_TREK_REF, BATCH_THRESHOLD_OPS, BlobStatus, BoardStatus, BookingStatus, CONSOLIDATED_PULL_THRESHOLD_LISTS, ChecklistAudience, ChecklistGate, ChecklistItemStatus, ChecklistPhase, ChecklistPullTemplateMode, ClaimStatusIngest, CloseoutPackSection, CollectionMode, CompressionOutcome, Currency, ERROR_CODE_TABLE, EventType, ExpenseCategory, ExpensePaymentMode, FALLBACK_GLYPH, FileContentType, FilePurpose, FloatDirection, Gender, HEURISTIC_SEVERITY, ItineraryItemStatusUpdate, ItineraryItemType, KaafilAbortError, KaafilApiError, KaafilCapabilityUnavailableError, KaafilClient, KaafilClientAlreadyOpenError, KaafilClientNotOpenError, KaafilDeltaCursorInFlightError, KaafilEntitlementError, KaafilError, KaafilErrorCode, KaafilIndexedDbUnavailableError, KaafilInvalidRequestError, KaafilLockedError, KaafilNetworkError, KaafilNotFoundError, KaafilNotImplementedError, KaafilPaginationError, KaafilPaginationExhaustedError, KaafilPaginationInFlightError, KaafilRateLimitedError, KaafilReadOnlyRoleError, KaafilShareBatchUnauthenticatedError, KaafilShareLinkExpiredError, KaafilShareTokenExpiredError, KaafilShareTokenRevokedError, KaafilTimeoutError, KaafilTransportError, KaafilUnauthenticatedError, KaafilValidationError, KaafilVersionConflictError, MAX_BATCH_OPS, ManagerRole, ManifestMode, OutboxClass, OutboxRetryCause, OutboxStatus, PartyKind, PickupKind, Retryability, RoomType, RoomingFillOrder, RoomingGenderPolicy, RoomingHeuristicKey, SHADES_PER_FAMILY, SHARE_WRITE_GRACE_MS, SYNC_PULL_SECTIONS, SeatingFillOrder, SeatingGenderAdjacency, SeatingHeuristicKey, SyncOpMethod, TONE_FAMILIES, TriggerAnchor, TripMode, TripStatus, UNKNOWN_TONE_FAMILY, UnsatisfiableSchemeError, VehicleLayout, VehicleType, compressImage, convertImageLossless, createBlobLane, createDeltaCursor, createDrainer, createHttpOfflineTransport, createInMemoryStorageAdapter, createIndexedDbStorageAdapter, createOfflineEngine, createOfflineEvents, createOutbox, createPullCoordinator, createReadCacheStore, createShareGuard, createSnapshotStore, createSyncResource, extractSectionRows, isCompressibleType, isImageCompressionSupported, isKaafilError, isRetryable, isRoomingHeuristicKey, isShareBatchPushAvailable, isTombstone, mergeDeltaRows, parseRoomingTone, readCurrentVersion, reconcileConflict, roomingHeuristicSeverity, scaledDimensions, shouldUseBatchTransport };
7415
7541
  //# sourceMappingURL=client-entry.js.map
7416
7542
  //# sourceMappingURL=client-entry.js.map