@plusscommunities/pluss-core-aws 2.0.25-beta.1 → 2.0.25-beta.3

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.
@@ -19,7 +19,7 @@
19
19
 
20
20
  const AWS = require("aws-sdk");
21
21
  const moment = require("moment");
22
- const indexQuery = require("../db/common/indexQuery");
22
+ const indexQueryRecursive = require("../db/common/indexQueryRecursive");
23
23
  const updateRef = require("../db/common/updateRef");
24
24
  const editRef = require("../db/common/editRef");
25
25
 
@@ -94,12 +94,18 @@ const findCopiesByHqSourceId = async (
94
94
  if (!hqSourceId) {
95
95
  return [];
96
96
  }
97
- const result = await indexQuery(tableName, {
97
+ // indexQueryRecursive paginates LastEvaluatedKey internally and returns a
98
+ // flat array — required because a single GSI query response caps at 1 MiB
99
+ // and large-content HQ fan-outs to ~50 client sites can exceed that. With
100
+ // the non-recursive indexQuery, propagateHqEdit + cascadeHqRetract would
101
+ // silently miss copies on page 2+, leaving them stale after edits and
102
+ // visible after retractions.
103
+ const items = await indexQueryRecursive(tableName, {
98
104
  IndexName: indexName,
99
105
  KeyConditionExpression: "HqSourceId = :sid",
100
106
  ExpressionAttributeValues: { ":sid": hqSourceId },
101
107
  });
102
- return result?.Items || [];
108
+ return items || [];
103
109
  };
104
110
 
105
111
  /**
@@ -136,7 +142,14 @@ const fanOutHqSource = async (hqSourceRow, tableName) => {
136
142
  return [];
137
143
  }
138
144
  const copyId = `hqcopy-${hqSourceRow.Id}`;
139
- const now = moment.utc().unix();
145
+ // IMPORTANT: milliseconds (.valueOf), not seconds (.unix). Newsletter
146
+ // rows written by addNewsletterEntry.js use `moment.utc().valueOf()` for
147
+ // UnixTimestamp. UnixTimestampReverse-keyed feed queries (the standard
148
+ // Pluss list pattern) would otherwise rank HQ copies ~1000× ahead of
149
+ // locally-authored posts forever — MAX_SAFE_INTEGER minus a number 10^9
150
+ // is vastly larger than MAX_SAFE_INTEGER minus 10^12. Keep parity with
151
+ // the canonical addNewsletterEntry convention.
152
+ const now = moment.utc().valueOf();
140
153
 
141
154
  const writes = targets.map((targetSiteId) => {
142
155
  // Spread + override pattern: keep all content fields, swap identity
@@ -215,9 +228,17 @@ const propagateHqEdit = async (
215
228
  }
216
229
  propagated.Changed = moment.utc().unix();
217
230
 
231
+ // IMPORTANT: pass a fresh shallow copy per call. `editRef` synchronously
232
+ // mutates its `updates` argument (`updates[keyCol] = id` inside the Promise
233
+ // constructor) AFTER taking a `cloneDeep` snapshot. Across parallel
234
+ // `Promise.all(map(...))` invocations, iteration N's `cloneDeep` would
235
+ // capture iteration N-1's `RowId` mutation, then later overwrite copy N's
236
+ // `RowId` during the get-merge-put, writing copy N's content back to copy
237
+ // N-1's key (and leaving copy N's own row untouched). Per-call `{...propagated}`
238
+ // gives each invocation an isolated payload it can mutate safely.
218
239
  await Promise.all(
219
240
  copies.map((copy) =>
220
- editRef(tableName, "RowId", copy.RowId, propagated),
241
+ editRef(tableName, "RowId", copy.RowId, { ...propagated }),
221
242
  ),
222
243
  );
223
244
  return copies.length;
@@ -256,9 +277,13 @@ const cascadeHqRetract = async (hqSourceRow, tableName, options = {}) => {
256
277
  Deleted: true,
257
278
  Changed: moment.utc().unix(),
258
279
  };
280
+ // Per-call shallow copy — see propagateHqEdit for the editRef-mutation-race
281
+ // rationale. Even though `deletionUpdate` is tiny, the same RowId clobber
282
+ // happens here: under sharing, only the FIRST copy actually receives
283
+ // `Deleted: true`; subsequent writes land at the wrong RowId.
259
284
  await Promise.all(
260
285
  copies.map((copy) =>
261
- editRef(tableName, "RowId", copy.RowId, deletionUpdate),
286
+ editRef(tableName, "RowId", copy.RowId, { ...deletionUpdate }),
262
287
  ),
263
288
  );
264
289
  return copies.length;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plusscommunities/pluss-core-aws",
3
- "version": "2.0.25-beta.1",
3
+ "version": "2.0.25-beta.3",
4
4
  "description": "Core extension package for Pluss Communities platform",
5
5
  "scripts": {
6
6
  "betapatch": "npm version prepatch --preid=beta",