@stamprally/server 0.9.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/README.md CHANGED
@@ -1,28 +1,20 @@
1
1
  # @stamprally/server
2
2
 
3
- Web Standard `Request` / `Response` handlers for server-authoritative stamp rally
4
- check-ins, reward claims, and offline synchronization.
3
+ Web Standard `Request` / `Response` handlers for server-authoritative check-ins and reward claims.
5
4
 
6
5
  ```ts
7
6
  import { StampRallyServer } from "@stamprally/server";
8
7
 
9
- const server = new StampRallyServer(
10
- {
11
- id: "spring-rally",
12
- secretKey: process.env.RALLY_SECRET ?? "replace-me",
13
- stamps: [{ id: "gate", name: { en: "Gate" }, condition: { type: "instant" } }],
14
- },
15
- storage,
16
- );
17
-
8
+ const server = new StampRallyServer(adminConfig, persistence, {
9
+ authenticate: (request) => request.headers.get("x-user-id"),
10
+ });
18
11
  const response = await server.handle(request);
19
12
  ```
20
13
 
21
- Provide a Redis-backed `ServerStorageAdapter` in production. Its
22
- `decrementRewardStock` implementation should use a single atomic Redis
23
- operation (for example, a Lua script or `DECR` guarded by a non-negative check).
24
- The built-in `InMemoryServerStorage` is intended for tests and local demos.
14
+ `ServerPersistenceAdapter` scopes locks, reward stock, idempotency records, user state, claim records, and audit logs by `rallyId`. Its `recordUserClaim` operation receives the rally, user, reward, issued ticket number, and timestamp. Use a transactional database or equivalent Redis primitives for multi-instance production deployments.
15
+
16
+ Hono can mount the handler directly because it accepts the same Web Standard request and response types.
17
+
18
+ ## License
25
19
 
26
- If authentication is already handled by the host application, use
27
- `authenticate` to return the authenticated user ID. The handler rejects bodies
28
- that attempt to act for a different user.
20
+ MIT
package/dist/index.cjs CHANGED
@@ -8,42 +8,73 @@ var InMemoryServerPersistenceAdapter = class {
8
8
  #idempotent = /* @__PURE__ */ new Map();
9
9
  #states = /* @__PURE__ */ new Map();
10
10
  #stocks;
11
+ #stockDefaults;
11
12
  #claims = /* @__PURE__ */ new Map();
13
+ #claimRecords = [];
12
14
  #auditLogs = [];
13
15
  constructor(options = {}) {
14
- this.#stocks = new Map(Object.entries(options.stocks ?? {}));
16
+ this.#stocks = /* @__PURE__ */ new Map();
17
+ this.#stockDefaults = /* @__PURE__ */ new Map();
18
+ for (const [key, stock] of Object.entries(options.stocks ?? {})) {
19
+ if (key.includes(":")) this.#stocks.set(key, stock);
20
+ else this.#stockDefaults.set(key, stock);
21
+ }
22
+ }
23
+ #key(rallyId, key) {
24
+ return `${rallyId}:${key}`;
25
+ }
26
+ #stockKey(rallyId, rewardId) {
27
+ const scopedKey = `${rallyId}:${rewardId}`;
28
+ if (this.#stocks.has(scopedKey)) return scopedKey;
29
+ const defaultStock = this.#stockDefaults.get(rewardId);
30
+ if (defaultStock !== void 0) {
31
+ this.#stocks.set(scopedKey, defaultStock);
32
+ return scopedKey;
33
+ }
34
+ return scopedKey;
15
35
  }
