Skip to content

Commit 879cffe

Browse files
committed
Make trait and extra context fields nullable
1 parent ec568e5 commit 879cffe

File tree

7 files changed

+113
-41
lines changed

7 files changed

+113
-41
lines changed

.buildkite/pipeline.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ steps:
9999
- echo "+++ Run tests"
100100
- yarn run -T core lint
101101
- yarn run -T core test
102+
- yarn turbo run --filter=core-integration-tests lint
102103
plugins:
103104
- ssh://[email protected]/segmentio/cache-buildkite-plugin#v2.0.0:
104105
key: "v1.1-cache-dev-{{ checksum 'yarn.lock' }}"

.changeset/hip-parents-smell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@segment/analytics-core': patch
3+
---
4+
5+
Make trait and extra context fields nullable.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@
2323
"release": "yarn clean && yarn build --force && changeset publish && git push origin HEAD:master --follow-tags --no-verify",
2424
"version-run-all": "yarn workspaces foreach -vpt --no-private run version",
2525
"core": "yarn workspace @segment/analytics-core",
26-
"core+deps": "turbo run --filter=@segment/analytics-core...",
26+
"core+deps": "turbo run --filter=@segment/analytics-core",
2727
"browser": "yarn workspace @segment/analytics-next",
28-
"browser+deps": "turbo run --filter=@segment/analytics-next...",
28+
"browser+deps": "turbo run --filter=@segment/analytics-next",
2929
"node": "yarn workspace @segment/analytics-node",
30-
"node+deps": "turbo run --filter=@segment/analytics-node...",
30+
"node+deps": "turbo run --filter=@segment/analytics-node",
3131
"clean": "bash scripts/clean.sh"
3232
},
3333
"packageManager": "[email protected]",

packages/browser/src/browser/__tests__/typedef-tests/analytics-browser.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,34 @@ export default {
119119
assertNotAny(analytics)
120120
assertIs<AnalyticsBrowser>(analytics)
121121
},
122-
'Should error if there is a type conflict in Traits': () => {
123-
const analytics = new AnalyticsBrowser().load({ writeKey: 'foo' })
124-
assertNotAny(analytics)
125-
assertIs<AnalyticsBrowser>(analytics)
126122

127-
// @ts-expect-error - id should be a string
128-
void analytics.identify('foo', { id: 123 })
123+
'Should accept traits': () => {
124+
const analytics = {} as AnalyticsBrowser
125+
126+
class Foo {
127+
name = 'hello'
128+
toJSON() {
129+
return this.name
130+
}
131+
}
132+
void analytics.identify('foo', new Foo())
133+
void analytics.identify('foo', { address: null })
134+
// @ts-expect-error - Type 'string' has no properties in common with type
135+
void analytics.identify('foo', { address: 'hello' })
136+
// @ts-expect-error - Type 'never[]' is not assignable to type
137+
void analytics.identify('foo', { id: [] })
138+
},
139+
140+
'Should accept optional ExtraContext': () => {
141+
const analytics = {} as AnalyticsBrowser
142+
void analytics.track('foo', undefined, { context: {} })
143+
void analytics.track('foo', undefined, { context: { active: null } })
144+
void analytics.track('foo', undefined, { context: { active: true } })
145+
void analytics.track('foo', undefined, {
146+
context: {
147+
// @ts-expect-error Type 'string' is not assignable to type 'boolean | null | undefined'.ts(2322)
148+
active: 'hello',
149+
},
150+
})
129151
},
130152
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { CoreAnalyticsTraits, CoreExtraContext } from '@segment/analytics-core'
2+
3+
class TestClass {
4+
name = 'hello'
5+
toJSON() {
6+
return this.name
7+
}
8+
}
9+
10+
export default {
11+
'CoreAnalyticsTraits test': () => {
12+
let traits: CoreAnalyticsTraits = {}
13+
14+
// should accept a class
15+
traits = new TestClass()
16+
// should be nullable
17+
traits = { address: null }
18+
// should accept aribtrary properties
19+
traits = { address: {}, foo: 123 }
20+
// should fail with type conflicts
21+
// @ts-expect-error
22+
traits = { address: 'hello' }
23+
},
24+
25+
'CoreExtraContext test': () => {
26+
// should accept a class
27+
let ec: CoreExtraContext = {}
28+
ec = new TestClass()
29+
// should be nullable
30+
ec = { page: null }
31+
ec = { page: { path: null } }
32+
// should error if type conflict
33+
// @ts-expect-error
34+
ec = { page: { path: {} } }
35+
},
36+
}

packages/core/src/events/interfaces.ts

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { CoreContext } from '../context'
22
import { ID } from '../user'
3+
import { DeepNullable } from '../utils/ts-helpers'
34

45
export type Callback<Ctx extends CoreContext = CoreContext> = (
56
ctx: Ctx
@@ -46,47 +47,47 @@ export interface CoreExtraContext {
4647
/**
4748
* This is usually used to flag an .identify() call to just update the trait, rather than "last seen".
4849
*/
49-
active?: boolean
50+
active?: boolean | null
5051

5152
/**
5253
* Current user's IP address.
5354
*/
54-
ip?: string
55+
ip?: string | null
5556

5657
/**
5758
* Locale string for the current user, for example en-US.
5859
* @example en-US
5960
*/
60-
locale?: string
61+
locale?: string | null
6162
/**
6263
* Dictionary of information about the user’s current location.
6364
*/
6465
location?: {
6566
/**
6667
* @example San Francisco
6768
*/
68-
city?: string
69+
city?: string | null
6970
/**
7071
* @example United States
7172
*/
72-
country?: string
73+
country?: string | null
7374
/**
7475
* @example 40.2964197
7576
*/
76-
latitude?: string
77+
latitude?: string | null
7778
/**
7879
* @example -76.9411617
7980
*/
80-
longitude?: string
81+
longitude?: string | null
8182
/**
8283
* @example CA
8384
*/
84-
region?: string
85+
region?: string | null
8586
/**
8687
* @example 100
8788
*/
88-
speed?: number
89-
}
89+
speed?: number | null
90+
} | null
9091

9192
/**
9293
* Dictionary of information about the current web page.
@@ -95,29 +96,29 @@ export interface CoreExtraContext {
9596
/**
9697
* @example /academy/
9798
*/
98-
path?: string
99+
path?: string | null
99100
/**
100101
* @example https://www.foo.com/
101102
*/
102-
referrer?: string
103+
referrer?: string | null
103104
/**
104105
* @example projectId=123
105106
*/
106-
search?: string
107+
search?: string | null
107108
/**
108109
* @example Analytics Academy
109110
*/
110-
title?: string
111+
title?: string | null
111112
/**
112113
* @example https://segment.com/academy/
113114
*/
114-
url?: string
115-
}
115+
url?: string | null
116+
} | null
116117

