@walkeros/transformer-ga4 4.8.1 → 4.8.2
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 +27 -0
- package/dist/dev.d.mts +4 -2
- package/dist/dev.d.ts +4 -2
- package/dist/dev.js +1 -1
- package/dist/dev.js.map +1 -1
- package/dist/dev.mjs +1 -1
- package/dist/dev.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/walkerOS.json +20 -3
- package/package.json +4 -3
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/parse.ts","../src/map.ts","../src/id.ts","../src/defaults.ts","../src/merge.ts","../src/transformer.ts"],"sourcesContent":["export { transformerGa4 } from './transformer';\nexport { defaultMapping } from './defaults';\nexport type {\n GA4Settings,\n GA4Mapping,\n GA4Request,\n GA4Hit,\n GA4Event,\n GA4EventParams,\n GA4HitParams,\n GA4Item,\n} from './types';\nexport { transformerGa4 as default } from './transformer';\n","import type { WalkerOS } from '@walkeros/core';\nimport type {\n GA4Event,\n GA4EventParams,\n GA4Hit,\n GA4HitParams,\n GA4Item,\n GA4Request,\n} from './types';\n\nexport function parseQuery(url: string): Record<string, string> {\n const out: Record<string, string> = {};\n const qIdx = url.indexOf('?');\n if (qIdx === -1) return out;\n const usp = new URLSearchParams(url.slice(qIdx + 1));\n usp.forEach((value, key) => {\n out[key] = value;\n });\n return out;\n}\n\n/** Non-empty, trimmed lines of a POST body: one GA4 event per line. */\nexport function splitBodyLines(body: string): string[] {\n if (!body) return [];\n return body\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n}\n\nexport function parseBody(body: string): Record<string, string>[] {\n return splitBodyLines(body).map((line) => {\n const out: Record<string, string> = {};\n const usp = new URLSearchParams(line);\n usp.forEach((v, k) => {\n out[k] = v;\n });\n return out;\n });\n}\n\n// Two separate dispatch maps keyed by 2-char prefix.\n// Split by type so the assignment site stays type-safe (no cast).\nconst ITEM_STRING_FIELDS: Readonly<Record<string, keyof GA4Item>> = {\n id: 'id',\n nm: 'name',\n br: 'brand',\n ca: 'category',\n c2: 'category2',\n c3: 'category3',\n c4: 'category4',\n c5: 'category5',\n va: 'variant',\n cp: 'coupon',\n ln: 'listName',\n li: 'listId',\n lo: 'locationId',\n af: 'affiliation',\n cn: 'creativeName',\n cs: 'creativeSlot',\n pi: 'promotionId',\n pn: 'promotionName',\n};\n\nconst ITEM_NUMBER_FIELDS: Readonly<Record<string, keyof GA4Item>> = {\n pr: 'price',\n qt: 'quantity',\n ds: 'discount',\n lp: 'listPosition',\n};\n\nfunction setItemString(item: GA4Item, key: keyof GA4Item, value: string): void {\n // Narrow assignment by key — these are the string-typed fields.\n switch (key) {\n case 'id':\n item.id = value;\n return;\n case 'name':\n item.name = value;\n return;\n case 'brand':\n item.brand = value;\n return;\n case 'category':\n item.category = value;\n return;\n case 'category2':\n item.category2 = value;\n return;\n case 'category3':\n item.category3 = value;\n return;\n case 'category4':\n item.category4 = value;\n return;\n case 'category5':\n item.category5 = value;\n return;\n case 'variant':\n item.variant = value;\n return;\n case 'coupon':\n item.coupon = value;\n return;\n case 'listName':\n item.listName = value;\n return;\n case 'listId':\n item.listId = value;\n return;\n case 'locationId':\n item.locationId = value;\n return;\n case 'affiliation':\n item.affiliation = value;\n return;\n case 'creativeName':\n item.creativeName = value;\n return;\n case 'creativeSlot':\n item.creativeSlot = value;\n return;\n case 'promotionId':\n item.promotionId = value;\n return;\n case 'promotionName':\n item.promotionName = value;\n return;\n }\n}\n\nfunction setItemNumber(item: GA4Item, key: keyof GA4Item, value: number): void {\n switch (key) {\n case 'price':\n item.price = value;\n return;\n case 'quantity':\n item.quantity = value;\n return;\n case 'discount':\n item.discount = value;\n return;\n case 'listPosition':\n item.listPosition = value;\n return;\n }\n}\n\nexport function parseItem(value: string): GA4Item {\n const item: GA4Item = { custom: {} };\n const customKeys = new Map<string, string>();\n const customVals = new Map<string, string>();\n\n for (const raw of value.split('~')) {\n if (raw.length === 0) continue;\n\n // Variable-length k<N>/v<N> (must match before 2-char prefix lookup\n // because k10/v10 etc are wider than 2 chars).\n const kvMatch = /^([kv])(\\d+)(.*)$/.exec(raw);\n if (kvMatch) {\n const [, kind, idx, val] = kvMatch;\n if (kind === 'k') customKeys.set(idx, val);\n else customVals.set(idx, val);\n continue;\n }\n\n if (raw.length < 2) continue;\n const prefix = raw.slice(0, 2);\n const val = raw.slice(2);\n\n const stringKey = ITEM_STRING_FIELDS[prefix];\n if (stringKey !== undefined) {\n setItemString(item, stringKey, val);\n continue;\n }\n const numberKey = ITEM_NUMBER_FIELDS[prefix];\n if (numberKey !== undefined) {\n const n = Number(val);\n if (!Number.isNaN(n)) setItemNumber(item, numberKey, n);\n continue;\n }\n }\n\n // Pair k<N> with v<N>; drop unmatched.\n for (const [idx, key] of customKeys) {\n const v = customVals.get(idx);\n if (v !== undefined) item.custom[key] = v;\n }\n return item;\n}\n\nexport function parseConsent(hit: { gcs?: string }): WalkerOS.Consent {\n const gcs = hit.gcs;\n if (!gcs || !/^G[01]{3}$/.test(gcs)) return {};\n // gcs[1] is reserved (always '1' for current consent mode v2)\n return {\n marketing: gcs[2] === '1', // ad_storage\n analytics: gcs[3] === '1', // analytics_storage\n };\n}\n\nconst HIT_PARAM_KEYS = new Set([\n 'v',\n 'tid',\n 'cid',\n 'sid',\n 'sct',\n 'seg',\n '_p',\n '_z',\n 'gtm',\n 'dl',\n 'dt',\n 'dr',\n 'ul',\n 'sr',\n 'uid',\n '_dbg',\n '_s',\n '_ss',\n '_fv',\n 'gcs',\n 'gcd',\n 'dma',\n 'dma_cps',\n 'npa',\n 'p',\n]);\n\nexport function parseRequest(req: GA4Request): GA4Hit {\n const queryParams = parseQuery(req.url);\n const hit: GA4HitParams = {};\n const queryEventParams: Record<string, string> = {};\n\n // Split URL params into hit-level vs event-level.\n for (const [k, v] of Object.entries(queryParams)) {\n if (HIT_PARAM_KEYS.has(k)) hit[k] = v;\n else queryEventParams[k] = v;\n }\n\n // GET: event params live in the URL alongside hit params.\n // POST: in body lines.\n const bodyLines = req.body ? parseBody(req.body) : [];\n const rawEvents =\n bodyLines.length > 0\n ? bodyLines\n : Object.keys(queryEventParams).length > 0\n ? [queryEventParams]\n : [];\n\n const events: GA4Event[] = rawEvents\n .filter((raw) => typeof raw.en === 'string' && raw.en.length > 0)\n .map((raw) => buildEvent(raw));\n\n return { hit, events };\n}\n\nfunction buildEvent(raw: Record<string, string>): GA4Event {\n const params: GA4EventParams = { ep: {}, epn: {}, up: {}, upn: {} };\n const itemEntries: Array<[number, string]> = [];\n\n for (const [k, v] of Object.entries(raw)) {\n if (k === 'en') continue;\n\n // pr<N> items\n const prMatch = /^pr(\\d+)$/.exec(k);\n if (prMatch) {\n itemEntries.push([Number(prMatch[1]), v]);\n continue;\n }\n\n // Dotted-key nesting\n if (k.startsWith('ep.')) {\n params.ep[k.slice(3)] = v;\n continue;\n }\n if (k.startsWith('epn.')) {\n const n = Number(v);\n if (!Number.isNaN(n)) params.epn[k.slice(4)] = n;\n continue;\n }\n if (k.startsWith('up.')) {\n params.up[k.slice(3)] = v;\n continue;\n }\n if (k.startsWith('upn.')) {\n const n = Number(v);\n if (!Number.isNaN(n)) params.upn[k.slice(4)] = n;\n continue;\n }\n\n // Standalone numeric keys\n if (k === '_et') {\n const n = Number(v);\n if (!Number.isNaN(n)) params._et = n;\n continue;\n }\n if (k === '_c') {\n params._c = v;\n continue;\n }\n // Unknown standalone keys: drop. Add to GA4EventParams type if a future\n // need surfaces.\n }\n\n itemEntries.sort((a, b) => a[0] - b[0]);\n const items = itemEntries.map(([, v]) => parseItem(v));\n\n return { en: raw.en, params, items };\n}\n","import type { Mapping, WalkerOS } from '@walkeros/core';\nimport { getByPath, deleteByPath, isObject } from '@walkeros/core';\nimport type {\n GA4Event,\n GA4Hit,\n GA4HitParams,\n GA4Item,\n GA4Mapping,\n} from './types';\nimport { parseConsent } from './parse';\nimport { getEventId } from './id';\n\n/**\n * Lookup root for mapping path resolution. Mapping rules reference keys via\n * dotted paths: `hit.dl`, `params.ep.transaction_id`, `params.epn.value`, `items`.\n *\n * `en` exposes the GA4 event name (used by the `*` fallback so unknown events\n * can carry their original name through). `firstItem` exposes `items[0]` so\n * promotion-style mappings can resolve `firstItem.promotionId` etc. without\n * needing array-index syntax in `getByPath`.\n *\n * Typed as `Record<string, unknown> & { ... }` so `getByPath` (which accepts\n * unknown) is happy without any narrowing cast on the call site.\n */\ntype LookupRoot = Record<string, unknown> & {\n hit: GA4HitParams;\n params: GA4Event['params'];\n items: GA4Event['items'];\n en: string;\n firstItem?: GA4Item;\n};\n\n/**\n * Convert a parsed GA4 hit into zero or more walkerOS events.\n * One GA4 hit may carry several events (POST batch); each is mapped\n * independently with hit-level metadata merged in.\n */\nexport function mapHitToEvents(\n hit: GA4Hit,\n mapping: GA4Mapping,\n): WalkerOS.DeepPartialEvent[] {\n const out: WalkerOS.DeepPartialEvent[] = [];\n hit.events.forEach((event, index) => {\n const mapped = mapOneEvent(hit.hit, event, index, mapping);\n if (mapped) out.push(mapped);\n });\n return out;\n}\n\nfunction mapOneEvent(\n hitParams: GA4HitParams,\n event: GA4Event,\n index: number,\n mapping: GA4Mapping,\n): WalkerOS.DeepPartialEvent | null {\n const ruleEntry = mapping[event.en] ?? mapping['*'];\n if (!ruleEntry) return null;\n // For v1, only single-rule entries are supported (no array fan-out).\n const rule: Mapping.Rule = Array.isArray(ruleEntry)\n ? ruleEntry[0]\n : ruleEntry;\n if (!rule || rule.ignore) return null;\n\n const lookup: LookupRoot = {\n hit: hitParams,\n params: event.params,\n items: event.items,\n en: event.en,\n ...(event.items.length > 0 ? { firstItem: event.items[0] } : {}),\n };\n\n const evaluated: WalkerOS.DeepPartialEvent = {};\n\n if (rule.name) {\n const [entity, ...rest] = rule.name.split(' ');\n evaluated.name = rule.name;\n evaluated.entity = entity;\n evaluated.action = rest.join(' ');\n }\n\n if (rule.data !== undefined) {\n let data = evalValue(rule.data, lookup);\n if (rule.remove && isObject(data)) {\n for (const path of rule.remove) data = deleteByPath(data, path);\n }\n if (data !== undefined) {\n evaluated.data = data as WalkerOS.Properties;\n }\n }\n\n // Hit-level merge: user / source. Browser language (ul) and screen\n // resolution (sr) describe the visitor, so they go to user; globals stays\n // free for site-level facts such as the site's own language.\n evaluated.user = {\n ...(hitParams.uid !== undefined ? { id: hitParams.uid } : {}),\n ...(hitParams.cid !== undefined ? { device: hitParams.cid } : {}),\n ...(hitParams.sid !== undefined ? { session: hitParams.sid } : {}),\n ...(hitParams.ul !== undefined ? { language: hitParams.ul } : {}),\n ...(hitParams.sr !== undefined ? { screenSize: hitParams.sr } : {}),\n };\n // source.type marks the GA4 decoder; url and referrer carry the page\n // context (dl, dr) as sent; the raw page load id (_p) and hit sequence (_s)\n // stay available for analysts next to the derived event id.\n evaluated.source = {\n type: 'ga4',\n ...(hitParams.dl !== undefined ? { url: hitParams.dl } : {}),\n ...(hitParams.dr !== undefined ? { referrer: hitParams.dr } : {}),\n ...(hitParams.p !== undefined ? { platform: hitParams.p } : {}),\n ...(hitParams._p !== undefined ? { pageLoadId: hitParams._p } : {}),\n ...(hitParams._s !== undefined ? { hitSequence: hitParams._s } : {}),\n };\n\n // Consent from gcs string, id derived from the hit (see getEventId),\n // timestamp is the receive time (sid is the session start, not the event\n // time), timing from event._et (ms; 0 if absent), trigger hard-coded to\n // \"ga4\" for v1.\n evaluated.consent = parseConsent(hitParams);\n evaluated.id = getEventId(hitParams, index);\n evaluated.timestamp = Date.now();\n evaluated.timing = event.params._et ?? 0;\n evaluated.trigger = 'ga4';\n\n if (event.items.length > 0) {\n evaluated.nested = itemsToNested(event.items);\n }\n\n return evaluated;\n}\n\n/**\n * GA4 items → walkerOS nested product entities.\n * Field naming: camelCase GA4Item keys are emitted as snake_case data keys\n * (`listName` → `list_name`, `promotionId` → `promotion_id`, etc).\n * Custom k<N>/v<N> pairs are spread as-is.\n *\n * Returns `WalkerOS.Entities` (i.e. `Array<WalkerOS.Entity>`). Note that the\n * canonical `WalkerOS.Entity` interface uses `entity: string` as the kind\n * discriminator (not `type`).\n */\nfunction itemsToNested(items: GA4Item[]): WalkerOS.Entities {\n return items.map((item): WalkerOS.Entity => {\n const data: WalkerOS.Properties = {};\n if (item.id !== undefined) data.id = item.id;\n if (item.name !== undefined) data.name = item.name;\n if (item.brand !== undefined) data.brand = item.brand;\n if (item.category !== undefined) data.category = item.category;\n if (item.category2 !== undefined) data.category2 = item.category2;\n if (item.category3 !== undefined) data.category3 = item.category3;\n if (item.category4 !== undefined) data.category4 = item.category4;\n if (item.category5 !== undefined) data.category5 = item.category5;\n if (item.variant !== undefined) data.variant = item.variant;\n if (item.price !== undefined) data.price = item.price;\n if (item.quantity !== undefined) data.quantity = item.quantity;\n if (item.coupon !== undefined) data.coupon = item.coupon;\n if (item.discount !== undefined) data.discount = item.discount;\n if (item.listName !== undefined) data.list_name = item.listName;\n if (item.listId !== undefined) data.list_id = item.listId;\n if (item.listPosition !== undefined) data.list_position = item.listPosition;\n if (item.locationId !== undefined) data.location_id = item.locationId;\n if (item.affiliation !== undefined) data.affiliation = item.affiliation;\n if (item.creativeName !== undefined) data.creative_name = item.creativeName;\n if (item.creativeSlot !== undefined) data.creative_slot = item.creativeSlot;\n if (item.promotionId !== undefined) data.promotion_id = item.promotionId;\n if (item.promotionName !== undefined)\n data.promotion_name = item.promotionName;\n for (const [k, v] of Object.entries(item.custom)) data[k] = v;\n return { entity: 'product', data, nested: [], context: {} };\n });\n}\n\n/**\n * Minimal mapping evaluator. v1 supports:\n * - string → path lookup via getByPath\n * - Array<Value> → fallback chain: first non-undefined wins\n * - { value: X } → literal\n * - { key: 'path' } → path lookup\n * - { map: {...} } → recursive object build\n *\n * Deferred to v2: condition, loop, fn, validate, set, consent, $code.\n */\nfunction evalValue(\n value: Mapping.Value | Mapping.Values,\n root: LookupRoot,\n): unknown {\n if (Array.isArray(value)) {\n for (const v of value) {\n const resolved = evalValue(v, root);\n if (resolved !== undefined && resolved !== null) return resolved;\n }\n return undefined;\n }\n if (typeof value === 'string') {\n return getByPath(root, value);\n }\n if (typeof value === 'object' && value !== null) {\n if ('value' in value && value.value !== undefined) return value.value;\n if ('key' in value && typeof value.key === 'string') {\n return getByPath(root, value.key);\n }\n if ('map' in value && value.map) {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value.map)) {\n const resolved = evalValue(v, root);\n if (resolved !== undefined) out[k] = resolved;\n }\n return out;\n }\n }\n return undefined;\n}\n","import { deriveSpanId, getSpanId } from '@walkeros/core';\nimport type { GA4HitParams } from './types';\n\n/**\n * walkerOS event id for the `index`-th event of a GA4 hit.\n *\n * Shaped like every other walkerOS event id (a W3C span id: 16 lowercase hex\n * characters), but derived from the hit instead of drawn at random, so a\n * re-delivered hit (gtag retry, proxy duplicate) decodes to the same ids and\n * dedup on `event.id` catches it. The hit key is the parent, the event's\n * position the child (the same derivation walkerOS uses for forks):\n *\n * - `_p` page load id, shared by every hit of one page load\n * - `_s` hit sequence counter within that page load\n * - index position of the event inside the hit (batched POST)\n * - `tid` and `cid` separate two properties on one page and page load\n * ids that happen to collide across devices\n *\n * Without `_p` or `_s` there is no stable per-hit key, so the id falls back\n * to a random span id: dedup then never drops such an event by mistake.\n */\nexport function getEventId(hit: GA4HitParams, index: number): string {\n const { _p: page, _s: seq } = hit;\n if (!page || !seq) return getSpanId();\n\n return deriveSpanId(\n JSON.stringify(['ga4', hit.tid ?? '', hit.cid ?? '', page, seq]),\n index,\n );\n}\n","import type { GA4Mapping } from './types';\n\n/**\n * Default GA4 event-name → walkerOS mapping rules.\n * Users may override or extend these via settings.mapping at config time.\n * The `'*'` key is the fallback for unknown event names.\n *\n * Field naming for `data.map`:\n * - Monetary: `currency` from `ep.currency`, `value`/`total` from `epn.value`,\n * `tax` from `epn.tax`, `shipping` from `epn.shipping`, `coupon` from\n * `ep.coupon`.\n * - Promotions read from `firstItem` (synthetic `items[0]` reference exposed by\n * the mapper), since GA4 encodes promotion info inside item tilde strings.\n */\nexport const defaultMapping: GA4Mapping = {\n // --- page ---\n page_view: {\n name: 'page view',\n data: {\n map: {\n id: 'hit.dl',\n title: 'hit.dt',\n referrer: 'hit.dr',\n },\n },\n },\n\n // --- ecommerce ---\n purchase: {\n name: 'order complete',\n data: {\n map: {\n id: 'params.ep.transaction_id',\n currency: 'params.ep.currency',\n total: 'params.epn.value',\n tax: 'params.epn.tax',\n shipping: 'params.epn.shipping',\n coupon: 'params.ep.coupon',\n },\n },\n },\n view_item: {\n name: 'product view',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n add_to_cart: {\n name: 'product add',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n remove_from_cart: {\n name: 'product remove',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n view_cart: {\n name: 'cart view',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n begin_checkout: {\n name: 'order start',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n coupon: 'params.ep.coupon',\n },\n },\n },\n add_shipping_info: {\n name: 'order shipping',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n tier: 'params.ep.shipping_tier',\n },\n },\n },\n add_payment_info: {\n name: 'order payment',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n type: 'params.ep.payment_type',\n },\n },\n },\n refund: {\n name: 'order refund',\n data: {\n map: {\n id: 'params.ep.transaction_id',\n currency: 'params.ep.currency',\n total: 'params.epn.value',\n },\n },\n },\n add_to_wishlist: {\n name: 'wishlist add',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n\n // --- list / promotion ---\n view_item_list: {\n name: 'list view',\n data: {\n map: {\n id: 'params.ep.item_list_id',\n name: 'params.ep.item_list_name',\n },\n },\n },\n select_item: {\n name: 'product click',\n data: {\n map: {\n list_id: 'params.ep.item_list_id',\n list_name: 'params.ep.item_list_name',\n },\n },\n },\n view_promotion: {\n name: 'promotion view',\n data: {\n map: {\n id: 'firstItem.promotionId',\n name: 'firstItem.promotionName',\n creative: 'firstItem.creativeName',\n slot: 'firstItem.creativeSlot',\n },\n },\n },\n select_promotion: {\n name: 'promotion click',\n data: {\n map: {\n id: 'firstItem.promotionId',\n name: 'firstItem.promotionName',\n creative: 'firstItem.creativeName',\n slot: 'firstItem.creativeSlot',\n },\n },\n },\n select_content: {\n name: 'content select',\n data: {\n map: {\n type: 'params.ep.content_type',\n id: 'params.ep.content_id',\n },\n },\n },\n\n // --- engagement ---\n scroll: {\n name: 'page scroll',\n data: {\n map: {\n percent: 'params.epn.percent_scrolled',\n },\n },\n },\n click: {\n name: 'link click',\n data: {\n map: {\n url: 'params.ep.link_url',\n domain: 'params.ep.link_domain',\n outbound: 'params.ep.outbound',\n },\n },\n },\n file_download: {\n name: 'file download',\n data: {\n map: {\n name: 'params.ep.file_name',\n extension: 'params.ep.file_extension',\n url: 'params.ep.link_url',\n },\n },\n },\n video_start: {\n name: 'video start',\n data: {\n map: {\n title: 'params.ep.video_title',\n duration: 'params.epn.video_duration',\n current: 'params.epn.video_current_time',\n percent: 'params.epn.video_percent',\n },\n },\n },\n video_progress: {\n name: 'video progress',\n data: {\n map: {\n title: 'params.ep.video_title',\n duration: 'params.epn.video_duration',\n current: 'params.epn.video_current_time',\n percent: 'params.epn.video_percent',\n },\n },\n },\n video_complete: {\n name: 'video complete',\n data: {\n map: {\n title: 'params.ep.video_title',\n duration: 'params.epn.video_duration',\n current: 'params.epn.video_current_time',\n percent: 'params.epn.video_percent',\n },\n },\n },\n form_start: {\n name: 'form start',\n data: {\n map: {\n id: 'params.ep.form_id',\n name: 'params.ep.form_name',\n destination: 'params.ep.form_destination',\n },\n },\n },\n form_submit: {\n name: 'form submit',\n data: {\n map: {\n id: 'params.ep.form_id',\n name: 'params.ep.form_name',\n destination: 'params.ep.form_destination',\n },\n },\n },\n search: {\n name: 'search submit',\n data: {\n map: {\n term: 'params.ep.search_term',\n },\n },\n },\n login: {\n name: 'session login',\n data: {\n map: {\n method: 'params.ep.method',\n },\n },\n },\n sign_up: {\n name: 'session signup',\n data: {\n map: {\n method: 'params.ep.method',\n },\n },\n },\n generate_lead: {\n name: 'lead generate',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n share: {\n name: 'content share',\n data: {\n map: {\n method: 'params.ep.method',\n type: 'params.ep.content_type',\n id: 'params.ep.item_id',\n },\n },\n },\n\n // --- auto-fired noise: ignore by default (users can override) ---\n user_engagement: { ignore: true },\n session_start: { ignore: true },\n first_visit: { ignore: true },\n\n // --- fallback for unknown event names ---\n '*': {\n name: 'ga4 track',\n data: {\n map: {\n event_name: 'en',\n },\n },\n },\n};\n","import { mergeMappingRule } from '@walkeros/core';\nimport type { GA4Mapping } from './types';\n\n/**\n * Merge user mapping overrides onto the shipped defaults, per GA4 event key.\n * A user entry with `extend`/`remove` patches the default; otherwise it\n * replaces it. `GA4Mapping` values are single rules (no Rule[]), matching\n * the evaluator. `extend` only changes anything for keys that ship a default.\n */\nexport function mergeGa4Mapping(\n defaults: GA4Mapping,\n user: GA4Mapping,\n): GA4Mapping {\n const merged: GA4Mapping = { ...defaults };\n for (const key of Object.keys(user)) {\n const userRule = user[key];\n if (!userRule) continue;\n const baseRule = defaults[key];\n merged[key] = baseRule\n ? mergeMappingRule(baseRule, userRule)\n : mergeMappingRule({}, userRule);\n }\n return merged;\n}\n","import type { Ingest, Transformer } from '@walkeros/core';\nimport { parseRequest, splitBodyLines } from './parse';\nimport { mapHitToEvents } from './map';\nimport { defaultMapping } from './defaults';\nimport { mergeGa4Mapping } from './merge';\nimport type { GA4Request, GA4Settings } from './types';\n\n/**\n * Read the raw GA4 HTTP request from `ctx.ingest`.\n *\n * Source wiring (e.g. an express source) is expected to populate\n * `ingest.url` (full request URL, query string included) and optionally\n * `ingest.body` (raw POST body as a string). `Ingest` is an open\n * `Record<string, unknown>` per `@walkeros/core`, so each field is\n * narrowed with a `typeof` guard instead of a cast.\n *\n * Returns `null` if `url` is missing or not a string — the caller drops\n * the event in that case.\n */\nfunction readGA4Request(ingest: Ingest): GA4Request | null {\n const url = ingest.url;\n if (typeof url !== 'string') return null;\n const body = ingest.body;\n return {\n url,\n body: typeof body === 'string' ? body : undefined,\n };\n}\n\nexport const transformerGa4: Transformer.Init<\n Transformer.Types<GA4Settings>\n> = (context) => {\n const { config } = context;\n const settings = config.settings ?? {};\n const tidPattern = new RegExp(settings.tidPattern ?? '^G-');\n const maxEvents = settings.maxEvents ?? 100;\n const mapping = mergeGa4Mapping(defaultMapping, settings.mapping ?? {});\n\n return {\n type: 'ga4',\n config,\n async push(_event, ctx) {\n const { logger } = ctx;\n\n const raw = readGA4Request(ctx.ingest);\n if (!raw) {\n logger.debug('transformer-ga4: no request in ingest; skipping');\n return false;\n }\n\n // Bound the fan-out before decoding: every event becomes a concurrent\n // push, and the source's batch cap does not apply to a raw text body.\n if (raw.body) {\n const lineCount = splitBodyLines(raw.body).length;\n if (lineCount > maxEvents) {\n logger.warn(\n `transformer-ga4: ${lineCount} events exceed maxEvents ${maxEvents}; request dropped`,\n );\n return false;\n }\n }\n\n try {\n const hit = parseRequest(raw);\n\n if (hit.hit.tid && !tidPattern.test(hit.hit.tid)) {\n logger.debug(\n `transformer-ga4: tid ${hit.hit.tid} dropped by tidPattern`,\n );\n return false;\n }\n\n const events = mapHitToEvents(hit, mapping);\n if (events.length === 0) {\n logger.debug(\n `transformer-ga4: no events mapped (tid=${hit.hit.tid ?? 'unknown'})`,\n );\n return false;\n }\n\n logger.debug(\n `transformer-ga4: decoded ${events.length} events from ${hit.hit.tid ?? 'unknown'}`,\n );\n\n if (events.length === 1) return { event: events[0] };\n return events.map((event) => ({ event }));\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.error(`transformer-ga4: parse error: ${message}`);\n return false;\n }\n },\n };\n};\n"],"mappings":"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","names":["val","import_core","import_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/transformer.ts","../src/parse.ts","../src/map.ts","../src/id.ts","../src/defaults.ts","../src/merge.ts"],"sourcesContent":["export { transformerGa4 } from './transformer';\nexport { defaultMapping } from './defaults';\nexport type {\n GA4Settings,\n GA4Mapping,\n GA4Request,\n GA4Hit,\n GA4Event,\n GA4EventParams,\n GA4HitParams,\n GA4Item,\n} from './types';\nexport { transformerGa4 as default } from './transformer';\n","import type { Ingest, Transformer } from '@walkeros/core';\nimport { StepError, reportCause, stepId } from '@walkeros/core';\nimport { parseRequest, splitBodyLines } from './parse';\nimport { mapHitToEvents } from './map';\nimport { defaultMapping } from './defaults';\nimport { mergeGa4Mapping } from './merge';\nimport type { GA4Request, GA4Settings } from './types';\n\n/**\n * Read the raw GA4 HTTP request from `ctx.ingest`.\n *\n * Source wiring (e.g. an express source) is expected to populate\n * `ingest.url` (full request URL, query string included) and optionally\n * `ingest.body` (raw POST body as a string). `Ingest` is an open\n * `Record<string, unknown>` per `@walkeros/core`, so each field is\n * narrowed with a `typeof` guard instead of a cast.\n *\n * Returns `null` if `url` is missing or not a string: the caller fails the\n * event with `missing-input`.\n */\nfunction readGA4Request(ingest: Ingest): GA4Request | null {\n const url = ingest.url;\n if (typeof url !== 'string') return null;\n const body = ingest.body;\n return {\n url,\n body: typeof body === 'string' ? body : undefined,\n };\n}\n\nexport const transformerGa4: Transformer.Init<\n Transformer.Types<GA4Settings>\n> = (context) => {\n const { config } = context;\n const settings = config.settings ?? {};\n const tidPattern = new RegExp(settings.tidPattern ?? '^G-');\n const maxEvents = settings.maxEvents ?? 100;\n const mapping = mergeGa4Mapping(defaultMapping, settings.mapping ?? {});\n\n return {\n type: 'ga4',\n config,\n async push(_event, ctx) {\n const { logger, collector, id } = ctx;\n\n // Without a request there is nothing to decode: a wiring mistake (the\n // source's config.ingest) that would drop all traffic, so it fails.\n const raw = readGA4Request(ctx.ingest);\n if (!raw)\n throw new StepError(\n 'transformer-ga4: no GA4 request in ingest (ingest.url missing)',\n { code: 'missing-input' },\n );\n\n // Bound the fan-out before decoding: every event becomes a concurrent\n // push, and the source's batch cap does not apply to a raw text body.\n // The dropped request is a loss, so it is reported (ERROR, once per\n // cause and minute).\n if (raw.body) {\n const lineCount = splitBodyLines(raw.body).length;\n if (lineCount > maxEvents) {\n reportCause(\n collector,\n logger,\n { step: stepId('transformer', id), code: 'invalid' },\n `transformer-ga4: ${lineCount} events exceed maxEvents ${maxEvents}; request dropped`,\n { events: lineCount, maxEvents },\n );\n return false;\n }\n }\n\n try {\n const hit = parseRequest(raw);\n\n if (hit.hit.tid && !tidPattern.test(hit.hit.tid)) {\n logger.debug(\n `transformer-ga4: tid ${hit.hit.tid} dropped by tidPattern`,\n );\n return false;\n }\n\n const events = mapHitToEvents(hit, mapping);\n if (events.length === 0) {\n logger.debug(\n `transformer-ga4: no events mapped (tid=${hit.hit.tid ?? 'unknown'})`,\n );\n return false;\n }\n\n logger.debug(\n `transformer-ga4: decoded ${events.length} events from ${hit.hit.tid ?? 'unknown'}`,\n );\n\n if (events.length === 1) return { event: events[0] };\n return events.map((event) => ({ event }));\n } catch (err) {\n // The collector reports it; the message names no request content.\n throw new StepError(\n 'transformer-ga4: the GA4 request could not be decoded',\n {\n code: 'transformer',\n cause: err,\n },\n );\n }\n },\n };\n};\n","import type { WalkerOS } from '@walkeros/core';\nimport type {\n GA4Event,\n GA4EventParams,\n GA4Hit,\n GA4HitParams,\n GA4Item,\n GA4Request,\n} from './types';\n\nexport function parseQuery(url: string): Record<string, string> {\n const out: Record<string, string> = {};\n const qIdx = url.indexOf('?');\n if (qIdx === -1) return out;\n const usp = new URLSearchParams(url.slice(qIdx + 1));\n usp.forEach((value, key) => {\n out[key] = value;\n });\n return out;\n}\n\n/** Non-empty, trimmed lines of a POST body: one GA4 event per line. */\nexport function splitBodyLines(body: string): string[] {\n if (!body) return [];\n return body\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n}\n\nexport function parseBody(body: string): Record<string, string>[] {\n return splitBodyLines(body).map((line) => {\n const out: Record<string, string> = {};\n const usp = new URLSearchParams(line);\n usp.forEach((v, k) => {\n out[k] = v;\n });\n return out;\n });\n}\n\n// Two separate dispatch maps keyed by 2-char prefix.\n// Split by type so the assignment site stays type-safe (no cast).\nconst ITEM_STRING_FIELDS: Readonly<Record<string, keyof GA4Item>> = {\n id: 'id',\n nm: 'name',\n br: 'brand',\n ca: 'category',\n c2: 'category2',\n c3: 'category3',\n c4: 'category4',\n c5: 'category5',\n va: 'variant',\n cp: 'coupon',\n ln: 'listName',\n li: 'listId',\n lo: 'locationId',\n af: 'affiliation',\n cn: 'creativeName',\n cs: 'creativeSlot',\n pi: 'promotionId',\n pn: 'promotionName',\n};\n\nconst ITEM_NUMBER_FIELDS: Readonly<Record<string, keyof GA4Item>> = {\n pr: 'price',\n qt: 'quantity',\n ds: 'discount',\n lp: 'listPosition',\n};\n\nfunction setItemString(item: GA4Item, key: keyof GA4Item, value: string): void {\n // Narrow assignment by key — these are the string-typed fields.\n switch (key) {\n case 'id':\n item.id = value;\n return;\n case 'name':\n item.name = value;\n return;\n case 'brand':\n item.brand = value;\n return;\n case 'category':\n item.category = value;\n return;\n case 'category2':\n item.category2 = value;\n return;\n case 'category3':\n item.category3 = value;\n return;\n case 'category4':\n item.category4 = value;\n return;\n case 'category5':\n item.category5 = value;\n return;\n case 'variant':\n item.variant = value;\n return;\n case 'coupon':\n item.coupon = value;\n return;\n case 'listName':\n item.listName = value;\n return;\n case 'listId':\n item.listId = value;\n return;\n case 'locationId':\n item.locationId = value;\n return;\n case 'affiliation':\n item.affiliation = value;\n return;\n case 'creativeName':\n item.creativeName = value;\n return;\n case 'creativeSlot':\n item.creativeSlot = value;\n return;\n case 'promotionId':\n item.promotionId = value;\n return;\n case 'promotionName':\n item.promotionName = value;\n return;\n }\n}\n\nfunction setItemNumber(item: GA4Item, key: keyof GA4Item, value: number): void {\n switch (key) {\n case 'price':\n item.price = value;\n return;\n case 'quantity':\n item.quantity = value;\n return;\n case 'discount':\n item.discount = value;\n return;\n case 'listPosition':\n item.listPosition = value;\n return;\n }\n}\n\nexport function parseItem(value: string): GA4Item {\n const item: GA4Item = { custom: {} };\n const customKeys = new Map<string, string>();\n const customVals = new Map<string, string>();\n\n for (const raw of value.split('~')) {\n if (raw.length === 0) continue;\n\n // Variable-length k<N>/v<N> (must match before 2-char prefix lookup\n // because k10/v10 etc are wider than 2 chars).\n const kvMatch = /^([kv])(\\d+)(.*)$/.exec(raw);\n if (kvMatch) {\n const [, kind, idx, val] = kvMatch;\n if (kind === 'k') customKeys.set(idx, val);\n else customVals.set(idx, val);\n continue;\n }\n\n if (raw.length < 2) continue;\n const prefix = raw.slice(0, 2);\n const val = raw.slice(2);\n\n const stringKey = ITEM_STRING_FIELDS[prefix];\n if (stringKey !== undefined) {\n setItemString(item, stringKey, val);\n continue;\n }\n const numberKey = ITEM_NUMBER_FIELDS[prefix];\n if (numberKey !== undefined) {\n const n = Number(val);\n if (!Number.isNaN(n)) setItemNumber(item, numberKey, n);\n continue;\n }\n }\n\n // Pair k<N> with v<N>; drop unmatched.\n for (const [idx, key] of customKeys) {\n const v = customVals.get(idx);\n if (v !== undefined) item.custom[key] = v;\n }\n return item;\n}\n\nexport function parseConsent(hit: { gcs?: string }): WalkerOS.Consent {\n const gcs = hit.gcs;\n if (!gcs || !/^G[01]{3}$/.test(gcs)) return {};\n // gcs[1] is reserved (always '1' for current consent mode v2)\n return {\n marketing: gcs[2] === '1', // ad_storage\n analytics: gcs[3] === '1', // analytics_storage\n };\n}\n\nconst HIT_PARAM_KEYS = new Set([\n 'v',\n 'tid',\n 'cid',\n 'sid',\n 'sct',\n 'seg',\n '_p',\n '_z',\n 'gtm',\n 'dl',\n 'dt',\n 'dr',\n 'ul',\n 'sr',\n 'uid',\n '_dbg',\n '_s',\n '_ss',\n '_fv',\n 'gcs',\n 'gcd',\n 'dma',\n 'dma_cps',\n 'npa',\n 'p',\n]);\n\nexport function parseRequest(req: GA4Request): GA4Hit {\n const queryParams = parseQuery(req.url);\n const hit: GA4HitParams = {};\n const queryEventParams: Record<string, string> = {};\n\n // Split URL params into hit-level vs event-level.\n for (const [k, v] of Object.entries(queryParams)) {\n if (HIT_PARAM_KEYS.has(k)) hit[k] = v;\n else queryEventParams[k] = v;\n }\n\n // GET: event params live in the URL alongside hit params.\n // POST: in body lines.\n const bodyLines = req.body ? parseBody(req.body) : [];\n const rawEvents =\n bodyLines.length > 0\n ? bodyLines\n : Object.keys(queryEventParams).length > 0\n ? [queryEventParams]\n : [];\n\n const events: GA4Event[] = rawEvents\n .filter((raw) => typeof raw.en === 'string' && raw.en.length > 0)\n .map((raw) => buildEvent(raw));\n\n return { hit, events };\n}\n\nfunction buildEvent(raw: Record<string, string>): GA4Event {\n const params: GA4EventParams = { ep: {}, epn: {}, up: {}, upn: {} };\n const itemEntries: Array<[number, string]> = [];\n\n for (const [k, v] of Object.entries(raw)) {\n if (k === 'en') continue;\n\n // pr<N> items\n const prMatch = /^pr(\\d+)$/.exec(k);\n if (prMatch) {\n itemEntries.push([Number(prMatch[1]), v]);\n continue;\n }\n\n // Dotted-key nesting\n if (k.startsWith('ep.')) {\n params.ep[k.slice(3)] = v;\n continue;\n }\n if (k.startsWith('epn.')) {\n const n = Number(v);\n if (!Number.isNaN(n)) params.epn[k.slice(4)] = n;\n continue;\n }\n if (k.startsWith('up.')) {\n params.up[k.slice(3)] = v;\n continue;\n }\n if (k.startsWith('upn.')) {\n const n = Number(v);\n if (!Number.isNaN(n)) params.upn[k.slice(4)] = n;\n continue;\n }\n\n // Standalone numeric keys\n if (k === '_et') {\n const n = Number(v);\n if (!Number.isNaN(n)) params._et = n;\n continue;\n }\n if (k === '_c') {\n params._c = v;\n continue;\n }\n // Unknown standalone keys: drop. Add to GA4EventParams type if a future\n // need surfaces.\n }\n\n itemEntries.sort((a, b) => a[0] - b[0]);\n const items = itemEntries.map(([, v]) => parseItem(v));\n\n return { en: raw.en, params, items };\n}\n","import type { Mapping, WalkerOS } from '@walkeros/core';\nimport { getByPath, deleteByPath, isObject } from '@walkeros/core';\nimport type {\n GA4Event,\n GA4Hit,\n GA4HitParams,\n GA4Item,\n GA4Mapping,\n} from './types';\nimport { parseConsent } from './parse';\nimport { getEventId } from './id';\n\n/**\n * Lookup root for mapping path resolution. Mapping rules reference keys via\n * dotted paths: `hit.dl`, `params.ep.transaction_id`, `params.epn.value`, `items`.\n *\n * `en` exposes the GA4 event name (used by the `*` fallback so unknown events\n * can carry their original name through). `firstItem` exposes `items[0]` so\n * promotion-style mappings can resolve `firstItem.promotionId` etc. without\n * needing array-index syntax in `getByPath`.\n *\n * Typed as `Record<string, unknown> & { ... }` so `getByPath` (which accepts\n * unknown) is happy without any narrowing cast on the call site.\n */\ntype LookupRoot = Record<string, unknown> & {\n hit: GA4HitParams;\n params: GA4Event['params'];\n items: GA4Event['items'];\n en: string;\n firstItem?: GA4Item;\n};\n\n/**\n * Convert a parsed GA4 hit into zero or more walkerOS events.\n * One GA4 hit may carry several events (POST batch); each is mapped\n * independently with hit-level metadata merged in.\n */\nexport function mapHitToEvents(\n hit: GA4Hit,\n mapping: GA4Mapping,\n): WalkerOS.DeepPartialEvent[] {\n const out: WalkerOS.DeepPartialEvent[] = [];\n hit.events.forEach((event, index) => {\n const mapped = mapOneEvent(hit.hit, event, index, mapping);\n if (mapped) out.push(mapped);\n });\n return out;\n}\n\nfunction mapOneEvent(\n hitParams: GA4HitParams,\n event: GA4Event,\n index: number,\n mapping: GA4Mapping,\n): WalkerOS.DeepPartialEvent | null {\n const ruleEntry = mapping[event.en] ?? mapping['*'];\n if (!ruleEntry) return null;\n // For v1, only single-rule entries are supported (no array fan-out).\n const rule: Mapping.Rule = Array.isArray(ruleEntry)\n ? ruleEntry[0]\n : ruleEntry;\n if (!rule || rule.ignore) return null;\n\n const lookup: LookupRoot = {\n hit: hitParams,\n params: event.params,\n items: event.items,\n en: event.en,\n ...(event.items.length > 0 ? { firstItem: event.items[0] } : {}),\n };\n\n const evaluated: WalkerOS.DeepPartialEvent = {};\n\n if (rule.name) {\n const [entity, ...rest] = rule.name.split(' ');\n evaluated.name = rule.name;\n evaluated.entity = entity;\n evaluated.action = rest.join(' ');\n }\n\n if (rule.data !== undefined) {\n let data = evalValue(rule.data, lookup);\n if (rule.remove && isObject(data)) {\n for (const path of rule.remove) data = deleteByPath(data, path);\n }\n if (data !== undefined) {\n evaluated.data = data as WalkerOS.Properties;\n }\n }\n\n // Hit-level merge: user / source. Browser language (ul) and screen\n // resolution (sr) describe the visitor, so they go to user; globals stays\n // free for site-level facts such as the site's own language.\n evaluated.user = {\n ...(hitParams.uid !== undefined ? { id: hitParams.uid } : {}),\n ...(hitParams.cid !== undefined ? { device: hitParams.cid } : {}),\n ...(hitParams.sid !== undefined ? { session: hitParams.sid } : {}),\n ...(hitParams.ul !== undefined ? { language: hitParams.ul } : {}),\n ...(hitParams.sr !== undefined ? { screenSize: hitParams.sr } : {}),\n };\n // source.type marks the GA4 decoder; url and referrer carry the page\n // context (dl, dr) as sent; the raw page load id (_p) and hit sequence (_s)\n // stay available for analysts next to the derived event id.\n evaluated.source = {\n type: 'ga4',\n ...(hitParams.dl !== undefined ? { url: hitParams.dl } : {}),\n ...(hitParams.dr !== undefined ? { referrer: hitParams.dr } : {}),\n ...(hitParams.p !== undefined ? { platform: hitParams.p } : {}),\n ...(hitParams._p !== undefined ? { pageLoadId: hitParams._p } : {}),\n ...(hitParams._s !== undefined ? { hitSequence: hitParams._s } : {}),\n };\n\n // Consent from gcs string, id derived from the hit (see getEventId),\n // timestamp is the receive time (sid is the session start, not the event\n // time), timing from event._et (ms; 0 if absent), trigger hard-coded to\n // \"ga4\" for v1.\n evaluated.consent = parseConsent(hitParams);\n evaluated.id = getEventId(hitParams, index);\n evaluated.timestamp = Date.now();\n evaluated.timing = event.params._et ?? 0;\n evaluated.trigger = 'ga4';\n\n if (event.items.length > 0) {\n evaluated.nested = itemsToNested(event.items);\n }\n\n return evaluated;\n}\n\n/**\n * GA4 items → walkerOS nested product entities.\n * Field naming: camelCase GA4Item keys are emitted as snake_case data keys\n * (`listName` → `list_name`, `promotionId` → `promotion_id`, etc).\n * Custom k<N>/v<N> pairs are spread as-is.\n *\n * Returns `WalkerOS.Entities` (i.e. `Array<WalkerOS.Entity>`). Note that the\n * canonical `WalkerOS.Entity` interface uses `entity: string` as the kind\n * discriminator (not `type`).\n */\nfunction itemsToNested(items: GA4Item[]): WalkerOS.Entities {\n return items.map((item): WalkerOS.Entity => {\n const data: WalkerOS.Properties = {};\n if (item.id !== undefined) data.id = item.id;\n if (item.name !== undefined) data.name = item.name;\n if (item.brand !== undefined) data.brand = item.brand;\n if (item.category !== undefined) data.category = item.category;\n if (item.category2 !== undefined) data.category2 = item.category2;\n if (item.category3 !== undefined) data.category3 = item.category3;\n if (item.category4 !== undefined) data.category4 = item.category4;\n if (item.category5 !== undefined) data.category5 = item.category5;\n if (item.variant !== undefined) data.variant = item.variant;\n if (item.price !== undefined) data.price = item.price;\n if (item.quantity !== undefined) data.quantity = item.quantity;\n if (item.coupon !== undefined) data.coupon = item.coupon;\n if (item.discount !== undefined) data.discount = item.discount;\n if (item.listName !== undefined) data.list_name = item.listName;\n if (item.listId !== undefined) data.list_id = item.listId;\n if (item.listPosition !== undefined) data.list_position = item.listPosition;\n if (item.locationId !== undefined) data.location_id = item.locationId;\n if (item.affiliation !== undefined) data.affiliation = item.affiliation;\n if (item.creativeName !== undefined) data.creative_name = item.creativeName;\n if (item.creativeSlot !== undefined) data.creative_slot = item.creativeSlot;\n if (item.promotionId !== undefined) data.promotion_id = item.promotionId;\n if (item.promotionName !== undefined)\n data.promotion_name = item.promotionName;\n for (const [k, v] of Object.entries(item.custom)) data[k] = v;\n return { entity: 'product', data, nested: [], context: {} };\n });\n}\n\n/**\n * Minimal mapping evaluator. v1 supports:\n * - string → path lookup via getByPath\n * - Array<Value> → fallback chain: first non-undefined wins\n * - { value: X } → literal\n * - { key: 'path' } → path lookup\n * - { map: {...} } → recursive object build\n *\n * Deferred to v2: condition, loop, fn, validate, set, consent, $code.\n */\nfunction evalValue(\n value: Mapping.Value | Mapping.Values,\n root: LookupRoot,\n): unknown {\n if (Array.isArray(value)) {\n for (const v of value) {\n const resolved = evalValue(v, root);\n if (resolved !== undefined && resolved !== null) return resolved;\n }\n return undefined;\n }\n if (typeof value === 'string') {\n return getByPath(root, value);\n }\n if (typeof value === 'object' && value !== null) {\n if ('value' in value && value.value !== undefined) return value.value;\n if ('key' in value && typeof value.key === 'string') {\n return getByPath(root, value.key);\n }\n if ('map' in value && value.map) {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value.map)) {\n const resolved = evalValue(v, root);\n if (resolved !== undefined) out[k] = resolved;\n }\n return out;\n }\n }\n return undefined;\n}\n","import { deriveSpanId, getSpanId } from '@walkeros/core';\nimport type { GA4HitParams } from './types';\n\n/**\n * walkerOS event id for the `index`-th event of a GA4 hit.\n *\n * Shaped like every other walkerOS event id (a W3C span id: 16 lowercase hex\n * characters), but derived from the hit instead of drawn at random, so a\n * re-delivered hit (gtag retry, proxy duplicate) decodes to the same ids and\n * dedup on `event.id` catches it. The hit key is the parent, the event's\n * position the child (the same derivation walkerOS uses for forks):\n *\n * - `_p` page load id, shared by every hit of one page load\n * - `_s` hit sequence counter within that page load\n * - index position of the event inside the hit (batched POST)\n * - `tid` and `cid` separate two properties on one page and page load\n * ids that happen to collide across devices\n *\n * Without `_p` or `_s` there is no stable per-hit key, so the id falls back\n * to a random span id: dedup then never drops such an event by mistake.\n */\nexport function getEventId(hit: GA4HitParams, index: number): string {\n const { _p: page, _s: seq } = hit;\n if (!page || !seq) return getSpanId();\n\n return deriveSpanId(\n JSON.stringify(['ga4', hit.tid ?? '', hit.cid ?? '', page, seq]),\n index,\n );\n}\n","import type { GA4Mapping } from './types';\n\n/**\n * Default GA4 event-name → walkerOS mapping rules.\n * Users may override or extend these via settings.mapping at config time.\n * The `'*'` key is the fallback for unknown event names.\n *\n * Field naming for `data.map`:\n * - Monetary: `currency` from `ep.currency`, `value` from `epn.value`,\n * `tax` from `epn.tax`, `shipping` from `epn.shipping`, `coupon` from\n * `ep.coupon`.\n * - Promotions read from `firstItem` (synthetic `items[0]` reference exposed by\n * the mapper), since GA4 encodes promotion info inside item tilde strings.\n */\nexport const defaultMapping: GA4Mapping = {\n // --- page ---\n page_view: {\n name: 'page view',\n data: {\n map: {\n id: 'hit.dl',\n title: 'hit.dt',\n referrer: 'hit.dr',\n },\n },\n },\n\n // --- ecommerce ---\n purchase: {\n name: 'order complete',\n data: {\n map: {\n id: 'params.ep.transaction_id',\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n tax: 'params.epn.tax',\n shipping: 'params.epn.shipping',\n coupon: 'params.ep.coupon',\n },\n },\n },\n view_item: {\n name: 'product view',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n add_to_cart: {\n name: 'product add',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n remove_from_cart: {\n name: 'product remove',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n view_cart: {\n name: 'cart view',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n begin_checkout: {\n name: 'order start',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n coupon: 'params.ep.coupon',\n },\n },\n },\n add_shipping_info: {\n name: 'order shipping',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n tier: 'params.ep.shipping_tier',\n },\n },\n },\n add_payment_info: {\n name: 'order payment',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n type: 'params.ep.payment_type',\n },\n },\n },\n refund: {\n name: 'order refund',\n data: {\n map: {\n id: 'params.ep.transaction_id',\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n add_to_wishlist: {\n name: 'wishlist add',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n\n // --- list / promotion ---\n view_item_list: {\n name: 'list view',\n data: {\n map: {\n id: 'params.ep.item_list_id',\n name: 'params.ep.item_list_name',\n },\n },\n },\n select_item: {\n name: 'product click',\n data: {\n map: {\n list_id: 'params.ep.item_list_id',\n list_name: 'params.ep.item_list_name',\n },\n },\n },\n view_promotion: {\n name: 'promotion view',\n data: {\n map: {\n id: 'firstItem.promotionId',\n name: 'firstItem.promotionName',\n creative: 'firstItem.creativeName',\n slot: 'firstItem.creativeSlot',\n },\n },\n },\n select_promotion: {\n name: 'promotion click',\n data: {\n map: {\n id: 'firstItem.promotionId',\n name: 'firstItem.promotionName',\n creative: 'firstItem.creativeName',\n slot: 'firstItem.creativeSlot',\n },\n },\n },\n select_content: {\n name: 'content select',\n data: {\n map: {\n type: 'params.ep.content_type',\n id: 'params.ep.content_id',\n },\n },\n },\n\n // --- engagement ---\n scroll: {\n name: 'page scroll',\n data: {\n map: {\n percent: 'params.epn.percent_scrolled',\n },\n },\n },\n click: {\n name: 'link click',\n data: {\n map: {\n url: 'params.ep.link_url',\n domain: 'params.ep.link_domain',\n outbound: 'params.ep.outbound',\n },\n },\n },\n file_download: {\n name: 'file download',\n data: {\n map: {\n name: 'params.ep.file_name',\n extension: 'params.ep.file_extension',\n url: 'params.ep.link_url',\n },\n },\n },\n video_start: {\n name: 'video start',\n data: {\n map: {\n title: 'params.ep.video_title',\n duration: 'params.epn.video_duration',\n current: 'params.epn.video_current_time',\n percent: 'params.epn.video_percent',\n },\n },\n },\n video_progress: {\n name: 'video progress',\n data: {\n map: {\n title: 'params.ep.video_title',\n duration: 'params.epn.video_duration',\n current: 'params.epn.video_current_time',\n percent: 'params.epn.video_percent',\n },\n },\n },\n video_complete: {\n name: 'video complete',\n data: {\n map: {\n title: 'params.ep.video_title',\n duration: 'params.epn.video_duration',\n current: 'params.epn.video_current_time',\n percent: 'params.epn.video_percent',\n },\n },\n },\n form_start: {\n name: 'form start',\n data: {\n map: {\n id: 'params.ep.form_id',\n name: 'params.ep.form_name',\n destination: 'params.ep.form_destination',\n },\n },\n },\n form_submit: {\n name: 'form submit',\n data: {\n map: {\n id: 'params.ep.form_id',\n name: 'params.ep.form_name',\n destination: 'params.ep.form_destination',\n },\n },\n },\n search: {\n name: 'search submit',\n data: {\n map: {\n term: 'params.ep.search_term',\n },\n },\n },\n login: {\n name: 'session login',\n data: {\n map: {\n method: 'params.ep.method',\n },\n },\n },\n sign_up: {\n name: 'session signup',\n data: {\n map: {\n method: 'params.ep.method',\n },\n },\n },\n generate_lead: {\n name: 'lead generate',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n share: {\n name: 'content share',\n data: {\n map: {\n method: 'params.ep.method',\n type: 'params.ep.content_type',\n id: 'params.ep.item_id',\n },\n },\n },\n\n // --- auto-fired noise: ignore by default (users can override) ---\n user_engagement: { ignore: true },\n session_start: { ignore: true },\n first_visit: { ignore: true },\n\n // --- fallback for unknown event names ---\n '*': {\n name: 'ga4 track',\n data: {\n map: {\n event_name: 'en',\n },\n },\n },\n};\n","import { mergeMappingRule } from '@walkeros/core';\nimport type { GA4Mapping } from './types';\n\n/**\n * Merge user mapping overrides onto the shipped defaults, per GA4 event key.\n * A user entry with `extend`/`remove` patches the default; otherwise it\n * replaces it. `GA4Mapping` values are single rules (no Rule[]), matching\n * the evaluator. `extend` only changes anything for keys that ship a default.\n */\nexport function mergeGa4Mapping(\n defaults: GA4Mapping,\n user: GA4Mapping,\n): GA4Mapping {\n const merged: GA4Mapping = { ...defaults };\n for (const key of Object.keys(user)) {\n const userRule = user[key];\n if (!userRule) continue;\n const baseRule = defaults[key];\n merged[key] = baseRule\n ? mergeMappingRule(baseRule, userRule)\n : mergeMappingRule({}, userRule);\n }\n return merged;\n}\n"],"mappings":"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","names":["import_core","val","import_core","import_core"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function e(e){return e?e.split(/\r?\n/).map(e=>e.trim()).filter(e=>e.length>0):[]}var t={id:"id",nm:"name",br:"brand",ca:"category",c2:"category2",c3:"category3",c4:"category4",c5:"category5",va:"variant",cp:"coupon",ln:"listName",li:"listId",lo:"locationId",af:"affiliation",cn:"creativeName",cs:"creativeSlot",pi:"promotionId",pn:"promotionName"},a={pr:"price",qt:"quantity",ds:"discount",lp:"listPosition"};function r(e,t,a){switch(t){case"id":return void(e.id=a);case"name":return void(e.name=a);case"brand":return void(e.brand=a);case"category":return void(e.category=a);case"category2":return void(e.category2=a);case"category3":return void(e.category3=a);case"category4":return void(e.category4=a);case"category5":return void(e.category5=a);case"variant":return void(e.variant=a);case"coupon":return void(e.coupon=a);case"listName":return void(e.listName=a);case"listId":return void(e.listId=a);case"locationId":return void(e.locationId=a);case"affiliation":return void(e.affiliation=a);case"creativeName":return void(e.creativeName=a);case"creativeSlot":return void(e.creativeSlot=a);case"promotionId":return void(e.promotionId=a);case"promotionName":return void(e.promotionName=a)}}function n(e,t,a){switch(t){case"price":return void(e.price=a);case"quantity":return void(e.quantity=a);case"discount":return void(e.discount=a);case"listPosition":return void(e.listPosition=a)}}var i=new Set(["v","tid","cid","sid","sct","seg","_p","_z","gtm","dl","dt","dr","ul","sr","uid","_dbg","_s","_ss","_fv","gcs","gcd","dma","dma_cps","npa","p"]);function o(o){const s=function(e){const t={},a=e.indexOf("?");return-1===a||new URLSearchParams(e.slice(a+1)).forEach((e,a)=>{t[a]=e}),t}(o.url),c={},p={};for(const[e,t]of Object.entries(s))i.has(e)?c[e]=t:p[e]=t;const m=o.body?e(o.body).map(e=>{const t={};return new URLSearchParams(e).forEach((e,a)=>{t[a]=e}),t}):[];return{hit:c,events:(m.length>0?m:Object.keys(p).length>0?[p]:[]).filter(e=>"string"==typeof e.en&&e.en.length>0).map(e=>function(e){const i={ep:{},epn:{},up:{},upn:{}},o=[];for(const[t,a]of Object.entries(e)){if("en"===t)continue;const e=/^pr(\d+)$/.exec(t);if(e)o.push([Number(e[1]),a]);else if(t.startsWith("ep."))i.ep[t.slice(3)]=a;else{if(t.startsWith("epn.")){const e=Number(a);Number.isNaN(e)||(i.epn[t.slice(4)]=e);continue}if(t.startsWith("up."))i.up[t.slice(3)]=a;else{if(t.startsWith("upn.")){const e=Number(a);Number.isNaN(e)||(i.upn[t.slice(4)]=e);continue}if("_et"===t){const e=Number(a);Number.isNaN(e)||(i._et=e);continue}"_c"!==t||(i._c=a)}}}o.sort((e,t)=>e[0]-t[0]);const s=o.map(([,e])=>function(e){const i={custom:{}},o=new Map,s=new Map;for(const c of e.split("~")){if(0===c.length)continue;const e=/^([kv])(\d+)(.*)$/.exec(c);if(e){const[,t,a,r]=e;"k"===t?o.set(a,r):s.set(a,r);continue}if(c.length<2)continue;const p=c.slice(0,2),m=c.slice(2),d=t[p];if(void 0!==d){r(i,d,m);continue}const u=a[p];if(void 0!==u){const e=Number(m);Number.isNaN(e)||n(i,u,e);continue}}for(const[e,t]of o){const a=s.get(e);void 0!==a&&(i.custom[t]=a)}return i}(e));return{en:e.en,params:i,items:s}}(e))}}import{getByPath as s,deleteByPath as c,isObject as p}from"@walkeros/core";import{deriveSpanId as m,getSpanId as d}from"@walkeros/core";function u(e,t){const a=[];return e.events.forEach((r,n)=>{const i=function(e,t,a,r){const n=r[t.en]??r["*"];if(!n)return null;const i=Array.isArray(n)?n[0]:n;if(!i||i.ignore)return null;const o={hit:e,params:t.params,items:t.items,en:t.en,...t.items.length>0?{firstItem:t.items[0]}:{}},s={};if(i.name){const[e,...t]=i.name.split(" ");s.name=i.name,s.entity=e,s.action=t.join(" ")}if(void 0!==i.data){let e=l(i.data,o);if(i.remove&&p(e))for(const t of i.remove)e=c(e,t);void 0!==e&&(s.data=e)}s.user={...void 0!==e.uid?{id:e.uid}:{},...void 0!==e.cid?{device:e.cid}:{},...void 0!==e.sid?{session:e.sid}:{},...void 0!==e.ul?{language:e.ul}:{},...void 0!==e.sr?{screenSize:e.sr}:{}},s.source={type:"ga4",...void 0!==e.dl?{url:e.dl}:{},...void 0!==e.dr?{referrer:e.dr}:{},...void 0!==e.p?{platform:e.p}:{},...void 0!==e._p?{pageLoadId:e._p}:{},...void 0!==e._s?{hitSequence:e._s}:{}},s.consent=function(e){const t=e.gcs;return t&&/^G[01]{3}$/.test(t)?{marketing:"1"===t[2],analytics:"1"===t[3]}:{}}(e),s.id=function(e,t){const{_p:a,_s:r}=e;return a&&r?m(JSON.stringify(["ga4",e.tid??"",e.cid??"",a,r]),t):d()}(e,a),s.timestamp=Date.now(),s.timing=t.params._et??0,s.trigger="ga4",t.items.length>0&&(s.nested=t.items.map(e=>{const t={};void 0!==e.id&&(t.id=e.id),void 0!==e.name&&(t.name=e.name),void 0!==e.brand&&(t.brand=e.brand),void 0!==e.category&&(t.category=e.category),void 0!==e.category2&&(t.category2=e.category2),void 0!==e.category3&&(t.category3=e.category3),void 0!==e.category4&&(t.category4=e.category4),void 0!==e.category5&&(t.category5=e.category5),void 0!==e.variant&&(t.variant=e.variant),void 0!==e.price&&(t.price=e.price),void 0!==e.quantity&&(t.quantity=e.quantity),void 0!==e.coupon&&(t.coupon=e.coupon),void 0!==e.discount&&(t.discount=e.discount),void 0!==e.listName&&(t.list_name=e.listName),void 0!==e.listId&&(t.list_id=e.listId),void 0!==e.listPosition&&(t.list_position=e.listPosition),void 0!==e.locationId&&(t.location_id=e.locationId),void 0!==e.affiliation&&(t.affiliation=e.affiliation),void 0!==e.creativeName&&(t.creative_name=e.creativeName),void 0!==e.creativeSlot&&(t.creative_slot=e.creativeSlot),void 0!==e.promotionId&&(t.promotion_id=e.promotionId),void 0!==e.promotionName&&(t.promotion_name=e.promotionName);for(const[a,r]of Object.entries(e.custom))t[a]=r;return{entity:"product",data:t,nested:[],context:{}}}));return s}(e.hit,r,n,t);i&&a.push(i)}),a}function l(e,t){if(Array.isArray(e))for(const a of e){const e=l(a,t);if(null!=e)return e}else{if("string"==typeof e)return s(t,e);if("object"==typeof e&&null!==e){if("value"in e&&void 0!==e.value)return e.value;if("key"in e&&"string"==typeof e.key)return s(t,e.key);if("map"in e&&e.map){const a={};for(const[r,n]of Object.entries(e.map)){const e=l(n,t);void 0!==e&&(a[r]=e)}return a}}}}var v={page_view:{name:"page view",data:{map:{id:"hit.dl",title:"hit.dt",referrer:"hit.dr"}}},purchase:{name:"order complete",data:{map:{id:"params.ep.transaction_id",currency:"params.ep.currency",total:"params.epn.value",tax:"params.epn.tax",shipping:"params.epn.shipping",coupon:"params.ep.coupon"}}},view_item:{name:"product view",data:{map:{currency:"params.ep.currency",value:"params.epn.value"}}},add_to_cart:{name:"product add",data:{map:{currency:"params.ep.currency",value:"params.epn.value"}}},remove_from_cart:{name:"product remove",data:{map:{currency:"params.ep.currency",value:"params.epn.value"}}},view_cart:{name:"cart view",data:{map:{currency:"params.ep.currency",value:"params.epn.value"}}},begin_checkout:{name:"order start",data:{map:{currency:"params.ep.currency",value:"params.epn.value",coupon:"params.ep.coupon"}}},add_shipping_info:{name:"order shipping",data:{map:{currency:"params.ep.currency",value:"params.epn.value",tier:"params.ep.shipping_tier"}}},add_payment_info:{name:"order payment",data:{map:{currency:"params.ep.currency",value:"params.epn.value",type:"params.ep.payment_type"}}},refund:{name:"order refund",data:{map:{id:"params.ep.transaction_id",currency:"params.ep.currency",total:"params.epn.value"}}},add_to_wishlist:{name:"wishlist add",data:{map:{currency:"params.ep.currency",value:"params.epn.value"}}},view_item_list:{name:"list view",data:{map:{id:"params.ep.item_list_id",name:"params.ep.item_list_name"}}},select_item:{name:"product click",data:{map:{list_id:"params.ep.item_list_id",list_name:"params.ep.item_list_name"}}},view_promotion:{name:"promotion view",data:{map:{id:"firstItem.promotionId",name:"firstItem.promotionName",creative:"firstItem.creativeName",slot:"firstItem.creativeSlot"}}},select_promotion:{name:"promotion click",data:{map:{id:"firstItem.promotionId",name:"firstItem.promotionName",creative:"firstItem.creativeName",slot:"firstItem.creativeSlot"}}},select_content:{name:"content select",data:{map:{type:"params.ep.content_type",id:"params.ep.content_id"}}},scroll:{name:"page scroll",data:{map:{percent:"params.epn.percent_scrolled"}}},click:{name:"link click",data:{map:{url:"params.ep.link_url",domain:"params.ep.link_domain",outbound:"params.ep.outbound"}}},file_download:{name:"file download",data:{map:{name:"params.ep.file_name",extension:"params.ep.file_extension",url:"params.ep.link_url"}}},video_start:{name:"video start",data:{map:{title:"params.ep.video_title",duration:"params.epn.video_duration",current:"params.epn.video_current_time",percent:"params.epn.video_percent"}}},video_progress:{name:"video progress",data:{map:{title:"params.ep.video_title",duration:"params.epn.video_duration",current:"params.epn.video_current_time",percent:"params.epn.video_percent"}}},video_complete:{name:"video complete",data:{map:{title:"params.ep.video_title",duration:"params.epn.video_duration",current:"params.epn.video_current_time",percent:"params.epn.video_percent"}}},form_start:{name:"form start",data:{map:{id:"params.ep.form_id",name:"params.ep.form_name",destination:"params.ep.form_destination"}}},form_submit:{name:"form submit",data:{map:{id:"params.ep.form_id",name:"params.ep.form_name",destination:"params.ep.form_destination"}}},search:{name:"search submit",data:{map:{term:"params.ep.search_term"}}},login:{name:"session login",data:{map:{method:"params.ep.method"}}},sign_up:{name:"session signup",data:{map:{method:"params.ep.method"}}},generate_lead:{name:"lead generate",data:{map:{currency:"params.ep.currency",value:"params.epn.value"}}},share:{name:"content share",data:{map:{method:"params.ep.method",type:"params.ep.content_type",id:"params.ep.item_id"}}},user_engagement:{ignore:!0},session_start:{ignore:!0},first_visit:{ignore:!0},"*":{name:"ga4 track",data:{map:{event_name:"en"}}}};import{mergeMappingRule as f}from"@walkeros/core";var g=t=>{const{config:a}=t,r=a.settings??{},n=new RegExp(r.tidPattern??"^G-"),i=r.maxEvents??100,s=function(e,t){const a={...e};for(const r of Object.keys(t)){const n=t[r];if(!n)continue;const i=e[r];a[r]=f(i||{},n)}return a}(v,r.mapping??{});return{type:"ga4",config:a,async push(t,a){const{logger:r}=a,c=function(e){const t=e.url;if("string"!=typeof t)return null;const a=e.body;return{url:t,body:"string"==typeof a?a:void 0}}(a.ingest);if(!c)return r.debug("transformer-ga4: no request in ingest; skipping"),!1;if(c.body){const t=e(c.body).length;if(t>i)return r.warn(`transformer-ga4: ${t} events exceed maxEvents ${i}; request dropped`),!1}try{const e=o(c);if(e.hit.tid&&!n.test(e.hit.tid))return r.debug(`transformer-ga4: tid ${e.hit.tid} dropped by tidPattern`),!1;const t=u(e,s);return 0===t.length?(r.debug(`transformer-ga4: no events mapped (tid=${e.hit.tid??"unknown"})`),!1):(r.debug(`transformer-ga4: decoded ${t.length} events from ${e.hit.tid??"unknown"}`),1===t.length?{event:t[0]}:t.map(e=>({event:e})))}catch(e){const t=e instanceof Error?e.message:String(e);return r.error(`transformer-ga4: parse error: ${t}`),!1}}}};export{g as default,v as defaultMapping,g as transformerGa4};//# sourceMappingURL=index.mjs.map
|
|
1
|
+
import{StepError as e,reportCause as t,stepId as a}from"@walkeros/core";function r(e){return e?e.split(/\r?\n/).map(e=>e.trim()).filter(e=>e.length>0):[]}var n={id:"id",nm:"name",br:"brand",ca:"category",c2:"category2",c3:"category3",c4:"category4",c5:"category5",va:"variant",cp:"coupon",ln:"listName",li:"listId",lo:"locationId",af:"affiliation",cn:"creativeName",cs:"creativeSlot",pi:"promotionId",pn:"promotionName"},i={pr:"price",qt:"quantity",ds:"discount",lp:"listPosition"};function o(e,t,a){switch(t){case"id":return void(e.id=a);case"name":return void(e.name=a);case"brand":return void(e.brand=a);case"category":return void(e.category=a);case"category2":return void(e.category2=a);case"category3":return void(e.category3=a);case"category4":return void(e.category4=a);case"category5":return void(e.category5=a);case"variant":return void(e.variant=a);case"coupon":return void(e.coupon=a);case"listName":return void(e.listName=a);case"listId":return void(e.listId=a);case"locationId":return void(e.locationId=a);case"affiliation":return void(e.affiliation=a);case"creativeName":return void(e.creativeName=a);case"creativeSlot":return void(e.creativeSlot=a);case"promotionId":return void(e.promotionId=a);case"promotionName":return void(e.promotionName=a)}}function s(e,t,a){switch(t){case"price":return void(e.price=a);case"quantity":return void(e.quantity=a);case"discount":return void(e.discount=a);case"listPosition":return void(e.listPosition=a)}}var c=new Set(["v","tid","cid","sid","sct","seg","_p","_z","gtm","dl","dt","dr","ul","sr","uid","_dbg","_s","_ss","_fv","gcs","gcd","dma","dma_cps","npa","p"]);function p(e){const t=function(e){const t={},a=e.indexOf("?");return-1===a||new URLSearchParams(e.slice(a+1)).forEach((e,a)=>{t[a]=e}),t}(e.url),a={},p={};for(const[e,r]of Object.entries(t))c.has(e)?a[e]=r:p[e]=r;const m=e.body?r(e.body).map(e=>{const t={};return new URLSearchParams(e).forEach((e,a)=>{t[a]=e}),t}):[];return{hit:a,events:(m.length>0?m:Object.keys(p).length>0?[p]:[]).filter(e=>"string"==typeof e.en&&e.en.length>0).map(e=>function(e){const t={ep:{},epn:{},up:{},upn:{}},a=[];for(const[r,n]of Object.entries(e)){if("en"===r)continue;const e=/^pr(\d+)$/.exec(r);if(e)a.push([Number(e[1]),n]);else if(r.startsWith("ep."))t.ep[r.slice(3)]=n;else{if(r.startsWith("epn.")){const e=Number(n);Number.isNaN(e)||(t.epn[r.slice(4)]=e);continue}if(r.startsWith("up."))t.up[r.slice(3)]=n;else{if(r.startsWith("upn.")){const e=Number(n);Number.isNaN(e)||(t.upn[r.slice(4)]=e);continue}if("_et"===r){const e=Number(n);Number.isNaN(e)||(t._et=e);continue}"_c"!==r||(t._c=n)}}}a.sort((e,t)=>e[0]-t[0]);const r=a.map(([,e])=>function(e){const t={custom:{}},a=new Map,r=new Map;for(const c of e.split("~")){if(0===c.length)continue;const e=/^([kv])(\d+)(.*)$/.exec(c);if(e){const[,t,n,i]=e;"k"===t?a.set(n,i):r.set(n,i);continue}if(c.length<2)continue;const p=c.slice(0,2),m=c.slice(2),d=n[p];if(void 0!==d){o(t,d,m);continue}const u=i[p];if(void 0!==u){const e=Number(m);Number.isNaN(e)||s(t,u,e);continue}}for(const[e,n]of a){const a=r.get(e);void 0!==a&&(t.custom[n]=a)}return t}(e));return{en:e.en,params:t,items:r}}(e))}}import{getByPath as m,deleteByPath as d,isObject as u}from"@walkeros/core";import{deriveSpanId as l,getSpanId as v}from"@walkeros/core";function f(e,t){const a=[];return e.events.forEach((r,n)=>{const i=function(e,t,a,r){const n=r[t.en]??r["*"];if(!n)return null;const i=Array.isArray(n)?n[0]:n;if(!i||i.ignore)return null;const o={hit:e,params:t.params,items:t.items,en:t.en,...t.items.length>0?{firstItem:t.items[0]}:{}},s={};if(i.name){const[e,...t]=i.name.split(" ");s.name=i.name,s.entity=e,s.action=t.join(" ")}if(void 0!==i.data){let e=g(i.data,o);if(i.remove&&u(e))for(const t of i.remove)e=d(e,t);void 0!==e&&(s.data=e)}s.user={...void 0!==e.uid?{id:e.uid}:{},...void 0!==e.cid?{device:e.cid}:{},...void 0!==e.sid?{session:e.sid}:{},...void 0!==e.ul?{language:e.ul}:{},...void 0!==e.sr?{screenSize:e.sr}:{}},s.source={type:"ga4",...void 0!==e.dl?{url:e.dl}:{},...void 0!==e.dr?{referrer:e.dr}:{},...void 0!==e.p?{platform:e.p}:{},...void 0!==e._p?{pageLoadId:e._p}:{},...void 0!==e._s?{hitSequence:e._s}:{}},s.consent=function(e){const t=e.gcs;return t&&/^G[01]{3}$/.test(t)?{marketing:"1"===t[2],analytics:"1"===t[3]}:{}}(e),s.id=function(e,t){const{_p:a,_s:r}=e;return a&&r?l(JSON.stringify(["ga4",e.tid??"",e.cid??"",a,r]),t):v()}(e,a),s.timestamp=Date.now(),s.timing=t.params._et??0,s.trigger="ga4",t.items.length>0&&(s.nested=t.items.map(e=>{const t={};void 0!==e.id&&(t.id=e.id),void 0!==e.name&&(t.name=e.name),void 0!==e.brand&&(t.brand=e.brand),void 0!==e.category&&(t.category=e.category),void 0!==e.category2&&(t.category2=e.category2),void 0!==e.category3&&(t.category3=e.category3),void 0!==e.category4&&(t.category4=e.category4),void 0!==e.category5&&(t.category5=e.category5),void 0!==e.variant&&(t.variant=e.variant),void 0!==e.price&&(t.price=e.price),void 0!==e.quantity&&(t.quantity=e.quantity),void 0!==e.coupon&&(t.coupon=e.coupon),void 0!==e.discount&&(t.discount=e.discount),void 0!==e.listName&&(t.list_name=e.listName),void 0!==e.listId&&(t.list_id=e.listId),void 0!==e.listPosition&&(t.list_position=e.listPosition),void 0!==e.locationId&&(t.location_id=e.locationId),void 0!==e.affiliation&&(t.affiliation=e.affiliation),void 0!==e.creativeName&&(t.creative_name=e.creativeName),void 0!==e.creativeSlot&&(t.creative_slot=e.creativeSlot),void 0!==e.promotionId&&(t.promotion_id=e.promotionId),void 0!==e.promotionName&&(t.promotion_name=e.promotionName);for(const[a,r]of Object.entries(e.custom))t[a]=r;return{entity:"product",data:t,nested:[],context:{}}}));return s}(e.hit,r,n,t);i&&a.push(i)}),a}function g(e,t){if(Array.isArray(e))for(const a of e){const e=g(a,t);if(null!=e)return e}else{if("string"==typeof e)return m(t,e);if("object"==typeof e&&null!==e){if("value"in e&&void 0!==e.value)return e.value;if("key"in e&&"string"==typeof e.key)return m(t,e.key);if("map"in e&&e.map){const a={};for(const[r,n]of Object.entries(e.map)){const e=g(n,t);void 0!==e&&(a[r]=e)}return a}}}}var _={page_view:{name:"page view",data:{map:{id:"hit.dl",title:"hit.dt",referrer:"hit.dr"}}},purchase:{name:"order complete",data:{map:{id:"params.ep.transaction_id",currency:"params.ep.currency",value:"params.epn.value",tax:"params.epn.tax",shipping:"params.epn.shipping",coupon:"params.ep.coupon"}}},view_item:{name:"product view",data:{map:{currency:"params.ep.currency",value:"params.epn.value"}}},add_to_cart:{name:"product add",data:{map:{currency:"params.ep.currency",value:"params.epn.value"}}},remove_from_cart:{name:"product remove",data:{map:{currency:"params.ep.currency",value:"params.epn.value"}}},view_cart:{name:"cart view",data:{map:{currency:"params.ep.currency",value:"params.epn.value"}}},begin_checkout:{name:"order start",data:{map:{currency:"params.ep.currency",value:"params.epn.value",coupon:"params.ep.coupon"}}},add_shipping_info:{name:"order shipping",data:{map:{currency:"params.ep.currency",value:"params.epn.value",tier:"params.ep.shipping_tier"}}},add_payment_info:{name:"order payment",data:{map:{currency:"params.ep.currency",value:"params.epn.value",type:"params.ep.payment_type"}}},refund:{name:"order refund",data:{map:{id:"params.ep.transaction_id",currency:"params.ep.currency",value:"params.epn.value"}}},add_to_wishlist:{name:"wishlist add",data:{map:{currency:"params.ep.currency",value:"params.epn.value"}}},view_item_list:{name:"list view",data:{map:{id:"params.ep.item_list_id",name:"params.ep.item_list_name"}}},select_item:{name:"product click",data:{map:{list_id:"params.ep.item_list_id",list_name:"params.ep.item_list_name"}}},view_promotion:{name:"promotion view",data:{map:{id:"firstItem.promotionId",name:"firstItem.promotionName",creative:"firstItem.creativeName",slot:"firstItem.creativeSlot"}}},select_promotion:{name:"promotion click",data:{map:{id:"firstItem.promotionId",name:"firstItem.promotionName",creative:"firstItem.creativeName",slot:"firstItem.creativeSlot"}}},select_content:{name:"content select",data:{map:{type:"params.ep.content_type",id:"params.ep.content_id"}}},scroll:{name:"page scroll",data:{map:{percent:"params.epn.percent_scrolled"}}},click:{name:"link click",data:{map:{url:"params.ep.link_url",domain:"params.ep.link_domain",outbound:"params.ep.outbound"}}},file_download:{name:"file download",data:{map:{name:"params.ep.file_name",extension:"params.ep.file_extension",url:"params.ep.link_url"}}},video_start:{name:"video start",data:{map:{title:"params.ep.video_title",duration:"params.epn.video_duration",current:"params.epn.video_current_time",percent:"params.epn.video_percent"}}},video_progress:{name:"video progress",data:{map:{title:"params.ep.video_title",duration:"params.epn.video_duration",current:"params.epn.video_current_time",percent:"params.epn.video_percent"}}},video_complete:{name:"video complete",data:{map:{title:"params.ep.video_title",duration:"params.epn.video_duration",current:"params.epn.video_current_time",percent:"params.epn.video_percent"}}},form_start:{name:"form start",data:{map:{id:"params.ep.form_id",name:"params.ep.form_name",destination:"params.ep.form_destination"}}},form_submit:{name:"form submit",data:{map:{id:"params.ep.form_id",name:"params.ep.form_name",destination:"params.ep.form_destination"}}},search:{name:"search submit",data:{map:{term:"params.ep.search_term"}}},login:{name:"session login",data:{map:{method:"params.ep.method"}}},sign_up:{name:"session signup",data:{map:{method:"params.ep.method"}}},generate_lead:{name:"lead generate",data:{map:{currency:"params.ep.currency",value:"params.epn.value"}}},share:{name:"content share",data:{map:{method:"params.ep.method",type:"params.ep.content_type",id:"params.ep.item_id"}}},user_engagement:{ignore:!0},session_start:{ignore:!0},first_visit:{ignore:!0},"*":{name:"ga4 track",data:{map:{event_name:"en"}}}};import{mergeMappingRule as y}from"@walkeros/core";var h=n=>{const{config:i}=n,o=i.settings??{},s=new RegExp(o.tidPattern??"^G-"),c=o.maxEvents??100,m=function(e,t){const a={...e};for(const r of Object.keys(t)){const n=t[r];if(!n)continue;const i=e[r];a[r]=y(i||{},n)}return a}(_,o.mapping??{});return{type:"ga4",config:i,async push(n,i){const{logger:o,collector:d,id:u}=i,l=function(e){const t=e.url;if("string"!=typeof t)return null;const a=e.body;return{url:t,body:"string"==typeof a?a:void 0}}(i.ingest);if(!l)throw new e("transformer-ga4: no GA4 request in ingest (ingest.url missing)",{code:"missing-input"});if(l.body){const e=r(l.body).length;if(e>c)return t(d,o,{step:a("transformer",u),code:"invalid"},`transformer-ga4: ${e} events exceed maxEvents ${c}; request dropped`,{events:e,maxEvents:c}),!1}try{const e=p(l);if(e.hit.tid&&!s.test(e.hit.tid))return o.debug(`transformer-ga4: tid ${e.hit.tid} dropped by tidPattern`),!1;const t=f(e,m);return 0===t.length?(o.debug(`transformer-ga4: no events mapped (tid=${e.hit.tid??"unknown"})`),!1):(o.debug(`transformer-ga4: decoded ${t.length} events from ${e.hit.tid??"unknown"}`),1===t.length?{event:t[0]}:t.map(e=>({event:e})))}catch(t){throw new e("transformer-ga4: the GA4 request could not be decoded",{code:"transformer",cause:t})}}}};export{h as default,_ as defaultMapping,h as transformerGa4};//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/parse.ts","../src/map.ts","../src/id.ts","../src/defaults.ts","../src/merge.ts","../src/transformer.ts"],"sourcesContent":["import type { WalkerOS } from '@walkeros/core';\nimport type {\n GA4Event,\n GA4EventParams,\n GA4Hit,\n GA4HitParams,\n GA4Item,\n GA4Request,\n} from './types';\n\nexport function parseQuery(url: string): Record<string, string> {\n const out: Record<string, string> = {};\n const qIdx = url.indexOf('?');\n if (qIdx === -1) return out;\n const usp = new URLSearchParams(url.slice(qIdx + 1));\n usp.forEach((value, key) => {\n out[key] = value;\n });\n return out;\n}\n\n/** Non-empty, trimmed lines of a POST body: one GA4 event per line. */\nexport function splitBodyLines(body: string): string[] {\n if (!body) return [];\n return body\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n}\n\nexport function parseBody(body: string): Record<string, string>[] {\n return splitBodyLines(body).map((line) => {\n const out: Record<string, string> = {};\n const usp = new URLSearchParams(line);\n usp.forEach((v, k) => {\n out[k] = v;\n });\n return out;\n });\n}\n\n// Two separate dispatch maps keyed by 2-char prefix.\n// Split by type so the assignment site stays type-safe (no cast).\nconst ITEM_STRING_FIELDS: Readonly<Record<string, keyof GA4Item>> = {\n id: 'id',\n nm: 'name',\n br: 'brand',\n ca: 'category',\n c2: 'category2',\n c3: 'category3',\n c4: 'category4',\n c5: 'category5',\n va: 'variant',\n cp: 'coupon',\n ln: 'listName',\n li: 'listId',\n lo: 'locationId',\n af: 'affiliation',\n cn: 'creativeName',\n cs: 'creativeSlot',\n pi: 'promotionId',\n pn: 'promotionName',\n};\n\nconst ITEM_NUMBER_FIELDS: Readonly<Record<string, keyof GA4Item>> = {\n pr: 'price',\n qt: 'quantity',\n ds: 'discount',\n lp: 'listPosition',\n};\n\nfunction setItemString(item: GA4Item, key: keyof GA4Item, value: string): void {\n // Narrow assignment by key — these are the string-typed fields.\n switch (key) {\n case 'id':\n item.id = value;\n return;\n case 'name':\n item.name = value;\n return;\n case 'brand':\n item.brand = value;\n return;\n case 'category':\n item.category = value;\n return;\n case 'category2':\n item.category2 = value;\n return;\n case 'category3':\n item.category3 = value;\n return;\n case 'category4':\n item.category4 = value;\n return;\n case 'category5':\n item.category5 = value;\n return;\n case 'variant':\n item.variant = value;\n return;\n case 'coupon':\n item.coupon = value;\n return;\n case 'listName':\n item.listName = value;\n return;\n case 'listId':\n item.listId = value;\n return;\n case 'locationId':\n item.locationId = value;\n return;\n case 'affiliation':\n item.affiliation = value;\n return;\n case 'creativeName':\n item.creativeName = value;\n return;\n case 'creativeSlot':\n item.creativeSlot = value;\n return;\n case 'promotionId':\n item.promotionId = value;\n return;\n case 'promotionName':\n item.promotionName = value;\n return;\n }\n}\n\nfunction setItemNumber(item: GA4Item, key: keyof GA4Item, value: number): void {\n switch (key) {\n case 'price':\n item.price = value;\n return;\n case 'quantity':\n item.quantity = value;\n return;\n case 'discount':\n item.discount = value;\n return;\n case 'listPosition':\n item.listPosition = value;\n return;\n }\n}\n\nexport function parseItem(value: string): GA4Item {\n const item: GA4Item = { custom: {} };\n const customKeys = new Map<string, string>();\n const customVals = new Map<string, string>();\n\n for (const raw of value.split('~')) {\n if (raw.length === 0) continue;\n\n // Variable-length k<N>/v<N> (must match before 2-char prefix lookup\n // because k10/v10 etc are wider than 2 chars).\n const kvMatch = /^([kv])(\\d+)(.*)$/.exec(raw);\n if (kvMatch) {\n const [, kind, idx, val] = kvMatch;\n if (kind === 'k') customKeys.set(idx, val);\n else customVals.set(idx, val);\n continue;\n }\n\n if (raw.length < 2) continue;\n const prefix = raw.slice(0, 2);\n const val = raw.slice(2);\n\n const stringKey = ITEM_STRING_FIELDS[prefix];\n if (stringKey !== undefined) {\n setItemString(item, stringKey, val);\n continue;\n }\n const numberKey = ITEM_NUMBER_FIELDS[prefix];\n if (numberKey !== undefined) {\n const n = Number(val);\n if (!Number.isNaN(n)) setItemNumber(item, numberKey, n);\n continue;\n }\n }\n\n // Pair k<N> with v<N>; drop unmatched.\n for (const [idx, key] of customKeys) {\n const v = customVals.get(idx);\n if (v !== undefined) item.custom[key] = v;\n }\n return item;\n}\n\nexport function parseConsent(hit: { gcs?: string }): WalkerOS.Consent {\n const gcs = hit.gcs;\n if (!gcs || !/^G[01]{3}$/.test(gcs)) return {};\n // gcs[1] is reserved (always '1' for current consent mode v2)\n return {\n marketing: gcs[2] === '1', // ad_storage\n analytics: gcs[3] === '1', // analytics_storage\n };\n}\n\nconst HIT_PARAM_KEYS = new Set([\n 'v',\n 'tid',\n 'cid',\n 'sid',\n 'sct',\n 'seg',\n '_p',\n '_z',\n 'gtm',\n 'dl',\n 'dt',\n 'dr',\n 'ul',\n 'sr',\n 'uid',\n '_dbg',\n '_s',\n '_ss',\n '_fv',\n 'gcs',\n 'gcd',\n 'dma',\n 'dma_cps',\n 'npa',\n 'p',\n]);\n\nexport function parseRequest(req: GA4Request): GA4Hit {\n const queryParams = parseQuery(req.url);\n const hit: GA4HitParams = {};\n const queryEventParams: Record<string, string> = {};\n\n // Split URL params into hit-level vs event-level.\n for (const [k, v] of Object.entries(queryParams)) {\n if (HIT_PARAM_KEYS.has(k)) hit[k] = v;\n else queryEventParams[k] = v;\n }\n\n // GET: event params live in the URL alongside hit params.\n // POST: in body lines.\n const bodyLines = req.body ? parseBody(req.body) : [];\n const rawEvents =\n bodyLines.length > 0\n ? bodyLines\n : Object.keys(queryEventParams).length > 0\n ? [queryEventParams]\n : [];\n\n const events: GA4Event[] = rawEvents\n .filter((raw) => typeof raw.en === 'string' && raw.en.length > 0)\n .map((raw) => buildEvent(raw));\n\n return { hit, events };\n}\n\nfunction buildEvent(raw: Record<string, string>): GA4Event {\n const params: GA4EventParams = { ep: {}, epn: {}, up: {}, upn: {} };\n const itemEntries: Array<[number, string]> = [];\n\n for (const [k, v] of Object.entries(raw)) {\n if (k === 'en') continue;\n\n // pr<N> items\n const prMatch = /^pr(\\d+)$/.exec(k);\n if (prMatch) {\n itemEntries.push([Number(prMatch[1]), v]);\n continue;\n }\n\n // Dotted-key nesting\n if (k.startsWith('ep.')) {\n params.ep[k.slice(3)] = v;\n continue;\n }\n if (k.startsWith('epn.')) {\n const n = Number(v);\n if (!Number.isNaN(n)) params.epn[k.slice(4)] = n;\n continue;\n }\n if (k.startsWith('up.')) {\n params.up[k.slice(3)] = v;\n continue;\n }\n if (k.startsWith('upn.')) {\n const n = Number(v);\n if (!Number.isNaN(n)) params.upn[k.slice(4)] = n;\n continue;\n }\n\n // Standalone numeric keys\n if (k === '_et') {\n const n = Number(v);\n if (!Number.isNaN(n)) params._et = n;\n continue;\n }\n if (k === '_c') {\n params._c = v;\n continue;\n }\n // Unknown standalone keys: drop. Add to GA4EventParams type if a future\n // need surfaces.\n }\n\n itemEntries.sort((a, b) => a[0] - b[0]);\n const items = itemEntries.map(([, v]) => parseItem(v));\n\n return { en: raw.en, params, items };\n}\n","import type { Mapping, WalkerOS } from '@walkeros/core';\nimport { getByPath, deleteByPath, isObject } from '@walkeros/core';\nimport type {\n GA4Event,\n GA4Hit,\n GA4HitParams,\n GA4Item,\n GA4Mapping,\n} from './types';\nimport { parseConsent } from './parse';\nimport { getEventId } from './id';\n\n/**\n * Lookup root for mapping path resolution. Mapping rules reference keys via\n * dotted paths: `hit.dl`, `params.ep.transaction_id`, `params.epn.value`, `items`.\n *\n * `en` exposes the GA4 event name (used by the `*` fallback so unknown events\n * can carry their original name through). `firstItem` exposes `items[0]` so\n * promotion-style mappings can resolve `firstItem.promotionId` etc. without\n * needing array-index syntax in `getByPath`.\n *\n * Typed as `Record<string, unknown> & { ... }` so `getByPath` (which accepts\n * unknown) is happy without any narrowing cast on the call site.\n */\ntype LookupRoot = Record<string, unknown> & {\n hit: GA4HitParams;\n params: GA4Event['params'];\n items: GA4Event['items'];\n en: string;\n firstItem?: GA4Item;\n};\n\n/**\n * Convert a parsed GA4 hit into zero or more walkerOS events.\n * One GA4 hit may carry several events (POST batch); each is mapped\n * independently with hit-level metadata merged in.\n */\nexport function mapHitToEvents(\n hit: GA4Hit,\n mapping: GA4Mapping,\n): WalkerOS.DeepPartialEvent[] {\n const out: WalkerOS.DeepPartialEvent[] = [];\n hit.events.forEach((event, index) => {\n const mapped = mapOneEvent(hit.hit, event, index, mapping);\n if (mapped) out.push(mapped);\n });\n return out;\n}\n\nfunction mapOneEvent(\n hitParams: GA4HitParams,\n event: GA4Event,\n index: number,\n mapping: GA4Mapping,\n): WalkerOS.DeepPartialEvent | null {\n const ruleEntry = mapping[event.en] ?? mapping['*'];\n if (!ruleEntry) return null;\n // For v1, only single-rule entries are supported (no array fan-out).\n const rule: Mapping.Rule = Array.isArray(ruleEntry)\n ? ruleEntry[0]\n : ruleEntry;\n if (!rule || rule.ignore) return null;\n\n const lookup: LookupRoot = {\n hit: hitParams,\n params: event.params,\n items: event.items,\n en: event.en,\n ...(event.items.length > 0 ? { firstItem: event.items[0] } : {}),\n };\n\n const evaluated: WalkerOS.DeepPartialEvent = {};\n\n if (rule.name) {\n const [entity, ...rest] = rule.name.split(' ');\n evaluated.name = rule.name;\n evaluated.entity = entity;\n evaluated.action = rest.join(' ');\n }\n\n if (rule.data !== undefined) {\n let data = evalValue(rule.data, lookup);\n if (rule.remove && isObject(data)) {\n for (const path of rule.remove) data = deleteByPath(data, path);\n }\n if (data !== undefined) {\n evaluated.data = data as WalkerOS.Properties;\n }\n }\n\n // Hit-level merge: user / source. Browser language (ul) and screen\n // resolution (sr) describe the visitor, so they go to user; globals stays\n // free for site-level facts such as the site's own language.\n evaluated.user = {\n ...(hitParams.uid !== undefined ? { id: hitParams.uid } : {}),\n ...(hitParams.cid !== undefined ? { device: hitParams.cid } : {}),\n ...(hitParams.sid !== undefined ? { session: hitParams.sid } : {}),\n ...(hitParams.ul !== undefined ? { language: hitParams.ul } : {}),\n ...(hitParams.sr !== undefined ? { screenSize: hitParams.sr } : {}),\n };\n // source.type marks the GA4 decoder; url and referrer carry the page\n // context (dl, dr) as sent; the raw page load id (_p) and hit sequence (_s)\n // stay available for analysts next to the derived event id.\n evaluated.source = {\n type: 'ga4',\n ...(hitParams.dl !== undefined ? { url: hitParams.dl } : {}),\n ...(hitParams.dr !== undefined ? { referrer: hitParams.dr } : {}),\n ...(hitParams.p !== undefined ? { platform: hitParams.p } : {}),\n ...(hitParams._p !== undefined ? { pageLoadId: hitParams._p } : {}),\n ...(hitParams._s !== undefined ? { hitSequence: hitParams._s } : {}),\n };\n\n // Consent from gcs string, id derived from the hit (see getEventId),\n // timestamp is the receive time (sid is the session start, not the event\n // time), timing from event._et (ms; 0 if absent), trigger hard-coded to\n // \"ga4\" for v1.\n evaluated.consent = parseConsent(hitParams);\n evaluated.id = getEventId(hitParams, index);\n evaluated.timestamp = Date.now();\n evaluated.timing = event.params._et ?? 0;\n evaluated.trigger = 'ga4';\n\n if (event.items.length > 0) {\n evaluated.nested = itemsToNested(event.items);\n }\n\n return evaluated;\n}\n\n/**\n * GA4 items → walkerOS nested product entities.\n * Field naming: camelCase GA4Item keys are emitted as snake_case data keys\n * (`listName` → `list_name`, `promotionId` → `promotion_id`, etc).\n * Custom k<N>/v<N> pairs are spread as-is.\n *\n * Returns `WalkerOS.Entities` (i.e. `Array<WalkerOS.Entity>`). Note that the\n * canonical `WalkerOS.Entity` interface uses `entity: string` as the kind\n * discriminator (not `type`).\n */\nfunction itemsToNested(items: GA4Item[]): WalkerOS.Entities {\n return items.map((item): WalkerOS.Entity => {\n const data: WalkerOS.Properties = {};\n if (item.id !== undefined) data.id = item.id;\n if (item.name !== undefined) data.name = item.name;\n if (item.brand !== undefined) data.brand = item.brand;\n if (item.category !== undefined) data.category = item.category;\n if (item.category2 !== undefined) data.category2 = item.category2;\n if (item.category3 !== undefined) data.category3 = item.category3;\n if (item.category4 !== undefined) data.category4 = item.category4;\n if (item.category5 !== undefined) data.category5 = item.category5;\n if (item.variant !== undefined) data.variant = item.variant;\n if (item.price !== undefined) data.price = item.price;\n if (item.quantity !== undefined) data.quantity = item.quantity;\n if (item.coupon !== undefined) data.coupon = item.coupon;\n if (item.discount !== undefined) data.discount = item.discount;\n if (item.listName !== undefined) data.list_name = item.listName;\n if (item.listId !== undefined) data.list_id = item.listId;\n if (item.listPosition !== undefined) data.list_position = item.listPosition;\n if (item.locationId !== undefined) data.location_id = item.locationId;\n if (item.affiliation !== undefined) data.affiliation = item.affiliation;\n if (item.creativeName !== undefined) data.creative_name = item.creativeName;\n if (item.creativeSlot !== undefined) data.creative_slot = item.creativeSlot;\n if (item.promotionId !== undefined) data.promotion_id = item.promotionId;\n if (item.promotionName !== undefined)\n data.promotion_name = item.promotionName;\n for (const [k, v] of Object.entries(item.custom)) data[k] = v;\n return { entity: 'product', data, nested: [], context: {} };\n });\n}\n\n/**\n * Minimal mapping evaluator. v1 supports:\n * - string → path lookup via getByPath\n * - Array<Value> → fallback chain: first non-undefined wins\n * - { value: X } → literal\n * - { key: 'path' } → path lookup\n * - { map: {...} } → recursive object build\n *\n * Deferred to v2: condition, loop, fn, validate, set, consent, $code.\n */\nfunction evalValue(\n value: Mapping.Value | Mapping.Values,\n root: LookupRoot,\n): unknown {\n if (Array.isArray(value)) {\n for (const v of value) {\n const resolved = evalValue(v, root);\n if (resolved !== undefined && resolved !== null) return resolved;\n }\n return undefined;\n }\n if (typeof value === 'string') {\n return getByPath(root, value);\n }\n if (typeof value === 'object' && value !== null) {\n if ('value' in value && value.value !== undefined) return value.value;\n if ('key' in value && typeof value.key === 'string') {\n return getByPath(root, value.key);\n }\n if ('map' in value && value.map) {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value.map)) {\n const resolved = evalValue(v, root);\n if (resolved !== undefined) out[k] = resolved;\n }\n return out;\n }\n }\n return undefined;\n}\n","import { deriveSpanId, getSpanId } from '@walkeros/core';\nimport type { GA4HitParams } from './types';\n\n/**\n * walkerOS event id for the `index`-th event of a GA4 hit.\n *\n * Shaped like every other walkerOS event id (a W3C span id: 16 lowercase hex\n * characters), but derived from the hit instead of drawn at random, so a\n * re-delivered hit (gtag retry, proxy duplicate) decodes to the same ids and\n * dedup on `event.id` catches it. The hit key is the parent, the event's\n * position the child (the same derivation walkerOS uses for forks):\n *\n * - `_p` page load id, shared by every hit of one page load\n * - `_s` hit sequence counter within that page load\n * - index position of the event inside the hit (batched POST)\n * - `tid` and `cid` separate two properties on one page and page load\n * ids that happen to collide across devices\n *\n * Without `_p` or `_s` there is no stable per-hit key, so the id falls back\n * to a random span id: dedup then never drops such an event by mistake.\n */\nexport function getEventId(hit: GA4HitParams, index: number): string {\n const { _p: page, _s: seq } = hit;\n if (!page || !seq) return getSpanId();\n\n return deriveSpanId(\n JSON.stringify(['ga4', hit.tid ?? '', hit.cid ?? '', page, seq]),\n index,\n );\n}\n","import type { GA4Mapping } from './types';\n\n/**\n * Default GA4 event-name → walkerOS mapping rules.\n * Users may override or extend these via settings.mapping at config time.\n * The `'*'` key is the fallback for unknown event names.\n *\n * Field naming for `data.map`:\n * - Monetary: `currency` from `ep.currency`, `value`/`total` from `epn.value`,\n * `tax` from `epn.tax`, `shipping` from `epn.shipping`, `coupon` from\n * `ep.coupon`.\n * - Promotions read from `firstItem` (synthetic `items[0]` reference exposed by\n * the mapper), since GA4 encodes promotion info inside item tilde strings.\n */\nexport const defaultMapping: GA4Mapping = {\n // --- page ---\n page_view: {\n name: 'page view',\n data: {\n map: {\n id: 'hit.dl',\n title: 'hit.dt',\n referrer: 'hit.dr',\n },\n },\n },\n\n // --- ecommerce ---\n purchase: {\n name: 'order complete',\n data: {\n map: {\n id: 'params.ep.transaction_id',\n currency: 'params.ep.currency',\n total: 'params.epn.value',\n tax: 'params.epn.tax',\n shipping: 'params.epn.shipping',\n coupon: 'params.ep.coupon',\n },\n },\n },\n view_item: {\n name: 'product view',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n add_to_cart: {\n name: 'product add',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n remove_from_cart: {\n name: 'product remove',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n view_cart: {\n name: 'cart view',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n begin_checkout: {\n name: 'order start',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n coupon: 'params.ep.coupon',\n },\n },\n },\n add_shipping_info: {\n name: 'order shipping',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n tier: 'params.ep.shipping_tier',\n },\n },\n },\n add_payment_info: {\n name: 'order payment',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n type: 'params.ep.payment_type',\n },\n },\n },\n refund: {\n name: 'order refund',\n data: {\n map: {\n id: 'params.ep.transaction_id',\n currency: 'params.ep.currency',\n total: 'params.epn.value',\n },\n },\n },\n add_to_wishlist: {\n name: 'wishlist add',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n\n // --- list / promotion ---\n view_item_list: {\n name: 'list view',\n data: {\n map: {\n id: 'params.ep.item_list_id',\n name: 'params.ep.item_list_name',\n },\n },\n },\n select_item: {\n name: 'product click',\n data: {\n map: {\n list_id: 'params.ep.item_list_id',\n list_name: 'params.ep.item_list_name',\n },\n },\n },\n view_promotion: {\n name: 'promotion view',\n data: {\n map: {\n id: 'firstItem.promotionId',\n name: 'firstItem.promotionName',\n creative: 'firstItem.creativeName',\n slot: 'firstItem.creativeSlot',\n },\n },\n },\n select_promotion: {\n name: 'promotion click',\n data: {\n map: {\n id: 'firstItem.promotionId',\n name: 'firstItem.promotionName',\n creative: 'firstItem.creativeName',\n slot: 'firstItem.creativeSlot',\n },\n },\n },\n select_content: {\n name: 'content select',\n data: {\n map: {\n type: 'params.ep.content_type',\n id: 'params.ep.content_id',\n },\n },\n },\n\n // --- engagement ---\n scroll: {\n name: 'page scroll',\n data: {\n map: {\n percent: 'params.epn.percent_scrolled',\n },\n },\n },\n click: {\n name: 'link click',\n data: {\n map: {\n url: 'params.ep.link_url',\n domain: 'params.ep.link_domain',\n outbound: 'params.ep.outbound',\n },\n },\n },\n file_download: {\n name: 'file download',\n data: {\n map: {\n name: 'params.ep.file_name',\n extension: 'params.ep.file_extension',\n url: 'params.ep.link_url',\n },\n },\n },\n video_start: {\n name: 'video start',\n data: {\n map: {\n title: 'params.ep.video_title',\n duration: 'params.epn.video_duration',\n current: 'params.epn.video_current_time',\n percent: 'params.epn.video_percent',\n },\n },\n },\n video_progress: {\n name: 'video progress',\n data: {\n map: {\n title: 'params.ep.video_title',\n duration: 'params.epn.video_duration',\n current: 'params.epn.video_current_time',\n percent: 'params.epn.video_percent',\n },\n },\n },\n video_complete: {\n name: 'video complete',\n data: {\n map: {\n title: 'params.ep.video_title',\n duration: 'params.epn.video_duration',\n current: 'params.epn.video_current_time',\n percent: 'params.epn.video_percent',\n },\n },\n },\n form_start: {\n name: 'form start',\n data: {\n map: {\n id: 'params.ep.form_id',\n name: 'params.ep.form_name',\n destination: 'params.ep.form_destination',\n },\n },\n },\n form_submit: {\n name: 'form submit',\n data: {\n map: {\n id: 'params.ep.form_id',\n name: 'params.ep.form_name',\n destination: 'params.ep.form_destination',\n },\n },\n },\n search: {\n name: 'search submit',\n data: {\n map: {\n term: 'params.ep.search_term',\n },\n },\n },\n login: {\n name: 'session login',\n data: {\n map: {\n method: 'params.ep.method',\n },\n },\n },\n sign_up: {\n name: 'session signup',\n data: {\n map: {\n method: 'params.ep.method',\n },\n },\n },\n generate_lead: {\n name: 'lead generate',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n share: {\n name: 'content share',\n data: {\n map: {\n method: 'params.ep.method',\n type: 'params.ep.content_type',\n id: 'params.ep.item_id',\n },\n },\n },\n\n // --- auto-fired noise: ignore by default (users can override) ---\n user_engagement: { ignore: true },\n session_start: { ignore: true },\n first_visit: { ignore: true },\n\n // --- fallback for unknown event names ---\n '*': {\n name: 'ga4 track',\n data: {\n map: {\n event_name: 'en',\n },\n },\n },\n};\n","import { mergeMappingRule } from '@walkeros/core';\nimport type { GA4Mapping } from './types';\n\n/**\n * Merge user mapping overrides onto the shipped defaults, per GA4 event key.\n * A user entry with `extend`/`remove` patches the default; otherwise it\n * replaces it. `GA4Mapping` values are single rules (no Rule[]), matching\n * the evaluator. `extend` only changes anything for keys that ship a default.\n */\nexport function mergeGa4Mapping(\n defaults: GA4Mapping,\n user: GA4Mapping,\n): GA4Mapping {\n const merged: GA4Mapping = { ...defaults };\n for (const key of Object.keys(user)) {\n const userRule = user[key];\n if (!userRule) continue;\n const baseRule = defaults[key];\n merged[key] = baseRule\n ? mergeMappingRule(baseRule, userRule)\n : mergeMappingRule({}, userRule);\n }\n return merged;\n}\n","import type { Ingest, Transformer } from '@walkeros/core';\nimport { parseRequest, splitBodyLines } from './parse';\nimport { mapHitToEvents } from './map';\nimport { defaultMapping } from './defaults';\nimport { mergeGa4Mapping } from './merge';\nimport type { GA4Request, GA4Settings } from './types';\n\n/**\n * Read the raw GA4 HTTP request from `ctx.ingest`.\n *\n * Source wiring (e.g. an express source) is expected to populate\n * `ingest.url` (full request URL, query string included) and optionally\n * `ingest.body` (raw POST body as a string). `Ingest` is an open\n * `Record<string, unknown>` per `@walkeros/core`, so each field is\n * narrowed with a `typeof` guard instead of a cast.\n *\n * Returns `null` if `url` is missing or not a string — the caller drops\n * the event in that case.\n */\nfunction readGA4Request(ingest: Ingest): GA4Request | null {\n const url = ingest.url;\n if (typeof url !== 'string') return null;\n const body = ingest.body;\n return {\n url,\n body: typeof body === 'string' ? body : undefined,\n };\n}\n\nexport const transformerGa4: Transformer.Init<\n Transformer.Types<GA4Settings>\n> = (context) => {\n const { config } = context;\n const settings = config.settings ?? {};\n const tidPattern = new RegExp(settings.tidPattern ?? '^G-');\n const maxEvents = settings.maxEvents ?? 100;\n const mapping = mergeGa4Mapping(defaultMapping, settings.mapping ?? {});\n\n return {\n type: 'ga4',\n config,\n async push(_event, ctx) {\n const { logger } = ctx;\n\n const raw = readGA4Request(ctx.ingest);\n if (!raw) {\n logger.debug('transformer-ga4: no request in ingest; skipping');\n return false;\n }\n\n // Bound the fan-out before decoding: every event becomes a concurrent\n // push, and the source's batch cap does not apply to a raw text body.\n if (raw.body) {\n const lineCount = splitBodyLines(raw.body).length;\n if (lineCount > maxEvents) {\n logger.warn(\n `transformer-ga4: ${lineCount} events exceed maxEvents ${maxEvents}; request dropped`,\n );\n return false;\n }\n }\n\n try {\n const hit = parseRequest(raw);\n\n if (hit.hit.tid && !tidPattern.test(hit.hit.tid)) {\n logger.debug(\n `transformer-ga4: tid ${hit.hit.tid} dropped by tidPattern`,\n );\n return false;\n }\n\n const events = mapHitToEvents(hit, mapping);\n if (events.length === 0) {\n logger.debug(\n `transformer-ga4: no events mapped (tid=${hit.hit.tid ?? 'unknown'})`,\n );\n return false;\n }\n\n logger.debug(\n `transformer-ga4: decoded ${events.length} events from ${hit.hit.tid ?? 'unknown'}`,\n );\n\n if (events.length === 1) return { event: events[0] };\n return events.map((event) => ({ event }));\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.error(`transformer-ga4: parse error: ${message}`);\n return false;\n }\n },\n };\n};\n"],"mappings":"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","names":["val"]}
|
|
1
|
+
{"version":3,"sources":["../src/transformer.ts","../src/parse.ts","../src/map.ts","../src/id.ts","../src/defaults.ts","../src/merge.ts"],"sourcesContent":["import type { Ingest, Transformer } from '@walkeros/core';\nimport { StepError, reportCause, stepId } from '@walkeros/core';\nimport { parseRequest, splitBodyLines } from './parse';\nimport { mapHitToEvents } from './map';\nimport { defaultMapping } from './defaults';\nimport { mergeGa4Mapping } from './merge';\nimport type { GA4Request, GA4Settings } from './types';\n\n/**\n * Read the raw GA4 HTTP request from `ctx.ingest`.\n *\n * Source wiring (e.g. an express source) is expected to populate\n * `ingest.url` (full request URL, query string included) and optionally\n * `ingest.body` (raw POST body as a string). `Ingest` is an open\n * `Record<string, unknown>` per `@walkeros/core`, so each field is\n * narrowed with a `typeof` guard instead of a cast.\n *\n * Returns `null` if `url` is missing or not a string: the caller fails the\n * event with `missing-input`.\n */\nfunction readGA4Request(ingest: Ingest): GA4Request | null {\n const url = ingest.url;\n if (typeof url !== 'string') return null;\n const body = ingest.body;\n return {\n url,\n body: typeof body === 'string' ? body : undefined,\n };\n}\n\nexport const transformerGa4: Transformer.Init<\n Transformer.Types<GA4Settings>\n> = (context) => {\n const { config } = context;\n const settings = config.settings ?? {};\n const tidPattern = new RegExp(settings.tidPattern ?? '^G-');\n const maxEvents = settings.maxEvents ?? 100;\n const mapping = mergeGa4Mapping(defaultMapping, settings.mapping ?? {});\n\n return {\n type: 'ga4',\n config,\n async push(_event, ctx) {\n const { logger, collector, id } = ctx;\n\n // Without a request there is nothing to decode: a wiring mistake (the\n // source's config.ingest) that would drop all traffic, so it fails.\n const raw = readGA4Request(ctx.ingest);\n if (!raw)\n throw new StepError(\n 'transformer-ga4: no GA4 request in ingest (ingest.url missing)',\n { code: 'missing-input' },\n );\n\n // Bound the fan-out before decoding: every event becomes a concurrent\n // push, and the source's batch cap does not apply to a raw text body.\n // The dropped request is a loss, so it is reported (ERROR, once per\n // cause and minute).\n if (raw.body) {\n const lineCount = splitBodyLines(raw.body).length;\n if (lineCount > maxEvents) {\n reportCause(\n collector,\n logger,\n { step: stepId('transformer', id), code: 'invalid' },\n `transformer-ga4: ${lineCount} events exceed maxEvents ${maxEvents}; request dropped`,\n { events: lineCount, maxEvents },\n );\n return false;\n }\n }\n\n try {\n const hit = parseRequest(raw);\n\n if (hit.hit.tid && !tidPattern.test(hit.hit.tid)) {\n logger.debug(\n `transformer-ga4: tid ${hit.hit.tid} dropped by tidPattern`,\n );\n return false;\n }\n\n const events = mapHitToEvents(hit, mapping);\n if (events.length === 0) {\n logger.debug(\n `transformer-ga4: no events mapped (tid=${hit.hit.tid ?? 'unknown'})`,\n );\n return false;\n }\n\n logger.debug(\n `transformer-ga4: decoded ${events.length} events from ${hit.hit.tid ?? 'unknown'}`,\n );\n\n if (events.length === 1) return { event: events[0] };\n return events.map((event) => ({ event }));\n } catch (err) {\n // The collector reports it; the message names no request content.\n throw new StepError(\n 'transformer-ga4: the GA4 request could not be decoded',\n {\n code: 'transformer',\n cause: err,\n },\n );\n }\n },\n };\n};\n","import type { WalkerOS } from '@walkeros/core';\nimport type {\n GA4Event,\n GA4EventParams,\n GA4Hit,\n GA4HitParams,\n GA4Item,\n GA4Request,\n} from './types';\n\nexport function parseQuery(url: string): Record<string, string> {\n const out: Record<string, string> = {};\n const qIdx = url.indexOf('?');\n if (qIdx === -1) return out;\n const usp = new URLSearchParams(url.slice(qIdx + 1));\n usp.forEach((value, key) => {\n out[key] = value;\n });\n return out;\n}\n\n/** Non-empty, trimmed lines of a POST body: one GA4 event per line. */\nexport function splitBodyLines(body: string): string[] {\n if (!body) return [];\n return body\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n}\n\nexport function parseBody(body: string): Record<string, string>[] {\n return splitBodyLines(body).map((line) => {\n const out: Record<string, string> = {};\n const usp = new URLSearchParams(line);\n usp.forEach((v, k) => {\n out[k] = v;\n });\n return out;\n });\n}\n\n// Two separate dispatch maps keyed by 2-char prefix.\n// Split by type so the assignment site stays type-safe (no cast).\nconst ITEM_STRING_FIELDS: Readonly<Record<string, keyof GA4Item>> = {\n id: 'id',\n nm: 'name',\n br: 'brand',\n ca: 'category',\n c2: 'category2',\n c3: 'category3',\n c4: 'category4',\n c5: 'category5',\n va: 'variant',\n cp: 'coupon',\n ln: 'listName',\n li: 'listId',\n lo: 'locationId',\n af: 'affiliation',\n cn: 'creativeName',\n cs: 'creativeSlot',\n pi: 'promotionId',\n pn: 'promotionName',\n};\n\nconst ITEM_NUMBER_FIELDS: Readonly<Record<string, keyof GA4Item>> = {\n pr: 'price',\n qt: 'quantity',\n ds: 'discount',\n lp: 'listPosition',\n};\n\nfunction setItemString(item: GA4Item, key: keyof GA4Item, value: string): void {\n // Narrow assignment by key — these are the string-typed fields.\n switch (key) {\n case 'id':\n item.id = value;\n return;\n case 'name':\n item.name = value;\n return;\n case 'brand':\n item.brand = value;\n return;\n case 'category':\n item.category = value;\n return;\n case 'category2':\n item.category2 = value;\n return;\n case 'category3':\n item.category3 = value;\n return;\n case 'category4':\n item.category4 = value;\n return;\n case 'category5':\n item.category5 = value;\n return;\n case 'variant':\n item.variant = value;\n return;\n case 'coupon':\n item.coupon = value;\n return;\n case 'listName':\n item.listName = value;\n return;\n case 'listId':\n item.listId = value;\n return;\n case 'locationId':\n item.locationId = value;\n return;\n case 'affiliation':\n item.affiliation = value;\n return;\n case 'creativeName':\n item.creativeName = value;\n return;\n case 'creativeSlot':\n item.creativeSlot = value;\n return;\n case 'promotionId':\n item.promotionId = value;\n return;\n case 'promotionName':\n item.promotionName = value;\n return;\n }\n}\n\nfunction setItemNumber(item: GA4Item, key: keyof GA4Item, value: number): void {\n switch (key) {\n case 'price':\n item.price = value;\n return;\n case 'quantity':\n item.quantity = value;\n return;\n case 'discount':\n item.discount = value;\n return;\n case 'listPosition':\n item.listPosition = value;\n return;\n }\n}\n\nexport function parseItem(value: string): GA4Item {\n const item: GA4Item = { custom: {} };\n const customKeys = new Map<string, string>();\n const customVals = new Map<string, string>();\n\n for (const raw of value.split('~')) {\n if (raw.length === 0) continue;\n\n // Variable-length k<N>/v<N> (must match before 2-char prefix lookup\n // because k10/v10 etc are wider than 2 chars).\n const kvMatch = /^([kv])(\\d+)(.*)$/.exec(raw);\n if (kvMatch) {\n const [, kind, idx, val] = kvMatch;\n if (kind === 'k') customKeys.set(idx, val);\n else customVals.set(idx, val);\n continue;\n }\n\n if (raw.length < 2) continue;\n const prefix = raw.slice(0, 2);\n const val = raw.slice(2);\n\n const stringKey = ITEM_STRING_FIELDS[prefix];\n if (stringKey !== undefined) {\n setItemString(item, stringKey, val);\n continue;\n }\n const numberKey = ITEM_NUMBER_FIELDS[prefix];\n if (numberKey !== undefined) {\n const n = Number(val);\n if (!Number.isNaN(n)) setItemNumber(item, numberKey, n);\n continue;\n }\n }\n\n // Pair k<N> with v<N>; drop unmatched.\n for (const [idx, key] of customKeys) {\n const v = customVals.get(idx);\n if (v !== undefined) item.custom[key] = v;\n }\n return item;\n}\n\nexport function parseConsent(hit: { gcs?: string }): WalkerOS.Consent {\n const gcs = hit.gcs;\n if (!gcs || !/^G[01]{3}$/.test(gcs)) return {};\n // gcs[1] is reserved (always '1' for current consent mode v2)\n return {\n marketing: gcs[2] === '1', // ad_storage\n analytics: gcs[3] === '1', // analytics_storage\n };\n}\n\nconst HIT_PARAM_KEYS = new Set([\n 'v',\n 'tid',\n 'cid',\n 'sid',\n 'sct',\n 'seg',\n '_p',\n '_z',\n 'gtm',\n 'dl',\n 'dt',\n 'dr',\n 'ul',\n 'sr',\n 'uid',\n '_dbg',\n '_s',\n '_ss',\n '_fv',\n 'gcs',\n 'gcd',\n 'dma',\n 'dma_cps',\n 'npa',\n 'p',\n]);\n\nexport function parseRequest(req: GA4Request): GA4Hit {\n const queryParams = parseQuery(req.url);\n const hit: GA4HitParams = {};\n const queryEventParams: Record<string, string> = {};\n\n // Split URL params into hit-level vs event-level.\n for (const [k, v] of Object.entries(queryParams)) {\n if (HIT_PARAM_KEYS.has(k)) hit[k] = v;\n else queryEventParams[k] = v;\n }\n\n // GET: event params live in the URL alongside hit params.\n // POST: in body lines.\n const bodyLines = req.body ? parseBody(req.body) : [];\n const rawEvents =\n bodyLines.length > 0\n ? bodyLines\n : Object.keys(queryEventParams).length > 0\n ? [queryEventParams]\n : [];\n\n const events: GA4Event[] = rawEvents\n .filter((raw) => typeof raw.en === 'string' && raw.en.length > 0)\n .map((raw) => buildEvent(raw));\n\n return { hit, events };\n}\n\nfunction buildEvent(raw: Record<string, string>): GA4Event {\n const params: GA4EventParams = { ep: {}, epn: {}, up: {}, upn: {} };\n const itemEntries: Array<[number, string]> = [];\n\n for (const [k, v] of Object.entries(raw)) {\n if (k === 'en') continue;\n\n // pr<N> items\n const prMatch = /^pr(\\d+)$/.exec(k);\n if (prMatch) {\n itemEntries.push([Number(prMatch[1]), v]);\n continue;\n }\n\n // Dotted-key nesting\n if (k.startsWith('ep.')) {\n params.ep[k.slice(3)] = v;\n continue;\n }\n if (k.startsWith('epn.')) {\n const n = Number(v);\n if (!Number.isNaN(n)) params.epn[k.slice(4)] = n;\n continue;\n }\n if (k.startsWith('up.')) {\n params.up[k.slice(3)] = v;\n continue;\n }\n if (k.startsWith('upn.')) {\n const n = Number(v);\n if (!Number.isNaN(n)) params.upn[k.slice(4)] = n;\n continue;\n }\n\n // Standalone numeric keys\n if (k === '_et') {\n const n = Number(v);\n if (!Number.isNaN(n)) params._et = n;\n continue;\n }\n if (k === '_c') {\n params._c = v;\n continue;\n }\n // Unknown standalone keys: drop. Add to GA4EventParams type if a future\n // need surfaces.\n }\n\n itemEntries.sort((a, b) => a[0] - b[0]);\n const items = itemEntries.map(([, v]) => parseItem(v));\n\n return { en: raw.en, params, items };\n}\n","import type { Mapping, WalkerOS } from '@walkeros/core';\nimport { getByPath, deleteByPath, isObject } from '@walkeros/core';\nimport type {\n GA4Event,\n GA4Hit,\n GA4HitParams,\n GA4Item,\n GA4Mapping,\n} from './types';\nimport { parseConsent } from './parse';\nimport { getEventId } from './id';\n\n/**\n * Lookup root for mapping path resolution. Mapping rules reference keys via\n * dotted paths: `hit.dl`, `params.ep.transaction_id`, `params.epn.value`, `items`.\n *\n * `en` exposes the GA4 event name (used by the `*` fallback so unknown events\n * can carry their original name through). `firstItem` exposes `items[0]` so\n * promotion-style mappings can resolve `firstItem.promotionId` etc. without\n * needing array-index syntax in `getByPath`.\n *\n * Typed as `Record<string, unknown> & { ... }` so `getByPath` (which accepts\n * unknown) is happy without any narrowing cast on the call site.\n */\ntype LookupRoot = Record<string, unknown> & {\n hit: GA4HitParams;\n params: GA4Event['params'];\n items: GA4Event['items'];\n en: string;\n firstItem?: GA4Item;\n};\n\n/**\n * Convert a parsed GA4 hit into zero or more walkerOS events.\n * One GA4 hit may carry several events (POST batch); each is mapped\n * independently with hit-level metadata merged in.\n */\nexport function mapHitToEvents(\n hit: GA4Hit,\n mapping: GA4Mapping,\n): WalkerOS.DeepPartialEvent[] {\n const out: WalkerOS.DeepPartialEvent[] = [];\n hit.events.forEach((event, index) => {\n const mapped = mapOneEvent(hit.hit, event, index, mapping);\n if (mapped) out.push(mapped);\n });\n return out;\n}\n\nfunction mapOneEvent(\n hitParams: GA4HitParams,\n event: GA4Event,\n index: number,\n mapping: GA4Mapping,\n): WalkerOS.DeepPartialEvent | null {\n const ruleEntry = mapping[event.en] ?? mapping['*'];\n if (!ruleEntry) return null;\n // For v1, only single-rule entries are supported (no array fan-out).\n const rule: Mapping.Rule = Array.isArray(ruleEntry)\n ? ruleEntry[0]\n : ruleEntry;\n if (!rule || rule.ignore) return null;\n\n const lookup: LookupRoot = {\n hit: hitParams,\n params: event.params,\n items: event.items,\n en: event.en,\n ...(event.items.length > 0 ? { firstItem: event.items[0] } : {}),\n };\n\n const evaluated: WalkerOS.DeepPartialEvent = {};\n\n if (rule.name) {\n const [entity, ...rest] = rule.name.split(' ');\n evaluated.name = rule.name;\n evaluated.entity = entity;\n evaluated.action = rest.join(' ');\n }\n\n if (rule.data !== undefined) {\n let data = evalValue(rule.data, lookup);\n if (rule.remove && isObject(data)) {\n for (const path of rule.remove) data = deleteByPath(data, path);\n }\n if (data !== undefined) {\n evaluated.data = data as WalkerOS.Properties;\n }\n }\n\n // Hit-level merge: user / source. Browser language (ul) and screen\n // resolution (sr) describe the visitor, so they go to user; globals stays\n // free for site-level facts such as the site's own language.\n evaluated.user = {\n ...(hitParams.uid !== undefined ? { id: hitParams.uid } : {}),\n ...(hitParams.cid !== undefined ? { device: hitParams.cid } : {}),\n ...(hitParams.sid !== undefined ? { session: hitParams.sid } : {}),\n ...(hitParams.ul !== undefined ? { language: hitParams.ul } : {}),\n ...(hitParams.sr !== undefined ? { screenSize: hitParams.sr } : {}),\n };\n // source.type marks the GA4 decoder; url and referrer carry the page\n // context (dl, dr) as sent; the raw page load id (_p) and hit sequence (_s)\n // stay available for analysts next to the derived event id.\n evaluated.source = {\n type: 'ga4',\n ...(hitParams.dl !== undefined ? { url: hitParams.dl } : {}),\n ...(hitParams.dr !== undefined ? { referrer: hitParams.dr } : {}),\n ...(hitParams.p !== undefined ? { platform: hitParams.p } : {}),\n ...(hitParams._p !== undefined ? { pageLoadId: hitParams._p } : {}),\n ...(hitParams._s !== undefined ? { hitSequence: hitParams._s } : {}),\n };\n\n // Consent from gcs string, id derived from the hit (see getEventId),\n // timestamp is the receive time (sid is the session start, not the event\n // time), timing from event._et (ms; 0 if absent), trigger hard-coded to\n // \"ga4\" for v1.\n evaluated.consent = parseConsent(hitParams);\n evaluated.id = getEventId(hitParams, index);\n evaluated.timestamp = Date.now();\n evaluated.timing = event.params._et ?? 0;\n evaluated.trigger = 'ga4';\n\n if (event.items.length > 0) {\n evaluated.nested = itemsToNested(event.items);\n }\n\n return evaluated;\n}\n\n/**\n * GA4 items → walkerOS nested product entities.\n * Field naming: camelCase GA4Item keys are emitted as snake_case data keys\n * (`listName` → `list_name`, `promotionId` → `promotion_id`, etc).\n * Custom k<N>/v<N> pairs are spread as-is.\n *\n * Returns `WalkerOS.Entities` (i.e. `Array<WalkerOS.Entity>`). Note that the\n * canonical `WalkerOS.Entity` interface uses `entity: string` as the kind\n * discriminator (not `type`).\n */\nfunction itemsToNested(items: GA4Item[]): WalkerOS.Entities {\n return items.map((item): WalkerOS.Entity => {\n const data: WalkerOS.Properties = {};\n if (item.id !== undefined) data.id = item.id;\n if (item.name !== undefined) data.name = item.name;\n if (item.brand !== undefined) data.brand = item.brand;\n if (item.category !== undefined) data.category = item.category;\n if (item.category2 !== undefined) data.category2 = item.category2;\n if (item.category3 !== undefined) data.category3 = item.category3;\n if (item.category4 !== undefined) data.category4 = item.category4;\n if (item.category5 !== undefined) data.category5 = item.category5;\n if (item.variant !== undefined) data.variant = item.variant;\n if (item.price !== undefined) data.price = item.price;\n if (item.quantity !== undefined) data.quantity = item.quantity;\n if (item.coupon !== undefined) data.coupon = item.coupon;\n if (item.discount !== undefined) data.discount = item.discount;\n if (item.listName !== undefined) data.list_name = item.listName;\n if (item.listId !== undefined) data.list_id = item.listId;\n if (item.listPosition !== undefined) data.list_position = item.listPosition;\n if (item.locationId !== undefined) data.location_id = item.locationId;\n if (item.affiliation !== undefined) data.affiliation = item.affiliation;\n if (item.creativeName !== undefined) data.creative_name = item.creativeName;\n if (item.creativeSlot !== undefined) data.creative_slot = item.creativeSlot;\n if (item.promotionId !== undefined) data.promotion_id = item.promotionId;\n if (item.promotionName !== undefined)\n data.promotion_name = item.promotionName;\n for (const [k, v] of Object.entries(item.custom)) data[k] = v;\n return { entity: 'product', data, nested: [], context: {} };\n });\n}\n\n/**\n * Minimal mapping evaluator. v1 supports:\n * - string → path lookup via getByPath\n * - Array<Value> → fallback chain: first non-undefined wins\n * - { value: X } → literal\n * - { key: 'path' } → path lookup\n * - { map: {...} } → recursive object build\n *\n * Deferred to v2: condition, loop, fn, validate, set, consent, $code.\n */\nfunction evalValue(\n value: Mapping.Value | Mapping.Values,\n root: LookupRoot,\n): unknown {\n if (Array.isArray(value)) {\n for (const v of value) {\n const resolved = evalValue(v, root);\n if (resolved !== undefined && resolved !== null) return resolved;\n }\n return undefined;\n }\n if (typeof value === 'string') {\n return getByPath(root, value);\n }\n if (typeof value === 'object' && value !== null) {\n if ('value' in value && value.value !== undefined) return value.value;\n if ('key' in value && typeof value.key === 'string') {\n return getByPath(root, value.key);\n }\n if ('map' in value && value.map) {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value.map)) {\n const resolved = evalValue(v, root);\n if (resolved !== undefined) out[k] = resolved;\n }\n return out;\n }\n }\n return undefined;\n}\n","import { deriveSpanId, getSpanId } from '@walkeros/core';\nimport type { GA4HitParams } from './types';\n\n/**\n * walkerOS event id for the `index`-th event of a GA4 hit.\n *\n * Shaped like every other walkerOS event id (a W3C span id: 16 lowercase hex\n * characters), but derived from the hit instead of drawn at random, so a\n * re-delivered hit (gtag retry, proxy duplicate) decodes to the same ids and\n * dedup on `event.id` catches it. The hit key is the parent, the event's\n * position the child (the same derivation walkerOS uses for forks):\n *\n * - `_p` page load id, shared by every hit of one page load\n * - `_s` hit sequence counter within that page load\n * - index position of the event inside the hit (batched POST)\n * - `tid` and `cid` separate two properties on one page and page load\n * ids that happen to collide across devices\n *\n * Without `_p` or `_s` there is no stable per-hit key, so the id falls back\n * to a random span id: dedup then never drops such an event by mistake.\n */\nexport function getEventId(hit: GA4HitParams, index: number): string {\n const { _p: page, _s: seq } = hit;\n if (!page || !seq) return getSpanId();\n\n return deriveSpanId(\n JSON.stringify(['ga4', hit.tid ?? '', hit.cid ?? '', page, seq]),\n index,\n );\n}\n","import type { GA4Mapping } from './types';\n\n/**\n * Default GA4 event-name → walkerOS mapping rules.\n * Users may override or extend these via settings.mapping at config time.\n * The `'*'` key is the fallback for unknown event names.\n *\n * Field naming for `data.map`:\n * - Monetary: `currency` from `ep.currency`, `value` from `epn.value`,\n * `tax` from `epn.tax`, `shipping` from `epn.shipping`, `coupon` from\n * `ep.coupon`.\n * - Promotions read from `firstItem` (synthetic `items[0]` reference exposed by\n * the mapper), since GA4 encodes promotion info inside item tilde strings.\n */\nexport const defaultMapping: GA4Mapping = {\n // --- page ---\n page_view: {\n name: 'page view',\n data: {\n map: {\n id: 'hit.dl',\n title: 'hit.dt',\n referrer: 'hit.dr',\n },\n },\n },\n\n // --- ecommerce ---\n purchase: {\n name: 'order complete',\n data: {\n map: {\n id: 'params.ep.transaction_id',\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n tax: 'params.epn.tax',\n shipping: 'params.epn.shipping',\n coupon: 'params.ep.coupon',\n },\n },\n },\n view_item: {\n name: 'product view',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n add_to_cart: {\n name: 'product add',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n remove_from_cart: {\n name: 'product remove',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n view_cart: {\n name: 'cart view',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n begin_checkout: {\n name: 'order start',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n coupon: 'params.ep.coupon',\n },\n },\n },\n add_shipping_info: {\n name: 'order shipping',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n tier: 'params.ep.shipping_tier',\n },\n },\n },\n add_payment_info: {\n name: 'order payment',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n type: 'params.ep.payment_type',\n },\n },\n },\n refund: {\n name: 'order refund',\n data: {\n map: {\n id: 'params.ep.transaction_id',\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n add_to_wishlist: {\n name: 'wishlist add',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n\n // --- list / promotion ---\n view_item_list: {\n name: 'list view',\n data: {\n map: {\n id: 'params.ep.item_list_id',\n name: 'params.ep.item_list_name',\n },\n },\n },\n select_item: {\n name: 'product click',\n data: {\n map: {\n list_id: 'params.ep.item_list_id',\n list_name: 'params.ep.item_list_name',\n },\n },\n },\n view_promotion: {\n name: 'promotion view',\n data: {\n map: {\n id: 'firstItem.promotionId',\n name: 'firstItem.promotionName',\n creative: 'firstItem.creativeName',\n slot: 'firstItem.creativeSlot',\n },\n },\n },\n select_promotion: {\n name: 'promotion click',\n data: {\n map: {\n id: 'firstItem.promotionId',\n name: 'firstItem.promotionName',\n creative: 'firstItem.creativeName',\n slot: 'firstItem.creativeSlot',\n },\n },\n },\n select_content: {\n name: 'content select',\n data: {\n map: {\n type: 'params.ep.content_type',\n id: 'params.ep.content_id',\n },\n },\n },\n\n // --- engagement ---\n scroll: {\n name: 'page scroll',\n data: {\n map: {\n percent: 'params.epn.percent_scrolled',\n },\n },\n },\n click: {\n name: 'link click',\n data: {\n map: {\n url: 'params.ep.link_url',\n domain: 'params.ep.link_domain',\n outbound: 'params.ep.outbound',\n },\n },\n },\n file_download: {\n name: 'file download',\n data: {\n map: {\n name: 'params.ep.file_name',\n extension: 'params.ep.file_extension',\n url: 'params.ep.link_url',\n },\n },\n },\n video_start: {\n name: 'video start',\n data: {\n map: {\n title: 'params.ep.video_title',\n duration: 'params.epn.video_duration',\n current: 'params.epn.video_current_time',\n percent: 'params.epn.video_percent',\n },\n },\n },\n video_progress: {\n name: 'video progress',\n data: {\n map: {\n title: 'params.ep.video_title',\n duration: 'params.epn.video_duration',\n current: 'params.epn.video_current_time',\n percent: 'params.epn.video_percent',\n },\n },\n },\n video_complete: {\n name: 'video complete',\n data: {\n map: {\n title: 'params.ep.video_title',\n duration: 'params.epn.video_duration',\n current: 'params.epn.video_current_time',\n percent: 'params.epn.video_percent',\n },\n },\n },\n form_start: {\n name: 'form start',\n data: {\n map: {\n id: 'params.ep.form_id',\n name: 'params.ep.form_name',\n destination: 'params.ep.form_destination',\n },\n },\n },\n form_submit: {\n name: 'form submit',\n data: {\n map: {\n id: 'params.ep.form_id',\n name: 'params.ep.form_name',\n destination: 'params.ep.form_destination',\n },\n },\n },\n search: {\n name: 'search submit',\n data: {\n map: {\n term: 'params.ep.search_term',\n },\n },\n },\n login: {\n name: 'session login',\n data: {\n map: {\n method: 'params.ep.method',\n },\n },\n },\n sign_up: {\n name: 'session signup',\n data: {\n map: {\n method: 'params.ep.method',\n },\n },\n },\n generate_lead: {\n name: 'lead generate',\n data: {\n map: {\n currency: 'params.ep.currency',\n value: 'params.epn.value',\n },\n },\n },\n share: {\n name: 'content share',\n data: {\n map: {\n method: 'params.ep.method',\n type: 'params.ep.content_type',\n id: 'params.ep.item_id',\n },\n },\n },\n\n // --- auto-fired noise: ignore by default (users can override) ---\n user_engagement: { ignore: true },\n session_start: { ignore: true },\n first_visit: { ignore: true },\n\n // --- fallback for unknown event names ---\n '*': {\n name: 'ga4 track',\n data: {\n map: {\n event_name: 'en',\n },\n },\n },\n};\n","import { mergeMappingRule } from '@walkeros/core';\nimport type { GA4Mapping } from './types';\n\n/**\n * Merge user mapping overrides onto the shipped defaults, per GA4 event key.\n * A user entry with `extend`/`remove` patches the default; otherwise it\n * replaces it. `GA4Mapping` values are single rules (no Rule[]), matching\n * the evaluator. `extend` only changes anything for keys that ship a default.\n */\nexport function mergeGa4Mapping(\n defaults: GA4Mapping,\n user: GA4Mapping,\n): GA4Mapping {\n const merged: GA4Mapping = { ...defaults };\n for (const key of Object.keys(user)) {\n const userRule = user[key];\n if (!userRule) continue;\n const baseRule = defaults[key];\n merged[key] = baseRule\n ? mergeMappingRule(baseRule, userRule)\n : mergeMappingRule({}, userRule);\n }\n return merged;\n}\n"],"mappings":"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","names":["val"]}
|
package/dist/walkerOS.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$meta": {
|
|
3
3
|
"package": "@walkeros/transformer-ga4",
|
|
4
|
-
"version": "4.8.
|
|
4
|
+
"version": "4.8.2",
|
|
5
5
|
"type": "transformer",
|
|
6
6
|
"platform": [
|
|
7
7
|
"server",
|
|
@@ -910,6 +910,23 @@
|
|
|
910
910
|
]
|
|
911
911
|
]
|
|
912
912
|
},
|
|
913
|
+
"noRequest": {
|
|
914
|
+
"public": false,
|
|
915
|
+
"description": "An event without a GA4 request in its ingest (the source sets no ingest.url): nothing to decode, so the transformer fails it with missing-input instead of dropping all traffic.",
|
|
916
|
+
"in": {
|
|
917
|
+
"name": "page view",
|
|
918
|
+
"source": {
|
|
919
|
+
"type": "express",
|
|
920
|
+
"platform": "server"
|
|
921
|
+
}
|
|
922
|
+
},
|
|
923
|
+
"out": [],
|
|
924
|
+
"outcome": {
|
|
925
|
+
"status": "failed",
|
|
926
|
+
"code": "missing-input",
|
|
927
|
+
"retryable": false
|
|
928
|
+
}
|
|
929
|
+
},
|
|
913
930
|
"pageView": {
|
|
914
931
|
"title": "Page view",
|
|
915
932
|
"description": "A standard GA4 page_view hit decoded to a walkerOS page view with id, title, and referrer.",
|
|
@@ -950,7 +967,7 @@
|
|
|
950
967
|
},
|
|
951
968
|
"purchase": {
|
|
952
969
|
"title": "Purchase (canary)",
|
|
953
|
-
"description": "A GA4 purchase hit decoded to a walkerOS order complete event with id, currency,
|
|
970
|
+
"description": "A GA4 purchase hit decoded to a walkerOS order complete event with id, currency, value, tax, shipping, and coupon.",
|
|
954
971
|
"in": {
|
|
955
972
|
"url": "https://www.google-analytics.com/g/collect?v=2&tid=G-EXAMPLE&_p=p1&_s=2&cid=cid-1&sid=1700000000&en=purchase&ep.transaction_id=T-9001&ep.currency=EUR&epn.value=149.97&epn.tax=23.97&epn.shipping=4.95&ep.coupon=WELCOME10"
|
|
956
973
|
},
|
|
@@ -978,7 +995,7 @@
|
|
|
978
995
|
"data": {
|
|
979
996
|
"id": "T-9001",
|
|
980
997
|
"currency": "EUR",
|
|
981
|
-
"
|
|
998
|
+
"value": 149.97,
|
|
982
999
|
"tax": 23.97,
|
|
983
1000
|
"shipping": 4.95,
|
|
984
1001
|
"coupon": "WELCOME10"
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walkeros/transformer-ga4",
|
|
3
3
|
"description": "Decodes GA4 Measurement Protocol v2 (gtag /g/collect) into walkerOS events",
|
|
4
|
-
"version": "4.8.
|
|
4
|
+
"version": "4.8.2",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.mjs",
|
|
@@ -32,10 +32,11 @@
|
|
|
32
32
|
"test": "jest"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@walkeros/core": "4.8.
|
|
35
|
+
"@walkeros/core": "4.8.2"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@walkeros/
|
|
38
|
+
"@walkeros/collector": "4.8.2",
|
|
39
|
+
"@walkeros/core": "4.8.2"
|
|
39
40
|
},
|
|
40
41
|
"repository": {
|
|
41
42
|
"url": "git+https://github.com/elbwalker/walkerOS.git",
|