@spaced-out/ui-design-system
Version:
Sense UI components library
101 lines • 3.61 kB
TypeScript
/**
* ------------------------------------------------------------------------
* Utility types for Flow-style spread + override semantics
* ------------------------------------------------------------------------
*
* WHY THIS Type EXISTS
* --------------------
* In Flow you can write:
*
* type B = { ...A, c: number };
*
* which means "take all of A's properties, but if there is a conflict,
* override with the new definition." In TypeScript, doing the naive
*
* type B = A & { c: number };
*
* will give you `c: string & number` (=> `never`) instead of the
* intended override. That leads to subtle bugs or unusable types.
*
* To avoid this pitfall and to set a clear team standard, we define
* short utilities here (`Over`, `OverAll`, etc.) that encode the
* intended semantics: **include everything from the left, right side
* wins on conflicts**.
*
*
* WHAT EACH UTILITY DOES
* ----------------------
*
* Over<T, R>
* - Include all properties from T
* - Add properties from R
* - For overlapping keys, R overrides T
*
* OverAll<T, [R1, R2, ...]>
* - Fold multiple overrides in sequence, left → right
* - Equivalent to: Over(Over(Over(T, R1), R2), ...)
* - Rightmost definition of a key always wins
*
* OverOnly<T, R>
* - Same as Over, but disallows adding brand-new keys
* - Useful when you want to enforce "true override only"
*
* OverAllOnly<T, [R1, R2, ...]>
* - Same as OverAll, but strict: every key in every R must
* already exist in T (or an earlier override)
*
*
* EXAMPLES
* --------
*
* // Base shape
* type A = { a: boolean; b: number; c: string };
*
* // Simple override
* type B = Over<A, { c: number }>;
* // => { a: boolean; b: number; c: number }
*
* // Multiple overrides
* type C = OverAll<A, [
* { c: number },
* { b: string },
* { d: Date }
* ]>;
* // => { a: boolean; c: number; b: string; d: Date }
*
* // Strict variant (no new keys allowed)
* type D = OverOnly<A, { c: number }>; // OK
* type E = OverOnly<A, { d: Date }>; // ERROR (d not in A)
*
*
* DESIGN SYSTEM USAGE
* -------------------
* - Use **Over** by default whenever you would otherwise write
* `A & { ... }`. This ensures right-biased override and prevents
* "never" bugs.
*
* - Use **OverAll** if you are combining 3+ prop bags or when you
* have `A & B & C & { ... }`.
*
* - Use **OverOnly / OverAllOnly** when you want to *forbid*
* introducing brand-new props, e.g. when extending a public API
* contract that must not grow new keys.
*
*
* TEAM STANDARD
* -------------
* ✅ Always prefer Over / OverAll for prop composition in DS components.
* ❌ Avoid raw `& { ... }` intersections for overrides — they will
* produce `never` on conflicts.
*
* This makes our type contracts predictable, Flow-like, and safer
* against accidental regressions when new keys are added upstream.
*
*/
export type Over<T extends object, R extends object> = Omit<T, keyof R> & R;
export type OverAll<T extends object, Rs extends readonly object[]> = Rs extends [infer H extends object, ...infer Tail extends readonly object[]] ? OverAll<Over<T, H>, Tail> : T;
export type OverOnly<T extends object, R extends {
[K in keyof R & keyof T]: unknown;
} & object> = Omit<T, keyof R> & R;
export type OverAllOnly<T extends object, Rs extends readonly object[]> = Rs extends [infer H extends object, ...infer Tail extends readonly object[]] ? OverAllOnly<OverOnly<T, H>, Tail> : T;
//# sourceMappingURL=utils.d.ts.map