Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | 1x 1x 1x 1x 1x 1x 15x 15x 15x 15x 13x 15x 15x 15x 15x 15x 1x 1x 1x 140x 140x 140x 140x 140x 138x 140x 3x 3x 135x 140x 3x 3x 132x 140x 95x 89x 6x 95x 37x 140x 1x 1x 36x 36x 36x 140x 9x 9x 140x 27x 27x 27x 140x 121x 121x 121x 121x 36x 36x | import { isPlainContainer } from '@/utils/typeGuards/common';
/**
* @category Collection Utilities
* @categoryDescription Collection utilities for working with arrays and objects.
* @showCategories
* @source
*/
/**
* Omit properties from an object.
*
* @category Collection Utilities
* @param data - The object to omit properties from.
* @param path - The property or properties to omit.
* @returns The object with the specified properties omitted.
* @example
* ```typescript
* const data = {
* name: "John",
* age: 30,
* city: "New York",
* };
* omit(data, "age"); // { name: "John", city: "New York" }
* omit(data, ["age", "city"]); // { name: "John" }
* ```
* @source
*/
export function omit<T extends object, K extends keyof T>(
data: T,
path: MaybeArray<K>,
): Omit<T, K> {
if (!path) {
return data;
}
if (typeof path === 'string') {
path = [path];
} else if (!Array.isArray(path)) {
throw new Error('path must be a string or an array of strings');
}
return Object.fromEntries(
// `key` is a runtime own-enumerable key of `T`, safe to narrow to `K` for membership check
Object.entries(data).filter(([key]) => !path.includes(key as K)),
// `Object.fromEntries` is typed as `{ [k: string]: unknown }`; result is structurally `Omit<T, K>`
) as Omit<T, K>;
}
type DiffChange =
| { type: 'created'; path: (string | number)[]; value: unknown }
| { type: 'deleted'; path: (string | number)[]; value: unknown }
| { type: 'modified'; path: (string | number)[]; oldValue: unknown; newValue: unknown };
/**
* Deep-diffs two arbitrary values and returns the list of changes as a flat
* array of `DiffChange` entries.
*
* Walk semantics:
* - Both `null` / `undefined` → empty result (no change).
* - Old missing, new present → `{ type: "created", path, value }`.
* - New missing, old present → `{ type: "deleted", path, value }`.
* - Primitive (or non-plain-container, e.g. `Date`, `Map`) on either side →
* a single `"modified"` entry when `Object.is` says they differ.
* - Array vs. object on opposite sides → wholesale `"modified"` entry.
* - Plain object / array on both sides → recurses per-key; the union of
* both sides' keys is walked so missing keys surface as created/deleted.
*
* Claude may or may not have stolen this right from Lodash.
* @category Collection Utilities
* @param oldObj - Previous value. Pass `null`/`undefined` for "nothing there".
* @param newObj - New value. Pass `null`/`undefined` for "gone".
* @param path - Accumulator used by the recursion; callers should omit it.
* @returns List of `DiffChange` entries — empty when the values are equal.
* @example
* ```typescript
* diff({ a: 1, b: 2 }, { a: 1, b: 3 });
* // [{ type: "modified", path: ["b"], oldValue: 2, newValue: 3 }]
*
* diff({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, d: 4 });
* // [
* // { type: "deleted", path: ["c"], value: 3 },
* // { type: "created", path: ["d"], value: 4 },
* // ]
*
* diff({ user: { name: "Ann" } }, { user: { name: "Bea" } });
* // [{ type: "modified", path: ["user", "name"], oldValue: "Ann", newValue: "Bea" }]
*
* diff([1, 2, 3], [1, 2, 3, 4]);
* // [{ type: "created", path: [3], value: 4 }]
*
* diff({ a: 1 }, { a: 1 }); // []
* ```
* @source
*/
export function diff(
oldObj: unknown,
newObj: unknown,
path: (string | number)[] = [],
): DiffChange[] {
// Both missing — no change
if (oldObj == null && newObj == null) return [];
// Creation: old is missing, new exists
if (oldObj == null) {
return [{ type: 'created', path, value: newObj }];
}
// Deletion: new is missing, old exists
if (newObj == null) {
return [{ type: 'deleted', path, value: oldObj }];
}
// Primitive comparison (or type mismatch between object and primitive)
if (!isPlainContainer(oldObj) || !isPlainContainer(newObj)) {
return Object.is(oldObj, newObj)
? []
: [{ type: 'modified', path, oldValue: oldObj, newValue: newObj }];
}
// Array/object type mismatch — treat as wholesale replacement
if (Array.isArray(oldObj) !== Array.isArray(newObj)) {
return [{ type: 'modified', path, oldValue: oldObj, newValue: newObj }];
}
// Recurse into container
const changes: DiffChange[] = [];
const keys = new Set<string | number>();
if (Array.isArray(oldObj) && Array.isArray(newObj)) {
const maxLen = Math.max(oldObj.length, newObj.length);
for (let i = 0; i < maxLen; i++) keys.add(i);
} else {
for (const k of Object.keys(oldObj)) keys.add(k);
for (const k of Object.keys(newObj)) keys.add(k);
}
for (const key of keys) {
const oldVal = Reflect.get(oldObj, key);
const newVal = Reflect.get(newObj, key);
changes.push(...diff(oldVal, newVal, [...path, key]));
}
return changes;
}
|