@koda-sl/baker-cli 0.316.1 → 0.319.0
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/README.md +13 -1
- package/dist/cli.js +611 -212
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -13271,6 +13271,172 @@ var STEP_SUBMIT_TRIGGERS = Object.fromEntries(
|
|
|
13271
13271
|
);
|
|
13272
13272
|
var MAX_CONVERSION_NAME = 80;
|
|
13273
13273
|
|
|
13274
|
+
// ../api/src/event-ingest/matching.ts
|
|
13275
|
+
var MATCH_FIELDS = [
|
|
13276
|
+
"first_name",
|
|
13277
|
+
"last_name",
|
|
13278
|
+
"date_of_birth",
|
|
13279
|
+
"gender",
|
|
13280
|
+
"city",
|
|
13281
|
+
"region",
|
|
13282
|
+
"postal_code",
|
|
13283
|
+
"country"
|
|
13284
|
+
];
|
|
13285
|
+
var MATCH_COOKIES = ["fbp", "fbc"];
|
|
13286
|
+
function tightened(value) {
|
|
13287
|
+
const stripped = value.toLowerCase().replace(/[^\p{L}\p{N}]/gu, "");
|
|
13288
|
+
return stripped === "" ? null : stripped;
|
|
13289
|
+
}
|
|
13290
|
+
function loosened(value) {
|
|
13291
|
+
const collapsed = value.trim().toLowerCase().replace(/\s+/g, " ");
|
|
13292
|
+
return collapsed === "" ? null : collapsed;
|
|
13293
|
+
}
|
|
13294
|
+
function birthDate(value) {
|
|
13295
|
+
const digits = value.replace(/\D/g, "");
|
|
13296
|
+
if (!/^\d{8}$/.test(digits)) return null;
|
|
13297
|
+
const month = Number(digits.slice(4, 6));
|
|
13298
|
+
const day = Number(digits.slice(6, 8));
|
|
13299
|
+
if (month < 1 || month > 12 || day < 1 || day > 31) return null;
|
|
13300
|
+
return digits;
|
|
13301
|
+
}
|
|
13302
|
+
function genderLetter(value) {
|
|
13303
|
+
const first = value.trim().toLowerCase().charAt(0);
|
|
13304
|
+
return first === "f" || first === "m" ? first : null;
|
|
13305
|
+
}
|
|
13306
|
+
function countryCode(value) {
|
|
13307
|
+
const code = value.trim().toLowerCase();
|
|
13308
|
+
return /^[a-z]{2}$/.test(code) ? code : null;
|
|
13309
|
+
}
|
|
13310
|
+
var MATCH_SPECS = {
|
|
13311
|
+
first_name: { keep: "hashed", normalize: tightened, meta: "fn", google: "hashed_first_name" },
|
|
13312
|
+
last_name: { keep: "hashed", normalize: tightened, meta: "ln", google: "hashed_last_name" },
|
|
13313
|
+
// Meta only. Google's enhanced conversions have no date of birth, so there is
|
|
13314
|
+
// no destination wanting it in the clear and every reason not to keep one.
|
|
13315
|
+
date_of_birth: { keep: "hashed", normalize: birthDate, meta: "db", google: null },
|
|
13316
|
+
gender: { keep: "plain", normalize: genderLetter, meta: "ge", google: null },
|
|
13317
|
+
city: { keep: "plain", normalize: loosened, meta: "ct", google: "city" },
|
|
13318
|
+
region: { keep: "plain", normalize: loosened, meta: "st", google: "state" },
|
|
13319
|
+
postal_code: { keep: "plain", normalize: loosened, meta: "zp", google: "postal_code" },
|
|
13320
|
+
country: { keep: "plain", normalize: countryCode, meta: "country", google: "country_code" }
|
|
13321
|
+
};
|
|
13322
|
+
var MATCH_FIELD_FORWARDING = [
|
|
13323
|
+
{ field: "email", meta: "em", google: "hashed_email", hashed: true },
|
|
13324
|
+
{ field: "phone", meta: "ph", google: "hashed_phone_number", hashed: true },
|
|
13325
|
+
// Kept in the clear — see `resolveOwnId`. The digest is still computed and
|
|
13326
|
+
// still stored beside it, so a Meta forward has one; what changed is that the
|
|
13327
|
+
// sender's own key is now readable, which is the entire reason they sent it.
|
|
13328
|
+
{ field: "external_id", meta: "external_id", google: null, hashed: false },
|
|
13329
|
+
...MATCH_FIELDS.map((field) => ({
|
|
13330
|
+
field,
|
|
13331
|
+
meta: MATCH_SPECS[field].meta,
|
|
13332
|
+
google: MATCH_SPECS[field].google,
|
|
13333
|
+
hashed: MATCH_SPECS[field].keep === "hashed"
|
|
13334
|
+
})),
|
|
13335
|
+
{ field: "fbp", meta: "fbp", google: null, hashed: false },
|
|
13336
|
+
{ field: "fbc", meta: "fbc", google: null, hashed: false }
|
|
13337
|
+
];
|
|
13338
|
+
|
|
13339
|
+
// ../api/src/event-ingest/vocabulary.ts
|
|
13340
|
+
var INGEST_CLICK_ID_PARAMS = [
|
|
13341
|
+
"gclid",
|
|
13342
|
+
"wbraid",
|
|
13343
|
+
"gbraid",
|
|
13344
|
+
"dclid",
|
|
13345
|
+
"fbclid",
|
|
13346
|
+
"ttclid",
|
|
13347
|
+
"twclid",
|
|
13348
|
+
"li_fat_id",
|
|
13349
|
+
"msclkid",
|
|
13350
|
+
"rdt_cid",
|
|
13351
|
+
"epik",
|
|
13352
|
+
"sccid"
|
|
13353
|
+
];
|
|
13354
|
+
|
|
13355
|
+
// ../api/src/event-sources/fields.ts
|
|
13356
|
+
var MATCH_LABELS = {
|
|
13357
|
+
first_name: "First name",
|
|
13358
|
+
last_name: "Last name",
|
|
13359
|
+
date_of_birth: "Date of birth",
|
|
13360
|
+
gender: "Gender",
|
|
13361
|
+
city: "City",
|
|
13362
|
+
region: "Region",
|
|
13363
|
+
postal_code: "Postcode",
|
|
13364
|
+
country: "Country"
|
|
13365
|
+
};
|
|
13366
|
+
var CLICK_LABELS = {
|
|
13367
|
+
gclid: "Google click id",
|
|
13368
|
+
wbraid: "Google click id (iOS, web to app)",
|
|
13369
|
+
gbraid: "Google click id (iOS, app)",
|
|
13370
|
+
dclid: "Google click id (Display & Video)",
|
|
13371
|
+
fbclid: "Meta click id",
|
|
13372
|
+
ttclid: "TikTok click id",
|
|
13373
|
+
twclid: "X click id",
|
|
13374
|
+
li_fat_id: "LinkedIn click id",
|
|
13375
|
+
msclkid: "Microsoft click id",
|
|
13376
|
+
rdt_cid: "Reddit click id",
|
|
13377
|
+
epik: "Pinterest click id",
|
|
13378
|
+
sccid: "Snapchat click id"
|
|
13379
|
+
};
|
|
13380
|
+
var SOURCE_FIELDS = [
|
|
13381
|
+
{ field: "event_name", label: "What happened", group: "event" },
|
|
13382
|
+
{ field: "event_time", label: "When it happened", group: "event" },
|
|
13383
|
+
{ field: "event_id", label: "Its id", group: "event" },
|
|
13384
|
+
{ field: "email", label: "Email", group: "person" },
|
|
13385
|
+
{ field: "phone", label: "Phone", group: "person" },
|
|
13386
|
+
{ field: "external_id", label: "Your own id for the person", group: "person" },
|
|
13387
|
+
...MATCH_FIELDS.map((field) => ({ field, label: MATCH_LABELS[field], group: "person" })),
|
|
13388
|
+
{ field: "value", label: "Amount", group: "money" },
|
|
13389
|
+
{ field: "currency", label: "Currency", group: "money" },
|
|
13390
|
+
...INGEST_CLICK_ID_PARAMS.map((field) => ({ field, label: CLICK_LABELS[field], group: "click" }))
|
|
13391
|
+
];
|
|
13392
|
+
var SOURCE_FIELD_NAMES = SOURCE_FIELDS.map((spec) => spec.field);
|
|
13393
|
+
var FIELD_SET = new Set(SOURCE_FIELD_NAMES);
|
|
13394
|
+
function isSourceField(value) {
|
|
13395
|
+
return FIELD_SET.has(value);
|
|
13396
|
+
}
|
|
13397
|
+
var PROPERTY_ROW_PREFIX = "properties.";
|
|
13398
|
+
var PROPERTY_KEY_MAX = 64;
|
|
13399
|
+
var PROPERTY_KEY = /^[A-Za-z0-9_][A-Za-z0-9_.-]*$/;
|
|
13400
|
+
var RESERVED_PROPERTY_KEYS = ["baker_source", "hubspot_property", "hubspot_value"];
|
|
13401
|
+
function isPropertyRow(row) {
|
|
13402
|
+
return row.startsWith(PROPERTY_ROW_PREFIX);
|
|
13403
|
+
}
|
|
13404
|
+
function propertyKeyOf(row) {
|
|
13405
|
+
return isPropertyRow(row) ? row.slice(PROPERTY_ROW_PREFIX.length) : null;
|
|
13406
|
+
}
|
|
13407
|
+
function propertyKeyProblem(key) {
|
|
13408
|
+
const trimmed = key.trim();
|
|
13409
|
+
if (trimmed === "") return "A property needs a name.";
|
|
13410
|
+
if (trimmed.length > PROPERTY_KEY_MAX) return `A property name is at most ${PROPERTY_KEY_MAX} characters.`;
|
|
13411
|
+
if (!PROPERTY_KEY.test(trimmed)) {
|
|
13412
|
+
return `"${trimmed}" is not a property name: letters, digits, underscore, dash and dot only, starting with a letter or digit.`;
|
|
13413
|
+
}
|
|
13414
|
+
if (RESERVED_PROPERTY_KEYS.includes(trimmed)) return `"${trimmed}" is written by Baker itself; pick another name.`;
|
|
13415
|
+
return null;
|
|
13416
|
+
}
|
|
13417
|
+
function mappingRowProblem(row) {
|
|
13418
|
+
if (isSourceField(row)) return null;
|
|
13419
|
+
const key = propertyKeyOf(row);
|
|
13420
|
+
if (key === null) {
|
|
13421
|
+
return `"${row}" is not a field an event can carry. A field is one of ${SOURCE_FIELD_NAMES.join(", ")}; an extra property is spelled properties.<name>.`;
|
|
13422
|
+
}
|
|
13423
|
+
return propertyKeyProblem(key);
|
|
13424
|
+
}
|
|
13425
|
+
|
|
13426
|
+
// ../api/src/event-sources/template.ts
|
|
13427
|
+
var MENTION = /\{\{\s*([^{}]+?)\s*\}\}/g;
|
|
13428
|
+
function mentionOf(path48) {
|
|
13429
|
+
return `{{${path48}}}`;
|
|
13430
|
+
}
|
|
13431
|
+
function templateMentions(template) {
|
|
13432
|
+
const seen = [];
|
|
13433
|
+
for (const match of template.matchAll(MENTION)) {
|
|
13434
|
+
const path48 = (match[1] ?? "").trim();
|
|
13435
|
+
if (path48 !== "" && !seen.includes(path48)) seen.push(path48);
|
|
13436
|
+
}
|
|
13437
|
+
return seen;
|
|
13438
|
+
}
|
|
13439
|
+
|
|
13274
13440
|
// ../api/src/analytics/eventKey.ts
|
|
13275
13441
|
var SEP = ":";
|
|
13276
13442
|
function formEventKey(flowSlug, nodeId, triggerId) {
|
|
@@ -13305,6 +13471,106 @@ function parseEventKey(key) {
|
|
|
13305
13471
|
}
|
|
13306
13472
|
}
|
|
13307
13473
|
|
|
13474
|
+
// ../api/src/analytics/sends.ts
|
|
13475
|
+
var SEND_PLATFORMS = ["meta", "googleAds", "linkedinInsightTag", "twitterAds", "googleAnalytics"];
|
|
13476
|
+
function isSendPlatform(value) {
|
|
13477
|
+
return SEND_PLATFORMS.includes(value);
|
|
13478
|
+
}
|
|
13479
|
+
var SEND_PLATFORM_SPECS = {
|
|
13480
|
+
meta: {
|
|
13481
|
+
title: "Meta",
|
|
13482
|
+
eventMeans: "A Meta event name \u2014 Lead, Purchase, Schedule, or one of your own",
|
|
13483
|
+
eventKind: "name",
|
|
13484
|
+
credentialFields: ["accessToken", "datasetId"],
|
|
13485
|
+
setupHint: "Add the Conversions API access token and dataset id to the Meta pixel in Tracking"
|
|
13486
|
+
},
|
|
13487
|
+
googleAds: {
|
|
13488
|
+
title: "Google Ads",
|
|
13489
|
+
eventMeans: "One of the account's conversion actions, picked from its list",
|
|
13490
|
+
eventKind: "id",
|
|
13491
|
+
credentialFields: ["oauthProviderId"],
|
|
13492
|
+
setupHint: "Connect the Google Ads conversion tag to a Google Ads account in Tracking"
|
|
13493
|
+
},
|
|
13494
|
+
linkedinInsightTag: {
|
|
13495
|
+
title: "LinkedIn",
|
|
13496
|
+
eventMeans: "The id of a conversion rule in LinkedIn Campaign Manager",
|
|
13497
|
+
eventKind: "id",
|
|
13498
|
+
credentialFields: ["oauthProviderId"],
|
|
13499
|
+
setupHint: "Connect the LinkedIn Insight Tag to a LinkedIn account in Tracking"
|
|
13500
|
+
},
|
|
13501
|
+
twitterAds: {
|
|
13502
|
+
title: "X",
|
|
13503
|
+
eventMeans: "The event tag id from X Events Manager, like tw-oka17-abc12",
|
|
13504
|
+
eventKind: "id",
|
|
13505
|
+
credentialFields: ["oauthProviderId"],
|
|
13506
|
+
setupHint: "Connect the X pixel to an X Ads account in Tracking"
|
|
13507
|
+
},
|
|
13508
|
+
googleAnalytics: {
|
|
13509
|
+
title: "Google Analytics",
|
|
13510
|
+
eventMeans: "A GA4 event name \u2014 generate_lead, purchase, or one of your own",
|
|
13511
|
+
eventKind: "name",
|
|
13512
|
+
credentialFields: ["apiSecret"],
|
|
13513
|
+
setupHint: "Add the Measurement Protocol API secret to the Google Analytics tag in Tracking"
|
|
13514
|
+
}
|
|
13515
|
+
};
|
|
13516
|
+
var SEND_ORIGINS = ["server", "website", "any"];
|
|
13517
|
+
var MAX_SEND_ID = 64;
|
|
13518
|
+
var MAX_SEND_EVENT_KEY = 320;
|
|
13519
|
+
function sendEventIsTemplated(event) {
|
|
13520
|
+
return templateMentions(event).length > 0;
|
|
13521
|
+
}
|
|
13522
|
+
function sendEventProblem(platform, event) {
|
|
13523
|
+
if (!isSendPlatform(platform)) return null;
|
|
13524
|
+
if (!sendEventIsTemplated(event)) return null;
|
|
13525
|
+
const spec = SEND_PLATFORM_SPECS[platform];
|
|
13526
|
+
if (spec.eventKind === "name") return null;
|
|
13527
|
+
return `${spec.title} identifies a conversion by an id it issued, so it cannot be written from the event. Paste the id; pass what varies as an extra parameter instead.`;
|
|
13528
|
+
}
|
|
13529
|
+
|
|
13530
|
+
// ../api/src/analytics/destinations.ts
|
|
13531
|
+
var CONVERSION_SEND_PREFIX = "conversionSend:";
|
|
13532
|
+
function isConversionSend(type) {
|
|
13533
|
+
return type.startsWith(CONVERSION_SEND_PREFIX);
|
|
13534
|
+
}
|
|
13535
|
+
var SERVER_SIDE_SENDER_TAG = {
|
|
13536
|
+
metaCapi: "meta",
|
|
13537
|
+
googleAdsCapi: "googleAds",
|
|
13538
|
+
linkedinCapi: "linkedinInsightTag",
|
|
13539
|
+
twitterAdsCapi: "twitterAds",
|
|
13540
|
+
ga4Mp: "googleAnalytics"
|
|
13541
|
+
};
|
|
13542
|
+
var DESTINATION_NAMES = {
|
|
13543
|
+
email: "Email",
|
|
13544
|
+
httpWebhook: "HTTP Webhook",
|
|
13545
|
+
zapier: "Zapier",
|
|
13546
|
+
tiktok: "TikTok Pixel",
|
|
13547
|
+
pinterest: "Pinterest Tag",
|
|
13548
|
+
googleTagManager: "Google Tag Manager",
|
|
13549
|
+
amplitude: "Amplitude",
|
|
13550
|
+
hotjar: "Hotjar",
|
|
13551
|
+
clarity: "Microsoft Clarity",
|
|
13552
|
+
hubspot: "HubSpot Events",
|
|
13553
|
+
vwo: "VWO",
|
|
13554
|
+
posthog: "PostHog",
|
|
13555
|
+
datafast: "DataFast",
|
|
13556
|
+
code: "Custom Code",
|
|
13557
|
+
pipedrive: "Pipedrive",
|
|
13558
|
+
googleSpreadsheet: "Google Sheets",
|
|
13559
|
+
hubspotForm: "HubSpot Form",
|
|
13560
|
+
goHighlevelContact: "GoHighLevel",
|
|
13561
|
+
crmble: "Crmble"
|
|
13562
|
+
};
|
|
13563
|
+
function destinationKey(type) {
|
|
13564
|
+
const platform = isConversionSend(type) ? type.slice(CONVERSION_SEND_PREFIX.length) : type;
|
|
13565
|
+
return SERVER_SIDE_SENDER_TAG[platform] ?? platform;
|
|
13566
|
+
}
|
|
13567
|
+
function destinationName(type) {
|
|
13568
|
+
if (type === "") return "";
|
|
13569
|
+
const key = destinationKey(type);
|
|
13570
|
+
if (isSendPlatform(key)) return SEND_PLATFORM_SPECS[key].title;
|
|
13571
|
+
return DESTINATION_NAMES[key] ?? type;
|
|
13572
|
+
}
|
|
13573
|
+
|
|
13308
13574
|
// ../api/src/analytics/events.ts
|
|
13309
13575
|
import { z as z28 } from "zod";
|
|
13310
13576
|
|
|
@@ -13464,11 +13730,15 @@ var analyticsBeaconClickIdsSchema = z28.object({
|
|
|
13464
13730
|
gclid: z28.string().max(255).optional(),
|
|
13465
13731
|
wbraid: z28.string().max(255).optional(),
|
|
13466
13732
|
gbraid: z28.string().max(255).optional(),
|
|
13733
|
+
dclid: z28.string().max(255).optional(),
|
|
13467
13734
|
fbclid: z28.string().max(255).optional(),
|
|
13468
13735
|
ttclid: z28.string().max(255).optional(),
|
|
13469
13736
|
twclid: z28.string().max(255).optional(),
|
|
13470
13737
|
liFatId: z28.string().max(255).optional(),
|
|
13471
|
-
msclkid: z28.string().max(255).optional()
|
|
13738
|
+
msclkid: z28.string().max(255).optional(),
|
|
13739
|
+
rdtCid: z28.string().max(255).optional(),
|
|
13740
|
+
epik: z28.string().max(255).optional(),
|
|
13741
|
+
sccid: z28.string().max(255).optional()
|
|
13472
13742
|
});
|
|
13473
13743
|
var analyticsBeaconIdentitySchema = z28.object({
|
|
13474
13744
|
/** Lowercase hex SHA-256. Anything else is dropped rather than stored. */
|
|
@@ -13624,218 +13894,169 @@ var analyticsBeaconPayloadSchema = z28.object({
|
|
|
13624
13894
|
consent: z28.object({ analytics: z28.boolean() }).optional()
|
|
13625
13895
|
});
|
|
13626
13896
|
|
|
13627
|
-
// ../api/src/
|
|
13628
|
-
var
|
|
13629
|
-
"
|
|
13630
|
-
|
|
13631
|
-
|
|
13632
|
-
|
|
13633
|
-
"
|
|
13634
|
-
|
|
13635
|
-
|
|
13636
|
-
|
|
13637
|
-
|
|
13638
|
-
|
|
13639
|
-
|
|
13640
|
-
|
|
13641
|
-
|
|
13642
|
-
|
|
13643
|
-
|
|
13644
|
-
|
|
13645
|
-
|
|
13646
|
-
}
|
|
13647
|
-
|
|
13648
|
-
|
|
13649
|
-
|
|
13650
|
-
|
|
13651
|
-
|
|
13652
|
-
|
|
13653
|
-
|
|
13897
|
+
// ../api/src/analytics/triggers.ts
|
|
13898
|
+
var TRIGGERS = {
|
|
13899
|
+
"calendlyEvent.eventScheduled": {
|
|
13900
|
+
what: "Call booked",
|
|
13901
|
+
meaning: "Somebody picked a slot and confirmed it. This is the booking."
|
|
13902
|
+
},
|
|
13903
|
+
"calendlyEvent.dateAndTimeSelected": {
|
|
13904
|
+
what: "Time picked",
|
|
13905
|
+
meaning: "Somebody chose a slot but has not confirmed the booking yet."
|
|
13906
|
+
},
|
|
13907
|
+
"calendlyEvent.eventTypeViewed": {
|
|
13908
|
+
what: "Calendar opened",
|
|
13909
|
+
meaning: "The calendar appeared. Nobody has picked a time yet.",
|
|
13910
|
+
lifecycle: true
|
|
13911
|
+
},
|
|
13912
|
+
"calendlyEvent.profilePageViewed": {
|
|
13913
|
+
what: "Booking page shown",
|
|
13914
|
+
meaning: "Calendly's own page appeared. Nobody has picked a time yet.",
|
|
13915
|
+
lifecycle: true
|
|
13916
|
+
},
|
|
13917
|
+
"customForm.onFormSubmitted": {
|
|
13918
|
+
what: "Form submitted",
|
|
13919
|
+
meaning: "Somebody filled this step in and sent it."
|
|
13920
|
+
},
|
|
13921
|
+
"highlevelEvent.appointmentScheduled": {
|
|
13922
|
+
what: "Appointment booked",
|
|
13923
|
+
meaning: "Somebody picked a slot and confirmed it. This is the booking."
|
|
13924
|
+
},
|
|
13925
|
+
"highlevelEvent.calendarLoaded": {
|
|
13926
|
+
what: "Calendar finished loading",
|
|
13927
|
+
meaning: "The calendar appeared. Nobody has picked a time yet.",
|
|
13928
|
+
lifecycle: true
|
|
13929
|
+
},
|
|
13930
|
+
"highlevelFormEvent.formSubmitted": {
|
|
13931
|
+
what: "Form submitted",
|
|
13932
|
+
meaning: "Somebody filled the form in and sent it."
|
|
13933
|
+
},
|
|
13934
|
+
"highlevelFormEvent.formLoaded": {
|
|
13935
|
+
what: "Form finished loading",
|
|
13936
|
+
meaning: "The form appeared. Nobody has typed anything yet.",
|
|
13937
|
+
lifecycle: true
|
|
13938
|
+
},
|
|
13939
|
+
"hubspotForm.onFormSubmitted": {
|
|
13940
|
+
what: "Form submitted",
|
|
13941
|
+
meaning: "Somebody filled the form in and sent it. This is the one that means a lead."
|
|
13942
|
+
},
|
|
13943
|
+
"hubspotForm.onBeforeFormSubmit": {
|
|
13944
|
+
what: "Form about to be sent",
|
|
13945
|
+
meaning: "Fires an instant before the form is sent \u2014 not the same as it arriving.",
|
|
13946
|
+
lifecycle: true
|
|
13947
|
+
},
|
|
13948
|
+
"hubspotForm.onFormReady": {
|
|
13949
|
+
what: "Form finished loading",
|
|
13950
|
+
meaning: "The form appeared. Nobody has typed anything yet.",
|
|
13951
|
+
lifecycle: true
|
|
13952
|
+
},
|
|
13953
|
+
"hubspotForm.onBeforeFormInit": {
|
|
13954
|
+
what: "Form starting to load",
|
|
13955
|
+
meaning: "Fires before the form has even drawn. It only means the page was opened.",
|
|
13956
|
+
lifecycle: true
|
|
13957
|
+
},
|
|
13958
|
+
"hubspotMeeting.onMeetingScheduled": {
|
|
13959
|
+
what: "Meeting booked",
|
|
13960
|
+
meaning: "Somebody picked a slot and confirmed it. This is the booking."
|
|
13961
|
+
},
|
|
13962
|
+
"link.onClick": {
|
|
13963
|
+
what: "Link clicked",
|
|
13964
|
+
meaning: "Somebody clicked the link on this step and left for wherever it points."
|
|
13965
|
+
},
|
|
13966
|
+
"savvycalEvent.scheduled": {
|
|
13967
|
+
what: "Call booked",
|
|
13968
|
+
meaning: "Somebody picked a slot and confirmed it. This is the booking."
|
|
13969
|
+
},
|
|
13970
|
+
"text.onTextDisplayed": {
|
|
13971
|
+
what: "Screen shown",
|
|
13972
|
+
meaning: "This screen appeared. On a thank-you screen that is the outcome; elsewhere it is not."
|
|
13973
|
+
},
|
|
13974
|
+
"text.onButtonClicked": {
|
|
13975
|
+
what: "Button pressed",
|
|
13976
|
+
meaning: "Somebody pressed the button on this screen."
|
|
13977
|
+
}
|
|
13978
|
+
};
|
|
13979
|
+
function triggerDescription(triggerId) {
|
|
13980
|
+
return TRIGGERS[triggerId];
|
|
13654
13981
|
}
|
|
13655
|
-
|
|
13656
|
-
|
|
13657
|
-
|
|
13982
|
+
|
|
13983
|
+
// ../api/src/analytics/feedNoise.ts
|
|
13984
|
+
var BOOKKEEPING_TYPES = /* @__PURE__ */ new Set([
|
|
13985
|
+
"visitor_new",
|
|
13986
|
+
"session_start",
|
|
13987
|
+
"identify",
|
|
13988
|
+
"form_step_view",
|
|
13989
|
+
"side_effect_settled"
|
|
13990
|
+
]);
|
|
13991
|
+
var SUBMIT_TRIGGERS = new Set(Object.values(STEP_SUBMIT_TRIGGERS));
|
|
13992
|
+
function isEmbedLifecycle(row) {
|
|
13993
|
+
return row.eventType === "form_trigger" && triggerDescription(row.triggerId)?.lifecycle === true;
|
|
13658
13994
|
}
|
|
13659
|
-
function
|
|
13660
|
-
|
|
13661
|
-
|
|
13995
|
+
function isBookkeepingRow(row) {
|
|
13996
|
+
if (BOOKKEEPING_TYPES.has(row.eventType)) return true;
|
|
13997
|
+
if (isEmbedLifecycle(row)) return true;
|
|
13998
|
+
return row.eventType === "form_trigger" && SUBMIT_TRIGGERS.has(row.triggerId);
|
|
13662
13999
|
}
|
|
13663
|
-
var MATCH_SPECS = {
|
|
13664
|
-
first_name: { keep: "hashed", normalize: tightened, meta: "fn", google: "hashed_first_name" },
|
|
13665
|
-
last_name: { keep: "hashed", normalize: tightened, meta: "ln", google: "hashed_last_name" },
|
|
13666
|
-
// Meta only. Google's enhanced conversions have no date of birth, so there is
|
|
13667
|
-
// no destination wanting it in the clear and every reason not to keep one.
|
|
13668
|
-
date_of_birth: { keep: "hashed", normalize: birthDate, meta: "db", google: null },
|
|
13669
|
-
gender: { keep: "plain", normalize: genderLetter, meta: "ge", google: null },
|
|
13670
|
-
city: { keep: "plain", normalize: loosened, meta: "ct", google: "city" },
|
|
13671
|
-
region: { keep: "plain", normalize: loosened, meta: "st", google: "state" },
|
|
13672
|
-
postal_code: { keep: "plain", normalize: loosened, meta: "zp", google: "postal_code" },
|
|
13673
|
-
country: { keep: "plain", normalize: countryCode, meta: "country", google: "country_code" }
|
|
13674
|
-
};
|
|
13675
|
-
var MATCH_FIELD_FORWARDING = [
|
|
13676
|
-
{ field: "email", meta: "em", google: "hashed_email", hashed: true },
|
|
13677
|
-
{ field: "phone", meta: "ph", google: "hashed_phone_number", hashed: true },
|
|
13678
|
-
// Kept in the clear — see `resolveOwnId`. The digest is still computed and
|
|
13679
|
-
// still stored beside it, so a Meta forward has one; what changed is that the
|
|
13680
|
-
// sender's own key is now readable, which is the entire reason they sent it.
|
|
13681
|
-
{ field: "external_id", meta: "external_id", google: null, hashed: false },
|
|
13682
|
-
...MATCH_FIELDS.map((field) => ({
|
|
13683
|
-
field,
|
|
13684
|
-
meta: MATCH_SPECS[field].meta,
|
|
13685
|
-
google: MATCH_SPECS[field].google,
|
|
13686
|
-
hashed: MATCH_SPECS[field].keep === "hashed"
|
|
13687
|
-
})),
|
|
13688
|
-
{ field: "fbp", meta: "fbp", google: null, hashed: false },
|
|
13689
|
-
{ field: "fbc", meta: "fbc", google: null, hashed: false }
|
|
13690
|
-
];
|
|
13691
14000
|
|
|
13692
|
-
// ../api/src/
|
|
13693
|
-
var
|
|
14001
|
+
// ../api/src/analytics/identityLinks.ts
|
|
14002
|
+
var IDENTITY_KEY_KINDS = [
|
|
14003
|
+
"email",
|
|
14004
|
+
"phone",
|
|
14005
|
+
"external",
|
|
14006
|
+
"browser",
|
|
13694
14007
|
"gclid",
|
|
13695
14008
|
"wbraid",
|
|
13696
14009
|
"gbraid",
|
|
14010
|
+
"dclid",
|
|
13697
14011
|
"fbclid",
|
|
13698
14012
|
"ttclid",
|
|
13699
14013
|
"twclid",
|
|
13700
14014
|
"li_fat_id",
|
|
13701
|
-
"msclkid"
|
|
14015
|
+
"msclkid",
|
|
14016
|
+
"rdt_cid",
|
|
14017
|
+
"epik",
|
|
14018
|
+
"sccid",
|
|
14019
|
+
"fbp",
|
|
14020
|
+
"fbc"
|
|
13702
14021
|
];
|
|
14022
|
+
var ADDRESS_BOOK_FILL_KINDS = IDENTITY_KEY_KINDS.filter((kind) => kind !== "browser");
|
|
13703
14023
|
|
|
13704
|
-
// ../api/src/
|
|
13705
|
-
var
|
|
13706
|
-
|
|
13707
|
-
|
|
13708
|
-
|
|
13709
|
-
|
|
13710
|
-
|
|
13711
|
-
|
|
13712
|
-
|
|
13713
|
-
|
|
13714
|
-
|
|
13715
|
-
|
|
13716
|
-
|
|
13717
|
-
|
|
13718
|
-
|
|
13719
|
-
|
|
13720
|
-
|
|
13721
|
-
|
|
13722
|
-
|
|
13723
|
-
|
|
14024
|
+
// ../api/src/analytics/matchKeys.ts
|
|
14025
|
+
var CONTACT_KEYS = ["email", "phone", "external_id", "baker_id"];
|
|
14026
|
+
var MATCH_KEY_FIELDS = [...CONTACT_KEYS, ...INGEST_CLICK_ID_PARAMS, ...MATCH_COOKIES];
|
|
14027
|
+
var MATCH_KEY_LABELS = {
|
|
14028
|
+
email: "email",
|
|
14029
|
+
phone: "phone",
|
|
14030
|
+
external_id: "external ID",
|
|
14031
|
+
baker_id: "Baker ID",
|
|
14032
|
+
gclid: "Google click",
|
|
14033
|
+
wbraid: "Google click (wbraid)",
|
|
14034
|
+
gbraid: "Google click (gbraid)",
|
|
14035
|
+
dclid: "Google Display click",
|
|
14036
|
+
fbclid: "Meta click",
|
|
14037
|
+
ttclid: "TikTok click",
|
|
14038
|
+
twclid: "X click",
|
|
14039
|
+
li_fat_id: "LinkedIn click",
|
|
14040
|
+
msclkid: "Microsoft click",
|
|
14041
|
+
rdt_cid: "Reddit click",
|
|
14042
|
+
epik: "Pinterest click",
|
|
14043
|
+
sccid: "Snapchat click",
|
|
14044
|
+
fbp: "Meta browser cookie",
|
|
14045
|
+
fbc: "Meta click cookie"
|
|
13724
14046
|
};
|
|
13725
|
-
|
|
13726
|
-
|
|
13727
|
-
|
|
13728
|
-
|
|
13729
|
-
{ field: "email", label: "Email", group: "person" },
|
|
13730
|
-
{ field: "phone", label: "Phone", group: "person" },
|
|
13731
|
-
{ field: "external_id", label: "Your own id for the person", group: "person" },
|
|
13732
|
-
...MATCH_FIELDS.map((field) => ({ field, label: MATCH_LABELS[field], group: "person" })),
|
|
13733
|
-
{ field: "value", label: "Amount", group: "money" },
|
|
13734
|
-
{ field: "currency", label: "Currency", group: "money" },
|
|
13735
|
-
...INGEST_CLICK_ID_PARAMS.map((field) => ({ field, label: CLICK_LABELS[field], group: "click" }))
|
|
13736
|
-
];
|
|
13737
|
-
var SOURCE_FIELD_NAMES = SOURCE_FIELDS.map((spec) => spec.field);
|
|
13738
|
-
var FIELD_SET = new Set(SOURCE_FIELD_NAMES);
|
|
13739
|
-
function isSourceField(value) {
|
|
13740
|
-
return FIELD_SET.has(value);
|
|
13741
|
-
}
|
|
13742
|
-
var PROPERTY_ROW_PREFIX = "properties.";
|
|
13743
|
-
var PROPERTY_KEY_MAX = 64;
|
|
13744
|
-
var PROPERTY_KEY = /^[A-Za-z0-9_][A-Za-z0-9_.-]*$/;
|
|
13745
|
-
var RESERVED_PROPERTY_KEYS = ["baker_source", "hubspot_property", "hubspot_value"];
|
|
13746
|
-
function isPropertyRow(row) {
|
|
13747
|
-
return row.startsWith(PROPERTY_ROW_PREFIX);
|
|
13748
|
-
}
|
|
13749
|
-
function propertyKeyOf(row) {
|
|
13750
|
-
return isPropertyRow(row) ? row.slice(PROPERTY_ROW_PREFIX.length) : null;
|
|
13751
|
-
}
|
|
13752
|
-
function propertyKeyProblem(key) {
|
|
13753
|
-
const trimmed = key.trim();
|
|
13754
|
-
if (trimmed === "") return "A property needs a name.";
|
|
13755
|
-
if (trimmed.length > PROPERTY_KEY_MAX) return `A property name is at most ${PROPERTY_KEY_MAX} characters.`;
|
|
13756
|
-
if (!PROPERTY_KEY.test(trimmed)) {
|
|
13757
|
-
return `"${trimmed}" is not a property name: letters, digits, underscore, dash and dot only, starting with a letter or digit.`;
|
|
13758
|
-
}
|
|
13759
|
-
if (RESERVED_PROPERTY_KEYS.includes(trimmed)) return `"${trimmed}" is written by Baker itself; pick another name.`;
|
|
13760
|
-
return null;
|
|
13761
|
-
}
|
|
13762
|
-
function mappingRowProblem(row) {
|
|
13763
|
-
if (isSourceField(row)) return null;
|
|
13764
|
-
const key = propertyKeyOf(row);
|
|
13765
|
-
if (key === null) {
|
|
13766
|
-
return `"${row}" is not a field an event can carry. A field is one of ${SOURCE_FIELD_NAMES.join(", ")}; an extra property is spelled properties.<name>.`;
|
|
13767
|
-
}
|
|
13768
|
-
return propertyKeyProblem(key);
|
|
13769
|
-
}
|
|
13770
|
-
|
|
13771
|
-
// ../api/src/event-sources/template.ts
|
|
13772
|
-
var MENTION = /\{\{\s*([^{}]+?)\s*\}\}/g;
|
|
13773
|
-
function mentionOf(path48) {
|
|
13774
|
-
return `{{${path48}}}`;
|
|
13775
|
-
}
|
|
13776
|
-
function templateMentions(template) {
|
|
13777
|
-
const seen = [];
|
|
13778
|
-
for (const match of template.matchAll(MENTION)) {
|
|
13779
|
-
const path48 = (match[1] ?? "").trim();
|
|
13780
|
-
if (path48 !== "" && !seen.includes(path48)) seen.push(path48);
|
|
13781
|
-
}
|
|
13782
|
-
return seen;
|
|
13783
|
-
}
|
|
13784
|
-
|
|
13785
|
-
// ../api/src/analytics/sends.ts
|
|
13786
|
-
var SEND_PLATFORMS = ["meta", "googleAds", "linkedinInsightTag", "twitterAds", "googleAnalytics"];
|
|
13787
|
-
function isSendPlatform(value) {
|
|
13788
|
-
return SEND_PLATFORMS.includes(value);
|
|
13789
|
-
}
|
|
13790
|
-
var SEND_PLATFORM_SPECS = {
|
|
13791
|
-
meta: {
|
|
13792
|
-
title: "Meta",
|
|
13793
|
-
eventMeans: "A Meta event name \u2014 Lead, Purchase, Schedule, or one of your own",
|
|
13794
|
-
eventKind: "name",
|
|
13795
|
-
credentialFields: ["accessToken", "datasetId"],
|
|
13796
|
-
setupHint: "Add the Conversions API access token and dataset id to the Meta pixel in Tracking"
|
|
13797
|
-
},
|
|
13798
|
-
googleAds: {
|
|
13799
|
-
title: "Google Ads",
|
|
13800
|
-
eventMeans: "One of the account's conversion actions, picked from its list",
|
|
13801
|
-
eventKind: "id",
|
|
13802
|
-
credentialFields: ["oauthProviderId"],
|
|
13803
|
-
setupHint: "Connect the Google Ads conversion tag to a Google Ads account in Tracking"
|
|
13804
|
-
},
|
|
13805
|
-
linkedinInsightTag: {
|
|
13806
|
-
title: "LinkedIn",
|
|
13807
|
-
eventMeans: "The id of a conversion rule in LinkedIn Campaign Manager",
|
|
13808
|
-
eventKind: "id",
|
|
13809
|
-
credentialFields: ["oauthProviderId"],
|
|
13810
|
-
setupHint: "Connect the LinkedIn Insight Tag to a LinkedIn account in Tracking"
|
|
13811
|
-
},
|
|
13812
|
-
twitterAds: {
|
|
13813
|
-
title: "X",
|
|
13814
|
-
eventMeans: "The event tag id from X Events Manager, like tw-oka17-abc12",
|
|
13815
|
-
eventKind: "id",
|
|
13816
|
-
credentialFields: ["oauthProviderId"],
|
|
13817
|
-
setupHint: "Connect the X pixel to an X Ads account in Tracking"
|
|
13818
|
-
},
|
|
13819
|
-
googleAnalytics: {
|
|
13820
|
-
title: "Google Analytics",
|
|
13821
|
-
eventMeans: "A GA4 event name \u2014 generate_lead, purchase, or one of your own",
|
|
13822
|
-
eventKind: "name",
|
|
13823
|
-
credentialFields: ["apiSecret"],
|
|
13824
|
-
setupHint: "Add the Measurement Protocol API secret to the Google Analytics tag in Tracking"
|
|
14047
|
+
function carries(userData, field) {
|
|
14048
|
+
for (const key of [field, `${field}_sha256`, `${field}_preview`]) {
|
|
14049
|
+
const value = userData[key];
|
|
14050
|
+
if (typeof value === "string" && value !== "") return true;
|
|
13825
14051
|
}
|
|
13826
|
-
|
|
13827
|
-
var SEND_ORIGINS = ["server", "website", "any"];
|
|
13828
|
-
var MAX_SEND_ID = 64;
|
|
13829
|
-
var MAX_SEND_EVENT_KEY = 320;
|
|
13830
|
-
function sendEventIsTemplated(event) {
|
|
13831
|
-
return templateMentions(event).length > 0;
|
|
14052
|
+
return false;
|
|
13832
14053
|
}
|
|
13833
|
-
function
|
|
13834
|
-
if (
|
|
13835
|
-
|
|
13836
|
-
|
|
13837
|
-
|
|
13838
|
-
|
|
14054
|
+
function rowMatchKeys(userData) {
|
|
14055
|
+
if (userData === void 0) return [];
|
|
14056
|
+
return MATCH_KEY_FIELDS.filter((field) => carries(userData, field)).map((field) => ({
|
|
14057
|
+
field,
|
|
14058
|
+
label: MATCH_KEY_LABELS[field] ?? field
|
|
14059
|
+
}));
|
|
13839
14060
|
}
|
|
13840
14061
|
|
|
13841
14062
|
// ../api/src/analytics/sendPayload.ts
|
|
@@ -13858,11 +14079,15 @@ var CLICK_LABELS2 = {
|
|
|
13858
14079
|
gclid: "Google click id",
|
|
13859
14080
|
wbraid: "Google click id (web-to-app)",
|
|
13860
14081
|
gbraid: "Google click id (app-to-web)",
|
|
14082
|
+
dclid: "Google click id (Display & Video)",
|
|
13861
14083
|
fbclid: "Meta click id",
|
|
13862
14084
|
ttclid: "TikTok click id",
|
|
13863
14085
|
twclid: "X click id",
|
|
13864
14086
|
li_fat_id: "LinkedIn click id",
|
|
13865
|
-
msclkid: "Microsoft click id"
|
|
14087
|
+
msclkid: "Microsoft click id",
|
|
14088
|
+
rdt_cid: "Reddit click id",
|
|
14089
|
+
epik: "Pinterest click id",
|
|
14090
|
+
sccid: "Snapchat click id"
|
|
13866
14091
|
};
|
|
13867
14092
|
var PERSON_FIELDS = MATCH_FIELD_FORWARDING.map((row) => ({
|
|
13868
14093
|
name: row.field,
|
|
@@ -14572,9 +14797,15 @@ var analyticsQueryRequestSchema = z31.object({
|
|
|
14572
14797
|
/**
|
|
14573
14798
|
* One browser, by its cookie id.
|
|
14574
14799
|
*
|
|
14575
|
-
* Narrows `people` to a single person,
|
|
14576
|
-
*
|
|
14577
|
-
*
|
|
14800
|
+
* Narrows `people` to a single person, narrows `stream` to everything
|
|
14801
|
+
* that person did, and replays that person's whole history on `session` —
|
|
14802
|
+
* every visit they made, not just the one. Given with `sessionId`, the
|
|
14803
|
+
* session wins and this is ignored.
|
|
14804
|
+
*
|
|
14805
|
+
* On `stream` it is a seed rather than a cookie filter: the browser plus
|
|
14806
|
+
* every address, phone, click id and platform cookie ever seen beside it.
|
|
14807
|
+
* Filtering on the cookie alone would drop exactly the rows the reader
|
|
14808
|
+
* came for — a deal a CRM posted names the person and carries no cookie.
|
|
14578
14809
|
*/
|
|
14579
14810
|
anonId: z31.string().max(120).optional(),
|
|
14580
14811
|
/**
|
|
@@ -15633,7 +15864,8 @@ var analyticsVisitorRowSchema = z31.object({
|
|
|
15633
15864
|
campaignParams: z31.record(z31.string(), z31.string()),
|
|
15634
15865
|
/**
|
|
15635
15866
|
* The click ids this person arrived on, by their own names — `gclid`,
|
|
15636
|
-
* `fbclid`, `msclkid`, `li_fat_id`, `ttclid`, `twclid`, `wbraid`, `gbraid
|
|
15867
|
+
* `fbclid`, `msclkid`, `li_fat_id`, `ttclid`, `twclid`, `wbraid`, `gbraid`,
|
|
15868
|
+
* `dclid`, `rdt_cid`, `epik`, `sccid`.
|
|
15637
15869
|
*
|
|
15638
15870
|
* Only the ones that were present. The endpoint has always returned all eight
|
|
15639
15871
|
* columns and nothing above it carried them, so the one screen whose subject
|
|
@@ -29229,9 +29461,15 @@ var EVENT_INGEST_BODY_MAX_BYTES = 8 * 1024 * 1024;
|
|
|
29229
29461
|
var EVENT_INGEST_REQUESTS_PER_MINUTE = 60;
|
|
29230
29462
|
var FUTURE_TOLERANCE_MS = 5 * 6e4;
|
|
29231
29463
|
var MAX_EVENT_NAME = 80;
|
|
29464
|
+
var EVENT_NAME = /^[\p{L}\p{N}][\p{L}\p{M}\p{N} ._-]*$/u;
|
|
29465
|
+
function validateEventName(name) {
|
|
29466
|
+
const value = typeof name === "string" ? name.trim() : "";
|
|
29467
|
+
if (value === "" || value.length > MAX_EVENT_NAME || !EVENT_NAME.test(value)) return "invalid_name";
|
|
29468
|
+
return null;
|
|
29469
|
+
}
|
|
29232
29470
|
var REJECTION_MESSAGES = {
|
|
29233
29471
|
invalid_event_id: "event_id, when sent, must be 1-128 characters of A-Z a-z 0-9 . _ : -",
|
|
29234
|
-
invalid_name: "event_name is required: up to 80 characters of letters, digits, spaces, . _ and
|
|
29472
|
+
invalid_name: "event_name is required: up to 80 characters of letters (any language), digits, spaces, . _ and -, starting with a letter or a digit",
|
|
29235
29473
|
missing_identity: "user_data named nobody, and no url named a page",
|
|
29236
29474
|
invalid_timestamp: "event_time must be an ISO-8601 instant with an offset or Unix seconds, and not in the future",
|
|
29237
29475
|
invalid_value: "value and currency go together: a non-negative decimal and an ISO-4217 code",
|
|
@@ -29259,8 +29497,8 @@ var EVENT_FIELDS = [
|
|
|
29259
29497
|
{
|
|
29260
29498
|
name: "event_name",
|
|
29261
29499
|
need: "required",
|
|
29262
|
-
takes: `What happened. Up to ${MAX_EVENT_NAME} characters of letters, digits, spaces, . _ and
|
|
29263
|
-
note:
|
|
29500
|
+
takes: `What happened. Up to ${MAX_EVENT_NAME} characters of letters, digits, spaces, . _ and -, starting with a letter or a digit`,
|
|
29501
|
+
note: 'The only required field. Use one of the standard names below wherever one fits \u2014 anything else is yours to invent, in whatever language you name things in: a letter is a letter in any script, and "Matr\xEDcula Realizada" is a name.'
|
|
29264
29502
|
},
|
|
29265
29503
|
{
|
|
29266
29504
|
name: "event_time",
|
|
@@ -29311,7 +29549,7 @@ var EVENT_FIELDS = [
|
|
|
29311
29549
|
name: "the ad click ids",
|
|
29312
29550
|
need: "optional",
|
|
29313
29551
|
under: true,
|
|
29314
|
-
takes: "The click id that brought them, if you kept it, under the name the platform stamps on the link: gclid, wbraid, gbraid, fbclid, ttclid, twclid, li_fat_id, msclkid.",
|
|
29552
|
+
takes: "The click id that brought them, if you kept it, under the name the platform stamps on the link: gclid, wbraid, gbraid, dclid, fbclid, ttclid, twclid, li_fat_id, msclkid, rdt_cid, epik, sccid (Snapchat's ScCid, in any case).",
|
|
29315
29553
|
note: "A click id names a person too \u2014 it is enough on its own, and it attributes the event to the campaign that earned it."
|
|
29316
29554
|
},
|
|
29317
29555
|
{
|
|
@@ -30121,6 +30359,40 @@ function mergeStoredWithCounts(stored, reported) {
|
|
|
30121
30359
|
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
30122
30360
|
}
|
|
30123
30361
|
|
|
30362
|
+
// src/commands/analytics/feedRows.ts
|
|
30363
|
+
function isStreamRow(value) {
|
|
30364
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
30365
|
+
}
|
|
30366
|
+
function enrich(row) {
|
|
30367
|
+
const out = { ...row };
|
|
30368
|
+
const destination = typeof row.sideEffectType === "string" ? row.sideEffectType : "";
|
|
30369
|
+
if (destination !== "") out.destination = destinationName(destination);
|
|
30370
|
+
const keys = rowMatchKeys(row.userData);
|
|
30371
|
+
if (keys.length > 0) out.matchedBy = keys.map((key) => key.field);
|
|
30372
|
+
return out;
|
|
30373
|
+
}
|
|
30374
|
+
function shapeFeed(stream, everything) {
|
|
30375
|
+
if (!Array.isArray(stream)) return { rows: [], heldBack: 0 };
|
|
30376
|
+
const rows = stream.filter(isStreamRow);
|
|
30377
|
+
const kept = everything ? rows : rows.filter((row) => !isBookkeepingRow({ eventType: row.eventType ?? "", triggerId: row.triggerId ?? "" }));
|
|
30378
|
+
return { rows: kept.map(enrich), heldBack: rows.length - kept.length };
|
|
30379
|
+
}
|
|
30380
|
+
function feedHints(shape) {
|
|
30381
|
+
const hints2 = [];
|
|
30382
|
+
if (shape.heldBack > 0) {
|
|
30383
|
+
hints2.push(
|
|
30384
|
+
`${shape.heldBack} ${shape.heldBack === 1 ? "row is" : "rows are"} not listed: rows recorded to work the reports out \u2014 identifications, step views, session starts, a destination's answer already folded onto its dispatch \u2014 and embedded forms and calendars announcing that they drew. Add --everything to see them.`
|
|
30385
|
+
);
|
|
30386
|
+
}
|
|
30387
|
+
const upload = shape.rows.find((row) => row.sideEffectType !== void 0 && row.eventId !== void 0);
|
|
30388
|
+
if (upload !== void 0) {
|
|
30389
|
+
hints2.push(
|
|
30390
|
+
`A delivery row carries the event it was made for in \`eventId\`. \`baker analytics feed --uid ${String(upload.eventId)}\` returns that event and every upload made for it, each with the platform's verdict.`
|
|
30391
|
+
);
|
|
30392
|
+
}
|
|
30393
|
+
return hints2;
|
|
30394
|
+
}
|
|
30395
|
+
|
|
30124
30396
|
// src/commands/analytics/flowConversionKeys.ts
|
|
30125
30397
|
function formKeyRefusal(eventKey, tree, slug) {
|
|
30126
30398
|
const parsed = parseEventKey(eventKey);
|
|
@@ -30752,10 +31024,10 @@ function sendsWrite(args4) {
|
|
|
30752
31024
|
return parsed.data;
|
|
30753
31025
|
}
|
|
30754
31026
|
function sendsView(response, full) {
|
|
30755
|
-
const { carries, templateFields, ...rest } = response.data;
|
|
31027
|
+
const { carries: carries2, templateFields, ...rest } = response.data;
|
|
30756
31028
|
return {
|
|
30757
31029
|
ok: true,
|
|
30758
|
-
data: { ...rest, ...full ? { carries, templateFields } : {} },
|
|
31030
|
+
data: { ...rest, ...full ? { carries: carries2, templateFields } : {} },
|
|
30759
31031
|
...response.hints && response.hints.length > 0 ? { hints: response.hints } : {}
|
|
30760
31032
|
};
|
|
30761
31033
|
}
|
|
@@ -31009,6 +31281,7 @@ import { z as z36 } from "zod";
|
|
|
31009
31281
|
var SOURCE_KINDS = ["webhook", "hubspot"];
|
|
31010
31282
|
var SOURCE_STATUSES = ["learning", "counting", "paused"];
|
|
31011
31283
|
var SOURCE_NAME_MAX = 80;
|
|
31284
|
+
var sourceNameSchema = z36.string().trim().min(1).max(SOURCE_NAME_MAX).refine((name) => validateEventName(name) === null, REJECTION_MESSAGES.invalid_name);
|
|
31012
31285
|
var sourceIdSchema = z36.string().min(1);
|
|
31013
31286
|
var sourceMappingSchema = z36.record(z36.string(), z36.string().min(1)).superRefine((mapping, ctx) => {
|
|
31014
31287
|
for (const row of Object.keys(mapping)) {
|
|
@@ -31084,10 +31357,10 @@ var hubspotPreviewSchema = z36.strictObject({
|
|
|
31084
31357
|
var sourcesRequestSchema = z36.strictObject({
|
|
31085
31358
|
/** The company's own server door: made once, `counting` from the start, the same address for ever after. */
|
|
31086
31359
|
server: z36.strictObject({}).optional(),
|
|
31087
|
-
create: z36.strictObject({ name:
|
|
31360
|
+
create: z36.strictObject({ name: sourceNameSchema }).optional(),
|
|
31088
31361
|
/** A HubSpot event: read every five minutes, counting from now. The labels are looked up server-side. */
|
|
31089
31362
|
createHubspot: z36.strictObject({
|
|
31090
|
-
name:
|
|
31363
|
+
name: sourceNameSchema.optional(),
|
|
31091
31364
|
/** A contact, or a deal. A deal's event is about its contact, read through `contact.<property>` rows. */
|
|
31092
31365
|
object: hubspotObjectSchema.default("contact"),
|
|
31093
31366
|
trigger: hubspotTriggerInputSchema,
|
|
@@ -31113,11 +31386,46 @@ var sourcesRequestSchema = z36.strictObject({
|
|
|
31113
31386
|
amountProperty: z36.string().trim().min(1).optional(),
|
|
31114
31387
|
currency: currencyCodeSchema.optional()
|
|
31115
31388
|
}).optional(),
|
|
31389
|
+
/**
|
|
31390
|
+
* Count a HubSpot event's history: every moment its trigger came to hold
|
|
31391
|
+
* since `since`, read out of HubSpot's own property history.
|
|
31392
|
+
*
|
|
31393
|
+
* Not the thing a source deliberately refuses to do. A source never
|
|
31394
|
+
* backfills the **present state** — somebody who already satisfies a
|
|
31395
|
+
* condition is not an event today — and this reports only moments HubSpot
|
|
31396
|
+
* has a timestamp for, so nothing is invented. Dry unless `write`.
|
|
31397
|
+
*/
|
|
31398
|
+
backfill: z36.strictObject({
|
|
31399
|
+
sourceId: sourceIdSchema,
|
|
31400
|
+
/** ISO instant, or Unix milliseconds. Bounded by the 400 days the store keeps. */
|
|
31401
|
+
since: z36.union([z36.string().trim().min(1), z36.number()]),
|
|
31402
|
+
/**
|
|
31403
|
+
* Where a capped run carries on from: only records last changed at or
|
|
31404
|
+
* before this instant are read.
|
|
31405
|
+
*
|
|
31406
|
+
* The **read** is what it bounds, never the history. Records are chosen
|
|
31407
|
+
* most-recently-changed first, so a run that reaches its cap has covered
|
|
31408
|
+
* the newest of them and the rest are the ones nearest `since` — which
|
|
31409
|
+
* no shorter window can reach, because shortening one only drops
|
|
31410
|
+
* candidates from the old end and hands back the same newest records
|
|
31411
|
+
* again. `reachedBack` is the value to pass here, and the moments each
|
|
31412
|
+
* record is then judged on are still the whole of `since` to now.
|
|
31413
|
+
*/
|
|
31414
|
+
before: z36.union([z36.string().trim().min(1), z36.number()]).optional(),
|
|
31415
|
+
/** Without it, nothing is counted and the answer says what would be. */
|
|
31416
|
+
write: z36.boolean().optional()
|
|
31417
|
+
}).optional(),
|
|
31116
31418
|
map: z36.strictObject({ sourceId: sourceIdSchema, mapping: sourceMappingSchema }).optional(),
|
|
31117
31419
|
pause: z36.strictObject({ sourceId: sourceIdSchema }).optional(),
|
|
31118
31420
|
resume: z36.strictObject({ sourceId: sourceIdSchema }).optional(),
|
|
31119
31421
|
remove: z36.strictObject({ sourceId: sourceIdSchema }).optional()
|
|
31120
31422
|
});
|
|
31423
|
+
var sourceRefusalSchema = z36.strictObject({
|
|
31424
|
+
at: z36.number(),
|
|
31425
|
+
count: z36.number(),
|
|
31426
|
+
code: z36.string(),
|
|
31427
|
+
reason: z36.string()
|
|
31428
|
+
});
|
|
31121
31429
|
var detectedFieldSchema = z36.strictObject({
|
|
31122
31430
|
path: z36.string(),
|
|
31123
31431
|
confidence: z36.enum(["key", "value"])
|
|
@@ -31145,6 +31453,14 @@ var sourceViewSchema = z36.strictObject({
|
|
|
31145
31453
|
/** Why the last delivery could not be counted, in product words; absent when the last one was. */
|
|
31146
31454
|
lastError: z36.string().optional(),
|
|
31147
31455
|
lastArrivalAt: z36.number().optional(),
|
|
31456
|
+
/**
|
|
31457
|
+
* What its last delivery **refused, event by event** — which `lastError` is
|
|
31458
|
+
* not and `lastArrivalAt` cannot be. A source refusing everything it
|
|
31459
|
+
* receives has no arrival and no read failure, so without this it reads as
|
|
31460
|
+
* one nobody has sent anything to. `refusalWords` turns it into the
|
|
31461
|
+
* sentence a person is shown. Cleared by the next delivery that counts.
|
|
31462
|
+
*/
|
|
31463
|
+
lastRefusal: sourceRefusalSchema.optional(),
|
|
31148
31464
|
/** The first send, masked, while learning. Kept once confirmed. */
|
|
31149
31465
|
sample: z36.strictObject({ receivedAt: z36.number(), body: z36.string() }).optional(),
|
|
31150
31466
|
/** Field → template; empty means the guess (an address) or the defaults (a HubSpot read). */
|
|
@@ -31166,12 +31482,37 @@ var stagedSourceOpSchema = z36.strictObject({
|
|
|
31166
31482
|
mapping: z36.record(z36.string(), z36.string()).optional(),
|
|
31167
31483
|
status: z36.enum(["paused", "counting"]).optional()
|
|
31168
31484
|
});
|
|
31485
|
+
var backfillResultSchema = z36.strictObject({
|
|
31486
|
+
sourceId: z36.string(),
|
|
31487
|
+
name: z36.string(),
|
|
31488
|
+
since: z36.number(),
|
|
31489
|
+
/** Records whose history was read. */
|
|
31490
|
+
examined: z36.number(),
|
|
31491
|
+
/** Moments the trigger came to hold, across them. */
|
|
31492
|
+
entries: z36.number(),
|
|
31493
|
+
/** Of those, already counted — the journal got there first, or a previous run did. */
|
|
31494
|
+
alreadyCounted: z36.number(),
|
|
31495
|
+
/** What a `write` run would hand over, or did. */
|
|
31496
|
+
pending: z36.number(),
|
|
31497
|
+
/** Whether the read stopped at its cap; {@link BackfillResult.reachedBack} is where to carry on from. */
|
|
31498
|
+
capped: z36.boolean(),
|
|
31499
|
+
/**
|
|
31500
|
+
* How far back the read got: the instant the oldest record it examined was
|
|
31501
|
+
* last changed. Only on a capped run, and it is the next run's `before`.
|
|
31502
|
+
*/
|
|
31503
|
+
reachedBack: z36.number().optional(),
|
|
31504
|
+
counted: z36.number().optional(),
|
|
31505
|
+
refused: z36.number().optional(),
|
|
31506
|
+
refusedReason: z36.string().optional()
|
|
31507
|
+
});
|
|
31169
31508
|
var sourcesResponseSchema = z36.strictObject({
|
|
31170
31509
|
ok: z36.literal(true),
|
|
31171
31510
|
data: z36.strictObject({
|
|
31172
31511
|
sources: z36.array(sourceViewSchema),
|
|
31173
31512
|
/** Only when the request carried `preview`. */
|
|
31174
31513
|
preview: hubspotPreviewSchema.optional(),
|
|
31514
|
+
/** Only when the request carried `backfill`. */
|
|
31515
|
+
backfill: backfillResultSchema.optional(),
|
|
31175
31516
|
/** What this chat has staged, when one is attached: applied at Publish, dropped with a discard. */
|
|
31176
31517
|
staged: z36.array(stagedSourceOpSchema).optional()
|
|
31177
31518
|
}),
|
|
@@ -31282,6 +31623,26 @@ var args2 = {
|
|
|
31282
31623
|
description: "Change where a HubSpot-read source takes its amount from. Needs --source; with --amount-property and --currency it reads them, with neither it stops reading an amount",
|
|
31283
31624
|
required: false
|
|
31284
31625
|
},
|
|
31626
|
+
backfill: {
|
|
31627
|
+
type: "boolean",
|
|
31628
|
+
description: "Count a HubSpot event's history: every moment its trigger came to hold since --since, at the instant HubSpot says it did. Needs --source and --since. Nothing is invented \u2014 a record that merely satisfies the trigger today is not an event today, and only moments HubSpot has a timestamp for are counted. Dry without --write. Reach for it when an event was made after the deals it should have counted, or after fixing one that was refusing everything it received",
|
|
31629
|
+
required: false
|
|
31630
|
+
},
|
|
31631
|
+
since: {
|
|
31632
|
+
type: "string",
|
|
31633
|
+
description: "With --backfill: the date to count this event's history from, `2026-09-19` or an ISO instant. At most 400 days back \u2014 the store keeps no more",
|
|
31634
|
+
required: false
|
|
31635
|
+
},
|
|
31636
|
+
before: {
|
|
31637
|
+
type: "string",
|
|
31638
|
+
description: "With --backfill: carry on from where a capped run stopped, by passing back the `reachedBack` it returned. One run reads at most 500 records, most recently changed first, so a capped run has covered the newest and the rest are the ones nearest --since \u2014 narrowing --since cannot reach them, this can. Repeat until `capped` is false; nothing is counted twice. It bounds which records are read, never which of their moments count: those are still the whole of --since to now",
|
|
31639
|
+
required: false
|
|
31640
|
+
},
|
|
31641
|
+
write: {
|
|
31642
|
+
type: "boolean",
|
|
31643
|
+
description: "With --backfill: actually count what it found. Without it nothing is counted and the answer says what would be. Events inside the last 72 hours are uploaded to this event's ad platforms within minutes of counting, which spends real budget",
|
|
31644
|
+
required: false
|
|
31645
|
+
},
|
|
31285
31646
|
name: {
|
|
31286
31647
|
type: "string",
|
|
31287
31648
|
description: "The tool the source is for \u2014 HubSpot, Zapier, Stripe. It is the event's name when the payload does not say one, and every event it sends carries it as `baker_source`",
|
|
@@ -31445,14 +31806,37 @@ var VERBS = [
|
|
|
31445
31806
|
"create-hubspot",
|
|
31446
31807
|
"set-trigger",
|
|
31447
31808
|
"preview",
|
|
31809
|
+
"backfill",
|
|
31448
31810
|
"set-amount",
|
|
31449
31811
|
"map",
|
|
31450
31812
|
"pause",
|
|
31451
31813
|
"resume",
|
|
31452
31814
|
"remove"
|
|
31453
31815
|
];
|
|
31454
|
-
function
|
|
31816
|
+
function checkedName(raw, argv) {
|
|
31455
31817
|
const name = onlyOnce(raw.name, "--name", argv);
|
|
31818
|
+
if (name === void 0 || validateEventName(name) === null) return name;
|
|
31819
|
+
throw new ConversionArgsError(
|
|
31820
|
+
`"${name}" cannot be an event name, and the name is what every event this source raises will carry. ${REJECTION_MESSAGES.invalid_name}`
|
|
31821
|
+
);
|
|
31822
|
+
}
|
|
31823
|
+
function backfillArgs(raw, argv, sourceId) {
|
|
31824
|
+
const since = onlyOnce(raw.since, "--since", argv);
|
|
31825
|
+
if (since === void 0) {
|
|
31826
|
+
throw new ConversionArgsError(
|
|
31827
|
+
"--backfill needs --since <date>, e.g. --since 2026-09-19. Nothing before it is read."
|
|
31828
|
+
);
|
|
31829
|
+
}
|
|
31830
|
+
const before = onlyOnce(raw.before, "--before", argv);
|
|
31831
|
+
return {
|
|
31832
|
+
sourceId,
|
|
31833
|
+
since,
|
|
31834
|
+
...before === void 0 ? {} : { before },
|
|
31835
|
+
...raw.write === true ? { write: true } : {}
|
|
31836
|
+
};
|
|
31837
|
+
}
|
|
31838
|
+
function sourcesRequest(raw, argv) {
|
|
31839
|
+
const name = checkedName(raw, argv);
|
|
31456
31840
|
const source = onlyOnce(raw.source, "--source", argv);
|
|
31457
31841
|
const verbs = VERBS.filter((verb2) => raw[verb2] === true);
|
|
31458
31842
|
if (verbs.length > 1) throw new ConversionArgsError(`One of --${verbs.join(", --")} per call.`);
|
|
@@ -31466,6 +31850,7 @@ function sourcesRequest(raw, argv) {
|
|
|
31466
31850
|
if (verb === "create-hubspot") return { createHubspot: createHubspotArgs(raw, argv, name) };
|
|
31467
31851
|
if (verb === "preview") return { preview: previewArgs(raw, argv) };
|
|
31468
31852
|
if (source === void 0) throw new ConversionArgsError(`--${verb} needs --source <id>, from the list.`);
|
|
31853
|
+
if (verb === "backfill") return { backfill: backfillArgs(raw, argv, source) };
|
|
31469
31854
|
if (verb === "set-trigger") {
|
|
31470
31855
|
const contacts = contactsArg(raw, argv);
|
|
31471
31856
|
return {
|
|
@@ -31754,7 +32139,10 @@ async function runPreset(args4, options) {
|
|
|
31754
32139
|
process.exit(1);
|
|
31755
32140
|
}
|
|
31756
32141
|
const data = pruneForAgent(response.data, args4.full === true);
|
|
32142
|
+
const feed = "stream" in data ? shapeFeed(data.stream, args4.everything === true) : void 0;
|
|
32143
|
+
if (feed !== void 0) data.stream = feed.rows;
|
|
31757
32144
|
const hints2 = [
|
|
32145
|
+
...feed === void 0 ? [] : feedHints(feed),
|
|
31758
32146
|
// The platform the ad blocks were scoped to, so the tagging hint can
|
|
31759
32147
|
// only speak about the inventory it was actually given. `adParams` is one
|
|
31760
32148
|
// platform's list; a hint drawn from it without knowing which platform
|
|
@@ -31984,7 +32372,8 @@ function feedNarrowing(args4) {
|
|
|
31984
32372
|
...args4.kind ? { eventKey: String(args4.kind) } : {},
|
|
31985
32373
|
...args4.send ? { sendId: String(args4.send) } : {},
|
|
31986
32374
|
...args4["sent-to"] ? { sentTo: String(args4["sent-to"]) } : {},
|
|
31987
|
-
...args4.uid ? { eventUid: String(args4.uid) } : {}
|
|
32375
|
+
...args4.uid ? { eventUid: String(args4.uid) } : {},
|
|
32376
|
+
...args4.person ? { anonId: String(args4.person) } : {}
|
|
31988
32377
|
};
|
|
31989
32378
|
}
|
|
31990
32379
|
var feedCommand = presetCommand({
|
|
@@ -32026,6 +32415,16 @@ var feedCommand = presetCommand({
|
|
|
32026
32415
|
type: "string",
|
|
32027
32416
|
description: "One kind of event, by the key `baker analytics conversions` lists (page:Deal won, form:contact:\u2026)",
|
|
32028
32417
|
required: false
|
|
32418
|
+
},
|
|
32419
|
+
person: {
|
|
32420
|
+
type: "string",
|
|
32421
|
+
description: "One person and everything they did, seeded by a browser id from the `anonId` on any row. A seed rather than a cookie filter: the store resolves it to the whole person \u2014 across devices, and across every address, phone, customer id, ad click and platform cookie ever seen beside that browser \u2014 so a deal their CRM posted, which carries no cookie at all, is on the list beside the page view that named them",
|
|
32422
|
+
required: false
|
|
32423
|
+
},
|
|
32424
|
+
everything: {
|
|
32425
|
+
type: "boolean",
|
|
32426
|
+
description: "Include the rows held back by default: the ones recorded so the reports can be worked out (identifications, step views, session starts, a destination's answer already folded onto its dispatch) and embedded forms and calendars announcing that they drew. Reach for it when debugging the collector rather than an integration \u2014 an embed that draws and never fires again is only visible here",
|
|
32427
|
+
required: false
|
|
32029
32428
|
}
|
|
32030
32429
|
},
|
|
32031
32430
|
resolve: (args4) => feedRequest(args4)
|