backend-manager 5.0.153 → 5.0.154

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/CHANGELOG.md CHANGED
@@ -14,6 +14,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
14
14
  - `Fixed` for any bug fixes.
15
15
  - `Security` in case of vulnerabilities.
16
16
 
17
+ # [5.0.154] - 2026-03-16
18
+ ### Changed
19
+ - Add `display` property to all marketing FIELDS entries so display names are defined in the SSOT
20
+ - Beehiiv provider now maps fields to display names instead of raw keys
21
+ - Add `skip` flag for per-provider field creation control (e.g., SendGrid has first/last name built-in)
22
+
23
+ ### Added
24
+ - `user_personal_name_first` and `user_personal_name_last` fields to FIELDS dictionary (skipped for SendGrid which has them built-in)
25
+
17
26
  # [5.0.152] - 2026-03-16
18
27
  ### Fixed
19
28
  - Email queue documents all stored at `emails-queue/NaN` — `powertools.random()` doesn't support string generation, replaced with `pushid()`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "5.0.153",
3
+ "version": "5.0.154",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -122,10 +122,10 @@ function errorWithCode(message, code) {
122
122
  return err;
123
123
  }
124
124
 
125
- // Master field dictionary — the key IS the field name used in both providers.
125
+ // Master field dictionary — SSOT for all marketing custom fields.
126
126
  //
127
- // SendGrid: key is matched against custom field names at runtime (fetched + cached).
128
- // Beehiiv: key is used directly as the custom field name.
127
+ // SendGrid: `display` is the custom field name (created by OMEGA, resolved by ID at runtime).
128
+ // Beehiiv: `display` is the custom field name (matched by display name).
129
129
  //
130
130
  // Source types:
131
131
  // 'user' — read from user doc via _.get(userDoc, path)
