@plusscommunities/pluss-core-aws 2.0.25-beta.7 → 2.0.25-beta.8

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.
@@ -144,6 +144,23 @@ const fanOutHqSource = async (hqSourceRow, tableName) => {
144
144
  if (targets.length === 0) {
145
145
  return [];
146
146
  }
147
+ return writeCopiesForTargets(hqSourceRow, targets, tableName);
148
+ };
149
+
150
+ /**
151
+ * Write one community-copy row per site in `targetSites` (all of the source's
152
+ * PublishedToSites on INSERT fan-out, or just the newly-added subset on a
153
+ * MODIFY target-set change). Shared by fanOutHqSource and syncHqTargets. The
154
+ * deterministic RowId makes each write idempotent AND makes re-adding a
155
+ * previously-removed (soft-deleted) site a lossless resurrection — the PUT
156
+ * overwrites the row and drops the Deleted flag.
157
+ *
158
+ * @param {object} hqSourceRow — the HQ-source row.
159
+ * @param {string[]} targetSites — sites to write copies for.
160
+ * @param {string} tableName — entity's table name.
161
+ * @returns {Promise<object[]>} the copy rows written.
162
+ */
163
+ const writeCopiesForTargets = (hqSourceRow, targetSites, tableName) => {
147
164
  const copyId = `hqcopy-${hqSourceRow.Id}`;
148
165
  // Preserve the HQ-source row's own UnixTimestamp on each copy rather than
149
166
  // stamping "now". For an immediate publish the source timestamp already IS
@@ -163,7 +180,7 @@ const fanOutHqSource = async (hqSourceRow, tableName) => {
163
180
  const now = moment.utc().valueOf();
164
181
  const publishTs = hqSourceRow.UnixTimestamp || now;
165
182
 
166
- const writes = targets.map((targetSiteId) => {
183
+ const writes = targetSites.map((targetSiteId) => {
167
184
  // Spread + override pattern: keep all content fields, swap identity
168
185
  // fields, drop HQ-only fields. Spread is shallow — nested objects
169
186
  // (Author, etc.) share references with the source row, which is fine
@@ -192,6 +209,63 @@ const fanOutHqSource = async (hqSourceRow, tableName) => {
192
209
  return Promise.all(writes);
193
210
  };
194
211
 
212
+ /**
213
+ * Reconcile community copies when an HQ-source MODIFY changes PublishedToSites:
214
+ * fan out copies for newly-ADDED sites, soft-delete copies for REMOVED sites.
215
+ * Content propagation to surviving copies is propagateHqEdit's job; this only
216
+ * reconciles the target SET. Re-adding a removed site resurrects its copy
217
+ * losslessly (deterministic RowId + PUT — see writeCopiesForTargets).
218
+ *
219
+ * MUST run sequentially relative to propagateHqEdit (never concurrently): both
220
+ * use editRef (get-merge-put), and two concurrent writes to the same removed-
221
+ * copy RowId would race (one clobbering the other's Deleted flag).
222
+ *
223
+ * @param {object} prevRow — OldImage of the HQ-source row.
224
+ * @param {object} nextRow — NewImage of the HQ-source row.
225
+ * @param {string} tableName — entity's table name.
226
+ * @param {object} [options]
227
+ * @param {string} [options.indexName="NewsletterEntriesHqSourceIdIndex"]
228
+ * @returns {Promise<{added: number, removed: number}>}
229
+ */
230
+ const syncHqTargets = async (prevRow, nextRow, tableName, options = {}) => {
231
+ const indexName = options.indexName || "NewsletterEntriesHqSourceIdIndex";
232
+ if (!nextRow || nextRow.Site !== "hq" || nextRow.Deleted) {
233
+ return { added: 0, removed: 0 };
234
+ }
235
+ const prev = Array.isArray(prevRow?.PublishedToSites)
236
+ ? prevRow.PublishedToSites
237
+ : [];
238
+ const next = Array.isArray(nextRow.PublishedToSites)
239
+ ? nextRow.PublishedToSites
240
+ : [];
241
+ const added = next.filter((s) => !prev.includes(s));
242
+ const removed = prev.filter((s) => !next.includes(s));
243
+
244
+ if (added.length > 0) {
245
+ await writeCopiesForTargets(nextRow, added, tableName);
246
+ }
247
+ if (removed.length > 0) {
248
+ const copies = await findCopiesByHqSourceId(
249
+ nextRow.Id,
250
+ tableName,
251
+ indexName,
252
+ );
253
+ const toRemove = copies.filter((c) => removed.includes(c.Site));
254
+ // Per-call fresh payload — editRef sync-mutates updates[keyCol]; sharing
255
+ // across Promise.all(map) would leak RowId between iterations (same
256
+ // rationale as propagateHqEdit / cascadeHqRetract).
257
+ await Promise.all(
258
+ toRemove.map((c) =>
259
+ editRef(tableName, "RowId", c.RowId, {
260
+ Deleted: true,
261
+ Changed: moment.utc().unix(),
262
+ }),
263
+ ),
264
+ );
265
+ }
266
+ return { added: added.length, removed: removed.length };
267
+ };
268
+
195
269
  /**
196
270
  * Propagate an HQ-source MODIFY to every community copy via the GSI lookup.
197
271
  * Skips the retract case (where `Deleted: true`) — that flows through
@@ -370,6 +444,8 @@ module.exports = {
370
444
  // Story B
371
445
  findCopiesByHqSourceId,
372
446
  fanOutHqSource,
447
+ writeCopiesForTargets,
448
+ syncHqTargets,
373
449
  propagateHqEdit,
374
450
  cascadeHqRetract,
375
451
  incrementTemplateUseCount,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plusscommunities/pluss-core-aws",
3
- "version": "2.0.25-beta.7",
3
+ "version": "2.0.25-beta.8",
4
4
  "description": "Core extension package for Pluss Communities platform",
5
5
  "scripts": {
6
6
  "betapatch": "npm version prepatch --preid=beta",