@stamprally/server 0.10.0 → 0.11.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 CHANGED
@@ -55,10 +55,15 @@ var InMemoryServerPersistenceAdapter = class {
55
55
  this.#stocks.set(key, current - 1);
56
56
  return { success: true, remainingStock: current - 1 };
57
57
  }
58
- async restoreRewardStock(rallyId, rewardId) {
58
+ async restoreRewardStock(rallyId, rewardId, count = 1) {
59
59
  const key = this.#stockKey(rallyId, rewardId);
60
60
  const current = this.#stocks.get(key);
61
- if (current !== void 0) this.#stocks.set(key, current + 1);
61
+ if (current !== void 0) this.#stocks.set(key, current + Math.max(0, count));
62
+ }
63
+ async rollbackUserState(rallyId, userId, previousState) {
64
+ const key = `${rallyId}:${userId}`;
65
+ if (previousState === null) this.#states.delete(key);
66
+ else this.#states.set(key, structuredClone(previousState));
62
67
  }
63
68
  async getIdempotentResult(rallyId, key) {
64
69
  const value = this.#idempotent.get(this.#key(rallyId, key));
@@ -87,6 +92,10 @@ var InMemoryServerPersistenceAdapter = class {
87
92
  async recordAuditLog(log) {
88
93
  this.#auditLogs.push(structuredClone(log));
89
94
  }
95
+ async removeAuditLog(auditId) {
96
+ const index = this.#auditLogs.findIndex((log) => log.id === auditId);
97
+ if (index >= 0) this.#auditLogs.splice(index, 1);
98
+ }
90
99
  getAuditLogs() {
91
100
  return structuredClone(this.#auditLogs);
92
101
  }
@@ -96,6 +105,16 @@ var InMemoryServerPersistenceAdapter = class {
96
105
  this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);
97
106
  this.#claimRecords.push(structuredClone(params));
98
107
  }
108
+ async rollbackUserClaim(rallyId, userId, rewardId, ticketNumber) {
109
+ const key = `${rallyId}:${userId}:${rewardId}`;
110
+ const count = this.#claims.get(key) ?? 0;
111
+ if (count <= 1) this.#claims.delete(key);
112
+ else this.#claims.set(key, count - 1);
113
+ const index = this.#claimRecords.findIndex(
114
+ (record) => record.rallyId === rallyId && record.userId === userId && record.rewardId === rewardId && record.ticketNumber === ticketNumber
115
+ );
116
+ if (index >= 0) this.#claimRecords.splice(index, 1);
117
+ }
99
118
  async incrementUserClaimCount(rallyId, userId, rewardId) {
100
119
  const key = `${rallyId}:${userId}:${rewardId}`;
101
120
  this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);
@@ -113,6 +132,32 @@ var InMemoryServerPersistenceAdapter = class {
113
132
  } : paramsOrRallyId;
114
133
  return this.recordUserClaim(params);
115
134
  }
135
+ async runTransaction(_rallyId, operation) {
136
+ const snapshot = {
137
+ stocks: new Map(this.#stocks),
138
+ idempotent: new Map(this.#idempotent),
139
+ states: new Map(this.#states),
140
+ claims: new Map(this.#claims),
141
+ claimRecords: structuredClone(this.#claimRecords),
142
+ auditLogs: structuredClone(this.#auditLogs)
143
+ };
144
+ try {
145
+ return await operation(this);
146
+ } catch (error) {
147
+ this.#stocks.clear();
148
+ for (const [key, value] of snapshot.stocks) this.#stocks.set(key, value);
149
+ this.#idempotent.clear();
150
+ for (const [key, value] of snapshot.idempotent)
151
+ this.#idempotent.set(key, structuredClone(value));
152
+ this.#states.clear();
153
+ for (const [key, value] of snapshot.states) this.#states.set(key, structuredClone(value));
154
+ this.#claims.clear();
155
+ for (const [key, value] of snapshot.claims) this.#claims.set(key, value);
156
+ this.#claimRecords.splice(0, this.#claimRecords.length, ...snapshot.claimRecords);
157
+ this.#auditLogs.splice(0, this.#auditLogs.length, ...snapshot.auditLogs);
158
+ throw error;
159
+ }
160
+ }
116
161
  };
117
162
  function json(body, status = 200) {
118
163
  return new Response(JSON.stringify(body), {
@@ -322,11 +367,16 @@ var StampRallyServer = class {
322
367
  }
323
368
  }
324
369
  async claimReward(request) {
370
+ if (this.#persistence.runTransaction !== void 0)
371
+ return this.#persistence.runTransaction(
372
+ request.rallyId,
373
+ (transaction) => this.#claimRewardMutation(request, transaction)
374
+ );
375
+ return this.#claimRewardMutation(request, this.#persistence);
376
+ }
377
+ async #claimRewardMutation(request, persistence) {
325
378
  const key = `claim:${request.rallyId}:${request.userId}:${request.rewardId}:${request.idempotencyKey}`;
326
- const previous = await this.#persistence.getIdempotentResult(
327
- request.rallyId,
328
- key
329
- );
379
+ const previous = await persistence.getIdempotentResult(request.rallyId, key);
330
380
  if (previous !== null) return previous;
331
381
  const reward = this.#config.rewards.find((item) => item.id === request.rewardId);
332
382
  if (reward === void 0)
@@ -334,25 +384,24 @@ var StampRallyServer = class {
334
384
  key,
335
385
  { ok: false, code: "REWARD_NOT_FOUND", message: "Reward was not found." },
336
386
  request,
337
- now(this.#options, request.now)
387
+ now(this.#options, request.now),
388
+ persistence
338
389
  );
339
390
  const lockKey = `reward:${request.rallyId}:${reward.id}`;
340
- if (!await this.#persistence.acquireLock(
341
- request.rallyId,
342
- lockKey,
343
- this.#options.lockTtlMs ?? 5e3
344
- ))
391
+ if (!await persistence.acquireLock(request.rallyId, lockKey, this.#options.lockTtlMs ?? 5e3))
345
392
  return { ok: false, code: "CONFLICT", message: "The reward is being claimed." };
346
393
  const timestamp = now(this.#options, request.now);
347
394
  let decremented = false;
395
+ let previousState = null;
396
+ let recordedTicket = null;
397
+ let successAuditId = null;
348
398
  try {
349
- const checked = await this.#persistence.getIdempotentResult(
350
- request.rallyId,
351
- key
352
- );
399
+ const checked = await persistence.getIdempotentResult(request.rallyId, key);
353
400
  if (checked !== null) return checked;
354
- const current = await this.#persistence.getUserState(request.rallyId, request.userId) ?? initialState(this.#config, request.userId, timestamp);
355
- const claimCount = await this.#persistence.getUserClaimCount(
401
+ const storedState = await persistence.getUserState(request.rallyId, request.userId);
402
+ previousState = storedState;
403
+ const current = storedState ?? initialState(this.#config, request.userId, timestamp);
404
+ const claimCount = await persistence.getUserClaimCount(
356
405
  request.rallyId,
357
406
  request.userId,
358
407
  reward.id
@@ -375,15 +424,17 @@ var StampRallyServer = class {
375
424
  key,
376
425
  { ok: false, code: local.error.code, message: "Reward cannot be claimed." },
377
426
  request,
378
- timestamp
427
+ timestamp,
428
+ persistence
379
429
  );
380
- const stock = await this.#persistence.decrementRewardStock(request.rallyId, reward.id);
430
+ const stock = await persistence.decrementRewardStock(request.rallyId, reward.id);
381
431
  if (!stock.success)
382
432
  return this.#rememberClaim(
383
433
  key,
384
434
  { ok: false, code: "OUT_OF_STOCK", message: "Reward is out of stock." },
385
435
  request,
386
- timestamp
436
+ timestamp,
437
+ persistence
387
438
  );
388
439
  decremented = true;
389
440
  const next = {
@@ -392,27 +443,28 @@ var StampRallyServer = class {
392
443
  updatedAt: timestamp
393
444
  };
394
445
  try {
395
- await this.#persistence.saveUserState(request.rallyId, request.userId, next);
446
+ await persistence.saveUserState(request.rallyId, request.userId, next);
396
447
  const response = local.value.claimTicketNumber === void 0 ? { ok: true, state: next } : { ok: true, state: next, claimTicketNumber: local.value.claimTicketNumber };
397
- await this.#persistence.recordUserClaim({
448
+ recordedTicket = local.value.claimTicketNumber ?? "";
449
+ await persistence.recordUserClaim({
398
450
  rallyId: request.rallyId,
399
451
  userId: request.userId,
400
452
  rewardId: reward.id,
401
- ticketNumber: local.value.claimTicketNumber ?? "",
453
+ ticketNumber: recordedTicket,
402
454
  timestamp: Number.isNaN(Date.parse(timestamp)) ? Date.now() : Date.parse(timestamp)
403
455
  });
404
- await this.#persistence.recordAuditLog(
405
- audit(
406
- request.rallyId,
407
- request.userId,
408
- "CLAIM_REWARD",
409
- reward.id,
410
- request.idempotencyKey,
411
- "SUCCESS",
412
- timestamp
413
- )
456
+ const successAudit = audit(
457
+ request.rallyId,
458
+ request.userId,
459
+ "CLAIM_REWARD",
460
+ reward.id,
461
+ request.idempotencyKey,
462
+ "SUCCESS",
463
+ timestamp
414
464
  );
415
- await this.#persistence.saveIdempotentResult(
465
+ successAuditId = successAudit.id;
466
+ await persistence.recordAuditLog(successAudit);
467
+ await persistence.saveIdempotentResult(
416
468
  request.rallyId,
417
469
  key,
418
470
  response,
@@ -420,19 +472,30 @@ var StampRallyServer = class {
420
472
  );
421
473
  return response;
422
474
  } catch (error) {
423
- await this.#restoreRewardStock(request.rallyId, reward.id);
475
+ if (recordedTicket !== null && persistence.rollbackUserClaim !== void 0)
476
+ await persistence.rollbackUserClaim(
477
+ request.rallyId,
478
+ request.userId,
479
+ reward.id,
480
+ recordedTicket
481
+ );
482
+ if (persistence.rollbackUserState !== void 0)
483
+ await persistence.rollbackUserState(request.rallyId, request.userId, previousState);
484
+ if (successAuditId !== null && persistence.removeAuditLog !== void 0)
485
+ await persistence.removeAuditLog(successAuditId);
486
+ await this.#restoreRewardStock(persistence, request.rallyId, reward.id);
424
487
  decremented = false;
425
488
  throw error;
426
489
  }
427
490
  } catch (error) {
428
- if (decremented) await this.#restoreRewardStock(request.rallyId, reward.id);
491
+ if (decremented) await this.#restoreRewardStock(persistence, request.rallyId, reward.id);
429
492
  return {
430
493
  ok: false,
431
494
  code: "PERSISTENCE_FAILED",
432
495
  message: error instanceof Error ? error.message : "Reward claim failed."
433
496
  };
434
497
  } finally {
435
- await this.#persistence.releaseLock(request.rallyId, lockKey);
498
+ await persistence.releaseLock(request.rallyId, lockKey);
436
499
  }
437
500
  }
438
501
  async sync(rallyId, userId) {
@@ -472,8 +535,8 @@ var StampRallyServer = class {
472
535
  );
473
536
  return result;
474
537
  }
475
- async #rememberClaim(key, result, request, timestamp) {
476
- await this.#persistence.recordAuditLog(
538
+ async #rememberClaim(key, result, request, timestamp, persistence = this.#persistence) {
539
+ await persistence.recordAuditLog(
477
540
  audit(
478
541
  request.rallyId,
479
542
  request.userId,
@@ -485,7 +548,7 @@ var StampRallyServer = class {
485
548
  result.ok ? void 0 : result.code
486
549
  )
487
550
  );
488
- await this.#persistence.saveIdempotentResult(
551
+ await persistence.saveIdempotentResult(
489
552
  request.rallyId,
490
553
  key,
491
554
  result,
@@ -493,9 +556,9 @@ var StampRallyServer = class {
493
556
  );
494
557
  return result;
495
558
  }
496
- async #restoreRewardStock(rallyId, rewardId) {
497
- if (this.#persistence.restoreRewardStock !== void 0)
498
- await this.#persistence.restoreRewardStock(rallyId, rewardId);
559
+ async #restoreRewardStock(persistence, rallyId, rewardId) {
560
+ if (persistence.restoreRewardStock !== void 0)
561
+ await persistence.restoreRewardStock(rallyId, rewardId);
499
562
  }
500
563
  };
501
564
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/persistence.ts","../src/server.ts"],"names":["now","reconcileRewardStates","evaluateConditionDetailed","consumeReward"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { UserRallyState } from \"@stamprally/core\";\nimport type { AuditLog } from \"./index.js\";\n\nexport interface UserClaimRecord {\n readonly rallyId: string;\n readonly userId: string;\n readonly rewardId: string;\n readonly ticketNumber: string;\n readonly timestamp: number;\n}\n\nexport interface ServerPersistenceAdapter {\n acquireLock(rallyId: string, lockKey: string, ttlMs: number): Promise<boolean>;\n releaseLock(rallyId: string, lockKey: string): Promise<void>;\n getRewardStock(rallyId: string, rewardId: string): Promise<number | null>;\n decrementRewardStock(\n rallyId: string,\n rewardId: string,\n ): Promise<{ readonly success: boolean; readonly remainingStock: number }>;\n restoreRewardStock?(rallyId: string, rewardId: string): Promise<void>;\n getIdempotentResult<T>(rallyId: string, key: string): Promise<T | null>;\n saveIdempotentResult<T>(rallyId: string, key: string, result: T, ttlMs: number): Promise<void>;\n getUserClaimCount(rallyId: string, userId: string, rewardId: string): Promise<number>;\n recordUserClaim(params: UserClaimRecord): Promise<void>;\n recordClaim?(params: UserClaimRecord): Promise<void>;\n incrementUserClaimCount?(rallyId: string, userId: string, rewardId: string): Promise<void>;\n getUserState(rallyId: string, userId: string): Promise<UserRallyState | null>;\n saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void>;\n recordAuditLog(log: AuditLog): Promise<void>;\n}\ninterface TimedValue<T> {\n readonly value: T;\n readonly expiresAt: number;\n}\nexport interface InMemoryServerPersistenceOptions {\n readonly stocks?: Readonly<Record<string, number>>;\n}\nexport class InMemoryServerPersistenceAdapter implements ServerPersistenceAdapter {\n readonly #locks = new Map<string, number>();\n readonly #idempotent = new Map<string, TimedValue<unknown>>();\n readonly #states = new Map<string, UserRallyState>();\n readonly #stocks: Map<string, number>;\n readonly #stockDefaults: Map<string, number>;\n readonly #claims = new Map<string, number>();\n readonly #claimRecords: UserClaimRecord[] = [];\n readonly #auditLogs: AuditLog[] = [];\n constructor(options: InMemoryServerPersistenceOptions = {}) {\n this.#stocks = new Map();\n this.#stockDefaults = new Map();\n for (const [key, stock] of Object.entries(options.stocks ?? {})) {\n if (key.includes(\":\")) this.#stocks.set(key, stock);\n else this.#stockDefaults.set(key, stock);\n }\n }\n #key(rallyId: string, key: string): string {\n return `${rallyId}:${key}`;\n }\n #stockKey(rallyId: string, rewardId: string): string {\n const scopedKey = `${rallyId}:${rewardId}`;\n if (this.#stocks.has(scopedKey)) return scopedKey;\n const defaultStock = this.#stockDefaults.get(rewardId);\n if (defaultStock !== undefined) {\n this.#stocks.set(scopedKey, defaultStock);\n return scopedKey;\n }\n return scopedKey;\n }\n async acquireLock(rallyId: string, key: string, ttlMs: number): Promise<boolean> {\n const scopedKey = this.#key(rallyId, key);\n const now = Date.now();\n const until = this.#locks.get(scopedKey);\n if (until !== undefined && until > now) return false;\n this.#locks.set(scopedKey, now + Math.max(1, ttlMs));\n return true;\n }\n async releaseLock(rallyId: string, key: string): Promise<void> {\n this.#locks.delete(this.#key(rallyId, key));\n }\n async getRewardStock(rallyId: string, rewardId: string): Promise<number | null> {\n return this.#stocks.get(this.#stockKey(rallyId, rewardId)) ?? null;\n }\n async decrementRewardStock(\n rallyId: string,\n rewardId: string,\n ): Promise<{ success: boolean; remainingStock: number }> {\n const key = this.#stockKey(rallyId, rewardId);\n const current = this.#stocks.get(key);\n if (current === undefined) return { success: true, remainingStock: Number.POSITIVE_INFINITY };\n if (current <= 0) return { success: false, remainingStock: 0 };\n this.#stocks.set(key, current - 1);\n return { success: true, remainingStock: current - 1 };\n }\n async restoreRewardStock(rallyId: string, rewardId: string): Promise<void> {\n const key = this.#stockKey(rallyId, rewardId);\n const current = this.#stocks.get(key);\n if (current !== undefined) this.#stocks.set(key, current + 1);\n }\n async getIdempotentResult<T>(rallyId: string, key: string): Promise<T | null> {\n const value = this.#idempotent.get(this.#key(rallyId, key));\n if (value === undefined || value.expiresAt <= Date.now()) {\n this.#idempotent.delete(this.#key(rallyId, key));\n return null;\n }\n return structuredClone(value.value) as T;\n }\n async saveIdempotentResult<T>(\n rallyId: string,\n key: string,\n result: T,\n ttlMs: number,\n ): Promise<void> {\n this.#idempotent.set(this.#key(rallyId, key), {\n value: structuredClone(result),\n expiresAt: Date.now() + Math.max(1, ttlMs),\n });\n }\n async getUserClaimCount(rallyId: string, userId: string, rewardId: string): Promise<number> {\n return this.#claims.get(`${rallyId}:${userId}:${rewardId}`) ?? 0;\n }\n async getUserState(rallyId: string, userId: string): Promise<UserRallyState | null> {\n const value = this.#states.get(`${rallyId}:${userId}`);\n return value === undefined ? null : structuredClone(value);\n }\n async saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void> {\n this.#states.set(`${rallyId}:${userId}`, structuredClone(state));\n }\n async recordAuditLog(log: AuditLog): Promise<void> {\n this.#auditLogs.push(structuredClone(log));\n }\n getAuditLogs(): ReadonlyArray<AuditLog> {\n return structuredClone(this.#auditLogs);\n }\n async recordUserClaim(params: UserClaimRecord): Promise<void> {\n const { rallyId, userId, rewardId } = params;\n const key = `${rallyId}:${userId}:${rewardId}`;\n this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);\n this.#claimRecords.push(structuredClone(params));\n }\n async incrementUserClaimCount(rallyId: string, userId: string, rewardId: string): Promise<void> {\n const key = `${rallyId}:${userId}:${rewardId}`;\n this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);\n }\n getClaimRecords(): ReadonlyArray<UserClaimRecord> {\n return structuredClone(this.#claimRecords);\n }\n /** @deprecated Use recordUserClaim with a ticket number and timestamp. */\n recordClaim(params: UserClaimRecord): Promise<void>;\n recordClaim(rallyId: string, userId: string, rewardId: string): Promise<void>;\n recordClaim(\n paramsOrRallyId: UserClaimRecord | string,\n userId?: string,\n rewardId?: string,\n ): Promise<void> {\n const params: UserClaimRecord =\n typeof paramsOrRallyId === \"string\"\n ? {\n rallyId: paramsOrRallyId,\n userId: userId ?? \"\",\n rewardId: rewardId ?? \"\",\n ticketNumber: \"\",\n timestamp: Date.now(),\n }\n : paramsOrRallyId;\n return this.recordUserClaim(params);\n }\n}\n","import {\n type AdminRallyConfig,\n type CheckInCondition,\n consumeReward,\n evaluateConditionDetailed,\n reconcileRewardStates,\n type UserRallyState,\n type Validator,\n type VerificationContext,\n} from \"@stamprally/core\";\nimport type {\n AuditLog,\n CheckInRequest,\n CheckInResponse,\n ClaimRewardRequest,\n ServerOptions,\n} from \"./index.js\";\nimport type { ServerPersistenceAdapter } from \"./persistence.js\";\n\ntype ErrorResponse = { readonly ok: false; readonly code: string; readonly message: string };\ntype ClaimResponse =\n | { readonly ok: true; readonly state: UserRallyState; readonly claimTicketNumber?: string }\n | ErrorResponse;\nfunction json(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"content-type\": \"application/json; charset=utf-8\" },\n });\n}\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\nfunction requestId(prefix: string): string {\n return `${prefix}-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;\n}\nfunction now(options: ServerOptions, requested?: string): string {\n return requested ?? options.now?.() ?? new Date().toISOString();\n}\nfunction initialState(config: AdminRallyConfig, userId: string, timestamp: string): UserRallyState {\n return {\n rallyId: config.id,\n userId,\n records: [],\n rewards: reconcileRewardStates(config.rewards, [], 0, timestamp),\n updatedAt: timestamp,\n };\n}\nfunction getProof(context: VerificationContext): unknown {\n return context.type === \"qr\"\n ? context.token\n : context.type === \"passcode\"\n ? context.code\n : context.type === \"gps\"\n ? { latitude: context.latitude, longitude: context.longitude }\n : context.type === \"nfc\"\n ? context.tagId\n : context.value;\n}\nasync function evaluate(\n condition: CheckInCondition,\n context: VerificationContext,\n validator: Validator | undefined,\n base: { rallyId: string; spotId: string; state: UserRallyState },\n): Promise<boolean> {\n if (condition.type !== \"custom\") return evaluateConditionDetailed(condition, context).ok;\n if (validator === undefined) return false;\n const validationContext = {\n rallyId: base.rallyId,\n spotId: base.spotId,\n proofData: getProof(context),\n condition,\n userState: base.state,\n };\n const result =\n typeof validator === \"function\"\n ? await validator(validationContext)\n : await validator.validate(validationContext);\n return result === true || (typeof result === \"object\" && result.valid);\n}\nfunction audit(\n rallyId: string,\n userId: string,\n action: AuditLog[\"action\"],\n resourceId: string,\n key: string,\n status: AuditLog[\"status\"],\n timestamp: string,\n code?: string,\n): AuditLog {\n return {\n id: requestId(\"audit\"),\n timestamp,\n rallyId,\n userId,\n action,\n resourceId,\n status,\n idempotencyKey: key,\n ...(code === undefined ? {} : { metadata: { errorCode: code } }),\n };\n}\n\nexport class StampRallyServer {\n readonly #config: AdminRallyConfig;\n readonly #persistence: ServerPersistenceAdapter;\n readonly #options: ServerOptions;\n constructor(\n config: AdminRallyConfig,\n persistence: ServerPersistenceAdapter,\n options: ServerOptions = {},\n ) {\n this.#config = config;\n this.#persistence = persistence;\n this.#options = options;\n }\n async handle(request: Request): Promise<Response> {\n if (request.method !== \"POST\")\n return json({ ok: false, code: \"METHOD_NOT_ALLOWED\", message: \"POST is required.\" }, 405);\n const path = new URL(request.url).pathname;\n if (path.endsWith(\"/check-in\")) return this.handleCheckIn(request);\n if (path.endsWith(\"/claim-reward\")) return this.handleClaimReward(request);\n if (path.endsWith(\"/sync\")) return this.handleSync(request);\n return json({ ok: false, code: \"NOT_FOUND\", message: \"Route not found.\" }, 404);\n }\n async handleCheckIn(request: Request): Promise<Response> {\n const body = await this.#body<CheckInRequest>(request);\n const userId = await this.#user(request, body?.userId);\n if (\n body === null ||\n userId === null ||\n body.rallyId !== this.#config.id ||\n body.spotId === \"\" ||\n body.idempotencyKey === \"\" ||\n body.context === undefined\n )\n return json(\n {\n ok: false,\n code: \"INVALID_REQUEST\",\n message: \"rallyId, spotId, context, and idempotencyKey are required.\",\n },\n 400,\n );\n const result = await this.checkIn({ ...body, userId });\n return json(result, result.ok ? 200 : result.code === \"SPOT_NOT_FOUND\" ? 404 : 422);\n }\n async handleClaimReward(request: Request): Promise<Response> {\n const body = await this.#body<ClaimRewardRequest>(request);\n const userId = await this.#user(request, body?.userId);\n if (\n body === null ||\n userId === null ||\n body.rallyId !== this.#config.id ||\n body.rewardId === \"\" ||\n body.idempotencyKey === \"\"\n )\n return json(\n {\n ok: false,\n code: \"INVALID_REQUEST\",\n message: \"rallyId, rewardId, and idempotencyKey are required.\",\n },\n 400,\n );\n const result = await this.claimReward({ ...body, userId });\n return json(result, result.ok ? 200 : result.code === \"REWARD_NOT_FOUND\" ? 404 : 422);\n }\n async handleSync(request: Request): Promise<Response> {\n const body = await this.#body<{ readonly rallyId: string; readonly userId?: string }>(request);\n const userId = await this.#user(request, body?.userId);\n if (body === null || userId === null || body.rallyId !== this.#config.id)\n return json({ ok: false, code: \"INVALID_REQUEST\", message: \"rallyId is required.\" }, 400);\n return json({ ok: true, state: await this.sync(body.rallyId, userId) });\n }\n async checkIn(request: CheckInRequest & { readonly userId: string }): Promise<CheckInResponse> {\n const key = `check-in:${request.rallyId}:${request.userId}:${request.idempotencyKey}`;\n const previous = await this.#persistence.getIdempotentResult<CheckInResponse>(\n request.rallyId,\n key,\n );\n if (previous !== null) return previous;\n const lockKey = `state:${request.rallyId}:${request.userId}`;\n if (\n !(await this.#persistence.acquireLock(\n request.rallyId,\n lockKey,\n this.#options.lockTtlMs ?? 5_000,\n ))\n )\n return { ok: false, code: \"CONFLICT\", message: \"The user state is being updated.\" };\n const timestamp = now(this.#options, request.now);\n try {\n const spot = this.#config.spots.find((item) => item.id === request.spotId);\n if (spot === undefined)\n return this.#remember(\n key,\n { ok: false, code: \"SPOT_NOT_FOUND\", message: \"Spot was not found.\" },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n const current =\n (await this.#persistence.getUserState(request.rallyId, request.userId)) ??\n initialState(this.#config, request.userId, timestamp);\n if (current.records.some((record) => record.stampId === request.spotId))\n return this.#remember(\n key,\n { ok: false, code: \"STAMP_ALREADY_ACQUIRED\", message: \"Spot was already claimed.\" },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n const acquired = new Set(current.records.map((record) => record.stampId));\n if (spot.prerequisites?.some((id) => !acquired.has(id)))\n return this.#remember(\n key,\n {\n ok: false,\n code: \"PREREQUISITES_NOT_MET\",\n message: \"Prerequisite spots are not complete.\",\n },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n for (const condition of spot.conditions)\n if (\n !(await evaluate(\n condition,\n request.context,\n condition.type === \"custom\"\n ? this.#options.customValidators?.[condition.validatorName]\n : undefined,\n { rallyId: request.rallyId, spotId: request.spotId, state: current },\n ))\n )\n return this.#remember(\n key,\n { ok: false, code: \"INVALID_PROOF\", message: \"Verification failed.\" },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n const next: UserRallyState = {\n ...current,\n records: [...current.records, { stampId: request.spotId, acquiredAt: timestamp }],\n rewards: reconcileRewardStates(\n this.#config.rewards,\n current.rewards,\n current.records.length + 1,\n timestamp,\n ),\n updatedAt: timestamp,\n };\n await this.#persistence.saveUserState(request.rallyId, request.userId, next);\n return this.#remember(\n key,\n { ok: true, state: next },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n } finally {\n await this.#persistence.releaseLock(request.rallyId, lockKey);\n }\n }\n async claimReward(\n request: ClaimRewardRequest & { readonly userId: string },\n ): Promise<ClaimResponse> {\n const key = `claim:${request.rallyId}:${request.userId}:${request.rewardId}:${request.idempotencyKey}`;\n const previous = await this.#persistence.getIdempotentResult<ClaimResponse>(\n request.rallyId,\n key,\n );\n if (previous !== null) return previous;\n const reward = this.#config.rewards.find((item) => item.id === request.rewardId);\n if (reward === undefined)\n return this.#rememberClaim(\n key,\n { ok: false, code: \"REWARD_NOT_FOUND\", message: \"Reward was not found.\" },\n request,\n now(this.#options, request.now),\n );\n const lockKey = `reward:${request.rallyId}:${reward.id}`;\n if (\n !(await this.#persistence.acquireLock(\n request.rallyId,\n lockKey,\n this.#options.lockTtlMs ?? 5_000,\n ))\n )\n return { ok: false, code: \"CONFLICT\", message: \"The reward is being claimed.\" };\n const timestamp = now(this.#options, request.now);\n let decremented = false;\n try {\n const checked = await this.#persistence.getIdempotentResult<ClaimResponse>(\n request.rallyId,\n key,\n );\n if (checked !== null) return checked;\n const current =\n (await this.#persistence.getUserState(request.rallyId, request.userId)) ??\n initialState(this.#config, request.userId, timestamp);\n const claimCount = await this.#persistence.getUserClaimCount(\n request.rallyId,\n request.userId,\n reward.id,\n );\n const storedReward = current.rewards.find((item) => item.rewardId === reward.id) ?? {\n rewardId: reward.id,\n status: \"LOCKED\" as const,\n };\n const currentReward =\n reward.redemptionMethod === \"server_claim\" &&\n storedReward.status === \"CONSUMED\" &&\n (reward.userClaimLimit === undefined || claimCount < reward.userClaimLimit)\n ? { ...storedReward, status: \"AVAILABLE\" as const }\n : storedReward;\n const local = consumeReward({\n reward,\n currentState: currentReward,\n now: timestamp,\n userRedemptionCount: claimCount,\n ...(request.staffPasscode === undefined ? {} : { inputPasscode: request.staffPasscode }),\n ...(request.staffId === undefined ? {} : { staffId: request.staffId }),\n });\n if (!local.ok)\n return this.#rememberClaim(\n key,\n { ok: false, code: local.error.code, message: \"Reward cannot be claimed.\" },\n request,\n timestamp,\n );\n const stock = await this.#persistence.decrementRewardStock(request.rallyId, reward.id);\n if (!stock.success)\n return this.#rememberClaim(\n key,\n { ok: false, code: \"OUT_OF_STOCK\", message: \"Reward is out of stock.\" },\n request,\n timestamp,\n );\n decremented = true;\n const next: UserRallyState = {\n ...current,\n rewards: current.rewards.map((item) => (item.rewardId === reward.id ? local.value : item)),\n updatedAt: timestamp,\n };\n try {\n await this.#persistence.saveUserState(request.rallyId, request.userId, next);\n const response: ClaimResponse =\n local.value.claimTicketNumber === undefined\n ? { ok: true, state: next }\n : { ok: true, state: next, claimTicketNumber: local.value.claimTicketNumber };\n await this.#persistence.recordUserClaim({\n rallyId: request.rallyId,\n userId: request.userId,\n rewardId: reward.id,\n ticketNumber: local.value.claimTicketNumber ?? \"\",\n timestamp: Number.isNaN(Date.parse(timestamp)) ? Date.now() : Date.parse(timestamp),\n });\n await this.#persistence.recordAuditLog(\n audit(\n request.rallyId,\n request.userId,\n \"CLAIM_REWARD\",\n reward.id,\n request.idempotencyKey,\n \"SUCCESS\",\n timestamp,\n ),\n );\n await this.#persistence.saveIdempotentResult(\n request.rallyId,\n key,\n response,\n this.#options.idempotencyTtlMs ?? 86_400_000,\n );\n return response;\n } catch (error) {\n await this.#restoreRewardStock(request.rallyId, reward.id);\n decremented = false;\n throw error;\n }\n } catch (error) {\n if (decremented) await this.#restoreRewardStock(request.rallyId, reward.id);\n return {\n ok: false,\n code: \"PERSISTENCE_FAILED\",\n message: error instanceof Error ? error.message : \"Reward claim failed.\",\n };\n } finally {\n await this.#persistence.releaseLock(request.rallyId, lockKey);\n }\n }\n async sync(rallyId: string, userId: string): Promise<UserRallyState> {\n return (\n (await this.#persistence.getUserState(rallyId, userId)) ??\n initialState(this.#config, userId, now(this.#options))\n );\n }\n async #body<T>(request: Request): Promise<T | null> {\n try {\n const value: unknown = await request.json();\n return isObject(value) ? (value as T) : null;\n } catch {\n return null;\n }\n }\n async #user(request: Request, requested: string | undefined): Promise<string | null> {\n if (this.#options.authenticate !== undefined)\n return (await this.#options.authenticate(request)) ?? null;\n return requested ?? null;\n }\n async #remember(\n key: string,\n result: CheckInResponse,\n rallyId: string,\n userId: string,\n action: AuditLog[\"action\"],\n resourceId: string,\n idempotencyKey: string,\n timestamp: string,\n ): Promise<CheckInResponse> {\n await this.#persistence.recordAuditLog(\n audit(\n rallyId,\n userId,\n action,\n resourceId,\n idempotencyKey,\n result.ok ? \"SUCCESS\" : \"REJECTED\",\n timestamp,\n result.ok ? undefined : result.code,\n ),\n );\n await this.#persistence.saveIdempotentResult(\n rallyId,\n key,\n result,\n this.#options.idempotencyTtlMs ?? 86_400_000,\n );\n return result;\n }\n async #rememberClaim(\n key: string,\n result: ClaimResponse,\n request: ClaimRewardRequest & { readonly userId: string },\n timestamp: string,\n ): Promise<ClaimResponse> {\n await this.#persistence.recordAuditLog(\n audit(\n request.rallyId,\n request.userId,\n \"CLAIM_REWARD\",\n request.rewardId,\n request.idempotencyKey,\n \"REJECTED\",\n timestamp,\n result.ok ? undefined : result.code,\n ),\n );\n await this.#persistence.saveIdempotentResult(\n request.rallyId,\n key,\n result,\n this.#options.idempotencyTtlMs ?? 86_400_000,\n );\n return result;\n }\n async #restoreRewardStock(rallyId: string, rewardId: string): Promise<void> {\n if (this.#persistence.restoreRewardStock !== undefined)\n await this.#persistence.restoreRewardStock(rallyId, rewardId);\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/persistence.ts","../src/server.ts"],"names":["now","reconcileRewardStates","evaluateConditionDetailed","consumeReward"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { UserRallyState } from \"@stamprally/core\";\nimport type { AuditLog } from \"./index.js\";\n\nexport interface UserClaimRecord {\n readonly rallyId: string;\n readonly userId: string;\n readonly rewardId: string;\n readonly ticketNumber: string;\n readonly timestamp: number;\n}\n\nexport interface ServerPersistenceAdapter {\n /** Runs the callback in a storage-native transaction when supported. */\n runTransaction?<T>(\n rallyId: string,\n operation: (transaction: ServerPersistenceAdapter) => Promise<T>,\n ): Promise<T>;\n acquireLock(rallyId: string, lockKey: string, ttlMs: number): Promise<boolean>;\n releaseLock(rallyId: string, lockKey: string): Promise<void>;\n getRewardStock(rallyId: string, rewardId: string): Promise<number | null>;\n decrementRewardStock(\n rallyId: string,\n rewardId: string,\n ): Promise<{ readonly success: boolean; readonly remainingStock: number }>;\n restoreRewardStock?(rallyId: string, rewardId: string, count?: number): Promise<void>;\n rollbackUserState?(\n rallyId: string,\n userId: string,\n previousState: UserRallyState | null,\n ): Promise<void>;\n rollbackUserClaim?(\n rallyId: string,\n userId: string,\n rewardId: string,\n ticketNumber: string,\n ): Promise<void>;\n getIdempotentResult<T>(rallyId: string, key: string): Promise<T | null>;\n saveIdempotentResult<T>(rallyId: string, key: string, result: T, ttlMs: number): Promise<void>;\n getUserClaimCount(rallyId: string, userId: string, rewardId: string): Promise<number>;\n recordUserClaim(params: UserClaimRecord): Promise<void>;\n recordClaim?(params: UserClaimRecord): Promise<void>;\n incrementUserClaimCount?(rallyId: string, userId: string, rewardId: string): Promise<void>;\n getUserState(rallyId: string, userId: string): Promise<UserRallyState | null>;\n saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void>;\n recordAuditLog(log: AuditLog): Promise<void>;\n removeAuditLog?(auditId: string): Promise<void>;\n}\ninterface TimedValue<T> {\n readonly value: T;\n readonly expiresAt: number;\n}\nexport interface InMemoryServerPersistenceOptions {\n readonly stocks?: Readonly<Record<string, number>>;\n}\nexport class InMemoryServerPersistenceAdapter implements ServerPersistenceAdapter {\n readonly #locks = new Map<string, number>();\n readonly #idempotent = new Map<string, TimedValue<unknown>>();\n readonly #states = new Map<string, UserRallyState>();\n readonly #stocks: Map<string, number>;\n readonly #stockDefaults: Map<string, number>;\n readonly #claims = new Map<string, number>();\n readonly #claimRecords: UserClaimRecord[] = [];\n readonly #auditLogs: AuditLog[] = [];\n constructor(options: InMemoryServerPersistenceOptions = {}) {\n this.#stocks = new Map();\n this.#stockDefaults = new Map();\n for (const [key, stock] of Object.entries(options.stocks ?? {})) {\n if (key.includes(\":\")) this.#stocks.set(key, stock);\n else this.#stockDefaults.set(key, stock);\n }\n }\n #key(rallyId: string, key: string): string {\n return `${rallyId}:${key}`;\n }\n #stockKey(rallyId: string, rewardId: string): string {\n const scopedKey = `${rallyId}:${rewardId}`;\n if (this.#stocks.has(scopedKey)) return scopedKey;\n const defaultStock = this.#stockDefaults.get(rewardId);\n if (defaultStock !== undefined) {\n this.#stocks.set(scopedKey, defaultStock);\n return scopedKey;\n }\n return scopedKey;\n }\n async acquireLock(rallyId: string, key: string, ttlMs: number): Promise<boolean> {\n const scopedKey = this.#key(rallyId, key);\n const now = Date.now();\n const until = this.#locks.get(scopedKey);\n if (until !== undefined && until > now) return false;\n this.#locks.set(scopedKey, now + Math.max(1, ttlMs));\n return true;\n }\n async releaseLock(rallyId: string, key: string): Promise<void> {\n this.#locks.delete(this.#key(rallyId, key));\n }\n async getRewardStock(rallyId: string, rewardId: string): Promise<number | null> {\n return this.#stocks.get(this.#stockKey(rallyId, rewardId)) ?? null;\n }\n async decrementRewardStock(\n rallyId: string,\n rewardId: string,\n ): Promise<{ success: boolean; remainingStock: number }> {\n const key = this.#stockKey(rallyId, rewardId);\n const current = this.#stocks.get(key);\n if (current === undefined) return { success: true, remainingStock: Number.POSITIVE_INFINITY };\n if (current <= 0) return { success: false, remainingStock: 0 };\n this.#stocks.set(key, current - 1);\n return { success: true, remainingStock: current - 1 };\n }\n async restoreRewardStock(rallyId: string, rewardId: string, count = 1): Promise<void> {\n const key = this.#stockKey(rallyId, rewardId);\n const current = this.#stocks.get(key);\n if (current !== undefined) this.#stocks.set(key, current + Math.max(0, count));\n }\n\n async rollbackUserState(\n rallyId: string,\n userId: string,\n previousState: UserRallyState | null,\n ): Promise<void> {\n const key = `${rallyId}:${userId}`;\n if (previousState === null) this.#states.delete(key);\n else this.#states.set(key, structuredClone(previousState));\n }\n async getIdempotentResult<T>(rallyId: string, key: string): Promise<T | null> {\n const value = this.#idempotent.get(this.#key(rallyId, key));\n if (value === undefined || value.expiresAt <= Date.now()) {\n this.#idempotent.delete(this.#key(rallyId, key));\n return null;\n }\n return structuredClone(value.value) as T;\n }\n async saveIdempotentResult<T>(\n rallyId: string,\n key: string,\n result: T,\n ttlMs: number,\n ): Promise<void> {\n this.#idempotent.set(this.#key(rallyId, key), {\n value: structuredClone(result),\n expiresAt: Date.now() + Math.max(1, ttlMs),\n });\n }\n async getUserClaimCount(rallyId: string, userId: string, rewardId: string): Promise<number> {\n return this.#claims.get(`${rallyId}:${userId}:${rewardId}`) ?? 0;\n }\n async getUserState(rallyId: string, userId: string): Promise<UserRallyState | null> {\n const value = this.#states.get(`${rallyId}:${userId}`);\n return value === undefined ? null : structuredClone(value);\n }\n async saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void> {\n this.#states.set(`${rallyId}:${userId}`, structuredClone(state));\n }\n async recordAuditLog(log: AuditLog): Promise<void> {\n this.#auditLogs.push(structuredClone(log));\n }\n async removeAuditLog(auditId: string): Promise<void> {\n const index = this.#auditLogs.findIndex((log) => log.id === auditId);\n if (index >= 0) this.#auditLogs.splice(index, 1);\n }\n getAuditLogs(): ReadonlyArray<AuditLog> {\n return structuredClone(this.#auditLogs);\n }\n async recordUserClaim(params: UserClaimRecord): Promise<void> {\n const { rallyId, userId, rewardId } = params;\n const key = `${rallyId}:${userId}:${rewardId}`;\n this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);\n this.#claimRecords.push(structuredClone(params));\n }\n async rollbackUserClaim(\n rallyId: string,\n userId: string,\n rewardId: string,\n ticketNumber: string,\n ): Promise<void> {\n const key = `${rallyId}:${userId}:${rewardId}`;\n const count = this.#claims.get(key) ?? 0;\n if (count <= 1) this.#claims.delete(key);\n else this.#claims.set(key, count - 1);\n const index = this.#claimRecords.findIndex(\n (record) =>\n record.rallyId === rallyId &&\n record.userId === userId &&\n record.rewardId === rewardId &&\n record.ticketNumber === ticketNumber,\n );\n if (index >= 0) this.#claimRecords.splice(index, 1);\n }\n async incrementUserClaimCount(rallyId: string, userId: string, rewardId: string): Promise<void> {\n const key = `${rallyId}:${userId}:${rewardId}`;\n this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);\n }\n getClaimRecords(): ReadonlyArray<UserClaimRecord> {\n return structuredClone(this.#claimRecords);\n }\n /** @deprecated Use recordUserClaim with a ticket number and timestamp. */\n recordClaim(params: UserClaimRecord): Promise<void>;\n recordClaim(rallyId: string, userId: string, rewardId: string): Promise<void>;\n recordClaim(\n paramsOrRallyId: UserClaimRecord | string,\n userId?: string,\n rewardId?: string,\n ): Promise<void> {\n const params: UserClaimRecord =\n typeof paramsOrRallyId === \"string\"\n ? {\n rallyId: paramsOrRallyId,\n userId: userId ?? \"\",\n rewardId: rewardId ?? \"\",\n ticketNumber: \"\",\n timestamp: Date.now(),\n }\n : paramsOrRallyId;\n return this.recordUserClaim(params);\n }\n\n async runTransaction<T>(\n _rallyId: string,\n operation: (transaction: ServerPersistenceAdapter) => Promise<T>,\n ): Promise<T> {\n const snapshot = {\n stocks: new Map(this.#stocks),\n idempotent: new Map(this.#idempotent),\n states: new Map(this.#states),\n claims: new Map(this.#claims),\n claimRecords: structuredClone(this.#claimRecords),\n auditLogs: structuredClone(this.#auditLogs),\n };\n try {\n return await operation(this);\n } catch (error) {\n this.#stocks.clear();\n for (const [key, value] of snapshot.stocks) this.#stocks.set(key, value);\n this.#idempotent.clear();\n for (const [key, value] of snapshot.idempotent)\n this.#idempotent.set(key, structuredClone(value));\n this.#states.clear();\n for (const [key, value] of snapshot.states) this.#states.set(key, structuredClone(value));\n this.#claims.clear();\n for (const [key, value] of snapshot.claims) this.#claims.set(key, value);\n this.#claimRecords.splice(0, this.#claimRecords.length, ...snapshot.claimRecords);\n this.#auditLogs.splice(0, this.#auditLogs.length, ...snapshot.auditLogs);\n throw error;\n }\n }\n}\n","import {\n type AdminRallyConfig,\n type CheckInCondition,\n consumeReward,\n evaluateConditionDetailed,\n reconcileRewardStates,\n type UserRallyState,\n type Validator,\n type VerificationContext,\n} from \"@stamprally/core\";\nimport type {\n AuditLog,\n CheckInRequest,\n CheckInResponse,\n ClaimRewardRequest,\n ServerOptions,\n} from \"./index.js\";\nimport type { ServerPersistenceAdapter } from \"./persistence.js\";\n\ntype ErrorResponse = { readonly ok: false; readonly code: string; readonly message: string };\ntype ClaimResponse =\n | { readonly ok: true; readonly state: UserRallyState; readonly claimTicketNumber?: string }\n | ErrorResponse;\nfunction json(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"content-type\": \"application/json; charset=utf-8\" },\n });\n}\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\nfunction requestId(prefix: string): string {\n return `${prefix}-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;\n}\nfunction now(options: ServerOptions, requested?: string): string {\n return requested ?? options.now?.() ?? new Date().toISOString();\n}\nfunction initialState(config: AdminRallyConfig, userId: string, timestamp: string): UserRallyState {\n return {\n rallyId: config.id,\n userId,\n records: [],\n rewards: reconcileRewardStates(config.rewards, [], 0, timestamp),\n updatedAt: timestamp,\n };\n}\nfunction getProof(context: VerificationContext): unknown {\n return context.type === \"qr\"\n ? context.token\n : context.type === \"passcode\"\n ? context.code\n : context.type === \"gps\"\n ? { latitude: context.latitude, longitude: context.longitude }\n : context.type === \"nfc\"\n ? context.tagId\n : context.value;\n}\nasync function evaluate(\n condition: CheckInCondition,\n context: VerificationContext,\n validator: Validator | undefined,\n base: { rallyId: string; spotId: string; state: UserRallyState },\n): Promise<boolean> {\n if (condition.type !== \"custom\") return evaluateConditionDetailed(condition, context).ok;\n if (validator === undefined) return false;\n const validationContext = {\n rallyId: base.rallyId,\n spotId: base.spotId,\n proofData: getProof(context),\n condition,\n userState: base.state,\n };\n const result =\n typeof validator === \"function\"\n ? await validator(validationContext)\n : await validator.validate(validationContext);\n return result === true || (typeof result === \"object\" && result.valid);\n}\nfunction audit(\n rallyId: string,\n userId: string,\n action: AuditLog[\"action\"],\n resourceId: string,\n key: string,\n status: AuditLog[\"status\"],\n timestamp: string,\n code?: string,\n): AuditLog {\n return {\n id: requestId(\"audit\"),\n timestamp,\n rallyId,\n userId,\n action,\n resourceId,\n status,\n idempotencyKey: key,\n ...(code === undefined ? {} : { metadata: { errorCode: code } }),\n };\n}\n\nexport class StampRallyServer {\n readonly #config: AdminRallyConfig;\n readonly #persistence: ServerPersistenceAdapter;\n readonly #options: ServerOptions;\n constructor(\n config: AdminRallyConfig,\n persistence: ServerPersistenceAdapter,\n options: ServerOptions = {},\n ) {\n this.#config = config;\n this.#persistence = persistence;\n this.#options = options;\n }\n async handle(request: Request): Promise<Response> {\n if (request.method !== \"POST\")\n return json({ ok: false, code: \"METHOD_NOT_ALLOWED\", message: \"POST is required.\" }, 405);\n const path = new URL(request.url).pathname;\n if (path.endsWith(\"/check-in\")) return this.handleCheckIn(request);\n if (path.endsWith(\"/claim-reward\")) return this.handleClaimReward(request);\n if (path.endsWith(\"/sync\")) return this.handleSync(request);\n return json({ ok: false, code: \"NOT_FOUND\", message: \"Route not found.\" }, 404);\n }\n async handleCheckIn(request: Request): Promise<Response> {\n const body = await this.#body<CheckInRequest>(request);\n const userId = await this.#user(request, body?.userId);\n if (\n body === null ||\n userId === null ||\n body.rallyId !== this.#config.id ||\n body.spotId === \"\" ||\n body.idempotencyKey === \"\" ||\n body.context === undefined\n )\n return json(\n {\n ok: false,\n code: \"INVALID_REQUEST\",\n message: \"rallyId, spotId, context, and idempotencyKey are required.\",\n },\n 400,\n );\n const result = await this.checkIn({ ...body, userId });\n return json(result, result.ok ? 200 : result.code === \"SPOT_NOT_FOUND\" ? 404 : 422);\n }\n async handleClaimReward(request: Request): Promise<Response> {\n const body = await this.#body<ClaimRewardRequest>(request);\n const userId = await this.#user(request, body?.userId);\n if (\n body === null ||\n userId === null ||\n body.rallyId !== this.#config.id ||\n body.rewardId === \"\" ||\n body.idempotencyKey === \"\"\n )\n return json(\n {\n ok: false,\n code: \"INVALID_REQUEST\",\n message: \"rallyId, rewardId, and idempotencyKey are required.\",\n },\n 400,\n );\n const result = await this.claimReward({ ...body, userId });\n return json(result, result.ok ? 200 : result.code === \"REWARD_NOT_FOUND\" ? 404 : 422);\n }\n async handleSync(request: Request): Promise<Response> {\n const body = await this.#body<{ readonly rallyId: string; readonly userId?: string }>(request);\n const userId = await this.#user(request, body?.userId);\n if (body === null || userId === null || body.rallyId !== this.#config.id)\n return json({ ok: false, code: \"INVALID_REQUEST\", message: \"rallyId is required.\" }, 400);\n return json({ ok: true, state: await this.sync(body.rallyId, userId) });\n }\n async checkIn(request: CheckInRequest & { readonly userId: string }): Promise<CheckInResponse> {\n const key = `check-in:${request.rallyId}:${request.userId}:${request.idempotencyKey}`;\n const previous = await this.#persistence.getIdempotentResult<CheckInResponse>(\n request.rallyId,\n key,\n );\n if (previous !== null) return previous;\n const lockKey = `state:${request.rallyId}:${request.userId}`;\n if (\n !(await this.#persistence.acquireLock(\n request.rallyId,\n lockKey,\n this.#options.lockTtlMs ?? 5_000,\n ))\n )\n return { ok: false, code: \"CONFLICT\", message: \"The user state is being updated.\" };\n const timestamp = now(this.#options, request.now);\n try {\n const spot = this.#config.spots.find((item) => item.id === request.spotId);\n if (spot === undefined)\n return this.#remember(\n key,\n { ok: false, code: \"SPOT_NOT_FOUND\", message: \"Spot was not found.\" },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n const current =\n (await this.#persistence.getUserState(request.rallyId, request.userId)) ??\n initialState(this.#config, request.userId, timestamp);\n if (current.records.some((record) => record.stampId === request.spotId))\n return this.#remember(\n key,\n { ok: false, code: \"STAMP_ALREADY_ACQUIRED\", message: \"Spot was already claimed.\" },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n const acquired = new Set(current.records.map((record) => record.stampId));\n if (spot.prerequisites?.some((id) => !acquired.has(id)))\n return this.#remember(\n key,\n {\n ok: false,\n code: \"PREREQUISITES_NOT_MET\",\n message: \"Prerequisite spots are not complete.\",\n },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n for (const condition of spot.conditions)\n if (\n !(await evaluate(\n condition,\n request.context,\n condition.type === \"custom\"\n ? this.#options.customValidators?.[condition.validatorName]\n : undefined,\n { rallyId: request.rallyId, spotId: request.spotId, state: current },\n ))\n )\n return this.#remember(\n key,\n { ok: false, code: \"INVALID_PROOF\", message: \"Verification failed.\" },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n const next: UserRallyState = {\n ...current,\n records: [...current.records, { stampId: request.spotId, acquiredAt: timestamp }],\n rewards: reconcileRewardStates(\n this.#config.rewards,\n current.rewards,\n current.records.length + 1,\n timestamp,\n ),\n updatedAt: timestamp,\n };\n await this.#persistence.saveUserState(request.rallyId, request.userId, next);\n return this.#remember(\n key,\n { ok: true, state: next },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n } finally {\n await this.#persistence.releaseLock(request.rallyId, lockKey);\n }\n }\n async claimReward(\n request: ClaimRewardRequest & { readonly userId: string },\n ): Promise<ClaimResponse> {\n if (this.#persistence.runTransaction !== undefined)\n return this.#persistence.runTransaction(request.rallyId, (transaction) =>\n this.#claimRewardMutation(request, transaction),\n );\n return this.#claimRewardMutation(request, this.#persistence);\n }\n async #claimRewardMutation(\n request: ClaimRewardRequest & { readonly userId: string },\n persistence: ServerPersistenceAdapter,\n ): Promise<ClaimResponse> {\n const key = `claim:${request.rallyId}:${request.userId}:${request.rewardId}:${request.idempotencyKey}`;\n const previous = await persistence.getIdempotentResult<ClaimResponse>(request.rallyId, key);\n if (previous !== null) return previous;\n const reward = this.#config.rewards.find((item) => item.id === request.rewardId);\n if (reward === undefined)\n return this.#rememberClaim(\n key,\n { ok: false, code: \"REWARD_NOT_FOUND\", message: \"Reward was not found.\" },\n request,\n now(this.#options, request.now),\n persistence,\n );\n const lockKey = `reward:${request.rallyId}:${reward.id}`;\n if (\n !(await persistence.acquireLock(request.rallyId, lockKey, this.#options.lockTtlMs ?? 5_000))\n )\n return { ok: false, code: \"CONFLICT\", message: \"The reward is being claimed.\" };\n const timestamp = now(this.#options, request.now);\n let decremented = false;\n let previousState: UserRallyState | null = null;\n let recordedTicket: string | null = null;\n let successAuditId: string | null = null;\n try {\n const checked = await persistence.getIdempotentResult<ClaimResponse>(request.rallyId, key);\n if (checked !== null) return checked;\n const storedState = await persistence.getUserState(request.rallyId, request.userId);\n previousState = storedState;\n const current = storedState ?? initialState(this.#config, request.userId, timestamp);\n const claimCount = await persistence.getUserClaimCount(\n request.rallyId,\n request.userId,\n reward.id,\n );\n const storedReward = current.rewards.find((item) => item.rewardId === reward.id) ?? {\n rewardId: reward.id,\n status: \"LOCKED\" as const,\n };\n const currentReward =\n reward.redemptionMethod === \"server_claim\" &&\n storedReward.status === \"CONSUMED\" &&\n (reward.userClaimLimit === undefined || claimCount < reward.userClaimLimit)\n ? { ...storedReward, status: \"AVAILABLE\" as const }\n : storedReward;\n const local = consumeReward({\n reward,\n currentState: currentReward,\n now: timestamp,\n userRedemptionCount: claimCount,\n ...(request.staffPasscode === undefined ? {} : { inputPasscode: request.staffPasscode }),\n ...(request.staffId === undefined ? {} : { staffId: request.staffId }),\n });\n if (!local.ok)\n return this.#rememberClaim(\n key,\n { ok: false, code: local.error.code, message: \"Reward cannot be claimed.\" },\n request,\n timestamp,\n persistence,\n );\n const stock = await persistence.decrementRewardStock(request.rallyId, reward.id);\n if (!stock.success)\n return this.#rememberClaim(\n key,\n { ok: false, code: \"OUT_OF_STOCK\", message: \"Reward is out of stock.\" },\n request,\n timestamp,\n persistence,\n );\n decremented = true;\n const next: UserRallyState = {\n ...current,\n rewards: current.rewards.map((item) => (item.rewardId === reward.id ? local.value : item)),\n updatedAt: timestamp,\n };\n try {\n await persistence.saveUserState(request.rallyId, request.userId, next);\n const response: ClaimResponse =\n local.value.claimTicketNumber === undefined\n ? { ok: true, state: next }\n : { ok: true, state: next, claimTicketNumber: local.value.claimTicketNumber };\n recordedTicket = local.value.claimTicketNumber ?? \"\";\n await persistence.recordUserClaim({\n rallyId: request.rallyId,\n userId: request.userId,\n rewardId: reward.id,\n ticketNumber: recordedTicket,\n timestamp: Number.isNaN(Date.parse(timestamp)) ? Date.now() : Date.parse(timestamp),\n });\n const successAudit = audit(\n request.rallyId,\n request.userId,\n \"CLAIM_REWARD\",\n reward.id,\n request.idempotencyKey,\n \"SUCCESS\",\n timestamp,\n );\n successAuditId = successAudit.id;\n await persistence.recordAuditLog(successAudit);\n await persistence.saveIdempotentResult(\n request.rallyId,\n key,\n response,\n this.#options.idempotencyTtlMs ?? 86_400_000,\n );\n return response;\n } catch (error) {\n if (recordedTicket !== null && persistence.rollbackUserClaim !== undefined)\n await persistence.rollbackUserClaim(\n request.rallyId,\n request.userId,\n reward.id,\n recordedTicket,\n );\n if (persistence.rollbackUserState !== undefined)\n await persistence.rollbackUserState(request.rallyId, request.userId, previousState);\n if (successAuditId !== null && persistence.removeAuditLog !== undefined)\n await persistence.removeAuditLog(successAuditId);\n await this.#restoreRewardStock(persistence, request.rallyId, reward.id);\n decremented = false;\n throw error;\n }\n } catch (error) {\n if (decremented) await this.#restoreRewardStock(persistence, request.rallyId, reward.id);\n return {\n ok: false,\n code: \"PERSISTENCE_FAILED\",\n message: error instanceof Error ? error.message : \"Reward claim failed.\",\n };\n } finally {\n await persistence.releaseLock(request.rallyId, lockKey);\n }\n }\n async sync(rallyId: string, userId: string): Promise<UserRallyState> {\n return (\n (await this.#persistence.getUserState(rallyId, userId)) ??\n initialState(this.#config, userId, now(this.#options))\n );\n }\n async #body<T>(request: Request): Promise<T | null> {\n try {\n const value: unknown = await request.json();\n return isObject(value) ? (value as T) : null;\n } catch {\n return null;\n }\n }\n async #user(request: Request, requested: string | undefined): Promise<string | null> {\n if (this.#options.authenticate !== undefined)\n return (await this.#options.authenticate(request)) ?? null;\n return requested ?? null;\n }\n async #remember(\n key: string,\n result: CheckInResponse,\n rallyId: string,\n userId: string,\n action: AuditLog[\"action\"],\n resourceId: string,\n idempotencyKey: string,\n timestamp: string,\n ): Promise<CheckInResponse> {\n await this.#persistence.recordAuditLog(\n audit(\n rallyId,\n userId,\n action,\n resourceId,\n idempotencyKey,\n result.ok ? \"SUCCESS\" : \"REJECTED\",\n timestamp,\n result.ok ? undefined : result.code,\n ),\n );\n await this.#persistence.saveIdempotentResult(\n rallyId,\n key,\n result,\n this.#options.idempotencyTtlMs ?? 86_400_000,\n );\n return result;\n }\n async #rememberClaim(\n key: string,\n result: ClaimResponse,\n request: ClaimRewardRequest & { readonly userId: string },\n timestamp: string,\n persistence: ServerPersistenceAdapter = this.#persistence,\n ): Promise<ClaimResponse> {\n await persistence.recordAuditLog(\n audit(\n request.rallyId,\n request.userId,\n \"CLAIM_REWARD\",\n request.rewardId,\n request.idempotencyKey,\n \"REJECTED\",\n timestamp,\n result.ok ? undefined : result.code,\n ),\n );\n await persistence.saveIdempotentResult(\n request.rallyId,\n key,\n result,\n this.#options.idempotencyTtlMs ?? 86_400_000,\n );\n return result;\n }\n async #restoreRewardStock(\n persistence: ServerPersistenceAdapter,\n rallyId: string,\n rewardId: string,\n ): Promise<void> {\n if (persistence.restoreRewardStock !== undefined)\n await persistence.restoreRewardStock(rallyId, rewardId);\n }\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -10,6 +10,8 @@ interface UserClaimRecord {
10
10
  readonly timestamp: number;
11
11
  }
12
12
  interface ServerPersistenceAdapter {
13
+ /** Runs the callback in a storage-native transaction when supported. */
14
+ runTransaction?<T>(rallyId: string, operation: (transaction: ServerPersistenceAdapter) => Promise<T>): Promise<T>;
13
15
  acquireLock(rallyId: string, lockKey: string, ttlMs: number): Promise<boolean>;
14
16
  releaseLock(rallyId: string, lockKey: string): Promise<void>;
15
17
  getRewardStock(rallyId: string, rewardId: string): Promise<number | null>;
@@ -17,7 +19,9 @@ interface ServerPersistenceAdapter {
17
19
  readonly success: boolean;
18
20
  readonly remainingStock: number;
19
21
  }>;
20
- restoreRewardStock?(rallyId: string, rewardId: string): Promise<void>;
22
+ restoreRewardStock?(rallyId: string, rewardId: string, count?: number): Promise<void>;
23
+ rollbackUserState?(rallyId: string, userId: string, previousState: UserRallyState | null): Promise<void>;
24
+ rollbackUserClaim?(rallyId: string, userId: string, rewardId: string, ticketNumber: string): Promise<void>;
21
25
  getIdempotentResult<T>(rallyId: string, key: string): Promise<T | null>;
22
26
  saveIdempotentResult<T>(rallyId: string, key: string, result: T, ttlMs: number): Promise<void>;
23
27
  getUserClaimCount(rallyId: string, userId: string, rewardId: string): Promise<number>;
@@ -27,6 +31,7 @@ interface ServerPersistenceAdapter {
27
31
  getUserState(rallyId: string, userId: string): Promise<UserRallyState | null>;
28
32
  saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void>;
29
33
  recordAuditLog(log: AuditLog): Promise<void>;
34
+ removeAuditLog?(auditId: string): Promise<void>;
30
35
  }
31
36
  interface InMemoryServerPersistenceOptions {
32
37
  readonly stocks?: Readonly<Record<string, number>>;
@@ -41,20 +46,24 @@ declare class InMemoryServerPersistenceAdapter implements ServerPersistenceAdapt
41
46
  success: boolean;
42
47
  remainingStock: number;
43
48
  }>;
44
- restoreRewardStock(rallyId: string, rewardId: string): Promise<void>;
49
+ restoreRewardStock(rallyId: string, rewardId: string, count?: number): Promise<void>;
50
+ rollbackUserState(rallyId: string, userId: string, previousState: UserRallyState | null): Promise<void>;
45
51
  getIdempotentResult<T>(rallyId: string, key: string): Promise<T | null>;
46
52
  saveIdempotentResult<T>(rallyId: string, key: string, result: T, ttlMs: number): Promise<void>;
47
53
  getUserClaimCount(rallyId: string, userId: string, rewardId: string): Promise<number>;
48
54
  getUserState(rallyId: string, userId: string): Promise<UserRallyState | null>;
49
55
  saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void>;
50
56
  recordAuditLog(log: AuditLog): Promise<void>;
57
+ removeAuditLog(auditId: string): Promise<void>;
51
58
  getAuditLogs(): ReadonlyArray<AuditLog>;
52
59
  recordUserClaim(params: UserClaimRecord): Promise<void>;
60
+ rollbackUserClaim(rallyId: string, userId: string, rewardId: string, ticketNumber: string): Promise<void>;
53
61
  incrementUserClaimCount(rallyId: string, userId: string, rewardId: string): Promise<void>;
54
62
  getClaimRecords(): ReadonlyArray<UserClaimRecord>;
55
63
  /** @deprecated Use recordUserClaim with a ticket number and timestamp. */
56
64
  recordClaim(params: UserClaimRecord): Promise<void>;
57
65
  recordClaim(rallyId: string, userId: string, rewardId: string): Promise<void>;
66
+ runTransaction<T>(_rallyId: string, operation: (transaction: ServerPersistenceAdapter) => Promise<T>): Promise<T>;
58
67
  }
59
68
 
60
69
  type ErrorResponse = {
package/dist/index.d.ts CHANGED
@@ -10,6 +10,8 @@ interface UserClaimRecord {
10
10
  readonly timestamp: number;
11
11
  }
12
12
  interface ServerPersistenceAdapter {
13
+ /** Runs the callback in a storage-native transaction when supported. */
14
+ runTransaction?<T>(rallyId: string, operation: (transaction: ServerPersistenceAdapter) => Promise<T>): Promise<T>;
13
15
  acquireLock(rallyId: string, lockKey: string, ttlMs: number): Promise<boolean>;
14
16
  releaseLock(rallyId: string, lockKey: string): Promise<void>;
15
17
  getRewardStock(rallyId: string, rewardId: string): Promise<number | null>;
@@ -17,7 +19,9 @@ interface ServerPersistenceAdapter {
17
19
  readonly success: boolean;
18
20
  readonly remainingStock: number;
19
21
  }>;
20
- restoreRewardStock?(rallyId: string, rewardId: string): Promise<void>;
22
+ restoreRewardStock?(rallyId: string, rewardId: string, count?: number): Promise<void>;
23
+ rollbackUserState?(rallyId: string, userId: string, previousState: UserRallyState | null): Promise<void>;
24
+ rollbackUserClaim?(rallyId: string, userId: string, rewardId: string, ticketNumber: string): Promise<void>;
21
25
  getIdempotentResult<T>(rallyId: string, key: string): Promise<T | null>;
22
26
  saveIdempotentResult<T>(rallyId: string, key: string, result: T, ttlMs: number): Promise<void>;
23
27
  getUserClaimCount(rallyId: string, userId: string, rewardId: string): Promise<number>;
@@ -27,6 +31,7 @@ interface ServerPersistenceAdapter {
27
31
  getUserState(rallyId: string, userId: string): Promise<UserRallyState | null>;
28
32
  saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void>;
29
33
  recordAuditLog(log: AuditLog): Promise<void>;
34
+ removeAuditLog?(auditId: string): Promise<void>;
30
35
  }
31
36
  interface InMemoryServerPersistenceOptions {
32
37
  readonly stocks?: Readonly<Record<string, number>>;
@@ -41,20 +46,24 @@ declare class InMemoryServerPersistenceAdapter implements ServerPersistenceAdapt
41
46
  success: boolean;
42
47
  remainingStock: number;
43
48
  }>;
44
- restoreRewardStock(rallyId: string, rewardId: string): Promise<void>;
49
+ restoreRewardStock(rallyId: string, rewardId: string, count?: number): Promise<void>;
50
+ rollbackUserState(rallyId: string, userId: string, previousState: UserRallyState | null): Promise<void>;
45
51
  getIdempotentResult<T>(rallyId: string, key: string): Promise<T | null>;
46
52
  saveIdempotentResult<T>(rallyId: string, key: string, result: T, ttlMs: number): Promise<void>;
47
53
  getUserClaimCount(rallyId: string, userId: string, rewardId: string): Promise<number>;
48
54
  getUserState(rallyId: string, userId: string): Promise<UserRallyState | null>;
49
55
  saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void>;
50
56
  recordAuditLog(log: AuditLog): Promise<void>;
57
+ removeAuditLog(auditId: string): Promise<void>;
51
58
  getAuditLogs(): ReadonlyArray<AuditLog>;
52
59
  recordUserClaim(params: UserClaimRecord): Promise<void>;
60
+ rollbackUserClaim(rallyId: string, userId: string, rewardId: string, ticketNumber: string): Promise<void>;
53
61
  incrementUserClaimCount(rallyId: string, userId: string, rewardId: string): Promise<void>;
54
62
  getClaimRecords(): ReadonlyArray<UserClaimRecord>;
55
63
  /** @deprecated Use recordUserClaim with a ticket number and timestamp. */
56
64
  recordClaim(params: UserClaimRecord): Promise<void>;
57
65
  recordClaim(rallyId: string, userId: string, rewardId: string): Promise<void>;
66
+ runTransaction<T>(_rallyId: string, operation: (transaction: ServerPersistenceAdapter) => Promise<T>): Promise<T>;
58
67
  }
59
68
 
60
69
  type ErrorResponse = {
package/dist/index.js CHANGED
@@ -53,10 +53,15 @@ var InMemoryServerPersistenceAdapter = class {
53
53
  this.#stocks.set(key, current - 1);
54
54
  return { success: true, remainingStock: current - 1 };
55
55
  }
56
- async restoreRewardStock(rallyId, rewardId) {
56
+ async restoreRewardStock(rallyId, rewardId, count = 1) {
57
57
  const key = this.#stockKey(rallyId, rewardId);
58
58
  const current = this.#stocks.get(key);
59
- if (current !== void 0) this.#stocks.set(key, current + 1);
59
+ if (current !== void 0) this.#stocks.set(key, current + Math.max(0, count));
60
+ }
61
+ async rollbackUserState(rallyId, userId, previousState) {
62
+ const key = `${rallyId}:${userId}`;
63
+ if (previousState === null) this.#states.delete(key);
64
+ else this.#states.set(key, structuredClone(previousState));
60
65
  }
61
66
  async getIdempotentResult(rallyId, key) {
62
67
  const value = this.#idempotent.get(this.#key(rallyId, key));
@@ -85,6 +90,10 @@ var InMemoryServerPersistenceAdapter = class {
85
90
  async recordAuditLog(log) {
86
91
  this.#auditLogs.push(structuredClone(log));
87
92
  }
93
+ async removeAuditLog(auditId) {
94
+ const index = this.#auditLogs.findIndex((log) => log.id === auditId);
95
+ if (index >= 0) this.#auditLogs.splice(index, 1);
96
+ }
88
97
  getAuditLogs() {
89
98
  return structuredClone(this.#auditLogs);
90
99
  }
@@ -94,6 +103,16 @@ var InMemoryServerPersistenceAdapter = class {
94
103
  this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);
95
104
  this.#claimRecords.push(structuredClone(params));
96
105
  }
106
+ async rollbackUserClaim(rallyId, userId, rewardId, ticketNumber) {
107
+ const key = `${rallyId}:${userId}:${rewardId}`;
108
+ const count = this.#claims.get(key) ?? 0;
109
+ if (count <= 1) this.#claims.delete(key);
110
+ else this.#claims.set(key, count - 1);
111
+ const index = this.#claimRecords.findIndex(
112
+ (record) => record.rallyId === rallyId && record.userId === userId && record.rewardId === rewardId && record.ticketNumber === ticketNumber
113
+ );
114
+ if (index >= 0) this.#claimRecords.splice(index, 1);
115
+ }
97
116
  async incrementUserClaimCount(rallyId, userId, rewardId) {
98
117
  const key = `${rallyId}:${userId}:${rewardId}`;
99
118
  this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);
@@ -111,6 +130,32 @@ var InMemoryServerPersistenceAdapter = class {
111
130
  } : paramsOrRallyId;
112
131
  return this.recordUserClaim(params);
113
132
  }
133
+ async runTransaction(_rallyId, operation) {
134
+ const snapshot = {
135
+ stocks: new Map(this.#stocks),
136
+ idempotent: new Map(this.#idempotent),
137
+ states: new Map(this.#states),
138
+ claims: new Map(this.#claims),
139
+ claimRecords: structuredClone(this.#claimRecords),
140
+ auditLogs: structuredClone(this.#auditLogs)
141
+ };
142
+ try {
143
+ return await operation(this);
144
+ } catch (error) {
145
+ this.#stocks.clear();
146
+ for (const [key, value] of snapshot.stocks) this.#stocks.set(key, value);
147
+ this.#idempotent.clear();
148
+ for (const [key, value] of snapshot.idempotent)
149
+ this.#idempotent.set(key, structuredClone(value));
150
+ this.#states.clear();
151
+ for (const [key, value] of snapshot.states) this.#states.set(key, structuredClone(value));
152
+ this.#claims.clear();
153
+ for (const [key, value] of snapshot.claims) this.#claims.set(key, value);
154
+ this.#claimRecords.splice(0, this.#claimRecords.length, ...snapshot.claimRecords);
155
+ this.#auditLogs.splice(0, this.#auditLogs.length, ...snapshot.auditLogs);
156
+ throw error;
157
+ }
158
+ }
114
159
  };
115
160
  function json(body, status = 200) {
116
161
  return new Response(JSON.stringify(body), {
@@ -320,11 +365,16 @@ var StampRallyServer = class {
320
365
  }
321
366
  }
322
367
  async claimReward(request) {
368
+ if (this.#persistence.runTransaction !== void 0)
369
+ return this.#persistence.runTransaction(
370
+ request.rallyId,
371
+ (transaction) => this.#claimRewardMutation(request, transaction)
372
+ );
373
+ return this.#claimRewardMutation(request, this.#persistence);
374
+ }
375
+ async #claimRewardMutation(request, persistence) {
323
376
  const key = `claim:${request.rallyId}:${request.userId}:${request.rewardId}:${request.idempotencyKey}`;
324
- const previous = await this.#persistence.getIdempotentResult(
325
- request.rallyId,
326
- key
327
- );
377
+ const previous = await persistence.getIdempotentResult(request.rallyId, key);
328
378
  if (previous !== null) return previous;
329
379
  const reward = this.#config.rewards.find((item) => item.id === request.rewardId);
330
380
  if (reward === void 0)
@@ -332,25 +382,24 @@ var StampRallyServer = class {
332
382
  key,
333
383
  { ok: false, code: "REWARD_NOT_FOUND", message: "Reward was not found." },
334
384
  request,
335
- now(this.#options, request.now)
385
+ now(this.#options, request.now),
386
+ persistence
336
387
  );
337
388
  const lockKey = `reward:${request.rallyId}:${reward.id}`;
338
- if (!await this.#persistence.acquireLock(
339
- request.rallyId,
340
- lockKey,
341
- this.#options.lockTtlMs ?? 5e3
342
- ))
389
+ if (!await persistence.acquireLock(request.rallyId, lockKey, this.#options.lockTtlMs ?? 5e3))
343
390
  return { ok: false, code: "CONFLICT", message: "The reward is being claimed." };
344
391
  const timestamp = now(this.#options, request.now);
345
392
  let decremented = false;
393
+ let previousState = null;
394
+ let recordedTicket = null;
395
+ let successAuditId = null;
346
396
  try {
347
- const checked = await this.#persistence.getIdempotentResult(
348
- request.rallyId,
349
- key
350
- );
397
+ const checked = await persistence.getIdempotentResult(request.rallyId, key);
351
398
  if (checked !== null) return checked;
352
- const current = await this.#persistence.getUserState(request.rallyId, request.userId) ?? initialState(this.#config, request.userId, timestamp);
353
- const claimCount = await this.#persistence.getUserClaimCount(
399
+ const storedState = await persistence.getUserState(request.rallyId, request.userId);
400
+ previousState = storedState;
401
+ const current = storedState ?? initialState(this.#config, request.userId, timestamp);
402
+ const claimCount = await persistence.getUserClaimCount(
354
403
  request.rallyId,
355
404
  request.userId,
356
405
  reward.id
@@ -373,15 +422,17 @@ var StampRallyServer = class {
373
422
  key,
374
423
  { ok: false, code: local.error.code, message: "Reward cannot be claimed." },
375
424
  request,
376
- timestamp
425
+ timestamp,
426
+ persistence
377
427
  );
378
- const stock = await this.#persistence.decrementRewardStock(request.rallyId, reward.id);
428
+ const stock = await persistence.decrementRewardStock(request.rallyId, reward.id);
379
429
  if (!stock.success)
380
430
  return this.#rememberClaim(
381
431
  key,
382
432
  { ok: false, code: "OUT_OF_STOCK", message: "Reward is out of stock." },
383
433
  request,
384
- timestamp
434
+ timestamp,
435
+ persistence
385
436
  );
386
437
  decremented = true;
387
438
  const next = {
@@ -390,27 +441,28 @@ var StampRallyServer = class {
390
441
  updatedAt: timestamp
391
442
  };
392
443
  try {
393
- await this.#persistence.saveUserState(request.rallyId, request.userId, next);
444
+ await persistence.saveUserState(request.rallyId, request.userId, next);
394
445
  const response = local.value.claimTicketNumber === void 0 ? { ok: true, state: next } : { ok: true, state: next, claimTicketNumber: local.value.claimTicketNumber };
395
- await this.#persistence.recordUserClaim({
446
+ recordedTicket = local.value.claimTicketNumber ?? "";
447
+ await persistence.recordUserClaim({
396
448
  rallyId: request.rallyId,
397
449
  userId: request.userId,
398
450
  rewardId: reward.id,
399
- ticketNumber: local.value.claimTicketNumber ?? "",
451
+ ticketNumber: recordedTicket,
400
452
  timestamp: Number.isNaN(Date.parse(timestamp)) ? Date.now() : Date.parse(timestamp)
401
453
  });
402
- await this.#persistence.recordAuditLog(
403
- audit(
404
- request.rallyId,
405
- request.userId,
406
- "CLAIM_REWARD",
407
- reward.id,
408
- request.idempotencyKey,
409
- "SUCCESS",
410
- timestamp
411
- )
454
+ const successAudit = audit(
455
+ request.rallyId,
456
+ request.userId,
457
+ "CLAIM_REWARD",
458
+ reward.id,
459
+ request.idempotencyKey,
460
+ "SUCCESS",
461
+ timestamp
412
462
  );
413
- await this.#persistence.saveIdempotentResult(
463
+ successAuditId = successAudit.id;
464
+ await persistence.recordAuditLog(successAudit);
465
+ await persistence.saveIdempotentResult(
414
466
  request.rallyId,
415
467
  key,
416
468
  response,
@@ -418,19 +470,30 @@ var StampRallyServer = class {
418
470
  );
419
471
  return response;
420
472
  } catch (error) {
421
- await this.#restoreRewardStock(request.rallyId, reward.id);
473
+ if (recordedTicket !== null && persistence.rollbackUserClaim !== void 0)
474
+ await persistence.rollbackUserClaim(
475
+ request.rallyId,
476
+ request.userId,
477
+ reward.id,
478
+ recordedTicket
479
+ );
480
+ if (persistence.rollbackUserState !== void 0)
481
+ await persistence.rollbackUserState(request.rallyId, request.userId, previousState);
482
+ if (successAuditId !== null && persistence.removeAuditLog !== void 0)
483
+ await persistence.removeAuditLog(successAuditId);
484
+ await this.#restoreRewardStock(persistence, request.rallyId, reward.id);
422
485
  decremented = false;
423
486
  throw error;
424
487
  }
425
488
  } catch (error) {
426
- if (decremented) await this.#restoreRewardStock(request.rallyId, reward.id);
489
+ if (decremented) await this.#restoreRewardStock(persistence, request.rallyId, reward.id);
427
490
  return {
428
491
  ok: false,
429
492
  code: "PERSISTENCE_FAILED",
430
493
  message: error instanceof Error ? error.message : "Reward claim failed."
431
494
  };
432
495
  } finally {
433
- await this.#persistence.releaseLock(request.rallyId, lockKey);
496
+ await persistence.releaseLock(request.rallyId, lockKey);
434
497
  }
435
498
  }
436
499
  async sync(rallyId, userId) {
@@ -470,8 +533,8 @@ var StampRallyServer = class {
470
533
  );
471
534
  return result;
472
535
  }
473
- async #rememberClaim(key, result, request, timestamp) {
474
- await this.#persistence.recordAuditLog(
536
+ async #rememberClaim(key, result, request, timestamp, persistence = this.#persistence) {
537
+ await persistence.recordAuditLog(
475
538
  audit(
476
539
  request.rallyId,
477
540
  request.userId,
@@ -483,7 +546,7 @@ var StampRallyServer = class {
483
546
  result.ok ? void 0 : result.code
484
547
  )
485
548
  );
486
- await this.#persistence.saveIdempotentResult(
549
+ await persistence.saveIdempotentResult(
487
550
  request.rallyId,
488
551
  key,
489
552
  result,
@@ -491,9 +554,9 @@ var StampRallyServer = class {
491
554
  );
492
555
  return result;
493
556
  }
494
- async #restoreRewardStock(rallyId, rewardId) {
495
- if (this.#persistence.restoreRewardStock !== void 0)
496
- await this.#persistence.restoreRewardStock(rallyId, rewardId);
557
+ async #restoreRewardStock(persistence, rallyId, rewardId) {
558
+ if (persistence.restoreRewardStock !== void 0)
559
+ await persistence.restoreRewardStock(rallyId, rewardId);
497
560
  }
498
561
  };
499
562
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/persistence.ts","../src/server.ts"],"names":["now"],"mappings":"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","file":"index.js","sourcesContent":["import type { UserRallyState } from \"@stamprally/core\";\nimport type { AuditLog } from \"./index.js\";\n\nexport interface UserClaimRecord {\n readonly rallyId: string;\n readonly userId: string;\n readonly rewardId: string;\n readonly ticketNumber: string;\n readonly timestamp: number;\n}\n\nexport interface ServerPersistenceAdapter {\n acquireLock(rallyId: string, lockKey: string, ttlMs: number): Promise<boolean>;\n releaseLock(rallyId: string, lockKey: string): Promise<void>;\n getRewardStock(rallyId: string, rewardId: string): Promise<number | null>;\n decrementRewardStock(\n rallyId: string,\n rewardId: string,\n ): Promise<{ readonly success: boolean; readonly remainingStock: number }>;\n restoreRewardStock?(rallyId: string, rewardId: string): Promise<void>;\n getIdempotentResult<T>(rallyId: string, key: string): Promise<T | null>;\n saveIdempotentResult<T>(rallyId: string, key: string, result: T, ttlMs: number): Promise<void>;\n getUserClaimCount(rallyId: string, userId: string, rewardId: string): Promise<number>;\n recordUserClaim(params: UserClaimRecord): Promise<void>;\n recordClaim?(params: UserClaimRecord): Promise<void>;\n incrementUserClaimCount?(rallyId: string, userId: string, rewardId: string): Promise<void>;\n getUserState(rallyId: string, userId: string): Promise<UserRallyState | null>;\n saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void>;\n recordAuditLog(log: AuditLog): Promise<void>;\n}\ninterface TimedValue<T> {\n readonly value: T;\n readonly expiresAt: number;\n}\nexport interface InMemoryServerPersistenceOptions {\n readonly stocks?: Readonly<Record<string, number>>;\n}\nexport class InMemoryServerPersistenceAdapter implements ServerPersistenceAdapter {\n readonly #locks = new Map<string, number>();\n readonly #idempotent = new Map<string, TimedValue<unknown>>();\n readonly #states = new Map<string, UserRallyState>();\n readonly #stocks: Map<string, number>;\n readonly #stockDefaults: Map<string, number>;\n readonly #claims = new Map<string, number>();\n readonly #claimRecords: UserClaimRecord[] = [];\n readonly #auditLogs: AuditLog[] = [];\n constructor(options: InMemoryServerPersistenceOptions = {}) {\n this.#stocks = new Map();\n this.#stockDefaults = new Map();\n for (const [key, stock] of Object.entries(options.stocks ?? {})) {\n if (key.includes(\":\")) this.#stocks.set(key, stock);\n else this.#stockDefaults.set(key, stock);\n }\n }\n #key(rallyId: string, key: string): string {\n return `${rallyId}:${key}`;\n }\n #stockKey(rallyId: string, rewardId: string): string {\n const scopedKey = `${rallyId}:${rewardId}`;\n if (this.#stocks.has(scopedKey)) return scopedKey;\n const defaultStock = this.#stockDefaults.get(rewardId);\n if (defaultStock !== undefined) {\n this.#stocks.set(scopedKey, defaultStock);\n return scopedKey;\n }\n return scopedKey;\n }\n async acquireLock(rallyId: string, key: string, ttlMs: number): Promise<boolean> {\n const scopedKey = this.#key(rallyId, key);\n const now = Date.now();\n const until = this.#locks.get(scopedKey);\n if (until !== undefined && until > now) return false;\n this.#locks.set(scopedKey, now + Math.max(1, ttlMs));\n return true;\n }\n async releaseLock(rallyId: string, key: string): Promise<void> {\n this.#locks.delete(this.#key(rallyId, key));\n }\n async getRewardStock(rallyId: string, rewardId: string): Promise<number | null> {\n return this.#stocks.get(this.#stockKey(rallyId, rewardId)) ?? null;\n }\n async decrementRewardStock(\n rallyId: string,\n rewardId: string,\n ): Promise<{ success: boolean; remainingStock: number }> {\n const key = this.#stockKey(rallyId, rewardId);\n const current = this.#stocks.get(key);\n if (current === undefined) return { success: true, remainingStock: Number.POSITIVE_INFINITY };\n if (current <= 0) return { success: false, remainingStock: 0 };\n this.#stocks.set(key, current - 1);\n return { success: true, remainingStock: current - 1 };\n }\n async restoreRewardStock(rallyId: string, rewardId: string): Promise<void> {\n const key = this.#stockKey(rallyId, rewardId);\n const current = this.#stocks.get(key);\n if (current !== undefined) this.#stocks.set(key, current + 1);\n }\n async getIdempotentResult<T>(rallyId: string, key: string): Promise<T | null> {\n const value = this.#idempotent.get(this.#key(rallyId, key));\n if (value === undefined || value.expiresAt <= Date.now()) {\n this.#idempotent.delete(this.#key(rallyId, key));\n return null;\n }\n return structuredClone(value.value) as T;\n }\n async saveIdempotentResult<T>(\n rallyId: string,\n key: string,\n result: T,\n ttlMs: number,\n ): Promise<void> {\n this.#idempotent.set(this.#key(rallyId, key), {\n value: structuredClone(result),\n expiresAt: Date.now() + Math.max(1, ttlMs),\n });\n }\n async getUserClaimCount(rallyId: string, userId: string, rewardId: string): Promise<number> {\n return this.#claims.get(`${rallyId}:${userId}:${rewardId}`) ?? 0;\n }\n async getUserState(rallyId: string, userId: string): Promise<UserRallyState | null> {\n const value = this.#states.get(`${rallyId}:${userId}`);\n return value === undefined ? null : structuredClone(value);\n }\n async saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void> {\n this.#states.set(`${rallyId}:${userId}`, structuredClone(state));\n }\n async recordAuditLog(log: AuditLog): Promise<void> {\n this.#auditLogs.push(structuredClone(log));\n }\n getAuditLogs(): ReadonlyArray<AuditLog> {\n return structuredClone(this.#auditLogs);\n }\n async recordUserClaim(params: UserClaimRecord): Promise<void> {\n const { rallyId, userId, rewardId } = params;\n const key = `${rallyId}:${userId}:${rewardId}`;\n this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);\n this.#claimRecords.push(structuredClone(params));\n }\n async incrementUserClaimCount(rallyId: string, userId: string, rewardId: string): Promise<void> {\n const key = `${rallyId}:${userId}:${rewardId}`;\n this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);\n }\n getClaimRecords(): ReadonlyArray<UserClaimRecord> {\n return structuredClone(this.#claimRecords);\n }\n /** @deprecated Use recordUserClaim with a ticket number and timestamp. */\n recordClaim(params: UserClaimRecord): Promise<void>;\n recordClaim(rallyId: string, userId: string, rewardId: string): Promise<void>;\n recordClaim(\n paramsOrRallyId: UserClaimRecord | string,\n userId?: string,\n rewardId?: string,\n ): Promise<void> {\n const params: UserClaimRecord =\n typeof paramsOrRallyId === \"string\"\n ? {\n rallyId: paramsOrRallyId,\n userId: userId ?? \"\",\n rewardId: rewardId ?? \"\",\n ticketNumber: \"\",\n timestamp: Date.now(),\n }\n : paramsOrRallyId;\n return this.recordUserClaim(params);\n }\n}\n","import {\n type AdminRallyConfig,\n type CheckInCondition,\n consumeReward,\n evaluateConditionDetailed,\n reconcileRewardStates,\n type UserRallyState,\n type Validator,\n type VerificationContext,\n} from \"@stamprally/core\";\nimport type {\n AuditLog,\n CheckInRequest,\n CheckInResponse,\n ClaimRewardRequest,\n ServerOptions,\n} from \"./index.js\";\nimport type { ServerPersistenceAdapter } from \"./persistence.js\";\n\ntype ErrorResponse = { readonly ok: false; readonly code: string; readonly message: string };\ntype ClaimResponse =\n | { readonly ok: true; readonly state: UserRallyState; readonly claimTicketNumber?: string }\n | ErrorResponse;\nfunction json(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"content-type\": \"application/json; charset=utf-8\" },\n });\n}\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\nfunction requestId(prefix: string): string {\n return `${prefix}-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;\n}\nfunction now(options: ServerOptions, requested?: string): string {\n return requested ?? options.now?.() ?? new Date().toISOString();\n}\nfunction initialState(config: AdminRallyConfig, userId: string, timestamp: string): UserRallyState {\n return {\n rallyId: config.id,\n userId,\n records: [],\n rewards: reconcileRewardStates(config.rewards, [], 0, timestamp),\n updatedAt: timestamp,\n };\n}\nfunction getProof(context: VerificationContext): unknown {\n return context.type === \"qr\"\n ? context.token\n : context.type === \"passcode\"\n ? context.code\n : context.type === \"gps\"\n ? { latitude: context.latitude, longitude: context.longitude }\n : context.type === \"nfc\"\n ? context.tagId\n : context.value;\n}\nasync function evaluate(\n condition: CheckInCondition,\n context: VerificationContext,\n validator: Validator | undefined,\n base: { rallyId: string; spotId: string; state: UserRallyState },\n): Promise<boolean> {\n if (condition.type !== \"custom\") return evaluateConditionDetailed(condition, context).ok;\n if (validator === undefined) return false;\n const validationContext = {\n rallyId: base.rallyId,\n spotId: base.spotId,\n proofData: getProof(context),\n condition,\n userState: base.state,\n };\n const result =\n typeof validator === \"function\"\n ? await validator(validationContext)\n : await validator.validate(validationContext);\n return result === true || (typeof result === \"object\" && result.valid);\n}\nfunction audit(\n rallyId: string,\n userId: string,\n action: AuditLog[\"action\"],\n resourceId: string,\n key: string,\n status: AuditLog[\"status\"],\n timestamp: string,\n code?: string,\n): AuditLog {\n return {\n id: requestId(\"audit\"),\n timestamp,\n rallyId,\n userId,\n action,\n resourceId,\n status,\n idempotencyKey: key,\n ...(code === undefined ? {} : { metadata: { errorCode: code } }),\n };\n}\n\nexport class StampRallyServer {\n readonly #config: AdminRallyConfig;\n readonly #persistence: ServerPersistenceAdapter;\n readonly #options: ServerOptions;\n constructor(\n config: AdminRallyConfig,\n persistence: ServerPersistenceAdapter,\n options: ServerOptions = {},\n ) {\n this.#config = config;\n this.#persistence = persistence;\n this.#options = options;\n }\n async handle(request: Request): Promise<Response> {\n if (request.method !== \"POST\")\n return json({ ok: false, code: \"METHOD_NOT_ALLOWED\", message: \"POST is required.\" }, 405);\n const path = new URL(request.url).pathname;\n if (path.endsWith(\"/check-in\")) return this.handleCheckIn(request);\n if (path.endsWith(\"/claim-reward\")) return this.handleClaimReward(request);\n if (path.endsWith(\"/sync\")) return this.handleSync(request);\n return json({ ok: false, code: \"NOT_FOUND\", message: \"Route not found.\" }, 404);\n }\n async handleCheckIn(request: Request): Promise<Response> {\n const body = await this.#body<CheckInRequest>(request);\n const userId = await this.#user(request, body?.userId);\n if (\n body === null ||\n userId === null ||\n body.rallyId !== this.#config.id ||\n body.spotId === \"\" ||\n body.idempotencyKey === \"\" ||\n body.context === undefined\n )\n return json(\n {\n ok: false,\n code: \"INVALID_REQUEST\",\n message: \"rallyId, spotId, context, and idempotencyKey are required.\",\n },\n 400,\n );\n const result = await this.checkIn({ ...body, userId });\n return json(result, result.ok ? 200 : result.code === \"SPOT_NOT_FOUND\" ? 404 : 422);\n }\n async handleClaimReward(request: Request): Promise<Response> {\n const body = await this.#body<ClaimRewardRequest>(request);\n const userId = await this.#user(request, body?.userId);\n if (\n body === null ||\n userId === null ||\n body.rallyId !== this.#config.id ||\n body.rewardId === \"\" ||\n body.idempotencyKey === \"\"\n )\n return json(\n {\n ok: false,\n code: \"INVALID_REQUEST\",\n message: \"rallyId, rewardId, and idempotencyKey are required.\",\n },\n 400,\n );\n const result = await this.claimReward({ ...body, userId });\n return json(result, result.ok ? 200 : result.code === \"REWARD_NOT_FOUND\" ? 404 : 422);\n }\n async handleSync(request: Request): Promise<Response> {\n const body = await this.#body<{ readonly rallyId: string; readonly userId?: string }>(request);\n const userId = await this.#user(request, body?.userId);\n if (body === null || userId === null || body.rallyId !== this.#config.id)\n return json({ ok: false, code: \"INVALID_REQUEST\", message: \"rallyId is required.\" }, 400);\n return json({ ok: true, state: await this.sync(body.rallyId, userId) });\n }\n async checkIn(request: CheckInRequest & { readonly userId: string }): Promise<CheckInResponse> {\n const key = `check-in:${request.rallyId}:${request.userId}:${request.idempotencyKey}`;\n const previous = await this.#persistence.getIdempotentResult<CheckInResponse>(\n request.rallyId,\n key,\n );\n if (previous !== null) return previous;\n const lockKey = `state:${request.rallyId}:${request.userId}`;\n if (\n !(await this.#persistence.acquireLock(\n request.rallyId,\n lockKey,\n this.#options.lockTtlMs ?? 5_000,\n ))\n )\n return { ok: false, code: \"CONFLICT\", message: \"The user state is being updated.\" };\n const timestamp = now(this.#options, request.now);\n try {\n const spot = this.#config.spots.find((item) => item.id === request.spotId);\n if (spot === undefined)\n return this.#remember(\n key,\n { ok: false, code: \"SPOT_NOT_FOUND\", message: \"Spot was not found.\" },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n const current =\n (await this.#persistence.getUserState(request.rallyId, request.userId)) ??\n initialState(this.#config, request.userId, timestamp);\n if (current.records.some((record) => record.stampId === request.spotId))\n return this.#remember(\n key,\n { ok: false, code: \"STAMP_ALREADY_ACQUIRED\", message: \"Spot was already claimed.\" },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n const acquired = new Set(current.records.map((record) => record.stampId));\n if (spot.prerequisites?.some((id) => !acquired.has(id)))\n return this.#remember(\n key,\n {\n ok: false,\n code: \"PREREQUISITES_NOT_MET\",\n message: \"Prerequisite spots are not complete.\",\n },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n for (const condition of spot.conditions)\n if (\n !(await evaluate(\n condition,\n request.context,\n condition.type === \"custom\"\n ? this.#options.customValidators?.[condition.validatorName]\n : undefined,\n { rallyId: request.rallyId, spotId: request.spotId, state: current },\n ))\n )\n return this.#remember(\n key,\n { ok: false, code: \"INVALID_PROOF\", message: \"Verification failed.\" },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n const next: UserRallyState = {\n ...current,\n records: [...current.records, { stampId: request.spotId, acquiredAt: timestamp }],\n rewards: reconcileRewardStates(\n this.#config.rewards,\n current.rewards,\n current.records.length + 1,\n timestamp,\n ),\n updatedAt: timestamp,\n };\n await this.#persistence.saveUserState(request.rallyId, request.userId, next);\n return this.#remember(\n key,\n { ok: true, state: next },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n } finally {\n await this.#persistence.releaseLock(request.rallyId, lockKey);\n }\n }\n async claimReward(\n request: ClaimRewardRequest & { readonly userId: string },\n ): Promise<ClaimResponse> {\n const key = `claim:${request.rallyId}:${request.userId}:${request.rewardId}:${request.idempotencyKey}`;\n const previous = await this.#persistence.getIdempotentResult<ClaimResponse>(\n request.rallyId,\n key,\n );\n if (previous !== null) return previous;\n const reward = this.#config.rewards.find((item) => item.id === request.rewardId);\n if (reward === undefined)\n return this.#rememberClaim(\n key,\n { ok: false, code: \"REWARD_NOT_FOUND\", message: \"Reward was not found.\" },\n request,\n now(this.#options, request.now),\n );\n const lockKey = `reward:${request.rallyId}:${reward.id}`;\n if (\n !(await this.#persistence.acquireLock(\n request.rallyId,\n lockKey,\n this.#options.lockTtlMs ?? 5_000,\n ))\n )\n return { ok: false, code: \"CONFLICT\", message: \"The reward is being claimed.\" };\n const timestamp = now(this.#options, request.now);\n let decremented = false;\n try {\n const checked = await this.#persistence.getIdempotentResult<ClaimResponse>(\n request.rallyId,\n key,\n );\n if (checked !== null) return checked;\n const current =\n (await this.#persistence.getUserState(request.rallyId, request.userId)) ??\n initialState(this.#config, request.userId, timestamp);\n const claimCount = await this.#persistence.getUserClaimCount(\n request.rallyId,\n request.userId,\n reward.id,\n );\n const storedReward = current.rewards.find((item) => item.rewardId === reward.id) ?? {\n rewardId: reward.id,\n status: \"LOCKED\" as const,\n };\n const currentReward =\n reward.redemptionMethod === \"server_claim\" &&\n storedReward.status === \"CONSUMED\" &&\n (reward.userClaimLimit === undefined || claimCount < reward.userClaimLimit)\n ? { ...storedReward, status: \"AVAILABLE\" as const }\n : storedReward;\n const local = consumeReward({\n reward,\n currentState: currentReward,\n now: timestamp,\n userRedemptionCount: claimCount,\n ...(request.staffPasscode === undefined ? {} : { inputPasscode: request.staffPasscode }),\n ...(request.staffId === undefined ? {} : { staffId: request.staffId }),\n });\n if (!local.ok)\n return this.#rememberClaim(\n key,\n { ok: false, code: local.error.code, message: \"Reward cannot be claimed.\" },\n request,\n timestamp,\n );\n const stock = await this.#persistence.decrementRewardStock(request.rallyId, reward.id);\n if (!stock.success)\n return this.#rememberClaim(\n key,\n { ok: false, code: \"OUT_OF_STOCK\", message: \"Reward is out of stock.\" },\n request,\n timestamp,\n );\n decremented = true;\n const next: UserRallyState = {\n ...current,\n rewards: current.rewards.map((item) => (item.rewardId === reward.id ? local.value : item)),\n updatedAt: timestamp,\n };\n try {\n await this.#persistence.saveUserState(request.rallyId, request.userId, next);\n const response: ClaimResponse =\n local.value.claimTicketNumber === undefined\n ? { ok: true, state: next }\n : { ok: true, state: next, claimTicketNumber: local.value.claimTicketNumber };\n await this.#persistence.recordUserClaim({\n rallyId: request.rallyId,\n userId: request.userId,\n rewardId: reward.id,\n ticketNumber: local.value.claimTicketNumber ?? \"\",\n timestamp: Number.isNaN(Date.parse(timestamp)) ? Date.now() : Date.parse(timestamp),\n });\n await this.#persistence.recordAuditLog(\n audit(\n request.rallyId,\n request.userId,\n \"CLAIM_REWARD\",\n reward.id,\n request.idempotencyKey,\n \"SUCCESS\",\n timestamp,\n ),\n );\n await this.#persistence.saveIdempotentResult(\n request.rallyId,\n key,\n response,\n this.#options.idempotencyTtlMs ?? 86_400_000,\n );\n return response;\n } catch (error) {\n await this.#restoreRewardStock(request.rallyId, reward.id);\n decremented = false;\n throw error;\n }\n } catch (error) {\n if (decremented) await this.#restoreRewardStock(request.rallyId, reward.id);\n return {\n ok: false,\n code: \"PERSISTENCE_FAILED\",\n message: error instanceof Error ? error.message : \"Reward claim failed.\",\n };\n } finally {\n await this.#persistence.releaseLock(request.rallyId, lockKey);\n }\n }\n async sync(rallyId: string, userId: string): Promise<UserRallyState> {\n return (\n (await this.#persistence.getUserState(rallyId, userId)) ??\n initialState(this.#config, userId, now(this.#options))\n );\n }\n async #body<T>(request: Request): Promise<T | null> {\n try {\n const value: unknown = await request.json();\n return isObject(value) ? (value as T) : null;\n } catch {\n return null;\n }\n }\n async #user(request: Request, requested: string | undefined): Promise<string | null> {\n if (this.#options.authenticate !== undefined)\n return (await this.#options.authenticate(request)) ?? null;\n return requested ?? null;\n }\n async #remember(\n key: string,\n result: CheckInResponse,\n rallyId: string,\n userId: string,\n action: AuditLog[\"action\"],\n resourceId: string,\n idempotencyKey: string,\n timestamp: string,\n ): Promise<CheckInResponse> {\n await this.#persistence.recordAuditLog(\n audit(\n rallyId,\n userId,\n action,\n resourceId,\n idempotencyKey,\n result.ok ? \"SUCCESS\" : \"REJECTED\",\n timestamp,\n result.ok ? undefined : result.code,\n ),\n );\n await this.#persistence.saveIdempotentResult(\n rallyId,\n key,\n result,\n this.#options.idempotencyTtlMs ?? 86_400_000,\n );\n return result;\n }\n async #rememberClaim(\n key: string,\n result: ClaimResponse,\n request: ClaimRewardRequest & { readonly userId: string },\n timestamp: string,\n ): Promise<ClaimResponse> {\n await this.#persistence.recordAuditLog(\n audit(\n request.rallyId,\n request.userId,\n \"CLAIM_REWARD\",\n request.rewardId,\n request.idempotencyKey,\n \"REJECTED\",\n timestamp,\n result.ok ? undefined : result.code,\n ),\n );\n await this.#persistence.saveIdempotentResult(\n request.rallyId,\n key,\n result,\n this.#options.idempotencyTtlMs ?? 86_400_000,\n );\n return result;\n }\n async #restoreRewardStock(rallyId: string, rewardId: string): Promise<void> {\n if (this.#persistence.restoreRewardStock !== undefined)\n await this.#persistence.restoreRewardStock(rallyId, rewardId);\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/persistence.ts","../src/server.ts"],"names":["now"],"mappings":"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","file":"index.js","sourcesContent":["import type { UserRallyState } from \"@stamprally/core\";\nimport type { AuditLog } from \"./index.js\";\n\nexport interface UserClaimRecord {\n readonly rallyId: string;\n readonly userId: string;\n readonly rewardId: string;\n readonly ticketNumber: string;\n readonly timestamp: number;\n}\n\nexport interface ServerPersistenceAdapter {\n /** Runs the callback in a storage-native transaction when supported. */\n runTransaction?<T>(\n rallyId: string,\n operation: (transaction: ServerPersistenceAdapter) => Promise<T>,\n ): Promise<T>;\n acquireLock(rallyId: string, lockKey: string, ttlMs: number): Promise<boolean>;\n releaseLock(rallyId: string, lockKey: string): Promise<void>;\n getRewardStock(rallyId: string, rewardId: string): Promise<number | null>;\n decrementRewardStock(\n rallyId: string,\n rewardId: string,\n ): Promise<{ readonly success: boolean; readonly remainingStock: number }>;\n restoreRewardStock?(rallyId: string, rewardId: string, count?: number): Promise<void>;\n rollbackUserState?(\n rallyId: string,\n userId: string,\n previousState: UserRallyState | null,\n ): Promise<void>;\n rollbackUserClaim?(\n rallyId: string,\n userId: string,\n rewardId: string,\n ticketNumber: string,\n ): Promise<void>;\n getIdempotentResult<T>(rallyId: string, key: string): Promise<T | null>;\n saveIdempotentResult<T>(rallyId: string, key: string, result: T, ttlMs: number): Promise<void>;\n getUserClaimCount(rallyId: string, userId: string, rewardId: string): Promise<number>;\n recordUserClaim(params: UserClaimRecord): Promise<void>;\n recordClaim?(params: UserClaimRecord): Promise<void>;\n incrementUserClaimCount?(rallyId: string, userId: string, rewardId: string): Promise<void>;\n getUserState(rallyId: string, userId: string): Promise<UserRallyState | null>;\n saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void>;\n recordAuditLog(log: AuditLog): Promise<void>;\n removeAuditLog?(auditId: string): Promise<void>;\n}\ninterface TimedValue<T> {\n readonly value: T;\n readonly expiresAt: number;\n}\nexport interface InMemoryServerPersistenceOptions {\n readonly stocks?: Readonly<Record<string, number>>;\n}\nexport class InMemoryServerPersistenceAdapter implements ServerPersistenceAdapter {\n readonly #locks = new Map<string, number>();\n readonly #idempotent = new Map<string, TimedValue<unknown>>();\n readonly #states = new Map<string, UserRallyState>();\n readonly #stocks: Map<string, number>;\n readonly #stockDefaults: Map<string, number>;\n readonly #claims = new Map<string, number>();\n readonly #claimRecords: UserClaimRecord[] = [];\n readonly #auditLogs: AuditLog[] = [];\n constructor(options: InMemoryServerPersistenceOptions = {}) {\n this.#stocks = new Map();\n this.#stockDefaults = new Map();\n for (const [key, stock] of Object.entries(options.stocks ?? {})) {\n if (key.includes(\":\")) this.#stocks.set(key, stock);\n else this.#stockDefaults.set(key, stock);\n }\n }\n #key(rallyId: string, key: string): string {\n return `${rallyId}:${key}`;\n }\n #stockKey(rallyId: string, rewardId: string): string {\n const scopedKey = `${rallyId}:${rewardId}`;\n if (this.#stocks.has(scopedKey)) return scopedKey;\n const defaultStock = this.#stockDefaults.get(rewardId);\n if (defaultStock !== undefined) {\n this.#stocks.set(scopedKey, defaultStock);\n return scopedKey;\n }\n return scopedKey;\n }\n async acquireLock(rallyId: string, key: string, ttlMs: number): Promise<boolean> {\n const scopedKey = this.#key(rallyId, key);\n const now = Date.now();\n const until = this.#locks.get(scopedKey);\n if (until !== undefined && until > now) return false;\n this.#locks.set(scopedKey, now + Math.max(1, ttlMs));\n return true;\n }\n async releaseLock(rallyId: string, key: string): Promise<void> {\n this.#locks.delete(this.#key(rallyId, key));\n }\n async getRewardStock(rallyId: string, rewardId: string): Promise<number | null> {\n return this.#stocks.get(this.#stockKey(rallyId, rewardId)) ?? null;\n }\n async decrementRewardStock(\n rallyId: string,\n rewardId: string,\n ): Promise<{ success: boolean; remainingStock: number }> {\n const key = this.#stockKey(rallyId, rewardId);\n const current = this.#stocks.get(key);\n if (current === undefined) return { success: true, remainingStock: Number.POSITIVE_INFINITY };\n if (current <= 0) return { success: false, remainingStock: 0 };\n this.#stocks.set(key, current - 1);\n return { success: true, remainingStock: current - 1 };\n }\n async restoreRewardStock(rallyId: string, rewardId: string, count = 1): Promise<void> {\n const key = this.#stockKey(rallyId, rewardId);\n const current = this.#stocks.get(key);\n if (current !== undefined) this.#stocks.set(key, current + Math.max(0, count));\n }\n\n async rollbackUserState(\n rallyId: string,\n userId: string,\n previousState: UserRallyState | null,\n ): Promise<void> {\n const key = `${rallyId}:${userId}`;\n if (previousState === null) this.#states.delete(key);\n else this.#states.set(key, structuredClone(previousState));\n }\n async getIdempotentResult<T>(rallyId: string, key: string): Promise<T | null> {\n const value = this.#idempotent.get(this.#key(rallyId, key));\n if (value === undefined || value.expiresAt <= Date.now()) {\n this.#idempotent.delete(this.#key(rallyId, key));\n return null;\n }\n return structuredClone(value.value) as T;\n }\n async saveIdempotentResult<T>(\n rallyId: string,\n key: string,\n result: T,\n ttlMs: number,\n ): Promise<void> {\n this.#idempotent.set(this.#key(rallyId, key), {\n value: structuredClone(result),\n expiresAt: Date.now() + Math.max(1, ttlMs),\n });\n }\n async getUserClaimCount(rallyId: string, userId: string, rewardId: string): Promise<number> {\n return this.#claims.get(`${rallyId}:${userId}:${rewardId}`) ?? 0;\n }\n async getUserState(rallyId: string, userId: string): Promise<UserRallyState | null> {\n const value = this.#states.get(`${rallyId}:${userId}`);\n return value === undefined ? null : structuredClone(value);\n }\n async saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void> {\n this.#states.set(`${rallyId}:${userId}`, structuredClone(state));\n }\n async recordAuditLog(log: AuditLog): Promise<void> {\n this.#auditLogs.push(structuredClone(log));\n }\n async removeAuditLog(auditId: string): Promise<void> {\n const index = this.#auditLogs.findIndex((log) => log.id === auditId);\n if (index >= 0) this.#auditLogs.splice(index, 1);\n }\n getAuditLogs(): ReadonlyArray<AuditLog> {\n return structuredClone(this.#auditLogs);\n }\n async recordUserClaim(params: UserClaimRecord): Promise<void> {\n const { rallyId, userId, rewardId } = params;\n const key = `${rallyId}:${userId}:${rewardId}`;\n this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);\n this.#claimRecords.push(structuredClone(params));\n }\n async rollbackUserClaim(\n rallyId: string,\n userId: string,\n rewardId: string,\n ticketNumber: string,\n ): Promise<void> {\n const key = `${rallyId}:${userId}:${rewardId}`;\n const count = this.#claims.get(key) ?? 0;\n if (count <= 1) this.#claims.delete(key);\n else this.#claims.set(key, count - 1);\n const index = this.#claimRecords.findIndex(\n (record) =>\n record.rallyId === rallyId &&\n record.userId === userId &&\n record.rewardId === rewardId &&\n record.ticketNumber === ticketNumber,\n );\n if (index >= 0) this.#claimRecords.splice(index, 1);\n }\n async incrementUserClaimCount(rallyId: string, userId: string, rewardId: string): Promise<void> {\n const key = `${rallyId}:${userId}:${rewardId}`;\n this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);\n }\n getClaimRecords(): ReadonlyArray<UserClaimRecord> {\n return structuredClone(this.#claimRecords);\n }\n /** @deprecated Use recordUserClaim with a ticket number and timestamp. */\n recordClaim(params: UserClaimRecord): Promise<void>;\n recordClaim(rallyId: string, userId: string, rewardId: string): Promise<void>;\n recordClaim(\n paramsOrRallyId: UserClaimRecord | string,\n userId?: string,\n rewardId?: string,\n ): Promise<void> {\n const params: UserClaimRecord =\n typeof paramsOrRallyId === \"string\"\n ? {\n rallyId: paramsOrRallyId,\n userId: userId ?? \"\",\n rewardId: rewardId ?? \"\",\n ticketNumber: \"\",\n timestamp: Date.now(),\n }\n : paramsOrRallyId;\n return this.recordUserClaim(params);\n }\n\n async runTransaction<T>(\n _rallyId: string,\n operation: (transaction: ServerPersistenceAdapter) => Promise<T>,\n ): Promise<T> {\n const snapshot = {\n stocks: new Map(this.#stocks),\n idempotent: new Map(this.#idempotent),\n states: new Map(this.#states),\n claims: new Map(this.#claims),\n claimRecords: structuredClone(this.#claimRecords),\n auditLogs: structuredClone(this.#auditLogs),\n };\n try {\n return await operation(this);\n } catch (error) {\n this.#stocks.clear();\n for (const [key, value] of snapshot.stocks) this.#stocks.set(key, value);\n this.#idempotent.clear();\n for (const [key, value] of snapshot.idempotent)\n this.#idempotent.set(key, structuredClone(value));\n this.#states.clear();\n for (const [key, value] of snapshot.states) this.#states.set(key, structuredClone(value));\n this.#claims.clear();\n for (const [key, value] of snapshot.claims) this.#claims.set(key, value);\n this.#claimRecords.splice(0, this.#claimRecords.length, ...snapshot.claimRecords);\n this.#auditLogs.splice(0, this.#auditLogs.length, ...snapshot.auditLogs);\n throw error;\n }\n }\n}\n","import {\n type AdminRallyConfig,\n type CheckInCondition,\n consumeReward,\n evaluateConditionDetailed,\n reconcileRewardStates,\n type UserRallyState,\n type Validator,\n type VerificationContext,\n} from \"@stamprally/core\";\nimport type {\n AuditLog,\n CheckInRequest,\n CheckInResponse,\n ClaimRewardRequest,\n ServerOptions,\n} from \"./index.js\";\nimport type { ServerPersistenceAdapter } from \"./persistence.js\";\n\ntype ErrorResponse = { readonly ok: false; readonly code: string; readonly message: string };\ntype ClaimResponse =\n | { readonly ok: true; readonly state: UserRallyState; readonly claimTicketNumber?: string }\n | ErrorResponse;\nfunction json(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"content-type\": \"application/json; charset=utf-8\" },\n });\n}\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\nfunction requestId(prefix: string): string {\n return `${prefix}-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;\n}\nfunction now(options: ServerOptions, requested?: string): string {\n return requested ?? options.now?.() ?? new Date().toISOString();\n}\nfunction initialState(config: AdminRallyConfig, userId: string, timestamp: string): UserRallyState {\n return {\n rallyId: config.id,\n userId,\n records: [],\n rewards: reconcileRewardStates(config.rewards, [], 0, timestamp),\n updatedAt: timestamp,\n };\n}\nfunction getProof(context: VerificationContext): unknown {\n return context.type === \"qr\"\n ? context.token\n : context.type === \"passcode\"\n ? context.code\n : context.type === \"gps\"\n ? { latitude: context.latitude, longitude: context.longitude }\n : context.type === \"nfc\"\n ? context.tagId\n : context.value;\n}\nasync function evaluate(\n condition: CheckInCondition,\n context: VerificationContext,\n validator: Validator | undefined,\n base: { rallyId: string; spotId: string; state: UserRallyState },\n): Promise<boolean> {\n if (condition.type !== \"custom\") return evaluateConditionDetailed(condition, context).ok;\n if (validator === undefined) return false;\n const validationContext = {\n rallyId: base.rallyId,\n spotId: base.spotId,\n proofData: getProof(context),\n condition,\n userState: base.state,\n };\n const result =\n typeof validator === \"function\"\n ? await validator(validationContext)\n : await validator.validate(validationContext);\n return result === true || (typeof result === \"object\" && result.valid);\n}\nfunction audit(\n rallyId: string,\n userId: string,\n action: AuditLog[\"action\"],\n resourceId: string,\n key: string,\n status: AuditLog[\"status\"],\n timestamp: string,\n code?: string,\n): AuditLog {\n return {\n id: requestId(\"audit\"),\n timestamp,\n rallyId,\n userId,\n action,\n resourceId,\n status,\n idempotencyKey: key,\n ...(code === undefined ? {} : { metadata: { errorCode: code } }),\n };\n}\n\nexport class StampRallyServer {\n readonly #config: AdminRallyConfig;\n readonly #persistence: ServerPersistenceAdapter;\n readonly #options: ServerOptions;\n constructor(\n config: AdminRallyConfig,\n persistence: ServerPersistenceAdapter,\n options: ServerOptions = {},\n ) {\n this.#config = config;\n this.#persistence = persistence;\n this.#options = options;\n }\n async handle(request: Request): Promise<Response> {\n if (request.method !== \"POST\")\n return json({ ok: false, code: \"METHOD_NOT_ALLOWED\", message: \"POST is required.\" }, 405);\n const path = new URL(request.url).pathname;\n if (path.endsWith(\"/check-in\")) return this.handleCheckIn(request);\n if (path.endsWith(\"/claim-reward\")) return this.handleClaimReward(request);\n if (path.endsWith(\"/sync\")) return this.handleSync(request);\n return json({ ok: false, code: \"NOT_FOUND\", message: \"Route not found.\" }, 404);\n }\n async handleCheckIn(request: Request): Promise<Response> {\n const body = await this.#body<CheckInRequest>(request);\n const userId = await this.#user(request, body?.userId);\n if (\n body === null ||\n userId === null ||\n body.rallyId !== this.#config.id ||\n body.spotId === \"\" ||\n body.idempotencyKey === \"\" ||\n body.context === undefined\n )\n return json(\n {\n ok: false,\n code: \"INVALID_REQUEST\",\n message: \"rallyId, spotId, context, and idempotencyKey are required.\",\n },\n 400,\n );\n const result = await this.checkIn({ ...body, userId });\n return json(result, result.ok ? 200 : result.code === \"SPOT_NOT_FOUND\" ? 404 : 422);\n }\n async handleClaimReward(request: Request): Promise<Response> {\n const body = await this.#body<ClaimRewardRequest>(request);\n const userId = await this.#user(request, body?.userId);\n if (\n body === null ||\n userId === null ||\n body.rallyId !== this.#config.id ||\n body.rewardId === \"\" ||\n body.idempotencyKey === \"\"\n )\n return json(\n {\n ok: false,\n code: \"INVALID_REQUEST\",\n message: \"rallyId, rewardId, and idempotencyKey are required.\",\n },\n 400,\n );\n const result = await this.claimReward({ ...body, userId });\n return json(result, result.ok ? 200 : result.code === \"REWARD_NOT_FOUND\" ? 404 : 422);\n }\n async handleSync(request: Request): Promise<Response> {\n const body = await this.#body<{ readonly rallyId: string; readonly userId?: string }>(request);\n const userId = await this.#user(request, body?.userId);\n if (body === null || userId === null || body.rallyId !== this.#config.id)\n return json({ ok: false, code: \"INVALID_REQUEST\", message: \"rallyId is required.\" }, 400);\n return json({ ok: true, state: await this.sync(body.rallyId, userId) });\n }\n async checkIn(request: CheckInRequest & { readonly userId: string }): Promise<CheckInResponse> {\n const key = `check-in:${request.rallyId}:${request.userId}:${request.idempotencyKey}`;\n const previous = await this.#persistence.getIdempotentResult<CheckInResponse>(\n request.rallyId,\n key,\n );\n if (previous !== null) return previous;\n const lockKey = `state:${request.rallyId}:${request.userId}`;\n if (\n !(await this.#persistence.acquireLock(\n request.rallyId,\n lockKey,\n this.#options.lockTtlMs ?? 5_000,\n ))\n )\n return { ok: false, code: \"CONFLICT\", message: \"The user state is being updated.\" };\n const timestamp = now(this.#options, request.now);\n try {\n const spot = this.#config.spots.find((item) => item.id === request.spotId);\n if (spot === undefined)\n return this.#remember(\n key,\n { ok: false, code: \"SPOT_NOT_FOUND\", message: \"Spot was not found.\" },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n const current =\n (await this.#persistence.getUserState(request.rallyId, request.userId)) ??\n initialState(this.#config, request.userId, timestamp);\n if (current.records.some((record) => record.stampId === request.spotId))\n return this.#remember(\n key,\n { ok: false, code: \"STAMP_ALREADY_ACQUIRED\", message: \"Spot was already claimed.\" },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n const acquired = new Set(current.records.map((record) => record.stampId));\n if (spot.prerequisites?.some((id) => !acquired.has(id)))\n return this.#remember(\n key,\n {\n ok: false,\n code: \"PREREQUISITES_NOT_MET\",\n message: \"Prerequisite spots are not complete.\",\n },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n for (const condition of spot.conditions)\n if (\n !(await evaluate(\n condition,\n request.context,\n condition.type === \"custom\"\n ? this.#options.customValidators?.[condition.validatorName]\n : undefined,\n { rallyId: request.rallyId, spotId: request.spotId, state: current },\n ))\n )\n return this.#remember(\n key,\n { ok: false, code: \"INVALID_PROOF\", message: \"Verification failed.\" },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n const next: UserRallyState = {\n ...current,\n records: [...current.records, { stampId: request.spotId, acquiredAt: timestamp }],\n rewards: reconcileRewardStates(\n this.#config.rewards,\n current.rewards,\n current.records.length + 1,\n timestamp,\n ),\n updatedAt: timestamp,\n };\n await this.#persistence.saveUserState(request.rallyId, request.userId, next);\n return this.#remember(\n key,\n { ok: true, state: next },\n request.rallyId,\n request.userId,\n \"CHECK_IN\",\n request.spotId,\n request.idempotencyKey,\n timestamp,\n );\n } finally {\n await this.#persistence.releaseLock(request.rallyId, lockKey);\n }\n }\n async claimReward(\n request: ClaimRewardRequest & { readonly userId: string },\n ): Promise<ClaimResponse> {\n if (this.#persistence.runTransaction !== undefined)\n return this.#persistence.runTransaction(request.rallyId, (transaction) =>\n this.#claimRewardMutation(request, transaction),\n );\n return this.#claimRewardMutation(request, this.#persistence);\n }\n async #claimRewardMutation(\n request: ClaimRewardRequest & { readonly userId: string },\n persistence: ServerPersistenceAdapter,\n ): Promise<ClaimResponse> {\n const key = `claim:${request.rallyId}:${request.userId}:${request.rewardId}:${request.idempotencyKey}`;\n const previous = await persistence.getIdempotentResult<ClaimResponse>(request.rallyId, key);\n if (previous !== null) return previous;\n const reward = this.#config.rewards.find((item) => item.id === request.rewardId);\n if (reward === undefined)\n return this.#rememberClaim(\n key,\n { ok: false, code: \"REWARD_NOT_FOUND\", message: \"Reward was not found.\" },\n request,\n now(this.#options, request.now),\n persistence,\n );\n const lockKey = `reward:${request.rallyId}:${reward.id}`;\n if (\n !(await persistence.acquireLock(request.rallyId, lockKey, this.#options.lockTtlMs ?? 5_000))\n )\n return { ok: false, code: \"CONFLICT\", message: \"The reward is being claimed.\" };\n const timestamp = now(this.#options, request.now);\n let decremented = false;\n let previousState: UserRallyState | null = null;\n let recordedTicket: string | null = null;\n let successAuditId: string | null = null;\n try {\n const checked = await persistence.getIdempotentResult<ClaimResponse>(request.rallyId, key);\n if (checked !== null) return checked;\n const storedState = await persistence.getUserState(request.rallyId, request.userId);\n previousState = storedState;\n const current = storedState ?? initialState(this.#config, request.userId, timestamp);\n const claimCount = await persistence.getUserClaimCount(\n request.rallyId,\n request.userId,\n reward.id,\n );\n const storedReward = current.rewards.find((item) => item.rewardId === reward.id) ?? {\n rewardId: reward.id,\n status: \"LOCKED\" as const,\n };\n const currentReward =\n reward.redemptionMethod === \"server_claim\" &&\n storedReward.status === \"CONSUMED\" &&\n (reward.userClaimLimit === undefined || claimCount < reward.userClaimLimit)\n ? { ...storedReward, status: \"AVAILABLE\" as const }\n : storedReward;\n const local = consumeReward({\n reward,\n currentState: currentReward,\n now: timestamp,\n userRedemptionCount: claimCount,\n ...(request.staffPasscode === undefined ? {} : { inputPasscode: request.staffPasscode }),\n ...(request.staffId === undefined ? {} : { staffId: request.staffId }),\n });\n if (!local.ok)\n return this.#rememberClaim(\n key,\n { ok: false, code: local.error.code, message: \"Reward cannot be claimed.\" },\n request,\n timestamp,\n persistence,\n );\n const stock = await persistence.decrementRewardStock(request.rallyId, reward.id);\n if (!stock.success)\n return this.#rememberClaim(\n key,\n { ok: false, code: \"OUT_OF_STOCK\", message: \"Reward is out of stock.\" },\n request,\n timestamp,\n persistence,\n );\n decremented = true;\n const next: UserRallyState = {\n ...current,\n rewards: current.rewards.map((item) => (item.rewardId === reward.id ? local.value : item)),\n updatedAt: timestamp,\n };\n try {\n await persistence.saveUserState(request.rallyId, request.userId, next);\n const response: ClaimResponse =\n local.value.claimTicketNumber === undefined\n ? { ok: true, state: next }\n : { ok: true, state: next, claimTicketNumber: local.value.claimTicketNumber };\n recordedTicket = local.value.claimTicketNumber ?? \"\";\n await persistence.recordUserClaim({\n rallyId: request.rallyId,\n userId: request.userId,\n rewardId: reward.id,\n ticketNumber: recordedTicket,\n timestamp: Number.isNaN(Date.parse(timestamp)) ? Date.now() : Date.parse(timestamp),\n });\n const successAudit = audit(\n request.rallyId,\n request.userId,\n \"CLAIM_REWARD\",\n reward.id,\n request.idempotencyKey,\n \"SUCCESS\",\n timestamp,\n );\n successAuditId = successAudit.id;\n await persistence.recordAuditLog(successAudit);\n await persistence.saveIdempotentResult(\n request.rallyId,\n key,\n response,\n this.#options.idempotencyTtlMs ?? 86_400_000,\n );\n return response;\n } catch (error) {\n if (recordedTicket !== null && persistence.rollbackUserClaim !== undefined)\n await persistence.rollbackUserClaim(\n request.rallyId,\n request.userId,\n reward.id,\n recordedTicket,\n );\n if (persistence.rollbackUserState !== undefined)\n await persistence.rollbackUserState(request.rallyId, request.userId, previousState);\n if (successAuditId !== null && persistence.removeAuditLog !== undefined)\n await persistence.removeAuditLog(successAuditId);\n await this.#restoreRewardStock(persistence, request.rallyId, reward.id);\n decremented = false;\n throw error;\n }\n } catch (error) {\n if (decremented) await this.#restoreRewardStock(persistence, request.rallyId, reward.id);\n return {\n ok: false,\n code: \"PERSISTENCE_FAILED\",\n message: error instanceof Error ? error.message : \"Reward claim failed.\",\n };\n } finally {\n await persistence.releaseLock(request.rallyId, lockKey);\n }\n }\n async sync(rallyId: string, userId: string): Promise<UserRallyState> {\n return (\n (await this.#persistence.getUserState(rallyId, userId)) ??\n initialState(this.#config, userId, now(this.#options))\n );\n }\n async #body<T>(request: Request): Promise<T | null> {\n try {\n const value: unknown = await request.json();\n return isObject(value) ? (value as T) : null;\n } catch {\n return null;\n }\n }\n async #user(request: Request, requested: string | undefined): Promise<string | null> {\n if (this.#options.authenticate !== undefined)\n return (await this.#options.authenticate(request)) ?? null;\n return requested ?? null;\n }\n async #remember(\n key: string,\n result: CheckInResponse,\n rallyId: string,\n userId: string,\n action: AuditLog[\"action\"],\n resourceId: string,\n idempotencyKey: string,\n timestamp: string,\n ): Promise<CheckInResponse> {\n await this.#persistence.recordAuditLog(\n audit(\n rallyId,\n userId,\n action,\n resourceId,\n idempotencyKey,\n result.ok ? \"SUCCESS\" : \"REJECTED\",\n timestamp,\n result.ok ? undefined : result.code,\n ),\n );\n await this.#persistence.saveIdempotentResult(\n rallyId,\n key,\n result,\n this.#options.idempotencyTtlMs ?? 86_400_000,\n );\n return result;\n }\n async #rememberClaim(\n key: string,\n result: ClaimResponse,\n request: ClaimRewardRequest & { readonly userId: string },\n timestamp: string,\n persistence: ServerPersistenceAdapter = this.#persistence,\n ): Promise<ClaimResponse> {\n await persistence.recordAuditLog(\n audit(\n request.rallyId,\n request.userId,\n \"CLAIM_REWARD\",\n request.rewardId,\n request.idempotencyKey,\n \"REJECTED\",\n timestamp,\n result.ok ? undefined : result.code,\n ),\n );\n await persistence.saveIdempotentResult(\n request.rallyId,\n key,\n result,\n this.#options.idempotencyTtlMs ?? 86_400_000,\n );\n return result;\n }\n async #restoreRewardStock(\n persistence: ServerPersistenceAdapter,\n rallyId: string,\n rewardId: string,\n ): Promise<void> {\n if (persistence.restoreRewardStock !== undefined)\n await persistence.restoreRewardStock(rallyId, rewardId);\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamprally/server",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Web Standard server handlers for stamp rally verification and claims.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,7 +28,7 @@
28
28
  }
29
29
  },
30
30
  "dependencies": {
31
- "@stamprally/core": "0.10.0"
31
+ "@stamprally/core": "0.11.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "tsup": "^8.5.1",