Skip to content

Commit

Permalink
Require Node.js 12.20 and move to ESM
Browse files Browse the repository at this point in the history
  • Loading branch information
sindresorhus committed Oct 18, 2021
1 parent 1e5c466 commit 0d0a6d8
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 186 deletions.
6 changes: 2 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ jobs:
fail-fast: false
matrix:
node-version:
- 14
- 12
- 10
- 16
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- run: npm install
Expand Down
204 changes: 98 additions & 106 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,84 +1,78 @@
// Unique symbol cannot be declared in a namespace directly, so we declare it top-level
// See: https://github.com/sindresorhus/map-obj/pull/38#discussion_r702396878
declare const skipSymbol: unique symbol;

declare namespace mapObject {
type Mapper<
SourceObjectType extends {[key: string]: any},
MappedObjectKeyType extends string,
MappedObjectValueType
> = (
sourceKey: keyof SourceObjectType,
sourceValue: SourceObjectType[keyof SourceObjectType],
source: SourceObjectType
) => [
targetKey: MappedObjectKeyType,
targetValue: MappedObjectValueType,
mapperOptions?: mapObject.MapperOptions
] | typeof mapObject.mapObjectSkip;

interface Options {
/**
Recurse nested objects and objects in arrays.
@default false
*/
deep?: boolean;

/**
Target object to map properties on to.
@default {}
*/
target?: {[key: string]: any};
}

interface DeepOptions extends Options {
deep: true;
}

interface TargetOptions<TargetObjectType extends {[key: string]: any}> extends Options {
target: TargetObjectType;
}

interface MapperOptions {
/**
Whether `targetValue` should be recursed.
Requires `deep: true`.
@default true
*/
readonly shouldRecurse?: boolean;
}
/**
Return this value from a `mapper` function to remove a key from an object.
@example
```
import mapObject, {mapObjectSkip} from 'map-obj';
const object = {one: 1, two: 2}
const mapper = (key, value) => value === 1 ? [key, value] : mapObjectSkip
const result = mapObject(object, mapper);
console.log(result);
//=> {one: 1}
```
*/
export const mapObjectSkip: unique symbol;

export type Mapper<
SourceObjectType extends Record<string, any>,
MappedObjectKeyType extends string,
MappedObjectValueType,
> = (
sourceKey: keyof SourceObjectType,
sourceValue: SourceObjectType[keyof SourceObjectType],
source: SourceObjectType
) => [
targetKey: MappedObjectKeyType,
targetValue: MappedObjectValueType,
mapperOptions?: MapperOptions,
] | typeof mapObjectSkip;

export interface Options {
/**
Return this value from a `mapper` function to remove a key from an object.
Recurse nested objects and objects in arrays.
@example
```
const mapObject = require('map-obj');
@default false
*/
readonly deep?: boolean;

const object = {one: 1, two: 2}
const mapper = (key, value) => value === 1 ? [key, value] : mapObject.mapObjectSkip
const result = mapObject(object, mapper);
/**
The target object to map properties on to.
console.log(result);
//=> {one: 1}
```
@default {}
*/
const mapObjectSkip: typeof skipSymbol
readonly target?: Record<string, any>;
}

export interface DeepOptions extends Options {
readonly deep: true;
}

export interface TargetOptions<TargetObjectType extends Record<string, any>> extends Options {
readonly target: TargetObjectType;
}

export interface MapperOptions {
/**
Whether `targetValue` should be recursed.
Requires `deep: true`.
@default true
*/
readonly shouldRecurse?: boolean;
}

/**
Map object keys and values into a new object.
@param source - Source object to copy properties from.
@param mapper - Mapping function.
@param source - The source object to copy properties from.
@param mapper - A mapping function.
@example
```
import mapObject = require('map-obj');
import mapObject, {mapObjectSkip} from 'map-obj';
const newObject = mapObject({foo: 'bar'}, (key, value) => [value, key]);
//=> {bar: 'foo'}
Expand All @@ -89,63 +83,61 @@ const newObject = mapObject({FOO: true, bAr: {bAz: true}}, (key, value) => [key.
const newObject = mapObject({FOO: true, bAr: {bAz: true}}, (key, value) => [key.toLowerCase(), value], {deep: true});
//=> {foo: true, bar: {baz: true}}
const newObject = mapObject({one: 1, two: 2}, (key, value) => value === 1 ? [key, value] : mapObject.mapObjectSkip);
const newObject = mapObject({one: 1, two: 2}, (key, value) => value === 1 ? [key, value] : mapObjectSkip);
//=> {one: 1}
```
*/
declare function mapObject<
SourceObjectType extends object,
TargetObjectType extends {[key: string]: any},
export default function mapObject<
SourceObjectType extends Record<string, unknown>,
TargetObjectType extends Record<string, any>,
MappedObjectKeyType extends string,
MappedObjectValueType
MappedObjectValueType,
>(
source: SourceObjectType,
mapper: mapObject.Mapper<
SourceObjectType,
MappedObjectKeyType,
MappedObjectValueType
mapper: Mapper<
SourceObjectType,
MappedObjectKeyType,
MappedObjectValueType
>,
options: mapObject.DeepOptions & mapObject.TargetOptions<TargetObjectType>
): TargetObjectType & {[key: string]: unknown};
declare function mapObject<
SourceObjectType extends object,
options: DeepOptions & TargetOptions<TargetObjectType>
): TargetObjectType & Record<string, unknown>;
export default function mapObject<
SourceObjectType extends Record<string, unknown>,
MappedObjectKeyType extends string,
MappedObjectValueType
MappedObjectValueType,
>(
source: SourceObjectType,
mapper: mapObject.Mapper<
SourceObjectType,
MappedObjectKeyType,
MappedObjectValueType
mapper: Mapper<
SourceObjectType,
MappedObjectKeyType,
MappedObjectValueType
>,
options: mapObject.DeepOptions
): {[key: string]: unknown};
declare function mapObject<
SourceObjectType extends {[key: string]: any},
TargetObjectType extends {[key: string]: any},
options: DeepOptions
): Record<string, unknown>;
export default function mapObject<
SourceObjectType extends Record<string, any>,
TargetObjectType extends Record<string, any>,
MappedObjectKeyType extends string,
MappedObjectValueType
MappedObjectValueType,
>(
source: SourceObjectType,
mapper: mapObject.Mapper<
SourceObjectType,
MappedObjectKeyType,
MappedObjectValueType
mapper: Mapper<
SourceObjectType,
MappedObjectKeyType,
MappedObjectValueType
>,
options: mapObject.TargetOptions<TargetObjectType>
options: TargetOptions<TargetObjectType>
): TargetObjectType & {[K in MappedObjectKeyType]: MappedObjectValueType};
declare function mapObject<
SourceObjectType extends {[key: string]: any},
export default function mapObject<
SourceObjectType extends Record<string, any>,
MappedObjectKeyType extends string,
MappedObjectValueType
MappedObjectValueType,
>(
source: SourceObjectType,
mapper: mapObject.Mapper<
SourceObjectType,
MappedObjectKeyType,
MappedObjectValueType
mapper: Mapper<
SourceObjectType,
MappedObjectKeyType,
MappedObjectValueType
>,
options?: mapObject.Options
options?: Options
): {[K in MappedObjectKeyType]: MappedObjectValueType};

