kaafil-js 0.7.0 → 0.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.
@@ -1017,6 +1017,31 @@ interface CreateDrainerOptions {
1017
1017
  /** Fed every observed `meta.serverTime` so the share layer can age a token
1018
1018
  * without a device clock (`06 §8`). */
1019
1019
  readonly onServerTime?: (serverTime: string) => void;
1020
+ /**
1021
+ * Called with the lanes a pass actually APPLIED something on, so the engine
1022
+ * can pull them straight afterwards.
1023
+ *
1024
+ * ── WHY A DRAIN HAS TO BE FOLLOWED BY A PULL ──────────────────────────────
1025
+ *
1026
+ * A write leaves the snapshot alone unless it was enqueued with an
1027
+ * `entity`/`optimistic` pairing, and several deliberately are not: a write
1028
+ * whose response carries server-assigned fields no client can compute
1029
+ * (`floatMovementId`, a resolved `receiptRequired` threshold, whether a
1030
+ * payment cleared a negative-float guard) is better left unguessed than
1031
+ * painted wrong and silently corrected later. Those writes reach the server
1032
+ * correctly and the snapshot simply does not know yet.
1033
+ *
1034
+ * Until now nothing closed that gap: `sync.drained` was emitted and the pass
1035
+ * ended. A host reading a snapshot-backed list saw its own successful write
1036
+ * missing from it — indefinitely, since nothing else schedules a pull — and
1037
+ * the fix was to reopen the screen. Observed in the field: an expense logged
1038
+ * against a trip's float moved the balance and stayed absent from the list
1039
+ * beside it.
1040
+ *
1041
+ * A callback rather than a direct dependency on `./pull.ts`, so this module
1042
+ * keeps knowing nothing about pulling and stays testable without one.
1043
+ */
1044
+ readonly onLanesApplied?: (lanes: readonly string[]) => void;
1020
1045
  }
1021
1046
  declare function createDrainer(options: CreateDrainerOptions): Drainer;
1022
1047
 
@@ -1017,6 +1017,31 @@ interface CreateDrainerOptions {
1017
1017
  /** Fed every observed `meta.serverTime` so the share layer can age a token
1018
1018
  * without a device clock (`06 §8`). */
1019
1019
  readonly onServerTime?: (serverTime: string) => void;
1020
+ /**
1021
+ * Called with the lanes a pass actually APPLIED something on, so the engine
1022
+ * can pull them straight afterwards.
1023
+ *
1024
+ * ── WHY A DRAIN HAS TO BE FOLLOWED BY A PULL ──────────────────────────────
1025
+ *
1026
+ * A write leaves the snapshot alone unless it was enqueued with an
1027
+ * `entity`/`optimistic` pairing, and several deliberately are not: a write
1028
+ * whose response carries server-assigned fields no client can compute
1029
+ * (`floatMovementId`, a resolved `receiptRequired` threshold, whether a
1030
+ * payment cleared a negative-float guard) is better left unguessed than
1031
+ * painted wrong and silently corrected later. Those writes reach the server
1032
+ * correctly and the snapshot simply does not know yet.
1033
+ *
1034
+ * Until now nothing closed that gap: `sync.drained` was emitted and the pass
1035
+ * ended. A host reading a snapshot-backed list saw its own successful write
1036
+ * missing from it — indefinitely, since nothing else schedules a pull — and
1037
+ * the fix was to reopen the screen. Observed in the field: an expense logged
1038
+ * against a trip's float moved the balance and stayed absent from the list
1039
+ * beside it.
1040
+ *
1041
+ * A callback rather than a direct dependency on `./pull.ts`, so this module
1042
+ * keeps knowing nothing about pulling and stays testable without one.
1043
+ */
1044
+ readonly onLanesApplied?: (lanes: readonly string[]) => void;
1020
1045
  }
1021
1046
  declare function createDrainer(options: CreateDrainerOptions): Drainer;
1022
1047
 
@@ -2875,6 +2875,9 @@ function createDrainer(options) {
2875
2875
  remaining: report.remaining,
2876
2876
  usedBatchTransport: report.usedBatchTransport
2877
2877
  });
2878
+ if (report.applied > 0) {
2879
+ options.onLanesApplied?.(report.lanes);
2880
+ }
2878
2881
  return report;
2879
2882
  }
2880
2883
  let kickTimer;
@@ -3598,22 +3601,45 @@ function createOfflineEngine(options) {
3598
3601
  ...options.share.monotonic !== void 0 ? { monotonic: options.share.monotonic } : {}
3599
3602
  });
3600
3603
  }
3604
+ const pull = createPullCoordinator({
3605
+ snapshot,
3606
+ transport: options.transport,
3607
+ events,
3608
+ onServerTime
3609
+ });
3601
3610
  const drainer = createDrainer({
3602
3611
  outbox,
3603
3612
  snapshot,
3604
3613
  transport: options.transport,
3605
3614
  events,
3606
3615
  onServerTime,
3616
+ /*
3617
+ * A DRAIN IS FOLLOWED BY A PULL, for the lanes it actually changed.
3618
+ *
3619
+ * Not every write rebases the snapshot: one enqueued without an
3620
+ * `entity`/`optimistic` pairing — deliberately, wherever the response
3621
+ * carries server-assigned fields a client cannot compute — reaches the
3622
+ * server and leaves the local snapshot untouched. Nothing used to close
3623
+ * that gap, so a host reading a snapshot-backed list saw its own
3624
+ * successful write missing from it until something reopened the screen.
3625
+ *
3626
+ * Failures are swallowed on purpose. This is a background reconcile behind
3627
+ * a write that has already succeeded; a pull that cannot reach the server
3628
+ * changes nothing the caller needs to hear about, and `pullTrip` has
3629
+ * already emitted whatever the host subscribes to. Rethrowing here would
3630
+ * surface a sync failure as if the WRITE had failed, which is the one
3631
+ * reading of it that is untrue.
3632
+ */
3633
+ onLanesApplied: (lanes) => {
3634
+ for (const tripRef of lanes) {
3635
+ void pull.pullTrip({ tripRef }).catch(() => {
3636
+ });
3637
+ }
3638
+ },
3607
3639
  ...options.conflictResolver !== void 0 ? { conflictResolver: options.conflictResolver } : {},
3608
3640
  ...options.now !== void 0 ? { now: options.now } : {},
3609
3641
  ...options.random !== void 0 ? { random: options.random } : {}
3610
3642
  });
3611
- const pull = createPullCoordinator({
3612
- snapshot,
3613
- transport: options.transport,
3614
- events,
3615
- onServerTime
3616
- });
3617
3643
  const blobs = options.blob === void 0 ? void 0 : createBlobLane({
3618
3644
  storage: options.storage,
3619
3645
  keyPrefix,