@stamprally/server 0.6.0 → 0.7.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
@@ -2,6 +2,197 @@
2
2
 
3
3
  var core = require('@stamprally/core');
4
4
 
5
+ // src/index.ts
6
+
7
+ // src/persistence.ts
8
+ var InMemoryServerPersistenceAdapter = class {
9
+ #locks = /* @__PURE__ */ new Map();
10
+ #idempotent = /* @__PURE__ */ new Map();
11
+ #states = /* @__PURE__ */ new Map();
12
+ #stocks;
13
+ #auditLogs = [];
14
+ constructor(options = {}) {
15
+ this.#stocks = new Map(Object.entries(options.stocks ?? {}));
16
+ }
17
+ async acquireLock(lockKey, ttlMs) {
18
+ const now2 = Date.now();
19
+ const expiresAt = this.#locks.get(lockKey);
20
+ if (expiresAt !== void 0 && expiresAt > now2) return false;
21
+ this.#locks.set(lockKey, now2 + Math.max(1, ttlMs));
22
+ return true;
23
+ }
24
+ async releaseLock(lockKey) {
25
+ this.#locks.delete(lockKey);
26
+ }
27
+ async decrementRewardStock(rewardId) {
28
+ const stock = this.#stocks.get(rewardId);
29
+ if (stock === void 0) return { success: true, remainingStock: Number.POSITIVE_INFINITY };
30
+ if (stock <= 0) return { success: false, remainingStock: 0 };
31
+ const remainingStock = stock - 1;
32
+ this.#stocks.set(rewardId, remainingStock);
33
+ return { success: true, remainingStock };
34
+ }
35
+ async getIdempotentResult(idempotencyKey) {
36
+ const entry = this.#idempotent.get(idempotencyKey);
37
+ if (entry === void 0) return null;
38
+ if (entry.expiresAt <= Date.now()) {
39
+ this.#idempotent.delete(idempotencyKey);
40
+ return null;
41
+ }
42
+ return structuredClone(entry.value);
43
+ }
44
+ async saveIdempotentResult(idempotencyKey, result, ttlMs) {
45
+ this.#idempotent.set(idempotencyKey, {
46
+ value: structuredClone(result),
47
+ expiresAt: Date.now() + Math.max(1, ttlMs)
48
+ });
49
+ }
50
+ async getUserState(rallyId, userId) {
51
+ const state = this.#states.get(`${rallyId}:${userId}`);
52
+ return state === void 0 ? null : structuredClone(state);
53
+ }
54
+ async saveUserState(rallyId, userId, state) {
55
+ this.#states.set(`${rallyId}:${userId}`, structuredClone(state));
56
+ }
57
+ async recordAuditLog(log) {
58
+ this.#auditLogs.push(structuredClone(log));
59
+ }
60
+ getAuditLogs() {
61
+ return structuredClone(this.#auditLogs);
62
+ }
63
+ };
64
+
65
+ // src/universalServer.ts
66
+ function distanceMeters(aLat, aLon, bLat, bLon) {
67
+ const radians = (degrees) => degrees * Math.PI / 180;
68
+ const dLat = radians(bLat - aLat);
69
+ const dLon = radians(bLon - aLon);
70
+ const latA = radians(aLat);
71
+ const latB = radians(bLat);
72
+ const value = Math.sin(dLat / 2) ** 2 + Math.cos(latA) * Math.cos(latB) * Math.sin(dLon / 2) ** 2;
73
+ return 6371e3 * 2 * Math.asin(Math.sqrt(Math.min(1, value)));
74
+ }
75
+ function initialState(config, now2) {
76
+ return { rallyId: config.id, records: [], updatedAt: now2 };
77
+ }
78
+ function matchesCondition(condition, context, customValidators) {
79
+ switch (condition.type) {
80
+ case "qr":
81
+ return context.type === "qr" && context.token === condition.secretToken;
82
+ case "passcode":
83
+ return context.type === "passcode" && (condition.caseSensitive === false ? context.code.toLocaleLowerCase() === condition.code.toLocaleLowerCase() : context.code === condition.code);
84
+ case "gps":
85
+ return context.type === "gps" && distanceMeters(
86
+ condition.latitude,
87
+ condition.longitude,
88
+ context.latitude,
89
+ context.longitude
90
+ ) <= condition.radiusMeters;
91
+ case "custom":
92
+ return customValidators?.[condition.validatorName]?.(
93
+ context.type === "custom" ? context.value : void 0,
94
+ condition
95
+ ) ?? false;
96
+ }
97
+ }
98
+ function audit(request, status, now2, errorCode) {
99
+ return {
100
+ id: `audit-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`,
101
+ timestamp: now2,
102
+ rallyId: request.rallyId,
103
+ userId: request.userId,
104
+ action: "CHECK_IN",
105
+ resourceId: request.spotId,
106
+ status,
107
+ idempotencyKey: request.idempotencyKey,
108
+ ...errorCode === void 0 ? {} : { metadata: { errorCode } }
109
+ };
110
+ }
111
+ var UniversalRallyServer = class {
112
+ #config;
113
+ #persistence;
114
+ #options;
115
+ constructor(config, persistence, options = {}) {
116
+ this.#config = config;
117
+ this.#persistence = persistence;
118
+ this.#options = options;
119
+ }
120
+ async checkIn(request) {
121
+ const key = `check-in:${request.rallyId}:${request.userId}:${request.idempotencyKey}`;
122
+ const previous = await this.#persistence.getIdempotentResult(key);
123
+ if (previous !== null) return previous;
124
+ const lockKey = `state:${request.rallyId}:${request.userId}`;
125
+ const locked = await this.#persistence.acquireLock(lockKey, this.#options.lockTtlMs ?? 5e3);
126
+ if (!locked)
127
+ return { ok: false, code: "CONFLICT", message: "The user state is being updated." };
128
+ const now2 = request.now ?? this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
129
+ try {
130
+ const spot = this.#config.spots.find((item) => item.id === request.spotId);
131
+ if (spot === void 0)
132
+ return this.#remember(
133
+ key,
134
+ { ok: false, code: "SPOT_NOT_FOUND", message: "Spot was not found." },
135
+ request,
136
+ now2
137
+ );
138
+ const current = await this.#persistence.getUserState(request.rallyId, request.userId) ?? initialState(this.#config, now2);
139
+ if (current.records.some((record) => record.stampId === request.spotId))
140
+ return this.#remember(
141
+ key,
142
+ { ok: false, code: "ALREADY_CLAIMED", message: "Spot was already claimed." },
143
+ request,
144
+ now2
145
+ );
146
+ const acquired = new Set(current.records.map((record) => record.stampId));
147
+ if (spot.prerequisites?.some((id2) => !acquired.has(id2)))
148
+ return this.#remember(
149
+ key,
150
+ {
151
+ ok: false,
152
+ code: "PREREQUISITES_NOT_MET",
153
+ message: "Prerequisite spots are not complete."
154
+ },
155
+ request,
156
+ now2
157
+ );
158
+ if (!spot.conditions.every(
159
+ (condition) => matchesCondition(condition, request.context, this.#options.customValidators)
160
+ ))
161
+ return this.#remember(
162
+ key,
163
+ { ok: false, code: "INVALID_PROOF", message: "Verification failed." },
164
+ request,
165
+ now2
166
+ );
167
+ const state = {
168
+ ...current,
169
+ records: [...current.records, { stampId: request.spotId, acquiredAt: now2 }],
170
+ updatedAt: now2
171
+ };
172
+ await this.#persistence.saveUserState(request.rallyId, request.userId, state);
173
+ await this.#persistence.recordAuditLog(audit(request, "SUCCESS", now2));
174
+ return this.#remember(key, { ok: true, state }, request, now2);
175
+ } finally {
176
+ await this.#persistence.releaseLock(lockKey);
177
+ }
178
+ }
179
+ async sync(rallyId, userId) {
180
+ const now2 = this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
181
+ return await this.#persistence.getUserState(rallyId, userId) ?? initialState(this.#config, now2);
182
+ }
183
+ async #remember(key, result, request, now2) {
184
+ await this.#persistence.recordAuditLog(
185
+ audit(request, "REJECTED", now2, result.ok ? void 0 : result.code)
186
+ );
187
+ await this.#persistence.saveIdempotentResult(
188
+ key,
189
+ result,
190
+ this.#options.idempotencyTtlMs ?? 864e5
191
+ );
192
+ return result;
193
+ }
194
+ };
195
+
5
196
  // src/index.ts
6
197
  var InMemoryServerStorage = class {
7
198
  #states = /* @__PURE__ */ new Map();
@@ -369,7 +560,9 @@ function emptyState(config, timestamp) {
369
560
  };
370
561
  }
371
562
 
563
+ exports.InMemoryServerPersistenceAdapter = InMemoryServerPersistenceAdapter;
372
564
  exports.InMemoryServerStorage = InMemoryServerStorage;
373
565
  exports.StampRallyServer = StampRallyServer;
566
+ exports.UniversalRallyServer = UniversalRallyServer;
374
567
  //# sourceMappingURL=index.cjs.map
