Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): add falsey check in hasProperty; prevents error w/ namingConventions improperly mapped names #573

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@


## [8.8.0](https://github.com/nartc/mapper/compare/8.7.7...8.8.0) (2024-01-22)


### Features

* **core:** add autoMap mapping configuration ([#538](https://github.com/nartc/mapper/issues/538)) ([5906add](https://github.com/nartc/mapper/commit/5906adddb2bf9a97916e7bbcb789cb4d4ac19b8a))
* **core:** use model's name on mapping error message ([#518](https://github.com/nartc/mapper/issues/518)) ([09bb926](https://github.com/nartc/mapper/commit/09bb9264301e72674a37df171976d7dbab374376))
* **nestjs:** bump nestjs dependencies to v10 ([#561](https://github.com/nartc/mapper/issues/561)) ([1403b41](https://github.com/nartc/mapper/commit/1403b41e2e7e17e026260914a0c96b921604db2e))
* **zod:** init zod ([aa010cd](https://github.com/nartc/mapper/commit/aa010cd7a1efee7caee2db193f320c82932781b4))


### Bug Fixes

* **classes:** add script to update plugin version for jest ([018c666](https://github.com/nartc/mapper/commit/018c6664ee8284513d98d757349e38287e400dfb)), closes [#528](https://github.com/nartc/mapper/issues/528)
* **classes:** clean up transformer plugin to ensure decorator is ignored ([e82e9a1](https://github.com/nartc/mapper/commit/e82e9a1c1699ef43d65ad5e93e90e284eeb6825e))
* **core:** consolidate some logic between forMember and createInitialMapping ([88397b4](https://github.com/nartc/mapper/commit/88397b4fe66ff257fc4360054a36ac04515bfdcc))


### Documentations

* **core:** adjust documentation ([193d541](https://github.com/nartc/mapper/commit/193d541e5d33440bf8de872607bc3e3d04264dfb))

### [8.7.7](https://github.com/nartc/mapper/compare/8.7.6...8.7.7) (2022-10-25)


Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
},
"devDependencies": {
"@mikro-orm/core": "5.6.13",
"@nestjs/common": "9.3.9",
"@nestjs/core": "9.3.9",
"@nestjs/platform-express": "9.3.9",
"@nestjs/schematics": "9.0.4",
"@nestjs/testing": "9.3.9",
"@nestjs/common": "10.0.3",
"@nestjs/core": "10.0.3",
"@nestjs/platform-express": "10.0.3",
"@nestjs/schematics": "10.0.1",
"@nestjs/testing": "10.0.3",
"@nrwl/devkit": "15.8.5",
"@nrwl/eslint-plugin-nx": "15.8.5",
"@nrwl/jest": "15.8.5",
Expand Down
5 changes: 3 additions & 2 deletions packages/classes/transformer-plugin/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,9 @@ export default {
};
```

### ttypescript
### ts-patch

ttypescript patches typescript in order to use transformers in tsconfig.json. See [ttypescript's README](https://github.com/cevek/ttypescript) for how to use this with module bundlers such as webpack or Rollup.
ts-patch patches typescript in order to use transformers in tsconfig.json. See [ts-patch's README](https://github.com/nonara/ts-patch) for how to use this with module bundlers such as webpack or Rollup.

```
{
Expand All @@ -210,6 +210,7 @@ ttypescript patches typescript in order to use transformers in tsconfig.json. Se
"plugins": [
{
"transform": "@automapper/classes/transformer-plugin",
"import": "tspBefore",
"modelFileNameSuffix": [...]
}
],
Expand Down
5 changes: 5 additions & 0 deletions packages/classes/transformer-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,10 @@ export const before = (
program: Program
) => automapperTransformerPlugin(program, options).before;

export const tspBefore = (
program: Program,
options: AutomapperTransformerPluginOptions
) => automapperTransformerPlugin(program, options).before;

export * from './lib/options';
export * from './lib/constants';
7 changes: 6 additions & 1 deletion packages/classes/transformer-plugin/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function getDecoratorOrUndefinedByNames(
names: string[],
decorators?: readonly Decorator[]
): Decorator | undefined {
return (decorators || []).find((item) =>
return (decorators ? decorators : []).find((item) =>
names.includes(getDecoratorName(item) as string)
);
}
Expand Down Expand Up @@ -110,6 +110,11 @@ export function replaceImportPath(
if (!importPath) {
return undefined;
}

if (process.platform === 'win32') {
return typeReference.replace('import', 'require')
}

importPath = importPath.slice(2, importPath.length - 1);

let relativePath = posix.relative(dirname(fileName), importPath);
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/lib/utils/get-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,6 @@ export function getFlatteningPaths(
}

function hasProperty(obj: Record<string, unknown>, property: string): boolean {
if (!obj) return false;
return Object.prototype.hasOwnProperty.call(obj, property);
}
29 changes: 29 additions & 0 deletions packages/documentations/docs/api/core/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,35 @@ ___

___

### autoMap

▸ **autoMap**<`TSource`, `TDestination`, `TKey`, `TValue`\>(`prop`): [`MappingConfiguration`](modules.md#mappingconfiguration)<`TSource`, `TDestination`\>

#### Type parameters

| Name | Type |
| :------ | :------ |
| `TSource` | extends { [key in `TKey`]: `TValue` } |
| `TDestination` | extends { [key in `TKey`]: `TValue` } |
| `TKey` | extends keyof `TSource` & keyof `TDestination` |
| `TValue` | extends `TSource`[`TKey`] & `TDestination`[`TKey`] |

#### Parameters

| Name | Type |
| :------ | :------ |
| `prop` | `TKey` |

#### Returns

[`MappingConfiguration`](modules.md#mappingconfiguration)<`TSource`, `TDestination`\>

#### Defined in

[lib/mapping-configurations/auto-map.ts:6](https://github.com/nartc/mapper/blob/5906addd/packages/core/src/lib/mapping-configurations/auto-map.ts#L6)

___

### beforeMap

▸ **beforeMap**<`TSource`, `TDestination`\>(`cb`): [`MappingConfiguration`](modules.md#mappingconfiguration)<`TSource`, `TDestination`\>
Expand Down
63 changes: 63 additions & 0 deletions packages/documentations/docs/mapping-configuration/auto-map.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
id: auto-map
title: AutoMap
sidebar_label: AutoMap
sidebar_position: 3
---

`autoMap()` is an alternative to the the `@AutoMap()` decorator. It trivially maps a property with the **same name and type** on the `Source` and `Destination` objects.

## Decoratorless entities and DTOs

Use this specially when your entities and DTOs cannot have the `@AutoMap()` decorator (e.g. when using **constructor assignments**), or when you would rather avoid them:

```ts
import {
autoMap,
createMap,
forMember,
mapFrom,
Mapper,
} from '@automapper/core';

export class DecoratorlessUserEntity {
constructor(
public readonly firstName: string,
public readonly lastName: string,
public readonly birthday: Date
) {}
}

export class DecoratorlessUserDto {
firstName!: string;
lastName!: string;
birthday!: string;
fullName!: string;
}

export function decoratorlessUserProfile(mapper: Mapper) {
createMap(
mapper,
DecoratorlessUserEntity,
DecoratorlessUserDto,
// Use `autoMap()` on properties that can be trivially mapped.
autoMap('firstName'),
autoMap('lastName'),

// Use more elaborate mapping configurations when necessary:

// 'birthday' exists on both `Source` and `Destination`, but with
// different types.
forMember(
(d) => d.birthday,
mapFrom((s) => s.birthday.toDateString())
),

// 'fullName' doesn't exist on `Source` and must be mapped manually.
forMember(
(d) => d.fullName,
mapFrom((s) => `${s.firstName} ${s.lastName}`)
)
);
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
id: before-map
title: BeforeMap
sidebar_label: BeforeMap
sidebar_position: 3
sidebar_position: 4
---

As the name suggests, `beforeMap()` sets up a `MapCallback` to be called **before** the map operation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
id: construct-using
title: ConstructUsing
sidebar_label: ConstructUsing
sidebar_position: 4
sidebar_position: 5
---

Call `constructUsing()` and pass in a `DestinationConstructor` to customize how AutoMapper should construct the `Destination` before every map operation against that `Destination`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
id: extend
title: Extend
sidebar_label: Extend
sidebar_position: 5
sidebar_position: 6
---

Call `extend()` and pass in either a `Mapping` or a pair of models to tell AutoMapper to extend the `MappingProperties` to the `Mapping` we are creating
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
id: for-self
title: ForSelf
sidebar_label: ForSelf
sidebar_position: 7
sidebar_position: 8
---

In previous sections, we've learned that we can have [Auto Flattening](../fundamentals/auto-flattening) with [Naming Conventions](../fundamentals/naming-convention).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
id: naming-conventions
title: NamingConventions
sidebar_label: NamingConventions
sidebar_position: 8
sidebar_position: 9
---

Call `namingConventions()` and pass in a `NamingConventionInput` to customize the Mapping's [NamingConvention](../fundamentals/naming-convention)
Expand Down
21 changes: 11 additions & 10 deletions packages/documentations/docs/mapping-configuration/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,17 @@ In addition, there is [Auto Flattening](../fundamentals/auto-flattening). These

`MappingConfiguration` are functions that augment a `Mapping`. When creating a `Mapping` with `createMap()`, we can pass in as many `MappingConfiguration` as we like and in any order that we want.

| mapping configuration | description |
| --------------------- | ----------------------------------------------------------------------- |
| `afterMap()` | Attach a `MapCallback` to run after the map operation |
| `beforeMap()` | Attach a `MapCallback` to run before the map operation |
| `constructUsing()` | Set a custom constructor for the `Destination` before the map operation |
| `extend()` | Extend another `Mapping` |
| `forMember()` | Configure a `MappingTransformation` for a property on the `Destination` |
| `forSelf()` | Configure flattening for `Destination` from a different `Source` |
| `namingConventions()` | Configure the `NamingConvention` for this `Mapping` |
| `typeConverters()` | Configure the `TypeConverter` for this `Mapping` |
| mapping configuration | description |
| --------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `afterMap()` | Attach a `MapCallback` to run after the map operation |
| `autoMap()` | Alternative to the `@AutoMap` decorator. Maps a property with same name and type on the `Source` and `Destination` |
| `beforeMap()` | Attach a `MapCallback` to run before the map operation |
| `constructUsing()` | Set a custom constructor for the `Destination` before the map operation |
| `extend()` | Extend another `Mapping` |
| `forMember()` | Configure a `MappingTransformation` for a property on the `Destination` |
| `forSelf()` | Configure flattening for `Destination` from a different `Source` |
| `namingConventions()` | Configure the `NamingConvention` for this `Mapping` |
| `typeConverters()` | Configure the `TypeConverter` for this `Mapping` |

:::caution

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
id: type-converters
title: TypeConverters
sidebar_label: TypeConverters
sidebar_position: 9
sidebar_position: 10
---

## What is Type Converter?
Expand Down
1 change: 1 addition & 0 deletions packages/documentations/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const sidebars = {
items: [
'mapping-configuration/overview',
'mapping-configuration/after-map',
'mapping-configuration/auto-map',
'mapping-configuration/before-map',
'mapping-configuration/construct-using',
'mapping-configuration/extend',
Expand Down
30 changes: 25 additions & 5 deletions packages/mikro/src/lib/serialize-entity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isEmpty } from '@automapper/core';
import type { AnyEntity } from '@mikro-orm/core';
import { Reference, Utils, wrap } from '@mikro-orm/core';
import type { IWrappedEntityInternal } from '@mikro-orm/core/typings';

const excluded = [
'__gettersDefined',
Expand All @@ -14,13 +15,16 @@ const excluded = [
export function serializeEntity(
item: AnyEntity,
itemMetadata: Record<string, unknown> | undefined,
toPojo = false
toPojo = false,
memorized = new Map<AnyEntity, Record<string, unknown>>,
skipCheckExisting = false
) {
if (!Utils.isEntity(item)) return item;
if (toPojo) return wrap(item).toPOJO();

const result = {} as Record<string | symbol, unknown>;
for (const key of Reflect.ownKeys(item)) {
const keys = Object.keys((wrap(item) as IWrappedEntityInternal<AnyEntity>).__meta.properties);
for (const key of keys) {
if (typeof key === 'symbol' || excluded.includes(key)) {
continue;
}
Expand All @@ -33,30 +37,46 @@ export function serializeEntity(
return serializeEntity(
snapshot as AnyEntity,
keyMetadata,
true,
memorized,
true
);
});
continue;
}

if (Reference.isReference(value)) {
const isExisting = memorized.has(value);

if (!skipCheckExisting && isExisting) {
result[key] = memorized.get(value);
continue;
}

if (!value.isInitialized()) {
memorized.set(value, wrap(value).toPOJO());
result[key] = serializeEntity(
wrap(value).toPOJO(),
keyMetadata
keyMetadata,
false,
memorized,
!isExisting
);
continue;
}

memorized.set(value, value.getEntity() as Record<string, unknown>);
result[key] = serializeEntity(
value.getEntity(),
keyMetadata,
typeof keyMetadata === 'object' && isEmpty(keyMetadata)
typeof keyMetadata === 'object' && isEmpty(keyMetadata),
memorized,
!isExisting
);
continue;
}

result[key] = serializeEntity(value, keyMetadata);
result[key] = serializeEntity(value, keyMetadata, false, memorized, false);
}

if (result['id'] == null && item['id'] != null) {
Expand Down
4 changes: 2 additions & 2 deletions packages/nestjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"type": "module",
"peerDependencies": {
"@automapper/core": "latest",
"@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0",
"@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0"
"@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0",
"@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0"
},
"sideEffects": false,
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/nestjs/tsconfig.lib.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": [],
"target": "es6",
"target": "es2021",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitReturns": true,
Expand Down
Loading