Skip to content

Commit e0be11c

Browse files
authored
Refactor / break up node types (#712)
1 parent 92ab2fb commit e0be11c

File tree

13 files changed

+159
-128
lines changed

13 files changed

+159
-128
lines changed

packages/node/src/__tests__/graceful-shutdown-integration.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import { performance as perf } from 'perf_hooks'
44
const fetcher = jest.fn()
55
jest.mock('../lib/fetch', () => ({ fetch: fetcher }))
66

7-
import { Analytics, SegmentEvent } from '../app/analytics-node'
7+
import { Analytics } from '../app/analytics-node'
88
import { sleep } from './test-helpers/sleep'
99
import { CoreContext, CorePlugin } from '@segment/analytics-core'
10+
import { SegmentEvent } from '../app/types'
1011

1112
const testPlugin: CorePlugin = {
1213
type: 'after',

packages/node/src/__tests__/typedef-tests.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Analytics } from '../'
1+
import { Analytics, Context, Plugin, Traits } from '../'
22

33
/**
44
* These are general typescript definition tests;
@@ -44,4 +44,15 @@ export default {
4444
previousId: 'foo',
4545
})
4646
},
47+
48+
'context should be exported': () => {
49+
console.log(Context)
50+
},
51+
'plugin should be exported': () => {
52+
interface MyPlugin extends Plugin {}
53+
console.log({} as MyPlugin)
54+
},
55+
'traits should be exported': () => {
56+
console.log({} as Traits)
57+
},
4758
}

packages/node/src/app/analytics-node.ts

Lines changed: 22 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,33 @@
11
import {
2-
EventProperties,
32
CoreAnalytics,
4-
CoreContext,
5-
CorePlugin,
63
EventFactory,
74
EventQueue,
85
CoreSegmentEvent,
96
bindAll,
10-
PriorityQueue,
117
pTimeout,
12-
Integrations,
13-
CoreExtraContext,
14-
CoreAnalyticsTraits,
8+
CoreContext,
159
} from '@segment/analytics-core'
1610
import { AnalyticsSettings, validateSettings } from './settings'
1711
import { version } from '../../package.json'
1812
import { createConfiguredNodePlugin } from '../plugins/segmentio'
19-
import { createNodeEventFactory } from '../lib/create-node-event-factory'
13+
import { NodeEventFactory } from './event-factory'
2014
import { Callback, dispatchAndEmit } from './dispatch-emit'
2115
import { NodeEmitter } from './emitter'
16+
import {
17+
AliasParams,
18+
GroupParams,
19+
IdentifyParams,
20+
PageParams,
21+
TrackParams,
22+
Plugin,
23+
SegmentEvent,
24+
} from './types'
25+
import { NodeEventQueue } from './event-queue'
2226

2327
// create a derived class since we may want to add node specific things to Context later
28+
// While this is not a type, it is a definition
2429
export class Context extends CoreContext {}
2530

26-
export interface Plugin extends CorePlugin {}
27-
type Timestamp = string | Date
28-
29-
/**
30-
* An ID associated with the user. Note: at least one of userId or anonymousId must be included.
31-
**/
32-
type IdentityOptions =
33-
| { userId: string; anonymousId?: string }
34-
| { userId?: string; anonymousId: string }
35-
36-
/**
37-
* A dictionary of extra context to attach to the call.
38-
* Note: context differs from traits because it is not attributes of the user itself.
39-
*/
40-
export interface ExtraContext extends CoreExtraContext {}
41-
42-
/**
43-
* Traits are pieces of information you know about a user that are included in an identify call. These could be demographics like age or gender, account-specific like plan, or even things like whether a user has seen a particular A/B test variation. Up to you!
44-
* Segment has reserved some traits that have semantic meanings for users, and we handle them in special ways. For example, Segment always expects email to be a string of the user’s email address.
45-
*
46-
* We’ll send this on to destinations like Mailchimp that require an email address for their tracking.
47-
*
48-
* You should only use reserved traits for their intended meaning.
49-
*/
50-
export interface Traits extends CoreAnalyticsTraits {}
51-
52-
class NodePriorityQueue extends PriorityQueue<Context> {
53-
constructor() {
54-
super(1, [])
55-
}
56-
// do not use an internal "seen" map
57-
getAttempts(ctx: Context): number {
58-
return ctx.attempts ?? 0
59-
}
60-
updateAttempts(ctx: Context): number {
61-
ctx.attempts = this.getAttempts(ctx) + 1
62-
return this.getAttempts(ctx)
63-
}
64-
}
65-
66-
type SegmentEventType = 'track' | 'page' | 'identify' | 'alias' | 'screen'
67-
68-
export interface SegmentEvent extends CoreSegmentEvent {
69-
type: SegmentEventType
70-
}
71-
7231
export class Analytics extends NodeEmitter implements CoreAnalytics {
7332
private readonly _eventFactory: EventFactory
7433
private _isClosed = false
@@ -86,8 +45,8 @@ export class Analytics extends NodeEmitter implements CoreAnalytics {
8645
super()
8746
validateSettings(settings)
8847

89-
this._eventFactory = createNodeEventFactory()
90-
this._queue = new EventQueue(new NodePriorityQueue())
48+
this._eventFactory = new NodeEventFactory()
49+
this._queue = new NodeEventQueue()
9150

9251
const flushInterval = settings.flushInterval ?? 10000
9352

@@ -161,21 +120,7 @@ export class Analytics extends NodeEmitter implements CoreAnalytics {
161120
* @link https://segment.com/docs/connections/sources/catalog/libraries/server/node/#alias
162121
*/
163122
alias(
164-
{
165-
userId,
166-
previousId,
167-
context,
168-
timestamp,
169-
integrations,
170-
}: {
171-
/* The new user id you want to associate with the user. */
172-
userId: string
173-
/* The previous id that the user was recognized by (this can be either a userId or an anonymousId). */
174-
previousId: string
175-
context?: ExtraContext
176-
timestamp?: Timestamp
177-
integrations?: Integrations
178-
},
123+
{ userId, previousId, context, timestamp, integrations }: AliasParams,
179124
callback?: Callback
180125
): void {
181126
const segmentEvent = this._eventFactory.alias(userId, previousId, {
@@ -199,13 +144,7 @@ export class Analytics extends NodeEmitter implements CoreAnalytics {
199144
traits = {},
200145
context,
201146
integrations,
202-
}: IdentityOptions & {
203-
groupId: string
204-
traits?: Traits
205-
context?: ExtraContext
206-
timestamp?: Timestamp
207-
integrations?: Integrations
208-
},
147+
}: GroupParams,
209148
callback?: Callback
210149
): void {
211150
const segmentEvent = this._eventFactory.group(groupId, traits, {
@@ -224,17 +163,7 @@ export class Analytics extends NodeEmitter implements CoreAnalytics {
224163
* @link https://segment.com/docs/connections/sources/catalog/libraries/server/node/#identify
225164
*/
226165
identify(
227-
{
228-
userId,
229-
anonymousId,
230-
traits = {},
231-
context,
232-
integrations,
233-
}: IdentityOptions & {
234-
traits?: Traits
235-
context?: ExtraContext
236-
integrations?: Integrations
237-
},
166+
{ userId, anonymousId, traits = {}, context, integrations }: IdentifyParams,
238167
callback?: Callback
239168
): void {
240169
const segmentEvent = this._eventFactory.identify(userId, traits, {
@@ -260,17 +189,7 @@ export class Analytics extends NodeEmitter implements CoreAnalytics {
260189
context,
261190
timestamp,
262191
integrations,
263-
}: IdentityOptions & {
264-
/* The category of the page. Useful for cases like ecommerce where many pages might live under a single category. */
265-
category?: string
266-
/* The name of the page.*/
267-
name?: string
268-
/* A dictionary of properties of the page. */
269-
properties?: EventProperties
270-
timestamp?: Timestamp
271-
context?: ExtraContext
272-
integrations?: Integrations
273-
},
192+
}: PageParams,
274193
callback?: Callback
275194
): void {
276195
const segmentEvent = this._eventFactory.page(
@@ -298,7 +217,7 @@ export class Analytics extends NodeEmitter implements CoreAnalytics {
298217
context,
299218
timestamp,
300219
integrations,
301-
}: Parameters<Analytics['page']>[0],
220+
}: PageParams,
302221
callback?: Callback
303222
): void {
304223
const segmentEvent = this._eventFactory.screen(
@@ -324,13 +243,7 @@ export class Analytics extends NodeEmitter implements CoreAnalytics {
324243
context,
325244
timestamp,
326245
integrations,
327-
}: IdentityOptions & {
328-
event: string
329-
properties?: EventProperties
330-
context?: ExtraContext
331-
timestamp?: Timestamp
332-
integrations?: Integrations
333-
},
246+
}: TrackParams,
334247
callback?: Callback
335248
): void {
336249
const segmentEvent = this._eventFactory.track(event, properties, {
@@ -348,7 +261,7 @@ export class Analytics extends NodeEmitter implements CoreAnalytics {
348261
* Registers one or more plugins to augment Analytics functionality.
349262
* @param plugins
350263
*/
351-
async register(...plugins: CorePlugin<any, any>[]): Promise<void> {
264+
async register(...plugins: Plugin[]): Promise<void> {
352265
return this._queue.criticalTasks.run(async () => {
353266
const ctx = Context.system()
354267

packages/node/src/app/emitter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { CoreEmitterContract, Emitter } from '@segment/analytics-core'
2-
import type { SegmentEvent, Context } from './analytics-node'
2+
import { Context } from './analytics-node'
33
import type { AnalyticsSettings } from './settings'
4+
import { SegmentEvent } from './types'
45

56
/**
67
* Map of emitter event names to method args.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { EventFactory } from '@segment/analytics-core'
2+
import { createMessageId } from '../lib/get-message-id'
3+
4+
export class NodeEventFactory extends EventFactory {
5+
constructor() {
6+
super({ createMessageId })
7+
}
8+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { EventQueue, PriorityQueue } from '@segment/analytics-core'
2+
import type { Context } from './analytics-node'
3+
4+
class NodePriorityQueue extends PriorityQueue<Context> {
5+
constructor() {
6+
super(1, [])
7+
}
8+
// do not use an internal "seen" map
9+
getAttempts(ctx: Context): number {
10+
return ctx.attempts ?? 0
11+
}
12+
updateAttempts(ctx: Context): number {
13+
ctx.attempts = this.getAttempts(ctx) + 1
14+
return this.getAttempts(ctx)
15+
}
16+
}
17+
18+
export class NodeEventQueue extends EventQueue {
19+
constructor() {
20+
super(new NodePriorityQueue())
21+
}
22+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './params'
2+
export * from './segment-event'
3+
export * from './plugin'
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import type {
2+
CoreAnalyticsTraits,
3+
CoreExtraContext,
4+
EventProperties,
5+
Integrations,
6+
Timestamp,
7+
} from '@segment/analytics-core'
8+
9+
/**
10+
* Traits are pieces of information you know about a user that are included in an identify call. These could be demographics like age or gender, account-specific like plan, or even things like whether a user has seen a particular A/B test variation. Up to you!
11+
* Segment has reserved some traits that have semantic meanings for users, and we handle them in special ways. For example, Segment always expects email to be a string of the user’s email address.
12+
*
13+
* We’ll send this on to destinations like Mailchimp that require an email address for their tracking.
14+
*
15+
* You should only use reserved traits for their intended meaning.
16+
*/
17+
export interface Traits extends CoreAnalyticsTraits {}
18+
19+
/**
20+
* A dictionary of extra context to attach to the call.
21+
* Note: context differs from traits because it is not attributes of the user itself.
22+
*/
23+
export interface ExtraContext extends CoreExtraContext {}
24+
25+
/**
26+
* An ID associated with the user. Note: at least one of userId or anonymousId must be included.
27+
**/
28+
type IdentityOptions =
29+
| { userId: string; anonymousId?: string }
30+
| { userId?: string; anonymousId: string }
31+
32+
export type AliasParams = {
33+
/* The new user id you want to associate with the user. */
34+
userId: string
35+
/* The previous id that the user was recognized by (this can be either a userId or an anonymousId). */
36+
previousId: string
37+
context?: ExtraContext
38+
timestamp?: Timestamp
39+
integrations?: Integrations
40+
}
41+
42+
export type GroupParams = IdentityOptions & {
43+
groupId: string
44+
traits?: Traits
45+
context?: ExtraContext
46+
timestamp?: Timestamp
47+
integrations?: Integrations
48+
}
49+
50+
export type IdentifyParams = IdentityOptions & {
51+
traits?: Traits
52+
context?: ExtraContext
53+
integrations?: Integrations
54+
}
55+
56+
export type PageParams = IdentityOptions & {
57+
/* The category of the page. Useful for cases like ecommerce where many pages might live under a single category. */
58+
category?: string
59+
/* The name of the page.*/
60+
name?: string
61+
/* A dictionary of properties of the page. */
62+
properties?: EventProperties
63+
timestamp?: Timestamp
64+
context?: ExtraContext
65+
integrations?: Integrations
66+
}
67+
68+
export type TrackParams = IdentityOptions & {
69+
event: string
70+
properties?: EventProperties
71+
context?: ExtraContext
72+
timestamp?: Timestamp
73+
integrations?: Integrations
74+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import type { CorePlugin } from '@segment/analytics-core'
2+
3+
export interface Plugin extends CorePlugin {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { CoreSegmentEvent } from '@segment/analytics-core'
2+
3+
type SegmentEventType = 'track' | 'page' | 'identify' | 'alias' | 'screen'
4+
5+
export interface SegmentEvent extends CoreSegmentEvent {
6+
type: SegmentEventType
7+
}

0 commit comments

Comments
 (0)