375
568
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["processStamp","createSecureToken","consumeReward","reconcileRewardStates"],"mappings":"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","file":"index.cjs","sourcesContent":["import {\n consumeReward,\n createSecureToken,\n processStamp,\n type RallyConfig,\n type RewardConsumeError,\n reconcileRewardStates,\n type SecureTokenSecretKey,\n type StampError,\n type StampRallyState,\n type StampRecord,\n type VerificationContext,\n} from \"@stamprally/core\";\n\nexport interface UserRallyState extends StampRallyState {\n readonly userId?: string;\n}\n\nexport interface RallyAuditLog {\n readonly id: string;\n readonly timestamp: string;\n readonly rallyId: string;\n readonly userId: string;\n readonly action: \"CHECK_IN\" | \"CLAIM_REWARD\";\n readonly resourceId: string;\n readonly status: \"SUCCESS\" | \"REJECTED\";\n readonly idempotencyKey: string;\n readonly proofData?: unknown;\n readonly metadata?: Readonly<Record<string, unknown>>;\n}\n\nexport interface ServerStorageAdapter {\n getRewardStock(rewardId: string): Promise<number | null>;\n decrementRewardStock(rewardId: string): Promise<boolean>;\n getUserClaims(userId: string, rewardId: string): Promise<number>;\n recordAuditLog(log: RallyAuditLog): Promise<void>;\n saveUserState(userId: string, state: UserRallyState): Promise<void>;\n getUserState(userId: string): Promise<UserRallyState | null>;\n}\n\nexport interface AdminRallyConfig extends RallyConfig {\n readonly secretKey: SecureTokenSecretKey;\n readonly proofTtlSeconds?: number;\n readonly authenticate?: (request: Request) => Promise<string | null> | string | null;\n}\n\nexport interface CheckInRequest {\n readonly userId: string;\n readonly spotId: string;\n readonly claimMethod: string;\n readonly proofData?: unknown;\n readonly idempotencyKey: string;\n}\n\nexport interface ClaimRewardRequest {\n readonly userId: string;\n readonly rewardId: string;\n readonly staffPasscode?: string;\n readonly idempotencyKey: string;\n readonly staffId?: string;\n}\n\nexport interface SyncRequest {\n readonly userId: string;\n readonly queue?: ReadonlyArray<SyncCheckInOperation>;\n readonly operations?: ReadonlyArray<SyncCheckInOperation>;\n}\n\nexport interface SyncCheckInOperation {\n readonly userId?: string;\n readonly spotId?: string;\n readonly stampId?: string;\n readonly claimMethod?: string;\n readonly proofData?: unknown;\n readonly context?: VerificationContext;\n readonly idempotencyKey: string;\n}\n\nexport interface StampClaimProof {\n readonly token: string;\n readonly rallyId: string;\n readonly userId: string;\n readonly spotId: string;\n readonly acquiredAt: string;\n}\n\nexport interface InMemoryServerStorageOptions {\n readonly stocks?: Readonly<Record<string, number>>;\n}\n\nexport class InMemoryServerStorage implements ServerStorageAdapter {\n readonly #states = new Map<string, UserRallyState>();\n readonly #stocks: Map<string, number>;\n readonly #claims = new Map<string, number>();\n readonly #auditLogs: RallyAuditLog[] = [];\n\n constructor(options: InMemoryServerStorageOptions = {}) {\n this.#stocks = new Map(Object.entries(options.stocks ?? {}));\n }\n\n async getRewardStock(rewardId: string): Promise<number | null> {\n return this.#stocks.get(rewardId) ?? null;\n }\n\n async decrementRewardStock(rewardId: string): Promise<boolean> {\n const stock = this.#stocks.get(rewardId);\n if (stock === undefined) return true;\n if (stock <= 0) return false;\n this.#stocks.set(rewardId, stock - 1);\n return true;\n }\n\n async getUserClaims(userId: string, rewardId: string): Promise<number> {\n return this.#claims.get(`${userId}:${rewardId}`) ?? 0;\n }\n\n async recordAuditLog(log: RallyAuditLog): Promise<void> {\n this.#auditLogs.push({ ...log });\n }\n\n async saveUserState(userId: string, state: UserRallyState): Promise<void> {\n this.#states.set(userId, cloneUserState(state));\n }\n\n async getUserState(userId: string): Promise<UserRallyState | null> {\n const state = this.#states.get(userId);\n return state === undefined ? null : cloneUserState(state);\n }\n\n getAuditLogs(): ReadonlyArray<RallyAuditLog> {\n return this.#auditLogs.map((log) => ({ ...log }));\n }\n\n recordClaim(userId: string, rewardId: string): void {\n const key = `${userId}:${rewardId}`;\n this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);\n }\n}\n\nfunction cloneUserState(state: UserRallyState): UserRallyState {\n return {\n ...state,\n records: state.records.map((record) => ({\n ...record,\n ...(record.metadata === undefined ? {} : { metadata: { ...record.metadata } }),\n })),\n ...(state.rewards === undefined\n ? {}\n : { rewards: state.rewards.map((reward) => ({ ...reward })) }),\n };\n}\n\nfunction jsonResponse(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}\n\nfunction errorResponse(code: string, message: string, status: number): Response {\n return jsonResponse({ ok: false, error: { code, message } }, status);\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction contextForClaim(method: string, proofData: unknown): VerificationContext {\n if (method === \"token\" || method === \"qr\" || method === \"passcode\") {\n return {\n type: \"token\",\n token:\n isObject(proofData) && typeof proofData.token === \"string\"\n ? proofData.token\n : String(proofData ?? \"\"),\n };\n }\n if (method === \"geo\" || method === \"geolocation\") {\n const value = isObject(proofData) ? proofData : {};\n return {\n type: \"geo\",\n currentLatitude:\n typeof value.latitude === \"number\"\n ? value.latitude\n : typeof value.currentLatitude === \"number\"\n ? value.currentLatitude\n : Number.NaN,\n currentLongitude:\n typeof value.longitude === \"number\"\n ? value.longitude\n : typeof value.currentLongitude === \"number\"\n ? value.currentLongitude\n : Number.NaN,\n };\n }\n return { type: \"instant\" };\n}\n\nfunction now(): string {\n return new Date().toISOString();\n}\n\nfunction hasText(value: unknown): value is string {\n return typeof value === \"string\" && value.trim() !== \"\";\n}\n\nfunction safeProofData(value: unknown): unknown {\n return isObject(value) && typeof value.token === \"string\" ? { type: \"token\" } : value;\n}\n\nfunction proofFromContext(context: VerificationContext | undefined): unknown {\n if (context?.type === \"token\") return { token: context.token };\n if (context?.type === \"geo\") {\n return { latitude: context.currentLatitude, longitude: context.currentLongitude };\n }\n return undefined;\n}\n\nfunction id(prefix: string): string {\n return `${prefix}-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;\n}\n\nexport class StampRallyServer {\n readonly #config: AdminRallyConfig;\n readonly #storage: ServerStorageAdapter;\n readonly #idempotent = new Map<string, Response>();\n readonly #claims = new Map<string, number>();\n #queue: Promise<unknown> = Promise.resolve();\n\n constructor(config: AdminRallyConfig, storage: ServerStorageAdapter) {\n this.#config = config;\n this.#storage = storage;\n }\n\n handle(request: Request): Promise<Response> {\n const path = new URL(request.url).pathname;\n if (request.method !== \"POST\")\n return Promise.resolve(errorResponse(\"METHOD_NOT_ALLOWED\", \"POST is required.\", 405));\n if (path.endsWith(\"/check-in\")) return this.verifyCheckIn(request);\n if (path.endsWith(\"/claim-reward\")) return this.claimReward(request);\n if (path.endsWith(\"/sync\")) return this.syncProgress(request);\n return Promise.resolve(errorResponse(\"NOT_FOUND\", \"Route not found.\", 404));\n }\n\n verifyCheckIn(request: Request): Promise<Response> {\n return this.#enqueue(() => this.#verifyCheckIn(request));\n }\n\n claimReward(request: Request): Promise<Response> {\n return this.#enqueue(() => this.#claimReward(request));\n }\n\n syncProgress(request: Request): Promise<Response> {\n return this.#enqueue(() => this.#syncProgress(request));\n }\n\n #enqueue<T>(operation: () => Promise<T>): Promise<T> {\n const next = this.#queue.then(operation, operation);\n this.#queue = next.then(\n () => undefined,\n () => undefined,\n );\n return next;\n }\n\n async #authenticate(request: Request): Promise<string | null> {\n return this.#config.authenticate === undefined ? null : this.#config.authenticate(request);\n }\n\n async #parse<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\n async #verifyCheckIn(request: Request): Promise<Response> {\n const authenticatedUser = await this.#authenticate(request);\n const body = await this.#parse<CheckInRequest>(request);\n const userId = body?.userId ?? authenticatedUser;\n if (\n userId === null ||\n userId === undefined ||\n body === null ||\n !hasText(userId) ||\n !hasText(body.spotId) ||\n !hasText(body.claimMethod) ||\n !hasText(body.idempotencyKey)\n ) {\n return errorResponse(\n \"INVALID_REQUEST\",\n \"userId, spotId, and idempotencyKey are required.\",\n 400,\n );\n }\n if (authenticatedUser !== null && authenticatedUser !== userId)\n return errorResponse(\"UNAUTHORIZED\", \"User identity does not match authentication.\", 401);\n const key = `check-in:${userId}:${body.idempotencyKey}`;\n const previous = this.#idempotent.get(key);\n if (previous !== undefined) return previous.clone();\n const timestamp = now();\n const current =\n (await this.#storage.getUserState(userId)) ?? emptyState(this.#config, timestamp);\n const result = processStamp(\n current,\n this.#config,\n body.spotId,\n contextForClaim(body.claimMethod, body.proofData),\n timestamp,\n );\n if (!result.ok) {\n await this.#audit(\n userId,\n \"CHECK_IN\",\n body.spotId,\n body.idempotencyKey,\n \"REJECTED\",\n safeProofData(body.proofData),\n result.error,\n );\n return this.#remember(key, jsonResponse({ ok: false, error: result.error }, 422));\n }\n const ttl = this.#config.proofTtlSeconds ?? 3600;\n const token = await createSecureToken(\n {\n type: \"stamp_claim\",\n rallyId: this.#config.id,\n userId,\n spotId: body.spotId,\n acquiredAt: timestamp,\n exp: Math.floor(Date.now() / 1000) + ttl,\n },\n this.#config.secretKey,\n { encrypt: true },\n );\n await this.#storage.saveUserState(userId, result.value.nextState);\n await this.#audit(\n userId,\n \"CHECK_IN\",\n body.spotId,\n body.idempotencyKey,\n \"SUCCESS\",\n safeProofData(body.proofData),\n );\n return this.#remember(\n key,\n jsonResponse({\n ok: true,\n state: result.value.nextState,\n proof: {\n token,\n rallyId: this.#config.id,\n userId,\n spotId: body.spotId,\n acquiredAt: timestamp,\n },\n }),\n );\n }\n\n async #claimReward(request: Request): Promise<Response> {\n const authenticatedUser = await this.#authenticate(request);\n const body = await this.#parse<ClaimRewardRequest>(request);\n const userId = body?.userId ?? authenticatedUser;\n if (\n userId === null ||\n userId === undefined ||\n body === null ||\n !hasText(userId) ||\n !hasText(body.rewardId) ||\n !hasText(body.idempotencyKey)\n )\n return errorResponse(\n \"INVALID_REQUEST\",\n \"userId, rewardId, and idempotencyKey are required.\",\n 400,\n );\n if (authenticatedUser !== null && authenticatedUser !== userId)\n return errorResponse(\"UNAUTHORIZED\", \"User identity does not match authentication.\", 401);\n const key = `claim-reward:${userId}:${body.rewardId}:${body.idempotencyKey}`;\n const previous = this.#idempotent.get(key);\n if (previous !== undefined) return previous.clone();\n const reward = this.#config.rewards?.find((item) => item.id === body.rewardId);\n if (reward === undefined) {\n await this.#audit(userId, \"CLAIM_REWARD\", body.rewardId, body.idempotencyKey, \"REJECTED\");\n return this.#remember(key, errorResponse(\"REWARD_NOT_FOUND\", \"Reward was not found.\", 404));\n }\n const timestamp = now();\n const current =\n (await this.#storage.getUserState(userId)) ?? emptyState(this.#config, timestamp);\n const rewardState = current.rewards?.find((item) => item.rewardId === reward.id);\n if (rewardState === undefined) {\n await this.#audit(userId, \"CLAIM_REWARD\", reward.id, body.idempotencyKey, \"REJECTED\");\n return this.#remember(key, errorResponse(\"NOT_AVAILABLE\", \"Reward is not available.\", 422));\n }\n const userClaims = Math.max(\n await this.#storage.getUserClaims(userId, reward.id),\n this.#claims.get(`${userId}:${reward.id}`) ?? 0,\n );\n const stock = await this.#storage.getRewardStock(reward.id);\n const userLimit = reward.userClaimLimit ?? reward.limitPerUser;\n const canReclaimServerReward =\n reward.redemptionMethod === \"server_claim\" &&\n (userLimit === undefined || userClaims < userLimit) &&\n (stock === null || stock > 0);\n const claimableState =\n canReclaimServerReward && rewardState.status === \"CONSUMED\"\n ? { ...rewardState, status: \"AVAILABLE\" as const }\n : rewardState;\n const local = consumeReward({\n reward,\n currentState: claimableState,\n now: timestamp,\n ...(body.staffPasscode === undefined ? {} : { inputPasscode: body.staffPasscode }),\n ...(body.staffId === undefined ? {} : { staffId: body.staffId }),\n userId,\n userRedemptionCount: userClaims,\n });\n if (!local.ok)\n return this.#remember(\n key,\n await this.#rewardError(userId, reward.id, body.idempotencyKey, local.error),\n );\n if (stock !== null && !(await this.#storage.decrementRewardStock(reward.id)))\n return this.#remember(\n key,\n await this.#rewardError(userId, reward.id, body.idempotencyKey, {\n code: \"OUT_OF_STOCK\",\n rewardId: reward.id,\n }),\n );\n const nextState: StampRallyState = {\n ...current,\n rewards: (current.rewards ?? []).map((item) =>\n item.rewardId === reward.id ? local.value : item,\n ),\n updatedAt: timestamp,\n };\n await this.#storage.saveUserState(userId, nextState);\n const claimKey = `${userId}:${reward.id}`;\n this.#claims.set(claimKey, userClaims + 1);\n if (this.#storage instanceof InMemoryServerStorage)\n this.#storage.recordClaim(userId, reward.id);\n await this.#audit(userId, \"CLAIM_REWARD\", reward.id, body.idempotencyKey, \"SUCCESS\", {\n staffId: body.staffId,\n });\n return this.#remember(\n key,\n jsonResponse({\n ok: true,\n state: nextState,\n claimTicketNumber: local.value.claimTicketNumber,\n }),\n );\n }\n\n async #rewardError(\n userId: string,\n rewardId: string,\n key: string,\n error: RewardConsumeError,\n ): Promise<Response> {\n await this.#audit(userId, \"CLAIM_REWARD\", rewardId, key, \"REJECTED\", undefined, error);\n return jsonResponse({ ok: false, error }, 422);\n }\n\n async #syncProgress(request: Request): Promise<Response> {\n const authenticatedUser = await this.#authenticate(request);\n const body = await this.#parse<SyncRequest>(request);\n if (body === null || body.userId === undefined)\n return errorResponse(\"INVALID_REQUEST\", \"userId is required.\", 400);\n if (authenticatedUser !== null && authenticatedUser !== body.userId)\n return errorResponse(\"UNAUTHORIZED\", \"User identity does not match authentication.\", 401);\n const queue = body.queue ?? body.operations ?? [];\n for (const operation of queue) {\n const userId = operation.userId ?? body.userId;\n const spotId = operation.spotId ?? operation.stampId;\n const claimMethod = operation.claimMethod ?? operation.context?.type;\n if (!hasText(userId) || !hasText(spotId) || !hasText(claimMethod)) continue;\n const synthetic = new Request(new URL(\"/api/check-in\", request.url), {\n method: \"POST\",\n body: JSON.stringify({\n userId,\n spotId,\n claimMethod,\n proofData: operation.proofData ?? proofFromContext(operation.context),\n idempotencyKey: operation.idempotencyKey,\n }),\n headers: { \"content-type\": \"application/json\" },\n });\n await this.#verifyCheckIn(synthetic);\n }\n const timestamp = now();\n const state =\n (await this.#storage.getUserState(body.userId)) ?? emptyState(this.#config, timestamp);\n await this.#storage.saveUserState(body.userId, state);\n return jsonResponse({ ok: true, state, accepted: queue.length });\n }\n\n async #audit(\n userId: string,\n action: RallyAuditLog[\"action\"],\n resourceId: string,\n idempotencyKey: string,\n status: RallyAuditLog[\"status\"],\n proofData?: unknown,\n error?: unknown,\n ): Promise<void> {\n await this.#storage.recordAuditLog({\n id: id(\"audit\"),\n timestamp: now(),\n rallyId: this.#config.id,\n userId,\n action,\n resourceId,\n status,\n idempotencyKey,\n ...(proofData === undefined ? {} : { proofData }),\n ...(error === undefined\n ? {}\n : {\n metadata: {\n errorCode: isObject(error) && typeof error.code === \"string\" ? error.code : \"UNKNOWN\",\n },\n }),\n });\n }\n\n #remember(key: string, response: Response): Response {\n this.#idempotent.set(key, response.clone());\n return response;\n }\n}\n\nfunction emptyState(config: RallyConfig, timestamp: string): UserRallyState {\n return {\n rallyId: config.id,\n records: [],\n ...(config.rewards === undefined\n ? {}\n : { rewards: reconcileRewardStates(config.rewards, [], 0, timestamp) }),\n updatedAt: timestamp,\n };\n}\n\nexport type { StampError, StampRecord };\n"]}
1
+ {"version":3,"sources":["../src/persistence.ts","../src/universalServer.ts","../src/index.ts"],"names":["now","id","processStamp","createSecureToken","consumeReward","reconcileRewardStates"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { RallyAuditLog, UserRallyState } from \"./index.js\";\n\n/** Persistence contract for implementations backed by Redis, SQL, or another RDB. */\nexport interface ServerPersistenceAdapter {\n acquireLock(lockKey: string, ttlMs: number): Promise<boolean>;\n releaseLock(lockKey: string): Promise<void>;\n decrementRewardStock(rewardId: string): Promise<{ success: boolean; remainingStock: number }>;\n getIdempotentResult<T>(idempotencyKey: string): Promise<T | null>;\n saveIdempotentResult<T>(idempotencyKey: string, result: T, ttlMs: number): Promise<void>;\n getUserState(rallyId: string, userId: string): Promise<UserRallyState | null>;\n saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void>;\n recordAuditLog(log: RallyAuditLog): Promise<void>;\n}\n\ninterface TimedValue<T> {\n readonly value: T;\n readonly expiresAt: number;\n}\n\nexport interface InMemoryServerPersistenceOptions {\n readonly stocks?: Readonly<Record<string, number>>;\n}\n\n/** Deterministic adapter for tests and small single-process deployments. */\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 #auditLogs: RallyAuditLog[] = [];\n\n constructor(options: InMemoryServerPersistenceOptions = {}) {\n this.#stocks = new Map(Object.entries(options.stocks ?? {}));\n }\n\n async acquireLock(lockKey: string, ttlMs: number): Promise<boolean> {\n const now = Date.now();\n const expiresAt = this.#locks.get(lockKey);\n if (expiresAt !== undefined && expiresAt > now) return false;\n this.#locks.set(lockKey, now + Math.max(1, ttlMs));\n return true;\n }\n\n async releaseLock(lockKey: string): Promise<void> {\n this.#locks.delete(lockKey);\n }\n\n async decrementRewardStock(\n rewardId: string,\n ): Promise<{ success: boolean; remainingStock: number }> {\n const stock = this.#stocks.get(rewardId);\n if (stock === undefined) return { success: true, remainingStock: Number.POSITIVE_INFINITY };\n if (stock <= 0) return { success: false, remainingStock: 0 };\n const remainingStock = stock - 1;\n this.#stocks.set(rewardId, remainingStock);\n return { success: true, remainingStock };\n }\n\n async getIdempotentResult<T>(idempotencyKey: string): Promise<T | null> {\n const entry = this.#idempotent.get(idempotencyKey);\n if (entry === undefined) return null;\n if (entry.expiresAt <= Date.now()) {\n this.#idempotent.delete(idempotencyKey);\n return null;\n }\n return structuredClone(entry.value) as T;\n }\n\n async saveIdempotentResult<T>(idempotencyKey: string, result: T, ttlMs: number): Promise<void> {\n this.#idempotent.set(idempotencyKey, {\n value: structuredClone(result),\n expiresAt: Date.now() + Math.max(1, ttlMs),\n });\n }\n\n async getUserState(rallyId: string, userId: string): Promise<UserRallyState | null> {\n const state = this.#states.get(`${rallyId}:${userId}`);\n return state === undefined ? null : structuredClone(state);\n }\n\n async saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void> {\n this.#states.set(`${rallyId}:${userId}`, structuredClone(state));\n }\n\n async recordAuditLog(log: RallyAuditLog): Promise<void> {\n this.#auditLogs.push(structuredClone(log));\n }\n\n getAuditLogs(): ReadonlyArray<RallyAuditLog> {\n return structuredClone(this.#auditLogs);\n }\n}\n","import type { AdminRallyConfig, StampRallyState, VerificationCondition } from \"@stamprally/core\";\nimport type { RallyAuditLog, UserRallyState } from \"./index.js\";\nimport type { ServerPersistenceAdapter } from \"./persistence.js\";\n\nexport type UniversalVerificationContext =\n | { readonly type: \"qr\"; readonly token: string }\n | { readonly type: \"passcode\"; readonly code: string }\n | { readonly type: \"gps\"; readonly latitude: number; readonly longitude: number }\n | { readonly type: \"custom\"; readonly value: unknown };\n\nexport interface UniversalCheckInRequest {\n readonly rallyId: string;\n readonly userId: string;\n readonly spotId: string;\n readonly context: UniversalVerificationContext;\n readonly idempotencyKey: string;\n readonly now?: string;\n}\n\nexport type UniversalCheckInResult =\n | { readonly ok: true; readonly state: UserRallyState }\n | { readonly ok: false; readonly code: string; readonly message: string };\n\nexport interface UniversalRallyServerOptions {\n readonly idempotencyTtlMs?: number;\n readonly lockTtlMs?: number;\n readonly customValidators?: Readonly<\n Record<string, (value: unknown, condition: VerificationCondition) => boolean>\n >;\n readonly now?: () => string;\n}\n\nfunction distanceMeters(aLat: number, aLon: number, bLat: number, bLon: number): number {\n const radians = (degrees: number): number => (degrees * Math.PI) / 180;\n const dLat = radians(bLat - aLat);\n const dLon = radians(bLon - aLon);\n const latA = radians(aLat);\n const latB = radians(bLat);\n const value = Math.sin(dLat / 2) ** 2 + Math.cos(latA) * Math.cos(latB) * Math.sin(dLon / 2) ** 2;\n return 6_371_000 * 2 * Math.asin(Math.sqrt(Math.min(1, value)));\n}\n\nfunction initialState(config: AdminRallyConfig, now: string): UserRallyState {\n return { rallyId: config.id, records: [], updatedAt: now };\n}\n\nfunction matchesCondition(\n condition: VerificationCondition,\n context: UniversalVerificationContext,\n customValidators: UniversalRallyServerOptions[\"customValidators\"],\n): boolean {\n switch (condition.type) {\n case \"qr\":\n return context.type === \"qr\" && context.token === condition.secretToken;\n case \"passcode\":\n return (\n context.type === \"passcode\" &&\n (condition.caseSensitive === false\n ? context.code.toLocaleLowerCase() === condition.code.toLocaleLowerCase()\n : context.code === condition.code)\n );\n case \"gps\":\n return (\n context.type === \"gps\" &&\n distanceMeters(\n condition.latitude,\n condition.longitude,\n context.latitude,\n context.longitude,\n ) <= condition.radiusMeters\n );\n case \"custom\":\n return (\n customValidators?.[condition.validatorName]?.(\n context.type === \"custom\" ? context.value : undefined,\n condition,\n ) ?? false\n );\n }\n}\n\nfunction audit(\n request: UniversalCheckInRequest,\n status: RallyAuditLog[\"status\"],\n now: string,\n errorCode?: string,\n): RallyAuditLog {\n return {\n id: `audit-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`,\n timestamp: now,\n rallyId: request.rallyId,\n userId: request.userId,\n action: \"CHECK_IN\",\n resourceId: request.spotId,\n status,\n idempotencyKey: request.idempotencyKey,\n ...(errorCode === undefined ? {} : { metadata: { errorCode } }),\n };\n}\n\n/** Atomic, adapter-backed check-in service for the universal model. */\nexport class UniversalRallyServer {\n readonly #config: AdminRallyConfig;\n readonly #persistence: ServerPersistenceAdapter;\n readonly #options: UniversalRallyServerOptions;\n\n constructor(\n config: AdminRallyConfig,\n persistence: ServerPersistenceAdapter,\n options: UniversalRallyServerOptions = {},\n ) {\n this.#config = config;\n this.#persistence = persistence;\n this.#options = options;\n }\n\n async checkIn(request: UniversalCheckInRequest): Promise<UniversalCheckInResult> {\n const key = `check-in:${request.rallyId}:${request.userId}:${request.idempotencyKey}`;\n const previous = await this.#persistence.getIdempotentResult<UniversalCheckInResult>(key);\n if (previous !== null) return previous;\n const lockKey = `state:${request.rallyId}:${request.userId}`;\n const locked = await this.#persistence.acquireLock(lockKey, this.#options.lockTtlMs ?? 5000);\n if (!locked)\n return { ok: false, code: \"CONFLICT\", message: \"The user state is being updated.\" };\n const now = request.now ?? this.#options.now?.() ?? new Date().toISOString();\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,\n now,\n );\n const current =\n (await this.#persistence.getUserState(request.rallyId, request.userId)) ??\n initialState(this.#config, now);\n if (current.records.some((record) => record.stampId === request.spotId))\n return this.#remember(\n key,\n { ok: false, code: \"ALREADY_CLAIMED\", message: \"Spot was already claimed.\" },\n request,\n now,\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,\n now,\n );\n if (\n !spot.conditions.every((condition) =>\n matchesCondition(condition, request.context, this.#options.customValidators),\n )\n )\n return this.#remember(\n key,\n { ok: false, code: \"INVALID_PROOF\", message: \"Verification failed.\" },\n request,\n now,\n );\n const state: UserRallyState = {\n ...current,\n records: [...current.records, { stampId: request.spotId, acquiredAt: now }],\n updatedAt: now,\n };\n await this.#persistence.saveUserState(request.rallyId, request.userId, state);\n await this.#persistence.recordAuditLog(audit(request, \"SUCCESS\", now));\n return this.#remember(key, { ok: true, state }, request, now);\n } finally {\n await this.#persistence.releaseLock(lockKey);\n }\n }\n\n async sync(rallyId: string, userId: string): Promise<UserRallyState> {\n const now = this.#options.now?.() ?? new Date().toISOString();\n return (\n (await this.#persistence.getUserState(rallyId, userId)) ?? initialState(this.#config, now)\n );\n }\n\n async #remember(\n key: string,\n result: UniversalCheckInResult,\n request: UniversalCheckInRequest,\n now: string,\n ): Promise<UniversalCheckInResult> {\n await this.#persistence.recordAuditLog(\n audit(request, \"REJECTED\", now, result.ok ? undefined : result.code),\n );\n await this.#persistence.saveIdempotentResult(\n key,\n result,\n this.#options.idempotencyTtlMs ?? 86_400_000,\n );\n return result;\n }\n}\n\nexport type { StampRallyState };\n","import {\n consumeReward,\n createSecureToken,\n processStamp,\n type RallyConfig,\n type RewardConsumeError,\n reconcileRewardStates,\n type SecureTokenSecretKey,\n type StampError,\n type StampRallyState,\n type StampRecord,\n type VerificationContext,\n} from \"@stamprally/core\";\n\nexport interface UserRallyState extends StampRallyState {\n readonly userId?: string;\n}\n\nexport interface RallyAuditLog {\n readonly id: string;\n readonly timestamp: string;\n readonly rallyId: string;\n readonly userId: string;\n readonly action: \"CHECK_IN\" | \"CLAIM_REWARD\";\n readonly resourceId: string;\n readonly status: \"SUCCESS\" | \"REJECTED\";\n readonly idempotencyKey: string;\n readonly proofData?: unknown;\n readonly metadata?: Readonly<Record<string, unknown>>;\n}\n\nexport interface ServerStorageAdapter {\n getRewardStock(rewardId: string): Promise<number | null>;\n decrementRewardStock(rewardId: string): Promise<boolean>;\n getUserClaims(userId: string, rewardId: string): Promise<number>;\n recordAuditLog(log: RallyAuditLog): Promise<void>;\n saveUserState(userId: string, state: UserRallyState): Promise<void>;\n getUserState(userId: string): Promise<UserRallyState | null>;\n}\n\nexport interface AdminRallyConfig extends RallyConfig {\n readonly secretKey: SecureTokenSecretKey;\n readonly proofTtlSeconds?: number;\n readonly authenticate?: (request: Request) => Promise<string | null> | string | null;\n}\n\nexport interface CheckInRequest {\n readonly userId: string;\n readonly spotId: string;\n readonly claimMethod: string;\n readonly proofData?: unknown;\n readonly idempotencyKey: string;\n}\n\nexport interface ClaimRewardRequest {\n readonly userId: string;\n readonly rewardId: string;\n readonly staffPasscode?: string;\n readonly idempotencyKey: string;\n readonly staffId?: string;\n}\n\nexport interface SyncRequest {\n readonly userId: string;\n readonly queue?: ReadonlyArray<SyncCheckInOperation>;\n readonly operations?: ReadonlyArray<SyncCheckInOperation>;\n}\n\nexport interface SyncCheckInOperation {\n readonly userId?: string;\n readonly spotId?: string;\n readonly stampId?: string;\n readonly claimMethod?: string;\n readonly proofData?: unknown;\n readonly context?: VerificationContext;\n readonly idempotencyKey: string;\n}\n\nexport interface StampClaimProof {\n readonly token: string;\n readonly rallyId: string;\n readonly userId: string;\n readonly spotId: string;\n readonly acquiredAt: string;\n}\n\nexport interface InMemoryServerStorageOptions {\n readonly stocks?: Readonly<Record<string, number>>;\n}\n\nexport class InMemoryServerStorage implements ServerStorageAdapter {\n readonly #states = new Map<string, UserRallyState>();\n readonly #stocks: Map<string, number>;\n readonly #claims = new Map<string, number>();\n readonly #auditLogs: RallyAuditLog[] = [];\n\n constructor(options: InMemoryServerStorageOptions = {}) {\n this.#stocks = new Map(Object.entries(options.stocks ?? {}));\n }\n\n async getRewardStock(rewardId: string): Promise<number | null> {\n return this.#stocks.get(rewardId) ?? null;\n }\n\n async decrementRewardStock(rewardId: string): Promise<boolean> {\n const stock = this.#stocks.get(rewardId);\n if (stock === undefined) return true;\n if (stock <= 0) return false;\n this.#stocks.set(rewardId, stock - 1);\n return true;\n }\n\n async getUserClaims(userId: string, rewardId: string): Promise<number> {\n return this.#claims.get(`${userId}:${rewardId}`) ?? 0;\n }\n\n async recordAuditLog(log: RallyAuditLog): Promise<void> {\n this.#auditLogs.push({ ...log });\n }\n\n async saveUserState(userId: string, state: UserRallyState): Promise<void> {\n this.#states.set(userId, cloneUserState(state));\n }\n\n async getUserState(userId: string): Promise<UserRallyState | null> {\n const state = this.#states.get(userId);\n return state === undefined ? null : cloneUserState(state);\n }\n\n getAuditLogs(): ReadonlyArray<RallyAuditLog> {\n return this.#auditLogs.map((log) => ({ ...log }));\n }\n\n recordClaim(userId: string, rewardId: string): void {\n const key = `${userId}:${rewardId}`;\n this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);\n }\n}\n\nfunction cloneUserState(state: UserRallyState): UserRallyState {\n return {\n ...state,\n records: state.records.map((record) => ({\n ...record,\n ...(record.metadata === undefined ? {} : { metadata: { ...record.metadata } }),\n })),\n ...(state.rewards === undefined\n ? {}\n : { rewards: state.rewards.map((reward) => ({ ...reward })) }),\n };\n}\n\nfunction jsonResponse(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}\n\nfunction errorResponse(code: string, message: string, status: number): Response {\n return jsonResponse({ ok: false, error: { code, message } }, status);\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction contextForClaim(method: string, proofData: unknown): VerificationContext {\n if (method === \"token\" || method === \"qr\" || method === \"passcode\") {\n return {\n type: \"token\",\n token:\n isObject(proofData) && typeof proofData.token === \"string\"\n ? proofData.token\n : String(proofData ?? \"\"),\n };\n }\n if (method === \"geo\" || method === \"geolocation\") {\n const value = isObject(proofData) ? proofData : {};\n return {\n type: \"geo\",\n currentLatitude:\n typeof value.latitude === \"number\"\n ? value.latitude\n : typeof value.currentLatitude === \"number\"\n ? value.currentLatitude\n : Number.NaN,\n currentLongitude:\n typeof value.longitude === \"number\"\n ? value.longitude\n : typeof value.currentLongitude === \"number\"\n ? value.currentLongitude\n : Number.NaN,\n };\n }\n return { type: \"instant\" };\n}\n\nfunction now(): string {\n return new Date().toISOString();\n}\n\nfunction hasText(value: unknown): value is string {\n return typeof value === \"string\" && value.trim() !== \"\";\n}\n\nfunction safeProofData(value: unknown): unknown {\n return isObject(value) && typeof value.token === \"string\" ? { type: \"token\" } : value;\n}\n\nfunction proofFromContext(context: VerificationContext | undefined): unknown {\n if (context?.type === \"token\") return { token: context.token };\n if (context?.type === \"geo\") {\n return { latitude: context.currentLatitude, longitude: context.currentLongitude };\n }\n return undefined;\n}\n\nfunction id(prefix: string): string {\n return `${prefix}-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;\n}\n\nexport class StampRallyServer {\n readonly #config: AdminRallyConfig;\n readonly #storage: ServerStorageAdapter;\n readonly #idempotent = new Map<string, Response>();\n readonly #claims = new Map<string, number>();\n #queue: Promise<unknown> = Promise.resolve();\n\n constructor(config: AdminRallyConfig, storage: ServerStorageAdapter) {\n this.#config = config;\n this.#storage = storage;\n }\n\n handle(request: Request): Promise<Response> {\n const path = new URL(request.url).pathname;\n if (request.method !== \"POST\")\n return Promise.resolve(errorResponse(\"METHOD_NOT_ALLOWED\", \"POST is required.\", 405));\n if (path.endsWith(\"/check-in\")) return this.verifyCheckIn(request);\n if (path.endsWith(\"/claim-reward\")) return this.claimReward(request);\n if (path.endsWith(\"/sync\")) return this.syncProgress(request);\n return Promise.resolve(errorResponse(\"NOT_FOUND\", \"Route not found.\", 404));\n }\n\n verifyCheckIn(request: Request): Promise<Response> {\n return this.#enqueue(() => this.#verifyCheckIn(request));\n }\n\n claimReward(request: Request): Promise<Response> {\n return this.#enqueue(() => this.#claimReward(request));\n }\n\n syncProgress(request: Request): Promise<Response> {\n return this.#enqueue(() => this.#syncProgress(request));\n }\n\n #enqueue<T>(operation: () => Promise<T>): Promise<T> {\n const next = this.#queue.then(operation, operation);\n this.#queue = next.then(\n () => undefined,\n () => undefined,\n );\n return next;\n }\n\n async #authenticate(request: Request): Promise<string | null> {\n return this.#config.authenticate === undefined ? null : this.#config.authenticate(request);\n }\n\n async #parse<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\n async #verifyCheckIn(request: Request): Promise<Response> {\n const authenticatedUser = await this.#authenticate(request);\n const body = await this.#parse<CheckInRequest>(request);\n const userId = body?.userId ?? authenticatedUser;\n if (\n userId === null ||\n userId === undefined ||\n body === null ||\n !hasText(userId) ||\n !hasText(body.spotId) ||\n !hasText(body.claimMethod) ||\n !hasText(body.idempotencyKey)\n ) {\n return errorResponse(\n \"INVALID_REQUEST\",\n \"userId, spotId, and idempotencyKey are required.\",\n 400,\n );\n }\n if (authenticatedUser !== null && authenticatedUser !== userId)\n return errorResponse(\"UNAUTHORIZED\", \"User identity does not match authentication.\", 401);\n const key = `check-in:${userId}:${body.idempotencyKey}`;\n const previous = this.#idempotent.get(key);\n if (previous !== undefined) return previous.clone();\n const timestamp = now();\n const current =\n (await this.#storage.getUserState(userId)) ?? emptyState(this.#config, timestamp);\n const result = processStamp(\n current,\n this.#config,\n body.spotId,\n contextForClaim(body.claimMethod, body.proofData),\n timestamp,\n );\n if (!result.ok) {\n await this.#audit(\n userId,\n \"CHECK_IN\",\n body.spotId,\n body.idempotencyKey,\n \"REJECTED\",\n safeProofData(body.proofData),\n result.error,\n );\n return this.#remember(key, jsonResponse({ ok: false, error: result.error }, 422));\n }\n const ttl = this.#config.proofTtlSeconds ?? 3600;\n const token = await createSecureToken(\n {\n type: \"stamp_claim\",\n rallyId: this.#config.id,\n userId,\n spotId: body.spotId,\n acquiredAt: timestamp,\n exp: Math.floor(Date.now() / 1000) + ttl,\n },\n this.#config.secretKey,\n { encrypt: true },\n );\n await this.#storage.saveUserState(userId, result.value.nextState);\n await this.#audit(\n userId,\n \"CHECK_IN\",\n body.spotId,\n body.idempotencyKey,\n \"SUCCESS\",\n safeProofData(body.proofData),\n );\n return this.#remember(\n key,\n jsonResponse({\n ok: true,\n state: result.value.nextState,\n proof: {\n token,\n rallyId: this.#config.id,\n userId,\n spotId: body.spotId,\n acquiredAt: timestamp,\n },\n }),\n );\n }\n\n async #claimReward(request: Request): Promise<Response> {\n const authenticatedUser = await this.#authenticate(request);\n const body = await this.#parse<ClaimRewardRequest>(request);\n const userId = body?.userId ?? authenticatedUser;\n if (\n userId === null ||\n userId === undefined ||\n body === null ||\n !hasText(userId) ||\n !hasText(body.rewardId) ||\n !hasText(body.idempotencyKey)\n )\n return errorResponse(\n \"INVALID_REQUEST\",\n \"userId, rewardId, and idempotencyKey are required.\",\n 400,\n );\n if (authenticatedUser !== null && authenticatedUser !== userId)\n return errorResponse(\"UNAUTHORIZED\", \"User identity does not match authentication.\", 401);\n const key = `claim-reward:${userId}:${body.rewardId}:${body.idempotencyKey}`;\n const previous = this.#idempotent.get(key);\n if (previous !== undefined) return previous.clone();\n const reward = this.#config.rewards?.find((item) => item.id === body.rewardId);\n if (reward === undefined) {\n await this.#audit(userId, \"CLAIM_REWARD\", body.rewardId, body.idempotencyKey, \"REJECTED\");\n return this.#remember(key, errorResponse(\"REWARD_NOT_FOUND\", \"Reward was not found.\", 404));\n }\n const timestamp = now();\n const current =\n (await this.#storage.getUserState(userId)) ?? emptyState(this.#config, timestamp);\n const rewardState = current.rewards?.find((item) => item.rewardId === reward.id);\n if (rewardState === undefined) {\n await this.#audit(userId, \"CLAIM_REWARD\", reward.id, body.idempotencyKey, \"REJECTED\");\n return this.#remember(key, errorResponse(\"NOT_AVAILABLE\", \"Reward is not available.\", 422));\n }\n const userClaims = Math.max(\n await this.#storage.getUserClaims(userId, reward.id),\n this.#claims.get(`${userId}:${reward.id}`) ?? 0,\n );\n const stock = await this.#storage.getRewardStock(reward.id);\n const userLimit = reward.userClaimLimit ?? reward.limitPerUser;\n const canReclaimServerReward =\n reward.redemptionMethod === \"server_claim\" &&\n (userLimit === undefined || userClaims < userLimit) &&\n (stock === null || stock > 0);\n const claimableState =\n canReclaimServerReward && rewardState.status === \"CONSUMED\"\n ? { ...rewardState, status: \"AVAILABLE\" as const }\n : rewardState;\n const local = consumeReward({\n reward,\n currentState: claimableState,\n now: timestamp,\n ...(body.staffPasscode === undefined ? {} : { inputPasscode: body.staffPasscode }),\n ...(body.staffId === undefined ? {} : { staffId: body.staffId }),\n userId,\n userRedemptionCount: userClaims,\n });\n if (!local.ok)\n return this.#remember(\n key,\n await this.#rewardError(userId, reward.id, body.idempotencyKey, local.error),\n );\n if (stock !== null && !(await this.#storage.decrementRewardStock(reward.id)))\n return this.#remember(\n key,\n await this.#rewardError(userId, reward.id, body.idempotencyKey, {\n code: \"OUT_OF_STOCK\",\n rewardId: reward.id,\n }),\n );\n const nextState: StampRallyState = {\n ...current,\n rewards: (current.rewards ?? []).map((item) =>\n item.rewardId === reward.id ? local.value : item,\n ),\n updatedAt: timestamp,\n };\n await this.#storage.saveUserState(userId, nextState);\n const claimKey = `${userId}:${reward.id}`;\n this.#claims.set(claimKey, userClaims + 1);\n if (this.#storage instanceof InMemoryServerStorage)\n this.#storage.recordClaim(userId, reward.id);\n await this.#audit(userId, \"CLAIM_REWARD\", reward.id, body.idempotencyKey, \"SUCCESS\", {\n staffId: body.staffId,\n });\n return this.#remember(\n key,\n jsonResponse({\n ok: true,\n state: nextState,\n claimTicketNumber: local.value.claimTicketNumber,\n }),\n );\n }\n\n async #rewardError(\n userId: string,\n rewardId: string,\n key: string,\n error: RewardConsumeError,\n ): Promise<Response> {\n await this.#audit(userId, \"CLAIM_REWARD\", rewardId, key, \"REJECTED\", undefined, error);\n return jsonResponse({ ok: false, error }, 422);\n }\n\n async #syncProgress(request: Request): Promise<Response> {\n const authenticatedUser = await this.#authenticate(request);\n const body = await this.#parse<SyncRequest>(request);\n if (body === null || body.userId === undefined)\n return errorResponse(\"INVALID_REQUEST\", \"userId is required.\", 400);\n if (authenticatedUser !== null && authenticatedUser !== body.userId)\n return errorResponse(\"UNAUTHORIZED\", \"User identity does not match authentication.\", 401);\n const queue = body.queue ?? body.operations ?? [];\n for (const operation of queue) {\n const userId = operation.userId ?? body.userId;\n const spotId = operation.spotId ?? operation.stampId;\n const claimMethod = operation.claimMethod ?? operation.context?.type;\n if (!hasText(userId) || !hasText(spotId) || !hasText(claimMethod)) continue;\n const synthetic = new Request(new URL(\"/api/check-in\", request.url), {\n method: \"POST\",\n body: JSON.stringify({\n userId,\n spotId,\n claimMethod,\n proofData: operation.proofData ?? proofFromContext(operation.context),\n idempotencyKey: operation.idempotencyKey,\n }),\n headers: { \"content-type\": \"application/json\" },\n });\n await this.#verifyCheckIn(synthetic);\n }\n const timestamp = now();\n const state =\n (await this.#storage.getUserState(body.userId)) ?? emptyState(this.#config, timestamp);\n await this.#storage.saveUserState(body.userId, state);\n return jsonResponse({ ok: true, state, accepted: queue.length });\n }\n\n async #audit(\n userId: string,\n action: RallyAuditLog[\"action\"],\n resourceId: string,\n idempotencyKey: string,\n status: RallyAuditLog[\"status\"],\n proofData?: unknown,\n error?: unknown,\n ): Promise<void> {\n await this.#storage.recordAuditLog({\n id: id(\"audit\"),\n timestamp: now(),\n rallyId: this.#config.id,\n userId,\n action,\n resourceId,\n status,\n idempotencyKey,\n ...(proofData === undefined ? {} : { proofData }),\n ...(error === undefined\n ? {}\n : {\n metadata: {\n errorCode: isObject(error) && typeof error.code === \"string\" ? error.code : \"UNKNOWN\",\n },\n }),\n });\n }\n\n #remember(key: string, response: Response): Response {\n this.#idempotent.set(key, response.clone());\n return response;\n }\n}\n\nfunction emptyState(config: RallyConfig, timestamp: string): UserRallyState {\n return {\n rallyId: config.id,\n records: [],\n ...(config.rewards === undefined\n ? {}\n : { rewards: reconcileRewardStates(config.rewards, [], 0, timestamp) }),\n updatedAt: timestamp,\n };\n}\n\nexport type { InMemoryServerPersistenceOptions, ServerPersistenceAdapter } from \"./persistence.js\";\nexport { InMemoryServerPersistenceAdapter } from \"./persistence.js\";\nexport type {\n UniversalCheckInRequest,\n UniversalCheckInResult,\n UniversalRallyServerOptions,\n UniversalVerificationContext,\n} from \"./universalServer.js\";\nexport { UniversalRallyServer } from \"./universalServer.js\";\nexport type { StampError, StampRecord };\n"]}
package/dist/index.d.cts CHANGED
@@ -1,6 +1,85 @@
1
- import { RallyConfig, SecureTokenSecretKey, StampRallyState, VerificationContext } from '@stamprally/core';
1
+ import { AdminRallyConfig as AdminRallyConfig$1, VerificationCondition, RallyConfig, SecureTokenSecretKey, StampRallyState, VerificationContext } from '@stamprally/core';
2
2
  export { StampError, StampRecord } from '@stamprally/core';
