@customerio/cdp-analytics-core 0.0.1

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.
Files changed (220) hide show
  1. package/LICENSE.MD +22 -0
  2. package/README.md +3 -0
  3. package/dist/cjs/analytics/dispatch.js +54 -0
  4. package/dist/cjs/analytics/dispatch.js.map +1 -0
  5. package/dist/cjs/analytics/index.js +3 -0
  6. package/dist/cjs/analytics/index.js.map +1 -0
  7. package/dist/cjs/callback/index.js +46 -0
  8. package/dist/cjs/callback/index.js.map +1 -0
  9. package/dist/cjs/connection/index.js +16 -0
  10. package/dist/cjs/connection/index.js.map +1 -0
  11. package/dist/cjs/context/index.js +87 -0
  12. package/dist/cjs/context/index.js.map +1 -0
  13. package/dist/cjs/emitter/index.js +66 -0
  14. package/dist/cjs/emitter/index.js.map +1 -0
  15. package/dist/cjs/emitter/interface.js +3 -0
  16. package/dist/cjs/emitter/interface.js.map +1 -0
  17. package/dist/cjs/events/index.js +149 -0
  18. package/dist/cjs/events/index.js.map +1 -0
  19. package/dist/cjs/events/interfaces.js +3 -0
  20. package/dist/cjs/events/interfaces.js.map +1 -0
  21. package/dist/cjs/index.js +25 -0
  22. package/dist/cjs/index.js.map +1 -0
  23. package/dist/cjs/logger/index.js +62 -0
  24. package/dist/cjs/logger/index.js.map +1 -0
  25. package/dist/cjs/plugins/index.js +3 -0
  26. package/dist/cjs/plugins/index.js.map +1 -0
  27. package/dist/cjs/priority-queue/backoff.js +10 -0
  28. package/dist/cjs/priority-queue/backoff.js.map +1 -0
  29. package/dist/cjs/priority-queue/index.js +92 -0
  30. package/dist/cjs/priority-queue/index.js.map +1 -0
  31. package/dist/cjs/queue/delivery.js +69 -0
  32. package/dist/cjs/queue/delivery.js.map +1 -0
  33. package/dist/cjs/queue/event-queue.js +340 -0
  34. package/dist/cjs/queue/event-queue.js.map +1 -0
  35. package/dist/cjs/stats/index.js +96 -0
  36. package/dist/cjs/stats/index.js.map +1 -0
  37. package/dist/cjs/task/task-group.js +24 -0
  38. package/dist/cjs/task/task-group.js.map +1 -0
  39. package/dist/cjs/user/index.js +3 -0
  40. package/dist/cjs/user/index.js.map +1 -0
  41. package/dist/cjs/utils/bind-all.js +18 -0
  42. package/dist/cjs/utils/bind-all.js.map +1 -0
  43. package/dist/cjs/utils/environment.js +12 -0
  44. package/dist/cjs/utils/environment.js.map +1 -0
  45. package/dist/cjs/utils/get-global.js +21 -0
  46. package/dist/cjs/utils/get-global.js.map +1 -0
  47. package/dist/cjs/utils/group-by.js +28 -0
  48. package/dist/cjs/utils/group-by.js.map +1 -0
  49. package/dist/cjs/utils/has-properties.js +13 -0
  50. package/dist/cjs/utils/has-properties.js.map +1 -0
  51. package/dist/cjs/utils/is-plain-object.js +28 -0
  52. package/dist/cjs/utils/is-plain-object.js.map +1 -0
  53. package/dist/cjs/utils/is-thenable.js +15 -0
  54. package/dist/cjs/utils/is-thenable.js.map +1 -0
  55. package/dist/cjs/utils/p-while.js +25 -0
  56. package/dist/cjs/utils/p-while.js.map +1 -0
  57. package/dist/cjs/utils/pick.js +10 -0
  58. package/dist/cjs/utils/pick.js.map +1 -0
  59. package/dist/cjs/utils/ts-helpers.js +3 -0
  60. package/dist/cjs/utils/ts-helpers.js.map +1 -0
  61. package/dist/cjs/validation/assertions.js +41 -0
  62. package/dist/cjs/validation/assertions.js.map +1 -0
  63. package/dist/cjs/validation/helpers.js +26 -0
  64. package/dist/cjs/validation/helpers.js.map +1 -0
  65. package/dist/esm/analytics/dispatch.js +49 -0
  66. package/dist/esm/analytics/dispatch.js.map +1 -0
  67. package/dist/esm/analytics/index.js +2 -0
  68. package/dist/esm/analytics/index.js.map +1 -0
  69. package/dist/esm/callback/index.js +40 -0
  70. package/dist/esm/callback/index.js.map +1 -0
  71. package/dist/esm/connection/index.js +11 -0
  72. package/dist/esm/connection/index.js.map +1 -0
  73. package/dist/esm/context/index.js +84 -0
  74. package/dist/esm/context/index.js.map +1 -0
  75. package/dist/esm/emitter/index.js +63 -0
  76. package/dist/esm/emitter/index.js.map +1 -0
  77. package/dist/esm/emitter/interface.js +2 -0
  78. package/dist/esm/emitter/interface.js.map +1 -0
  79. package/dist/esm/events/index.js +146 -0
  80. package/dist/esm/events/index.js.map +1 -0
  81. package/dist/esm/events/interfaces.js +2 -0
  82. package/dist/esm/events/interfaces.js.map +1 -0
  83. package/dist/esm/index.js +19 -0
  84. package/dist/esm/index.js.map +1 -0
  85. package/dist/esm/logger/index.js +59 -0
  86. package/dist/esm/logger/index.js.map +1 -0
  87. package/dist/esm/plugins/index.js +2 -0
  88. package/dist/esm/plugins/index.js.map +1 -0
  89. package/dist/esm/priority-queue/backoff.js +6 -0
  90. package/dist/esm/priority-queue/backoff.js.map +1 -0
  91. package/dist/esm/priority-queue/index.js +89 -0
  92. package/dist/esm/priority-queue/index.js.map +1 -0
  93. package/dist/esm/queue/delivery.js +64 -0
  94. package/dist/esm/queue/delivery.js.map +1 -0
  95. package/dist/esm/queue/event-queue.js +337 -0
  96. package/dist/esm/queue/event-queue.js.map +1 -0
  97. package/dist/esm/stats/index.js +93 -0
  98. package/dist/esm/stats/index.js.map +1 -0
  99. package/dist/esm/task/task-group.js +20 -0
  100. package/dist/esm/task/task-group.js.map +1 -0
  101. package/dist/esm/user/index.js +2 -0
  102. package/dist/esm/user/index.js.map +1 -0
  103. package/dist/esm/utils/bind-all.js +14 -0
  104. package/dist/esm/utils/bind-all.js.map +1 -0
  105. package/dist/esm/utils/environment.js +7 -0
  106. package/dist/esm/utils/environment.js.map +1 -0
  107. package/dist/esm/utils/get-global.js +17 -0
  108. package/dist/esm/utils/get-global.js.map +1 -0
  109. package/dist/esm/utils/group-by.js +24 -0
  110. package/dist/esm/utils/group-by.js.map +1 -0
  111. package/dist/esm/utils/has-properties.js +9 -0
  112. package/dist/esm/utils/has-properties.js.map +1 -0
  113. package/dist/esm/utils/is-plain-object.js +24 -0
  114. package/dist/esm/utils/is-plain-object.js.map +1 -0
  115. package/dist/esm/utils/is-thenable.js +11 -0
  116. package/dist/esm/utils/is-thenable.js.map +1 -0
  117. package/dist/esm/utils/p-while.js +21 -0
  118. package/dist/esm/utils/p-while.js.map +1 -0
  119. package/dist/esm/utils/pick.js +6 -0
  120. package/dist/esm/utils/pick.js.map +1 -0
  121. package/dist/esm/utils/ts-helpers.js +2 -0
  122. package/dist/esm/utils/ts-helpers.js.map +1 -0
  123. package/dist/esm/validation/assertions.js +37 -0
  124. package/dist/esm/validation/assertions.js.map +1 -0
  125. package/dist/esm/validation/helpers.js +18 -0
  126. package/dist/esm/validation/helpers.js.map +1 -0
  127. package/dist/types/analytics/dispatch.d.ts +20 -0
  128. package/dist/types/analytics/dispatch.d.ts.map +1 -0
  129. package/dist/types/analytics/index.d.ts +12 -0
  130. package/dist/types/analytics/index.d.ts.map +1 -0
  131. package/dist/types/callback/index.d.ts +11 -0
  132. package/dist/types/callback/index.d.ts.map +1 -0
  133. package/dist/types/connection/index.d.ts +3 -0
  134. package/dist/types/connection/index.d.ts.map +1 -0
  135. package/dist/types/context/index.d.ts +44 -0
  136. package/dist/types/context/index.d.ts.map +1 -0
  137. package/dist/types/emitter/index.d.ts +25 -0
  138. package/dist/types/emitter/index.d.ts.map +1 -0
  139. package/dist/types/emitter/interface.d.ts +27 -0
  140. package/dist/types/emitter/interface.d.ts.map +1 -0
  141. package/dist/types/events/index.d.ts +27 -0
  142. package/dist/types/events/index.d.ts.map +1 -0
  143. package/dist/types/events/interfaces.d.ts +373 -0
  144. package/dist/types/events/interfaces.d.ts.map +1 -0
  145. package/dist/types/index.d.ts +19 -0
  146. package/dist/types/index.d.ts.map +1 -0
  147. package/dist/types/logger/index.d.ts +19 -0
  148. package/dist/types/logger/index.d.ts.map +1 -0
  149. package/dist/types/plugins/index.d.ts +25 -0
  150. package/dist/types/plugins/index.d.ts.map +1 -0
  151. package/dist/types/priority-queue/backoff.d.ts +13 -0
  152. package/dist/types/priority-queue/backoff.d.ts.map +1 -0
  153. package/dist/types/priority-queue/index.d.ts +25 -0
  154. package/dist/types/priority-queue/index.d.ts.map +1 -0
  155. package/dist/types/queue/delivery.d.ts +5 -0
  156. package/dist/types/queue/delivery.d.ts.map +1 -0
  157. package/dist/types/queue/event-queue.d.ts +43 -0
  158. package/dist/types/queue/event-queue.d.ts.map +1 -0
  159. package/dist/types/stats/index.d.ts +34 -0
  160. package/dist/types/stats/index.d.ts.map +1 -0
  161. package/dist/types/task/task-group.d.ts +6 -0
  162. package/dist/types/task/task-group.d.ts.map +1 -0
  163. package/dist/types/user/index.d.ts +6 -0
  164. package/dist/types/user/index.d.ts.map +1 -0
  165. package/dist/types/utils/bind-all.d.ts +4 -0
  166. package/dist/types/utils/bind-all.d.ts.map +1 -0
  167. package/dist/types/utils/environment.d.ts +3 -0
  168. package/dist/types/utils/environment.d.ts.map +1 -0
  169. package/dist/types/utils/get-global.d.ts +2 -0
  170. package/dist/types/utils/get-global.d.ts.map +1 -0
  171. package/dist/types/utils/group-by.d.ts +4 -0
  172. package/dist/types/utils/group-by.d.ts.map +1 -0
  173. package/dist/types/utils/has-properties.d.ts +4 -0
  174. package/dist/types/utils/has-properties.d.ts.map +1 -0
  175. package/dist/types/utils/is-plain-object.d.ts +2 -0
  176. package/dist/types/utils/is-plain-object.d.ts.map +1 -0
  177. package/dist/types/utils/is-thenable.d.ts +6 -0
  178. package/dist/types/utils/is-thenable.d.ts.map +1 -0
  179. package/dist/types/utils/p-while.d.ts +2 -0
  180. package/dist/types/utils/p-while.d.ts.map +1 -0
  181. package/dist/types/utils/pick.d.ts +2 -0
  182. package/dist/types/utils/pick.d.ts.map +1 -0
  183. package/dist/types/utils/ts-helpers.d.ts +13 -0
  184. package/dist/types/utils/ts-helpers.d.ts.map +1 -0
  185. package/dist/types/validation/assertions.d.ts +7 -0
  186. package/dist/types/validation/assertions.d.ts.map +1 -0
  187. package/dist/types/validation/helpers.d.ts +7 -0
  188. package/dist/types/validation/helpers.d.ts.map +1 -0
  189. package/package.json +39 -0
  190. package/src/analytics/dispatch.ts +58 -0
  191. package/src/analytics/index.ts +11 -0
  192. package/src/callback/index.ts +51 -0
  193. package/src/connection/index.ts +13 -0
  194. package/src/context/index.ts +123 -0
  195. package/src/emitter/index.ts +65 -0
  196. package/src/emitter/interface.ts +31 -0
  197. package/src/events/index.ts +280 -0
  198. package/src/events/interfaces.ts +447 -0
  199. package/src/index.ts +18 -0
  200. package/src/logger/index.ts +74 -0
  201. package/src/plugins/index.ts +43 -0
  202. package/src/priority-queue/backoff.ts +24 -0
  203. package/src/priority-queue/index.ts +103 -0
  204. package/src/queue/delivery.ts +73 -0
  205. package/src/queue/event-queue.ts +320 -0
  206. package/src/stats/index.ts +88 -0
  207. package/src/task/task-group.ts +31 -0
  208. package/src/user/index.ts +7 -0
  209. package/src/utils/bind-all.ts +19 -0
  210. package/src/utils/environment.ts +7 -0
  211. package/src/utils/get-global.ts +16 -0
  212. package/src/utils/group-by.ts +30 -0
  213. package/src/utils/has-properties.ts +7 -0
  214. package/src/utils/is-plain-object.ts +26 -0
  215. package/src/utils/is-thenable.ts +9 -0
  216. package/src/utils/p-while.ts +12 -0
  217. package/src/utils/pick.ts +8 -0
  218. package/src/utils/ts-helpers.ts +13 -0
  219. package/src/validation/assertions.ts +43 -0
  220. package/src/validation/helpers.ts +27 -0