16
- async acquireLock(key, ttlMs) {
36
+ async acquireLock(rallyId, key, ttlMs) {
37
+ const scopedKey = this.#key(rallyId, key);
17
38
  const now2 = Date.now();
18
- const until = this.#locks.get(key);
39
+ const until = this.#locks.get(scopedKey);
19
40
  if (until !== void 0 && until > now2) return false;
20
- this.#locks.set(key, now2 + Math.max(1, ttlMs));
41
+ this.#locks.set(scopedKey, now2 + Math.max(1, ttlMs));
21
42
  return true;
22
43
  }
23
- async releaseLock(key) {
24
- this.#locks.delete(key);
44
+ async releaseLock(rallyId, key) {
45
+ this.#locks.delete(this.#key(rallyId, key));
46
+ }
47
+ async getRewardStock(rallyId, rewardId) {
48
+ return this.#stocks.get(this.#stockKey(rallyId, rewardId)) ?? null;
25
49
  }
26
- async decrementRewardStock(rewardId) {
27
- const current = this.#stocks.get(rewardId);
50
+ async decrementRewardStock(rallyId, rewardId) {
51
+ const key = this.#stockKey(rallyId, rewardId);
52
+ const current = this.#stocks.get(key);
28
53
  if (current === void 0) return { success: true, remainingStock: Number.POSITIVE_INFINITY };
29
54
  if (current <= 0) return { success: false, remainingStock: 0 };
30
- this.#stocks.set(rewardId, current - 1);
55
+ this.#stocks.set(key, current - 1);
31
56
  return { success: true, remainingStock: current - 1 };
32
57
  }
33
- async incrementRewardStock(rewardId) {
34
- const current = this.#stocks.get(rewardId);
35
- if (current !== void 0) this.#stocks.set(rewardId, current + 1);
58
+ async restoreRewardStock(rallyId, rewardId, count = 1) {
59
+ const key = this.#stockKey(rallyId, rewardId);
60
+ const current = this.#stocks.get(key);
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));
36
67
  }
37
- async getIdempotentResult(key) {
38
- const value = this.#idempotent.get(key);
68
+ async getIdempotentResult(rallyId, key) {
69
+ const value = this.#idempotent.get(this.#key(rallyId, key));
39
70
  if (value === void 0 || value.expiresAt <= Date.now()) {
40
- this.#idempotent.delete(key);
71
+ this.#idempotent.delete(this.#key(rallyId, key));
41
72
  return null;
42
73
  }
43
74
  return structuredClone(value.value);
44
75
  }
45
- async saveIdempotentResult(key, result, ttlMs) {
46
- this.#idempotent.set(key, {
76
+ async saveIdempotentResult(rallyId, key, result, ttlMs) {
77
+ this.#idempotent.set(this.#key(rallyId, key), {
47
78
  value: structuredClone(result),
48
79
  expiresAt: Date.now() + Math.max(1, ttlMs)
49
80
  });
@@ -61,13 +92,72 @@ var InMemoryServerPersistenceAdapter = class {
61
92
  async recordAuditLog(log) {
62
93
  this.#auditLogs.push(structuredClone(log));
63
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
+ }
64
99
  getAuditLogs() {
65
100
  return structuredClone(this.#auditLogs);
66
101
  }
67
- recordClaim(rallyId, userId, rewardId) {
102
+ async recordUserClaim(params) {
103
+ const { rallyId, userId, rewardId } = params;
104
+ const key = `${rallyId}:${userId}:${rewardId}`;
105
+ this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);
106
+ this.#claimRecords.push(structuredClone(params));
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
+ }
118
+ async incrementUserClaimCount(rallyId, userId, rewardId) {
68
119
  const key = `${rallyId}:${userId}:${rewardId}`;
69
120
  this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);
70
121
  }
122
+ getClaimRecords() {
123
+ return structuredClone(this.#claimRecords);
124
+ }
125
+ recordClaim(paramsOrRallyId, userId, rewardId) {
126
+ const params = typeof paramsOrRallyId === "string" ? {
127
+ rallyId: paramsOrRallyId,
128
+ userId: userId ?? "",
129
+ rewardId: rewardId ?? "",
130
+ ticketNumber: "",
131
+ timestamp: Date.now()
132
+ } : paramsOrRallyId;
133
+ return this.recordUserClaim(params);
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
+ }
71
161
  };
72
162
  function json(body, status = 200) {
73
163
  return new Response(JSON.stringify(body), {
@@ -179,10 +269,17 @@ var StampRallyServer = class {
179
269
  }
180
270
  async checkIn(request) {
181
271
  const key = `check-in:${request.rallyId}:${request.userId}:${request.idempotencyKey}`;
182
- const previous = await this.#persistence.getIdempotentResult(key);
272
+ const previous = await this.#persistence.getIdempotentResult(
273
+ request.rallyId,
274
+ key
275
+ );
183
276
  if (previous !== null) return previous;
184
277
  const lockKey = `state:${request.rallyId}:${request.userId}`;
185
- if (!await this.#persistence.acquireLock(lockKey, this.#options.lockTtlMs ?? 5e3))
278
+ if (!await this.#persistence.acquireLock(
279
+ request.rallyId,
280
+ lockKey,
281
+ this.#options.lockTtlMs ?? 5e3
282
+ ))
186
283
  return { ok: false, code: "CONFLICT", message: "The user state is being updated." };
187
284
  const timestamp = now(this.#options, request.now);
188
285
  try {
@@ -266,12 +363,20 @@ var StampRallyServer = class {
266
363
  timestamp
267
364
  );
268
365
  } finally {
269
- await this.#persistence.releaseLock(lockKey);
366
+ await this.#persistence.releaseLock(request.rallyId, lockKey);
270
367
  }
271
368
  }
272
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) {
273
378
  const key = `claim:${request.rallyId}:${request.userId}:${request.rewardId}:${request.idempotencyKey}`;
274
- const previous = await this.#persistence.getIdempotentResult(key);
379
+ const previous = await persistence.getIdempotentResult(request.rallyId, key);
275
380
  if (previous !== null) return previous;
276
381
  const reward = this.#config.rewards.find((item) => item.id === request.rewardId);
277
382
  if (reward === void 0)
@@ -279,18 +384,24 @@ var StampRallyServer = class {
279
384
  key,
280
385
  { ok: false, code: "REWARD_NOT_FOUND", message: "Reward was not found." },
281
386
  request,
282
- now(this.#options, request.now)
387
+ now(this.#options, request.now),
388
+ persistence
283
389
  );
284
390
  const lockKey = `reward:${request.rallyId}:${reward.id}`;
285
- if (!await this.#persistence.acquireLock(lockKey, this.#options.lockTtlMs ?? 5e3))
391
+ if (!await persistence.acquireLock(request.rallyId, lockKey, this.#options.lockTtlMs ?? 5e3))
286
392
  return { ok: false, code: "CONFLICT", message: "The reward is being claimed." };
287
393
  const timestamp = now(this.#options, request.now);
288
394
  let decremented = false;
395
+ let previousState = null;
396
+ let recordedTicket = null;
397
+ let successAuditId = null;
289
398
  try {
290
- const checked = await this.#persistence.getIdempotentResult(key);
399
+ const checked = await persistence.getIdempotentResult(request.rallyId, key);
291
400
  if (checked !== null) return checked;
292
- const current = await this.#persistence.getUserState(request.rallyId, request.userId) ?? initialState(this.#config, request.userId, timestamp);
293
- 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(
294
405
  request.rallyId,
295
406
  request.userId,
296
407
  reward.id
@@ -313,15 +424,17 @@ var StampRallyServer = class {
313
424
  key,
314
425
  { ok: false, code: local.error.code, message: "Reward cannot be claimed." },
315
426
  request,
316
- timestamp
427
+ timestamp,
428
+ persistence
317
429
  );
318
- const stock = await this.#persistence.decrementRewardStock(reward.id);
430
+ const stock = await persistence.decrementRewardStock(request.rallyId, reward.id);
319
431
  if (!stock.success)
320
432
  return this.#rememberClaim(
321
433
  key,
322
434
  { ok: false, code: "OUT_OF_STOCK", message: "Reward is out of stock." },
323
435
  request,
324
- timestamp
436
+ timestamp,
437
+ persistence
325
438
  );
326
439
  decremented = true;
327
440
  const next = {
@@ -330,41 +443,59 @@ var StampRallyServer = class {
330
443
  updatedAt: timestamp
331
444
  };
332
445
  try {
333
- await this.#persistence.saveUserState(request.rallyId, request.userId, next);
446
+ await persistence.saveUserState(request.rallyId, request.userId, next);
334
447
  const response = local.value.claimTicketNumber === void 0 ? { ok: true, state: next } : { ok: true, state: next, claimTicketNumber: local.value.claimTicketNumber };
335
- await this.#persistence.recordAuditLog(
336
- audit(
337
- request.rallyId,
338
- request.userId,
339
- "CLAIM_REWARD",
340
- reward.id,
341
- request.idempotencyKey,
342
- "SUCCESS",
343
- timestamp
344
- )
448
+ recordedTicket = local.value.claimTicketNumber ?? "";
449
+ await persistence.recordUserClaim({
450
+ rallyId: request.rallyId,
451
+ userId: request.userId,
452
+ rewardId: reward.id,
453
+ ticketNumber: recordedTicket,
454
+ timestamp: Number.isNaN(Date.parse(timestamp)) ? Date.now() : Date.parse(timestamp)
455
+ });
456
+ const successAudit = audit(
457
+ request.rallyId,
458
+ request.userId,
459
+ "CLAIM_REWARD",
460
+ reward.id,
461
+ request.idempotencyKey,
462
+ "SUCCESS",
463
+ timestamp
345
464
  );
346
- await this.#persistence.saveIdempotentResult(
465
+ successAuditId = successAudit.id;
466
+ await persistence.recordAuditLog(successAudit);
467
+ await persistence.saveIdempotentResult(
468
+ request.rallyId,
347
469
  key,
348
470
  response,
349
471
  this.#options.idempotencyTtlMs ?? 864e5
350
472
  );
351
- if (this.#persistence instanceof Object && "recordClaim" in this.#persistence && typeof this.#persistence.recordClaim === "function")
352
- this.#persistence.recordClaim(request.rallyId, request.userId, reward.id);
353
473
  return response;
354
474
  } catch (error) {
355
- await this.#persistence.incrementRewardStock(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);
356
487
  decremented = false;
357
488
  throw error;
358
489
  }
359
490
  } catch (error) {
360
- if (decremented) await this.#persistence.incrementRewardStock(reward.id);
491
+ if (decremented) await this.#restoreRewardStock(persistence, request.rallyId, reward.id);
361
492
  return {
362
493
  ok: false,
363
494
  code: "PERSISTENCE_FAILED",
364
495
  message: error instanceof Error ? error.message : "Reward claim failed."
365
496
  };
366
497
  } finally {
367
- await this.#persistence.releaseLock(lockKey);
498
+ await persistence.releaseLock(request.rallyId, lockKey);
368
499
  }
369
500
  }
370
501
  async sync(rallyId, userId) {
@@ -397,14 +528,15 @@ var StampRallyServer = class {
397
528
  )
398
529
  );
399
530
  await this.#persistence.saveIdempotentResult(
531
+ rallyId,
400
532
  key,
401
533
  result,
402
534
  this.#options.idempotencyTtlMs ?? 864e5
403
535
  );
404
536
  return result;
405
537
  }
406
- async #rememberClaim(key, result, request, timestamp) {
407
- await this.#persistence.recordAuditLog(
538
+ async #rememberClaim(key, result, request, timestamp, persistence = this.#persistence) {
539
+ await persistence.recordAuditLog(
408
540
  audit(
409
541
  request.rallyId,
410
542
  request.userId,
@@ -416,13 +548,18 @@ var StampRallyServer = class {
416
548
  result.ok ? void 0 : result.code
417
549
  )
418
550
  );
419
- await this.#persistence.saveIdempotentResult(
551
+ await persistence.saveIdempotentResult(
552
+ request.rallyId,
420
553
  key,
421
554
  result,
422
555
  this.#options.idempotencyTtlMs ?? 864e5
423
556
  );
424
557
  return result;
425
558
  }
559
+ async #restoreRewardStock(persistence, rallyId, rewardId) {
560
+ if (persistence.restoreRewardStock !== void 0)
561
+ await persistence.restoreRewardStock(rallyId, rewardId);
562
+ }
426
563
  };
427
564
 
428
565
  exports.InMemoryServerPersistenceAdapter = InMemoryServerPersistenceAdapter;
@@ -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 ServerPersistenceAdapter {\n acquireLock(lockKey: string, ttlMs: number): Promise<boolean>;\n releaseLock(lockKey: string): Promise<void>;\n decrementRewardStock(\n rewardId: string,\n ): Promise<{ readonly success: boolean; readonly remainingStock: number }>;\n incrementRewardStock(rewardId: string): Promise<void>;\n getIdempotentResult<T>(key: string): Promise<T | null>;\n saveIdempotentResult<T>(key: string, result: T, ttlMs: number): Promise<void>;\n getUserClaimCount(rallyId: string, userId: string, rewardId: string): Promise<number>;\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 #claims = new Map<string, number>();\n readonly #auditLogs: AuditLog[] = [];\n constructor(options: InMemoryServerPersistenceOptions = {}) {\n this.#stocks = new Map(Object.entries(options.stocks ?? {}));\n }\n async acquireLock(key: string, ttlMs: number): Promise<boolean> {\n const now = Date.now();\n const until = this.#locks.get(key);\n if (until !== undefined && until > now) return false;\n this.#locks.set(key, now + Math.max(1, ttlMs));\n return true;\n }\n async releaseLock(key: string): Promise<void> {\n this.#locks.delete(key);\n }\n async decrementRewardStock(\n rewardId: string,\n ): Promise<{ success: boolean; remainingStock: number }> {\n const current = this.#stocks.get(rewardId);\n if (current === undefined) return { success: true, remainingStock: Number.POSITIVE_INFINITY };\n if (current <= 0) return { success: false, remainingStock: 0 };\n this.#stocks.set(rewardId, current - 1);\n return { success: true, remainingStock: current - 1 };\n }\n async incrementRewardStock(rewardId: string): Promise<void> {\n const current = this.#stocks.get(rewardId);\n if (current !== undefined) this.#stocks.set(rewardId, current + 1);\n }\n async getIdempotentResult<T>(key: string): Promise<T | null> {\n const value = this.#idempotent.get(key);\n if (value === undefined || value.expiresAt <= Date.now()) {\n this.#idempotent.delete(key);\n return null;\n }\n return structuredClone(value.value) as T;\n }\n async saveIdempotentResult<T>(key: string, result: T, ttlMs: number): Promise<void> {\n this.#idempotent.set(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 recordClaim(rallyId: string, userId: string, rewardId: string): void {\n const key = `${rallyId}:${userId}:${rewardId}`;\n this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);\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>(key);\n if (previous !== null) return previous;\n const lockKey = `state:${request.rallyId}:${request.userId}`;\n if (!(await this.#persistence.acquireLock(lockKey, this.#options.lockTtlMs ?? 5_000)))\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(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>(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 );\n const lockKey = `reward:${request.rallyId}:${reward.id}`;\n if (!(await this.#persistence.acquireLock(lockKey, this.#options.lockTtlMs ?? 5_000)))\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>(key);\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(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.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 key,\n response,\n this.#options.idempotencyTtlMs ?? 86_400_000,\n );\n if (\n this.#persistence instanceof Object &&\n \"recordClaim\" in this.#persistence &&\n typeof this.#persistence.recordClaim === \"function\"\n )\n this.#persistence.recordClaim(request.rallyId, request.userId, reward.id);\n return response;\n } catch (error) {\n await this.#persistence.incrementRewardStock(reward.id);\n decremented = false;\n throw error;\n }\n } catch (error) {\n if (decremented) await this.#persistence.incrementRewardStock(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(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 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 key,\n result,\n this.#options.idempotencyTtlMs ?? 86_400_000,\n );\n return result;\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"]}