3
3
 
4
+ /** Persistence contract for implementations backed by Redis, SQL, or another RDB. */
5
+ interface ServerPersistenceAdapter {
6
+ acquireLock(lockKey: string, ttlMs: number): Promise<boolean>;
7
+ releaseLock(lockKey: string): Promise<void>;
8
+ decrementRewardStock(rewardId: string): Promise<{
9
+ success: boolean;
10
+ remainingStock: number;
11
+ }>;
12
+ getIdempotentResult<T>(idempotencyKey: string): Promise<T | null>;
13
+ saveIdempotentResult<T>(idempotencyKey: string, result: T, ttlMs: number): Promise<void>;
14
+ getUserState(rallyId: string, userId: string): Promise<UserRallyState | null>;
15
+ saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void>;
16
+ recordAuditLog(log: RallyAuditLog): Promise<void>;
17
+ }
18
+ interface InMemoryServerPersistenceOptions {
19
+ readonly stocks?: Readonly<Record<string, number>>;
20
+ }
21
+ /** Deterministic adapter for tests and small single-process deployments. */
22
+ declare class InMemoryServerPersistenceAdapter implements ServerPersistenceAdapter {
23
+ #private;
24
+ constructor(options?: InMemoryServerPersistenceOptions);
25
+ acquireLock(lockKey: string, ttlMs: number): Promise<boolean>;
26
+ releaseLock(lockKey: string): Promise<void>;
27
+ decrementRewardStock(rewardId: string): Promise<{
28
+ success: boolean;
29
+ remainingStock: number;
30
+ }>;
31
+ getIdempotentResult<T>(idempotencyKey: string): Promise<T | null>;
32
+ saveIdempotentResult<T>(idempotencyKey: string, result: T, ttlMs: number): Promise<void>;
33
+ getUserState(rallyId: string, userId: string): Promise<UserRallyState | null>;
34
+ saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void>;
35
+ recordAuditLog(log: RallyAuditLog): Promise<void>;
36
+ getAuditLogs(): ReadonlyArray<RallyAuditLog>;
37
+ }
38
+
39
+ type UniversalVerificationContext = {
40
+ readonly type: "qr";
41
+ readonly token: string;
42
+ } | {
43
+ readonly type: "passcode";
44
+ readonly code: string;
45
+ } | {
46
+ readonly type: "gps";
47
+ readonly latitude: number;
48
+ readonly longitude: number;
49
+ } | {
50
+ readonly type: "custom";
51
+ readonly value: unknown;
52
+ };
53
+ interface UniversalCheckInRequest {
54
+ readonly rallyId: string;
55
+ readonly userId: string;
56
+ readonly spotId: string;
57
+ readonly context: UniversalVerificationContext;
58
+ readonly idempotencyKey: string;
59
+ readonly now?: string;
60
+ }
61
+ type UniversalCheckInResult = {
62
+ readonly ok: true;
63
+ readonly state: UserRallyState;
64
+ } | {
65
+ readonly ok: false;
66
+ readonly code: string;
67
+ readonly message: string;
68
+ };
69
+ interface UniversalRallyServerOptions {
70
+ readonly idempotencyTtlMs?: number;
71
+ readonly lockTtlMs?: number;
72
+ readonly customValidators?: Readonly<Record<string, (value: unknown, condition: VerificationCondition) => boolean>>;
73
+ readonly now?: () => string;
74
+ }
75
+ /** Atomic, adapter-backed check-in service for the universal model. */
76
+ declare class UniversalRallyServer {
77
+ #private;
78
+ constructor(config: AdminRallyConfig$1, persistence: ServerPersistenceAdapter, options?: UniversalRallyServerOptions);
79
+ checkIn(request: UniversalCheckInRequest): Promise<UniversalCheckInResult>;
80
+ sync(rallyId: string, userId: string): Promise<UserRallyState>;
81
+ }
82
+
4
83
  interface UserRallyState extends StampRallyState {
5
84
  readonly userId?: string;
6
85
  }