@@ -0,0 +1,123 @@
1
+ import { CoreCustomerioEvent } from '../events/interfaces'
2
+
3
+ import { v4 as uuid } from '@lukeed/uuid'
4
+ import { dset } from 'dset'
5
+ import { CoreLogger, LogLevel, LogMessage } from '../logger'
6
+ import { CoreStats, CoreMetric, NullStats } from '../stats'
7
+
8
+ export interface SerializedContext {
9
+ id: string
10
+ event: CoreCustomerioEvent
11
+ logs: LogMessage[]
12
+ metrics?: CoreMetric[]
13
+ }
14
+
15
+ export interface ContextFailedDelivery {
16
+ reason: unknown
17
+ }
18
+
19
+ export interface CancelationOptions {
20
+ retry?: boolean
21
+ reason?: string
22
+ type?: string
23
+ }
24
+
25
+ export class ContextCancelation {
26
+ retry: boolean
27
+ type: string
28
+ reason?: string
29
+
30
+ constructor(options: CancelationOptions) {
31
+ this.retry = options.retry ?? true
32
+ this.type = options.type ?? 'plugin Error'
33
+ this.reason = options.reason ?? ''
34
+ }
35
+ }
36
+
37
+ export abstract class CoreContext<
38
+ Event extends CoreCustomerioEvent = CoreCustomerioEvent
39
+ > {
40
+ event: Event
41
+ logger: CoreLogger
42
+ stats: CoreStats
43
+ attempts = 0
44
+
45
+ private _failedDelivery?: ContextFailedDelivery
46
+ private _id: string
47
+
48
+ constructor(
49
+ event: Event,
50
+ id = uuid(),
51
+ stats: CoreStats = new NullStats(),
52
+ logger = new CoreLogger()
53
+ ) {
54
+ this.event = event
55
+ this._id = id
56
+ this.logger = logger
57
+ this.stats = stats
58
+ }
59
+
60
+ static system(): void {
61
+ // This should be overridden by the subclass to return an instance of the subclass.
62
+ }
63
+
64
+ isSame(other: CoreContext): boolean {
65
+ return other.id === this.id
66
+ }
67
+
68
+ cancel(error?: Error | ContextCancelation): never {
69
+ if (error) {
70
+ throw error
71
+ }
72
+
73
+ throw new ContextCancelation({ reason: 'Context Cancel' })
74
+ }
75
+
76
+ log(level: LogLevel, message: string, extras?: object): void {
77
+ this.logger.log(level, message, extras)
78
+ }
79
+
80
+ get id(): string {
81
+ return this._id
82
+ }
83
+
84
+ updateEvent(path: string, val: unknown): Event {
85
+ // Don't allow integrations that are set to false to be overwritten with integration settings.
86
+ if (path.split('.')[0] === 'integrations') {
87
+ const integrationName = path.split('.')[1]
88
+
89
+ if (this.event.integrations?.[integrationName] === false) {
90
+ return this.event
91
+ }
92
+ }
93
+
94
+ dset(this.event, path, val)
95
+ return this.event
96
+ }
97
+
98
+ failedDelivery(): ContextFailedDelivery | undefined {
99
+ return this._failedDelivery
100
+ }
101
+
102
+ setFailedDelivery(options: ContextFailedDelivery) {
103
+ this._failedDelivery = options
104
+ }
105
+
106
+ logs(): LogMessage[] {
107
+ return this.logger.logs
108
+ }
109
+
110
+ flush(): void {
111
+ this.logger.flush()
112
+ this.stats.flush()
113
+ }
114
+
115
+ toJSON(): SerializedContext {
116
+ return {
117
+ id: this._id,
118
+ event: this.event,
119
+ logs: this.logger.logs,
120
+ metrics: this.stats.metrics,
121
+ }
122
+ }
123
+ }
@@ -0,0 +1,65 @@
1
+ type EventName = string
2
+ type EventFnArgs = any[]
3
+ type EmitterContract = Record<EventName, EventFnArgs>
4
+
5
+ /**
6
+ * Event Emitter that takes the expected contract as a generic
7
+ * @example
8
+ * ```ts
9
+ * type Contract = {
10
+ * delivery_success: [DeliverySuccessResponse, Metrics],
11
+ * delivery_failure: [DeliveryError]
12
+ * }
13
+ * new Emitter<Contract>()
14
+ * .on('delivery_success', (res, metrics) => ...)
15
+ * .on('delivery_failure', (err) => ...)
16
+ * ```
17
+ */
18
+ export class Emitter<Contract extends EmitterContract = EmitterContract> {
19
+ private callbacks: Partial<Contract> = {}
20
+ on<EventName extends keyof Contract>(
21
+ event: EventName,
22
+ callback: (...args: Contract[EventName]) => void
23
+ ): this {
24
+ if (!this.callbacks[event]) {
25
+ this.callbacks[event] = [callback] as Contract[EventName]
26
+ } else {
27
+ this.callbacks[event]!.push(callback)
28
+ }
29
+ return this
30
+ }
31
+
32
+ once<EventName extends keyof Contract>(
33
+ event: EventName,
34
+ callback: (...args: Contract[EventName]) => void
35
+ ): this {
36
+ const on = (...args: Contract[EventName]): void => {
37
+ this.off(event, on)
38
+ callback.apply(this, args)
39
+ }
40
+
41
+ this.on(event, on)
42
+ return this
43
+ }
44
+
45
+ off<EventName extends keyof Contract>(
46
+ event: EventName,
47
+ callback: (...args: Contract[EventName]) => void
48
+ ): this {
49
+ const fns = this.callbacks[event] ?? []
50
+ const without = fns.filter((fn) => fn !== callback) as Contract[EventName]
51
+ this.callbacks[event] = without
52
+ return this
53
+ }
54
+
55
+ emit<EventName extends keyof Contract>(
56
+ event: EventName,
57
+ ...args: Contract[EventName]
58
+ ): this {
59
+ const callbacks = this.callbacks[event] ?? []
60
+ callbacks.forEach((callback) => {
61
+ callback.apply(this, args)
62
+ })
63
+ return this
64
+ }
65
+ }
@@ -0,0 +1,31 @@
1
+ import { CoreContext } from '../context'
2
+
3
+ /**
4
+ * This is the base contract for all emitted errors. This interface may be extended.
5
+ */
6
+ export interface CoreEmittedError<Ctx extends CoreContext> {
7
+ /**
8
+ * e.g. 'delivery_failure'
9
+ */
10
+ code: string
11
+ /**
12
+ * Why the error occurred. This can be an actual error object or a just a message.
13
+ */
14
+ reason?: unknown
15
+ ctx?: Ctx
16
+ }
17
+
18
+ export type CoreEmitterContract<
19
+ Ctx extends CoreContext,
20
+ Err extends CoreEmittedError<Ctx> = CoreEmittedError<Ctx>
21
+ > = {
22
+ alias: [ctx: Ctx]
23
+ track: [ctx: Ctx]
24
+ identify: [ctx: Ctx]
25
+ page: [ctx: Ctx]
26
+ screen: [ctx: Ctx]
27
+ group: [ctx: Ctx]
28
+ register: [pluginNames: string[]]
29
+ deregister: [pluginNames: string[]]
30
+ error: [error: Err]
31
+ }
@@ -0,0 +1,280 @@
1
+ export * from './interfaces'
2
+ import { dset } from 'dset'
3
+ import { ID, User } from '../user'
4
+ import {
5
+ Integrations,
6
+ EventProperties,
7
+ CoreCustomerioEvent,
8
+ CoreOptions,
9
+ CoreExtraContext,
10
+ UserTraits,
11
+ GroupTraits,
12
+ } from './interfaces'
13
+ import { pickBy } from '../utils/pick'
14
+ import { validateEvent } from '../validation/assertions'
15
+ import type { RemoveIndexSignature } from '../utils/ts-helpers'
16
+
17
+ interface EventFactorySettings {
18
+ createMessageId: () => string
19
+ user?: User
20
+ }
21
+
22
+ export class EventFactory {
23
+ createMessageId: EventFactorySettings['createMessageId']
24
+ user?: User
25
+
26
+ constructor(settings: EventFactorySettings) {
27
+ this.user = settings.user
28
+ this.createMessageId = settings.createMessageId
29
+ }
30
+
31
+ track(
32
+ event: string,
33
+ properties?: EventProperties,
34
+ options?: CoreOptions,
35
+ globalIntegrations?: Integrations
36
+ ) {
37
+ return this.normalize({
38
+ ...this.baseEvent(),
39
+ event,
40
+ type: 'track',
41
+ properties: properties ?? {}, // TODO: why is this not a shallow copy like everywhere else?
42
+ options: { ...options },
43
+ integrations: { ...globalIntegrations },
44
+ })
45
+ }
46
+
47
+ page(
48
+ category: string | null,
49
+ page: string | null,
50
+ properties?: EventProperties,
51
+ options?: CoreOptions,
52
+ globalIntegrations?: Integrations
53
+ ): CoreCustomerioEvent {
54
+ const event: CoreCustomerioEvent = {
55
+ type: 'page',
56
+ properties: { ...properties },
57
+ options: { ...options },
58
+ integrations: { ...globalIntegrations },
59
+ }
60
+
61
+ if (category !== null) {
62
+ event.category = category
63
+ event.properties = event.properties ?? {}
64
+ event.properties.category = category
65
+ }
66
+
67
+ if (page !== null) {
68
+ event.name = page
69
+ }
70
+
71
+ return this.normalize({
72
+ ...this.baseEvent(),
73
+ ...event,
74
+ })
75
+ }
76
+
77
+ screen(
78
+ category: string | null,
79
+ screen: string | null,
80
+ properties?: EventProperties,
81
+ options?: CoreOptions,
82
+ globalIntegrations?: Integrations
83
+ ): CoreCustomerioEvent {
84
+ const event: CoreCustomerioEvent = {
85
+ type: 'screen',
86
+ properties: { ...properties },
87
+ options: { ...options },
88
+ integrations: { ...globalIntegrations },
89
+ }
90
+
91
+ if (category !== null) {
92
+ event.category = category
93
+ }
94
+
95
+ if (screen !== null) {
96
+ event.name = screen
97
+ }
98
+
99
+ return this.normalize({
100
+ ...this.baseEvent(),
101
+ ...event,
102
+ })
103
+ }
104
+
105
+ identify(
106
+ userId: ID,
107
+ traits?: UserTraits,
108
+ options?: CoreOptions,
109
+ globalIntegrations?: Integrations
110
+ ): CoreCustomerioEvent {
111
+ return this.normalize({
112
+ ...this.baseEvent(),
113
+ type: 'identify',
114
+ userId,
115
+ traits: traits ?? {},
116
+ options: { ...options },
117
+ integrations: globalIntegrations,
118
+ })
119
+ }
120
+
121
+ group(
122
+ groupId: ID,
123
+ traits?: GroupTraits,
124
+ options?: CoreOptions,
125
+ globalIntegrations?: Integrations
126
+ ): CoreCustomerioEvent {
127
+ return this.normalize({
128
+ ...this.baseEvent(),
129
+ type: 'group',
130
+ traits: traits ?? {},
131
+ options: { ...options }, // this spreading is intentional
132
+ integrations: { ...globalIntegrations }, //
133
+ groupId,
134
+ })
135
+ }
136
+
137
+ alias(
138
+ to: string,
139
+ from: string | null, // TODO: can we make this undefined?
140
+ options?: CoreOptions,
141
+ globalIntegrations?: Integrations
142
+ ): CoreCustomerioEvent {
143
+ const base: CoreCustomerioEvent = {
144
+ userId: to,
145
+ type: 'alias',
146
+ options: { ...options },
147
+ integrations: { ...globalIntegrations },
148
+ }
149
+
150
+ if (from !== null) {
151
+ base.previousId = from
152
+ }
153
+
154
+ if (to === undefined) {
155
+ return this.normalize({
156
+ ...base,
157
+ ...this.baseEvent(),
158
+ })
159
+ }
160
+
161
+ return this.normalize({
162
+ ...this.baseEvent(),
163
+ ...base,
164
+ })
165
+ }
166
+
167
+ private baseEvent(): Partial<CoreCustomerioEvent> {
168
+ const base: Partial<CoreCustomerioEvent> = {
169
+ integrations: {},
170
+ options: {},
171
+ }
172
+
173
+ if (!this.user) return base
174
+
175
+ const user = this.user
176
+
177
+ if (user.id()) {
178
+ base.userId = user.id()
179
+ }
180
+
181
+ if (user.anonymousId()) {
182
+ base.anonymousId = user.anonymousId()
183
+ }
184
+
185
+ return base
186
+ }
187
+
188
+ /**
189
+ * Builds the context part of an event based on "foreign" keys that
190
+ * are provided in the `Options` parameter for an Event
191
+ */
192
+ private context(
193
+ options: CoreOptions
194
+ ): [CoreExtraContext, Partial<CoreCustomerioEvent>] {
195
+ type CoreOptionKeys = keyof RemoveIndexSignature<CoreOptions>
196
+ /**
197
+ * If the event options are known keys from this list, we move them to the top level of the event.
198
+ * Any other options are moved to context.
199
+ */
200
+ const eventOverrideKeys: CoreOptionKeys[] = [
201
+ 'userId',
202
+ 'anonymousId',
203
+ 'timestamp',
204
+ ]
205
+
206
+ delete options['integrations']
207
+ const providedOptionsKeys = Object.keys(options) as Exclude<
208
+ CoreOptionKeys,
209
+ 'integrations'
210
+ >[]
211
+
212
+ const context = options.context ?? {}
213
+ const eventOverrides = {}
214
+
215
+ providedOptionsKeys.forEach((key) => {
216
+ if (key === 'context') {
217
+ return
218
+ }
219
+
220
+ if (eventOverrideKeys.includes(key)) {
221
+ dset(eventOverrides, key, options[key])
222
+ } else {
223
+ dset(context, key, options[key])
224
+ }
225
+ })
226
+
227
+ return [context, eventOverrides]
228
+ }
229
+
230
+ public normalize(event: CoreCustomerioEvent): CoreCustomerioEvent {
231
+ const integrationBooleans = Object.keys(event.integrations ?? {}).reduce(
232
+ (integrationNames, name) => {
233
+ return {
234
+ ...integrationNames,
235
+ [name]: Boolean(event.integrations?.[name]),
236
+ }
237
+ },
238
+ {} as Record<string, boolean>
239
+ )
240
+
241
+ // filter out any undefined options
242
+ event.options = pickBy(event.options || {}, (_, value) => {
243
+ return value !== undefined
244
+ })
245
+
246
+ // This is pretty trippy, but here's what's going on:
247
+ // - a) We don't pass initial integration options as part of the event, only if they're true or false
248
+ // - b) We do accept per integration overrides (like integrations.Amplitude.sessionId) at the event level
249
+ // Hence the need to convert base integration options to booleans, but maintain per event integration overrides
250
+ const allIntegrations = {
251
+ // Base config integrations object as booleans
252
+ ...integrationBooleans,
253
+
254
+ // Per event overrides, for things like amplitude sessionId, for example
255
+ ...event.options?.integrations,
256
+ }
257
+
258
+ const [context, overrides] = event.options
259
+ ? this.context(event.options)
260
+ : []
261
+
262
+ const { options, ...rest } = event
263
+
264
+ const body = {
265
+ timestamp: new Date(),
266
+ ...rest,
267
+ integrations: allIntegrations,
268
+ context,
269
+ ...overrides,
270
+ }
271
+
272
+ const evt: CoreCustomerioEvent = {
273
+ ...body,
274
+ messageId: this.createMessageId(),
275
+ }
276
+
277
+ validateEvent(evt)
278
+ return evt
279
+ }
280
+ }