@stamprally/server 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.cjs +56 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -25
- package/dist/index.d.ts +28 -25
- package/dist/index.js +56 -15
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -2,6 +2,16 @@ import * as _stamprally_core from '@stamprally/core';
|
|
|
2
2
|
import { UserRallyState, AdminRallyConfig } from '@stamprally/core';
|
|
3
3
|
export { UserRallyState } from '@stamprally/core';
|
|
4
4
|
|
|
5
|
+
/** Authentication already verified by the host application's middleware. */
|
|
6
|
+
interface TrustedAuthContext {
|
|
7
|
+
readonly authenticatedUserId: string;
|
|
8
|
+
readonly isAnonymous?: boolean;
|
|
9
|
+
readonly sessionId?: string;
|
|
10
|
+
readonly claims?: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
/** @deprecated Use TrustedAuthContext. */
|
|
13
|
+
type AuthenticationContext = TrustedAuthContext;
|
|
14
|
+
|
|
5
15
|
interface UserClaimRecord {
|
|
6
16
|
readonly rallyId: string;
|
|
7
17
|
readonly userId: string;
|
|
@@ -39,6 +49,16 @@ interface ClaimRewardTransactionMutation {
|
|
|
39
49
|
/** A domain rejection is committed as an audit/idempotency record without mutations. */
|
|
40
50
|
readonly error?: string;
|
|
41
51
|
}
|
|
52
|
+
interface ClaimRewardMutationContext {
|
|
53
|
+
readonly rewardStock: number | null;
|
|
54
|
+
readonly sharedStock: number | null;
|
|
55
|
+
readonly primaryStock: number | null;
|
|
56
|
+
readonly secondaryStock: number | null;
|
|
57
|
+
/** @deprecated Use rewardStock. */
|
|
58
|
+
readonly stock: number | null;
|
|
59
|
+
readonly claimCount: number;
|
|
60
|
+
readonly userState: UserRallyState;
|
|
61
|
+
}
|
|
42
62
|
interface CheckInTransactionParams {
|
|
43
63
|
readonly rallyId: string;
|
|
44
64
|
readonly userId: string;
|
|
@@ -67,12 +87,7 @@ interface ServerPersistenceAdapter {
|
|
|
67
87
|
* Atomically commits stock, user state, claim count, audit log, and idempotency data.
|
|
68
88
|
* Adapters must roll back every write when the callback or any commit operation fails.
|
|
69
89
|
*/
|
|
70
|
-
executeClaimRewardTransaction(params: ClaimRewardTransactionParams, mutation: (current: {
|
|
71
|
-
readonly stock: number | null;
|
|
72
|
-
readonly secondaryStock: number | null;
|
|
73
|
-
readonly claimCount: number;
|
|
74
|
-
readonly userState: UserRallyState;
|
|
75
|
-
}) => ClaimRewardTransactionMutation): Promise<{
|
|
90
|
+
executeClaimRewardTransaction(params: ClaimRewardTransactionParams, mutation: (current: ClaimRewardMutationContext) => ClaimRewardTransactionMutation): Promise<{
|
|
76
91
|
readonly success: boolean;
|
|
77
92
|
readonly error?: string;
|
|
78
93
|
}>;
|
|
@@ -101,7 +116,7 @@ interface InMemoryServerPersistenceOptions {
|
|
|
101
116
|
}
|
|
102
117
|
declare class InMemoryServerPersistenceAdapter implements ServerPersistenceAdapter {
|
|
103
118
|
#private;
|
|
104
|
-
readonly supportsRewardStock
|
|
119
|
+
readonly supportsRewardStock: boolean;
|
|
105
120
|
constructor(options?: InMemoryServerPersistenceOptions);
|
|
106
121
|
acquireLock(rallyId: string, key: string, ttlMs: number): Promise<boolean>;
|
|
107
122
|
releaseLock(rallyId: string, key: string): Promise<void>;
|
|
@@ -111,12 +126,7 @@ declare class InMemoryServerPersistenceAdapter implements ServerPersistenceAdapt
|
|
|
111
126
|
remainingStock: number;
|
|
112
127
|
}>;
|
|
113
128
|
restoreRewardStock(rallyId: string, rewardId: string, count?: number): Promise<void>;
|
|
114
|
-
executeClaimRewardTransaction(params: ClaimRewardTransactionParams, mutation: (current: {
|
|
115
|
-
readonly stock: number | null;
|
|
116
|
-
readonly secondaryStock: number | null;
|
|
117
|
-
readonly claimCount: number;
|
|
118
|
-
readonly userState: UserRallyState;
|
|
119
|
-
}) => ClaimRewardTransactionMutation): Promise<{
|
|
129
|
+
executeClaimRewardTransaction(params: ClaimRewardTransactionParams, mutation: (current: ClaimRewardMutationContext) => ClaimRewardTransactionMutation): Promise<{
|
|
120
130
|
readonly success: boolean;
|
|
121
131
|
readonly error?: string;
|
|
122
132
|
}>;
|
|
@@ -175,11 +185,7 @@ interface SqlClaimRewardStore<Tx> {
|
|
|
175
185
|
* while unexpected write failures reject the transaction and roll everything
|
|
176
186
|
* back.
|
|
177
187
|
*/
|
|
178
|
-
declare function executeClaimRewardTransaction<Tx>(database: SqlTransactionDatabase<Tx>, store: SqlClaimRewardStore<Tx>, params: ClaimRewardTransactionParams, mutation: (current: {
|
|
179
|
-
readonly stock: number | null;
|
|
180
|
-
readonly claimCount: number;
|
|
181
|
-
readonly userState: UserRallyState;
|
|
182
|
-
}) => ClaimRewardTransactionMutation): Promise<{
|
|
188
|
+
declare function executeClaimRewardTransaction<Tx>(database: SqlTransactionDatabase<Tx>, store: SqlClaimRewardStore<Tx>, params: ClaimRewardTransactionParams, mutation: (current: ClaimRewardMutationContext) => ClaimRewardTransactionMutation): Promise<{
|
|
183
189
|
readonly success: boolean;
|
|
184
190
|
readonly error?: string;
|
|
185
191
|
}>;
|
|
@@ -263,8 +269,8 @@ declare class StampRallyServer {
|
|
|
263
269
|
handleCheckIn(request: Request): Promise<Response>;
|
|
264
270
|
handleClaimReward(request: Request): Promise<Response>;
|
|
265
271
|
handleSync(request: Request): Promise<Response>;
|
|
266
|
-
checkIn(request: CheckInRequest): Promise<CheckInResponse>;
|
|
267
|
-
claimReward(request: ClaimRewardRequest): Promise<ClaimResponse>;
|
|
272
|
+
checkIn(request: CheckInRequest, authContext?: TrustedAuthContext): Promise<CheckInResponse>;
|
|
273
|
+
claimReward(request: ClaimRewardRequest, authContext?: TrustedAuthContext): Promise<ClaimResponse>;
|
|
268
274
|
sync(rallyId: string, userId: string): Promise<UserRallyState>;
|
|
269
275
|
syncProgress(request: {
|
|
270
276
|
readonly rallyId: string;
|
|
@@ -323,13 +329,10 @@ type CheckInResponse = {
|
|
|
323
329
|
interface ServerOptions {
|
|
324
330
|
readonly lockTtlMs?: number;
|
|
325
331
|
readonly idempotencyTtlMs?: number;
|
|
326
|
-
readonly authenticate?: (request: Request) => Promise<string |
|
|
332
|
+
readonly authenticate?: (request: Request) => Promise<string | TrustedAuthContext | null> | string | TrustedAuthContext | null;
|
|
327
333
|
readonly customValidators?: Readonly<Record<string, _stamprally_core.Validator>>;
|
|
328
334
|
readonly now?: () => string;
|
|
329
335
|
readonly anonymousPolicy?: "session_scoped" | "reject" | "shared_global_opt_in_insecure";
|
|
330
336
|
}
|
|
331
|
-
interface AuthenticationContext {
|
|
332
|
-
readonly authenticatedUserId: string;
|
|
333
|
-
}
|
|
334
337
|
|
|
335
|
-
export { type AuditLog, type AuthenticationContext, type CheckInRequest, type CheckInResponse, type CheckInTransactionMutation, type CheckInTransactionParams, type ClaimRewardRequest, type ClaimRewardTransactionMutation, type ClaimRewardTransactionParams, InMemoryServerPersistenceAdapter, type InMemoryServerPersistenceOptions, type RedisMultiExecutor, type RequestValidationError, RequestValidationException, type RequestValidationResult, type ServerOptions, type ServerPersistenceAdapter, type SqlCheckInStore, type SqlClaimRewardStore, type SqlTransactionDatabase, StampRallyServer, type SyncOperationStatus, type UserClaimRecord, assertValidCheckInParams, assertValidClaimParams, assertValidSyncParams, executeCheckInTransaction, executeClaimRewardTransaction, executeRedisTransaction, runPersistenceAdapterComplianceTests, validateCheckInRequest, validateClaimRewardRequest, validateSyncRequest };
|
|
338
|
+
export { type AuditLog, type AuthenticationContext, type CheckInRequest, type CheckInResponse, type CheckInTransactionMutation, type CheckInTransactionParams, type ClaimRewardMutationContext, type ClaimRewardRequest, type ClaimRewardTransactionMutation, type ClaimRewardTransactionParams, InMemoryServerPersistenceAdapter, type InMemoryServerPersistenceOptions, type RedisMultiExecutor, type RequestValidationError, RequestValidationException, type RequestValidationResult, type ServerOptions, type ServerPersistenceAdapter, type SqlCheckInStore, type SqlClaimRewardStore, type SqlTransactionDatabase, StampRallyServer, type SyncOperationStatus, type TrustedAuthContext, type UserClaimRecord, assertValidCheckInParams, assertValidClaimParams, assertValidSyncParams, executeCheckInTransaction, executeClaimRewardTransaction, executeRedisTransaction, runPersistenceAdapterComplianceTests, validateCheckInRequest, validateClaimRewardRequest, validateSyncRequest };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,16 @@ import * as _stamprally_core from '@stamprally/core';
|
|
|
2
2
|
import { UserRallyState, AdminRallyConfig } from '@stamprally/core';
|
|
3
3
|
export { UserRallyState } from '@stamprally/core';
|
|
4
4
|
|
|
5
|
+
/** Authentication already verified by the host application's middleware. */
|
|
6
|
+
interface TrustedAuthContext {
|
|
7
|
+
readonly authenticatedUserId: string;
|
|
8
|
+
readonly isAnonymous?: boolean;
|
|
9
|
+
readonly sessionId?: string;
|
|
10
|
+
readonly claims?: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
/** @deprecated Use TrustedAuthContext. */
|
|
13
|
+
type AuthenticationContext = TrustedAuthContext;
|
|
14
|
+
|
|
5
15
|
interface UserClaimRecord {
|
|
6
16
|
readonly rallyId: string;
|
|
7
17
|
readonly userId: string;
|
|
@@ -39,6 +49,16 @@ interface ClaimRewardTransactionMutation {
|
|
|
39
49
|
/** A domain rejection is committed as an audit/idempotency record without mutations. */
|
|
40
50
|
readonly error?: string;
|
|
41
51
|
}
|
|
52
|
+
interface ClaimRewardMutationContext {
|
|
53
|
+
readonly rewardStock: number | null;
|
|
54
|
+
readonly sharedStock: number | null;
|
|
55
|
+
readonly primaryStock: number | null;
|
|
56
|
+
readonly secondaryStock: number | null;
|
|
57
|
+
/** @deprecated Use rewardStock. */
|
|
58
|
+
readonly stock: number | null;
|
|
59
|
+
readonly claimCount: number;
|
|
60
|
+
readonly userState: UserRallyState;
|
|
61
|
+
}
|
|
42
62
|
interface CheckInTransactionParams {
|
|
43
63
|
readonly rallyId: string;
|
|
44
64
|
readonly userId: string;
|
|
@@ -67,12 +87,7 @@ interface ServerPersistenceAdapter {
|
|
|
67
87
|
* Atomically commits stock, user state, claim count, audit log, and idempotency data.
|
|
68
88
|
* Adapters must roll back every write when the callback or any commit operation fails.
|
|
69
89
|
*/
|
|
70
|
-
executeClaimRewardTransaction(params: ClaimRewardTransactionParams, mutation: (current: {
|
|
71
|
-
readonly stock: number | null;
|
|
72
|
-
readonly secondaryStock: number | null;
|
|
73
|
-
readonly claimCount: number;
|
|
74
|
-
readonly userState: UserRallyState;
|
|
75
|
-
}) => ClaimRewardTransactionMutation): Promise<{
|
|
90
|
+
executeClaimRewardTransaction(params: ClaimRewardTransactionParams, mutation: (current: ClaimRewardMutationContext) => ClaimRewardTransactionMutation): Promise<{
|
|
76
91
|
readonly success: boolean;
|
|
77
92
|
readonly error?: string;
|
|
78
93
|
}>;
|
|
@@ -101,7 +116,7 @@ interface InMemoryServerPersistenceOptions {
|
|
|
101
116
|
}
|
|
102
117
|
declare class InMemoryServerPersistenceAdapter implements ServerPersistenceAdapter {
|
|
103
118
|
#private;
|
|
104
|
-
readonly supportsRewardStock
|
|
119
|
+
readonly supportsRewardStock: boolean;
|
|
105
120
|
constructor(options?: InMemoryServerPersistenceOptions);
|
|
106
121
|
acquireLock(rallyId: string, key: string, ttlMs: number): Promise<boolean>;
|
|
107
122
|
releaseLock(rallyId: string, key: string): Promise<void>;
|
|
@@ -111,12 +126,7 @@ declare class InMemoryServerPersistenceAdapter implements ServerPersistenceAdapt
|
|
|
111
126
|
remainingStock: number;
|
|
112
127
|
}>;
|
|
113
128
|
restoreRewardStock(rallyId: string, rewardId: string, count?: number): Promise<void>;
|
|
114
|
-
executeClaimRewardTransaction(params: ClaimRewardTransactionParams, mutation: (current: {
|
|
115
|
-
readonly stock: number | null;
|
|
116
|
-
readonly secondaryStock: number | null;
|
|
117
|
-
readonly claimCount: number;
|
|
118
|
-
readonly userState: UserRallyState;
|
|
119
|
-
}) => ClaimRewardTransactionMutation): Promise<{
|
|
129
|
+
executeClaimRewardTransaction(params: ClaimRewardTransactionParams, mutation: (current: ClaimRewardMutationContext) => ClaimRewardTransactionMutation): Promise<{
|
|
120
130
|
readonly success: boolean;
|
|
121
131
|
readonly error?: string;
|
|
122
132
|
}>;
|
|
@@ -175,11 +185,7 @@ interface SqlClaimRewardStore<Tx> {
|
|
|
175
185
|
* while unexpected write failures reject the transaction and roll everything
|
|
176
186
|
* back.
|
|
177
187
|
*/
|
|
178
|
-
declare function executeClaimRewardTransaction<Tx>(database: SqlTransactionDatabase<Tx>, store: SqlClaimRewardStore<Tx>, params: ClaimRewardTransactionParams, mutation: (current: {
|
|
179
|
-
readonly stock: number | null;
|
|
180
|
-
readonly claimCount: number;
|
|
181
|
-
readonly userState: UserRallyState;
|
|
182
|
-
}) => ClaimRewardTransactionMutation): Promise<{
|
|
188
|
+
declare function executeClaimRewardTransaction<Tx>(database: SqlTransactionDatabase<Tx>, store: SqlClaimRewardStore<Tx>, params: ClaimRewardTransactionParams, mutation: (current: ClaimRewardMutationContext) => ClaimRewardTransactionMutation): Promise<{
|
|
183
189
|
readonly success: boolean;
|
|
184
190
|
readonly error?: string;
|
|
185
191
|
}>;
|
|
@@ -263,8 +269,8 @@ declare class StampRallyServer {
|
|
|
263
269
|
handleCheckIn(request: Request): Promise<Response>;
|
|
264
270
|
handleClaimReward(request: Request): Promise<Response>;
|
|
265
271
|
handleSync(request: Request): Promise<Response>;
|
|
266
|
-
checkIn(request: CheckInRequest): Promise<CheckInResponse>;
|
|
267
|
-
claimReward(request: ClaimRewardRequest): Promise<ClaimResponse>;
|
|
272
|
+
checkIn(request: CheckInRequest, authContext?: TrustedAuthContext): Promise<CheckInResponse>;
|
|
273
|
+
claimReward(request: ClaimRewardRequest, authContext?: TrustedAuthContext): Promise<ClaimResponse>;
|
|
268
274
|
sync(rallyId: string, userId: string): Promise<UserRallyState>;
|
|
269
275
|
syncProgress(request: {
|
|
270
276
|
readonly rallyId: string;
|
|
@@ -323,13 +329,10 @@ type CheckInResponse = {
|
|
|
323
329
|
interface ServerOptions {
|
|
324
330
|
readonly lockTtlMs?: number;
|
|
325
331
|
readonly idempotencyTtlMs?: number;
|
|
326
|
-
readonly authenticate?: (request: Request) => Promise<string |
|
|
332
|
+
readonly authenticate?: (request: Request) => Promise<string | TrustedAuthContext | null> | string | TrustedAuthContext | null;
|
|
327
333
|
readonly customValidators?: Readonly<Record<string, _stamprally_core.Validator>>;
|
|
328
334
|
readonly now?: () => string;
|
|
329
335
|
readonly anonymousPolicy?: "session_scoped" | "reject" | "shared_global_opt_in_insecure";
|
|
330
336
|
}
|
|
331
|
-
interface AuthenticationContext {
|
|
332
|
-
readonly authenticatedUserId: string;
|
|
333
|
-
}
|
|
334
337
|
|
|
335
|
-
export { type AuditLog, type AuthenticationContext, type CheckInRequest, type CheckInResponse, type CheckInTransactionMutation, type CheckInTransactionParams, type ClaimRewardRequest, type ClaimRewardTransactionMutation, type ClaimRewardTransactionParams, InMemoryServerPersistenceAdapter, type InMemoryServerPersistenceOptions, type RedisMultiExecutor, type RequestValidationError, RequestValidationException, type RequestValidationResult, type ServerOptions, type ServerPersistenceAdapter, type SqlCheckInStore, type SqlClaimRewardStore, type SqlTransactionDatabase, StampRallyServer, type SyncOperationStatus, type UserClaimRecord, assertValidCheckInParams, assertValidClaimParams, assertValidSyncParams, executeCheckInTransaction, executeClaimRewardTransaction, executeRedisTransaction, runPersistenceAdapterComplianceTests, validateCheckInRequest, validateClaimRewardRequest, validateSyncRequest };
|
|
338
|
+
export { type AuditLog, type AuthenticationContext, type CheckInRequest, type CheckInResponse, type CheckInTransactionMutation, type CheckInTransactionParams, type ClaimRewardMutationContext, type ClaimRewardRequest, type ClaimRewardTransactionMutation, type ClaimRewardTransactionParams, InMemoryServerPersistenceAdapter, type InMemoryServerPersistenceOptions, type RedisMultiExecutor, type RequestValidationError, RequestValidationException, type RequestValidationResult, type ServerOptions, type ServerPersistenceAdapter, type SqlCheckInStore, type SqlClaimRewardStore, type SqlTransactionDatabase, StampRallyServer, type SyncOperationStatus, type TrustedAuthContext, type UserClaimRecord, assertValidCheckInParams, assertValidClaimParams, assertValidSyncParams, executeCheckInTransaction, executeClaimRewardTransaction, executeRedisTransaction, runPersistenceAdapterComplianceTests, validateCheckInRequest, validateClaimRewardRequest, validateSyncRequest };
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,16 @@ async function executeClaimRewardTransaction(database, store, params2, mutation2
|
|
|
5
5
|
try {
|
|
6
6
|
return await database.transaction(async (transaction) => {
|
|
7
7
|
const current = await store.readContext(transaction, params2);
|
|
8
|
-
const
|
|
8
|
+
const secondaryStock = current.secondaryStock ?? null;
|
|
9
|
+
const rewardStock2 = params2.stockKey === "__shared__" ? secondaryStock : current.stock;
|
|
10
|
+
const next = mutation2({
|
|
11
|
+
...current,
|
|
12
|
+
rewardStock: rewardStock2,
|
|
13
|
+
sharedStock: params2.stockKey === "__shared__" ? current.stock : null,
|
|
14
|
+
primaryStock: rewardStock2,
|
|
15
|
+
secondaryStock,
|
|
16
|
+
stock: current.stock
|
|
17
|
+
});
|
|
9
18
|
if (next.error !== void 0) {
|
|
10
19
|
await store.writeAudit(transaction, next.auditLog);
|
|
11
20
|
if (params2.idempotencyKey !== void 0 && next.result !== void 0)
|
|
@@ -13,7 +22,7 @@ async function executeClaimRewardTransaction(database, store, params2, mutation2
|
|
|
13
22
|
return { success: false, error: next.error };
|
|
14
23
|
}
|
|
15
24
|
if (next.nextSecondaryStock !== void 0 && store.writeSecondaryStock === void 0)
|
|
16
|
-
return { success: false, error: "
|
|
25
|
+
return { success: false, error: "INVENTORY_STORAGE_NOT_IMPLEMENTED" };
|
|
17
26
|
if (next.nextStock !== null) await store.writeStock(transaction, params2, next.nextStock);
|
|
18
27
|
if (next.nextSecondaryStock !== void 0 && store.writeSecondaryStock !== void 0) {
|
|
19
28
|
if (next.nextSecondaryStock !== null)
|
|
@@ -144,9 +153,16 @@ var InMemoryServerPersistenceAdapter = class {
|
|
|
144
153
|
params2.stockKey ?? params2.rewardId
|
|
145
154
|
);
|
|
146
155
|
const storedSecondaryStock = params2.secondaryStockKey === void 0 ? null : await this.getRewardStock(params2.rallyId, params2.secondaryStockKey);
|
|
156
|
+
const stock = storedStock ?? initialStock;
|
|
157
|
+
const secondaryStock = storedSecondaryStock ?? initialSecondaryStock;
|
|
158
|
+
const rewardStock2 = params2.stockKey === "__shared__" ? secondaryStock : stock;
|
|
159
|
+
const sharedStock2 = params2.stockKey === "__shared__" ? stock : null;
|
|
147
160
|
const mutationResult = mutation2({
|
|
148
|
-
|
|
149
|
-
|
|
161
|
+
rewardStock: rewardStock2,
|
|
162
|
+
sharedStock: sharedStock2,
|
|
163
|
+
primaryStock: rewardStock2,
|
|
164
|
+
secondaryStock,
|
|
165
|
+
stock,
|
|
150
166
|
claimCount: await this.getUserClaimCount(params2.rallyId, params2.userId, params2.rewardId),
|
|
151
167
|
userState
|
|
152
168
|
});
|
|
@@ -678,7 +694,18 @@ function operationStatus(result) {
|
|
|
678
694
|
if (result.ok) return "ACCEPTED";
|
|
679
695
|
return result.code === "CONFLICT" || result.code === "PERSISTENCE_FAILED" ? "RETRYABLE_ERROR" : "REJECTED_PERMANENT";
|
|
680
696
|
}
|
|
681
|
-
function withDirectIdentity(request) {
|
|
697
|
+
function withDirectIdentity(request, authContext) {
|
|
698
|
+
if (authContext !== void 0) {
|
|
699
|
+
if (authContext.authenticatedUserId.trim() === "")
|
|
700
|
+
throw new RequestValidationException([
|
|
701
|
+
{
|
|
702
|
+
path: "authContext.authenticatedUserId",
|
|
703
|
+
message: "An authenticated userId is required.",
|
|
704
|
+
code: "REQUIRED"
|
|
705
|
+
}
|
|
706
|
+
]);
|
|
707
|
+
return { ...request, userId: authContext.authenticatedUserId };
|
|
708
|
+
}
|
|
682
709
|
if (request.userId !== void 0) return { ...request, userId: request.userId };
|
|
683
710
|
if (request.anonymousSessionId !== void 0 && isUuidV4(request.anonymousSessionId))
|
|
684
711
|
return { ...request, userId: request.anonymousSessionId };
|
|
@@ -808,8 +835,8 @@ var StampRallyServer = class {
|
|
|
808
835
|
throw error;
|
|
809
836
|
}
|
|
810
837
|
}
|
|
811
|
-
async checkIn(request) {
|
|
812
|
-
const directRequest = withDirectIdentity(request);
|
|
838
|
+
async checkIn(request, authContext) {
|
|
839
|
+
const directRequest = withDirectIdentity(request, authContext);
|
|
813
840
|
assertValidCheckInParams(directRequest, this.#config);
|
|
814
841
|
const { userId } = directRequest;
|
|
815
842
|
const key = `check-in:${request.rallyId}:${userId}:${request.idempotencyKey}`;
|
|
@@ -942,8 +969,8 @@ var StampRallyServer = class {
|
|
|
942
969
|
await this.#persistence.releaseLock(request.rallyId, lockKey);
|
|
943
970
|
}
|
|
944
971
|
}
|
|
945
|
-
async claimReward(request) {
|
|
946
|
-
const directRequest = withDirectIdentity(request);
|
|
972
|
+
async claimReward(request, authContext) {
|
|
973
|
+
const directRequest = withDirectIdentity(request, authContext);
|
|
947
974
|
assertValidClaimParams(directRequest, this.#config);
|
|
948
975
|
const { userId } = directRequest;
|
|
949
976
|
const key = `claim:${request.rallyId}:${userId}:${request.rewardId}:${request.idempotencyKey}`;
|
|
@@ -961,13 +988,14 @@ var StampRallyServer = class {
|
|
|
961
988
|
now(this.#options)
|
|
962
989
|
);
|
|
963
990
|
const plan = inventoryPlan(this.#config, reward.id, reward.stockLimit);
|
|
964
|
-
|
|
991
|
+
const inventoryEnabled = plan.primaryInitial !== null || plan.secondaryInitial !== void 0 && plan.secondaryInitial !== null;
|
|
992
|
+
if (inventoryEnabled && (this.#persistence.supportsRewardStock === false || typeof this.#persistence.getRewardStock !== "function" || typeof this.#persistence.executeClaimRewardTransaction !== "function"))
|
|
965
993
|
return this.#rememberClaim(
|
|
966
994
|
key,
|
|
967
995
|
{
|
|
968
996
|
ok: false,
|
|
969
|
-
code: "
|
|
970
|
-
message: "This persistence adapter cannot store
|
|
997
|
+
code: "INVENTORY_STORAGE_NOT_IMPLEMENTED",
|
|
998
|
+
message: "This persistence adapter cannot atomically store inventory."
|
|
971
999
|
},
|
|
972
1000
|
directRequest,
|
|
973
1001
|
now(this.#options)
|
|
@@ -1098,11 +1126,11 @@ var StampRallyServer = class {
|
|
|
1098
1126
|
const response = responseHolder.value;
|
|
1099
1127
|
if (!result.success) {
|
|
1100
1128
|
if (response !== null && !response.ok && response.code === result.error) return response;
|
|
1101
|
-
if (result.error === "
|
|
1129
|
+
if (result.error === "INVENTORY_STORAGE_NOT_IMPLEMENTED")
|
|
1102
1130
|
return {
|
|
1103
1131
|
ok: false,
|
|
1104
|
-
code: "
|
|
1105
|
-
message: "This persistence adapter cannot store
|
|
1132
|
+
code: "INVENTORY_STORAGE_NOT_IMPLEMENTED",
|
|
1133
|
+
message: "This persistence adapter cannot atomically store inventory."
|
|
1106
1134
|
};
|
|
1107
1135
|
return {
|
|
1108
1136
|
ok: false,
|
|
@@ -1310,6 +1338,15 @@ async function runPersistenceAdapterComplianceTests(createAdapter) {
|
|
|
1310
1338
|
await adapter.getRewardStock("compliance-rally", "reward") === 0,
|
|
1311
1339
|
"per-reward stock was not decremented atomically"
|
|
1312
1340
|
);
|
|
1341
|
+
const exhausted = await adapter.executeClaimRewardTransaction(
|
|
1342
|
+
params("carol", "boundary"),
|
|
1343
|
+
mutation
|
|
1344
|
+
);
|
|
1345
|
+
assert(!exhausted.success, "a zero-stock claim was accepted");
|
|
1346
|
+
assert(
|
|
1347
|
+
await adapter.getRewardStock("compliance-rally", "__shared__") === 0 && await adapter.getRewardStock("compliance-rally", "reward") === 0,
|
|
1348
|
+
"zero-stock rejection did not preserve both stock boundaries"
|
|
1349
|
+
);
|
|
1313
1350
|
const idempotentAdapter = await createAdapter();
|
|
1314
1351
|
const idempotentParams = params("alice", "same-key");
|
|
1315
1352
|
const firstClaim = await idempotentAdapter.executeClaimRewardTransaction(
|
|
@@ -1325,6 +1362,10 @@ async function runPersistenceAdapterComplianceTests(createAdapter) {
|
|
|
1325
1362
|
await idempotentAdapter.getRewardStock("compliance-rally", "__shared__") === 0,
|
|
1326
1363
|
"idempotent retry decremented shared stock twice"
|
|
1327
1364
|
);
|
|
1365
|
+
assert(
|
|
1366
|
+
await idempotentAdapter.getRewardStock("compliance-rally", "reward") === 0,
|
|
1367
|
+
"idempotent retry decremented per-reward stock twice"
|
|
1368
|
+
);
|
|
1328
1369
|
const rollbackAdapter = await createAdapter();
|
|
1329
1370
|
const rollbackParams = params("alice", "rollback");
|
|
1330
1371
|
const rollback = await rollbackAdapter.executeClaimRewardTransaction(rollbackParams, () => {
|