@rmdes/indiekit-endpoint-syndicate 1.0.0-beta.38 → 1.0.0-beta.39

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.
@@ -15,6 +15,24 @@ import {
15
15
  */
16
16
  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
17
17
 
18
+ /**
19
+ * Guards batch mode against overlapping runs.
20
+ *
21
+ * A post stays selectable until its Micropub update lands at the end of a
22
+ * batch, so a second trigger arriving mid-batch sees `mp-syndicate-to` still
23
+ * set and syndicates it again. Both triggers — the poller and the Eleventy
24
+ * post-build hook — reach the same process, so a module-level flag is enough.
25
+ * Anything skipped is picked up by the next poll.
26
+ *
27
+ * Batch mode only: the single-post force path (source_url) is deliberately
28
+ * unguarded so manual re-syndication always runs.
29
+ *
30
+ * ponytail: single-process guard. If Indiekit is ever run multi-process or
31
+ * multi-container, replace with an atomic per-post claim (findOneAndUpdate
32
+ * setting a claim timestamp, with a stale-claim timeout).
33
+ */
34
+ let batchRunning = false;
35
+
18
36
  /**
19
37
  * Syndicate a single post and update it via Micropub
20
38
  * @param {object} options - Options
@@ -153,87 +171,98 @@ export const syndicateController = {
153
171
  }
154
172
 
155
173
  // --- Batch mode (no source_url — process ALL pending posts) ---
156
- const allPostData = await getAllPostData(postsCollection);
157
-
158
- if (!allPostData || allPostData.length === 0) {
174
+ if (batchRunning) {
175
+ console.log("[syndication] Batch already in progress, skipping");
159
176
  return response.json({
160
177
  success: "OK",
161
- success_description: "No posts awaiting syndication",
178
+ success_description: "Batch already in progress",
162
179
  });
163
180
  }
164
181
 
165
- console.log(
166
- `[syndication] Batch processing ${allPostData.length} post(s)`,
167
- );
182
+ batchRunning = true;
168
183
 
169
- const results = [];
170
- let successCount = 0;
171
- let failCount = 0;
172
- let skippedCount = 0;
173
-
174
- for (const postData of allPostData) {
175
- try {
176
- const result = await syndicatePost({
177
- application,
178
- publication,
179
- postData,
180
- bearerToken,
184
+ try {
185
+ const allPostData = await getAllPostData(postsCollection);
186
+
187
+ if (!allPostData || allPostData.length === 0) {
188
+ return response.json({
189
+ success: "OK",
190
+ success_description: "No posts awaiting syndication",
181
191
  });
192
+ }
193
+
194
+ console.log(
195
+ `[syndication] Batch processing ${allPostData.length} post(s)`,
196
+ );
197
+
198
+ const results = [];
199
+
200
+ for (const [index, postData] of allPostData.entries()) {
201
+ try {
202
+ const result = await syndicatePost({
203
+ application,
204
+ publication,
205
+ postData,
206
+ bearerToken,
207
+ });
208
+
209
+ // Post was skipped (not yet built)
210
+ if (result.skipped) {
211
+ results.push({
212
+ url: result.url,
213
+ success: true,
214
+ skipped: true,
215
+ reason: `Not yet built (post: ${result.readiness.postStatus}, og: ${result.readiness.ogStatus})`,
216
+ });
217
+ continue;
218
+ }
182
219
 
183
- // Post was skipped (not yet built)
184
- if (result.skipped) {
185
220
  results.push({
186
221
  url: result.url,
187
222
  success: true,
188
- skipped: true,
189
- reason: `Not yet built (post: ${result.readiness.postStatus}, og: ${result.readiness.ogStatus})`,
223
+ syndicatedUrls: result.syndicatedUrls,
224
+ ...(result.failedTargets && {
225
+ failedTargets: result.failedTargets,
226
+ }),
190
227
  });
191
- skippedCount++;
192
- continue;
193
- }
194
-
195
- results.push({
196
- url: result.url,
197
- success: true,
198
- syndicatedUrls: result.syndicatedUrls,
199
- ...(result.failedTargets && {
200
- failedTargets: result.failedTargets,
201
- }),
202
- });
203
228
 
204
- successCount++;
205
- console.log(
206
- `[syndication] Syndicated: ${result.url} (${result.syndicatedUrls.length} target(s))`,
207
- );
208
- } catch (error) {
209
- results.push({
210
- url: postData.properties?.url,
211
- success: false,
212
- error: error.message,
213
- });
229
+ console.log(
230
+ `[syndication] Syndicated: ${result.url} (${result.syndicatedUrls.length} target(s))`,
231
+ );
232
+ } catch (error) {
233
+ results.push({
234
+ url: postData.properties?.url,
235
+ success: false,
236
+ error: error.message,
237
+ });
214
238
 
215
- failCount++;
216
- console.error(
217
- `[syndication] Failed: ${postData.properties?.url} - ${error.message}`,
218
- );
219
- }
239
+ console.error(
240
+ `[syndication] Failed: ${postData.properties?.url} - ${error.message}`,
241
+ );
242
+ }
220
243
 
221
- // Rate limit delay between posts (2 seconds)
222
- if (allPostData.indexOf(postData) < allPostData.length - 1) {
223
- await delay(2000);
244
+ // Rate limit delay between posts (2 seconds)
245
+ if (index < allPostData.length - 1) {
246
+ await delay(2000);
247
+ }
224
248
  }
225
- }
226
249
 
227
- const description =
228
- `Processed ${allPostData.length} post(s): ${successCount} succeeded, ${failCount} failed, ${skippedCount} not yet built`;
250
+ const failed = results.filter((result) => !result.success).length;
251
+ const skipped = results.filter((result) => result.skipped).length;
252
+ const succeeded = results.length - failed - skipped;
253
+ const description =
254
+ `Processed ${allPostData.length} post(s): ${succeeded} succeeded, ${failed} failed, ${skipped} not yet built`;
229
255
 
230
- console.log(`[syndication] ${description}`);
256
+ console.log(`[syndication] ${description}`);
231
257
 
232
- return response.json({
233
- success: "OK",
234
- success_description: description,
235
- results,
236
- });
258
+ return response.json({
259
+ success: "OK",
260
+ success_description: description,
261
+ results,
262
+ });
263
+ } finally {
264
+ batchRunning = false;
265
+ }
237
266
  } catch (error) {
238
267
  next(error);
239
268
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-syndicate",
3
- "version": "1.0.0-beta.38",
3
+ "version": "1.0.0-beta.39",
4
4
  "description": "Syndication endpoint for Indiekit. Fork of @indiekit/endpoint-syndicate with batch syndication support and bug fixes.",
5
5
  "keywords": [
6
6
  "indiekit",
@@ -37,5 +37,8 @@
37
37
  },
38
38
  "publishConfig": {
39
39
  "access": "public"
40
+ },
41
+ "scripts": {
42
+ "test": "node --test test/*.js"
40
43
  }
41
44
  }