Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export * from "./is-typed-array.js";
export * from "./is-undefined.js";
export * from "./is-weak-map.js";
export * from "./is-weak-set.js";
export * from "./iteratee.js";
export * from "./join.js";
export * from "./kebab-case.js";
export * from "./keys.js";
Expand Down
42 changes: 42 additions & 0 deletions src/iteratee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { type CalculatedPathT, type CalculatedPropertyT } from "./get.js";
import { type RecordT } from "./internals/index.js";
import { isFunction } from "./is-function.js";
import { property, type PropertyAccessorT } from "./property.js";

/**
* Creates a function that invokes `fn` with the arguments of the created function.
* If `fn` is a property name, the created function returns the property value for a given element.
*
* @group Other
* @since 1.0.0
* @param fn The value to convert to a callback.
* @param fallback The fallback value to use, in case of `fn` being a property name.
* @returns The callback.
* @example
* const callback = iteratee("foo"); // => (value) => get(value, "foo");
*
* callback({ foo: "bar" }); // => "bar"
* @example
* const callback = iteratee((foo: { bar: string }) => foo.bar); // => (foo: { bar: string }) => foo.bar;
*
* callback({ bar: "baz" }); // => "baz"
*/
export function iteratee<
Iteratee extends IterateeT,
Fallback = undefined,
>(
fn: Iteratee,
fallback?: Iteratee extends PropertyKey ? Fallback : never,
): Iteratee extends PropertyKey ? PropertyAccessorT<Iteratee, Fallback> : Iteratee {
if (isFunction(fn)) return fn as never;

return property(fn, fallback) as never;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type IterateeT<Value = any, Return = any> = Value extends RecordT
? CalculatedPropertyT<CalculatedPathT<Value>> | IterateeFnT<Value, Return>
: IterateeFnT<Value, Return>;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type IterateeFnT<Value = any, Return = any> = (value: Value) => Return;
2 changes: 1 addition & 1 deletion src/property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { type PathT } from "./to-path.js";
*/
export function property<
Property extends PropertyKey,
Fallback,
Fallback = undefined,
>(path: Property, fallback?: Fallback): PropertyAccessorT<Property, Fallback> {
return value => get(value, path as never, fallback as never) as never;
}
Expand Down
49 changes: 49 additions & 0 deletions tests/iteratee.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { iteratee } from "@extremejs/utils";

describe("with property as iteratee", () => {
it("should return value of the provided direct property", () => {
const fn = iteratee("foo");
const obj = { foo: "bar" };

expect(fn(obj)).toEqual(obj.foo);
});

it("should return value of the provided nested property", () => {
const fn = iteratee("foo.bar");
const obj = { foo: { bar: "baz" } };

expect(fn(obj)).toEqual(obj.foo.bar);
});

it("should return the fallback value", () => {
const fallback = "baz";
const fn = iteratee("foo.bar", fallback);
const obj = { foo: "bar" };

expect(fn(obj)).toEqual(fallback);
});
});

describe("with function as iteratee", () => {
it("should return value of the provided direct property", () => {
const fn = iteratee((value: { foo: string }) => value.foo);
const obj = { foo: "bar" };

expect(fn(obj)).toEqual(obj.foo);
});

it("should return value of the provided nested property", () => {
const fn = iteratee((value: { foo: { bar: string } }) => value.foo.bar);
const obj = { foo: { bar: "baz" } };

expect(fn(obj)).toEqual(obj.foo.bar);
});

it("should ignore the fallback value", () => {
// eslint-disable-next-line
const fn = iteratee((value: { foo: any }) => value.foo.bar, "baz" as never);
const obj = { foo: "bar" };

expect(fn(obj)).toBeUndefined();
});
});