@stamprally/server 0.20.0 → 0.21.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 +9 -3
- package/dist/index.cjs +58 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.js +58 -28
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @stamprally/server v0.
|
|
1
|
+
# @stamprally/server v0.21.0
|
|
2
2
|
|
|
3
3
|
Web Standard `Request` / `Response` handlers for server-authoritative check-ins and reward claims.
|
|
4
4
|
|
|
@@ -33,9 +33,15 @@ timestamp, preserving FIFO order for equal timestamps, and returns
|
|
|
33
33
|
`{ results, currentState, syncTimestamp }`. A permanently rejected
|
|
34
34
|
operation does not stop unrelated operations; a later operation depending on that
|
|
35
35
|
resource receives `REJECTED_PREREQUISITE_FAILED`. Retryable failures are returned
|
|
36
|
-
as `FAILED_RETRYABLE` so the client can retain them for the next request.
|
|
36
|
+
as `FAILED_RETRYABLE` so the client can retain them for the next request. The
|
|
37
|
+
server catches unexpected exceptions at each operation boundary, returns the
|
|
38
|
+
affected operation as `FAILED_RETRYABLE`, and continues independent operations.
|
|
37
39
|
|
|
38
|
-
Direct
|
|
40
|
+
Direct `checkIn`, `claimReward`, and `syncProgress` calls accept a verified
|
|
41
|
+
`TrustedAuthContext`, `{ userId: string }`, or a string `userId` for simplified
|
|
42
|
+
trusted calls. The latter two forms are normalized to
|
|
43
|
+
`{ authenticatedUserId: userId, isAnonymous: false }`; production integrations
|
|
44
|
+
should pass `TrustedAuthContext` from authentication middleware:
|
|
39
45
|
|
|
40
46
|
```ts
|
|
41
47
|
const progress = await server.syncProgress(
|
package/dist/index.cjs
CHANGED
|
@@ -641,6 +641,15 @@ function validateSyncRequest(value) {
|
|
|
641
641
|
data: value
|
|
642
642
|
};
|
|
643
643
|
}
|
|
644
|
+
|
|
645
|
+
// src/types.ts
|
|
646
|
+
function normalizeAuthContext(auth) {
|
|
647
|
+
if (typeof auth === "string") return { authenticatedUserId: auth, isAnonymous: false };
|
|
648
|
+
if ("authenticatedUserId" in auth) return auth;
|
|
649
|
+
return { authenticatedUserId: auth.userId, isAnonymous: false };
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/server.ts
|
|
644
653
|
function json(body, status = 200) {
|
|
645
654
|
return new Response(JSON.stringify(body), {
|
|
646
655
|
status,
|
|
@@ -791,8 +800,17 @@ function syncOperationResult(operation, userId, result) {
|
|
|
791
800
|
reason: result.ok ? "The operation was rejected." : result.message
|
|
792
801
|
};
|
|
793
802
|
}
|
|
794
|
-
function
|
|
795
|
-
|
|
803
|
+
function syncOperationError(operation, userId, error) {
|
|
804
|
+
return {
|
|
805
|
+
operationId: syncOperationId(operation, userId),
|
|
806
|
+
status: "FAILED_RETRYABLE",
|
|
807
|
+
resourceId: operation.kind === "checkIn" ? operation.request.spotId : operation.request.rewardId,
|
|
808
|
+
error: error instanceof Error ? error.message : "Unknown server error"
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
function withDirectIdentity(request, authInput) {
|
|
812
|
+
if (authInput !== void 0) {
|
|
813
|
+
const authContext = normalizeAuthContext(authInput);
|
|
796
814
|
if (authContext.authenticatedUserId.trim() === "")
|
|
797
815
|
throw new RequestValidationException([
|
|
798
816
|
{
|
|
@@ -940,8 +958,8 @@ var StampRallyServer = class {
|
|
|
940
958
|
throw error;
|
|
941
959
|
}
|
|
942
960
|
}
|
|
943
|
-
async checkIn(request,
|
|
944
|
-
const directRequest = withDirectIdentity(request,
|
|
961
|
+
async checkIn(request, authInput) {
|
|
962
|
+
const directRequest = withDirectIdentity(request, authInput);
|
|
945
963
|
assertValidCheckInParams(directRequest, this.#config);
|
|
946
964
|
const { userId } = directRequest;
|
|
947
965
|
const key = `check-in:${request.rallyId}:${userId}:${request.idempotencyKey}`;
|
|
@@ -1074,8 +1092,8 @@ var StampRallyServer = class {
|
|
|
1074
1092
|
await this.#persistence.releaseLock(request.rallyId, lockKey);
|
|
1075
1093
|
}
|
|
1076
1094
|
}
|
|
1077
|
-
async claimReward(request,
|
|
1078
|
-
const directRequest = withDirectIdentity(request,
|
|
1095
|
+
async claimReward(request, authInput) {
|
|
1096
|
+
const directRequest = withDirectIdentity(request, authInput);
|
|
1079
1097
|
assertValidClaimParams(directRequest, this.#config);
|
|
1080
1098
|
const { userId } = directRequest;
|
|
1081
1099
|
const key = `claim:${request.rallyId}:${userId}:${request.rewardId}:${request.idempotencyKey}`;
|
|
@@ -1267,35 +1285,47 @@ var StampRallyServer = class {
|
|
|
1267
1285
|
const state2 = await this.#persistence.getUserState(rallyId, userId) ?? initialState(this.#config, userId, now(this.#options));
|
|
1268
1286
|
return this.#attachInventory(state2);
|
|
1269
1287
|
}
|
|
1270
|
-
async syncProgress(request,
|
|
1271
|
-
const directRequest = withDirectIdentity(request,
|
|
1272
|
-
|
|
1288
|
+
async syncProgress(request, authInput) {
|
|
1289
|
+
const directRequest = withDirectIdentity(request, authInput);
|
|
1290
|
+
const authContext = normalizeAuthContext(authInput);
|
|
1291
|
+
assertValidSyncParams(
|
|
1292
|
+
{
|
|
1293
|
+
rallyId: directRequest.rallyId,
|
|
1294
|
+
userId: directRequest.userId,
|
|
1295
|
+
...directRequest.anonymousSessionId === void 0 ? {} : { anonymousSessionId: directRequest.anonymousSessionId }
|
|
1296
|
+
},
|
|
1297
|
+
this.#config
|
|
1298
|
+
);
|
|
1273
1299
|
const results = [];
|
|
1274
1300
|
const rejectedCheckIns = /* @__PURE__ */ new Set();
|
|
1275
1301
|
const operations = [...request.operations ?? []].map((operation, index) => ({ operation, index })).sort(
|
|
1276
1302
|
(left, right) => operationTimestamp(left.operation) - operationTimestamp(right.operation) || left.index - right.index
|
|
1277
1303
|
).map(({ operation }) => operation);
|
|
1278
1304
|
for (const operation of operations) {
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1305
|
+
try {
|
|
1306
|
+
const resourceId = operation.kind === "checkIn" ? operation.request.spotId : operation.request.rewardId;
|
|
1307
|
+
if (operation.kind === "checkIn") {
|
|
1308
|
+
const spot = this.#config.spots.find((candidate) => candidate.id === resourceId);
|
|
1309
|
+
if (spot?.prerequisites?.some((prerequisite) => rejectedCheckIns.has(prerequisite))) {
|
|
1310
|
+
results.push({
|
|
1311
|
+
operationId: syncOperationId(operation, directRequest.userId),
|
|
1312
|
+
status: "REJECTED_PERMANENT",
|
|
1313
|
+
resourceId,
|
|
1314
|
+
errorCode: "REJECTED_PREREQUISITE_FAILED",
|
|
1315
|
+
reason: "A prerequisite operation was rejected by the server."
|
|
1316
|
+
});
|
|
1317
|
+
rejectedCheckIns.add(resourceId);
|
|
1318
|
+
continue;
|
|
1319
|
+
}
|
|
1292
1320
|
}
|
|
1321
|
+
const result = operation.kind === "checkIn" ? await this.checkIn(operation.request, authContext) : await this.claimReward(operation.request, authContext);
|
|
1322
|
+
const operationResult = syncOperationResult(operation, directRequest.userId, result);
|
|
1323
|
+
results.push(operationResult);
|
|
1324
|
+
if (operation.kind === "checkIn" && operationResult.status === "REJECTED_PERMANENT")
|
|
1325
|
+
rejectedCheckIns.add(resourceId);
|
|
1326
|
+
} catch (error) {
|
|
1327
|
+
results.push(syncOperationError(operation, directRequest.userId, error));
|
|
1293
1328
|
}
|
|
1294
|
-
const result = operation.kind === "checkIn" ? await this.checkIn(operation.request, authContext) : await this.claimReward(operation.request, authContext);
|
|
1295
|
-
const operationResult = syncOperationResult(operation, directRequest.userId, result);
|
|
1296
|
-
results.push(operationResult);
|
|
1297
|
-
if (operation.kind === "checkIn" && operationResult.status === "REJECTED_PERMANENT")
|
|
1298
|
-
rejectedCheckIns.add(resourceId);
|
|
1299
1329
|
}
|
|
1300
1330
|
const currentState = await this.sync(directRequest.rallyId, authContext);
|
|
1301
1331
|
return {
|
|
@@ -1533,6 +1563,7 @@ exports.assertValidSyncParams = assertValidSyncParams;
|
|
|
1533
1563
|
exports.executeCheckInTransaction = executeCheckInTransaction;
|
|
1534
1564
|
exports.executeClaimRewardTransaction = executeClaimRewardTransaction;
|
|
1535
1565
|
exports.executeRedisTransaction = executeRedisTransaction;
|
|
1566
|
+
exports.normalizeAuthContext = normalizeAuthContext;
|
|
1536
1567
|
exports.runPersistenceAdapterComplianceTests = runPersistenceAdapterComplianceTests;
|
|
1537
1568
|
exports.validateCheckInRequest = validateCheckInRequest;
|
|
1538
1569
|
exports.validateClaimRewardRequest = validateClaimRewardRequest;
|