@stamprally/server 0.12.0 → 0.14.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.js CHANGED
@@ -1,5 +1,30 @@
1
1
  import { reconcileRewardStates, consumeReward, evaluateConditionDetailed } from '@stamprally/core';
2
2
 
3
+ // src/examples/transaction.ts
4
+ async function executeClaimRewardTransaction(database, store, params, mutation) {
5
+ try {
6
+ return await database.transaction(async (transaction) => {
7
+ const current = await store.readContext(transaction, params);
8
+ const next = mutation(current);
9
+ if (next.error !== void 0) {
10
+ await store.writeAudit(transaction, next.auditLog);
11
+ if (params.idempotencyKey !== void 0 && next.result !== void 0)
12
+ await store.writeIdempotency(transaction, params, next.result);
13
+ return { success: false, error: next.error };
14
+ }
15
+ if (next.nextStock !== null) await store.writeStock(transaction, params, next.nextStock);
16
+ await store.writeUserState(transaction, params, next.nextUserState);
17
+ await store.writeClaimRecord(transaction, params, next.nextUserState);
18
+ await store.writeAudit(transaction, next.auditLog);
19
+ if (params.idempotencyKey !== void 0 && next.result !== void 0)
20
+ await store.writeIdempotency(transaction, params, next.result);
21
+ return { success: true };
22
+ });
23
+ } catch (error2) {
24
+ return { success: false, error: error2 instanceof Error ? error2.message : String(error2) };
25
+ }
26
+ }
27
+
3
28
  // src/persistence.ts
4
29
  var InMemoryServerPersistenceAdapter = class {
5
30
  #locks = /* @__PURE__ */ new Map();
@@ -113,8 +138,41 @@ var InMemoryServerPersistenceAdapter = class {
113
138
  );
114
139
  return { success: true };
115
140
  });
116
- } catch (error) {
117
- return { success: false, error: error instanceof Error ? error.message : String(error) };
141
+ } catch (error2) {
142
+ return { success: false, error: error2 instanceof Error ? error2.message : String(error2) };
143
+ }
144
+ }
145
+ async executeCheckInTransaction(params, mutation) {
146
+ try {
147
+ return await this.runTransaction(params.rallyId, async () => {
148
+ const userState = await this.getUserState(params.rallyId, params.userId) ?? params.initialUserState;
149
+ if (userState === void 0)
150
+ return { success: false, error: "A user state is required for this transaction." };
151
+ const mutationResult = mutation({ userState });
152
+ if (mutationResult.error !== void 0) {
153
+ await this.recordAuditLog(mutationResult.auditLog);
154
+ if (params.idempotencyKey !== void 0 && mutationResult.result !== void 0)
155
+ await this.saveIdempotentResult(
156
+ params.rallyId,
157
+ params.idempotencyKey,
158
+ mutationResult.result,
159
+ params.idempotencyTtlMs ?? 864e5
160
+ );
161
+ return { success: false, error: mutationResult.error };
162
+ }
163
+ await this.saveUserState(params.rallyId, params.userId, mutationResult.nextUserState);
164
+ await this.recordAuditLog(mutationResult.auditLog);
165
+ if (params.idempotencyKey !== void 0 && mutationResult.result !== void 0)
166
+ await this.saveIdempotentResult(
167
+ params.rallyId,
168
+ params.idempotencyKey,
169
+ mutationResult.result,
170
+ params.idempotencyTtlMs ?? 864e5
171
+ );
172
+ return { success: true };
173
+ });
174
+ } catch (error2) {
175
+ return { success: false, error: error2 instanceof Error ? error2.message : String(error2) };
118
176
  }
119
177
  }
120
178
  async rollbackUserState(rallyId, userId, previousState) {
@@ -168,7 +226,7 @@ var InMemoryServerPersistenceAdapter = class {
168
226
  if (count <= 1) this.#claims.delete(key);
169
227
  else this.#claims.set(key, count - 1);
170
228
  const index = this.#claimRecords.findIndex(
171
- (record) => record.rallyId === rallyId && record.userId === userId && record.rewardId === rewardId && record.ticketNumber === ticketNumber
229
+ (record2) => record2.rallyId === rallyId && record2.userId === userId && record2.rewardId === rewardId && record2.ticketNumber === ticketNumber
172
230
  );
173
231
  if (index >= 0) this.#claimRecords.splice(index, 1);
174
232
  }
