@stamprally/server 0.17.0 → 0.19.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 +88 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -7
- package/dist/index.d.ts +20 -7
- package/dist/index.js +88 -18
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -4,9 +4,13 @@ var core = require('@stamprally/core');
|
|
|
4
4
|
|
|
5
5
|
// src/examples/transaction.ts
|
|
6
6
|
async function executeClaimRewardTransaction(database, store, params2, mutation2) {
|
|
7
|
+
if (params2.secondaryStockKey !== void 0 && store.writeSecondaryStock === void 0)
|
|
8
|
+
return { success: false, error: "SECONDARY_STOCK_UNSUPPORTED" };
|
|
7
9
|
try {
|
|
8
10
|
return await database.transaction(async (transaction) => {
|
|
9
11
|
const current = await store.readContext(transaction, params2);
|
|
12
|
+
if (params2.secondaryStockKey !== void 0 && current.secondaryStock === void 0)
|
|
13
|
+
return { success: false, error: "SECONDARY_STOCK_UNSUPPORTED" };
|
|
10
14
|
const secondaryStock = current.secondaryStock ?? null;
|
|
11
15
|
const rewardStock2 = params2.stockKey === "__shared__" ? secondaryStock : current.stock;
|
|
12
16
|
const next = mutation2({
|
|
@@ -78,6 +82,7 @@ async function executeRedisTransaction(redis, queue) {
|
|
|
78
82
|
// src/persistence.ts
|
|
79
83
|
var InMemoryServerPersistenceAdapter = class {
|
|
80
84
|
supportsRewardStock = true;
|
|
85
|
+
supportsSecondaryStock = true;
|
|
81
86
|
#locks = /* @__PURE__ */ new Map();
|
|
82
87
|
#idempotent = /* @__PURE__ */ new Map();
|
|
83
88
|
#states = /* @__PURE__ */ new Map();
|
|
@@ -597,6 +602,40 @@ function validateSyncRequest(value) {
|
|
|
597
602
|
return errors({ path: "$", message: "Expected an object.", code: "INVALID_TYPE" });
|
|
598
603
|
if (value.userId !== void 0 && !nonEmpty(value.userId))
|
|
599
604
|
return errors({ path: "userId", message: "userId must be non-empty.", code: "INVALID_TYPE" });
|
|
605
|
+
if (value.operations !== void 0) {
|
|
606
|
+
if (!Array.isArray(value.operations))
|
|
607
|
+
return errors({
|
|
608
|
+
path: "operations",
|
|
609
|
+
message: "operations must be an array.",
|
|
610
|
+
code: "INVALID_TYPE"
|
|
611
|
+
});
|
|
612
|
+
const operationErrors = [];
|
|
613
|
+
value.operations.forEach((operation, index) => {
|
|
614
|
+
const path = `operations[${index}]`;
|
|
615
|
+
if (!record(operation)) {
|
|
616
|
+
operationErrors.push({
|
|
617
|
+
path,
|
|
618
|
+
message: "Expected an operation object.",
|
|
619
|
+
code: "INVALID_TYPE"
|
|
620
|
+
});
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
if (operation.kind !== "checkIn" && operation.kind !== "claimReward") {
|
|
624
|
+
operationErrors.push({
|
|
625
|
+
path: `${path}.kind`,
|
|
626
|
+
message: "Unknown sync operation.",
|
|
627
|
+
code: "INVALID_ENUM"
|
|
628
|
+
});
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
const result = operation.kind === "checkIn" ? validateCheckInRequest(operation.request) : validateClaimRewardRequest(operation.request);
|
|
632
|
+
if (!result.success)
|
|
633
|
+
operationErrors.push(
|
|
634
|
+
...result.errors.map((error) => ({ ...error, path: `${path}.request.${error.path}` }))
|
|
635
|
+
);
|
|
636
|
+
});
|
|
637
|
+
if (operationErrors.length > 0) return errors(...operationErrors);
|
|
638
|
+
}
|
|
600
639
|
return {
|
|
601
640
|
success: true,
|
|
602
641
|
data: value
|
|
@@ -642,26 +681,41 @@ function initialState(config, userId, timestamp) {
|
|
|
642
681
|
updatedAt: timestamp
|
|
643
682
|
};
|
|
644
683
|
}
|
|
645
|
-
function rewardStock(config,
|
|
646
|
-
const
|
|
684
|
+
function rewardStock(config, reward) {
|
|
685
|
+
const stockLimit = reward.stockLimit;
|
|
686
|
+
const key = reward.stockKey ?? reward.id;
|
|
687
|
+
const configured = key === "__shared__" ? config.inventory?.sharedStock : config.inventory?.[key];
|
|
647
688
|
if (stockLimit === void 0) return configured ?? null;
|
|
648
689
|
if (configured === void 0) return stockLimit;
|
|
649
690
|
return Math.min(stockLimit, configured);
|
|
650
691
|
}
|
|
651
692
|
function sharedStock(config) {
|
|
652
|
-
return config.inventory?.sharedStock ??
|
|
693
|
+
return config.inventory?.sharedStock ?? null;
|
|
653
694
|
}
|
|
654
695
|
function inventoryPlan(config, rewardId, stockLimit) {
|
|
655
|
-
const
|
|
696
|
+
const reward = config.rewards.find((item) => item.id === rewardId);
|
|
697
|
+
const individual = reward === void 0 ? stockLimit ?? null : rewardStock(config, reward);
|
|
656
698
|
const shared = sharedStock(config);
|
|
699
|
+
const explicitPrimaryKey = reward?.stockKey;
|
|
700
|
+
const explicitSecondaryKey = reward?.secondaryStockKey;
|
|
657
701
|
if (config.inventoryMode === "shared" && shared !== null) {
|
|
658
702
|
return {
|
|
659
|
-
primaryKey: "__shared__",
|
|
660
|
-
primaryInitial: shared,
|
|
661
|
-
...
|
|
703
|
+
primaryKey: explicitPrimaryKey ?? "__shared__",
|
|
704
|
+
primaryInitial: explicitPrimaryKey === void 0 ? shared : individual,
|
|
705
|
+
...explicitSecondaryKey !== void 0 ? {
|
|
706
|
+
secondaryKey: explicitSecondaryKey,
|
|
707
|
+
secondaryInitial: config.inventory?.[explicitSecondaryKey] ?? individual
|
|
708
|
+
} : individual === null ? {} : { secondaryKey: rewardId, secondaryInitial: individual }
|
|
662
709
|
};
|
|
663
710
|
}
|
|
664
|
-
return {
|
|
711
|
+
return {
|
|
712
|
+
primaryKey: explicitPrimaryKey ?? rewardId,
|
|
713
|
+
primaryInitial: individual,
|
|
714
|
+
...explicitSecondaryKey === void 0 ? {} : {
|
|
715
|
+
secondaryKey: explicitSecondaryKey,
|
|
716
|
+
secondaryInitial: config.inventory?.[explicitSecondaryKey] ?? null
|
|
717
|
+
}
|
|
718
|
+
};
|
|
665
719
|
}
|
|
666
720
|
function getProof(context) {
|
|
667
721
|
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;
|
|
@@ -823,14 +877,21 @@ var StampRallyServer = class {
|
|
|
823
877
|
401
|
|
824
878
|
);
|
|
825
879
|
const sessionId = request.headers.get("x-anonymous-session-id");
|
|
880
|
+
const authContext = {
|
|
881
|
+
authenticatedUserId: userId,
|
|
882
|
+
...sessionId === null ? {} : { isAnonymous: true, sessionId }
|
|
883
|
+
};
|
|
826
884
|
try {
|
|
827
885
|
return json({
|
|
828
886
|
ok: true,
|
|
829
|
-
state: await this.syncProgress(
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
887
|
+
state: await this.syncProgress(
|
|
888
|
+
{
|
|
889
|
+
rallyId: body.data.rallyId,
|
|
890
|
+
...body.data.operations === void 0 ? {} : { operations: body.data.operations },
|
|
891
|
+
...sessionId === null ? {} : { anonymousSessionId: sessionId }
|
|
892
|
+
},
|
|
893
|
+
authContext
|
|
894
|
+
)
|
|
834
895
|
});
|
|
835
896
|
} catch (error) {
|
|
836
897
|
if (error instanceof RequestValidationException) return validationResponse(error.errors);
|
|
@@ -990,6 +1051,8 @@ var StampRallyServer = class {
|
|
|
990
1051
|
now(this.#options)
|
|
991
1052
|
);
|
|
992
1053
|
const plan = inventoryPlan(this.#config, reward.id, reward.stockLimit);
|
|
1054
|
+
if (plan.secondaryKey !== void 0 && this.#persistence.supportsSecondaryStock !== true)
|
|
1055
|
+
throw new Error("SECONDARY_STOCK_UNSUPPORTED");
|
|
993
1056
|
const inventoryEnabled = plan.primaryInitial !== null || plan.secondaryInitial !== void 0 && plan.secondaryInitial !== null;
|
|
994
1057
|
if (inventoryEnabled && (this.#persistence.supportsRewardStock === false || typeof this.#persistence.getRewardStock !== "function" || typeof this.#persistence.executeClaimRewardTransaction !== "function"))
|
|
995
1058
|
return this.#rememberClaim(
|
|
@@ -1024,7 +1087,7 @@ var StampRallyServer = class {
|
|
|
1024
1087
|
rewardId: reward.id,
|
|
1025
1088
|
stockKey: plan.primaryKey,
|
|
1026
1089
|
...plan.secondaryKey === void 0 ? {} : { secondaryStockKey: plan.secondaryKey },
|
|
1027
|
-
rewardStockLimit: rewardStock(this.#config, reward
|
|
1090
|
+
rewardStockLimit: rewardStock(this.#config, reward),
|
|
1028
1091
|
sharedStockLimit: this.#config.inventoryMode === "shared" ? sharedStock(this.#config) : null,
|
|
1029
1092
|
initialStock: plan.primaryInitial,
|
|
1030
1093
|
...plan.secondaryInitial === void 0 ? {} : { initialSecondaryStock: plan.secondaryInitial },
|
|
@@ -1156,21 +1219,28 @@ var StampRallyServer = class {
|
|
|
1156
1219
|
await this.#persistence.releaseLock(request.rallyId, lockKey);
|
|
1157
1220
|
}
|
|
1158
1221
|
}
|
|
1159
|
-
async sync(rallyId,
|
|
1222
|
+
async sync(rallyId, identity) {
|
|
1223
|
+
const userId = typeof identity === "string" ? identity : identity.authenticatedUserId;
|
|
1160
1224
|
assertValidSyncParams({ rallyId, userId }, this.#config);
|
|
1161
1225
|
const state2 = await this.#persistence.getUserState(rallyId, userId) ?? initialState(this.#config, userId, now(this.#options));
|
|
1162
1226
|
return this.#attachInventory(state2);
|
|
1163
1227
|
}
|
|
1164
|
-
async syncProgress(request) {
|
|
1165
|
-
const directRequest = withDirectIdentity(request);
|
|
1228
|
+
async syncProgress(request, authContext) {
|
|
1229
|
+
const directRequest = withDirectIdentity(request, authContext);
|
|
1166
1230
|
assertValidSyncParams(directRequest, this.#config);
|
|
1167
|
-
|
|
1231
|
+
for (const operation of request.operations ?? []) {
|
|
1232
|
+
if (operation.kind === "checkIn") await this.checkIn(operation.request, authContext);
|
|
1233
|
+
else await this.claimReward(operation.request, authContext);
|
|
1234
|
+
}
|
|
1235
|
+
return this.sync(directRequest.rallyId, authContext);
|
|
1168
1236
|
}
|
|
1169
1237
|
async #attachInventory(state2) {
|
|
1170
1238
|
const rewardRemaining = {};
|
|
1171
1239
|
for (const reward of this.#config.rewards) {
|
|
1172
1240
|
const plan = inventoryPlan(this.#config, reward.id, reward.stockLimit);
|
|
1173
1241
|
if (plan.secondaryKey !== void 0) {
|
|
1242
|
+
if (this.#persistence.supportsSecondaryStock !== true)
|
|
1243
|
+
throw new Error("SECONDARY_STOCK_UNSUPPORTED");
|
|
1174
1244
|
const stock = await this.#persistence.getRewardStock(state2.rallyId, plan.secondaryKey);
|
|
1175
1245
|
const remaining = stock ?? plan.secondaryInitial ?? null;
|
|
1176
1246
|
if (remaining !== null) rewardRemaining[reward.id] = Math.max(0, remaining);
|