@stamprally/core 0.11.0 → 0.12.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.cjs +78 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +78 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -99,6 +99,47 @@ function issueClaimTicketNumber(reward2, currentState, options = {}) {
|
|
|
99
99
|
return { ...currentState, claimTicketNumber };
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
// src/engine/sync.ts
|
|
103
|
+
function latestTimestamp(serverTimestamp, localTimestamp) {
|
|
104
|
+
const serverTime = Date.parse(serverTimestamp);
|
|
105
|
+
const localTime = Date.parse(localTimestamp);
|
|
106
|
+
if (!Number.isNaN(serverTime) && !Number.isNaN(localTime))
|
|
107
|
+
return serverTime >= localTime ? serverTimestamp : localTimestamp;
|
|
108
|
+
if (!Number.isNaN(serverTime)) return serverTimestamp;
|
|
109
|
+
if (!Number.isNaN(localTime)) return localTimestamp;
|
|
110
|
+
return serverTimestamp >= localTimestamp ? serverTimestamp : localTimestamp;
|
|
111
|
+
}
|
|
112
|
+
function mergeRewardStates(serverRewards, localRewards) {
|
|
113
|
+
const merged = new Map(serverRewards.map((reward2) => [reward2.rewardId, reward2]));
|
|
114
|
+
for (const localReward of localRewards) {
|
|
115
|
+
const serverReward = merged.get(localReward.rewardId);
|
|
116
|
+
if (serverReward === void 0) {
|
|
117
|
+
merged.set(localReward.rewardId, localReward);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (localReward.status === "CONSUMED" && serverReward.status !== "CONSUMED")
|
|
121
|
+
merged.set(localReward.rewardId, localReward);
|
|
122
|
+
}
|
|
123
|
+
return [...merged.values()];
|
|
124
|
+
}
|
|
125
|
+
function resolveRallyStateConflict(serverState, localState, options = { policy: "merge" }) {
|
|
126
|
+
if (options.policy === "server_wins") return serverState;
|
|
127
|
+
const records = [...serverState.records];
|
|
128
|
+
const knownStamps = new Set(records.map((record) => record.stampId));
|
|
129
|
+
for (const record of localState.records) {
|
|
130
|
+
if (!knownStamps.has(record.stampId)) {
|
|
131
|
+
records.push(record);
|
|
132
|
+
knownStamps.add(record.stampId);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
...serverState,
|
|
137
|
+
records,
|
|
138
|
+
rewards: mergeRewardStates(serverState.rewards, localState.rewards),
|
|
139
|
+
updatedAt: latestTimestamp(serverState.updatedAt, localState.updatedAt)
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
102
143
|
// src/detectors/types.ts
|
|
103
144
|
function createDetectorError(detector, code, message, cause) {
|
|
104
145
|
return cause === void 0 ? { detector, code, message } : { detector, code, message, cause };
|
|
@@ -1000,6 +1041,7 @@ var StampRallyClient = class {
|
|
|
1000
1041
|
this.#storage = this.#options.storage ?? new InMemoryStorage();
|
|
1001
1042
|
this.#userId = this.#options.userId ?? null;
|
|
1002
1043
|
this.#offlineQueue = this.#options.offlineQueue;
|
|
1044
|
+
this.#offlineQueue?.setSyncResultListener((event) => this.#handleOfflineSyncResult(event));
|
|
1003
1045
|
}
|
|
1004
1046
|
getConfig() {
|
|
1005
1047
|
return this.#config;
|
|
@@ -1217,12 +1259,19 @@ var StampRallyClient = class {
|
|
|
1217
1259
|
});
|
|
1218
1260
|
}
|
|
1219
1261
|
if (adapter?.sync === void 0) {
|
|
1220
|
-
this.#emitEvent({ type: "sync", state: current });
|
|
1262
|
+
this.#emitEvent({ type: "sync", state: this.#state ?? current });
|
|
1221
1263
|
return;
|
|
1222
1264
|
}
|
|
1223
|
-
const
|
|
1224
|
-
|
|
1225
|
-
|
|
1265
|
+
const serverState = await adapter.sync({
|
|
1266
|
+
rallyId: this.#config.id,
|
|
1267
|
+
userId: this.#userId,
|
|
1268
|
+
state: this.#state ?? current
|
|
1269
|
+
});
|
|
1270
|
+
const localState = this.#state ?? current;
|
|
1271
|
+
const merged = this.#offlineQueue === void 0 ? serverState : resolveRallyStateConflict(serverState, localState, {
|
|
1272
|
+
policy: this.#offlineQueue.conflictPolicy
|
|
1273
|
+
});
|
|
1274
|
+
const next = this.#reconcile(merged);
|
|
1226
1275
|
await this.#storage.save(next);
|
|
1227
1276
|
this.#state = next;
|
|
1228
1277
|
this.#emit(next);
|
|
@@ -1282,6 +1331,16 @@ var StampRallyClient = class {
|
|
|
1282
1331
|
)
|
|
1283
1332
|
};
|
|
1284
1333
|
}
|
|
1334
|
+
async #handleOfflineSyncResult(event) {
|
|
1335
|
+
if (event.state !== void 0) {
|
|
1336
|
+
const next = this.#reconcile(event.state);
|
|
1337
|
+
await this.#storage.save(next);
|
|
1338
|
+
this.#state = next;
|
|
1339
|
+
this.#emit(next);
|
|
1340
|
+
}
|
|
1341
|
+
if ("ok" in event.result && !event.result.ok)
|
|
1342
|
+
this.#emitEvent({ type: "error", error: event.result.error });
|
|
1343
|
+
}
|
|
1285
1344
|
#now() {
|
|
1286
1345
|
return this.#options.clock?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1287
1346
|
}
|
|
@@ -1418,6 +1477,7 @@ var OfflineQueue = class {
|
|
|
1418
1477
|
#error = null;
|
|
1419
1478
|
#sender;
|
|
1420
1479
|
#syncPromise = null;
|
|
1480
|
+
#syncResultListener;
|
|
1421
1481
|
constructor(options = {}) {
|
|
1422
1482
|
if (options.storage !== void 0) this.#storage = options.storage;
|
|
1423
1483
|
else if (options.storageLike !== void 0 && options.storageLike !== null)
|
|
@@ -1439,6 +1499,12 @@ var OfflineQueue = class {
|
|
|
1439
1499
|
get operations() {
|
|
1440
1500
|
return this.#operations;
|
|
1441
1501
|
}
|
|
1502
|
+
get conflictPolicy() {
|
|
1503
|
+
return this.#conflictPolicy;
|
|
1504
|
+
}
|
|
1505
|
+
setSyncResultListener(listener) {
|
|
1506
|
+
this.#syncResultListener = listener;
|
|
1507
|
+
}
|
|
1442
1508
|
async initialize() {
|
|
1443
1509
|
if (this.#loaded) return;
|
|
1444
1510
|
this.#operations = [...await this.#storage.load(this.#key)];
|
|
@@ -1491,8 +1557,12 @@ var OfflineQueue = class {
|
|
|
1491
1557
|
} catch (cause) {
|
|
1492
1558
|
throw cause instanceof Error ? cause : new Error(String(cause));
|
|
1493
1559
|
}
|
|
1494
|
-
|
|
1495
|
-
|
|
1560
|
+
const state = "conflict" in result && result.conflict === true ? await this.resolveConflict(operation, result.localState, result.serverState) : "ok" in result && result.ok ? result.value.state : void 0;
|
|
1561
|
+
await this.#syncResultListener?.({
|
|
1562
|
+
operation,
|
|
1563
|
+
result,
|
|
1564
|
+
...state === void 0 ? {} : { state }
|
|
1565
|
+
});
|
|
1496
1566
|
this.#operations = this.#operations.slice(1);
|
|
1497
1567
|
await this.#storage.save(this.#key, this.#operations);
|
|
1498
1568
|
}
|
|
@@ -1506,9 +1576,7 @@ var OfflineQueue = class {
|
|
|
1506
1576
|
async resolveConflict(operation, localState, serverState) {
|
|
1507
1577
|
const configured = this.#onSyncConflict;
|
|
1508
1578
|
const policy = (typeof configured === "function" ? await configured({ operation, localState, serverState }) : configured) ?? this.#conflictPolicy;
|
|
1509
|
-
|
|
1510
|
-
return;
|
|
1511
|
-
}
|
|
1579
|
+
return resolveRallyStateConflict(serverState, localState, { policy });
|
|
1512
1580
|
}
|
|
1513
1581
|
};
|
|
1514
1582
|
|
|
@@ -2437,6 +2505,7 @@ exports.readNfcContext = readNfcContext;
|
|
|
2437
2505
|
exports.readQrContext = readQrContext;
|
|
2438
2506
|
exports.reconcileRewardStates = reconcileRewardStates;
|
|
2439
2507
|
exports.resolveLocalizedText = resolveLocalizedText;
|
|
2508
|
+
exports.resolveRallyStateConflict = resolveRallyStateConflict;
|
|
2440
2509
|
exports.safeParseAdminConfig = safeParseAdminConfig;
|
|
2441
2510
|
exports.safeParsePublicConfig = safeParsePublicConfig;
|
|
2442
2511
|
exports.sanitizeAdminConfig = sanitizeAdminConfig;
|