Last active
October 6, 2025 01:45
-
-
Save jmakeig/d8db1a0c517070bc3013777242d3fa08 to your computer and use it in GitHub Desktop.
Pipeline entity model
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
declare const IDBrand: unique symbol; | |
type ID = string & { [IDBrand]: void }; | |
/** | |
* Allows for each property’s original type or string. | |
* This is useful for accepting values from HTML forms, | |
* which only allow string inputs. | |
*/ | |
type Stringish<T> = { | |
[p in keyof T]: T[p] | string; | |
}; | |
/** | |
* Exclusive or: one or the other property, but not both. | |
*/ | |
type XOR<T, U> = | |
| (T & { [K in Exclude<keyof U, keyof T>]?: never }) | |
| (U & { [K in Exclude<keyof T, keyof U>]?: never }); | |
/** | |
* Given a static object, return a union type with any of its key-value combinations. | |
*/ | |
type OneOf<T extends Record<PropertyKey, unknown>> = { | |
[K in keyof T]: { [P in K]: T[P] } | |
}[keyof T]; | |
type Tracked = { | |
created_at: Date; | |
updated_at: Date; | |
} | |
type Pending<Entity> = { | |
// [P in keyof Entity]?: | |
// Entity[P] extends object | |
// ? Pending<Entity[P]> | |
// : Entity[P] | string | null; | |
[P in keyof Entity]?: Entity[P] | string | null; | |
}; | |
/** | |
* Adds a snake_case prefix to every key. | |
* `as` renames the property. | |
*/ | |
type Prefix<T, P extends string> = { | |
[K in keyof T & string as `${P}_${K}`]: T[K]; | |
}; | |
/*****************************************/ | |
namespace Pipeline { | |
export type Customer = { | |
customer: ID; | |
label: string; | |
name: string; | |
description: string | null; | |
region: Region | null; | |
segment: Segment | null; | |
workloads: Omit<Workload, 'customer'>[]; | |
events: Omit<Event, 'customer'>[]; | |
} & Tracked; | |
export type Workload = { | |
workload: ID; | |
label: string; | |
name: string; | |
description: string | null; | |
customer: Omit<Customer, 'workloads'> ; | |
stage: Stage | null; | |
size: number | null; | |
engagement_lead: Participant | null; | |
events: Omit<Event, 'customer' | 'workload'>[]; | |
} & Tracked; | |
export type Event = ( | |
XOR<{ workload: Omit<Workload, 'events'>; } & WorkloadAction, { customer: Omit<Customer, 'events'>; }> | |
) | |
& { | |
happened_at: Date; | |
outcome: string; | |
notes: string | null; | |
team_participants: Participant[] | null; | |
field_participants: Participant[] | null; | |
}; | |
export type WorkloadAction = Partial<Prefix<Pick<Workload, 'stage' | 'size' | 'engagement_lead'>, 'update'>>; | |
type Participant = { | |
user: string; | |
name: string; | |
} | |
const Regions = { | |
'NORTHAM': 'North America', | |
'EMEA': 'Europe and Middle East', | |
'LATAM': 'Latin America', | |
'JAPAC': 'Japan and Asia Pacific' | |
} as const; | |
export type Region = OneOf<typeof Regions>; | |
export const Stages = { | |
0: 'Qualify', | |
1: 'Refine', | |
2: 'Tech Eval/Solution Dev', | |
3: 'Proposal Negotiation', | |
4: 'Migration/Implementation', | |
5: 'Live/Production', | |
97: 'Qualified Out', | |
98: 'Stalled', | |
99: 'Lost', | |
100: 'Closed', | |
101: 'Deleted' | |
} as const; | |
export type Stage = OneOf<typeof Stages>; | |
export const Segments = { | |
0: 'Select', | |
1: 'Enterprise', | |
2: 'Corporate', | |
3: 'SMB' | |
} as const; | |
export type Segment = OneOf<typeof Segments>; | |
} | |
const segment_good : Pipeline.Segment = { 1: 'Enterprise' }; | |
//@ts-expect-error | |
const segment_bad1 : Pipeline.Segment = { 0: 'Enterprise' }; | |
//@ts-expect-error | |
const segment_bad2 : Pipeline.Segment = { 99: 'Other' }; | |
const acme : Pipeline.Customer = { | |
customer: '123' as ID, | |
label: 'acme', | |
name: 'Acme Corp.', | |
description: null, | |
region: null, | |
segment: { 0: 'Select'}, | |
workloads: [], | |
events: [], | |
created_at: new Date(), | |
updated_at: new Date() | |
} | |
const acme_child = { | |
customer: '123' as ID, | |
label: 'acme', | |
name: 'Acme Corp.', | |
description: null, | |
region: { 'LATAM': 'Latin America'}, | |
segment: null, | |
// workloads: [], | |
events: [], | |
created_at: new Date(), | |
updated_at: new Date() | |
} | |
const new_customer : Pending<Pipeline.Customer> = { | |
label: 'acme', | |
name: 'Acme Corp.' | |
}; | |
const starline : Pipeline.Workload = { | |
workload: 'abc' as ID, | |
label: 'starline', | |
name: 'Straline', | |
description: null, | |
customer: | |
{ | |
customer: '123' as ID, | |
label: 'acme', | |
name: 'Acme Corp.', | |
description: null, | |
region: null, | |
segment: null, | |
//workloads: [], // Explicitly prohibited above | |
events: [], | |
created_at: new Date(), | |
updated_at: new Date() | |
}, | |
stage: {0: 'Qualify'}, | |
size: null, | |
engagement_lead: null, | |
events: [ | |
{ | |
outcome: 'Event nested in Workload', | |
happened_at: new Date(), | |
team_participants: null, | |
field_participants: null, | |
notes: null | |
} | |
], | |
created_at: new Date(), | |
updated_at: new Date() | |
}; | |
const event_workload : Pipeline.Event = { | |
workload: starline, | |
outcome: 'asdf', | |
happened_at: new Date(), | |
notes: null, | |
team_participants: null, | |
field_participants: null | |
}; | |
const event_customer : Pipeline.Event = { | |
customer: acme, | |
outcome: 'asdf', | |
happened_at: new Date(), | |
notes: null, | |
team_participants: null, | |
field_participants: null | |
}; | |
//@ts-expect-error Missing one of customer or workload | |
const event_customer_missing : Pipeline.Event = { | |
outcome: 'asdf', | |
happened_at: new Date(), | |
notes: null, | |
team_participants: null, | |
field_participants: null | |
}; | |
//@ts-expect-error Mismatch between customer and workload_action | |
const event_customer_mismatch : Pipeline.Event = { | |
outcome: 'asdf', | |
happened_at: new Date(), | |
notes: null, | |
team_participants: null, | |
field_participants: null, | |
customer: acme, | |
update_size: 100 | |
}; | |
const event_pending : Pending<Pipeline.Event> = { | |
workload: 'asdf', | |
outcome: null, | |
happened_at: 'today', | |
update_size: 100, | |
update_stage: '0' | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment