@stamprally/core 0.19.0 → 0.20.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/README.md +29 -2
- package/dist/index.cjs +99 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +111 -79
- package/dist/index.d.ts +111 -79
- package/dist/index.js +99 -100
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @stamprally/core v0.
|
|
1
|
+
# @stamprally/core v0.20.0
|
|
2
2
|
|
|
3
3
|
Dependency-free domain models, immutable state transitions, storage adapters, browser detectors, and safe configuration parsers.
|
|
4
4
|
|
|
@@ -7,7 +7,7 @@ import { InMemoryStorage, StampRallyClient, type PublicRallyConfig } from "@stam
|
|
|
7
7
|
|
|
8
8
|
const config: PublicRallyConfig = {
|
|
9
9
|
id: "city-tour",
|
|
10
|
-
version: "0.
|
|
10
|
+
version: "0.20.0",
|
|
11
11
|
title: "City Tour",
|
|
12
12
|
spots: [{ id: "station", orderIndex: 0, name: "Central Station", conditions: [{ type: "passcode" }] }],
|
|
13
13
|
rewards: [],
|
|
@@ -35,6 +35,33 @@ When no authenticated user is supplied, the client creates a persistent UUID v4
|
|
|
35
35
|
`anonymousSessionId` and includes it in sync requests. Queue replay uses Web Locks
|
|
36
36
|
when available and supports bounded exponential backoff through `retryOptions`.
|
|
37
37
|
|
|
38
|
+
### v0.20.0 batch sync and storage capability
|
|
39
|
+
|
|
40
|
+
An adapter can return `SyncProgressResponse` from `sync` when it sends queued
|
|
41
|
+
operations to a server-side `syncProgress` endpoint. The adapter passes queued
|
|
42
|
+
operations to its transport and maps the server request shape as needed:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const batchAdapter: SyncAdapter = {
|
|
46
|
+
sync: async ({ rallyId, userId, state, operations }) =>
|
|
47
|
+
postBatch({ rallyId, userId, state, operations }),
|
|
48
|
+
};
|
|
49
|
+
await client.sync(batchAdapter);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Each `SyncOperationResult`
|
|
53
|
+
is `ACCEPTED`, `REJECTED_PERMANENT`, or `FAILED_RETRYABLE`. Accepted and permanent
|
|
54
|
+
results are removed from the queue; permanent reasons are added to
|
|
55
|
+
`rejectedHistory`, while retryable operations are kept for the next send. A
|
|
56
|
+
permanent prerequisite failure does not prevent later independent operations from
|
|
57
|
+
being evaluated.
|
|
58
|
+
|
|
59
|
+
`client.queueCapability.multiTabSync` is `"supported_web_locks"` when the Web
|
|
60
|
+
Locks API is available. Otherwise it is `"disabled_unsafe_environment"`:
|
|
61
|
+
cross-tab automatic synchronization is disabled, the foreground tab must trigger
|
|
62
|
+
sync explicitly, and a `storageCapabilityWarning` event is emitted. localStorage
|
|
63
|
+
may still be used for durable queue data, but it is never used as a lock.
|
|
64
|
+
|
|
38
65
|
`evaluateSpotStatus` derives `UNCLAIMED`, `CLAIMED`, `LOCKED`, or `VERIFYING`
|
|
39
66
|
without mutating state. A spot with incomplete `prerequisites` is `LOCKED` and
|
|
40
67
|
must not be verified by a client or viewer.
|
package/dist/index.cjs
CHANGED
|
@@ -1200,8 +1200,6 @@ function errorValue(value, fallbackCode) {
|
|
|
1200
1200
|
if (typeof value === "string") return { code: fallbackCode, message: value };
|
|
1201
1201
|
return { code: fallbackCode, message: "Offline operation was rejected." };
|
|
1202
1202
|
}
|
|
1203
|
-
var syncLocks = /* @__PURE__ */ new Map();
|
|
1204
|
-
var SYNC_LOCK_TTL_MS = 3e4;
|
|
1205
1203
|
var DEFAULT_RETRY_OPTIONS = {
|
|
1206
1204
|
maxRetries: 0,
|
|
1207
1205
|
initialIntervalMs: 250,
|
|
@@ -1228,9 +1226,7 @@ var OfflineQueue = class {
|
|
|
1228
1226
|
#synchronizeInstances;
|
|
1229
1227
|
#retryOptions;
|
|
1230
1228
|
#instanceId = randomId();
|
|
1231
|
-
#
|
|
1232
|
-
#observedLocks = /* @__PURE__ */ new Map();
|
|
1233
|
-
#warnedMemoryLock = false;
|
|
1229
|
+
#warnedCapabilityMessages = /* @__PURE__ */ new Set();
|
|
1234
1230
|
#capabilityWarningListener;
|
|
1235
1231
|
#replayConfig;
|
|
1236
1232
|
#storageListener;
|
|
@@ -1260,8 +1256,7 @@ var OfflineQueue = class {
|
|
|
1260
1256
|
initialIntervalMs: Math.max(0, retryOptions.initialIntervalMs),
|
|
1261
1257
|
backoffMultiplier: Math.max(1, retryOptions.backoffMultiplier)
|
|
1262
1258
|
};
|
|
1263
|
-
this.#
|
|
1264
|
-
if (this.#synchronizeInstances) this.#subscribeToExternalChanges();
|
|
1259
|
+
if (this.#synchronizeInstances && this.#hasWebLocks()) this.#subscribeToExternalChanges();
|
|
1265
1260
|
}
|
|
1266
1261
|
get syncState() {
|
|
1267
1262
|
return this.#state;
|
|
@@ -1270,12 +1265,14 @@ var OfflineQueue = class {
|
|
|
1270
1265
|
return this.#operations.length;
|
|
1271
1266
|
}
|
|
1272
1267
|
get queueCapability() {
|
|
1273
|
-
return
|
|
1268
|
+
return {
|
|
1269
|
+
storage: this.#queueCapability,
|
|
1270
|
+
multiTabSync: this.#hasWebLocks() ? "supported_web_locks" : "disabled_unsafe_environment"
|
|
1271
|
+
};
|
|
1274
1272
|
}
|
|
1275
1273
|
get storageCapability() {
|
|
1276
1274
|
if (this.#queueCapability === "memory") return "memory";
|
|
1277
|
-
|
|
1278
|
-
return locks !== void 0 || this.#lockStorage !== null ? this.#queueCapability : "volatile_single_tab";
|
|
1275
|
+
return this.#hasWebLocks() ? this.#queueCapability : "volatile_single_tab";
|
|
1279
1276
|
}
|
|
1280
1277
|
get isStoragePersistent() {
|
|
1281
1278
|
return this.#queueCapability !== "memory";
|
|
@@ -1322,12 +1319,16 @@ var OfflineQueue = class {
|
|
|
1322
1319
|
this.#queueCapability = "memory";
|
|
1323
1320
|
this.#operations = [];
|
|
1324
1321
|
this.#rejectedHistory = [];
|
|
1325
|
-
this.#
|
|
1322
|
+
this.#warnCapability(
|
|
1326
1323
|
`Offline queue persistence is unavailable; queued data can be lost after reload (${String(error)}).`
|
|
1327
1324
|
);
|
|
1328
1325
|
}
|
|
1329
1326
|
if (this.#queueCapability === "memory")
|
|
1330
|
-
this.#
|
|
1327
|
+
this.#warnCapability("Offline queue persistence is unavailable; queued data is memory-only.");
|
|
1328
|
+
if (this.queueCapability.multiTabSync === "disabled_unsafe_environment")
|
|
1329
|
+
this.#warnCapability(
|
|
1330
|
+
"Web Locks is unavailable; automatic cross-tab synchronization is disabled. Sync must be triggered by the foreground tab."
|
|
1331
|
+
);
|
|
1331
1332
|
this.#loaded = true;
|
|
1332
1333
|
}
|
|
1333
1334
|
/** Releases browser listeners when the queue is no longer used. */
|
|
@@ -1338,7 +1339,6 @@ var OfflineQueue = class {
|
|
|
1338
1339
|
this.#storageListener = void 0;
|
|
1339
1340
|
this.#channel?.close();
|
|
1340
1341
|
this.#channel = null;
|
|
1341
|
-
this.#releaseSyncLock();
|
|
1342
1342
|
}
|
|
1343
1343
|
/** Selects a rally/user queue scope and loads its pending operations. */
|
|
1344
1344
|
async setScope(rallyId, userId) {
|
|
@@ -1450,6 +1450,12 @@ var OfflineQueue = class {
|
|
|
1450
1450
|
async retrySync(sender = this.#sender) {
|
|
1451
1451
|
return this.sync(sender);
|
|
1452
1452
|
}
|
|
1453
|
+
/** Applies a server-side batch response while preserving retryable operations. */
|
|
1454
|
+
async applySyncProgress(response) {
|
|
1455
|
+
await this.initialize();
|
|
1456
|
+
for (const result of response.results)
|
|
1457
|
+
await this.#applySyncOperationResult(result, response.currentState);
|
|
1458
|
+
}
|
|
1453
1459
|
async #run(sender) {
|
|
1454
1460
|
const locks = globalThis.navigator?.locks;
|
|
1455
1461
|
if (locks !== void 0 && typeof locks.request === "function") {
|
|
@@ -1466,7 +1472,7 @@ var OfflineQueue = class {
|
|
|
1466
1472
|
return false;
|
|
1467
1473
|
}
|
|
1468
1474
|
callbackStarted = true;
|
|
1469
|
-
await this.#
|
|
1475
|
+
await this.#runSingleTab(sender);
|
|
1470
1476
|
return true;
|
|
1471
1477
|
}
|
|
1472
1478
|
);
|
|
@@ -1476,22 +1482,12 @@ var OfflineQueue = class {
|
|
|
1476
1482
|
if (callbackStarted) throw error;
|
|
1477
1483
|
}
|
|
1478
1484
|
}
|
|
1479
|
-
|
|
1480
|
-
this.#warnMemoryLock(
|
|
1481
|
-
"No cross-tab storage lock is available; offline sync is single-tab only."
|
|
1482
|
-
);
|
|
1483
|
-
await this.#runWithStorageLock(sender);
|
|
1485
|
+
await this.#runSingleTab(sender);
|
|
1484
1486
|
}
|
|
1485
|
-
async #
|
|
1487
|
+
async #runSingleTab(sender) {
|
|
1486
1488
|
this.#state = "syncing";
|
|
1487
1489
|
this.#error = null;
|
|
1488
1490
|
this.#changeListener?.();
|
|
1489
|
-
if (!this.#acquireSyncLock()) {
|
|
1490
|
-
await this.#reloadFromStorage();
|
|
1491
|
-
this.#state = "idle";
|
|
1492
|
-
this.#changeListener?.();
|
|
1493
|
-
return;
|
|
1494
|
-
}
|
|
1495
1491
|
try {
|
|
1496
1492
|
while (this.#operations.length > 0) {
|
|
1497
1493
|
const operation = this.#operations[0];
|
|
@@ -1563,8 +1559,6 @@ var OfflineQueue = class {
|
|
|
1563
1559
|
this.#error = cause instanceof Error ? cause : new Error(String(cause));
|
|
1564
1560
|
this.#changeListener?.();
|
|
1565
1561
|
throw this.#error;
|
|
1566
|
-
} finally {
|
|
1567
|
-
this.#releaseSyncLock();
|
|
1568
1562
|
}
|
|
1569
1563
|
}
|
|
1570
1564
|
async #updateOperationStatus(status, attempts) {
|
|
@@ -1574,6 +1568,59 @@ var OfflineQueue = class {
|
|
|
1574
1568
|
await this.#storage.save(this.#storageKey(), this.#operations);
|
|
1575
1569
|
this.#announceChange();
|
|
1576
1570
|
}
|
|
1571
|
+
async #applySyncOperationResult(result, currentState) {
|
|
1572
|
+
const operationIndex = this.#operations.findIndex(
|
|
1573
|
+
(operation2) => offlineOperationId(operation2) === result.operationId
|
|
1574
|
+
);
|
|
1575
|
+
const operation = operationIndex < 0 ? void 0 : this.#operations[operationIndex];
|
|
1576
|
+
if (operation === void 0) return;
|
|
1577
|
+
if (result.status === "FAILED_RETRYABLE") {
|
|
1578
|
+
const attempts = (operation.attempts ?? 0) + 1;
|
|
1579
|
+
this.#operations = [
|
|
1580
|
+
...this.#operations.slice(0, operationIndex),
|
|
1581
|
+
{ ...operation, status: "FAILED_RETRYABLE", attempts },
|
|
1582
|
+
...this.#operations.slice(operationIndex + 1)
|
|
1583
|
+
];
|
|
1584
|
+
await this.#storage.save(this.#storageKey(), this.#operations);
|
|
1585
|
+
this.#announceChange();
|
|
1586
|
+
await this.#syncResultListener?.({
|
|
1587
|
+
operation,
|
|
1588
|
+
status: "RETRYABLE_ERROR",
|
|
1589
|
+
error: { code: "FAILED_RETRYABLE", message: result.error }
|
|
1590
|
+
});
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
this.#operations = [
|
|
1594
|
+
...this.#operations.slice(0, operationIndex),
|
|
1595
|
+
...this.#operations.slice(operationIndex + 1)
|
|
1596
|
+
];
|
|
1597
|
+
if (result.status === "REJECTED_PERMANENT") {
|
|
1598
|
+
const attempts = (operation.attempts ?? 0) + 1;
|
|
1599
|
+
const error = {
|
|
1600
|
+
code: result.errorCode,
|
|
1601
|
+
message: result.reason
|
|
1602
|
+
};
|
|
1603
|
+
this.#rejectedHistory = [
|
|
1604
|
+
...this.#rejectedHistory,
|
|
1605
|
+
{
|
|
1606
|
+
operation: { ...operation, status: "REJECTED_PERMANENT", attempts },
|
|
1607
|
+
reason: error,
|
|
1608
|
+
errorCode: result.errorCode,
|
|
1609
|
+
rejectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1610
|
+
attempts
|
|
1611
|
+
}
|
|
1612
|
+
];
|
|
1613
|
+
await this.#saveRejectedHistory();
|
|
1614
|
+
}
|
|
1615
|
+
await this.#storage.save(this.#storageKey(), this.#operations);
|
|
1616
|
+
this.#announceChange();
|
|
1617
|
+
await this.#syncResultListener?.({
|
|
1618
|
+
operation,
|
|
1619
|
+
status: result.status,
|
|
1620
|
+
state: currentState,
|
|
1621
|
+
...result.status === "REJECTED_PERMANENT" ? { error: { code: result.errorCode, message: result.reason } } : {}
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1577
1624
|
async #rejectFailedPrerequisite(operation) {
|
|
1578
1625
|
if (operation.kind !== "checkIn" || this.#replayConfig === void 0) return false;
|
|
1579
1626
|
const spot2 = this.#replayConfig.spots.find(
|
|
@@ -1627,20 +1674,6 @@ var OfflineQueue = class {
|
|
|
1627
1674
|
try {
|
|
1628
1675
|
this.#channel = new Channel("stamprally:queue-sync");
|
|
1629
1676
|
this.#channel.addEventListener("message", (event) => {
|
|
1630
|
-
if (typeof event.data === "object" && event.data !== null) {
|
|
1631
|
-
const data = event.data;
|
|
1632
|
-
if (data.type === "lock" && data.lockKey === this.#lockKey()) {
|
|
1633
|
-
this.#observedLocks.set(data.lockKey, {
|
|
1634
|
-
owner: typeof data.owner === "string" ? data.owner : "unknown",
|
|
1635
|
-
expiresAt: typeof data.expiresAt === "number" ? data.expiresAt : 0
|
|
1636
|
-
});
|
|
1637
|
-
return;
|
|
1638
|
-
}
|
|
1639
|
-
if (data.type === "unlock" && data.lockKey === this.#lockKey()) {
|
|
1640
|
-
this.#observedLocks.delete(data.lockKey);
|
|
1641
|
-
return;
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
1677
|
if (typeof event.data === "object" && event.data !== null && "key" in event.data && event.data.key === this.#storageKey())
|
|
1645
1678
|
void this.#reloadFromStorage();
|
|
1646
1679
|
});
|
|
@@ -1667,57 +1700,6 @@ var OfflineQueue = class {
|
|
|
1667
1700
|
} catch {
|
|
1668
1701
|
}
|
|
1669
1702
|
}
|
|
1670
|
-
#lockKey() {
|
|
1671
|
-
return `${this.#storageKey()}:sync-lock`;
|
|
1672
|
-
}
|
|
1673
|
-
#acquireSyncLock() {
|
|
1674
|
-
const key = this.#lockKey();
|
|
1675
|
-
const now = Date.now();
|
|
1676
|
-
const local = syncLocks.get(key);
|
|
1677
|
-
if (local !== void 0 && local.expiresAt > now && local.owner !== this.#instanceId)
|
|
1678
|
-
return false;
|
|
1679
|
-
const observed = this.#observedLocks.get(key);
|
|
1680
|
-
if (observed !== void 0 && observed.expiresAt > now && observed.owner !== this.#instanceId)
|
|
1681
|
-
return false;
|
|
1682
|
-
if (this.#lockStorage !== null) {
|
|
1683
|
-
try {
|
|
1684
|
-
const existing = this.#lockStorage.getItem(key);
|
|
1685
|
-
if (existing !== null) {
|
|
1686
|
-
const parsed = JSON.parse(existing);
|
|
1687
|
-
if (typeof parsed === "object" && parsed !== null && typeof parsed.expiresAt === "number" && parsed.expiresAt > now && parsed.owner !== this.#instanceId)
|
|
1688
|
-
return false;
|
|
1689
|
-
}
|
|
1690
|
-
this.#lockStorage.setItem(
|
|
1691
|
-
key,
|
|
1692
|
-
JSON.stringify({ owner: this.#instanceId, expiresAt: now + SYNC_LOCK_TTL_MS })
|
|
1693
|
-
);
|
|
1694
|
-
this.#channel?.postMessage({
|
|
1695
|
-
type: "lock",
|
|
1696
|
-
lockKey: key,
|
|
1697
|
-
owner: this.#instanceId,
|
|
1698
|
-
expiresAt: now + SYNC_LOCK_TTL_MS
|
|
1699
|
-
});
|
|
1700
|
-
} catch {
|
|
1701
|
-
this.#warnMemoryLock("Persistent lock access failed; offline sync is single-tab only.");
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
syncLocks.set(key, { owner: this.#instanceId, expiresAt: now + SYNC_LOCK_TTL_MS });
|
|
1705
|
-
return true;
|
|
1706
|
-
}
|
|
1707
|
-
#releaseSyncLock() {
|
|
1708
|
-
const key = this.#lockKey();
|
|
1709
|
-
const current = syncLocks.get(key);
|
|
1710
|
-
if (current?.owner === this.#instanceId) syncLocks.delete(key);
|
|
1711
|
-
if (this.#lockStorage !== null) {
|
|
1712
|
-
try {
|
|
1713
|
-
const value = this.#lockStorage.getItem(key);
|
|
1714
|
-
if (value !== null && JSON.parse(value).owner === this.#instanceId)
|
|
1715
|
-
this.#lockStorage.removeItem?.(key);
|
|
1716
|
-
this.#channel?.postMessage({ type: "unlock", lockKey: key, owner: this.#instanceId });
|
|
1717
|
-
} catch {
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
1703
|
#storageKey() {
|
|
1722
1704
|
if (this.#configuredKey !== void 0) return this.#configuredKey;
|
|
1723
1705
|
return `stamprally:queue:${this.#rallyId ?? "unscoped"}:${this.#userId ?? "anonymous"}`;
|
|
@@ -1740,17 +1722,22 @@ var OfflineQueue = class {
|
|
|
1740
1722
|
async #saveRejectedHistory() {
|
|
1741
1723
|
await this.#storage.saveRejectedHistory?.(this.#storageKey(), this.#rejectedHistory);
|
|
1742
1724
|
}
|
|
1743
|
-
#
|
|
1744
|
-
if (this.#
|
|
1745
|
-
this.#
|
|
1725
|
+
#warnCapability(message) {
|
|
1726
|
+
if (this.#warnedCapabilityMessages.has(message)) return;
|
|
1727
|
+
this.#warnedCapabilityMessages.add(message);
|
|
1746
1728
|
console.warn(`[@stamprally/core] ${message}`);
|
|
1747
1729
|
this.#capabilityWarningListener?.({
|
|
1748
1730
|
type: "STORAGE_CAPABILITY_WARNING",
|
|
1749
1731
|
storageCapability: this.storageCapability === "memory" ? "memory" : "volatile_single_tab",
|
|
1732
|
+
multiTabSync: "disabled_unsafe_environment",
|
|
1750
1733
|
isStoragePersistent: this.isStoragePersistent,
|
|
1751
1734
|
message
|
|
1752
1735
|
});
|
|
1753
1736
|
}
|
|
1737
|
+
#hasWebLocks() {
|
|
1738
|
+
const locks = globalThis.navigator?.locks;
|
|
1739
|
+
return locks !== void 0 && typeof locks.request === "function";
|
|
1740
|
+
}
|
|
1754
1741
|
};
|
|
1755
1742
|
function normalizeOperation(operation) {
|
|
1756
1743
|
const status = operation.status;
|
|
@@ -1942,6 +1929,9 @@ function applyOptimisticRewardClaim(state, rewardId, reward2, now) {
|
|
|
1942
1929
|
function errorMessage(error, fallback) {
|
|
1943
1930
|
return error !== void 0 && "message" in error && typeof error.message === "string" ? error.message : fallback;
|
|
1944
1931
|
}
|
|
1932
|
+
function isSyncProgressResponse(value) {
|
|
1933
|
+
return typeof value === "object" && value !== null && "results" in value && Array.isArray(value.results) && "currentState" in value && "syncTimestamp" in value;
|
|
1934
|
+
}
|
|
1945
1935
|
var StampRallyClient = class {
|
|
1946
1936
|
#listeners = /* @__PURE__ */ new Set();
|
|
1947
1937
|
#eventListeners = /* @__PURE__ */ new Set();
|
|
@@ -1999,7 +1989,10 @@ var StampRallyClient = class {
|
|
|
1999
1989
|
return this.#syncRevision;
|
|
2000
1990
|
}
|
|
2001
1991
|
get queueCapability() {
|
|
2002
|
-
return this.#offlineQueue?.queueCapability ??
|
|
1992
|
+
return this.#offlineQueue?.queueCapability ?? {
|
|
1993
|
+
storage: "custom",
|
|
1994
|
+
multiTabSync: "disabled_unsafe_environment"
|
|
1995
|
+
};
|
|
2003
1996
|
}
|
|
2004
1997
|
get storageCapability() {
|
|
2005
1998
|
return this.#offlineQueue?.storageCapability ?? "custom";
|
|
@@ -2216,7 +2209,9 @@ var StampRallyClient = class {
|
|
|
2216
2209
|
this.#syncMetrics = { processed: 0, failed: 0 };
|
|
2217
2210
|
try {
|
|
2218
2211
|
const current = await this.initialize();
|
|
2219
|
-
|
|
2212
|
+
const queuedOperations = this.#offlineQueue?.operations ?? [];
|
|
2213
|
+
const hasPerOperationAdapters = adapter?.checkIn !== void 0 || adapter?.claimReward !== void 0;
|
|
2214
|
+
if (this.#offlineQueue !== void 0 && adapter !== void 0 && hasPerOperationAdapters) {
|
|
2220
2215
|
await this.#offlineQueue.sync(async (operation) => {
|
|
2221
2216
|
if (operation.kind === "checkIn") {
|
|
2222
2217
|
if (adapter.checkIn === void 0)
|
|
@@ -2233,12 +2228,16 @@ var StampRallyClient = class {
|
|
|
2233
2228
|
return;
|
|
2234
2229
|
}
|
|
2235
2230
|
const localState = this.#state ?? current;
|
|
2236
|
-
const
|
|
2231
|
+
const syncResult = await adapter.sync({
|
|
2237
2232
|
rallyId: this.#config.id,
|
|
2238
2233
|
userId: this.#userId,
|
|
2239
2234
|
state: localState,
|
|
2240
|
-
...this.#userId === this.#anonymousSessionId ? { anonymousSessionId: this.#anonymousSessionId } : {}
|
|
2235
|
+
...this.#userId === this.#anonymousSessionId ? { anonymousSessionId: this.#anonymousSessionId } : {},
|
|
2236
|
+
...this.#offlineQueue !== void 0 && !hasPerOperationAdapters && queuedOperations.length > 0 ? { operations: queuedOperations } : {}
|
|
2241
2237
|
});
|
|
2238
|
+
const progress = isSyncProgressResponse(syncResult) ? syncResult : void 0;
|
|
2239
|
+
if (progress !== void 0) await this.#offlineQueue?.applySyncProgress(progress);
|
|
2240
|
+
const serverState = isSyncProgressResponse(syncResult) ? syncResult.currentState : syncResult;
|
|
2242
2241
|
const resolved = rebuildUserStateFromLog(
|
|
2243
2242
|
serverState,
|
|
2244
2243
|
this.#offlineQueue?.operations ?? [],
|