From 9ece2726de257ecd594d601ef098bea746858133 Mon Sep 17 00:00:00 2001 From: Brody McKee Date: Thu, 26 Sep 2024 04:04:50 +1000 Subject: [PATCH] Add support for `exactOptionalPropertyTypes` (#1156) --- .changeset/stupid-bikes-kiss.md | 6 + packages/core/src/events/interfaces.ts | 380 +++++++++++++------------ packages/node/src/app/types/params.ts | 62 ++-- 3 files changed, 238 insertions(+), 210 deletions(-) create mode 100644 .changeset/stupid-bikes-kiss.md diff --git a/.changeset/stupid-bikes-kiss.md b/.changeset/stupid-bikes-kiss.md new file mode 100644 index 000000000..fd5ab0bdf --- /dev/null +++ b/.changeset/stupid-bikes-kiss.md @@ -0,0 +1,6 @@ +--- +'@segment/analytics-core': minor +'@segment/analytics-node': minor +--- + +Improves support for TypeScript's `exactOptionalPropertyTypes` by allowing `undefined` to be passed to optional properties. diff --git a/packages/core/src/events/interfaces.ts b/packages/core/src/events/interfaces.ts index 0db091874..c4bfe85df 100644 --- a/packages/core/src/events/interfaces.ts +++ b/packages/core/src/events/interfaces.ts @@ -22,23 +22,23 @@ export type JSONArray = JSONValue[] export type EventProperties = Record export type Integrations = { - All?: boolean + All?: boolean | undefined [integration: string]: boolean | JSONObject | undefined } export interface CoreOptions { - integrations?: Integrations - timestamp?: Timestamp - context?: CoreExtraContext - anonymousId?: string - userId?: string - traits?: Traits + integrations?: Integrations | undefined + timestamp?: Timestamp | undefined + context?: CoreExtraContext | undefined + anonymousId?: string | undefined + userId?: string | undefined + traits?: Traits | undefined /** * Override the messageId. Under normal circumstances, this is not recommended -- but neccessary for deduping events. * * **Currently, This option only works in `@segment/analytics-node`.** */ - messageId?: string + messageId?: string | undefined // ugh, this is ugly, but we allow literally any property to be passed to options (which get spread onto the event) [key: string]: any } @@ -51,101 +51,111 @@ export interface CoreExtraContext { /** * This is usually used to flag an .identify() call to just update the trait, rather than "last seen". */ - active?: boolean + active?: boolean | undefined /** * Current user's IP address. */ - ip?: string + ip?: string | undefined /** * Locale string for the current user, for example en-US. * @example en-US */ - locale?: string + locale?: string | undefined /** * Dictionary of information about the user’s current location. */ - location?: { - /** - * @example San Francisco - */ - city?: string - /** - * @example United States - */ - country?: string - /** - * @example 40.2964197 - */ - latitude?: string - /** - * @example -76.9411617 - */ - longitude?: string - /** - * @example CA - */ - region?: string - /** - * @example 100 - */ - speed?: number - } + location?: + | { + /** + * @example San Francisco + */ + city?: string | undefined + /** + * @example United States + */ + country?: string | undefined + /** + * @example 40.2964197 + */ + latitude?: string | undefined + /** + * @example -76.9411617 + */ + longitude?: string | undefined + /** + * @example CA + */ + region?: string | undefined + /** + * @example 100 + */ + speed?: number | undefined + } + | undefined /** * Dictionary of information about the current web page. */ - page?: { - /** - * @example /academy/ - */ - path?: string - /** - * @example https://www.foo.com/ - */ - referrer?: string - /** - * @example projectId=123 - */ - search?: string - /** - * @example Analytics Academy - */ - title?: string - /** - * @example https://segment.com/academy/ - */ - url?: string - } + page?: + | { + /** + * @example /academy/ + */ + path?: string | undefined + /** + * @example https://www.foo.com/ + */ + referrer?: string | undefined + /** + * @example projectId=123 + */ + search?: string | undefined + /** + * @example Analytics Academy + */ + title?: string | undefined + /** + * @example https://segment.com/academy/ + */ + url?: string | undefined + } + | undefined /** * User agent of the device making the request. */ - userAgent?: string + userAgent?: string | undefined /** * User agent data returned by the Client Hints API */ - userAgentData?: { - brands?: { - brand: string - version: string - }[] - mobile?: boolean - platform?: string - architecture?: string - bitness?: string - model?: string - platformVersion?: string - /** @deprecated in favour of fullVersionList */ - uaFullVersion?: string - fullVersionList?: { - brand: string - version: string - }[] - wow64?: boolean - } + userAgentData?: + | { + brands?: + | { + brand: string + version: string + }[] + | undefined + mobile?: boolean | undefined + platform?: string | undefined + architecture?: string | undefined + bitness?: string | undefined + model?: string | undefined + platformVersion?: string | undefined + /** @deprecated in favour of fullVersionList */ + uaFullVersion?: string | undefined + fullVersionList?: + | { + brand: string + version: string + }[] + | undefined + wow64?: boolean | undefined + } + | undefined /** * Information about the current library. @@ -154,91 +164,97 @@ export interface CoreExtraContext { * * This type should probably be "never" */ - library?: { - /** - * @example analytics-node-next/latest - */ - name: string - /** - * @example "1.43.1" - */ - version: string - } + library?: + | { + /** + * @example analytics-node-next/latest + */ + name: string + /** + * @example "1.43.1" + */ + version: string + } + | undefined /** * This is useful in cases where you need to track an event, * but also associate information from a previous identify call. * You should fill this object the same way you would fill traits in an identify call. */ - traits?: Traits + traits?: Traits | undefined /** * Dictionary of information about the campaign that resulted in the API call, containing name, source, medium, term, content, and any other custom UTM parameter. */ - campaign?: Campaign + campaign?: Campaign | undefined /** * Dictionary of information about the way the user was referred to the website or app. */ - referrer?: { - type?: string - name?: string - url?: string - link?: string - - id?: string // undocumented - btid?: string // undocumented? - urid?: string // undocumented? - } - - amp?: { - // undocumented? - id: string - } + referrer?: + | { + type?: string | undefined + name?: string | undefined + url?: string | undefined + link?: string | undefined + + id?: string | undefined // undocumented + btid?: string | undefined // undocumented? + urid?: string | undefined // undocumented? + } + | undefined + + amp?: + | { + // undocumented? + id: string + } + | undefined [key: string]: any } export interface CoreSegmentEvent { - messageId?: string + messageId?: string | undefined type: SegmentEventType // page specific - category?: string - name?: string + category?: string | undefined + name?: string | undefined - properties?: EventProperties + properties?: EventProperties | undefined - traits?: Traits // Traits is only defined in 'identify' and 'group', even if it can be passed in other calls. + traits?: Traits | undefined // Traits is only defined in 'identify' and 'group', even if it can be passed in other calls. - integrations?: Integrations - context?: CoreExtraContext - options?: CoreOptions + integrations?: Integrations | undefined + context?: CoreExtraContext | undefined + options?: CoreOptions | undefined - userId?: ID - anonymousId?: ID - groupId?: ID - previousId?: ID + userId?: ID | undefined + anonymousId?: ID | undefined + groupId?: ID | undefined + previousId?: ID | undefined - event?: string + event?: string | undefined - writeKey?: string + writeKey?: string | undefined - sentAt?: Date + sentAt?: Date | undefined - _metadata?: SegmentEventMetadata + _metadata?: SegmentEventMetadata | undefined - timestamp?: Timestamp + timestamp?: Timestamp | undefined } export interface SegmentEventMetadata { - failedInitializations?: unknown[] - bundled?: string[] - unbundled?: string[] - nodeVersion?: string - bundledConfigIds?: string[] - unbundledConfigIds?: string[] - bundledIds?: string[] + failedInitializations?: unknown[] | undefined + bundled?: string[] | undefined + unbundled?: string[] | undefined + nodeVersion?: string | undefined + bundledConfigIds?: string[] | undefined + unbundledConfigIds?: string[] | undefined + bundledIds?: string[] | undefined } export type Timestamp = Date | string @@ -247,9 +263,9 @@ export type Timestamp = Date | string * A Plan allows users to specify events and which destinations they would like them to be sent to */ export interface Plan { - track?: TrackPlan - identify?: TrackPlan - group?: TrackPlan + track?: TrackPlan | undefined + identify?: TrackPlan | undefined + group?: TrackPlan | undefined } export interface TrackPlan { @@ -266,9 +282,11 @@ export interface PlanEvent { /** * Which integrations the plan event applies to */ - integrations?: { - [key: string]: boolean - } + integrations?: + | { + [key: string]: boolean + } + | undefined } type DbId = string | number // TODO: the docs says that this can only be a string? @@ -283,60 +301,60 @@ type BaseGroupTraits = DeepNullable<{ /** * Street address of a group. */ - address?: BaseUserTraits['address'] + address?: BaseUserTraits['address'] | undefined /** * URL to an avatar image for the group. */ - avatar?: BaseUserTraits['avatar'] + avatar?: BaseUserTraits['avatar'] | undefined /** * Date the group's account was first created. Segment recommends ISO-8601 date strings. */ - createdAt?: BaseUserTraits['createdAt'] + createdAt?: BaseUserTraits['createdAt'] | undefined /** * Description of a group */ - description?: BaseUserTraits['description'] + description?: BaseUserTraits['description'] | undefined /** * Email address of group. */ - email?: BaseUserTraits['email'] + email?: BaseUserTraits['email'] | undefined /** * Number of employees of a group, typically used for companies. */ - employees?: string | number // TODO: the docs says that this must be a string? + employees?: string | number | undefined // TODO: the docs says that this must be a string? /** * Unique ID in your database for a group. */ - id?: BaseUserTraits['id'] + id?: BaseUserTraits['id'] | undefined /** * Industry a group is part of. */ - industry?: BaseUserTraits['industry'] + industry?: BaseUserTraits['industry'] | undefined /** * Name of a group. */ - name?: BaseUserTraits['name'] + name?: BaseUserTraits['name'] | undefined /** * Phone number of a group */ - phone?: BaseUserTraits['phone'] + phone?: BaseUserTraits['phone'] | undefined /** * Website of a group. */ - website?: BaseUserTraits['website'] + website?: BaseUserTraits['website'] | undefined /** * Plan that a group is in. */ - plan?: BaseUserTraits['plan'] + plan?: BaseUserTraits['plan'] | undefined }> /** @@ -348,111 +366,115 @@ type BaseUserTraits = DeepNullable<{ /** * Unique ID in your database for a user */ - id?: DbId + id?: DbId | undefined /** * Industry a user works in */ - industry?: string + industry?: string | undefined /** * First name of a user. */ - firstName?: string + firstName?: string | undefined /** * Last name of a user. */ - lastName?: string + lastName?: string | undefined /** * Full name of a user. If you only pass a first and last name Segment automatically fills in the full name for you. */ - name?: string + name?: string | undefined /** * Phone number of a user */ - phone?: PhoneNumber + phone?: PhoneNumber | undefined /** * Title of a user, usually related to their position at a specific company. * @example VP of Engineering */ - title?: string + title?: string | undefined /** * User's username. This should be unique to each user, like the usernames of Twitter or GitHub. */ - username?: string + username?: string | undefined /** * Website of a user. */ - website?: string + website?: string | undefined /** * Street address of a user. */ - address?: { - city?: string - country?: string - postalCode?: string - state?: string - street?: string - } + address?: + | { + city?: string | undefined + country?: string | undefined + postalCode?: string | undefined + state?: string | undefined + street?: string | undefined + } + | undefined /** * Age of a user. */ - age?: number + age?: number | undefined /** * URL to an avatar image for the user. */ - avatar?: string + avatar?: string | undefined /** * User's birthday. */ - birthday?: Timestamp + birthday?: Timestamp | undefined /** * User's company. */ - company?: { - name?: string - id?: DbId - industry?: BaseUserTraits['industry'] - employee_count?: number - plan?: BaseUserTraits['plan'] - } + company?: + | { + name?: string | undefined + id?: DbId | undefined + industry?: BaseUserTraits['industry'] | undefined + employee_count?: number | undefined + plan?: BaseUserTraits['plan'] | undefined + } + | undefined /** Plan that a user is in. * @example enterprise */ - plan?: string + plan?: string | undefined /** * Date the user's account was first created. Segment recommends using ISO-8601 date strings. */ - createdAt?: Timestamp + createdAt?: Timestamp | undefined /** * Description of user, such as bio. */ - description?: string + description?: string | undefined /** * Email address of a user. */ - email?: string + email?: string | undefined /** * @example female */ - gender?: string + gender?: string | undefined }> /** diff --git a/packages/node/src/app/types/params.ts b/packages/node/src/app/types/params.ts index 983be3b2b..88dd3d247 100644 --- a/packages/node/src/app/types/params.ts +++ b/packages/node/src/app/types/params.ts @@ -19,22 +19,22 @@ export interface ExtraContext extends CoreExtraContext {} * An ID associated with the user. Note: at least one of userId or anonymousId must be included. **/ type IdentityOptions = - | { userId: string; anonymousId?: string } - | { userId?: string; anonymousId: string } + | { userId: string; anonymousId?: string | undefined } + | { userId?: string | undefined; anonymousId: string } export type AliasParams = { /* The new user id you want to associate with the user. */ userId: string /* The previous id that the user was recognized by (this can be either a userId or an anonymousId). */ previousId: string - context?: ExtraContext - timestamp?: Timestamp - integrations?: Integrations + context?: ExtraContext | undefined + timestamp?: Timestamp | undefined + integrations?: Integrations | undefined /** * Override the default messageId for the purposes of deduping events. Using a uuid library is strongly encouraged. * @link https://segment.com/docs/partners/faqs/#does-segment-de-dupe-messages */ - messageId?: string + messageId?: string | undefined } export type GroupParams = { @@ -44,15 +44,15 @@ export type GroupParams = { * This interface represents reserved traits that Segment has standardized. * @link https://segment.com/docs/connections/spec/group/#traits */ - traits?: GroupTraits - context?: ExtraContext - timestamp?: Timestamp - integrations?: Integrations + traits?: GroupTraits | undefined + context?: ExtraContext | undefined + timestamp?: Timestamp | undefined + integrations?: Integrations | undefined /** * Override the default messageId for the purposes of deduping events. Using a uuid library is strongly encouraged. * @link https://segment.com/docs/partners/faqs/#does-segment-de-dupe-messages */ - messageId?: string + messageId?: string | undefined } & IdentityOptions export type IdentifyParams = { @@ -61,61 +61,61 @@ export type IdentifyParams = { * This interface represents reserved traits that Segment has standardized. * @link https://segment.com/docs/connections/spec/group/#traits */ - traits?: UserTraits - context?: ExtraContext - timestamp?: Timestamp - integrations?: Integrations + traits?: UserTraits | undefined + context?: ExtraContext | undefined + timestamp?: Timestamp | undefined + integrations?: Integrations | undefined /** * Override the default messageId for the purposes of deduping events. Using a uuid library is strongly encouraged. * @link https://segment.com/docs/partners/faqs/#does-segment-de-dupe-messages */ - messageId?: string + messageId?: string | undefined } & IdentityOptions export type PageParams = { /* The category of the page. Useful for cases like ecommerce where many pages might live under a single category. */ - category?: string + category?: string | undefined /* The name of the page.*/ - name?: string + name?: string | undefined /* A dictionary of properties of the page. */ - properties?: EventProperties - timestamp?: Timestamp - context?: ExtraContext - integrations?: Integrations + properties?: EventProperties | undefined + timestamp?: Timestamp | undefined + context?: ExtraContext | undefined + integrations?: Integrations | undefined /** * Override the default messageId for the purposes of deduping events. Using a uuid library is strongly encouraged. * @link https://segment.com/docs/partners/faqs/#does-segment-de-dupe-messages */ - messageId?: string + messageId?: string | undefined } & IdentityOptions export type TrackParams = { event: string - properties?: EventProperties - context?: ExtraContext - timestamp?: Timestamp - integrations?: Integrations + properties?: EventProperties | undefined + context?: ExtraContext | undefined + timestamp?: Timestamp | undefined + integrations?: Integrations | undefined /** * Override the default messageId for the purposes of deduping events. Using a uuid library is strongly encouraged. * @link https://segment.com/docs/partners/faqs/#does-segment-de-dupe-messages */ - messageId?: string + messageId?: string | undefined } & IdentityOptions export type FlushParams = { /** * Max time in milliseconds to wait until the resulting promise resolves. */ - timeout?: number + timeout?: number | undefined /** * If true, will prevent new events from entering the pipeline. Default: false */ - close?: boolean + close?: boolean | undefined } export type CloseAndFlushParams = { /** * Max time in milliseconds to wait until the resulting promise resolves. */ - timeout?: FlushParams['timeout'] + timeout?: FlushParams['timeout'] | undefined }