@mpgd/adapter-devvit 0.9.11 → 0.9.13

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.
@@ -35,8 +35,10 @@ function normalizeDevvitOrder(input, expectedStatus) {
35
35
  }
36
36
  const product = requireRecord(record.products[0], 'Devvit payment product');
37
37
  const orderId = requireString(record.id, 'order.id');
38
- if (record.userId !== undefined
39
- && requireString(record.userId, 'order.userId') !== playerId) {
38
+ // The order must carry the purchasing user so the server-side binding to the
39
+ // authenticated context is always enforced; an order without userId cannot be
40
+ // attributed and must not be fulfilled or refunded.
41
+ if (requireString(record.userId, 'order.userId') !== playerId) {
40
42
  throw new TypeError('Devvit payment order userId must match the authenticated context playerId.');
41
43
  }
42
44
  const platformSku = requireString(product.sku, 'order.products[0].sku');
@@ -98,17 +98,26 @@ async function mutateIfValue(input) {
98
98
  }
99
99
  await transaction.multi();
100
100
  multiStarted = true;
101
- await input.queueMutation(transaction);
101
+ const counting = countQueuedCommands(transaction);
102
+ await input.queueMutation(counting.transaction);
103
+ if (counting.count() === 0) {
104
+ throw new Error('Devvit Redis transaction queued no commands before EXEC.');
105
+ }
102
106
  const results = await transaction.exec();
107
+ // `@devvit/redis` `TxClient.exec()` never resolves `null`: it maps the
108
+ // server reply to an array, so an aborted WATCH transaction surfaces as
109
+ // an empty array. Fewer results than queued commands is a conflict; `null`
110
+ // stays a conflict for clients that follow the classic Redis contract.
103
111
  if (results === null) {
104
112
  continue;
105
113
  }
106
114
  if (!Array.isArray(results)) {
107
115
  throw new Error('Devvit Redis transaction returned an unsupported response.');
108
116
  }
109
- if (results.length > 0) {
110
- return true;
117
+ if (results.length < counting.count()) {
118
+ continue;
111
119
  }
120
+ return true;
112
121
  }
113
122
  catch (error) {
114
123
  await bestEffortReset(transaction, multiStarted);
@@ -117,6 +126,33 @@ async function mutateIfValue(input) {
117
126
  }
118
127
  throw new Error(`Devvit Redis transaction contention exceeded ${String(input.transactionAttempts)} attempts for key: ${input.key}`);
119
128
  }
129
+ /**
130
+ * Wraps a transaction so the number of commands queued after MULTI is known
131
+ * when EXEC resolves, which is what distinguishes a completed transaction from
132
+ * an aborted one on the Devvit Redis client.
133
+ */
134
+ function countQueuedCommands(transaction) {
135
+ let queued = 0;
136
+ return {
137
+ count: () => queued,
138
+ transaction: {
139
+ multi: () => transaction.multi(),
140
+ discard: () => transaction.discard(),
141
+ exec: () => transaction.exec(),
142
+ unwatch: () => transaction.unwatch(),
143
+ async set(key, value, options) {
144
+ const result = await transaction.set(key, value, options);
145
+ queued += 1;
146
+ return result;
147
+ },
148
+ async del(...keys) {
149
+ const result = await transaction.del(...keys);
150
+ queued += 1;
151
+ return result;
152
+ },
153
+ },
154
+ };
155
+ }
120
156
  async function bestEffortReset(transaction, multiStarted) {
121
157
  try {
122
158
  if (multiStarted) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/adapter-devvit",
3
- "version": "0.9.11",
3
+ "version": "0.9.13",
4
4
  "description": "Reddit Devvit Web platform adapter for mpgd games.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -56,8 +56,8 @@
56
56
  "dependencies": {
57
57
  "@devvit/web": "0.14.1",
58
58
  "@mpgd/bridge": "0.10.0",
59
- "@mpgd/platform": "0.13.0",
60
- "@mpgd/game-services": "0.16.0"
59
+ "@mpgd/game-services": "0.17.1",
60
+ "@mpgd/platform": "0.14.0"
61
61
  },
62
62
  "devDependencies": {
63
63
  "ttsc": "0.30.4",