Skip to content

Commit feced1c

Browse files
committed
Make trait nullable and type group vs params
revert name ,# ,# Changes to be committed:
1 parent ec568e5 commit feced1c

File tree

15 files changed

+254
-71
lines changed

15 files changed

+254
-71
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://git@github.com/segmentio/cache-buildkite-plugin#v2.0.0:
104105
key: "v1.1-cache-dev-{{ checksum 'yarn.lock' }}"

.changeset/hip-parents-smell.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@segment/analytics-core': minor
3+
'@segment/analytics-next': minor
4+
'@segment/analytics-node': minor
5+
---
6+
7+
Make trait fields nullable. Type traits for group() differently than identify() call.

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": "yarn@3.2.1",

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

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,33 @@ 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: true } })
144+
void analytics.track('foo', undefined, {
145+
context: {
146+
// @ts-expect-error Type 'string' is not assignable to type 'boolean | null | undefined'.ts(2322)
147+
active: 'hello',
148+
},
149+
})
129150
},
130151
}

packages/browser/src/core/analytics/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import {
22
AliasParams,
33
DispatchedEvent,
44
EventParams,
5+
GroupParams,
56
PageParams,
67
resolveAliasArguments,
78
resolveArguments,
89
resolvePageArguments,
910
resolveUserArguments,
10-
UserParams,
11+
IdentifyParams,
1112
} from '../arguments-resolver'
1213
import type { FormArgs, LinkArgs } from '../auto-track'
1314
import { isOffline } from '../connection'
@@ -202,7 +203,7 @@ export class Analytics
202203
})
203204
}
204205

