backend-manager 5.11.4 → 5.11.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "5.11.4",
3
+ "version": "5.11.5",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "files": [
@@ -39,6 +39,9 @@ const moment = require('moment');
39
39
  const pushid = require('pushid');
40
40
  const notification = require('../../../libraries/notification.js');
41
41
  const { getNextFutureOccurrence } = require('../../../libraries/email/constants.js');
42
+ // firebase-admin v13 dropped the admin.firestore.FieldValue static — import
43
+ // the modular way (same pattern as admin/send-email.js).
44
+ const { FieldValue } = require('firebase-admin/firestore');
42
45
 
43
46
  // How long a 'processing' lease is honored before the campaign of a crashed or
44
47
  // timed-out run is reclaimed for retry. Must comfortably exceed the function
@@ -147,7 +150,7 @@ module.exports = async ({ Manager, assistant, libraries }) => {
147
150
  await doc.ref.set({
148
151
  status: 'pending',
149
152
  sendAt: nextSendAt,
150
- generatorAttempts: admin.firestore.FieldValue.delete(),
153
+ generatorAttempts: FieldValue.delete(),
151
154
  metadata: { updated: stamp() },
152
155
  }, { merge: true });
153
156
 
@@ -216,7 +219,7 @@ module.exports = async ({ Manager, assistant, libraries }) => {
216
219
  await doc.ref.set({
217
220
  status: 'pending',
218
221
  sendAt: nextSendAt,
219
- generatorAttempts: admin.firestore.FieldValue.delete(),
222
+ generatorAttempts: FieldValue.delete(),
220
223
  metadata: { updated: stamp() },
221
224
  }, { merge: true });
222
225
 
@@ -226,7 +229,7 @@ module.exports = async ({ Manager, assistant, libraries }) => {
226
229
  await doc.ref.set({
227
230
  status: success ? 'sent' : 'failed',
228
231
  results: campaignResults,
229
- generatorAttempts: admin.firestore.FieldValue.delete(),
232
+ generatorAttempts: FieldValue.delete(),
230
233
  metadata: { updated: stamp() },
231
234
  }, { merge: true });
232
235
 
@@ -1127,11 +1127,11 @@ Manager.prototype.setupFunctions = function (exporter, options) {
1127
1127
  .onRun((context) => self.EventMiddleware({ context }).run(`${cron}/daily.js`));
1128
1128
 
1129
1129
  // Frequent cron runs the inline newsletter generator (AI structure + section
1130
- // images + article + uploads) — needs the v1 max timeout and headroom for
1131
- // image buffers. If a run still times out, the campaign lease reclaim in
1132
- // marketing-campaigns.js retries it safely.
1130
+ // images + article + uploads) — needs the v1 max timeout. If a run times out
1131
+ // or OOMs, the campaign lease reclaim in marketing-campaigns.js retries it
1132
+ // safely.
1133
1133
  exporter.bm_cronFrequent =
1134
- fn({memory: '512MB', timeoutSeconds: 540})
1134
+ fn({memory: '256MB', timeoutSeconds: 540})
1135
1135
  .pubsub.schedule('*/10 * * * *')
1136
1136
  .onRun((context) => self.EventMiddleware({ context }).run(`${cron}/frequent.js`));
1137
1137
  };
@@ -255,7 +255,11 @@ module.exports = {
255
255
  const unknownGenerator = await firestore.get('marketing-campaigns/_test-unknown-generator');
256
256
  const unknownType = await firestore.get('marketing-campaigns/_test-unknown-type');
257
257
 
258
- return oneoff?.status !== 'pending'
258
+ // 'sent'/'failed' — NOT merely !== 'pending': the claim flips the
259
+ // doc to 'processing' first, and proceeding on that mid-flight
260
+ // state races the send (oneoff-campaign-processed then reads
261
+ // 'processing' and fails).
262
+ return ['sent', 'failed'].includes(oneoff?.status)
259
263
  && recurring?.sendAt !== state.pastSendAt
260
264
  && recurringStale?.sendAt !== state.staleSendAt
261
265
  && ['sent', 'failed'].includes(staleProcessing?.status)
@@ -47,7 +47,7 @@ module.exports = {
47
47
 
48
48
  {
49
49
  name: 'no-wait-gets-clobbered',
50
- async run({ Manager, assert }) {
50
+ async run({ Manager, assert, skip }) {
51
51
  const admin = Manager.libraries.admin;
52
52
  const testUid = '_test-race-no-wait';
53
53
  const testEmail = '_test.race-no-wait@test.com';
@@ -76,7 +76,16 @@ module.exports = {
76
76
  const doc = await userRef.get();
77
77
  const survived = doc.exists && !!(doc.data()?.api?.clientId);
78
78
 
79
- assert.ok(!survived, 'Doc should be clobbered by late on-delete (proves the race exists)');
79
+ // The race resolves either way: when the emulator happens to finish
80
+ // the on-delete BEFORE the re-create's onCreate writes the doc, the
81
+ // dangerous late-clobber ordering never materializes that run —
82
+ // scheduler luck, not a regression. Only the clobber outcome is
83
+ // assertable; the benign ordering skips.
84
+ if (survived) {
85
+ await admin.auth().deleteUser(testUid).catch(() => {});
86
+ await pollUntilGone(userRef);
87
+ return skip('on-delete completed before the re-create this run — race did not manifest');
88
+ }
80
89
 
81
90
  await admin.auth().deleteUser(testUid).catch(() => {});
82
91
  await pollUntilGone(userRef);