@@ -88,4 +167,4 @@ declare class StampRallyServer {
88
167
  syncProgress(request: Request): Promise<Response>;
89
168
  }
90
169
 
91
- export { type AdminRallyConfig, type CheckInRequest, type ClaimRewardRequest, InMemoryServerStorage, type InMemoryServerStorageOptions, type RallyAuditLog, type ServerStorageAdapter, type StampClaimProof, StampRallyServer, type SyncCheckInOperation, type SyncRequest, type UserRallyState };
170
+ export { type AdminRallyConfig, type CheckInRequest, type ClaimRewardRequest, InMemoryServerPersistenceAdapter, type InMemoryServerPersistenceOptions, InMemoryServerStorage, type InMemoryServerStorageOptions, type RallyAuditLog, type ServerPersistenceAdapter, type ServerStorageAdapter, type StampClaimProof, StampRallyServer, type SyncCheckInOperation, type SyncRequest, type UniversalCheckInRequest, type UniversalCheckInResult, UniversalRallyServer, type UniversalRallyServerOptions, type UniversalVerificationContext, type UserRallyState };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,85 @@
1
- import { RallyConfig, SecureTokenSecretKey, StampRallyState, VerificationContext } from '@stamprally/core';
1
+ import { AdminRallyConfig as AdminRallyConfig$1, VerificationCondition, RallyConfig, SecureTokenSecretKey, StampRallyState, VerificationContext } from '@stamprally/core';
2
2
  export { StampError, StampRecord } from '@stamprally/core';