export = mapObject;
33 changes: 15 additions & 18 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
'use strict';

const isObject = value => typeof value === 'object' && value !== null;
const mapObjectSkip = Symbol('skip');

// Customized for this use-case
const isObjectCustom = value =>
isObject(value) &&
!(value instanceof RegExp) &&
!(value instanceof Error) &&
!(value instanceof Date);
isObject(value)
&& !(value instanceof RegExp)
&& !(value instanceof Error)
&& !(value instanceof Date);

export const mapObjectSkip = Symbol('mapObjectSkip');

const mapObject = (object, mapper, options, isSeen = new WeakMap()) => {
const _mapObject = (object, mapper, options, isSeen = new WeakMap()) => {
options = {
deep: false,
target: {},
...options
...options,
};

if (isSeen.has(object)) {
Expand All @@ -26,7 +25,7 @@ const mapObject = (object, mapper, options, isSeen = new WeakMap()) => {
const {target} = options;
delete options.target;

const mapArray = array => array.map(element => isObjectCustom(element) ? mapObject(element, mapper, options, isSeen) : element);
const mapArray = array => array.map(element => isObjectCustom(element) ? _mapObject(element, mapper, options, isSeen) : element);
if (Array.isArray(object)) {
return mapArray(object);
}
Expand All @@ -46,9 +45,9 @@ const mapObject = (object, mapper, options, isSeen = new WeakMap()) => {
}

if (options.deep && shouldRecurse && isObjectCustom(newValue)) {
newValue = Array.isArray(newValue) ?
mapArray(newValue) :
mapObject(newValue, mapper, options, isSeen);
newValue = Array.isArray(newValue)
? mapArray(newValue)
: _mapObject(newValue, mapper, options, isSeen);
}

target[newKey] = newValue;
Expand All @@ -57,12 +56,10 @@ const mapObject = (object, mapper, options, isSeen = new WeakMap()) => {
return target;
};

module.exports = (object, mapper, options) => {
export default function mapObject(object, mapper, options) {
if (!isObject(object)) {
throw new TypeError(`Expected an object, got \`${object}\` (${typeof object})`);
}

return mapObject(object, mapper, options);
};

module.exports.mapObjectSkip = mapObjectSkip;
return _mapObject(object, mapper, options);
}
24 changes: 12 additions & 12 deletions index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
import {expectType, expectAssignable} from 'tsd';
import mapObject = require('./index.js');
import mapObject, {Options, mapObjectSkip} from './index.js';

const options: mapObject.Options = {};
const options: Options = {}; // eslint-disable-line @typescript-eslint/no-unused-vars

const newObject = mapObject({foo: 'bar'}, (key, value) => [value, key]);
expectType<{[key: string]: 'foo'}>(newObject);
expectType<Record<string, 'foo'>>(newObject);
expectType<'foo'>(newObject.bar);

const object = mapObject({foo: 'bar'}, (key, value) => [value, key], {
target: {baz: 'baz'}
target: {baz: 'baz'},
});
expectType<{baz: string} & {[x: string]: 'foo'}>(object);
expectType<{baz: string} & Record<string, 'foo'>>(object);
expectType<'foo'>(object.bar);
expectType<string>(object.baz);

const object1 = mapObject({foo: 'bar'}, (key, value) => [value, key], {
target: {baz: 'baz'},
deep: false
deep: false,
});
expectType<{baz: string} & {[x: string]: 'foo'}>(object1);
expectType<{baz: string} & Record<string, 'foo'>>(object1);
expectType<'foo'>(object1.bar);
expectType<string>(object1.baz);

const object2 = mapObject({foo: 'bar'}, (key, value) => [value, key], {
deep: true
deep: true,
});
expectType<{[key: string]: unknown}>(object2);
expectType<Record<string, unknown>>(object2);
const object3 = mapObject({foo: 'bar'}, (key, value) => [value, key], {
deep: true,
target: {bar: 'baz' as const}
target: {bar: 'baz' as const},
});
expectAssignable<{[key: string]: unknown}>(object3);
expectAssignable<Record<string, unknown>>(object3);
expectType<'baz'>(object3.bar);

mapObject({foo: 'bar'}, (key, value) => [value, key, {shouldRecurse: false}]);

mapObject({foo: 'bar'}, () => mapObject.mapObjectSkip);
mapObject({foo: 'bar'}, () => mapObjectSkip);
Loading

0 comments on commit 0d0a6d8

Please sign in to comment.