ts-data-forge
Version:
[](https://www.npmjs.com/package/ts-data-forge) [](https://www.npmjs.com/package/ts-data-forge) [ • 7.13 kB
JavaScript
/**
* A collection of type-safe object utility functions providing functional programming patterns
* for object manipulation, including pick, omit, shallow equality checks, and more.
*
* All functions maintain TypeScript type safety and support both direct and curried usage patterns
* for better composition with pipe operations.
*/
var Obj;
(function (Obj) {
/**
* Performs a shallow equality check on two records using a configurable equality function.
* Verifies that both records have the same number of entries and that for every key in the first record,
* the corresponding value passes the equality test with the value in the second record.
*
* @param a - The first record to compare
* @param b - The second record to compare
* @param eq - Optional equality function (defaults to Object.is for strict equality)
* @returns `true` if the records are shallowly equal according to the equality function, `false` otherwise
*
* @example
* ```typescript
* // Basic usage with default Object.is equality
* Obj.shallowEq({ x: 1, y: 2 }, { x: 1, y: 2 }); // true
* Obj.shallowEq({ x: 1 }, { x: 1, y: 2 }); // false (different number of keys)
* Obj.shallowEq({ x: 1 }, { x: 2 }); // false (different values)
* Obj.shallowEq({}, {}); // true (both empty)
*
* // String comparisons
* Obj.shallowEq({ a: "hello" }, { a: "hello" }); // true
* Obj.shallowEq({ a: "hello" }, { a: "world" }); // false
*
* // Using custom equality function
* const caseInsensitiveEq = (a: unknown, b: unknown) =>
* typeof a === 'string' && typeof b === 'string'
* ? a.toLowerCase() === b.toLowerCase()
* : a === b;
*
* Obj.shallowEq({ name: "ALICE" }, { name: "alice" }, caseInsensitiveEq); // true
*
* // Handling special values
* Obj.shallowEq({ x: NaN }, { x: NaN }); // true (Object.is treats NaN === NaN)
* Obj.shallowEq({ x: +0 }, { x: -0 }); // false (Object.is distinguishes +0 and -0)
* ```
*/
Obj.shallowEq = (a, b, eq = Object.is) => {
const aEntries = Object.entries(a);
const bEntries = Object.entries(b);
if (aEntries.length !== bEntries.length)
return false;
return aEntries.every(([k, v]) => eq(b[k], v));
};
function pick(...args) {
switch (args.length) {
case 2: {
const [record, keys] = args;
const keysSet = new Set(keys);
return (
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
Object.fromEntries(Object.entries(record).filter(([k, _v]) => keysSet.has(k))));
}
case 1: {
const [keys] = args;
return (record) => pick(record, keys);
}
}
}
Obj.pick = pick;
function omit(...args) {
switch (args.length) {
case 2: {
const [record, keys] = args;
const keysSet = new Set(keys);
return (
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
Object.fromEntries(Object.entries(record).filter(([k, _v]) => !keysSet.has(k))));
}
case 1: {
const [keys] = args;
return (record) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const result = omit(record, keys);
return result;
};
}
}
}
Obj.omit = omit;
/**
* Creates an object from an array of key-value pairs with precise TypeScript typing.
* This is a type-safe wrapper around `Object.fromEntries` that provides better type inference
* and compile-time guarantees about the resulting object structure.
*
* **Type Behavior**:
* - When entries is a fixed-length tuple, the exact object type is inferred
* - When entries has dynamic length with union key types, `Partial` is applied to prevent
* incorrect assumptions about which keys will be present
*
* @template Entries - The readonly array type of key-value entry tuples
* @param entries - An array of readonly key-value entry tuples `[key, value]`
* @returns An object created from the entries with precise typing
*
* @example
* ```typescript
* // Fixed entries with precise typing
* const fixedEntries = [
* ['name', 'Alice'],
* ['age', 30],
* ['active', true]
* ] as const;
*
* const user = Obj.fromEntries(fixedEntries);
* // Type: { readonly name: "Alice"; readonly age: 30; readonly active: true }
* // Value: { name: "Alice", age: 30, active: true }
*
* // Simple coordinate example
* const coordEntries = [['x', 1], ['y', 3]] as const;
* const point = Obj.fromEntries(coordEntries);
* // Type: { readonly x: 1; readonly y: 3 }
* // Value: { x: 1, y: 3 }
*
* // Dynamic entries with union keys
* const dynamicEntries: Array<['name' | 'email', string]> = [
* ['name', 'Alice']
* // email might or might not be present
* ];
* const partialUser = Obj.fromEntries(dynamicEntries);
* // Type: Partial<{ name: string; email: string }>
* // This prevents assuming both 'name' and 'email' are always present
*
* // Creating configuration objects
* const configEntries = [
* ['apiUrl', 'https://api.example.com'],
* ['timeout', 5000],
* ['retries', 3],
* ['debug', false]
* ] as const;
* const config = Obj.fromEntries(configEntries);
* // Precise types for each configuration value
*
* // Converting Maps to objects
* const settingsMap = new Map([
* ['theme', 'dark'],
* ['language', 'en'],
* ['notifications', true]
* ] as const);
* const settings = Obj.fromEntries([...settingsMap]);
*
* // Building objects from computed entries
* const keys = ['a', 'b', 'c'] as const;
* const values = [1, 2, 3] as const;
* const computedEntries = keys.map((k, i) => [k, values[i]] as const);
* const computed = Obj.fromEntries(computedEntries);
* // Type reflects the specific key-value associations
*
* // Error handling with validation
* function createUserFromEntries(entries: ReadonlyArray<readonly [string, unknown]>) {
* const user = Obj.fromEntries(entries);
* // Type is Partial<Record<string, unknown>> - safe for dynamic data
*
* if ('name' in user && typeof user.name === 'string') {
* // Type narrowing works correctly
* return { name: user.name, ...user };
* }
* throw new Error('Invalid user data');
* }
* ```
*/
Obj.fromEntries = (entries) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
Object.fromEntries(entries);
})(Obj || (Obj = {}));
export { Obj };
//# sourceMappingURL=object.mjs.map