Skip to content

Instantly share code, notes, and snippets.

@ianldgs
Created March 20, 2025 13:08
Show Gist options
  • Save ianldgs/c7ee02336ed381393c75e909bcc2bc33 to your computer and use it in GitHub Desktop.
Save ianldgs/c7ee02336ed381393c75e909bcc2bc33 to your computer and use it in GitHub Desktop.
const a = { foo: "bar" };
const b = { bar: "foo" };
const c = { foo: "baz", bar: "baz" };
// ok - no conflicts
const result1 = merge(a).with(b).commit();
// @ts-expect-error
const result2 = merge(a).with(c).commit();
// @ts-expect-error
const result3 = merge(b).with(c).commit();
// @ts-expect-error
const result4 = merge(a).with(b).with(c).commit();
// @ts-expect-error
const result5 = merge(a).withConflicting(c, {}).commit();
// @ts-expect-error
const result6 = merge(b).withConflicting(c, {}).commit();
// ok - handled all conflicts
const result7 = merge(a).with(b).withConflicting(c, { foo: "", bar: "" }).commit();
type GetConflictingProps<A, B> = keyof A & keyof B;
type AssertNoConflict<X, Y> = GetConflictingProps<X, Y> extends never ? unknown : never;
type ConflictResolution<X, Y> = { [K in GetConflictingProps<X, Y>]: (X & Y)[K] };
class Merger<X extends NonNullable<unknown>> {
constructor(
private readonly obj: X,
private resolutions: NonNullable<unknown> = {}
) { }
commit() {
return { ...this.obj, ...this.resolutions };
}
with<Y extends NonNullable<unknown>>(y: Y & AssertNoConflict<X, Y>) {
return new Merger({ ...this.commit(), ...y });
}
withConflicting<Y extends NonNullable<unknown>>(y: Y, resolutions: ConflictResolution<X, Y>) {
return new Merger({ ...this.commit(), ...y }, resolutions);
}
}
function merge<X extends NonNullable<unknown>>(x: X): Merger<X> {
return new Merger(x);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment