@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 CHANGED
@@ -1,4 +1,4 @@
1
- # @stamprally/server v0.20.0
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 server calls require a verified `TrustedAuthContext`:
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 withDirectIdentity(request, authContext) {
795
- if (authContext !== void 0) {
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, authContext) {
944
- const directRequest = withDirectIdentity(request, authContext);
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, authContext) {
1078
- const directRequest = withDirectIdentity(request, authContext);
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, authContext) {
1271
- const directRequest = withDirectIdentity(request, authContext);
1272
- assertValidSyncParams(directRequest, this.#config);
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
- const resourceId = operation.kind === "checkIn" ? operation.request.spotId : operation.request.rewardId;
1280
- if (operation.kind === "checkIn") {
1281
- const spot = this.#config.spots.find((candidate) => candidate.id === resourceId);
1282
- if (spot?.prerequisites?.some((prerequisite) => rejectedCheckIns.has(prerequisite))) {
1283
- results.push({
1284
- operationId: syncOperationId(operation, directRequest.userId),
1285
- status: "REJECTED_PERMANENT",
1286
- resourceId,
1287
- errorCode: "REJECTED_PREREQUISITE_FAILED",
1288
- reason: "A prerequisite operation was rejected by the server."
1289
- });
1290
- rejectedCheckIns.add(resourceId);
1291
- continue;
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;