@stamprally/server 0.17.0 → 0.18.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 +45 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +45 -18
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -93,6 +93,8 @@ interface ServerPersistenceAdapter {
|
|
|
93
93
|
}>;
|
|
94
94
|
/** Set false when the adapter cannot persist per-reward inventory atomically. */
|
|
95
95
|
readonly supportsRewardStock?: boolean;
|
|
96
|
+
/** Must be true when a claim plan uses `secondaryStockKey`. */
|
|
97
|
+
readonly supportsSecondaryStock?: boolean;
|
|
96
98
|
acquireLock(rallyId: string, lockKey: string, ttlMs: number): Promise<boolean>;
|
|
97
99
|
releaseLock(rallyId: string, lockKey: string): Promise<void>;
|
|
98
100
|
getRewardStock(rallyId: string, rewardId: string): Promise<number | null>;
|
|
@@ -117,6 +119,7 @@ interface InMemoryServerPersistenceOptions {
|
|
|
117
119
|
declare class InMemoryServerPersistenceAdapter implements ServerPersistenceAdapter {
|
|
118
120
|
#private;
|
|
119
121
|
readonly supportsRewardStock: boolean;
|
|
122
|
+
readonly supportsSecondaryStock: boolean;
|
|
120
123
|
constructor(options?: InMemoryServerPersistenceOptions);
|
|
121
124
|
acquireLock(rallyId: string, key: string, ttlMs: number): Promise<boolean>;
|
|
122
125
|
releaseLock(rallyId: string, key: string): Promise<void>;
|
|
@@ -271,12 +274,12 @@ declare class StampRallyServer {
|
|
|
271
274
|
handleSync(request: Request): Promise<Response>;
|
|
272
275
|
checkIn(request: CheckInRequest, authContext?: TrustedAuthContext): Promise<CheckInResponse>;
|
|
273
276
|
claimReward(request: ClaimRewardRequest, authContext?: TrustedAuthContext): Promise<ClaimResponse>;
|
|
274
|
-
sync(rallyId: string,
|
|
277
|
+
sync(rallyId: string, identity: string | TrustedAuthContext): Promise<UserRallyState>;
|
|
275
278
|
syncProgress(request: {
|
|
276
279
|
readonly rallyId: string;
|
|
277
280
|
readonly userId?: string;
|
|
278
281
|
readonly anonymousSessionId?: string;
|
|
279
|
-
}): Promise<UserRallyState>;
|
|
282
|
+
}, authContext: TrustedAuthContext): Promise<UserRallyState>;
|
|
280
283
|
}
|
|
281
284
|
|
|
282
285
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -93,6 +93,8 @@ interface ServerPersistenceAdapter {
|
|
|
93
93
|
}>;
|
|
94
94
|
/** Set false when the adapter cannot persist per-reward inventory atomically. */
|
|
95
95
|
readonly supportsRewardStock?: boolean;
|
|
96
|
+
/** Must be true when a claim plan uses `secondaryStockKey`. */
|
|
97
|
+
readonly supportsSecondaryStock?: boolean;
|
|
96
98
|
acquireLock(rallyId: string, lockKey: string, ttlMs: number): Promise<boolean>;
|
|
97
99
|
releaseLock(rallyId: string, lockKey: string): Promise<void>;
|
|
98
100
|
getRewardStock(rallyId: string, rewardId: string): Promise<number | null>;
|
|
@@ -117,6 +119,7 @@ interface InMemoryServerPersistenceOptions {
|
|
|
117
119
|
declare class InMemoryServerPersistenceAdapter implements ServerPersistenceAdapter {
|
|
118
120
|
#private;
|
|
119
121
|
readonly supportsRewardStock: boolean;
|
|
122
|
+
readonly supportsSecondaryStock: boolean;
|
|
120
123
|
constructor(options?: InMemoryServerPersistenceOptions);
|
|
121
124
|
acquireLock(rallyId: string, key: string, ttlMs: number): Promise<boolean>;
|
|
122
125
|
releaseLock(rallyId: string, key: string): Promise<void>;
|
|
@@ -271,12 +274,12 @@ declare class StampRallyServer {
|
|
|
271
274
|
handleSync(request: Request): Promise<Response>;
|
|
272
275
|
checkIn(request: CheckInRequest, authContext?: TrustedAuthContext): Promise<CheckInResponse>;
|
|
273
276
|
claimReward(request: ClaimRewardRequest, authContext?: TrustedAuthContext): Promise<ClaimResponse>;
|
|
274
|
-
sync(rallyId: string,
|
|
277
|
+
sync(rallyId: string, identity: string | TrustedAuthContext): Promise<UserRallyState>;
|
|
275
278
|
syncProgress(request: {
|
|
276
279
|
readonly rallyId: string;
|
|
277
280
|
readonly userId?: string;
|
|
278
281
|
readonly anonymousSessionId?: string;
|
|
279
|
-
}): Promise<UserRallyState>;
|
|
282
|
+
}, authContext: TrustedAuthContext): Promise<UserRallyState>;
|
|
280
283
|
}
|
|
281
284
|
|
|
282
285
|
/**
|
package/dist/index.js
CHANGED
|
@@ -76,6 +76,7 @@ async function executeRedisTransaction(redis, queue) {
|
|
|
76
76
|
// src/persistence.ts
|
|
77
77
|
var InMemoryServerPersistenceAdapter = class {
|
|
78
78
|
supportsRewardStock = true;
|
|
79
|
+
supportsSecondaryStock = true;
|
|
79
80
|
#locks = /* @__PURE__ */ new Map();
|
|
80
81
|
#idempotent = /* @__PURE__ */ new Map();
|
|
81
82
|
#states = /* @__PURE__ */ new Map();
|
|
@@ -640,26 +641,41 @@ function initialState(config, userId, timestamp) {
|
|
|
640
641
|
updatedAt: timestamp
|
|
641
642
|
};
|
|
642
643
|
}
|
|
643
|
-
function rewardStock(config,
|
|
644
|
-
const
|
|
644
|
+
function rewardStock(config, reward) {
|
|
645
|
+
const stockLimit = reward.stockLimit;
|
|
646
|
+
const key = reward.stockKey ?? reward.id;
|
|
647
|
+
const configured = key === "__shared__" ? config.inventory?.sharedStock : config.inventory?.[key];
|
|
645
648
|
if (stockLimit === void 0) return configured ?? null;
|
|
646
649
|
if (configured === void 0) return stockLimit;
|
|
647
650
|
return Math.min(stockLimit, configured);
|
|
648
651
|
}
|
|
649
652
|
function sharedStock(config) {
|
|
650
|
-
return config.inventory?.sharedStock ??
|
|
653
|
+
return config.inventory?.sharedStock ?? null;
|
|
651
654
|
}
|
|
652
655
|
function inventoryPlan(config, rewardId, stockLimit) {
|
|
653
|
-
const
|
|
656
|
+
const reward = config.rewards.find((item) => item.id === rewardId);
|
|
657
|
+
const individual = reward === void 0 ? stockLimit ?? null : rewardStock(config, reward);
|
|
654
658
|
const shared = sharedStock(config);
|
|
659
|
+
const explicitPrimaryKey = reward?.stockKey;
|
|
660
|
+
const explicitSecondaryKey = reward?.secondaryStockKey;
|
|
655
661
|
if (config.inventoryMode === "shared" && shared !== null) {
|
|
656
662
|
return {
|
|
657
|
-
primaryKey: "__shared__",
|
|
658
|
-
primaryInitial: shared,
|
|
659
|
-
...
|
|
663
|
+
primaryKey: explicitPrimaryKey ?? "__shared__",
|
|
664
|
+
primaryInitial: explicitPrimaryKey === void 0 ? shared : individual,
|
|
665
|
+
...explicitSecondaryKey !== void 0 ? {
|
|
666
|
+
secondaryKey: explicitSecondaryKey,
|
|
667
|
+
secondaryInitial: config.inventory?.[explicitSecondaryKey] ?? individual
|
|
668
|
+
} : individual === null ? {} : { secondaryKey: rewardId, secondaryInitial: individual }
|
|
660
669
|
};
|
|
661
670
|
}
|
|
662
|
-
return {
|
|
671
|
+
return {
|
|
672
|
+
primaryKey: explicitPrimaryKey ?? rewardId,
|
|
673
|
+
primaryInitial: individual,
|
|
674
|
+
...explicitSecondaryKey === void 0 ? {} : {
|
|
675
|
+
secondaryKey: explicitSecondaryKey,
|
|
676
|
+
secondaryInitial: config.inventory?.[explicitSecondaryKey] ?? null
|
|
677
|
+
}
|
|
678
|
+
};
|
|
663
679
|
}
|
|
664
680
|
function getProof(context) {
|
|
665
681
|
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;
|
|
@@ -821,14 +837,20 @@ var StampRallyServer = class {
|
|
|
821
837
|
401
|
|
822
838
|
);
|
|
823
839
|
const sessionId = request.headers.get("x-anonymous-session-id");
|
|
840
|
+
const authContext = {
|
|
841
|
+
authenticatedUserId: userId,
|
|
842
|
+
...sessionId === null ? {} : { isAnonymous: true, sessionId }
|
|
843
|
+
};
|
|
824
844
|
try {
|
|
825
845
|
return json({
|
|
826
846
|
ok: true,
|
|
827
|
-
state: await this.syncProgress(
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
847
|
+
state: await this.syncProgress(
|
|
848
|
+
{
|
|
849
|
+
rallyId: body.data.rallyId,
|
|
850
|
+
...sessionId === null ? {} : { anonymousSessionId: sessionId }
|
|
851
|
+
},
|
|
852
|
+
authContext
|
|
853
|
+
)
|
|
832
854
|
});
|
|
833
855
|
} catch (error) {
|
|
834
856
|
if (error instanceof RequestValidationException) return validationResponse(error.errors);
|
|
@@ -988,6 +1010,8 @@ var StampRallyServer = class {
|
|
|
988
1010
|
now(this.#options)
|
|
989
1011
|
);
|
|
990
1012
|
const plan = inventoryPlan(this.#config, reward.id, reward.stockLimit);
|
|
1013
|
+
if (plan.secondaryKey !== void 0 && this.#persistence.supportsSecondaryStock !== true)
|
|
1014
|
+
throw new Error("SECONDARY_STOCK_UNSUPPORTED");
|
|
991
1015
|
const inventoryEnabled = plan.primaryInitial !== null || plan.secondaryInitial !== void 0 && plan.secondaryInitial !== null;
|
|
992
1016
|
if (inventoryEnabled && (this.#persistence.supportsRewardStock === false || typeof this.#persistence.getRewardStock !== "function" || typeof this.#persistence.executeClaimRewardTransaction !== "function"))
|
|
993
1017
|
return this.#rememberClaim(
|
|
@@ -1022,7 +1046,7 @@ var StampRallyServer = class {
|
|
|
1022
1046
|
rewardId: reward.id,
|
|
1023
1047
|
stockKey: plan.primaryKey,
|
|
1024
1048
|
...plan.secondaryKey === void 0 ? {} : { secondaryStockKey: plan.secondaryKey },
|
|
1025
|
-
rewardStockLimit: rewardStock(this.#config, reward
|
|
1049
|
+
rewardStockLimit: rewardStock(this.#config, reward),
|
|
1026
1050
|
sharedStockLimit: this.#config.inventoryMode === "shared" ? sharedStock(this.#config) : null,
|
|
1027
1051
|
initialStock: plan.primaryInitial,
|
|
1028
1052
|
...plan.secondaryInitial === void 0 ? {} : { initialSecondaryStock: plan.secondaryInitial },
|
|
@@ -1154,21 +1178,24 @@ var StampRallyServer = class {
|
|
|
1154
1178
|
await this.#persistence.releaseLock(request.rallyId, lockKey);
|
|
1155
1179
|
}
|
|
1156
1180
|
}
|
|
1157
|
-
async sync(rallyId,
|
|
1181
|
+
async sync(rallyId, identity) {
|
|
1182
|
+
const userId = typeof identity === "string" ? identity : identity.authenticatedUserId;
|
|
1158
1183
|
assertValidSyncParams({ rallyId, userId }, this.#config);
|
|
1159
1184
|
const state2 = await this.#persistence.getUserState(rallyId, userId) ?? initialState(this.#config, userId, now(this.#options));
|
|
1160
1185
|
return this.#attachInventory(state2);
|
|
1161
1186
|
}
|
|
1162
|
-
async syncProgress(request) {
|
|
1163
|
-
const directRequest = withDirectIdentity(request);
|
|
1187
|
+
async syncProgress(request, authContext) {
|
|
1188
|
+
const directRequest = withDirectIdentity(request, authContext);
|
|
1164
1189
|
assertValidSyncParams(directRequest, this.#config);
|
|
1165
|
-
return this.sync(directRequest.rallyId,
|
|
1190
|
+
return this.sync(directRequest.rallyId, authContext);
|
|
1166
1191
|
}
|
|
1167
1192
|
async #attachInventory(state2) {
|
|
1168
1193
|
const rewardRemaining = {};
|
|
1169
1194
|
for (const reward of this.#config.rewards) {
|
|
1170
1195
|
const plan = inventoryPlan(this.#config, reward.id, reward.stockLimit);
|
|
1171
1196
|
if (plan.secondaryKey !== void 0) {
|
|
1197
|
+
if (this.#persistence.supportsSecondaryStock !== true)
|
|
1198
|
+
throw new Error("SECONDARY_STOCK_UNSUPPORTED");
|
|
1172
1199
|
const stock = await this.#persistence.getRewardStock(state2.rallyId, plan.secondaryKey);
|
|
1173
1200
|
const remaining = stock ?? plan.secondaryInitial ?? null;
|
|
1174
1201
|
if (remaining !== null) rewardRemaining[reward.id] = Math.max(0, remaining);
|