205-
async identify(...args: UserParams): Promise<DispatchedEvent> {
206+
async identify(...args: IdentifyParams): Promise<DispatchedEvent> {
206207
const [id, _traits, options, callback] = resolveUserArguments(this._user)(
207208
...args
208209
)
@@ -227,8 +228,8 @@ export class Analytics
227228
}
228229

229230
group(): Group
230-
group(...args: UserParams): Promise<DispatchedEvent>
231-
group(...args: UserParams): Promise<DispatchedEvent> | Group {
231+
group(...args: GroupParams): Promise<DispatchedEvent>
232+
group(...args: GroupParams): Promise<DispatchedEvent> | Group {
232233
if (args.length === 0) {
233234
return this._group
234235
}

packages/browser/src/core/analytics/interfaces.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import type {
44
EventParams,
55
DispatchedEvent,
66
PageParams,
7-
UserParams,
7+
IdentifyParams,
88
AliasParams,
9+
GroupParams,
910
} from '../arguments-resolver'
1011
import type { Context } from '../context'
1112
import type { SegmentEvent } from '../events'
@@ -78,9 +79,9 @@ export interface AnalyticsClassic extends AnalyticsClassicStubs {
7879
export interface AnalyticsCore extends CoreAnalytics {
7980
track(...args: EventParams): Promise<DispatchedEvent>
8081
page(...args: PageParams): Promise<DispatchedEvent>
81-
identify(...args: UserParams): Promise<DispatchedEvent>
82+
identify(...args: IdentifyParams): Promise<DispatchedEvent>
8283
group(): Group
83-
group(...args: UserParams): Promise<DispatchedEvent>
84+
group(...args: GroupParams): Promise<DispatchedEvent>
8485
alias(...args: AliasParams): Promise<DispatchedEvent>
8586
screen(...args: PageParams): Promise<DispatchedEvent>
8687
register(...plugins: Plugin[]): Promise<Context>
@@ -94,6 +95,6 @@ export interface AnalyticsCore extends CoreAnalytics {
9495
*/
9596
export type AnalyticsBrowserCore = Omit<AnalyticsCore, 'group' | 'user'> & {
9697
group(): Promise<Group>
97-
group(...args: UserParams): Promise<DispatchedEvent>
98+
group(...args: GroupParams): Promise<DispatchedEvent>
9899
user(): Promise<User>
99100
}

packages/browser/src/core/arguments-resolver/index.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
EventProperties,
1313
SegmentEvent,
1414
Traits,
15+
GroupTraits,
16+
UserTraits,
1517
} from '../events'
1618
import { ID, User } from '../user'
1719

@@ -105,8 +107,10 @@ export function resolvePageArguments(
105107
/**
106108
* Helper for group, identify methods
107109
*/
108-
export const resolveUserArguments = (user: User): ResolveUser => {
109-
return (...args): ReturnType<ResolveUser> => {
110+
export const resolveUserArguments = <T extends Traits, U extends User>(
111+
user: U
112+
): ResolveUser<T> => {
113+
return (...args): ReturnType<ResolveUser<T>> => {
110114
let id: string | ID | null = null
111115
id = args.find(isString) ?? args.find(isNumber)?.toString() ?? user.id()
112116

@@ -117,7 +121,7 @@ export const resolveUserArguments = (user: User): ResolveUser => {
117121
return isPlainObject(obj) || obj === null
118122
}) as Array<Traits | null>
119123

120-
const traits = (objects[0] ?? {}) as Traits
124+
const traits = (objects[0] ?? {}) as T
121125
const opts = (objects[1] ?? {}) as Options
122126

123127
const resolvedCallback = args.find(isFunction) as Callback | undefined
@@ -146,14 +150,15 @@ export function resolveAliasArguments(
146150
return [aliasTo, aliasFrom, opts, resolvedCallback]
147151
}
148152

149-
type ResolveUser = (
153+
type ResolveUser<T extends Traits> = (
150154
id?: ID | object,
151-
traits?: Traits | Callback | null,
155+
traits?: T | Callback | null,
152156
options?: Options | Callback,
153157
callback?: Callback
154-
) => [ID, Traits, Options, Callback | undefined]
158+
) => [ID, T, Options, Callback | undefined]
155159

156-
export type UserParams = Parameters<ResolveUser>
160+
export type IdentifyParams = Parameters<ResolveUser<UserTraits>>
161+
export type GroupParams = Parameters<ResolveUser<GroupTraits>>
157162
export type EventParams = Parameters<typeof resolveArguments>
158163
export type PageParams = Parameters<typeof resolvePageArguments>
159164
export type AliasParams = Parameters<typeof resolveAliasArguments>

packages/browser/src/core/events/interfaces.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {
2-
CoreAnalyticsTraits,
2+
CoreTraits,
33
CoreOptions,
44
CoreSegmentEvent,
55
Callback,
@@ -11,12 +11,27 @@ import {
1111
JSONValue,
1212
JSONPrimitive,
1313
JSONObject,
14+
CoreGroupTraits,
15+
CoreUserTraits,
1416
} from '@segment/analytics-core'
1517

1618
export interface Options extends CoreOptions {}
1719

18-
// This is not ideal, but it works with all the edge cases
19-
export interface Traits extends CoreAnalyticsTraits {}
20+
export interface Traits extends CoreTraits {}
21+
22+
/**
23+
* Traits are pieces of information you know about a group.
24+
* This interface represents reserved traits that Segment has standardized.
25+
* @link https://segment.com/docs/connections/spec/group/#traits
26+
*/
27+
export interface GroupTraits extends CoreGroupTraits {}
28+
29+
/**
30+
* Traits are pieces of information you know about a user.
31+
* This interface represents reserved traits that Segment has standardized.
32+
* @link https://segment.com/docs/connections/spec/identify/#traits
33+
*/
34+
export interface UserTraits extends CoreUserTraits {}
2035

2136
export type EventProperties = Record<string, any>
2237

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { CoreTraits, 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+
'CoreTraits test': () => {
12+
let traits: CoreTraits = {}
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 error if type conflict
30+
// @ts-expect-error
31+
ec = { page: { path: {} } }
32+
},
33+
}

packages/core/src/events/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import { ID, User } from '../user'
44
import {
55
Integrations,
66
EventProperties,
7-
CoreAnalyticsTraits,
87
CoreSegmentEvent,
98
CoreOptions,
109
CoreExtraContext,
10+
CoreUserTraits,
11+
CoreGroupTraits,
1112
} from './interfaces'
1213
import { pickBy } from '../utils/pick'
1314
import { validateEvent } from '../validation/assertions'
@@ -103,7 +104,7 @@ export class EventFactory {
103104

104105
identify(
105106
userId: ID,
106-
traits?: CoreAnalyticsTraits,
107+
traits?: CoreUserTraits,
107108
options?: CoreOptions,
108109
globalIntegrations?: Integrations
109110
): CoreSegmentEvent {
@@ -119,7 +120,7 @@ export class EventFactory {
119120

120121
group(
121122
groupId: ID,
122-
traits?: CoreAnalyticsTraits,
123+
traits?: CoreGroupTraits,
123124
options?: CoreOptions,
124125
globalIntegrations?: Integrations
125126
): CoreSegmentEvent {

0 commit comments

Comments
 (0)