@walkeros/server-destination-hubspot 3.4.0-next-1776749829492

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.
@@ -0,0 +1,247 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ // src/examples/env.ts
8
+ var env_exports = {};
9
+ __export(env_exports, {
10
+ push: () => push,
11
+ simulation: () => simulation
12
+ });
13
+ var asyncNoop = () => Promise.resolve();
14
+ function createMockClient() {
15
+ return {
16
+ events: {
17
+ send: {
18
+ basicApi: { send: asyncNoop },
19
+ batchApi: { send: asyncNoop }
20
+ }
21
+ },
22
+ crm: {
23
+ contacts: {
24
+ basicApi: { update: asyncNoop }
25
+ }
26
+ }
27
+ };
28
+ }
29
+ var push = {
30
+ client: createMockClient()
31
+ };
32
+ var simulation = [
33
+ "call:events.send.basicApi.send",
34
+ "call:events.send.batchApi.send",
35
+ "call:crm.contacts.basicApi.update"
36
+ ];
37
+
38
+ // src/examples/step.ts
39
+ var step_exports = {};
40
+ __export(step_exports, {
41
+ defaultEvent: () => defaultEvent,
42
+ defaultProperties: () => defaultProperties,
43
+ destinationIdentify: () => destinationIdentify,
44
+ mappedEventName: () => mappedEventName,
45
+ noIdentity: () => noIdentity,
46
+ objectIdAssociation: () => objectIdAssociation,
47
+ userLoginIdentify: () => userLoginIdentify,
48
+ wildcardIgnored: () => wildcardIgnored
49
+ });
50
+ import { getEvent } from "@walkeros/core";
51
+ var defaultEvent = {
52
+ in: getEvent("product view", {
53
+ timestamp: 1700000100,
54
+ user: { email: "user@example.com" }
55
+ }),
56
+ out: [
57
+ [
58
+ "events.send.basicApi.send",
59
+ {
60
+ eventName: "pe12345678_product_view",
61
+ email: "user@example.com",
62
+ occurredAt: /* @__PURE__ */ new Date(1700000100),
63
+ properties: {}
64
+ }
65
+ ]
66
+ ]
67
+ };
68
+ var mappedEventName = {
69
+ in: getEvent("order complete", {
70
+ timestamp: 1700000101,
71
+ user: { email: "user@example.com" },
72
+ data: { total: 99.5, currency: "EUR", id: "ord-123" }
73
+ }),
74
+ mapping: {
75
+ name: "order complete",
76
+ settings: {
77
+ eventName: "purchase_completed",
78
+ properties: {
79
+ map: {
80
+ revenue: "data.total",
81
+ currency: "data.currency",
82
+ order_id: "data.id"
83
+ }
84
+ }
85
+ }
86
+ },
87
+ out: [
88
+ [
89
+ "events.send.basicApi.send",
90
+ {
91
+ eventName: "pe12345678_purchase_completed",
92
+ email: "user@example.com",
93
+ occurredAt: /* @__PURE__ */ new Date(1700000101),
94
+ properties: {
95
+ revenue: "99.5",
96
+ currency: "EUR",
97
+ order_id: "ord-123"
98
+ }
99
+ }
100
+ ]
101
+ ]
102
+ };
103
+ var defaultProperties = {
104
+ in: getEvent("page view", {
105
+ timestamp: 1700000102,
106
+ user: { email: "user@example.com" }
107
+ }),
108
+ settings: {
109
+ defaultProperties: {
110
+ hs_touchpoint_source: "walkerOS",
111
+ hs_page_content_type: "STANDARD_PAGE"
112
+ }
113
+ },
114
+ out: [
115
+ [
116
+ "events.send.basicApi.send",
117
+ {
118
+ eventName: "pe12345678_page_view",
119
+ email: "user@example.com",
120
+ occurredAt: /* @__PURE__ */ new Date(1700000102),
121
+ properties: {
122
+ hs_touchpoint_source: "walkerOS",
123
+ hs_page_content_type: "STANDARD_PAGE"
124
+ }
125
+ }
126
+ ]
127
+ ]
128
+ };
129
+ var destinationIdentify = {
130
+ in: getEvent("page view", {
131
+ timestamp: 1700000103,
132
+ user: { email: "user@example.com", firstName: "Jane", lastName: "Doe" }
133
+ }),
134
+ settings: {
135
+ identify: {
136
+ map: {
137
+ email: "user.email",
138
+ properties: {
139
+ map: {
140
+ firstname: "user.firstName",
141
+ lastname: "user.lastName"
142
+ }
143
+ }
144
+ }
145
+ }
146
+ },
147
+ out: [
148
+ [
149
+ "crm.contacts.basicApi.update",
150
+ "user@example.com",
151
+ { properties: { firstname: "Jane", lastname: "Doe" } },
152
+ "email"
153
+ ],
154
+ [
155
+ "events.send.basicApi.send",
156
+ {
157
+ eventName: "pe12345678_page_view",
158
+ email: "user@example.com",
159
+ occurredAt: /* @__PURE__ */ new Date(1700000103),
160
+ properties: {}
161
+ }
162
+ ]
163
+ ]
164
+ };
165
+ var userLoginIdentify = {
166
+ in: getEvent("user login", {
167
+ timestamp: 1700000104,
168
+ user: { email: "user@example.com" },
169
+ data: {
170
+ email: "login@acme.com",
171
+ first_name: "Jane",
172
+ last_name: "Doe",
173
+ lifecycle: "lead"
174
+ }
175
+ }),
176
+ mapping: {
177
+ skip: true,
178
+ settings: {
179
+ identify: {
180
+ map: {
181
+ email: "data.email",
182
+ properties: {
183
+ map: {
184
+ firstname: "data.first_name",
185
+ lastname: "data.last_name",
186
+ lifecyclestage: "data.lifecycle"
187
+ }
188
+ }
189
+ }
190
+ }
191
+ }
192
+ },
193
+ out: [
194
+ [
195
+ "crm.contacts.basicApi.update",
196
+ "login@acme.com",
197
+ {
198
+ properties: {
199
+ firstname: "Jane",
200
+ lastname: "Doe",
201
+ lifecyclestage: "lead"
202
+ }
203
+ },
204
+ "email"
205
+ ]
206
+ ]
207
+ };
208
+ var objectIdAssociation = {
209
+ in: getEvent("product view", {
210
+ timestamp: 1700000105,
211
+ user: { id: "hs-contact-789" }
212
+ }),
213
+ settings: {
214
+ email: void 0,
215
+ objectId: "user.id"
216
+ },
217
+ out: [
218
+ [
219
+ "events.send.basicApi.send",
220
+ {
221
+ eventName: "pe12345678_product_view",
222
+ objectId: "hs-contact-789",
223
+ occurredAt: /* @__PURE__ */ new Date(1700000105),
224
+ properties: {}
225
+ }
226
+ ]
227
+ ]
228
+ };
229
+ var noIdentity = {
230
+ in: getEvent("product view", {
231
+ timestamp: 1700000106,
232
+ user: {}
233
+ }),
234
+ out: []
235
+ };
236
+ var wildcardIgnored = {
237
+ in: getEvent("debug noise", {
238
+ timestamp: 1700000107,
239
+ user: { email: "user@example.com" }
240
+ }),
241
+ mapping: { ignore: true },
242
+ out: []
243
+ };
244
+ export {
245
+ env_exports as env,
246
+ step_exports as step
247
+ };
@@ -0,0 +1,158 @@
1
+ import { Mapping as Mapping$1, Destination as Destination$1 } from '@walkeros/core';
2
+ import { DestinationServer } from '@walkeros/server-core';
3
+
4
+ /** Shape of a single custom event occurrence sent to HubSpot. */
5
+ interface HubSpotEventRequest {
6
+ eventName: string;
7
+ email?: string;
8
+ objectId?: string;
9
+ utk?: string;
10
+ uuid?: string;
11
+ occurredAt?: Date;
12
+ properties?: Record<string, string>;
13
+ }
14
+ /**
15
+ * Mock-friendly interface for the HubSpot Client methods the destination
16
+ * actually calls. Tests provide this via env.client instead of the real SDK.
17
+ */
18
+ interface HubSpotClientMock {
19
+ events: {
20
+ send: {
21
+ basicApi: {
22
+ send: (data: HubSpotEventRequest) => Promise<void>;
23
+ };
24
+ batchApi: {
25
+ send: (data: {
26
+ inputs: HubSpotEventRequest[];
27
+ }) => Promise<void>;
28
+ };
29
+ };
30
+ };
31
+ crm: {
32
+ contacts: {
33
+ basicApi: {
34
+ update: (id: string, data: {
35
+ properties: Record<string, string>;
36
+ }, idProperty?: string) => Promise<void>;
37
+ };
38
+ };
39
+ };
40
+ }
41
+ interface Settings {
42
+ /** HubSpot private app access token (required). */
43
+ accessToken: string;
44
+ /**
45
+ * Fully qualified event name prefix: pe{HubID}_
46
+ * Used to construct eventName from walkerOS event names.
47
+ * Example: 'pe12345678_'
48
+ */
49
+ eventNamePrefix: string;
50
+ /**
51
+ * walkerOS mapping value path to resolve contact email from events.
52
+ * Default: 'user.email'
53
+ */
54
+ email?: string;
55
+ /**
56
+ * walkerOS mapping value path to resolve HubSpot objectId from events.
57
+ * Default: undefined (use email for association)
58
+ */
59
+ objectId?: string;
60
+ /**
61
+ * Destination-level contact upsert mapping.
62
+ * Resolves to { email, properties } and upserts the contact on each push
63
+ * (with dedup via state hash).
64
+ */
65
+ identify?: Mapping$1.Value;
66
+ /**
67
+ * Static event properties added to every event occurrence.
68
+ * Useful for hs_touchpoint_source, hs_page_content_type, etc.
69
+ */
70
+ defaultProperties?: Record<string, string>;
71
+ /**
72
+ * Whether to use batch API for events (accumulate and flush).
73
+ * Default: false (single event sends).
74
+ */
75
+ batch?: boolean;
76
+ /**
77
+ * Batch size before auto-flush. Only used when batch: true.
78
+ * Default: 50. Max: 500.
79
+ */
80
+ batchSize?: number;
81
+ /** Runtime state -- not user-facing. Mutated by init/push. */
82
+ _client?: HubSpotClientMock;
83
+ _state?: RuntimeState;
84
+ _eventQueue?: HubSpotEventRequest[];
85
+ }
86
+ interface RuntimeState {
87
+ lastIdentity?: {
88
+ email?: string;
89
+ propertiesHash?: string;
90
+ };
91
+ }
92
+ type InitSettings = Partial<Settings>;
93
+ /**
94
+ * Per-rule mapping settings.
95
+ */
96
+ interface Mapping {
97
+ /**
98
+ * Override eventName for this rule. If not set, the walkerOS event name
99
+ * is transformed: spaces to underscores, lowercased, prefixed with
100
+ * eventNamePrefix.
101
+ */
102
+ eventName?: string;
103
+ /**
104
+ * Per-event contact upsert. Resolves to { email, properties }.
105
+ * Overrides destination-level identify.
106
+ */
107
+ identify?: Mapping$1.Value;
108
+ /**
109
+ * Additional event properties mapping. Resolved values are merged
110
+ * with defaultProperties and sent as the event's properties field.
111
+ */
112
+ properties?: Mapping$1.Value;
113
+ }
114
+ /**
115
+ * Env -- optional SDK override. Production leaves this undefined and the
116
+ * destination creates a real Client instance. Tests provide a mock via
117
+ * env.client.
118
+ */
119
+ interface Env extends DestinationServer.Env {
120
+ client?: HubSpotClientMock;
121
+ }
122
+ type Types = Destination$1.Types<Settings, Mapping, Env, InitSettings>;
123
+ interface Destination extends DestinationServer.Destination<Types> {
124
+ init: DestinationServer.InitFn<Types>;
125
+ }
126
+ type Config = {
127
+ settings: Settings;
128
+ } & DestinationServer.Config<Types>;
129
+ type InitFn = DestinationServer.InitFn<Types>;
130
+ type PushFn = DestinationServer.PushFn<Types>;
131
+ type PartialConfig = DestinationServer.PartialConfig<Types>;
132
+ type PushEvents = DestinationServer.PushEvents<Mapping>;
133
+ type Rule = Mapping$1.Rule<Mapping>;
134
+ type Rules = Mapping$1.Rules<Rule>;
135
+
136
+ type index_Config = Config;
137
+ type index_Destination = Destination;
138
+ type index_Env = Env;
139
+ type index_HubSpotClientMock = HubSpotClientMock;
140
+ type index_HubSpotEventRequest = HubSpotEventRequest;
141
+ type index_InitFn = InitFn;
142
+ type index_InitSettings = InitSettings;
143
+ type index_Mapping = Mapping;
144
+ type index_PartialConfig = PartialConfig;
145
+ type index_PushEvents = PushEvents;
146
+ type index_PushFn = PushFn;
147
+ type index_Rule = Rule;
148
+ type index_Rules = Rules;
149
+ type index_RuntimeState = RuntimeState;
150
+ type index_Settings = Settings;
151
+ type index_Types = Types;
152
+ declare namespace index {
153
+ export type { index_Config as Config, index_Destination as Destination, index_Env as Env, index_HubSpotClientMock as HubSpotClientMock, index_HubSpotEventRequest as HubSpotEventRequest, index_InitFn as InitFn, index_InitSettings as InitSettings, index_Mapping as Mapping, index_PartialConfig as PartialConfig, index_PushEvents as PushEvents, index_PushFn as PushFn, index_Rule as Rule, index_Rules as Rules, index_RuntimeState as RuntimeState, index_Settings as Settings, index_Types as Types };
154
+ }
155
+
156
+ declare const destinationHubspot: Destination;
157
+
158
+ export { index as DestinationHubspot, destinationHubspot as default, destinationHubspot };
@@ -0,0 +1,158 @@
1
+ import { Mapping as Mapping$1, Destination as Destination$1 } from '@walkeros/core';
2
+ import { DestinationServer } from '@walkeros/server-core';
3
+
4
+ /** Shape of a single custom event occurrence sent to HubSpot. */
5
+ interface HubSpotEventRequest {
6
+ eventName: string;
7
+ email?: string;
8
+ objectId?: string;
9
+ utk?: string;
10
+ uuid?: string;
11
+ occurredAt?: Date;
12
+ properties?: Record<string, string>;
13
+ }
14
+ /**
15
+ * Mock-friendly interface for the HubSpot Client methods the destination
16
+ * actually calls. Tests provide this via env.client instead of the real SDK.
17
+ */
18
+ interface HubSpotClientMock {
19
+ events: {
20
+ send: {
21
+ basicApi: {
22
+ send: (data: HubSpotEventRequest) => Promise<void>;
23
+ };
24
+ batchApi: {
25
+ send: (data: {
26
+ inputs: HubSpotEventRequest[];
27
+ }) => Promise<void>;
28
+ };
29
+ };
30
+ };
31
+ crm: {
32
+ contacts: {
33
+ basicApi: {
34
+ update: (id: string, data: {
35
+ properties: Record<string, string>;
36
+ }, idProperty?: string) => Promise<void>;
37
+ };
38
+ };
39
+ };
40
+ }
41
+ interface Settings {
42
+ /** HubSpot private app access token (required). */
43
+ accessToken: string;
44
+ /**
45
+ * Fully qualified event name prefix: pe{HubID}_
46
+ * Used to construct eventName from walkerOS event names.
47
+ * Example: 'pe12345678_'
48
+ */
49
+ eventNamePrefix: string;
50
+ /**
51
+ * walkerOS mapping value path to resolve contact email from events.
52
+ * Default: 'user.email'
53
+ */
54
+ email?: string;
55
+ /**
56
+ * walkerOS mapping value path to resolve HubSpot objectId from events.
57
+ * Default: undefined (use email for association)
58
+ */
59
+ objectId?: string;
60
+ /**
61
+ * Destination-level contact upsert mapping.
62
+ * Resolves to { email, properties } and upserts the contact on each push
63
+ * (with dedup via state hash).
64
+ */
65
+ identify?: Mapping$1.Value;
66
+ /**
67
+ * Static event properties added to every event occurrence.
68
+ * Useful for hs_touchpoint_source, hs_page_content_type, etc.
69
+ */
70
+ defaultProperties?: Record<string, string>;
71
+ /**
72
+ * Whether to use batch API for events (accumulate and flush).
73
+ * Default: false (single event sends).
74
+ */
75
+ batch?: boolean;
76
+ /**
77
+ * Batch size before auto-flush. Only used when batch: true.
78
+ * Default: 50. Max: 500.
79
+ */
80
+ batchSize?: number;
81
+ /** Runtime state -- not user-facing. Mutated by init/push. */
82
+ _client?: HubSpotClientMock;
83
+ _state?: RuntimeState;
84
+ _eventQueue?: HubSpotEventRequest[];
85
+ }
86
+ interface RuntimeState {
87
+ lastIdentity?: {
88
+ email?: string;
89
+ propertiesHash?: string;
90
+ };
91
+ }
92
+ type InitSettings = Partial<Settings>;
93
+ /**
94
+ * Per-rule mapping settings.
95
+ */
96
+ interface Mapping {
97
+ /**
98
+ * Override eventName for this rule. If not set, the walkerOS event name
99
+ * is transformed: spaces to underscores, lowercased, prefixed with
100
+ * eventNamePrefix.
101
+ */
102
+ eventName?: string;
103
+ /**
104
+ * Per-event contact upsert. Resolves to { email, properties }.
105
+ * Overrides destination-level identify.
106
+ */
107
+ identify?: Mapping$1.Value;
108
+ /**
109
+ * Additional event properties mapping. Resolved values are merged
110
+ * with defaultProperties and sent as the event's properties field.
111
+ */
112
+ properties?: Mapping$1.Value;
113
+ }
114
+ /**
115
+ * Env -- optional SDK override. Production leaves this undefined and the
116
+ * destination creates a real Client instance. Tests provide a mock via
117
+ * env.client.
118
+ */
119
+ interface Env extends DestinationServer.Env {
120
+ client?: HubSpotClientMock;
121
+ }
122
+ type Types = Destination$1.Types<Settings, Mapping, Env, InitSettings>;
123
+ interface Destination extends DestinationServer.Destination<Types> {
124
+ init: DestinationServer.InitFn<Types>;
125
+ }
126
+ type Config = {
127
+ settings: Settings;
128
+ } & DestinationServer.Config<Types>;
129
+ type InitFn = DestinationServer.InitFn<Types>;
130
+ type PushFn = DestinationServer.PushFn<Types>;
131
+ type PartialConfig = DestinationServer.PartialConfig<Types>;
132
+ type PushEvents = DestinationServer.PushEvents<Mapping>;
133
+ type Rule = Mapping$1.Rule<Mapping>;
134
+ type Rules = Mapping$1.Rules<Rule>;
135
+
136
+ type index_Config = Config;
137
+ type index_Destination = Destination;
138
+ type index_Env = Env;
139
+ type index_HubSpotClientMock = HubSpotClientMock;
140
+ type index_HubSpotEventRequest = HubSpotEventRequest;
141
+ type index_InitFn = InitFn;
142
+ type index_InitSettings = InitSettings;
143
+ type index_Mapping = Mapping;
144
+ type index_PartialConfig = PartialConfig;
145
+ type index_PushEvents = PushEvents;
146
+ type index_PushFn = PushFn;
147
+ type index_Rule = Rule;
148
+ type index_Rules = Rules;
149
+ type index_RuntimeState = RuntimeState;
150
+ type index_Settings = Settings;
151
+ type index_Types = Types;
152
+ declare namespace index {
153
+ export type { index_Config as Config, index_Destination as Destination, index_Env as Env, index_HubSpotClientMock as HubSpotClientMock, index_HubSpotEventRequest as HubSpotEventRequest, index_InitFn as InitFn, index_InitSettings as InitSettings, index_Mapping as Mapping, index_PartialConfig as PartialConfig, index_PushEvents as PushEvents, index_PushFn as PushFn, index_Rule as Rule, index_Rules as Rules, index_RuntimeState as RuntimeState, index_Settings as Settings, index_Types as Types };
154
+ }
155
+
156
+ declare const destinationHubspot: Destination;
157
+
158
+ export { index as DestinationHubspot, destinationHubspot as default, destinationHubspot };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";var e,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,o=Object.prototype.hasOwnProperty,a={};((e,n)=>{for(var i in n)t(e,i,{get:n[i],enumerable:!0})})(a,{DestinationHubspot:()=>p,default:()=>g,destinationHubspot:()=>f}),module.exports=(e=a,((e,a,r,s)=>{if(a&&"object"==typeof a||"function"==typeof a)for(let c of i(a))o.call(e,c)||c===r||t(e,c,{get:()=>a[c],enumerable:!(s=n(a,c))||s.enumerable});return e})(t({},"__esModule",{value:!0}),e));var r=require("@walkeros/core"),s=async function(e,{config:t,rule:n,collector:i,env:o,logger:a}){var s;const p=t.settings,f=(null==o?void 0:o.client)||p._client;if(!f)return void a.warn("HubSpot client not initialized");const g=p._state||{},v=(null==n?void 0:n.settings)||{},d=p.email?c(await(0,r.getMappingValue)(e,p.email,{collector:i})):void 0,b=p.objectId?c(await(0,r.getMappingValue)(e,p.objectId,{collector:i})):void 0;if(!d&&!b)return void a.warn("HubSpot requires email or objectId; skipping event",{event:e.name});const m=null!=(s=v.identify)?s:p.identify;if(void 0!==m){const t=await(0,r.getMappingValue)(e,m,{collector:i});(0,r.isObject)(t)&&await async function(e,t,n,i){var o;const a=(0,r.isString)(t.email)?t.email:void 0;if(!a)return void i.warn("HubSpot identify requires email; skipping contact upsert");const s=(0,r.isObject)(t.properties)?l(t.properties):void 0,c=function(e){if(!e)return"";try{return JSON.stringify(e)}catch(e){return""}}(s),u=n.lastIdentity||{},p=a!==u.email,f=c!==(null!=(o=u.propertiesHash)?o:"");if(!p&&!f)return;s&&await e.crm.contacts.basicApi.update(a,{properties:s},"email");n.lastIdentity={email:a,propertiesHash:c}}(f,t,g,a)}if(!0!==(null==n?void 0:n.skip)){const t=function(e,t,n){const i=t||n.toLowerCase().replace(/\s+/g,"_");return`${e}${i}`}(p.eventNamePrefix,v.eventName,(0,r.isString)(null==n?void 0:n.name)?n.name:e.name);let o={...p.defaultProperties||{}};if(void 0!==v.properties){const t=await(0,r.getMappingValue)(e,v.properties,{collector:i});(0,r.isObject)(t)&&(o={...o,...l(t)})}const a={eventName:t,occurredAt:new Date(e.timestamp||Date.now()),properties:o};d&&(a.email=d),b&&(a.objectId=b),p.batch?(p._eventQueue||(p._eventQueue=[]),p._eventQueue.push(a),p._eventQueue.length>=(p.batchSize||50)&&await u(f,p)):await f.events.send.basicApi.send(a)}p._state=g};function c(e){if((0,r.isString)(e)&&e.length>0)return e}function l(e){const t={};for(const[n,i]of Object.entries(e))null!=i&&(t[n]=String(i));return t}async function u(e,t){const n=t._eventQueue;if(!n||0===n.length)return;const i=Math.min(t.batchSize||50,500);for(;n.length>0;){const t=n.splice(0,i);await e.events.send.batchApi.send({inputs:t})}}var p={},f={type:"hubspot",config:{},init({config:e,logger:t,env:n}){const i=function(e={},t){var n,i,o;const a=e.settings||{},{accessToken:r,eventNamePrefix:s}=a;r||t.throw("Config settings accessToken missing"),s||t.throw("Config settings eventNamePrefix missing");const c={...a,accessToken:r,eventNamePrefix:s,email:null!=(n=a.email)?n:"user.email",batch:null!=(i=a.batch)&&i,batchSize:Math.min(null!=(o=a.batchSize)?o:50,500)};return{...e,settings:c}}(e,t),o=i.settings;if(!(null==n?void 0:n.client))try{const{Client:e}=require("@hubspot/api-client");o._client=new e({accessToken:o.accessToken})}catch(e){t.throw(`Failed to initialize HubSpot SDK: ${e}`)}return o._state={},o.batch&&(o._eventQueue=[]),i},push:async(e,t)=>await s(e,t),async destroy({config:e}){const t=(null==e?void 0:e.settings)||{};if(t.batch&&t._eventQueue&&t._eventQueue.length>0){const e=t._client;e&&await u(e,t)}}},g=f;//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/push.ts","../src/types/index.ts"],"sourcesContent":["import type { Destination, Settings, HubSpotClientMock } from './types';\nimport { getConfig } from './config';\nimport { push, flushBatch } from './push';\n\n// Types\nexport * as DestinationHubspot from './types';\n\nexport const destinationHubspot: Destination = {\n type: 'hubspot',\n\n config: {},\n\n init({ config: partialConfig, logger, env }) {\n const config = getConfig(partialConfig, logger);\n const settings = config.settings as Settings;\n\n // Use env.client mock if provided (testing), otherwise create real SDK\n const envClient = (env as { client?: HubSpotClientMock } | undefined)\n ?.client;\n\n if (!envClient) {\n // Production path: create real HubSpot Client instance\n try {\n const { Client } = require('@hubspot/api-client');\n settings._client = new Client({\n accessToken: settings.accessToken,\n });\n } catch (err) {\n logger.throw(`Failed to initialize HubSpot SDK: ${err}`);\n }\n }\n\n settings._state = {};\n if (settings.batch) settings._eventQueue = [];\n\n return config;\n },\n\n async push(event, context) {\n return await push(event, context);\n },\n\n async destroy({ config }) {\n const settings = (config?.settings || {}) as Settings;\n\n // Flush remaining queued events in batch mode\n if (\n settings.batch &&\n settings._eventQueue &&\n settings._eventQueue.length > 0\n ) {\n const sdk = settings._client;\n if (sdk) {\n await flushBatch(sdk, settings);\n }\n }\n },\n};\n\nexport default destinationHubspot;\n","import type { Config, Settings, PartialConfig } from './types';\nimport type { Logger } from '@walkeros/core';\n\nexport function getConfig(\n partialConfig: PartialConfig = {},\n logger: Logger.Instance,\n): Config {\n const settings = (partialConfig.settings || {}) as Partial<Settings>;\n const { accessToken, eventNamePrefix } = settings;\n\n if (!accessToken) logger.throw('Config settings accessToken missing');\n if (!eventNamePrefix) logger.throw('Config settings eventNamePrefix missing');\n\n const settingsConfig: Settings = {\n ...settings,\n accessToken,\n eventNamePrefix,\n // Default identity resolution path\n email: settings.email ?? 'user.email',\n // Batch defaults\n batch: settings.batch ?? false,\n batchSize: Math.min(settings.batchSize ?? 50, 500),\n };\n\n return { ...partialConfig, settings: settingsConfig };\n}\n","import type {\n PushFn,\n Settings,\n RuntimeState,\n HubSpotClientMock,\n HubSpotEventRequest,\n} from './types';\nimport type { Logger } from '@walkeros/core';\nimport { getMappingValue, isObject, isString } from '@walkeros/core';\n\nexport const push: PushFn = async function (\n event,\n { config, rule, collector, env, logger },\n) {\n const settings = config.settings as Settings;\n const envClient = (env as { client?: HubSpotClientMock } | undefined)?.client;\n const sdk = envClient || settings._client;\n\n if (!sdk) {\n logger.warn('HubSpot client not initialized');\n return;\n }\n\n const state: RuntimeState = settings._state || {};\n const mappingSettings = rule?.settings || {};\n\n // 1. Resolve identity from event\n const email = settings.email\n ? resolveString(await getMappingValue(event, settings.email, { collector }))\n : undefined;\n const objectId = settings.objectId\n ? resolveString(\n await getMappingValue(event, settings.objectId, { collector }),\n )\n : undefined;\n\n if (!email && !objectId) {\n logger.warn('HubSpot requires email or objectId; skipping event', {\n event: event.name,\n });\n return;\n }\n\n // 2. Contact upsert -- rule-level overrides destination-level\n const identifyMapping = mappingSettings.identify ?? settings.identify;\n if (identifyMapping !== undefined) {\n const resolved = await getMappingValue(event, identifyMapping, {\n collector,\n });\n if (isObject(resolved)) {\n await applyIdentify(\n sdk,\n resolved as Record<string, unknown>,\n state,\n logger,\n );\n }\n }\n\n // 3. Send event (unless skip: true)\n if (rule?.skip !== true) {\n const eventName = buildEventName(\n settings.eventNamePrefix,\n mappingSettings.eventName,\n isString(rule?.name) ? rule.name : event.name,\n );\n\n // Build properties: defaultProperties + mapped properties, all strings\n let properties: Record<string, string> = {\n ...(settings.defaultProperties || {}),\n };\n\n if (mappingSettings.properties !== undefined) {\n const resolved = await getMappingValue(\n event,\n mappingSettings.properties,\n { collector },\n );\n if (isObject(resolved)) {\n properties = {\n ...properties,\n ...toStringProperties(resolved as Record<string, unknown>),\n };\n }\n }\n\n const occurredAt = new Date(event.timestamp || Date.now());\n\n const eventRequest: HubSpotEventRequest = {\n eventName,\n occurredAt,\n properties,\n };\n\n // Attach identity\n if (email) eventRequest.email = email;\n if (objectId) eventRequest.objectId = objectId;\n\n if (settings.batch) {\n // Queue for batch flush\n if (!settings._eventQueue) settings._eventQueue = [];\n settings._eventQueue.push(eventRequest);\n\n if (settings._eventQueue.length >= (settings.batchSize || 50)) {\n await flushBatch(sdk, settings);\n }\n } else {\n await sdk.events.send.basicApi.send(eventRequest);\n }\n }\n\n settings._state = state;\n};\n\nfunction resolveString(value: unknown): string | undefined {\n if (isString(value) && value.length > 0) return value;\n return undefined;\n}\n\nfunction buildEventName(\n prefix: string,\n override: string | undefined,\n eventName: string,\n): string {\n const name = override || eventName.toLowerCase().replace(/\\s+/g, '_');\n return `${prefix}${name}`;\n}\n\n/**\n * Serialize all values to strings per HubSpot's API contract.\n */\nexport function toStringProperties(\n data: Record<string, unknown>,\n): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(data)) {\n if (value !== undefined && value !== null) {\n result[key] = String(value);\n }\n }\n return result;\n}\n\nfunction hashProperties(\n properties: Record<string, string> | undefined,\n): string {\n if (!properties) return '';\n try {\n return JSON.stringify(properties);\n } catch {\n return '';\n }\n}\n\nasync function applyIdentify(\n sdk: HubSpotClientMock,\n resolved: Record<string, unknown>,\n state: RuntimeState,\n logger: Logger.Instance,\n): Promise<void> {\n const identifyEmail = isString(resolved.email) ? resolved.email : undefined;\n if (!identifyEmail) {\n logger.warn('HubSpot identify requires email; skipping contact upsert');\n return;\n }\n\n const rawProperties = isObject(resolved.properties)\n ? toStringProperties(resolved.properties as Record<string, unknown>)\n : undefined;\n\n const propertiesHash = hashProperties(rawProperties);\n const last = state.lastIdentity || {};\n\n const emailChanged = identifyEmail !== last.email;\n const propertiesChanged = propertiesHash !== (last.propertiesHash ?? '');\n\n if (!emailChanged && !propertiesChanged) return;\n\n if (rawProperties) {\n await sdk.crm.contacts.basicApi.update(\n identifyEmail,\n { properties: rawProperties },\n 'email',\n );\n }\n\n state.lastIdentity = {\n email: identifyEmail,\n propertiesHash,\n };\n}\n\nexport async function flushBatch(\n sdk: HubSpotClientMock,\n settings: Settings,\n): Promise<void> {\n const queue = settings._eventQueue;\n if (!queue || queue.length === 0) return;\n\n // Flush in chunks of batchSize (max 500)\n const batchSize = Math.min(settings.batchSize || 50, 500);\n while (queue.length > 0) {\n const batch = queue.splice(0, batchSize);\n await sdk.events.send.batchApi.send({ inputs: batch });\n }\n}\n","import type {\n Mapping as WalkerOSMapping,\n Destination as CoreDestination,\n} from '@walkeros/core';\nimport type { DestinationServer } from '@walkeros/server-core';\n\n/** Shape of a single custom event occurrence sent to HubSpot. */\nexport interface HubSpotEventRequest {\n eventName: string;\n email?: string;\n objectId?: string;\n utk?: string;\n uuid?: string;\n occurredAt?: Date;\n properties?: Record<string, string>;\n}\n\n/**\n * Mock-friendly interface for the HubSpot Client methods the destination\n * actually calls. Tests provide this via env.client instead of the real SDK.\n */\nexport interface HubSpotClientMock {\n events: {\n send: {\n basicApi: {\n send: (data: HubSpotEventRequest) => Promise<void>;\n };\n batchApi: {\n send: (data: { inputs: HubSpotEventRequest[] }) => Promise<void>;\n };\n };\n };\n crm: {\n contacts: {\n basicApi: {\n update: (\n id: string,\n data: { properties: Record<string, string> },\n idProperty?: string,\n ) => Promise<void>;\n };\n };\n };\n}\n\nexport interface Settings {\n /** HubSpot private app access token (required). */\n accessToken: string;\n\n /**\n * Fully qualified event name prefix: pe{HubID}_\n * Used to construct eventName from walkerOS event names.\n * Example: 'pe12345678_'\n */\n eventNamePrefix: string;\n\n /**\n * walkerOS mapping value path to resolve contact email from events.\n * Default: 'user.email'\n */\n email?: string;\n\n /**\n * walkerOS mapping value path to resolve HubSpot objectId from events.\n * Default: undefined (use email for association)\n */\n objectId?: string;\n\n /**\n * Destination-level contact upsert mapping.\n * Resolves to { email, properties } and upserts the contact on each push\n * (with dedup via state hash).\n */\n identify?: WalkerOSMapping.Value;\n\n /**\n * Static event properties added to every event occurrence.\n * Useful for hs_touchpoint_source, hs_page_content_type, etc.\n */\n defaultProperties?: Record<string, string>;\n\n /**\n * Whether to use batch API for events (accumulate and flush).\n * Default: false (single event sends).\n */\n batch?: boolean;\n\n /**\n * Batch size before auto-flush. Only used when batch: true.\n * Default: 50. Max: 500.\n */\n batchSize?: number;\n\n /** Runtime state -- not user-facing. Mutated by init/push. */\n _client?: HubSpotClientMock;\n _state?: RuntimeState;\n _eventQueue?: HubSpotEventRequest[];\n}\n\nexport interface RuntimeState {\n lastIdentity?: {\n email?: string;\n propertiesHash?: string;\n };\n}\n\nexport type InitSettings = Partial<Settings>;\n\n/**\n * Per-rule mapping settings.\n */\nexport interface Mapping {\n /**\n * Override eventName for this rule. If not set, the walkerOS event name\n * is transformed: spaces to underscores, lowercased, prefixed with\n * eventNamePrefix.\n */\n eventName?: string;\n\n /**\n * Per-event contact upsert. Resolves to { email, properties }.\n * Overrides destination-level identify.\n */\n identify?: WalkerOSMapping.Value;\n\n /**\n * Additional event properties mapping. Resolved values are merged\n * with defaultProperties and sent as the event's properties field.\n */\n properties?: WalkerOSMapping.Value;\n}\n\n/**\n * Env -- optional SDK override. Production leaves this undefined and the\n * destination creates a real Client instance. Tests provide a mock via\n * env.client.\n */\nexport interface Env extends DestinationServer.Env {\n client?: HubSpotClientMock;\n}\n\nexport type Types = CoreDestination.Types<Settings, Mapping, Env, InitSettings>;\n\nexport interface Destination extends DestinationServer.Destination<Types> {\n init: DestinationServer.InitFn<Types>;\n}\n\nexport type Config = {\n settings: Settings;\n} & DestinationServer.Config<Types>;\n\nexport type InitFn = DestinationServer.InitFn<Types>;\nexport type PushFn = DestinationServer.PushFn<Types>;\nexport type PartialConfig = DestinationServer.PartialConfig<Types>;\nexport type PushEvents = DestinationServer.PushEvents<Mapping>;\nexport type Rule = WalkerOSMapping.Rule<Mapping>;\nexport type Rules = WalkerOSMapping.Rules<Rule>;\n"],"mappings":"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","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ var e=(e=>"undefined"!=typeof require?require:"undefined"!=typeof Proxy?new Proxy(e,{get:(e,t)=>("undefined"!=typeof require?require:e)[t]}):e)(function(e){if("undefined"!=typeof require)return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});import{getMappingValue as t,isObject as n,isString as i}from"@walkeros/core";var o=async function(e,{config:o,rule:c,collector:u,env:l,logger:p}){var f;const d=o.settings,v=(null==l?void 0:l.client)||d._client;if(!v)return void p.warn("HubSpot client not initialized");const m=d._state||{},g=(null==c?void 0:c.settings)||{},h=d.email?r(await t(e,d.email,{collector:u})):void 0,b=d.objectId?r(await t(e,d.objectId,{collector:u})):void 0;if(!h&&!b)return void p.warn("HubSpot requires email or objectId; skipping event",{event:e.name});const w=null!=(f=g.identify)?f:d.identify;if(void 0!==w){const o=await t(e,w,{collector:u});n(o)&&await async function(e,t,o,r){var a;const c=i(t.email)?t.email:void 0;if(!c)return void r.warn("HubSpot identify requires email; skipping contact upsert");const u=n(t.properties)?s(t.properties):void 0,l=function(e){if(!e)return"";try{return JSON.stringify(e)}catch(e){return""}}(u),p=o.lastIdentity||{},f=c!==p.email,d=l!==(null!=(a=p.propertiesHash)?a:"");if(!f&&!d)return;u&&await e.crm.contacts.basicApi.update(c,{properties:u},"email");o.lastIdentity={email:c,propertiesHash:l}}(v,o,m,p)}if(!0!==(null==c?void 0:c.skip)){const o=function(e,t,n){const i=t||n.toLowerCase().replace(/\s+/g,"_");return`${e}${i}`}(d.eventNamePrefix,g.eventName,i(null==c?void 0:c.name)?c.name:e.name);let r={...d.defaultProperties||{}};if(void 0!==g.properties){const i=await t(e,g.properties,{collector:u});n(i)&&(r={...r,...s(i)})}const l={eventName:o,occurredAt:new Date(e.timestamp||Date.now()),properties:r};h&&(l.email=h),b&&(l.objectId=b),d.batch?(d._eventQueue||(d._eventQueue=[]),d._eventQueue.push(l),d._eventQueue.length>=(d.batchSize||50)&&await a(v,d)):await v.events.send.basicApi.send(l)}d._state=m};function r(e){if(i(e)&&e.length>0)return e}function s(e){const t={};for(const[n,i]of Object.entries(e))null!=i&&(t[n]=String(i));return t}async function a(e,t){const n=t._eventQueue;if(!n||0===n.length)return;const i=Math.min(t.batchSize||50,500);for(;n.length>0;){const t=n.splice(0,i);await e.events.send.batchApi.send({inputs:t})}}var c={},u={type:"hubspot",config:{},init({config:t,logger:n,env:i}){const o=function(e={},t){var n,i,o;const r=e.settings||{},{accessToken:s,eventNamePrefix:a}=r;s||t.throw("Config settings accessToken missing"),a||t.throw("Config settings eventNamePrefix missing");const c={...r,accessToken:s,eventNamePrefix:a,email:null!=(n=r.email)?n:"user.email",batch:null!=(i=r.batch)&&i,batchSize:Math.min(null!=(o=r.batchSize)?o:50,500)};return{...e,settings:c}}(t,n),r=o.settings;if(!(null==i?void 0:i.client))try{const{Client:t}=e("@hubspot/api-client");r._client=new t({accessToken:r.accessToken})}catch(e){n.throw(`Failed to initialize HubSpot SDK: ${e}`)}return r._state={},r.batch&&(r._eventQueue=[]),o},push:async(e,t)=>await o(e,t),async destroy({config:e}){const t=(null==e?void 0:e.settings)||{};if(t.batch&&t._eventQueue&&t._eventQueue.length>0){const e=t._client;e&&await a(e,t)}}},l=u;export{c as DestinationHubspot,l as default,u as destinationHubspot};//# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/push.ts","../src/types/index.ts","../src/index.ts"],"sourcesContent":["import type { Config, Settings, PartialConfig } from './types';\nimport type { Logger } from '@walkeros/core';\n\nexport function getConfig(\n partialConfig: PartialConfig = {},\n logger: Logger.Instance,\n): Config {\n const settings = (partialConfig.settings || {}) as Partial<Settings>;\n const { accessToken, eventNamePrefix } = settings;\n\n if (!accessToken) logger.throw('Config settings accessToken missing');\n if (!eventNamePrefix) logger.throw('Config settings eventNamePrefix missing');\n\n const settingsConfig: Settings = {\n ...settings,\n accessToken,\n eventNamePrefix,\n // Default identity resolution path\n email: settings.email ?? 'user.email',\n // Batch defaults\n batch: settings.batch ?? false,\n batchSize: Math.min(settings.batchSize ?? 50, 500),\n };\n\n return { ...partialConfig, settings: settingsConfig };\n}\n","import type {\n PushFn,\n Settings,\n RuntimeState,\n HubSpotClientMock,\n HubSpotEventRequest,\n} from './types';\nimport type { Logger } from '@walkeros/core';\nimport { getMappingValue, isObject, isString } from '@walkeros/core';\n\nexport const push: PushFn = async function (\n event,\n { config, rule, collector, env, logger },\n) {\n const settings = config.settings as Settings;\n const envClient = (env as { client?: HubSpotClientMock } | undefined)?.client;\n const sdk = envClient || settings._client;\n\n if (!sdk) {\n logger.warn('HubSpot client not initialized');\n return;\n }\n\n const state: RuntimeState = settings._state || {};\n const mappingSettings = rule?.settings || {};\n\n // 1. Resolve identity from event\n const email = settings.email\n ? resolveString(await getMappingValue(event, settings.email, { collector }))\n : undefined;\n const objectId = settings.objectId\n ? resolveString(\n await getMappingValue(event, settings.objectId, { collector }),\n )\n : undefined;\n\n if (!email && !objectId) {\n logger.warn('HubSpot requires email or objectId; skipping event', {\n event: event.name,\n });\n return;\n }\n\n // 2. Contact upsert -- rule-level overrides destination-level\n const identifyMapping = mappingSettings.identify ?? settings.identify;\n if (identifyMapping !== undefined) {\n const resolved = await getMappingValue(event, identifyMapping, {\n collector,\n });\n if (isObject(resolved)) {\n await applyIdentify(\n sdk,\n resolved as Record<string, unknown>,\n state,\n logger,\n );\n }\n }\n\n // 3. Send event (unless skip: true)\n if (rule?.skip !== true) {\n const eventName = buildEventName(\n settings.eventNamePrefix,\n mappingSettings.eventName,\n isString(rule?.name) ? rule.name : event.name,\n );\n\n // Build properties: defaultProperties + mapped properties, all strings\n let properties: Record<string, string> = {\n ...(settings.defaultProperties || {}),\n };\n\n if (mappingSettings.properties !== undefined) {\n const resolved = await getMappingValue(\n event,\n mappingSettings.properties,\n { collector },\n );\n if (isObject(resolved)) {\n properties = {\n ...properties,\n ...toStringProperties(resolved as Record<string, unknown>),\n };\n }\n }\n\n const occurredAt = new Date(event.timestamp || Date.now());\n\n const eventRequest: HubSpotEventRequest = {\n eventName,\n occurredAt,\n properties,\n };\n\n // Attach identity\n if (email) eventRequest.email = email;\n if (objectId) eventRequest.objectId = objectId;\n\n if (settings.batch) {\n // Queue for batch flush\n if (!settings._eventQueue) settings._eventQueue = [];\n settings._eventQueue.push(eventRequest);\n\n if (settings._eventQueue.length >= (settings.batchSize || 50)) {\n await flushBatch(sdk, settings);\n }\n } else {\n await sdk.events.send.basicApi.send(eventRequest);\n }\n }\n\n settings._state = state;\n};\n\nfunction resolveString(value: unknown): string | undefined {\n if (isString(value) && value.length > 0) return value;\n return undefined;\n}\n\nfunction buildEventName(\n prefix: string,\n override: string | undefined,\n eventName: string,\n): string {\n const name = override || eventName.toLowerCase().replace(/\\s+/g, '_');\n return `${prefix}${name}`;\n}\n\n/**\n * Serialize all values to strings per HubSpot's API contract.\n */\nexport function toStringProperties(\n data: Record<string, unknown>,\n): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(data)) {\n if (value !== undefined && value !== null) {\n result[key] = String(value);\n }\n }\n return result;\n}\n\nfunction hashProperties(\n properties: Record<string, string> | undefined,\n): string {\n if (!properties) return '';\n try {\n return JSON.stringify(properties);\n } catch {\n return '';\n }\n}\n\nasync function applyIdentify(\n sdk: HubSpotClientMock,\n resolved: Record<string, unknown>,\n state: RuntimeState,\n logger: Logger.Instance,\n): Promise<void> {\n const identifyEmail = isString(resolved.email) ? resolved.email : undefined;\n if (!identifyEmail) {\n logger.warn('HubSpot identify requires email; skipping contact upsert');\n return;\n }\n\n const rawProperties = isObject(resolved.properties)\n ? toStringProperties(resolved.properties as Record<string, unknown>)\n : undefined;\n\n const propertiesHash = hashProperties(rawProperties);\n const last = state.lastIdentity || {};\n\n const emailChanged = identifyEmail !== last.email;\n const propertiesChanged = propertiesHash !== (last.propertiesHash ?? '');\n\n if (!emailChanged && !propertiesChanged) return;\n\n if (rawProperties) {\n await sdk.crm.contacts.basicApi.update(\n identifyEmail,\n { properties: rawProperties },\n 'email',\n );\n }\n\n state.lastIdentity = {\n email: identifyEmail,\n propertiesHash,\n };\n}\n\nexport async function flushBatch(\n sdk: HubSpotClientMock,\n settings: Settings,\n): Promise<void> {\n const queue = settings._eventQueue;\n if (!queue || queue.length === 0) return;\n\n // Flush in chunks of batchSize (max 500)\n const batchSize = Math.min(settings.batchSize || 50, 500);\n while (queue.length > 0) {\n const batch = queue.splice(0, batchSize);\n await sdk.events.send.batchApi.send({ inputs: batch });\n }\n}\n","import type {\n Mapping as WalkerOSMapping,\n Destination as CoreDestination,\n} from '@walkeros/core';\nimport type { DestinationServer } from '@walkeros/server-core';\n\n/** Shape of a single custom event occurrence sent to HubSpot. */\nexport interface HubSpotEventRequest {\n eventName: string;\n email?: string;\n objectId?: string;\n utk?: string;\n uuid?: string;\n occurredAt?: Date;\n properties?: Record<string, string>;\n}\n\n/**\n * Mock-friendly interface for the HubSpot Client methods the destination\n * actually calls. Tests provide this via env.client instead of the real SDK.\n */\nexport interface HubSpotClientMock {\n events: {\n send: {\n basicApi: {\n send: (data: HubSpotEventRequest) => Promise<void>;\n };\n batchApi: {\n send: (data: { inputs: HubSpotEventRequest[] }) => Promise<void>;\n };\n };\n };\n crm: {\n contacts: {\n basicApi: {\n update: (\n id: string,\n data: { properties: Record<string, string> },\n idProperty?: string,\n ) => Promise<void>;\n };\n };\n };\n}\n\nexport interface Settings {\n /** HubSpot private app access token (required). */\n accessToken: string;\n\n /**\n * Fully qualified event name prefix: pe{HubID}_\n * Used to construct eventName from walkerOS event names.\n * Example: 'pe12345678_'\n */\n eventNamePrefix: string;\n\n /**\n * walkerOS mapping value path to resolve contact email from events.\n * Default: 'user.email'\n */\n email?: string;\n\n /**\n * walkerOS mapping value path to resolve HubSpot objectId from events.\n * Default: undefined (use email for association)\n */\n objectId?: string;\n\n /**\n * Destination-level contact upsert mapping.\n * Resolves to { email, properties } and upserts the contact on each push\n * (with dedup via state hash).\n */\n identify?: WalkerOSMapping.Value;\n\n /**\n * Static event properties added to every event occurrence.\n * Useful for hs_touchpoint_source, hs_page_content_type, etc.\n */\n defaultProperties?: Record<string, string>;\n\n /**\n * Whether to use batch API for events (accumulate and flush).\n * Default: false (single event sends).\n */\n batch?: boolean;\n\n /**\n * Batch size before auto-flush. Only used when batch: true.\n * Default: 50. Max: 500.\n */\n batchSize?: number;\n\n /** Runtime state -- not user-facing. Mutated by init/push. */\n _client?: HubSpotClientMock;\n _state?: RuntimeState;\n _eventQueue?: HubSpotEventRequest[];\n}\n\nexport interface RuntimeState {\n lastIdentity?: {\n email?: string;\n propertiesHash?: string;\n };\n}\n\nexport type InitSettings = Partial<Settings>;\n\n/**\n * Per-rule mapping settings.\n */\nexport interface Mapping {\n /**\n * Override eventName for this rule. If not set, the walkerOS event name\n * is transformed: spaces to underscores, lowercased, prefixed with\n * eventNamePrefix.\n */\n eventName?: string;\n\n /**\n * Per-event contact upsert. Resolves to { email, properties }.\n * Overrides destination-level identify.\n */\n identify?: WalkerOSMapping.Value;\n\n /**\n * Additional event properties mapping. Resolved values are merged\n * with defaultProperties and sent as the event's properties field.\n */\n properties?: WalkerOSMapping.Value;\n}\n\n/**\n * Env -- optional SDK override. Production leaves this undefined and the\n * destination creates a real Client instance. Tests provide a mock via\n * env.client.\n */\nexport interface Env extends DestinationServer.Env {\n client?: HubSpotClientMock;\n}\n\nexport type Types = CoreDestination.Types<Settings, Mapping, Env, InitSettings>;\n\nexport interface Destination extends DestinationServer.Destination<Types> {\n init: DestinationServer.InitFn<Types>;\n}\n\nexport type Config = {\n settings: Settings;\n} & DestinationServer.Config<Types>;\n\nexport type InitFn = DestinationServer.InitFn<Types>;\nexport type PushFn = DestinationServer.PushFn<Types>;\nexport type PartialConfig = DestinationServer.PartialConfig<Types>;\nexport type PushEvents = DestinationServer.PushEvents<Mapping>;\nexport type Rule = WalkerOSMapping.Rule<Mapping>;\nexport type Rules = WalkerOSMapping.Rules<Rule>;\n","import type { Destination, Settings, HubSpotClientMock } from './types';\nimport { getConfig } from './config';\nimport { push, flushBatch } from './push';\n\n// Types\nexport * as DestinationHubspot from './types';\n\nexport const destinationHubspot: Destination = {\n type: 'hubspot',\n\n config: {},\n\n init({ config: partialConfig, logger, env }) {\n const config = getConfig(partialConfig, logger);\n const settings = config.settings as Settings;\n\n // Use env.client mock if provided (testing), otherwise create real SDK\n const envClient = (env as { client?: HubSpotClientMock } | undefined)\n ?.client;\n\n if (!envClient) {\n // Production path: create real HubSpot Client instance\n try {\n const { Client } = require('@hubspot/api-client');\n settings._client = new Client({\n accessToken: settings.accessToken,\n });\n } catch (err) {\n logger.throw(`Failed to initialize HubSpot SDK: ${err}`);\n }\n }\n\n settings._state = {};\n if (settings.batch) settings._eventQueue = [];\n\n return config;\n },\n\n async push(event, context) {\n return await push(event, context);\n },\n\n async destroy({ config }) {\n const settings = (config?.settings || {}) as Settings;\n\n // Flush remaining queued events in batch mode\n if (\n settings.batch &&\n settings._eventQueue &&\n settings._eventQueue.length > 0\n ) {\n const sdk = settings._client;\n if (sdk) {\n await flushBatch(sdk, settings);\n }\n }\n },\n};\n\nexport default destinationHubspot;\n"],"mappings":"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","names":[]}