117118
/**
118119
* User agent of the device making the request.
119120
*/
120-
userAgent?: string
121+
userAgent?: string | null
121122

122123
/**
123124
* Information about the current library.
@@ -135,14 +136,14 @@ export interface CoreExtraContext {
135136
* @example "1.43.1"
136137
*/
137138
version: string
138-
}
139+
} | null
139140

140141
/**
141142
* This is useful in cases where you need to track an event,
142143
* but also associate information from a previous identify call.
143144
* You should fill this object the same way you would fill traits in an identify call.
144145
*/
145-
traits?: CoreAnalyticsTraits
146+
traits?: CoreAnalyticsTraits | null
146147

147148
/**
148149
* Dictionary of information about the campaign that resulted in the API call, containing name, source, medium, term, content, and any other custom UTM parameter.
@@ -153,26 +154,26 @@ export interface CoreExtraContext {
153154
source: string
154155
medium: string
155156
content: string
156-
}
157+
} | null
157158

158159
/**
159160
* Dictionary of information about the way the user was referred to the website or app.
160161
*/
161162
referrer?: {
162-
type?: string
163-
name?: string
164-
url?: string
165-
link?: string
163+
type?: string | null
164+
name?: string | null
165+
url?: string | null
166+
link?: string | null
166167

167-
id?: string // undocumented
168-
btid?: string // undocumented?
169-
urid?: string // undocumented?
170-
}
168+
id?: string | null // undocumented
169+
btid?: string | null // undocumented?
170+
urid?: string | null // undocumented?
171+
} | null
171172

172173
amp?: {
173174
// undocumented?
174175
id: string
175-
}
176+
} | null
176177

177178
[key: string]: any
178179
}
@@ -255,11 +256,11 @@ export interface PlanEvent {
255256
* @link https://segment.com/docs/connections/spec/identify/#traits
256257
* @link https://segment.com/docs/connections/spec/group/#traits
257258
*/
258-
export interface CoreAnalyticsTraits {
259+
export type CoreAnalyticsTraits = DeepNullable<{
259260
/**
260261
* Unique ID in your database for a user/group.
261262
*/
262-
id?: string
263+
id?: string | number
263264

264265
/**
265266
* Industry a user works in, or a group is part of.
@@ -372,4 +373,4 @@ export interface CoreAnalyticsTraits {
372373
gender?: string
373374

374375
[customTrait: string]: any
375-
}
376+
}>

packages/core/src/utils/ts-helpers.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,10 @@
44
export type RemoveIndexSignature<T> = {
55
[K in keyof T as {} extends Record<K, 1> ? never : K]: T[K]
66
}
7+
8+
/**
9+
* Recursively make all object properties nullable
10+
*/
11+
export type DeepNullable<T> = {
12+
[K in keyof T]: T[K] extends object ? DeepNullable<T[K]> | null : T[K] | null
13+
}

0 commit comments

Comments
 (0)