Skip to content

Commit

Permalink
feat(core): add Hiddentype as an alternative to HiddenProps symbol (
Browse files Browse the repository at this point in the history
#5009)

`Hidden` type is an alternative to the `HiddenProps` symbol. It works
the same as the `Opt` type (an alternative for `OptionalProps` symbol),
and can be used in two ways:

- with generics: `hiddenField?: Hidden<string>;`
- with intersections: `hiddenField?: string & Hidden;`

Both will work the same, and can be combined with the `HiddenProps`
symbol approach.

```ts
@entity()
class Book {

  @Property({ hidden: true })
  hiddenField: Hidden<Date> = Date.now();

  @Property({ hidden: true, nullable: true })
  otherHiddenField?: string & Hidden;

}
```
  • Loading branch information
B4nan committed Dec 12, 2023
1 parent 21e51ff commit c047bb1
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 24 deletions.
32 changes: 26 additions & 6 deletions docs/docs/serializing.md
Expand Up @@ -5,7 +5,7 @@ title: Serializing
By default, all entities are monkey patched with `toObject()` and `toJSON` methods:

```ts
export interface AnyEntity<K = number | string> {
interface AnyEntity<K = number | string> {
toObject(parent?: AnyEntity, isCollection?: boolean): Record<string, any>;
toJSON(...args: any[]): Record<string, any>;
// ...
Expand All @@ -16,7 +16,7 @@ When you serialize your entity via `JSON.stringify(entity)`, its `toJSON` method

```ts
@Entity()
export class Book {
class Book {

// ...

Expand All @@ -41,7 +41,7 @@ If you want to omit some properties from serialized result, you can mark them wi

```ts
@Entity()
export class Book {
class Book {

// we use the `HiddenProps` symbol to define hidden properties on type level
[HiddenProps]?: 'hiddenField' | 'otherHiddenField';
Expand All @@ -61,6 +61,26 @@ console.log(wrap(book).toObject().hiddenField); // undefined
console.log(wrap(book).toJSON().hiddenField); // undefined
```

Alternatively, you can use the `Hidden` type. It works the same as the `Opt` type (an alternative for `OptionalProps` symbol), and can be used in two ways:

- with generics: `hiddenField?: Hidden<string>;`
- with intersections: `hiddenField?: string & Hidden;`

Both will work the same, and can be combined with the `HiddenProps` symbol approach.

```ts
@Entity()
class Book {

@Property({ hidden: true })
hiddenField: Hidden<Date> = Date.now();

@Property({ hidden: true, nullable: true })
otherHiddenField?: string & Hidden;

}
```

## Shadow Properties

The opposite situation where you want to define a property that lives only in memory (is not persisted into database) can be solved by defining your property as `persist: false`. Such property can be assigned via one of `Entity.assign()`, `em.create()` and `em.merge()`. It will be also part of serialized result.
Expand All @@ -69,7 +89,7 @@ This can be handled when dealing with additional values selected via `QueryBuild

```ts
@Entity()
export class Book {
class Book {

@Property({ persist: false })
count?: number;
Expand All @@ -88,7 +108,7 @@ As an alternative to custom `toJSON()` method, we can also use property serializ

```ts
@Entity()
export class Book {
class Book {

@ManyToOne({ serializer: value => value.name, serializedName: 'authorName' })
author: Author;
Expand Down Expand Up @@ -159,7 +179,7 @@ const dto2 = wrap(user1).serialize();
By default, every relation is considered as not populated - this will result in the foreign key values to be present. Loaded collections will be represented as arrays of the foreign keys. To control the shape of the serialized response we can use the second `options` parameter:

```ts
export interface SerializeOptions<T extends object, P extends string = never, E extends string = never> {
interface SerializeOptions<T extends object, P extends string = never, E extends string = never> {
/** Specify which relation should be serialized as populated and which as a FK. */
populate?: AutoPath<T, P>[] | boolean;

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Expand Up @@ -9,7 +9,7 @@ export {
GetRepository, EntityRepositoryType, MigrationObject, DeepPartial, PrimaryProperty, Cast, IsUnknown, EntityDictionary, EntityDTO, MigrationDiff, GenerateOptions, FilterObject,
IEntityGenerator, ISeedManager, EntityClassGroup, OptionalProps, EagerProps, HiddenProps, RequiredEntityData, CheckCallback, SimpleColumnMeta, Rel, Ref, ScalarRef, EntityRef, ISchemaGenerator,
UmzugMigration, MigrateOptions, MigrationResult, MigrationRow, EntityKey, EntityValue, FilterKey, Opt, EntityType, FromEntityType, Selected, IsSubset,
EntityProps, ExpandProperty, ExpandScalar, FilterItemValue, ExpandQuery, Scalar, ExpandHint,
EntityProps, ExpandProperty, ExpandScalar, FilterItemValue, ExpandQuery, Scalar, ExpandHint, Hidden,
} from './typings';
export * from './enums';
export * from './errors';
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/typings.ts
Expand Up @@ -60,6 +60,7 @@ export const EagerProps = Symbol('EagerProps');
export const HiddenProps = Symbol('HiddenProps');

export type Opt<T = unknown> = T & { __optional?: 1 };
export type Hidden<T = unknown> = T & { __hidden?: 1 };

export type UnwrapPrimary<T> = T extends Scalar
? T
Expand Down Expand Up @@ -331,7 +332,7 @@ export type EntityDTOProp<T> = T extends Scalar
: T extends Relation<T>
? EntityDTONested<T>
: T;
type ExtractHiddenProps<T> = T extends { [HiddenProps]?: infer Prop } ? Prop : never;
type ExtractHiddenProps<T> = (T extends { [HiddenProps]?: infer K } ? K : never) | ({ [K in keyof T]: T[K] extends Hidden ? K : never }[keyof T] & {});
type ExcludeHidden<T, K extends keyof T> = K extends ExtractHiddenProps<T> ? never : K;
export type EntityDTO<T> = { [K in EntityKey<T> as ExcludeHidden<T, K>]: EntityDTOProp<T[K]> };

Expand Down
8 changes: 3 additions & 5 deletions tests/entities-sql/Author2.ts
Expand Up @@ -22,7 +22,7 @@ import {
t,
OnLoad,
Opt,
HiddenProps,
Hidden,
Embeddable,
Embedded,
} from '@mikro-orm/core';
Expand All @@ -34,13 +34,11 @@ import { Address2 } from './Address2';
@Embeddable()
export class Identity {

[HiddenProps]?: 'foo' | 'bar';

@Property({ hidden: true })
foo: string;
foo: string & Hidden;

@Property({ hidden: true })
bar: number;
bar: Hidden<number>;

constructor(foo: string, bar: number) {
this.foo = foo;
Expand Down
6 changes: 2 additions & 4 deletions tests/features/result-cache/GH3294.test.ts
@@ -1,20 +1,18 @@
import { Entity, MikroORM, PrimaryKey, Property, wrap, HiddenProps } from '@mikro-orm/core';
import { Entity, MikroORM, PrimaryKey, Property, wrap, Hidden } from '@mikro-orm/core';
import { mockLogger } from '../../helpers';
import { BetterSqliteDriver } from '@mikro-orm/better-sqlite';

@Entity()
export class EntityWithHiddenProp {

[HiddenProps]?: 'hiddenProp';

@PrimaryKey()
id!: number;

@Property()
notHiddenProp: string = 'foo';

@Property({ hidden: true })
hiddenProp: string = 'hidden prop';
hiddenProp: Hidden<string> = 'hidden prop';

}

Expand Down
11 changes: 4 additions & 7 deletions tests/issues/GH3429.test.ts
@@ -1,19 +1,16 @@
import { MikroORM, Embeddable, Embedded, Entity, PrimaryKey, Property, HiddenProps, OptionalProps, wrap } from '@mikro-orm/sqlite';
import { MikroORM, Embeddable, Embedded, Entity, PrimaryKey, Property, Hidden, Opt, wrap } from '@mikro-orm/sqlite';

@Embeddable()
class Address {

[HiddenProps]?: 'addressLine1' | 'addressLine2';
[OptionalProps]?: 'address';

@Property({ hidden: true })
addressLine1!: string;
addressLine1!: Hidden & string;

@Property({ hidden: true })
addressLine2!: string;
addressLine2!: Hidden<string>;

@Property({ persist: false })
get address() {
get address(): Opt<string> {
return [this.addressLine1, this.addressLine2].join(' ');
}

Expand Down

0 comments on commit c047bb1

Please sign in to comment.