@@ -200,7 +258,7 @@ var InMemoryServerPersistenceAdapter = class {
200
258
  };
201
259
  try {
202
260
  return await operation(this);
203
- } catch (error) {
261
+ } catch (error2) {
204
262
  this.#stocks.clear();
205
263
  for (const [key, value] of snapshot.stocks) this.#stocks.set(key, value);
206
264
  this.#idempotent.clear();
@@ -212,10 +270,63 @@ var InMemoryServerPersistenceAdapter = class {
212
270
  for (const [key, value] of snapshot.claims) this.#claims.set(key, value);
213
271
  this.#claimRecords.splice(0, this.#claimRecords.length, ...snapshot.claimRecords);
214
272
  this.#auditLogs.splice(0, this.#auditLogs.length, ...snapshot.auditLogs);
215
- throw error;
273
+ throw error2;
216
274
  }
217
275
  }
218
276
  };
277
+
278
+ // src/security.ts
279
+ function error(path, message) {
280
+ return { success: false, errors: [{ path, message, code: "invalid_request" }] };
281
+ }
282
+ function record(value) {
283
+ return typeof value === "object" && value !== null && !Array.isArray(value);
284
+ }
285
+ function nonEmpty(value) {
286
+ return typeof value === "string" && value.trim().length > 0;
287
+ }
288
+ function context(value) {
289
+ if (!record(value) || typeof value.type !== "string") return false;
290
+ if (value.type === "qr") return nonEmpty(value.token);
291
+ if (value.type === "passcode") return nonEmpty(value.code);
292
+ if (value.type === "nfc") return nonEmpty(value.tagId);
293
+ if (value.type === "custom") return "value" in value;
294
+ return value.type === "gps" && typeof value.latitude === "number" && Number.isFinite(value.latitude) && typeof value.longitude === "number" && Number.isFinite(value.longitude);
295
+ }
296
+ function common(value, fields) {
297
+ if (!record(value)) return false;
298
+ return fields.every((field) => nonEmpty(value[field]));
299
+ }
300
+ function validateCheckInRequest(value) {
301
+ if (!common(value, ["rallyId", "spotId", "idempotencyKey"]))
302
+ return error("$", "rallyId, spotId, and idempotencyKey must be non-empty strings.");
303
+ if (!context(value.context))
304
+ return error("context", "context is not a valid verification context.");
305
+ if (value.userId !== void 0 && !nonEmpty(value.userId))
306
+ return error("userId", "userId must be non-empty.");
307
+ if (value.now !== void 0 && !nonEmpty(value.now))
308
+ return error("now", "now must be non-empty when provided.");
309
+ return { success: true, data: value };
310
+ }
311
+ function validateClaimRewardRequest(value) {
312
+ if (!common(value, ["rallyId", "rewardId", "idempotencyKey"]))
313
+ return error("$", "rallyId, rewardId, and idempotencyKey must be non-empty strings.");
314
+ if (value.userId !== void 0 && !nonEmpty(value.userId))
315
+ return error("userId", "userId must be non-empty.");
316
+ if (value.staffPasscode !== void 0 && !nonEmpty(value.staffPasscode))
317
+ return error("staffPasscode", "staffPasscode must be non-empty.");
318
+ if (value.staffId !== void 0 && !nonEmpty(value.staffId))
319
+ return error("staffId", "staffId must be non-empty.");
320
+ if (value.now !== void 0 && !nonEmpty(value.now))
321
+ return error("now", "now must be non-empty when provided.");
322
+ return { success: true, data: value };
323
+ }
324
+ function validateSyncRequest(value) {
325
+ if (!common(value, ["rallyId"])) return error("rallyId", "rallyId must be a non-empty string.");
326
+ if (value.userId !== void 0 && !nonEmpty(value.userId))
327
+ return error("userId", "userId must be non-empty.");
328
+ return { success: true, data: value };
329
+ }
219
330
  function json(body, status = 200) {
220
331
  return new Response(JSON.stringify(body), {
221
332
  status,
@@ -228,8 +339,12 @@ function isObject(value) {
228
339
  function requestId(prefix) {
229
340
  return `${prefix}-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;
230
341
  }
231
- function now(options, requested) {
232
- return requested ?? options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
342
+ function now(options) {
343
+ return options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
344
+ }
345
+ function timestampMillis(timestamp) {
346
+ const value = Date.parse(timestamp);
347
+ return Number.isFinite(value) ? value : Date.now();
233
348
  }
234
349
  function initialState(config, userId, timestamp) {
235
350
  return {
@@ -240,16 +355,16 @@ function initialState(config, userId, timestamp) {
240
355
  updatedAt: timestamp
241
356
  };
242
357
  }
243
- function getProof(context) {
244
- return context.type === "qr" ? context.token : context.type === "passcode" ? context.code : context.type === "gps" ? { latitude: context.latitude, longitude: context.longitude } : context.type === "nfc" ? context.tagId : context.value;
358
+ function getProof(context2) {
359
+ return context2.type === "qr" ? context2.token : context2.type === "passcode" ? context2.code : context2.type === "gps" ? { latitude: context2.latitude, longitude: context2.longitude } : context2.type === "nfc" ? context2.tagId : context2.value;
245
360
  }
246
- async function evaluate(condition, context, validator, base) {
247
- if (condition.type !== "custom") return evaluateConditionDetailed(condition, context).ok;
361
+ async function evaluate(condition, context2, validator, base) {
362
+ if (condition.type !== "custom") return evaluateConditionDetailed(condition, context2).ok;
248
363
  if (validator === void 0) return false;
249
364
  const validationContext = {
250
365
  rallyId: base.rallyId,
251
366
  spotId: base.spotId,
252
- proofData: getProof(context),
367
+ proofData: getProof(context2),
253
368
  condition,
254
369
  userState: base.state
255
370
  };
@@ -269,6 +384,10 @@ function audit(rallyId, userId, action, resourceId, key, status, timestamp, code
269
384
  ...code === void 0 ? {} : { metadata: { errorCode: code } }
270
385
  };
271
386
  }
387
+ function operationStatus(result) {
388
+ if (result.ok) return "ACCEPTED";
389
+ return result.code === "CONFLICT" || result.code === "PERSISTENCE_FAILED" ? "RETRYABLE_ERROR" : "REJECTED_PERMANENT";
390
+ }
272
391
  var StampRallyServer = class {
273
392
  #config;
274
393
  #persistence;
@@ -288,41 +407,51 @@ var StampRallyServer = class {
288
407
  return json({ ok: false, code: "NOT_FOUND", message: "Route not found." }, 404);
289
408
  }
290
409
  async handleCheckIn(request) {
291
- const body = await this.#body(request);
292
- const userId = await this.#user(request, body?.userId);
293
- if (body === null || userId === null || body.rallyId !== this.#config.id || body.spotId === "" || body.idempotencyKey === "" || body.context === void 0)
410
+ const body = validateCheckInRequest(await this.#body(request));
411
+ if (!body.success || body.data.rallyId !== this.#config.id)
294
412
  return json(
295
- {
296
- ok: false,
297
- code: "INVALID_REQUEST",
298
- message: "rallyId, spotId, context, and idempotencyKey are required."
299
- },
413
+ { ok: false, code: "INVALID_REQUEST", message: "Invalid check-in request." },
300
414
  400
301
415
  );
302
- const result = await this.checkIn({ ...body, userId });
303
- return json(result, result.ok ? 200 : result.code === "SPOT_NOT_FOUND" ? 404 : 422);
416
+ const userId = await this.#user(request);
417
+ if (userId === null)
418
+ return json(
419
+ { ok: false, code: "UNAUTHENTICATED", message: "Authentication is required." },
420
+ 401
421
+ );
422
+ const result = await this.checkIn({ ...body.data, userId });
423
+ return json(
424
+ { ...result, status: operationStatus(result) },
425
+ result.ok ? 200 : result.code === "SPOT_NOT_FOUND" ? 404 : 422
426
+ );
304
427
  }
305
428
  async handleClaimReward(request) {
306
- const body = await this.#body(request);
307
- const userId = await this.#user(request, body?.userId);
308
- if (body === null || userId === null || body.rallyId !== this.#config.id || body.rewardId === "" || body.idempotencyKey === "")
429
+ const body = validateClaimRewardRequest(await this.#body(request));
430
+ if (!body.success || body.data.rallyId !== this.#config.id)
431
+ return json({ ok: false, code: "INVALID_REQUEST", message: "Invalid reward request." }, 400);
432
+ const userId = await this.#user(request);
433
+ if (userId === null)
309
434
  return json(
310
- {
311
- ok: false,
312
- code: "INVALID_REQUEST",
313
- message: "rallyId, rewardId, and idempotencyKey are required."
314
- },
315
- 400
435
+ { ok: false, code: "UNAUTHENTICATED", message: "Authentication is required." },
436
+ 401
316
437
  );
317
- const result = await this.claimReward({ ...body, userId });
318
- return json(result, result.ok ? 200 : result.code === "REWARD_NOT_FOUND" ? 404 : 422);
438
+ const result = await this.claimReward({ ...body.data, userId });
439
+ return json(
440
+ { ...result, status: operationStatus(result) },
441
+ result.ok ? 200 : result.code === "REWARD_NOT_FOUND" ? 404 : 422
442
+ );
319
443
  }
320
444
  async handleSync(request) {
321
- const body = await this.#body(request);
322
- const userId = await this.#user(request, body?.userId);
323
- if (body === null || userId === null || body.rallyId !== this.#config.id)
324
- return json({ ok: false, code: "INVALID_REQUEST", message: "rallyId is required." }, 400);
325
- return json({ ok: true, state: await this.sync(body.rallyId, userId) });
445
+ const body = validateSyncRequest(await this.#body(request));
446
+ if (!body.success || body.data.rallyId !== this.#config.id)
447
+ return json({ ok: false, code: "INVALID_REQUEST", message: "Invalid sync request." }, 400);
448
+ const userId = await this.#user(request);
449
+ if (userId === null)
450
+ return json(
451
+ { ok: false, code: "UNAUTHENTICATED", message: "Authentication is required." },
452
+ 401
453
+ );
454
+ return json({ ok: true, state: await this.sync(body.data.rallyId, userId) });
326
455
  }
327
456
  async checkIn(request) {
328
457
  const key = `check-in:${request.rallyId}:${request.userId}:${request.idempotencyKey}`;
@@ -338,47 +467,68 @@ var StampRallyServer = class {
338
467
  this.#options.lockTtlMs ?? 5e3
339
468
  ))
340
469
  return { ok: false, code: "CONFLICT", message: "The user state is being updated." };
341
- const timestamp = now(this.#options, request.now);
470
+ const timestamp = now(this.#options);
342
471
  try {
472
+ const current = await this.#persistence.getUserState(request.rallyId, request.userId) ?? initialState(this.#config, request.userId, timestamp);
473
+ const responseHolder = { value: null };
474
+ const makeAudit = (status, code) => audit(
475
+ request.rallyId,
476
+ request.userId,
477
+ "CHECK_IN",
478
+ request.spotId,
479
+ request.idempotencyKey,
480
+ status,
481
+ timestamp,
482
+ code
483
+ );
343
484
  const spot = this.#config.spots.find((item) => item.id === request.spotId);
344
- if (spot === void 0)
345
- return this.#remember(
485
+ if (spot === void 0) {
486
+ responseHolder.value = {
487
+ ok: false,
488
+ code: "SPOT_NOT_FOUND",
489
+ message: "Spot was not found."
490
+ };
491
+ return await this.#rememberCheckInTransaction(
492
+ request,
493
+ timestamp,
346
494
  key,
347
- { ok: false, code: "SPOT_NOT_FOUND", message: "Spot was not found." },
348
- request.rallyId,
349
- request.userId,
350
- "CHECK_IN",
351
- request.spotId,
352
- request.idempotencyKey,
353
- timestamp
495
+ current,
496
+ {
497
+ nextUserState: current,
498
+ auditLog: makeAudit("REJECTED", "SPOT_NOT_FOUND"),
499
+ result: responseHolder.value,
500
+ error: "SPOT_NOT_FOUND"
501
+ },
502
+ responseHolder
354
503
  );
355
- const current = await this.#persistence.getUserState(request.rallyId, request.userId) ?? initialState(this.#config, request.userId, timestamp);
356
- if (current.records.some((record) => record.stampId === request.spotId))
357
- return this.#remember(
504
+ }
505
+ const rejected = (code, message) => {
506
+ responseHolder.value = { ok: false, code, message };
507
+ return {
508
+ nextUserState: current,
509
+ auditLog: makeAudit("REJECTED", code),
510
+ result: responseHolder.value,
511
+ error: code
512
+ };
513
+ };
514
+ if (current.records.some((record2) => record2.stampId === request.spotId))
515
+ return await this.#rememberCheckInTransaction(
516
+ request,
517
+ timestamp,
358
518
  key,
359
- { ok: false, code: "STAMP_ALREADY_ACQUIRED", message: "Spot was already claimed." },
360
- request.rallyId,
361
- request.userId,
362
- "CHECK_IN",
363
- request.spotId,
364
- request.idempotencyKey,
365
- timestamp
519
+ current,
520
+ rejected("STAMP_ALREADY_ACQUIRED", "Spot was already claimed."),
521
+ responseHolder
366
522
  );
367
- const acquired = new Set(current.records.map((record) => record.stampId));
523
+ const acquired = new Set(current.records.map((record2) => record2.stampId));
368
524
  if (spot.prerequisites?.some((id) => !acquired.has(id)))
369
- return this.#remember(
525
+ return await this.#rememberCheckInTransaction(
526
+ request,
527
+ timestamp,
370
528
  key,
371
- {
372
- ok: false,
373
- code: "PREREQUISITES_NOT_MET",
374
- message: "Prerequisite spots are not complete."
375
- },
376
- request.rallyId,
377
- request.userId,
378
- "CHECK_IN",
379
- request.spotId,
380
- request.idempotencyKey,
381
- timestamp
529
+ current,
530
+ rejected("PREREQUISITES_NOT_MET", "Prerequisite spots are not complete."),
531
+ responseHolder
382
532
  );
383
533
  for (const condition of spot.conditions)
384
534
  if (!await evaluate(
@@ -387,15 +537,13 @@ var StampRallyServer = class {
387
537
  condition.type === "custom" ? this.#options.customValidators?.[condition.validatorName] : void 0,
388
538
  { rallyId: request.rallyId, spotId: request.spotId, state: current }
389
539
  ))
390
- return this.#remember(
540
+ return await this.#rememberCheckInTransaction(
541
+ request,
542
+ timestamp,
391
543
  key,
392
- { ok: false, code: "INVALID_PROOF", message: "Verification failed." },
393
- request.rallyId,
394
- request.userId,
395
- "CHECK_IN",
396
- request.spotId,
397
- request.idempotencyKey,
398
- timestamp
544
+ current,
545
+ rejected("INVALID_PROOF", "Verification failed."),
546
+ responseHolder
399
547
  );
400
548
  const next = {
401
549
  ...current,
@@ -408,17 +556,30 @@ var StampRallyServer = class {
408
556
  ),
409
557
  updatedAt: timestamp
410
558
  };
411
- await this.#persistence.saveUserState(request.rallyId, request.userId, next);
412
- return this.#remember(
413
- key,
414
- { ok: true, state: next },
415
- request.rallyId,
416
- request.userId,
417
- "CHECK_IN",
418
- request.spotId,
419
- request.idempotencyKey,
420
- timestamp
559
+ responseHolder.value = { ok: true, state: next };
560
+ const transaction = await this.#persistence.executeCheckInTransaction(
561
+ {
562
+ rallyId: request.rallyId,
563
+ userId: request.userId,
564
+ spotId: request.spotId,
565
+ timestamp: timestampMillis(timestamp),
566
+ idempotencyKey: key,
567
+ proofData: request.context,
568
+ ...this.#options.idempotencyTtlMs === void 0 ? {} : { idempotencyTtlMs: this.#options.idempotencyTtlMs },
569
+ initialUserState: current
570
+ },
571
+ () => ({
572
+ nextUserState: next,
573
+ auditLog: makeAudit("SUCCESS"),
574
+ result: responseHolder.value
575
+ })
421
576
  );
577
+ if (transaction.success && responseHolder.value !== null) return responseHolder.value;
578
+ return {
579
+ ok: false,
580
+ code: "PERSISTENCE_FAILED",
581
+ message: transaction.error ?? "Check-in failed."
582
+ };
422
583
  } finally {
423
584
  await this.#persistence.releaseLock(request.rallyId, lockKey);
424
585
  }
@@ -436,7 +597,7 @@ var StampRallyServer = class {
436
597
  key,
437
598
  { ok: false, code: "REWARD_NOT_FOUND", message: "Reward was not found." },
438
599
  request,
439
- now(this.#options, request.now)
600
+ now(this.#options)
440
601
  );
441
602
  const lockKey = `reward:${request.rallyId}:${reward.id}`;
442
603
  if (!await this.#persistence.acquireLock(
@@ -445,7 +606,7 @@ var StampRallyServer = class {
445
606
  this.#options.lockTtlMs ?? 5e3
446
607
  ))
447
608
  return { ok: false, code: "CONFLICT", message: "The reward is being claimed." };
448
- const timestamp = now(this.#options, request.now);
609
+ const timestamp = now(this.#options);
449
610
  try {
450
611
  const checked = await this.#persistence.getIdempotentResult(
451
612
  request.rallyId,
@@ -495,8 +656,9 @@ var StampRallyServer = class {
495
656
  error: "OUT_OF_STOCK"
496
657
  };
497
658
  }
659
+ const effectiveReward = reward.staffPasscode === void 0 && this.#config.staffPasscode !== void 0 ? { ...reward, staffPasscode: this.#config.staffPasscode } : reward;
498
660
  const local = consumeReward({
499
- reward,
661
+ reward: effectiveReward,
500
662
  currentState: currentReward,
501
663
  now: timestamp,
502
664
  userRedemptionCount: claimCount,
@@ -547,11 +709,11 @@ var StampRallyServer = class {
547
709
  code: "PERSISTENCE_FAILED",
548
710
  message: result.error ?? "Reward claim failed."
549
711
  };
550
- } catch (error) {
712
+ } catch (error2) {
551
713
  return {
552
714
  ok: false,
553
715
  code: "PERSISTENCE_FAILED",
554
- message: error instanceof Error ? error.message : "Reward claim failed."
716
+ message: error2 instanceof Error ? error2.message : "Reward claim failed."
555
717
  };
556
718
  } finally {
557
719
  await this.#persistence.releaseLock(request.rallyId, lockKey);
@@ -568,31 +730,36 @@ var StampRallyServer = class {
568
730
  return null;
569
731
  }
570
732
  }
571
- async #user(request, requested) {
572
- if (this.#options.authenticate !== void 0)
573
- return await this.#options.authenticate(request) ?? null;
574
- return requested ?? null;
575
- }
576
- async #remember(key, result, rallyId, userId, action, resourceId, idempotencyKey, timestamp) {
577
- await this.#persistence.recordAuditLog(
578
- audit(
579
- rallyId,
580
- userId,
581
- action,
582
- resourceId,
583
- idempotencyKey,
584
- result.ok ? "SUCCESS" : "REJECTED",
585
- timestamp,
586
- result.ok ? void 0 : result.code
587
- )
588
- );
589
- await this.#persistence.saveIdempotentResult(
590
- rallyId,
591
- key,
592
- result,
593
- this.#options.idempotencyTtlMs ?? 864e5
733
+ async #user(request) {
734
+ if (this.#options.authenticate !== void 0) {
735
+ const identity = await this.#options.authenticate(request);
736
+ if (typeof identity === "string") return identity.length > 0 ? identity : null;
737
+ if (identity === null) return null;
738
+ const authenticatedUserId = identity.authenticatedUserId;
739
+ return authenticatedUserId.length > 0 ? authenticatedUserId : null;
740
+ }
741
+ return "anonymous";
742
+ }
743
+ async #rememberCheckInTransaction(request, timestamp, key, current, mutation, responseHolder) {
744
+ const transaction = await this.#persistence.executeCheckInTransaction(
745
+ {
746
+ rallyId: request.rallyId,
747
+ userId: request.userId,
748
+ spotId: request.spotId,
749
+ timestamp: timestampMillis(timestamp),
750
+ idempotencyKey: key,
751
+ ...this.#options.idempotencyTtlMs === void 0 ? {} : { idempotencyTtlMs: this.#options.idempotencyTtlMs },
752
+ initialUserState: current
753
+ },
754
+ () => mutation
594
755
  );
595
- return result;
756
+ if (responseHolder.value !== null && (transaction.success || transaction.error === mutation.error))
757
+ return responseHolder.value;
758
+ return {
759
+ ok: false,
760
+ code: "PERSISTENCE_FAILED",
761
+ message: transaction.error ?? "Check-in failed."
762
+ };
596
763
  }
597
764
  async #rememberClaim(key, result, request, timestamp) {
598
765
  await this.#persistence.recordAuditLog(
@@ -617,6 +784,6 @@ var StampRallyServer = class {
617
784
  }
618
785
  };
619
786
 
620
- export { InMemoryServerPersistenceAdapter, StampRallyServer };
787
+ export { InMemoryServerPersistenceAdapter, StampRallyServer, executeClaimRewardTransaction, validateCheckInRequest, validateClaimRewardRequest, validateSyncRequest };
621
788
  //# sourceMappingURL=index.js.map
622
789
  //# sourceMappingURL=index.js.map