@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/README.md +3 -4
- package/dist/index.cjs +53 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +53 -32
- 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.19.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.19.0",
|
|
11
11
|
title: "City Tour",
|
|
12
12
|
spots: [{ id: "station", orderIndex: 0, name: "Central Station", conditions: [{ type: "passcode" }] }],
|
|
13
13
|
rewards: [],
|
|
@@ -28,8 +28,7 @@ The browser detectors `getCurrentGeoContext`, `readNfcContext`, and `readQrConte
|
|
|
28
28
|
Configure `OfflineQueue` with `rallyId` and `userId` to persist pending work under
|
|
29
29
|
`stamprally:queue:<rallyId>:<userId-or-anonymous>`. `switchUser` loads the other
|
|
30
30
|
user's queue. The default `authoritative_replay` policy uses the server state as
|
|
31
|
-
the conflict baseline;
|
|
32
|
-
option, and `server_wins` uses the server state unchanged. Sync adapters may return `ACCEPTED`, `REJECTED_PERMANENT`,
|
|
31
|
+
the conflict baseline; it is the only supported conflict policy. Sync adapters may return `ACCEPTED`, `REJECTED_PERMANENT`,
|
|
33
32
|
or `RETRYABLE_ERROR`; only the first two remove an operation.
|
|
34
33
|
|
|
35
34
|
When no authenticated user is supplied, the client creates a persistent UUID v4
|
package/dist/index.cjs
CHANGED
|
@@ -1581,9 +1581,10 @@ var OfflineQueue = class {
|
|
|
1581
1581
|
);
|
|
1582
1582
|
if (spot2 === void 0) return false;
|
|
1583
1583
|
const failedSpots = new Set(
|
|
1584
|
-
|
|
1585
|
-
(entry) => entry.operation
|
|
1586
|
-
|
|
1584
|
+
[
|
|
1585
|
+
...this.#rejectedHistory.map((entry) => entry.operation),
|
|
1586
|
+
...this.#operations.filter((candidate) => candidate.status === "REJECTED_PERMANENT")
|
|
1587
|
+
].filter((candidate) => candidate.kind === "checkIn").map((candidate) => candidate.kind === "checkIn" ? candidate.request.spotId : void 0).filter((spotId) => spotId !== void 0)
|
|
1587
1588
|
);
|
|
1588
1589
|
if (!spot2.prerequisites?.some((prerequisite) => failedSpots.has(prerequisite))) return false;
|
|
1589
1590
|
const error = {
|
|
@@ -1794,18 +1795,18 @@ function applyInventoryDelta(state, previous, optimistic) {
|
|
|
1794
1795
|
if (before !== void 0 && after !== void 0)
|
|
1795
1796
|
rewardRemaining[key] = Math.max(0, (currentRewards[key] ?? before) + after - before);
|
|
1796
1797
|
}
|
|
1797
|
-
|
|
1798
|
-
...
|
|
1799
|
-
|
|
1800
|
-
...currentInventory.sharedRemaining === void 0 || sharedDelta === void 0 ? {} : { sharedRemaining: Math.max(0, currentInventory.sharedRemaining + sharedDelta) },
|
|
1801
|
-
...Object.keys(rewardRemaining).length === 0 ? {} : { rewardRemaining }
|
|
1802
|
-
}
|
|
1798
|
+
const nextInventory = {
|
|
1799
|
+
...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) },
|
|
1800
|
+
...Object.keys(rewardRemaining).length === 0 ? {} : { rewardRemaining }
|
|
1803
1801
|
};
|
|
1802
|
+
return { ...state, inventory: nextInventory };
|
|
1804
1803
|
}
|
|
1805
|
-
function applyOperation(state, operation, config) {
|
|
1804
|
+
function applyOperation(state, operation, config, rejectedCheckIns) {
|
|
1806
1805
|
if (operation.kind === "checkIn") {
|
|
1807
1806
|
const spot2 = config?.spots.find((candidate) => candidate.id === operation.request.spotId);
|
|
1808
|
-
if (spot2?.prerequisites?.some(
|
|
1807
|
+
if (spot2?.prerequisites?.some(
|
|
1808
|
+
(id2) => rejectedCheckIns.has(id2) || !state.records.some((record2) => record2.stampId === id2)
|
|
1809
|
+
))
|
|
1809
1810
|
return { state, prerequisiteFailed: true };
|
|
1810
1811
|
if (state.records.some((record2) => record2.stampId === operation.request.spotId))
|
|
1811
1812
|
return { state, prerequisiteFailed: false };
|
|
@@ -1852,11 +1853,21 @@ function rebuildUserStateFromLog(baselineOrOptions, operationsArgument, configAr
|
|
|
1852
1853
|
function rebuildUserStateLog(baseline, operations, config) {
|
|
1853
1854
|
let state = cloneState(baseline);
|
|
1854
1855
|
const rejectedOperationIds = [];
|
|
1856
|
+
const rejectedCheckIns = new Set(
|
|
1857
|
+
operations.filter(
|
|
1858
|
+
(operation) => operation.status === "REJECTED_PERMANENT" && operation.kind === "checkIn"
|
|
1859
|
+
).map((operation) => operation.kind === "checkIn" ? operation.request.spotId : void 0).filter((spotId) => spotId !== void 0)
|
|
1860
|
+
);
|
|
1855
1861
|
for (const operation of operations) {
|
|
1862
|
+
if (operation.status === "REJECTED_PERMANENT") {
|
|
1863
|
+
if (operation.kind === "checkIn") rejectedCheckIns.add(operation.request.spotId);
|
|
1864
|
+
continue;
|
|
1865
|
+
}
|
|
1856
1866
|
if (!isReplayable(operation)) continue;
|
|
1857
|
-
const replay = applyOperation(state, operation, config);
|
|
1867
|
+
const replay = applyOperation(state, operation, config, rejectedCheckIns);
|
|
1858
1868
|
if (replay.prerequisiteFailed) {
|
|
1859
1869
|
rejectedOperationIds.push(operationId(operation));
|
|
1870
|
+
if (operation.kind === "checkIn") rejectedCheckIns.add(operation.request.spotId);
|
|
1860
1871
|
continue;
|
|
1861
1872
|
}
|
|
1862
1873
|
state = replay.state;
|
|
@@ -1904,6 +1915,30 @@ function emptyState(config, userId, now) {
|
|
|
1904
1915
|
updatedAt: now
|
|
1905
1916
|
};
|
|
1906
1917
|
}
|
|
1918
|
+
function applyOptimisticRewardClaim(state, rewardId, reward2, now) {
|
|
1919
|
+
if (reward2.claimTicketNumber === void 0 || state.inventory === void 0)
|
|
1920
|
+
return {
|
|
1921
|
+
...state,
|
|
1922
|
+
rewards: state.rewards.map((item) => item.rewardId === rewardId ? reward2 : item),
|
|
1923
|
+
updatedAt: now
|
|
1924
|
+
};
|
|
1925
|
+
const currentInventory = state.inventory;
|
|
1926
|
+
const currentRewardRemaining = currentInventory.rewardRemaining?.[rewardId];
|
|
1927
|
+
return {
|
|
1928
|
+
...state,
|
|
1929
|
+
rewards: state.rewards.map((item) => item.rewardId === rewardId ? reward2 : item),
|
|
1930
|
+
updatedAt: now,
|
|
1931
|
+
inventory: {
|
|
1932
|
+
...currentInventory.sharedRemaining === void 0 ? {} : { sharedRemaining: Math.max(0, currentInventory.sharedRemaining - 1) },
|
|
1933
|
+
...currentInventory.rewardRemaining === void 0 || currentRewardRemaining === void 0 ? {} : {
|
|
1934
|
+
rewardRemaining: {
|
|
1935
|
+
...currentInventory.rewardRemaining,
|
|
1936
|
+
[rewardId]: Math.max(0, currentRewardRemaining - 1)
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1907
1942
|
function errorMessage(error, fallback) {
|
|
1908
1943
|
return error !== void 0 && "message" in error && typeof error.message === "string" ? error.message : fallback;
|
|
1909
1944
|
}
|
|
@@ -1931,6 +1966,9 @@ var StampRallyClient = class {
|
|
|
1931
1966
|
this.#offlineQueue = this.#options.offlineQueue;
|
|
1932
1967
|
this.#offlineQueue?.setReplayConfig(config);
|
|
1933
1968
|
this.#offlineQueue?.setSyncResultListener((event) => this.#handleOfflineSyncResult(event));
|
|
1969
|
+
this.#offlineQueue?.setCapabilityWarningListener(
|
|
1970
|
+
(warning) => this.#emitEvent({ type: "storageCapabilityWarning", warning })
|
|
1971
|
+
);
|
|
1934
1972
|
this.#offlineQueue?.setChangeListener(() => {
|
|
1935
1973
|
this.#syncRevision += 1;
|
|
1936
1974
|
if (this.#state !== null) this.#emit(this.#state);
|
|
@@ -2161,23 +2199,13 @@ var StampRallyClient = class {
|
|
|
2161
2199
|
return result.ok ? this.#commitClaim(result) : this.#fail(result.error);
|
|
2162
2200
|
} catch (error) {
|
|
2163
2201
|
if (this.#offlineQueue === void 0) throw error;
|
|
2164
|
-
const next2 =
|
|
2165
|
-
...current,
|
|
2166
|
-
rewards: current.rewards.map(
|
|
2167
|
-
(item) => item.rewardId === rewardId ? local.value : item
|
|
2168
|
-
),
|
|
2169
|
-
updatedAt: now
|
|
2170
|
-
};
|
|
2202
|
+
const next2 = applyOptimisticRewardClaim(current, rewardId, local.value, now);
|
|
2171
2203
|
await this.#offlineQueue.enqueueClaimReward(request, next2);
|
|
2172
2204
|
await this.#storage.save(next2);
|
|
2173
2205
|
return this.#commitClaim({ ok: true, value: { state: next2, reward: local.value } });
|
|
2174
2206
|
}
|
|
2175
2207
|
}
|
|
2176
|
-
const next =
|
|
2177
|
-
...current,
|
|
2178
|
-
rewards: current.rewards.map((item) => item.rewardId === rewardId ? local.value : item),
|
|
2179
|
-
updatedAt: now
|
|
2180
|
-
};
|
|
2208
|
+
const next = applyOptimisticRewardClaim(current, rewardId, local.value, now);
|
|
2181
2209
|
await this.#storage.save(next);
|
|
2182
2210
|
return this.#commitClaim({ ok: true, value: { state: next, reward: local.value } });
|
|
2183
2211
|
});
|
|
@@ -3251,7 +3279,7 @@ function validateRallyConfigRelations(config) {
|
|
|
3251
3279
|
["stockKey", reward2.stockKey],
|
|
3252
3280
|
["secondaryStockKey", reward2.secondaryStockKey]
|
|
3253
3281
|
]) {
|
|
3254
|
-
if (key !== void 0 && key !== "__shared__" && inventory?.[key] === void 0)
|
|
3282
|
+
if (key !== void 0 && (key === "__shared__" && inventory?.sharedStock === void 0 || key !== "__shared__" && inventory?.[key] === void 0))
|
|
3255
3283
|
add(
|
|
3256
3284
|
errors,
|
|
3257
3285
|
`rewards[${index}].${field}`,
|
|
@@ -3259,13 +3287,6 @@ function validateRallyConfigRelations(config) {
|
|
|
3259
3287
|
"missing_inventory_key"
|
|
3260
3288
|
);
|
|
3261
3289
|
}
|
|
3262
|
-
if (reward2.stockKey === "__shared__" && inventory?.sharedStock === void 0)
|
|
3263
|
-
add(
|
|
3264
|
-
errors,
|
|
3265
|
-
`rewards[${index}].stockKey`,
|
|
3266
|
-
"sharedStock is not defined.",
|
|
3267
|
-
"missing_inventory_key"
|
|
3268
|
-
);
|
|
3269
3290
|
});
|
|
3270
3291
|
const visiting = /* @__PURE__ */ new Set();
|
|
3271
3292
|
const visited = /* @__PURE__ */ new Set();
|