3
3
 
4
+ /** Persistence contract for implementations backed by Redis, SQL, or another RDB. */
5
+ interface ServerPersistenceAdapter {
6
+ acquireLock(lockKey: string, ttlMs: number): Promise<boolean>;
7
+ releaseLock(lockKey: string): Promise<void>;
8
+ decrementRewardStock(rewardId: string): Promise<{
9
+ success: boolean;
10
+ remainingStock: number;
11
+ }>;
12
+ getIdempotentResult<T>(idempotencyKey: string): Promise<T | null>;
13
+ saveIdempotentResult<T>(idempotencyKey: string, result: T, ttlMs: number): Promise<void>;
14
+ getUserState(rallyId: string, userId: string): Promise<UserRallyState | null>;
15
+ saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void>;
16
+ recordAuditLog(log: RallyAuditLog): Promise<void>;
17
+ }
18
+ interface InMemoryServerPersistenceOptions {
19
+ readonly stocks?: Readonly<Record<string, number>>;
20
+ }
21
+ /** Deterministic adapter for tests and small single-process deployments. */
22
+ declare class InMemoryServerPersistenceAdapter implements ServerPersistenceAdapter {
23
+ #private;
24
+ constructor(options?: InMemoryServerPersistenceOptions);
25
+ acquireLock(lockKey: string, ttlMs: number): Promise<boolean>;
26
+ releaseLock(lockKey: string): Promise<void>;
27
+ decrementRewardStock(rewardId: string): Promise<{
28
+ success: boolean;
29
+ remainingStock: number;
30
+ }>;
31
+ getIdempotentResult<T>(idempotencyKey: string): Promise<T | null>;
32
+ saveIdempotentResult<T>(idempotencyKey: string, result: T, ttlMs: number): Promise<void>;
33
+ getUserState(rallyId: string, userId: string): Promise<UserRallyState | null>;
34
+ saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void>;
35
+ recordAuditLog(log: RallyAuditLog): Promise<void>;
36
+ getAuditLogs(): ReadonlyArray<RallyAuditLog>;
37
+ }
38
+
39
+ type UniversalVerificationContext = {
40
+ readonly type: "qr";
41
+ readonly token: string;
42
+ } | {
43
+ readonly type: "passcode";
44
+ readonly code: string;
45
+ } | {
46
+ readonly type: "gps";
47
+ readonly latitude: number;
48
+ readonly longitude: number;
49
+ } | {
50
+ readonly type: "custom";
51
+ readonly value: unknown;
52
+ };
53
+ interface UniversalCheckInRequest {
54
+ readonly rallyId: string;
55
+ readonly userId: string;
56
+ readonly spotId: string;
57
+ readonly context: UniversalVerificationContext;
58
+ readonly idempotencyKey: string;
59
+ readonly now?: string;
60
+ }
61
+ type UniversalCheckInResult = {
62
+ readonly ok: true;
63
+ readonly state: UserRallyState;
64
+ } | {
65
+ readonly ok: false;
66
+ readonly code: string;
67
+ readonly message: string;
68
+ };
69
+ interface UniversalRallyServerOptions {
70
+ readonly idempotencyTtlMs?: number;
71
+ readonly lockTtlMs?: number;
72
+ readonly customValidators?: Readonly<Record<string, (value: unknown, condition: VerificationCondition) => boolean>>;
73
+ readonly now?: () => string;
74
+ }
75
+ /** Atomic, adapter-backed check-in service for the universal model. */
76
+ declare class UniversalRallyServer {
77
+ #private;
78
+ constructor(config: AdminRallyConfig$1, persistence: ServerPersistenceAdapter, options?: UniversalRallyServerOptions);
79
+ checkIn(request: UniversalCheckInRequest): Promise<UniversalCheckInResult>;
80
+ sync(rallyId: string, userId: string): Promise<UserRallyState>;
81
+ }
82
+
4
83
  interface UserRallyState extends StampRallyState {
5
84
  readonly userId?: string;
6
85
  }
@@ -88,4 +167,4 @@ declare class StampRallyServer {
88
167
  syncProgress(request: Request): Promise<Response>;
89
168
  }
90
169
 
91
- export { type AdminRallyConfig, type CheckInRequest, type ClaimRewardRequest, InMemoryServerStorage, type InMemoryServerStorageOptions, type RallyAuditLog, type ServerStorageAdapter, type StampClaimProof, StampRallyServer, type SyncCheckInOperation, type SyncRequest, type UserRallyState };
170
+ export { type AdminRallyConfig, type CheckInRequest, type ClaimRewardRequest, InMemoryServerPersistenceAdapter, type InMemoryServerPersistenceOptions, InMemoryServerStorage, type InMemoryServerStorageOptions, type RallyAuditLog, type ServerPersistenceAdapter, type ServerStorageAdapter, type StampClaimProof, StampRallyServer, type SyncCheckInOperation, type SyncRequest, type UniversalCheckInRequest, type UniversalCheckInResult, UniversalRallyServer, type UniversalRallyServerOptions, type UniversalVerificationContext, type UserRallyState };
package/dist/index.js CHANGED
@@ -1,5 +1,196 @@
1
1
  import { processStamp, createSecureToken, consumeReward, reconcileRewardStates } from '@stamprally/core';
2
2
 
3
+ // src/index.ts
4
+
5
+ // src/persistence.ts
6
+ var InMemoryServerPersistenceAdapter = class {
7
+ #locks = /* @__PURE__ */ new Map();
8
+ #idempotent = /* @__PURE__ */ new Map();
9
+ #states = /* @__PURE__ */ new Map();
10
+ #stocks;
11
+ #auditLogs = [];
12
+ constructor(options = {}) {
13
+ this.#stocks = new Map(Object.entries(options.stocks ?? {}));
14
+ }
15
+ async acquireLock(lockKey, ttlMs) {
16
+ const now2 = Date.now();
17
+ const expiresAt = this.#locks.get(lockKey);
18
+ if (expiresAt !== void 0 && expiresAt > now2) return false;
19
+ this.#locks.set(lockKey, now2 + Math.max(1, ttlMs));
20
+ return true;
21
+ }
22
+ async releaseLock(lockKey) {
23
+ this.#locks.delete(lockKey);
24
+ }
25
+ async decrementRewardStock(rewardId) {
26
+ const stock = this.#stocks.get(rewardId);
27
+ if (stock === void 0) return { success: true, remainingStock: Number.POSITIVE_INFINITY };
28
+ if (stock <= 0) return { success: false, remainingStock: 0 };
29
+ const remainingStock = stock - 1;
30
+ this.#stocks.set(rewardId, remainingStock);
31
+ return { success: true, remainingStock };
32
+ }
33
+ async getIdempotentResult(idempotencyKey) {
34
+ const entry = this.#idempotent.get(idempotencyKey);
35
+ if (entry === void 0) return null;
36
+ if (entry.expiresAt <= Date.now()) {
37
+ this.#idempotent.delete(idempotencyKey);
38
+ return null;
39
+ }
40
+ return structuredClone(entry.value);
41
+ }
42
+ async saveIdempotentResult(idempotencyKey, result, ttlMs) {
43
+ this.#idempotent.set(idempotencyKey, {
44
+ value: structuredClone(result),
45
+ expiresAt: Date.now() + Math.max(1, ttlMs)
46
+ });
47
+ }
48
+ async getUserState(rallyId, userId) {
49
+ const state = this.#states.get(`${rallyId}:${userId}`);
50
+ return state === void 0 ? null : structuredClone(state);
51
+ }
52
+ async saveUserState(rallyId, userId, state) {
53
+ this.#states.set(`${rallyId}:${userId}`, structuredClone(state));
54
+ }
55
+ async recordAuditLog(log) {
56
+ this.#auditLogs.push(structuredClone(log));
57
+ }
58
+ getAuditLogs() {
59
+ return structuredClone(this.#auditLogs);
60
+ }
61
+ };
62
+
63
+ // src/universalServer.ts
64
+ function distanceMeters(aLat, aLon, bLat, bLon) {
65
+ const radians = (degrees) => degrees * Math.PI / 180;
66
+ const dLat = radians(bLat - aLat);
67
+ const dLon = radians(bLon - aLon);
68
+ const latA = radians(aLat);
69
+ const latB = radians(bLat);
70
+ const value = Math.sin(dLat / 2) ** 2 + Math.cos(latA) * Math.cos(latB) * Math.sin(dLon / 2) ** 2;
71
+ return 6371e3 * 2 * Math.asin(Math.sqrt(Math.min(1, value)));
72
+ }
73
+ function initialState(config, now2) {
74
+ return { rallyId: config.id, records: [], updatedAt: now2 };
75
+ }
76
+ function matchesCondition(condition, context, customValidators) {
77
+ switch (condition.type) {
78
+ case "qr":
79
+ return context.type === "qr" && context.token === condition.secretToken;
80
+ case "passcode":
81
+ return context.type === "passcode" && (condition.caseSensitive === false ? context.code.toLocaleLowerCase() === condition.code.toLocaleLowerCase() : context.code === condition.code);
82
+ case "gps":
83
+ return context.type === "gps" && distanceMeters(
84
+ condition.latitude,
85
+ condition.longitude,
86
+ context.latitude,
87
+ context.longitude
88
+ ) <= condition.radiusMeters;
89
+ case "custom":
90
+ return customValidators?.[condition.validatorName]?.(
91
+ context.type === "custom" ? context.value : void 0,
92
+ condition
93
+ ) ?? false;
94
+ }
95
+ }
96
+ function audit(request, status, now2, errorCode) {
97
+ return {
98
+ id: `audit-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`,
99
+ timestamp: now2,
100
+ rallyId: request.rallyId,
101
+ userId: request.userId,
102
+ action: "CHECK_IN",
103
+ resourceId: request.spotId,
104
+ status,
105
+ idempotencyKey: request.idempotencyKey,
106
+ ...errorCode === void 0 ? {} : { metadata: { errorCode } }
107
+ };
108
+ }
109
+ var UniversalRallyServer = class {
110
+ #config;
111
+ #persistence;
112
+ #options;
113
+ constructor(config, persistence, options = {}) {
114
+ this.#config = config;
115
+ this.#persistence = persistence;
116
+ this.#options = options;
117
+ }
118
+ async checkIn(request) {
119
+ const key = `check-in:${request.rallyId}:${request.userId}:${request.idempotencyKey}`;
120
+ const previous = await this.#persistence.getIdempotentResult(key);
121
+ if (previous !== null) return previous;
122
+ const lockKey = `state:${request.rallyId}:${request.userId}`;
123
+ const locked = await this.#persistence.acquireLock(lockKey, this.#options.lockTtlMs ?? 5e3);
124
+ if (!locked)
125
+ return { ok: false, code: "CONFLICT", message: "The user state is being updated." };
126
+ const now2 = request.now ?? this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
127
+ try {
128
+ const spot = this.#config.spots.find((item) => item.id === request.spotId);
129
+ if (spot === void 0)
130
+ return this.#remember(
131
+ key,
132
+ { ok: false, code: "SPOT_NOT_FOUND", message: "Spot was not found." },
133
+ request,
134
+ now2
135
+ );
136
+ const current = await this.#persistence.getUserState(request.rallyId, request.userId) ?? initialState(this.#config, now2);
137
+ if (current.records.some((record) => record.stampId === request.spotId))
138
+ return this.#remember(
139
+ key,
140
+ { ok: false, code: "ALREADY_CLAIMED", message: "Spot was already claimed." },
141
+ request,
142
+ now2
143
+ );
144
+ const acquired = new Set(current.records.map((record) => record.stampId));
145
+ if (spot.prerequisites?.some((id2) => !acquired.has(id2)))
146
+ return this.#remember(
147
+ key,
148
+ {
149
+ ok: false,
150
+ code: "PREREQUISITES_NOT_MET",
151
+ message: "Prerequisite spots are not complete."
152
+ },
153
+ request,
154
+ now2
155
+ );
156
+ if (!spot.conditions.every(
157
+ (condition) => matchesCondition(condition, request.context, this.#options.customValidators)
158
+ ))
159
+ return this.#remember(
160
+ key,
161
+ { ok: false, code: "INVALID_PROOF", message: "Verification failed." },
162
+ request,
163
+ now2
164
+ );
165
+ const state = {
166
+ ...current,
167
+ records: [...current.records, { stampId: request.spotId, acquiredAt: now2 }],
168
+ updatedAt: now2
169
+ };
170
+ await this.#persistence.saveUserState(request.rallyId, request.userId, state);
171
+ await this.#persistence.recordAuditLog(audit(request, "SUCCESS", now2));
172
+ return this.#remember(key, { ok: true, state }, request, now2);
173
+ } finally {
174
+ await this.#persistence.releaseLock(lockKey);
175
+ }
176
+ }
177
+ async sync(rallyId, userId) {
178
+ const now2 = this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
179
+ return await this.#persistence.getUserState(rallyId, userId) ?? initialState(this.#config, now2);
180
+ }
181
+ async #remember(key, result, request, now2) {
182
+ await this.#persistence.recordAuditLog(
183
+ audit(request, "REJECTED", now2, result.ok ? void 0 : result.code)
184
+ );
185
+ await this.#persistence.saveIdempotentResult(
186
+ key,
187
+ result,
188
+ this.#options.idempotencyTtlMs ?? 864e5
189
+ );
190
+ return result;
191
+ }
192
+ };
193
+
3
194
  // src/index.ts
4
195
  var InMemoryServerStorage = class {
5
196
  #states = /* @__PURE__ */ new Map();
@@ -367,6 +558,6 @@ function emptyState(config, timestamp) {
367
558
  };
368
559
  }
369
560
 
370
- export { InMemoryServerStorage, StampRallyServer };
561
+ export { InMemoryServerPersistenceAdapter, InMemoryServerStorage, StampRallyServer, UniversalRallyServer };
371
562
  //# sourceMappingURL=index.js.map