@@ -133,35 +133,41 @@ function errorWithCode(message, code) {
133
133
  // 'config' — read from Manager.config via _.get(config, path)
134
134
  //
135
135
  // To add a new tracked marketing field:
136
- // 1. Add an entry here — the key becomes the field name in both providers
137
- // 2. Add matching entry in OMEGA's src/lib/bem-fields.js (name, display, type)
138
- // 3. Run OMEGA: npm start -- --service=sendgrid,beehiiv --brand=X
139
- // 4. BEM resolves field IDs at runtime — no provider code changes needed
140
- // 5. If 'resolved' source, ensure resolveFieldValues() computes it
136
+ // 1. Add an entry here (key, display, source, path, type)
137
+ // 2. Run OMEGA: npm start -- --service=sendgrid,beehiiv --brand=X
138
+ // 3. BEM resolves field IDs at runtime — no provider code changes needed
139
+ // 4. If 'resolved' source, ensure resolveFieldValues() computes it
140
+ //
141
+ // Flags:
142
+ // skip — Array of provider names to skip field creation for (e.g., ['sendgrid'])
143
+ // SendGrid has first_name/last_name as built-in contact fields
144
+ // Beehiiv needs them created as custom fields (preset templates)
141
145
  const FIELDS = {
142
146
  // Brand
143
- brand_id: { source: 'config', path: 'brand.id', type: 'text' },
147
+ brand_id: { display: 'Brand ID', source: 'config', path: 'brand.id', type: 'text' },
144
148
 
145
149
  // User identity
146
- user_auth_uid: { source: 'user', path: 'auth.uid', type: 'text' },
147
- user_personal_company: { source: 'user', path: 'personal.company.name', type: 'text' },
148
- user_personal_country: { source: 'user', path: 'personal.location.country', type: 'text' },
149
- user_metadata_signup_date: { source: 'user', path: 'metadata.created.timestamp', type: 'date' },
150
- user_metadata_last_activity: { source: 'user', path: 'metadata.updated.timestamp', type: 'date' },
150
+ user_auth_uid: { display: 'User UID', source: 'user', path: 'auth.uid', type: 'text' },
151
+ user_personal_name_first: { display: 'First Name', source: 'user', path: 'personal.name.first', type: 'text', skip: ['sendgrid'] },
152
+ user_personal_name_last: { display: 'Last Name', source: 'user', path: 'personal.name.last', type: 'text', skip: ['sendgrid'] },
153
+ user_personal_company: { display: 'Company', source: 'user', path: 'personal.company.name', type: 'text' },
154
+ user_personal_country: { display: 'Country', source: 'user', path: 'personal.location.country', type: 'text' },
155
+ user_metadata_signup_date: { display: 'Signup Date', source: 'user', path: 'metadata.created.timestamp', type: 'date' },
156
+ user_metadata_last_activity: { display: 'Last Activity', source: 'user', path: 'metadata.updated.timestamp', type: 'date' },
151
157
 
152
158
  // Subscription
153
- user_subscription_plan: { source: 'resolved', path: 'plan', type: 'text' },
154
- user_subscription_status: { source: 'resolved', path: 'status', type: 'text' },
155
- user_subscription_trialing: { source: 'resolved', path: 'trialing', type: 'text' },
156
- user_subscription_cancelling: { source: 'resolved', path: 'cancelling', type: 'text' },
157
- user_subscription_ever_paid: { source: 'resolved', path: 'everPaid', type: 'text' },
158
- user_subscription_payment_processor: { source: 'user', path: 'subscription.payment.processor', type: 'text' },
159
- user_subscription_payment_frequency: { source: 'user', path: 'subscription.payment.frequency', type: 'text' },
160
- user_subscription_payment_price: { source: 'user', path: 'subscription.payment.price', type: 'number' },
161
- user_subscription_payment_last_date: { source: 'user', path: 'subscription.payment.updatedBy.date.timestamp', type: 'date' },
159
+ user_subscription_plan: { display: 'Plan', source: 'resolved', path: 'plan', type: 'text' },
160
+ user_subscription_status: { display: 'Status', source: 'resolved', path: 'status', type: 'text' },
161
+ user_subscription_trialing: { display: 'Trialing', source: 'resolved', path: 'trialing', type: 'text' },
162
+ user_subscription_cancelling: { display: 'Cancelling', source: 'resolved', path: 'cancelling', type: 'text' },
163
+ user_subscription_ever_paid: { display: 'Ever Paid', source: 'resolved', path: 'everPaid', type: 'text' },
164
+ user_subscription_payment_processor: { display: 'Payment Processor', source: 'user', path: 'subscription.payment.processor', type: 'text' },
165
+ user_subscription_payment_frequency: { display: 'Payment Frequency', source: 'user', path: 'subscription.payment.frequency', type: 'text' },
166
+ user_subscription_payment_price: { display: 'Payment Price', source: 'user', path: 'subscription.payment.price', type: 'number' },
167
+ user_subscription_payment_last_date: { display: 'Last Payment Date', source: 'user', path: 'subscription.payment.updatedBy.date.timestamp', type: 'date' },
162
168
 
163
169
  // Attribution
164
- user_attribution_utm_source: { source: 'user', path: 'attribution.utm.tags.utm_source', type: 'text' },
170
+ user_attribution_utm_source: { display: 'UTM Source', source: 'user', path: 'attribution.utm.tags.utm_source', type: 'text' },
165
171
  };
166
172
 
167
173
 
@@ -5,7 +5,7 @@
5
5
  */
6
6
  const fetch = require('wonderful-fetch');
7
7
  const Manager = require('../../../index.js');
8
- const { resolveFieldValues } = require('../constants.js');
8
+ const { FIELDS, resolveFieldValues } = require('../constants.js');
9
9
 
10
10
  const BASE_URL = 'https://api.beehiiv.com/v2';
11
11
 
@@ -255,7 +255,8 @@ async function removeContact(email) {
255
255
 
256
256
  /**
257
257
  * Build Beehiiv custom_fields array from a user doc.
258
- * Resolves all field values the key IS the field name in Beehiiv.
258
+ * Resolves all field values, then maps to display names for Beehiiv.
259
+ * Beehiiv matches custom fields by their display name.
259
260
  *
260
261
  * @param {object} userDoc - User document from Firestore
261
262
  * @returns {Array<{name: string, value: string}>} Custom fields in Beehiiv format
@@ -265,7 +266,9 @@ function buildFields(userDoc) {
265
266
  const fields = [];
266
267
 
267
268
  for (const [name, value] of Object.entries(values)) {
268
- fields.push({ name, value: String(value) });
269
+ const fieldConfig = FIELDS[name];
270
+ const displayName = fieldConfig?.display || name;
271
+ fields.push({ name: displayName, value: String(value) });
269
272
  }
270
273
 
271
274
  return fields;
@@ -402,7 +402,7 @@ async function addContact({ email, firstName, lastName, company, customFields })
402
402
 
403
403
  /**
404
404
  * Build SendGrid custom_fields object from a user doc.
405
- * Resolves all field values, then maps field names to SendGrid IDs via runtime lookup.
405
+ * Resolves all field values, maps to display names, then resolves SendGrid IDs.
406
406
  *
407
407
  * @param {object} userDoc - User document from Firestore
408
408
  * @returns {object} Custom fields keyed by SendGrid field ID (e.g., { e1_T: 'basic' })