@stamprally/server 0.19.0 → 0.20.1

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.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as _stamprally_core from '@stamprally/core';
2
- import { UserRallyState, AdminRallyConfig } from '@stamprally/core';
3
- export { UserRallyState } from '@stamprally/core';
2
+ import { UserRallyState, AdminRallyConfig, SyncProgressResponse } from '@stamprally/core';
3
+ export { SyncOperationResult, SyncProgressResponse, UserRallyState } from '@stamprally/core';
4
4
 
5
5
  /** Authentication already verified by the host application's middleware. */
6
6
  interface TrustedAuthContext {
@@ -276,7 +276,7 @@ declare class StampRallyServer {
276
276
  checkIn(request: CheckInRequest, authContext?: TrustedAuthContext): Promise<CheckInResponse>;
277
277
  claimReward(request: ClaimRewardRequest, authContext?: TrustedAuthContext): Promise<ClaimResponse>;
278
278
  sync(rallyId: string, identity: string | TrustedAuthContext): Promise<UserRallyState>;
279
- syncProgress(request: SyncProgressRequest, authContext: TrustedAuthContext): Promise<UserRallyState>;
279
+ syncProgress(request: SyncProgressRequest, authContext: TrustedAuthContext): Promise<SyncProgressResponse>;
280
280
  }
281
281
 
282
282
  /**
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as _stamprally_core from '@stamprally/core';
2
- import { UserRallyState, AdminRallyConfig } from '@stamprally/core';
3
- export { UserRallyState } from '@stamprally/core';
2
+ import { UserRallyState, AdminRallyConfig, SyncProgressResponse } from '@stamprally/core';
3
+ export { SyncOperationResult, SyncProgressResponse, UserRallyState } from '@stamprally/core';
4
4
 
5
5
  /** Authentication already verified by the host application's middleware. */
6
6
  interface TrustedAuthContext {
@@ -276,7 +276,7 @@ declare class StampRallyServer {
276
276
  checkIn(request: CheckInRequest, authContext?: TrustedAuthContext): Promise<CheckInResponse>;
277
277
  claimReward(request: ClaimRewardRequest, authContext?: TrustedAuthContext): Promise<ClaimResponse>;
278
278
  sync(rallyId: string, identity: string | TrustedAuthContext): Promise<UserRallyState>;
279
- syncProgress(request: SyncProgressRequest, authContext: TrustedAuthContext): Promise<UserRallyState>;
279
+ syncProgress(request: SyncProgressRequest, authContext: TrustedAuthContext): Promise<SyncProgressResponse>;
280
280
  }
281
281
 
282
282
  /**
package/dist/index.js CHANGED
@@ -661,6 +661,15 @@ function timestampMillis(timestamp) {
661
661
  const value = Date.parse(timestamp);
662
662
  return Number.isFinite(value) ? value : Date.now();
663
663
  }
664
+ function operationTimestamp(operation) {
665
+ const value = operation.request.now;
666
+ if (typeof value === "number" && Number.isFinite(value)) return value;
667
+ if (typeof value === "string") {
668
+ const parsed = Date.parse(value);
669
+ if (Number.isFinite(parsed)) return parsed;
670
+ }
671
+ return Number.MAX_SAFE_INTEGER;
672
+ }
664
673
  function validationResponse(errors2) {
665
674
  return json(
666
675
  {
@@ -748,6 +757,38 @@ function operationStatus(result) {
748
757
  if (result.ok) return "ACCEPTED";
749
758
  return result.code === "CONFLICT" || result.code === "PERSISTENCE_FAILED" ? "RETRYABLE_ERROR" : "REJECTED_PERMANENT";
750
759
  }
760
+ function syncOperationId(operation, userId) {
761
+ return `${operation.kind}:${operation.request.rallyId}:${userId}:${operation.request.idempotencyKey}`;
762
+ }
763
+ function syncOperationResult(operation, userId, result) {
764
+ const operationId = syncOperationId(operation, userId);
765
+ const resourceId = operation.kind === "checkIn" ? operation.request.spotId : operation.request.rewardId;
766
+ const action = operation.kind === "checkIn" ? "CHECK_IN" : "CLAIM_REWARD";
767
+ const status = operationStatus(result);
768
+ if (status === "ACCEPTED") {
769
+ return {
770
+ operationId,
771
+ status,
772
+ resourceId,
773
+ action,
774
+ appliedAt: timestampMillis(result.ok ? result.state.updatedAt : "")
775
+ };
776
+ }
777
+ if (status === "RETRYABLE_ERROR")
778
+ return {
779
+ operationId,
780
+ status: "FAILED_RETRYABLE",
781
+ resourceId,
782
+ error: result.ok ? "The operation can be retried." : result.message
783
+ };
784
+ return {
785
+ operationId,
786
+ status,
787
+ resourceId,
788
+ errorCode: result.ok ? "REJECTED_PERMANENT" : result.code,
789
+ reason: result.ok ? "The operation was rejected." : result.message
790
+ };
791
+ }
751
792
  function withDirectIdentity(request, authContext) {
752
793
  if (authContext !== void 0) {
753
794
  if (authContext.authenticatedUserId.trim() === "")
@@ -880,16 +921,17 @@ var StampRallyServer = class {
880
921
  ...sessionId === null ? {} : { isAnonymous: true, sessionId }
881
922
  };
882
923
  try {
924
+ const progress = await this.syncProgress(
925
+ {
926
+ rallyId: body.data.rallyId,
927
+ ...body.data.operations === void 0 ? {} : { operations: body.data.operations },
928
+ ...sessionId === null ? {} : { anonymousSessionId: sessionId }
929
+ },
930
+ authContext
931
+ );
883
932
  return json({
884
933
  ok: true,
885
- state: await this.syncProgress(
886
- {
887
- rallyId: body.data.rallyId,
888
- ...body.data.operations === void 0 ? {} : { operations: body.data.operations },
889
- ...sessionId === null ? {} : { anonymousSessionId: sessionId }
890
- },
891
- authContext
892
- )
934
+ ...progress
893
935
  });
894
936
  } catch (error) {
895
937
  if (error instanceof RequestValidationException) return validationResponse(error.errors);
@@ -1226,11 +1268,39 @@ var StampRallyServer = class {
1226
1268
  async syncProgress(request, authContext) {
1227
1269
  const directRequest = withDirectIdentity(request, authContext);
1228
1270
  assertValidSyncParams(directRequest, this.#config);
1229
- for (const operation of request.operations ?? []) {
1230
- if (operation.kind === "checkIn") await this.checkIn(operation.request, authContext);
1231
- else await this.claimReward(operation.request, authContext);
1271
+ const results = [];
1272
+ const rejectedCheckIns = /* @__PURE__ */ new Set();
1273
+ const operations = [...request.operations ?? []].map((operation, index) => ({ operation, index })).sort(
1274
+ (left, right) => operationTimestamp(left.operation) - operationTimestamp(right.operation) || left.index - right.index
1275
+ ).map(({ operation }) => operation);
1276
+ for (const operation of operations) {
1277
+ const resourceId = operation.kind === "checkIn" ? operation.request.spotId : operation.request.rewardId;
1278
+ if (operation.kind === "checkIn") {
1279
+ const spot = this.#config.spots.find((candidate) => candidate.id === resourceId);
1280
+ if (spot?.prerequisites?.some((prerequisite) => rejectedCheckIns.has(prerequisite))) {
1281
+ results.push({
1282
+ operationId: syncOperationId(operation, directRequest.userId),
1283
+ status: "REJECTED_PERMANENT",
1284
+ resourceId,
1285
+ errorCode: "REJECTED_PREREQUISITE_FAILED",
1286
+ reason: "A prerequisite operation was rejected by the server."
1287
+ });
1288
+ rejectedCheckIns.add(resourceId);
1289
+ continue;
1290
+ }
1291
+ }
1292
+ const result = operation.kind === "checkIn" ? await this.checkIn(operation.request, authContext) : await this.claimReward(operation.request, authContext);
1293
+ const operationResult = syncOperationResult(operation, directRequest.userId, result);
1294
+ results.push(operationResult);
1295
+ if (operation.kind === "checkIn" && operationResult.status === "REJECTED_PERMANENT")
1296
+ rejectedCheckIns.add(resourceId);
1232
1297
  }
1233
- return this.sync(directRequest.rallyId, authContext);
1298
+ const currentState = await this.sync(directRequest.rallyId, authContext);
1299
+ return {
1300
+ results,
1301
+ currentState,
1302
+ syncTimestamp: timestampMillis(now(this.#options))
1303
+ };
1234
1304
  }
1235
1305
  async #attachInventory(state2) {
1236
1306
  const rewardRemaining = {};