372
563
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import {\n consumeReward,\n createSecureToken,\n processStamp,\n type RallyConfig,\n type RewardConsumeError,\n reconcileRewardStates,\n type SecureTokenSecretKey,\n type StampError,\n type StampRallyState,\n type StampRecord,\n type VerificationContext,\n} from \"@stamprally/core\";\n\nexport interface UserRallyState extends StampRallyState {\n readonly userId?: string;\n}\n\nexport interface RallyAuditLog {\n readonly id: string;\n readonly timestamp: string;\n readonly rallyId: string;\n readonly userId: string;\n readonly action: \"CHECK_IN\" | \"CLAIM_REWARD\";\n readonly resourceId: string;\n readonly status: \"SUCCESS\" | \"REJECTED\";\n readonly idempotencyKey: string;\n readonly proofData?: unknown;\n readonly metadata?: Readonly<Record<string, unknown>>;\n}\n\nexport interface ServerStorageAdapter {\n getRewardStock(rewardId: string): Promise<number | null>;\n decrementRewardStock(rewardId: string): Promise<boolean>;\n getUserClaims(userId: string, rewardId: string): Promise<number>;\n recordAuditLog(log: RallyAuditLog): Promise<void>;\n saveUserState(userId: string, state: UserRallyState): Promise<void>;\n getUserState(userId: string): Promise<UserRallyState | null>;\n}\n\nexport interface AdminRallyConfig extends RallyConfig {\n readonly secretKey: SecureTokenSecretKey;\n readonly proofTtlSeconds?: number;\n readonly authenticate?: (request: Request) => Promise<string | null> | string | null;\n}\n\nexport interface CheckInRequest {\n readonly userId: string;\n readonly spotId: string;\n readonly claimMethod: string;\n readonly proofData?: unknown;\n readonly idempotencyKey: string;\n}\n\nexport interface ClaimRewardRequest {\n readonly userId: string;\n readonly rewardId: string;\n readonly staffPasscode?: string;\n readonly idempotencyKey: string;\n readonly staffId?: string;\n}\n\nexport interface SyncRequest {\n readonly userId: string;\n readonly queue?: ReadonlyArray<SyncCheckInOperation>;\n readonly operations?: ReadonlyArray<SyncCheckInOperation>;\n}\n\nexport interface SyncCheckInOperation {\n readonly userId?: string;\n readonly spotId?: string;\n readonly stampId?: string;\n readonly claimMethod?: string;\n readonly proofData?: unknown;\n readonly context?: VerificationContext;\n readonly idempotencyKey: string;\n}\n\nexport interface StampClaimProof {\n readonly token: string;\n readonly rallyId: string;\n readonly userId: string;\n readonly spotId: string;\n readonly acquiredAt: string;\n}\n\nexport interface InMemoryServerStorageOptions {\n readonly stocks?: Readonly<Record<string, number>>;\n}\n\nexport class InMemoryServerStorage implements ServerStorageAdapter {\n readonly #states = new Map<string, UserRallyState>();\n readonly #stocks: Map<string, number>;\n readonly #claims = new Map<string, number>();\n readonly #auditLogs: RallyAuditLog[] = [];\n\n constructor(options: InMemoryServerStorageOptions = {}) {\n this.#stocks = new Map(Object.entries(options.stocks ?? {}));\n }\n\n async getRewardStock(rewardId: string): Promise<number | null> {\n return this.#stocks.get(rewardId) ?? null;\n }\n\n async decrementRewardStock(rewardId: string): Promise<boolean> {\n const stock = this.#stocks.get(rewardId);\n if (stock === undefined) return true;\n if (stock <= 0) return false;\n this.#stocks.set(rewardId, stock - 1);\n return true;\n }\n\n async getUserClaims(userId: string, rewardId: string): Promise<number> {\n return this.#claims.get(`${userId}:${rewardId}`) ?? 0;\n }\n\n async recordAuditLog(log: RallyAuditLog): Promise<void> {\n this.#auditLogs.push({ ...log });\n }\n\n async saveUserState(userId: string, state: UserRallyState): Promise<void> {\n this.#states.set(userId, cloneUserState(state));\n }\n\n async getUserState(userId: string): Promise<UserRallyState | null> {\n const state = this.#states.get(userId);\n return state === undefined ? null : cloneUserState(state);\n }\n\n getAuditLogs(): ReadonlyArray<RallyAuditLog> {\n return this.#auditLogs.map((log) => ({ ...log }));\n }\n\n recordClaim(userId: string, rewardId: string): void {\n const key = `${userId}:${rewardId}`;\n this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);\n }\n}\n\nfunction cloneUserState(state: UserRallyState): UserRallyState {\n return {\n ...state,\n records: state.records.map((record) => ({\n ...record,\n ...(record.metadata === undefined ? {} : { metadata: { ...record.metadata } }),\n })),\n ...(state.rewards === undefined\n ? {}\n : { rewards: state.rewards.map((reward) => ({ ...reward })) }),\n };\n}\n\nfunction jsonResponse(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}\n\nfunction errorResponse(code: string, message: string, status: number): Response {\n return jsonResponse({ ok: false, error: { code, message } }, status);\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction contextForClaim(method: string, proofData: unknown): VerificationContext {\n if (method === \"token\" || method === \"qr\" || method === \"passcode\") {\n return {\n type: \"token\",\n token:\n isObject(proofData) && typeof proofData.token === \"string\"\n ? proofData.token\n : String(proofData ?? \"\"),\n };\n }\n if (method === \"geo\" || method === \"geolocation\") {\n const value = isObject(proofData) ? proofData : {};\n return {\n type: \"geo\",\n currentLatitude:\n typeof value.latitude === \"number\"\n ? value.latitude\n : typeof value.currentLatitude === \"number\"\n ? value.currentLatitude\n : Number.NaN,\n currentLongitude:\n typeof value.longitude === \"number\"\n ? value.longitude\n : typeof value.currentLongitude === \"number\"\n ? value.currentLongitude\n : Number.NaN,\n };\n }\n return { type: \"instant\" };\n}\n\nfunction now(): string {\n return new Date().toISOString();\n}\n\nfunction hasText(value: unknown): value is string {\n return typeof value === \"string\" && value.trim() !== \"\";\n}\n\nfunction safeProofData(value: unknown): unknown {\n return isObject(value) && typeof value.token === \"string\" ? { type: \"token\" } : value;\n}\n\nfunction proofFromContext(context: VerificationContext | undefined): unknown {\n if (context?.type === \"token\") return { token: context.token };\n if (context?.type === \"geo\") {\n return { latitude: context.currentLatitude, longitude: context.currentLongitude };\n }\n return undefined;\n}\n\nfunction id(prefix: string): string {\n return `${prefix}-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;\n}\n\nexport class StampRallyServer {\n readonly #config: AdminRallyConfig;\n readonly #storage: ServerStorageAdapter;\n readonly #idempotent = new Map<string, Response>();\n readonly #claims = new Map<string, number>();\n #queue: Promise<unknown> = Promise.resolve();\n\n constructor(config: AdminRallyConfig, storage: ServerStorageAdapter) {\n this.#config = config;\n this.#storage = storage;\n }\n\n handle(request: Request): Promise<Response> {\n const path = new URL(request.url).pathname;\n if (request.method !== \"POST\")\n return Promise.resolve(errorResponse(\"METHOD_NOT_ALLOWED\", \"POST is required.\", 405));\n if (path.endsWith(\"/check-in\")) return this.verifyCheckIn(request);\n if (path.endsWith(\"/claim-reward\")) return this.claimReward(request);\n if (path.endsWith(\"/sync\")) return this.syncProgress(request);\n return Promise.resolve(errorResponse(\"NOT_FOUND\", \"Route not found.\", 404));\n }\n\n verifyCheckIn(request: Request): Promise<Response> {\n return this.#enqueue(() => this.#verifyCheckIn(request));\n }\n\n claimReward(request: Request): Promise<Response> {\n return this.#enqueue(() => this.#claimReward(request));\n }\n\n syncProgress(request: Request): Promise<Response> {\n return this.#enqueue(() => this.#syncProgress(request));\n }\n\n #enqueue<T>(operation: () => Promise<T>): Promise<T> {\n const next = this.#queue.then(operation, operation);\n this.#queue = next.then(\n () => undefined,\n () => undefined,\n );\n return next;\n }\n\n async #authenticate(request: Request): Promise<string | null> {\n return this.#config.authenticate === undefined ? null : this.#config.authenticate(request);\n }\n\n async #parse<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\n async #verifyCheckIn(request: Request): Promise<Response> {\n const authenticatedUser = await this.#authenticate(request);\n const body = await this.#parse<CheckInRequest>(request);\n const userId = body?.userId ?? authenticatedUser;\n if (\n userId === null ||\n userId === undefined ||\n body === null ||\n !hasText(userId) ||\n !hasText(body.spotId) ||\n !hasText(body.claimMethod) ||\n !hasText(body.idempotencyKey)\n ) {\n return errorResponse(\n \"INVALID_REQUEST\",\n \"userId, spotId, and idempotencyKey are required.\",\n 400,\n );\n }\n if (authenticatedUser !== null && authenticatedUser !== userId)\n return errorResponse(\"UNAUTHORIZED\", \"User identity does not match authentication.\", 401);\n const key = `check-in:${userId}:${body.idempotencyKey}`;\n const previous = this.#idempotent.get(key);\n if (previous !== undefined) return previous.clone();\n const timestamp = now();\n const current =\n (await this.#storage.getUserState(userId)) ?? emptyState(this.#config, timestamp);\n const result = processStamp(\n current,\n this.#config,\n body.spotId,\n contextForClaim(body.claimMethod, body.proofData),\n timestamp,\n );\n if (!result.ok) {\n await this.#audit(\n userId,\n \"CHECK_IN\",\n body.spotId,\n body.idempotencyKey,\n \"REJECTED\",\n safeProofData(body.proofData),\n result.error,\n );\n return this.#remember(key, jsonResponse({ ok: false, error: result.error }, 422));\n }\n const ttl = this.#config.proofTtlSeconds ?? 3600;\n const token = await createSecureToken(\n {\n type: \"stamp_claim\",\n rallyId: this.#config.id,\n userId,\n spotId: body.spotId,\n acquiredAt: timestamp,\n exp: Math.floor(Date.now() / 1000) + ttl,\n },\n this.#config.secretKey,\n { encrypt: true },\n );\n await this.#storage.saveUserState(userId, result.value.nextState);\n await this.#audit(\n userId,\n \"CHECK_IN\",\n body.spotId,\n body.idempotencyKey,\n \"SUCCESS\",\n safeProofData(body.proofData),\n );\n return this.#remember(\n key,\n jsonResponse({\n ok: true,\n state: result.value.nextState,\n proof: {\n token,\n rallyId: this.#config.id,\n userId,\n spotId: body.spotId,\n acquiredAt: timestamp,\n },\n }),\n );\n }\n\n async #claimReward(request: Request): Promise<Response> {\n const authenticatedUser = await this.#authenticate(request);\n const body = await this.#parse<ClaimRewardRequest>(request);\n const userId = body?.userId ?? authenticatedUser;\n if (\n userId === null ||\n userId === undefined ||\n body === null ||\n !hasText(userId) ||\n !hasText(body.rewardId) ||\n !hasText(body.idempotencyKey)\n )\n return errorResponse(\n \"INVALID_REQUEST\",\n \"userId, rewardId, and idempotencyKey are required.\",\n 400,\n );\n if (authenticatedUser !== null && authenticatedUser !== userId)\n return errorResponse(\"UNAUTHORIZED\", \"User identity does not match authentication.\", 401);\n const key = `claim-reward:${userId}:${body.rewardId}:${body.idempotencyKey}`;\n const previous = this.#idempotent.get(key);\n if (previous !== undefined) return previous.clone();\n const reward = this.#config.rewards?.find((item) => item.id === body.rewardId);\n if (reward === undefined) {\n await this.#audit(userId, \"CLAIM_REWARD\", body.rewardId, body.idempotencyKey, \"REJECTED\");\n return this.#remember(key, errorResponse(\"REWARD_NOT_FOUND\", \"Reward was not found.\", 404));\n }\n const timestamp = now();\n const current =\n (await this.#storage.getUserState(userId)) ?? emptyState(this.#config, timestamp);\n const rewardState = current.rewards?.find((item) => item.rewardId === reward.id);\n if (rewardState === undefined) {\n await this.#audit(userId, \"CLAIM_REWARD\", reward.id, body.idempotencyKey, \"REJECTED\");\n return this.#remember(key, errorResponse(\"NOT_AVAILABLE\", \"Reward is not available.\", 422));\n }\n const userClaims = Math.max(\n await this.#storage.getUserClaims(userId, reward.id),\n this.#claims.get(`${userId}:${reward.id}`) ?? 0,\n );\n const stock = await this.#storage.getRewardStock(reward.id);\n const userLimit = reward.userClaimLimit ?? reward.limitPerUser;\n const canReclaimServerReward =\n reward.redemptionMethod === \"server_claim\" &&\n (userLimit === undefined || userClaims < userLimit) &&\n (stock === null || stock > 0);\n const claimableState =\n canReclaimServerReward && rewardState.status === \"CONSUMED\"\n ? { ...rewardState, status: \"AVAILABLE\" as const }\n : rewardState;\n const local = consumeReward({\n reward,\n currentState: claimableState,\n now: timestamp,\n ...(body.staffPasscode === undefined ? {} : { inputPasscode: body.staffPasscode }),\n ...(body.staffId === undefined ? {} : { staffId: body.staffId }),\n userId,\n userRedemptionCount: userClaims,\n });\n if (!local.ok)\n return this.#remember(\n key,\n await this.#rewardError(userId, reward.id, body.idempotencyKey, local.error),\n );\n if (stock !== null && !(await this.#storage.decrementRewardStock(reward.id)))\n return this.#remember(\n key,\n await this.#rewardError(userId, reward.id, body.idempotencyKey, {\n code: \"OUT_OF_STOCK\",\n rewardId: reward.id,\n }),\n );\n const nextState: StampRallyState = {\n ...current,\n rewards: (current.rewards ?? []).map((item) =>\n item.rewardId === reward.id ? local.value : item,\n ),\n updatedAt: timestamp,\n };\n await this.#storage.saveUserState(userId, nextState);\n const claimKey = `${userId}:${reward.id}`;\n this.#claims.set(claimKey, userClaims + 1);\n if (this.#storage instanceof InMemoryServerStorage)\n this.#storage.recordClaim(userId, reward.id);\n await this.#audit(userId, \"CLAIM_REWARD\", reward.id, body.idempotencyKey, \"SUCCESS\", {\n staffId: body.staffId,\n });\n return this.#remember(\n key,\n jsonResponse({\n ok: true,\n state: nextState,\n claimTicketNumber: local.value.claimTicketNumber,\n }),\n );\n }\n\n async #rewardError(\n userId: string,\n rewardId: string,\n key: string,\n error: RewardConsumeError,\n ): Promise<Response> {\n await this.#audit(userId, \"CLAIM_REWARD\", rewardId, key, \"REJECTED\", undefined, error);\n return jsonResponse({ ok: false, error }, 422);\n }\n\n async #syncProgress(request: Request): Promise<Response> {\n const authenticatedUser = await this.#authenticate(request);\n const body = await this.#parse<SyncRequest>(request);\n if (body === null || body.userId === undefined)\n return errorResponse(\"INVALID_REQUEST\", \"userId is required.\", 400);\n if (authenticatedUser !== null && authenticatedUser !== body.userId)\n return errorResponse(\"UNAUTHORIZED\", \"User identity does not match authentication.\", 401);\n const queue = body.queue ?? body.operations ?? [];\n for (const operation of queue) {\n const userId = operation.userId ?? body.userId;\n const spotId = operation.spotId ?? operation.stampId;\n const claimMethod = operation.claimMethod ?? operation.context?.type;\n if (!hasText(userId) || !hasText(spotId) || !hasText(claimMethod)) continue;\n const synthetic = new Request(new URL(\"/api/check-in\", request.url), {\n method: \"POST\",\n body: JSON.stringify({\n userId,\n spotId,\n claimMethod,\n proofData: operation.proofData ?? proofFromContext(operation.context),\n idempotencyKey: operation.idempotencyKey,\n }),\n headers: { \"content-type\": \"application/json\" },\n });\n await this.#verifyCheckIn(synthetic);\n }\n const timestamp = now();\n const state =\n (await this.#storage.getUserState(body.userId)) ?? emptyState(this.#config, timestamp);\n await this.#storage.saveUserState(body.userId, state);\n return jsonResponse({ ok: true, state, accepted: queue.length });\n }\n\n async #audit(\n userId: string,\n action: RallyAuditLog[\"action\"],\n resourceId: string,\n idempotencyKey: string,\n status: RallyAuditLog[\"status\"],\n proofData?: unknown,\n error?: unknown,\n ): Promise<void> {\n await this.#storage.recordAuditLog({\n id: id(\"audit\"),\n timestamp: now(),\n rallyId: this.#config.id,\n userId,\n action,\n resourceId,\n status,\n idempotencyKey,\n ...(proofData === undefined ? {} : { proofData }),\n ...(error === undefined\n ? {}\n : {\n metadata: {\n errorCode: isObject(error) && typeof error.code === \"string\" ? error.code : \"UNKNOWN\",\n },\n }),\n });\n }\n\n #remember(key: string, response: Response): Response {\n this.#idempotent.set(key, response.clone());\n return response;\n }\n}\n\nfunction emptyState(config: RallyConfig, timestamp: string): UserRallyState {\n return {\n rallyId: config.id,\n records: [],\n ...(config.rewards === undefined\n ? {}\n : { rewards: reconcileRewardStates(config.rewards, [], 0, timestamp) }),\n updatedAt: timestamp,\n };\n}\n\nexport type { StampError, StampRecord };\n"]}
1
+ {"version":3,"sources":["../src/persistence.ts","../src/universalServer.ts","../src/index.ts"],"names":["now","id"],"mappings":"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","file":"index.js","sourcesContent":["import type { RallyAuditLog, UserRallyState } from \"./index.js\";\n\n/** Persistence contract for implementations backed by Redis, SQL, or another RDB. */\nexport interface ServerPersistenceAdapter {\n acquireLock(lockKey: string, ttlMs: number): Promise<boolean>;\n releaseLock(lockKey: string): Promise<void>;\n decrementRewardStock(rewardId: string): Promise<{ success: boolean; remainingStock: number }>;\n getIdempotentResult<T>(idempotencyKey: string): Promise<T | null>;\n saveIdempotentResult<T>(idempotencyKey: string, result: T, ttlMs: number): Promise<void>;\n getUserState(rallyId: string, userId: string): Promise<UserRallyState | null>;\n saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void>;\n recordAuditLog(log: RallyAuditLog): Promise<void>;\n}\n\ninterface TimedValue<T> {\n readonly value: T;\n readonly expiresAt: number;\n}\n\nexport interface InMemoryServerPersistenceOptions {\n readonly stocks?: Readonly<Record<string, number>>;\n}\n\n/** Deterministic adapter for tests and small single-process deployments. */\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 #auditLogs: RallyAuditLog[] = [];\n\n constructor(options: InMemoryServerPersistenceOptions = {}) {\n this.#stocks = new Map(Object.entries(options.stocks ?? {}));\n }\n\n async acquireLock(lockKey: string, ttlMs: number): Promise<boolean> {\n const now = Date.now();\n const expiresAt = this.#locks.get(lockKey);\n if (expiresAt !== undefined && expiresAt > now) return false;\n this.#locks.set(lockKey, now + Math.max(1, ttlMs));\n return true;\n }\n\n async releaseLock(lockKey: string): Promise<void> {\n this.#locks.delete(lockKey);\n }\n\n async decrementRewardStock(\n rewardId: string,\n ): Promise<{ success: boolean; remainingStock: number }> {\n const stock = this.#stocks.get(rewardId);\n if (stock === undefined) return { success: true, remainingStock: Number.POSITIVE_INFINITY };\n if (stock <= 0) return { success: false, remainingStock: 0 };\n const remainingStock = stock - 1;\n this.#stocks.set(rewardId, remainingStock);\n return { success: true, remainingStock };\n }\n\n async getIdempotentResult<T>(idempotencyKey: string): Promise<T | null> {\n const entry = this.#idempotent.get(idempotencyKey);\n if (entry === undefined) return null;\n if (entry.expiresAt <= Date.now()) {\n this.#idempotent.delete(idempotencyKey);\n return null;\n }\n return structuredClone(entry.value) as T;\n }\n\n async saveIdempotentResult<T>(idempotencyKey: string, result: T, ttlMs: number): Promise<void> {\n this.#idempotent.set(idempotencyKey, {\n value: structuredClone(result),\n expiresAt: Date.now() + Math.max(1, ttlMs),\n });\n }\n\n async getUserState(rallyId: string, userId: string): Promise<UserRallyState | null> {\n const state = this.#states.get(`${rallyId}:${userId}`);\n return state === undefined ? null : structuredClone(state);\n }\n\n async saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void> {\n this.#states.set(`${rallyId}:${userId}`, structuredClone(state));\n }\n\n async recordAuditLog(log: RallyAuditLog): Promise<void> {\n this.#auditLogs.push(structuredClone(log));\n }\n\n getAuditLogs(): ReadonlyArray<RallyAuditLog> {\n return structuredClone(this.#auditLogs);\n }\n}\n","import type { AdminRallyConfig, StampRallyState, VerificationCondition } from \"@stamprally/core\";\nimport type { RallyAuditLog, UserRallyState } from \"./index.js\";\nimport type { ServerPersistenceAdapter } from \"./persistence.js\";\n\nexport type UniversalVerificationContext =\n | { readonly type: \"qr\"; readonly token: string }\n | { readonly type: \"passcode\"; readonly code: string }\n | { readonly type: \"gps\"; readonly latitude: number; readonly longitude: number }\n | { readonly type: \"custom\"; readonly value: unknown };\n\nexport interface UniversalCheckInRequest {\n readonly rallyId: string;\n readonly userId: string;\n readonly spotId: string;\n readonly context: UniversalVerificationContext;\n readonly idempotencyKey: string;\n readonly now?: string;\n}\n\nexport type UniversalCheckInResult =\n | { readonly ok: true; readonly state: UserRallyState }\n | { readonly ok: false; readonly code: string; readonly message: string };\n\nexport interface UniversalRallyServerOptions {\n readonly idempotencyTtlMs?: number;\n readonly lockTtlMs?: number;\n readonly customValidators?: Readonly<\n Record<string, (value: unknown, condition: VerificationCondition) => boolean>\n >;\n readonly now?: () => string;\n}\n\nfunction distanceMeters(aLat: number, aLon: number, bLat: number, bLon: number): number {\n const radians = (degrees: number): number => (degrees * Math.PI) / 180;\n const dLat = radians(bLat - aLat);\n const dLon = radians(bLon - aLon);\n const latA = radians(aLat);\n const latB = radians(bLat);\n const value = Math.sin(dLat / 2) ** 2 + Math.cos(latA) * Math.cos(latB) * Math.sin(dLon / 2) ** 2;\n return 6_371_000 * 2 * Math.asin(Math.sqrt(Math.min(1, value)));\n}\n\nfunction initialState(config: AdminRallyConfig, now: string): UserRallyState {\n return { rallyId: config.id, records: [], updatedAt: now };\n}\n\nfunction matchesCondition(\n condition: VerificationCondition,\n context: UniversalVerificationContext,\n customValidators: UniversalRallyServerOptions[\"customValidators\"],\n): boolean {\n switch (condition.type) {\n case \"qr\":\n return context.type === \"qr\" && context.token === condition.secretToken;\n case \"passcode\":\n return (\n context.type === \"passcode\" &&\n (condition.caseSensitive === false\n ? context.code.toLocaleLowerCase() === condition.code.toLocaleLowerCase()\n : context.code === condition.code)\n );\n case \"gps\":\n return (\n context.type === \"gps\" &&\n distanceMeters(\n condition.latitude,\n condition.longitude,\n context.latitude,\n context.longitude,\n ) <= condition.radiusMeters\n );\n case \"custom\":\n return (\n customValidators?.[condition.validatorName]?.(\n context.type === \"custom\" ? context.value : undefined,\n condition,\n ) ?? false\n );\n }\n}\n\nfunction audit(\n request: UniversalCheckInRequest,\n status: RallyAuditLog[\"status\"],\n now: string,\n errorCode?: string,\n): RallyAuditLog {\n return {\n id: `audit-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`,\n timestamp: now,\n rallyId: request.rallyId,\n userId: request.userId,\n action: \"CHECK_IN\",\n resourceId: request.spotId,\n status,\n idempotencyKey: request.idempotencyKey,\n ...(errorCode === undefined ? {} : { metadata: { errorCode } }),\n };\n}\n\n/** Atomic, adapter-backed check-in service for the universal model. */\nexport class UniversalRallyServer {\n readonly #config: AdminRallyConfig;\n readonly #persistence: ServerPersistenceAdapter;\n readonly #options: UniversalRallyServerOptions;\n\n constructor(\n config: AdminRallyConfig,\n persistence: ServerPersistenceAdapter,\n options: UniversalRallyServerOptions = {},\n ) {\n this.#config = config;\n this.#persistence = persistence;\n this.#options = options;\n }\n\n async checkIn(request: UniversalCheckInRequest): Promise<UniversalCheckInResult> {\n const key = `check-in:${request.rallyId}:${request.userId}:${request.idempotencyKey}`;\n const previous = await this.#persistence.getIdempotentResult<UniversalCheckInResult>(key);\n if (previous !== null) return previous;\n const lockKey = `state:${request.rallyId}:${request.userId}`;\n const locked = await this.#persistence.acquireLock(lockKey, this.#options.lockTtlMs ?? 5000);\n if (!locked)\n return { ok: false, code: \"CONFLICT\", message: \"The user state is being updated.\" };\n const now = request.now ?? this.#options.now?.() ?? new Date().toISOString();\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,\n now,\n );\n const current =\n (await this.#persistence.getUserState(request.rallyId, request.userId)) ??\n initialState(this.#config, now);\n if (current.records.some((record) => record.stampId === request.spotId))\n return this.#remember(\n key,\n { ok: false, code: \"ALREADY_CLAIMED\", message: \"Spot was already claimed.\" },\n request,\n now,\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,\n now,\n );\n if (\n !spot.conditions.every((condition) =>\n matchesCondition(condition, request.context, this.#options.customValidators),\n )\n )\n return this.#remember(\n key,\n { ok: false, code: \"INVALID_PROOF\", message: \"Verification failed.\" },\n request,\n now,\n );\n const state: UserRallyState = {\n ...current,\n records: [...current.records, { stampId: request.spotId, acquiredAt: now }],\n updatedAt: now,\n };\n await this.#persistence.saveUserState(request.rallyId, request.userId, state);\n await this.#persistence.recordAuditLog(audit(request, \"SUCCESS\", now));\n return this.#remember(key, { ok: true, state }, request, now);\n } finally {\n await this.#persistence.releaseLock(lockKey);\n }\n }\n\n async sync(rallyId: string, userId: string): Promise<UserRallyState> {\n const now = this.#options.now?.() ?? new Date().toISOString();\n return (\n (await this.#persistence.getUserState(rallyId, userId)) ?? initialState(this.#config, now)\n );\n }\n\n async #remember(\n key: string,\n result: UniversalCheckInResult,\n request: UniversalCheckInRequest,\n now: string,\n ): Promise<UniversalCheckInResult> {\n await this.#persistence.recordAuditLog(\n audit(request, \"REJECTED\", now, result.ok ? undefined : result.code),\n );\n await this.#persistence.saveIdempotentResult(\n key,\n result,\n this.#options.idempotencyTtlMs ?? 86_400_000,\n );\n return result;\n }\n}\n\nexport type { StampRallyState };\n","import {\n consumeReward,\n createSecureToken,\n processStamp,\n type RallyConfig,\n type RewardConsumeError,\n reconcileRewardStates,\n type SecureTokenSecretKey,\n type StampError,\n type StampRallyState,\n type StampRecord,\n type VerificationContext,\n} from \"@stamprally/core\";\n\nexport interface UserRallyState extends StampRallyState {\n readonly userId?: string;\n}\n\nexport interface RallyAuditLog {\n readonly id: string;\n readonly timestamp: string;\n readonly rallyId: string;\n readonly userId: string;\n readonly action: \"CHECK_IN\" | \"CLAIM_REWARD\";\n readonly resourceId: string;\n readonly status: \"SUCCESS\" | \"REJECTED\";\n readonly idempotencyKey: string;\n readonly proofData?: unknown;\n readonly metadata?: Readonly<Record<string, unknown>>;\n}\n\nexport interface ServerStorageAdapter {\n getRewardStock(rewardId: string): Promise<number | null>;\n decrementRewardStock(rewardId: string): Promise<boolean>;\n getUserClaims(userId: string, rewardId: string): Promise<number>;\n recordAuditLog(log: RallyAuditLog): Promise<void>;\n saveUserState(userId: string, state: UserRallyState): Promise<void>;\n getUserState(userId: string): Promise<UserRallyState | null>;\n}\n\nexport interface AdminRallyConfig extends RallyConfig {\n readonly secretKey: SecureTokenSecretKey;\n readonly proofTtlSeconds?: number;\n readonly authenticate?: (request: Request) => Promise<string | null> | string | null;\n}\n\nexport interface CheckInRequest {\n readonly userId: string;\n readonly spotId: string;\n readonly claimMethod: string;\n readonly proofData?: unknown;\n readonly idempotencyKey: string;\n}\n\nexport interface ClaimRewardRequest {\n readonly userId: string;\n readonly rewardId: string;\n readonly staffPasscode?: string;\n readonly idempotencyKey: string;\n readonly staffId?: string;\n}\n\nexport interface SyncRequest {\n readonly userId: string;\n readonly queue?: ReadonlyArray<SyncCheckInOperation>;\n readonly operations?: ReadonlyArray<SyncCheckInOperation>;\n}\n\nexport interface SyncCheckInOperation {\n readonly userId?: string;\n readonly spotId?: string;\n readonly stampId?: string;\n readonly claimMethod?: string;\n readonly proofData?: unknown;\n readonly context?: VerificationContext;\n readonly idempotencyKey: string;\n}\n\nexport interface StampClaimProof {\n readonly token: string;\n readonly rallyId: string;\n readonly userId: string;\n readonly spotId: string;\n readonly acquiredAt: string;\n}\n\nexport interface InMemoryServerStorageOptions {\n readonly stocks?: Readonly<Record<string, number>>;\n}\n\nexport class InMemoryServerStorage implements ServerStorageAdapter {\n readonly #states = new Map<string, UserRallyState>();\n readonly #stocks: Map<string, number>;\n readonly #claims = new Map<string, number>();\n readonly #auditLogs: RallyAuditLog[] = [];\n\n constructor(options: InMemoryServerStorageOptions = {}) {\n this.#stocks = new Map(Object.entries(options.stocks ?? {}));\n }\n\n async getRewardStock(rewardId: string): Promise<number | null> {\n return this.#stocks.get(rewardId) ?? null;\n }\n\n async decrementRewardStock(rewardId: string): Promise<boolean> {\n const stock = this.#stocks.get(rewardId);\n if (stock === undefined) return true;\n if (stock <= 0) return false;\n this.#stocks.set(rewardId, stock - 1);\n return true;\n }\n\n async getUserClaims(userId: string, rewardId: string): Promise<number> {\n return this.#claims.get(`${userId}:${rewardId}`) ?? 0;\n }\n\n async recordAuditLog(log: RallyAuditLog): Promise<void> {\n this.#auditLogs.push({ ...log });\n }\n\n async saveUserState(userId: string, state: UserRallyState): Promise<void> {\n this.#states.set(userId, cloneUserState(state));\n }\n\n async getUserState(userId: string): Promise<UserRallyState | null> {\n const state = this.#states.get(userId);\n return state === undefined ? null : cloneUserState(state);\n }\n\n getAuditLogs(): ReadonlyArray<RallyAuditLog> {\n return this.#auditLogs.map((log) => ({ ...log }));\n }\n\n recordClaim(userId: string, rewardId: string): void {\n const key = `${userId}:${rewardId}`;\n this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);\n }\n}\n\nfunction cloneUserState(state: UserRallyState): UserRallyState {\n return {\n ...state,\n records: state.records.map((record) => ({\n ...record,\n ...(record.metadata === undefined ? {} : { metadata: { ...record.metadata } }),\n })),\n ...(state.rewards === undefined\n ? {}\n : { rewards: state.rewards.map((reward) => ({ ...reward })) }),\n };\n}\n\nfunction jsonResponse(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}\n\nfunction errorResponse(code: string, message: string, status: number): Response {\n return jsonResponse({ ok: false, error: { code, message } }, status);\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction contextForClaim(method: string, proofData: unknown): VerificationContext {\n if (method === \"token\" || method === \"qr\" || method === \"passcode\") {\n return {\n type: \"token\",\n token:\n isObject(proofData) && typeof proofData.token === \"string\"\n ? proofData.token\n : String(proofData ?? \"\"),\n };\n }\n if (method === \"geo\" || method === \"geolocation\") {\n const value = isObject(proofData) ? proofData : {};\n return {\n type: \"geo\",\n currentLatitude:\n typeof value.latitude === \"number\"\n ? value.latitude\n : typeof value.currentLatitude === \"number\"\n ? value.currentLatitude\n : Number.NaN,\n currentLongitude:\n typeof value.longitude === \"number\"\n ? value.longitude\n : typeof value.currentLongitude === \"number\"\n ? value.currentLongitude\n : Number.NaN,\n };\n }\n return { type: \"instant\" };\n}\n\nfunction now(): string {\n return new Date().toISOString();\n}\n\nfunction hasText(value: unknown): value is string {\n return typeof value === \"string\" && value.trim() !== \"\";\n}\n\nfunction safeProofData(value: unknown): unknown {\n return isObject(value) && typeof value.token === \"string\" ? { type: \"token\" } : value;\n}\n\nfunction proofFromContext(context: VerificationContext | undefined): unknown {\n if (context?.type === \"token\") return { token: context.token };\n if (context?.type === \"geo\") {\n return { latitude: context.currentLatitude, longitude: context.currentLongitude };\n }\n return undefined;\n}\n\nfunction id(prefix: string): string {\n return `${prefix}-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;\n}\n\nexport class StampRallyServer {\n readonly #config: AdminRallyConfig;\n readonly #storage: ServerStorageAdapter;\n readonly #idempotent = new Map<string, Response>();\n readonly #claims = new Map<string, number>();\n #queue: Promise<unknown> = Promise.resolve();\n\n constructor(config: AdminRallyConfig, storage: ServerStorageAdapter) {\n this.#config = config;\n this.#storage = storage;\n }\n\n handle(request: Request): Promise<Response> {\n const path = new URL(request.url).pathname;\n if (request.method !== \"POST\")\n return Promise.resolve(errorResponse(\"METHOD_NOT_ALLOWED\", \"POST is required.\", 405));\n if (path.endsWith(\"/check-in\")) return this.verifyCheckIn(request);\n if (path.endsWith(\"/claim-reward\")) return this.claimReward(request);\n if (path.endsWith(\"/sync\")) return this.syncProgress(request);\n return Promise.resolve(errorResponse(\"NOT_FOUND\", \"Route not found.\", 404));\n }\n\n verifyCheckIn(request: Request): Promise<Response> {\n return this.#enqueue(() => this.#verifyCheckIn(request));\n }\n\n claimReward(request: Request): Promise<Response> {\n return this.#enqueue(() => this.#claimReward(request));\n }\n\n syncProgress(request: Request): Promise<Response> {\n return this.#enqueue(() => this.#syncProgress(request));\n }\n\n #enqueue<T>(operation: () => Promise<T>): Promise<T> {\n const next = this.#queue.then(operation, operation);\n this.#queue = next.then(\n () => undefined,\n () => undefined,\n );\n return next;\n }\n\n async #authenticate(request: Request): Promise<string | null> {\n return this.#config.authenticate === undefined ? null : this.#config.authenticate(request);\n }\n\n async #parse<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\n async #verifyCheckIn(request: Request): Promise<Response> {\n const authenticatedUser = await this.#authenticate(request);\n const body = await this.#parse<CheckInRequest>(request);\n const userId = body?.userId ?? authenticatedUser;\n if (\n userId === null ||\n userId === undefined ||\n body === null ||\n !hasText(userId) ||\n !hasText(body.spotId) ||\n !hasText(body.claimMethod) ||\n !hasText(body.idempotencyKey)\n ) {\n return errorResponse(\n \"INVALID_REQUEST\",\n \"userId, spotId, and idempotencyKey are required.\",\n 400,\n );\n }\n if (authenticatedUser !== null && authenticatedUser !== userId)\n return errorResponse(\"UNAUTHORIZED\", \"User identity does not match authentication.\", 401);\n const key = `check-in:${userId}:${body.idempotencyKey}`;\n const previous = this.#idempotent.get(key);\n if (previous !== undefined) return previous.clone();\n const timestamp = now();\n const current =\n (await this.#storage.getUserState(userId)) ?? emptyState(this.#config, timestamp);\n const result = processStamp(\n current,\n this.#config,\n body.spotId,\n contextForClaim(body.claimMethod, body.proofData),\n timestamp,\n );\n if (!result.ok) {\n await this.#audit(\n userId,\n \"CHECK_IN\",\n body.spotId,\n body.idempotencyKey,\n \"REJECTED\",\n safeProofData(body.proofData),\n result.error,\n );\n return this.#remember(key, jsonResponse({ ok: false, error: result.error }, 422));\n }\n const ttl = this.#config.proofTtlSeconds ?? 3600;\n const token = await createSecureToken(\n {\n type: \"stamp_claim\",\n rallyId: this.#config.id,\n userId,\n spotId: body.spotId,\n acquiredAt: timestamp,\n exp: Math.floor(Date.now() / 1000) + ttl,\n },\n this.#config.secretKey,\n { encrypt: true },\n );\n await this.#storage.saveUserState(userId, result.value.nextState);\n await this.#audit(\n userId,\n \"CHECK_IN\",\n body.spotId,\n body.idempotencyKey,\n \"SUCCESS\",\n safeProofData(body.proofData),\n );\n return this.#remember(\n key,\n jsonResponse({\n ok: true,\n state: result.value.nextState,\n proof: {\n token,\n rallyId: this.#config.id,\n userId,\n spotId: body.spotId,\n acquiredAt: timestamp,\n },\n }),\n );\n }\n\n async #claimReward(request: Request): Promise<Response> {\n const authenticatedUser = await this.#authenticate(request);\n const body = await this.#parse<ClaimRewardRequest>(request);\n const userId = body?.userId ?? authenticatedUser;\n if (\n userId === null ||\n userId === undefined ||\n body === null ||\n !hasText(userId) ||\n !hasText(body.rewardId) ||\n !hasText(body.idempotencyKey)\n )\n return errorResponse(\n \"INVALID_REQUEST\",\n \"userId, rewardId, and idempotencyKey are required.\",\n 400,\n );\n if (authenticatedUser !== null && authenticatedUser !== userId)\n return errorResponse(\"UNAUTHORIZED\", \"User identity does not match authentication.\", 401);\n const key = `claim-reward:${userId}:${body.rewardId}:${body.idempotencyKey}`;\n const previous = this.#idempotent.get(key);\n if (previous !== undefined) return previous.clone();\n const reward = this.#config.rewards?.find((item) => item.id === body.rewardId);\n if (reward === undefined) {\n await this.#audit(userId, \"CLAIM_REWARD\", body.rewardId, body.idempotencyKey, \"REJECTED\");\n return this.#remember(key, errorResponse(\"REWARD_NOT_FOUND\", \"Reward was not found.\", 404));\n }\n const timestamp = now();\n const current =\n (await this.#storage.getUserState(userId)) ?? emptyState(this.#config, timestamp);\n const rewardState = current.rewards?.find((item) => item.rewardId === reward.id);\n if (rewardState === undefined) {\n await this.#audit(userId, \"CLAIM_REWARD\", reward.id, body.idempotencyKey, \"REJECTED\");\n return this.#remember(key, errorResponse(\"NOT_AVAILABLE\", \"Reward is not available.\", 422));\n }\n const userClaims = Math.max(\n await this.#storage.getUserClaims(userId, reward.id),\n this.#claims.get(`${userId}:${reward.id}`) ?? 0,\n );\n const stock = await this.#storage.getRewardStock(reward.id);\n const userLimit = reward.userClaimLimit ?? reward.limitPerUser;\n const canReclaimServerReward =\n reward.redemptionMethod === \"server_claim\" &&\n (userLimit === undefined || userClaims < userLimit) &&\n (stock === null || stock > 0);\n const claimableState =\n canReclaimServerReward && rewardState.status === \"CONSUMED\"\n ? { ...rewardState, status: \"AVAILABLE\" as const }\n : rewardState;\n const local = consumeReward({\n reward,\n currentState: claimableState,\n now: timestamp,\n ...(body.staffPasscode === undefined ? {} : { inputPasscode: body.staffPasscode }),\n ...(body.staffId === undefined ? {} : { staffId: body.staffId }),\n userId,\n userRedemptionCount: userClaims,\n });\n if (!local.ok)\n return this.#remember(\n key,\n await this.#rewardError(userId, reward.id, body.idempotencyKey, local.error),\n );\n if (stock !== null && !(await this.#storage.decrementRewardStock(reward.id)))\n return this.#remember(\n key,\n await this.#rewardError(userId, reward.id, body.idempotencyKey, {\n code: \"OUT_OF_STOCK\",\n rewardId: reward.id,\n }),\n );\n const nextState: StampRallyState = {\n ...current,\n rewards: (current.rewards ?? []).map((item) =>\n item.rewardId === reward.id ? local.value : item,\n ),\n updatedAt: timestamp,\n };\n await this.#storage.saveUserState(userId, nextState);\n const claimKey = `${userId}:${reward.id}`;\n this.#claims.set(claimKey, userClaims + 1);\n if (this.#storage instanceof InMemoryServerStorage)\n this.#storage.recordClaim(userId, reward.id);\n await this.#audit(userId, \"CLAIM_REWARD\", reward.id, body.idempotencyKey, \"SUCCESS\", {\n staffId: body.staffId,\n });\n return this.#remember(\n key,\n jsonResponse({\n ok: true,\n state: nextState,\n claimTicketNumber: local.value.claimTicketNumber,\n }),\n );\n }\n\n async #rewardError(\n userId: string,\n rewardId: string,\n key: string,\n error: RewardConsumeError,\n ): Promise<Response> {\n await this.#audit(userId, \"CLAIM_REWARD\", rewardId, key, \"REJECTED\", undefined, error);\n return jsonResponse({ ok: false, error }, 422);\n }\n\n async #syncProgress(request: Request): Promise<Response> {\n const authenticatedUser = await this.#authenticate(request);\n const body = await this.#parse<SyncRequest>(request);\n if (body === null || body.userId === undefined)\n return errorResponse(\"INVALID_REQUEST\", \"userId is required.\", 400);\n if (authenticatedUser !== null && authenticatedUser !== body.userId)\n return errorResponse(\"UNAUTHORIZED\", \"User identity does not match authentication.\", 401);\n const queue = body.queue ?? body.operations ?? [];\n for (const operation of queue) {\n const userId = operation.userId ?? body.userId;\n const spotId = operation.spotId ?? operation.stampId;\n const claimMethod = operation.claimMethod ?? operation.context?.type;\n if (!hasText(userId) || !hasText(spotId) || !hasText(claimMethod)) continue;\n const synthetic = new Request(new URL(\"/api/check-in\", request.url), {\n method: \"POST\",\n body: JSON.stringify({\n userId,\n spotId,\n claimMethod,\n proofData: operation.proofData ?? proofFromContext(operation.context),\n idempotencyKey: operation.idempotencyKey,\n }),\n headers: { \"content-type\": \"application/json\" },\n });\n await this.#verifyCheckIn(synthetic);\n }\n const timestamp = now();\n const state =\n (await this.#storage.getUserState(body.userId)) ?? emptyState(this.#config, timestamp);\n await this.#storage.saveUserState(body.userId, state);\n return jsonResponse({ ok: true, state, accepted: queue.length });\n }\n\n async #audit(\n userId: string,\n action: RallyAuditLog[\"action\"],\n resourceId: string,\n idempotencyKey: string,\n status: RallyAuditLog[\"status\"],\n proofData?: unknown,\n error?: unknown,\n ): Promise<void> {\n await this.#storage.recordAuditLog({\n id: id(\"audit\"),\n timestamp: now(),\n rallyId: this.#config.id,\n userId,\n action,\n resourceId,\n status,\n idempotencyKey,\n ...(proofData === undefined ? {} : { proofData }),\n ...(error === undefined\n ? {}\n : {\n metadata: {\n errorCode: isObject(error) && typeof error.code === \"string\" ? error.code : \"UNKNOWN\",\n },\n }),\n });\n }\n\n #remember(key: string, response: Response): Response {\n this.#idempotent.set(key, response.clone());\n return response;\n }\n}\n\nfunction emptyState(config: RallyConfig, timestamp: string): UserRallyState {\n return {\n rallyId: config.id,\n records: [],\n ...(config.rewards === undefined\n ? {}\n : { rewards: reconcileRewardStates(config.rewards, [], 0, timestamp) }),\n updatedAt: timestamp,\n };\n}\n\nexport type { InMemoryServerPersistenceOptions, ServerPersistenceAdapter } from \"./persistence.js\";\nexport { InMemoryServerPersistenceAdapter } from \"./persistence.js\";\nexport type {\n UniversalCheckInRequest,\n UniversalCheckInResult,\n UniversalRallyServerOptions,\n UniversalVerificationContext,\n} from \"./universalServer.js\";\nexport { UniversalRallyServer } from \"./universalServer.js\";\nexport type { StampError, StampRecord };\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamprally/server",
3
- "version": "0.6.0",
3
+ "version": "0.7.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.6.0"
31
+ "@stamprally/core": "0.7.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "tsup": "^8.5.1",