@stamprally/core 0.18.0 → 0.19.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/dist/index.d.cts CHANGED
@@ -653,6 +653,9 @@ type ClientEvent = {
653
653
  } | {
654
654
  readonly type: "syncLifecycle";
655
655
  readonly event: SyncLifecycleEvent;
656
+ } | {
657
+ readonly type: "storageCapabilityWarning";
658
+ readonly warning: OfflineQueueCapabilityWarning;
656
659
  } | {
657
660
  readonly type: "error";
658
661
  readonly error: ClientError | OfflineOperationError;
package/dist/index.d.ts CHANGED
@@ -653,6 +653,9 @@ type ClientEvent = {
653
653
  } | {
654
654
  readonly type: "syncLifecycle";
655
655
  readonly event: SyncLifecycleEvent;
656
+ } | {
657
+ readonly type: "storageCapabilityWarning";
658
+ readonly warning: OfflineQueueCapabilityWarning;
656
659
  } | {
657
660
  readonly type: "error";
658
661
  readonly error: ClientError | OfflineOperationError;
package/dist/index.js CHANGED
@@ -1579,9 +1579,10 @@ var OfflineQueue = class {
1579
1579
  );
1580
1580
  if (spot2 === void 0) return false;
1581
1581
  const failedSpots = new Set(
1582
- this.#rejectedHistory.filter((entry) => entry.operation.kind === "checkIn").map(
1583
- (entry) => entry.operation.kind === "checkIn" ? entry.operation.request.spotId : void 0
1584
- ).filter((spotId) => spotId !== void 0)
1582
+ [
1583
+ ...this.#rejectedHistory.map((entry) => entry.operation),
1584
+ ...this.#operations.filter((candidate) => candidate.status === "REJECTED_PERMANENT")
1585
+ ].filter((candidate) => candidate.kind === "checkIn").map((candidate) => candidate.kind === "checkIn" ? candidate.request.spotId : void 0).filter((spotId) => spotId !== void 0)
1585
1586
  );
1586
1587
  if (!spot2.prerequisites?.some((prerequisite) => failedSpots.has(prerequisite))) return false;
1587
1588
  const error = {
@@ -1792,18 +1793,18 @@ function applyInventoryDelta(state, previous, optimistic) {
1792
1793
  if (before !== void 0 && after !== void 0)
1793
1794
  rewardRemaining[key] = Math.max(0, (currentRewards[key] ?? before) + after - before);
1794
1795
  }
1795
- return {
1796
- ...state,
1797
- inventory: {
1798
- ...currentInventory.sharedRemaining === void 0 || sharedDelta === void 0 ? {} : { sharedRemaining: Math.max(0, currentInventory.sharedRemaining + sharedDelta) },
1799
- ...Object.keys(rewardRemaining).length === 0 ? {} : { rewardRemaining }
1800
- }
1796
+ const nextInventory = {
1797
+ ...currentInventory.sharedRemaining === void 0 || sharedDelta === void 0 ? optimisticInventory.sharedRemaining === void 0 || sharedDelta === void 0 ? {} : { sharedRemaining: Math.max(0, optimisticInventory.sharedRemaining) } : { sharedRemaining: Math.max(0, currentInventory.sharedRemaining + sharedDelta) },
1798
+ ...Object.keys(rewardRemaining).length === 0 ? {} : { rewardRemaining }
1801
1799
  };
1800
+ return { ...state, inventory: nextInventory };
1802
1801
  }
1803
- function applyOperation(state, operation, config) {
1802
+ function applyOperation(state, operation, config, rejectedCheckIns) {
1804
1803
  if (operation.kind === "checkIn") {
1805
1804
  const spot2 = config?.spots.find((candidate) => candidate.id === operation.request.spotId);
1806
- if (spot2?.prerequisites?.some((id2) => !state.records.some((record2) => record2.stampId === id2)))
1805
+ if (spot2?.prerequisites?.some(
1806
+ (id2) => rejectedCheckIns.has(id2) || !state.records.some((record2) => record2.stampId === id2)
1807
+ ))
1807
1808
  return { state, prerequisiteFailed: true };
1808
1809
  if (state.records.some((record2) => record2.stampId === operation.request.spotId))
1809
1810
  return { state, prerequisiteFailed: false };
@@ -1850,11 +1851,21 @@ function rebuildUserStateFromLog(baselineOrOptions, operationsArgument, configAr
1850
1851
  function rebuildUserStateLog(baseline, operations, config) {
1851
1852
  let state = cloneState(baseline);
1852
1853
  const rejectedOperationIds = [];
1854
+ const rejectedCheckIns = new Set(
1855
+ operations.filter(
1856
+ (operation) => operation.status === "REJECTED_PERMANENT" && operation.kind === "checkIn"
1857
+ ).map((operation) => operation.kind === "checkIn" ? operation.request.spotId : void 0).filter((spotId) => spotId !== void 0)
1858
+ );
1853
1859
  for (const operation of operations) {
1860
+ if (operation.status === "REJECTED_PERMANENT") {
1861
+ if (operation.kind === "checkIn") rejectedCheckIns.add(operation.request.spotId);
1862
+ continue;
1863
+ }
1854
1864
  if (!isReplayable(operation)) continue;
1855
- const replay = applyOperation(state, operation, config);
1865
+ const replay = applyOperation(state, operation, config, rejectedCheckIns);
1856
1866
  if (replay.prerequisiteFailed) {
1857
1867
  rejectedOperationIds.push(operationId(operation));
1868
+ if (operation.kind === "checkIn") rejectedCheckIns.add(operation.request.spotId);
1858
1869
  continue;
1859
1870
  }
1860
1871
  state = replay.state;
@@ -1902,6 +1913,30 @@ function emptyState(config, userId, now) {
1902
1913
  updatedAt: now
1903
1914
  };
1904
1915
  }
1916
+ function applyOptimisticRewardClaim(state, rewardId, reward2, now) {
1917
+ if (reward2.claimTicketNumber === void 0 || state.inventory === void 0)
1918
+ return {
1919
+ ...state,
1920
+ rewards: state.rewards.map((item) => item.rewardId === rewardId ? reward2 : item),
1921
+ updatedAt: now
1922
+ };
1923
+ const currentInventory = state.inventory;
1924
+ const currentRewardRemaining = currentInventory.rewardRemaining?.[rewardId];
1925
+ return {
1926
+ ...state,
1927
+ rewards: state.rewards.map((item) => item.rewardId === rewardId ? reward2 : item),
1928
+ updatedAt: now,
1929
+ inventory: {
1930
+ ...currentInventory.sharedRemaining === void 0 ? {} : { sharedRemaining: Math.max(0, currentInventory.sharedRemaining - 1) },
1931
+ ...currentInventory.rewardRemaining === void 0 || currentRewardRemaining === void 0 ? {} : {
1932
+ rewardRemaining: {
1933
+ ...currentInventory.rewardRemaining,
1934
+ [rewardId]: Math.max(0, currentRewardRemaining - 1)
1935
+ }
1936
+ }
1937
+ }
1938
+ };
1939
+ }
1905
1940
  function errorMessage(error, fallback) {
1906
1941
  return error !== void 0 && "message" in error && typeof error.message === "string" ? error.message : fallback;
1907
1942
  }
@@ -1929,6 +1964,9 @@ var StampRallyClient = class {
1929
1964
  this.#offlineQueue = this.#options.offlineQueue;
1930
1965
  this.#offlineQueue?.setReplayConfig(config);
1931
1966
  this.#offlineQueue?.setSyncResultListener((event) => this.#handleOfflineSyncResult(event));
1967
+ this.#offlineQueue?.setCapabilityWarningListener(
1968
+ (warning) => this.#emitEvent({ type: "storageCapabilityWarning", warning })
1969
+ );
1932
1970
  this.#offlineQueue?.setChangeListener(() => {
1933
1971
  this.#syncRevision += 1;
1934
1972
  if (this.#state !== null) this.#emit(this.#state);
@@ -2159,23 +2197,13 @@ var StampRallyClient = class {
2159
2197
  return result.ok ? this.#commitClaim(result) : this.#fail(result.error);
2160
2198
  } catch (error) {
2161
2199
  if (this.#offlineQueue === void 0) throw error;
2162
- const next2 = {
2163
- ...current,
2164
- rewards: current.rewards.map(
2165
- (item) => item.rewardId === rewardId ? local.value : item
2166
- ),
2167
- updatedAt: now
2168
- };
2200
+ const next2 = applyOptimisticRewardClaim(current, rewardId, local.value, now);
2169
2201
  await this.#offlineQueue.enqueueClaimReward(request, next2);
2170
2202
  await this.#storage.save(next2);
2171
2203
  return this.#commitClaim({ ok: true, value: { state: next2, reward: local.value } });
2172
2204
  }
2173
2205
  }
2174
- const next = {
2175
- ...current,
2176
- rewards: current.rewards.map((item) => item.rewardId === rewardId ? local.value : item),
2177
- updatedAt: now
2178
- };
2206
+ const next = applyOptimisticRewardClaim(current, rewardId, local.value, now);
2179
2207
  await this.#storage.save(next);
2180
2208
  return this.#commitClaim({ ok: true, value: { state: next, reward: local.value } });
2181
2209
  });
@@ -3249,7 +3277,7 @@ function validateRallyConfigRelations(config) {
3249
3277
  ["stockKey", reward2.stockKey],
3250
3278
  ["secondaryStockKey", reward2.secondaryStockKey]
3251
3279
  ]) {
3252
- if (key !== void 0 && key !== "__shared__" && inventory?.[key] === void 0)
3280
+ if (key !== void 0 && (key === "__shared__" && inventory?.sharedStock === void 0 || key !== "__shared__" && inventory?.[key] === void 0))
3253
3281
  add(
3254
3282
  errors,
3255
3283
  `rewards[${index}].${field}`,
@@ -3257,13 +3285,6 @@ function validateRallyConfigRelations(config) {
3257
3285
  "missing_inventory_key"
3258
3286
  );
3259
3287
  }
3260
- if (reward2.stockKey === "__shared__" && inventory?.sharedStock === void 0)
3261
- add(
3262
- errors,
3263
- `rewards[${index}].stockKey`,
3264
- "sharedStock is not defined.",
3265
- "missing_inventory_key"
3266
- );
3267
3288
  });
3268
3289
  const visiting = /* @__PURE__ */ new Set();
3269
3290
  const visited = /* @__PURE__ */ new Set();