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

Allow EntityManager.create to infer EntityInstance type #364

Open
wants to merge 2 commits into
base: develop
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
31 changes: 27 additions & 4 deletions packages/core/src/classes/manager/__test__/entity-manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
import {Connection} from '../../connection/connection';
import {
CONSUMED_CAPACITY_TYPE,
EntityInstance,
InvalidDynamicUpdateAttributeValueError,
} from '@typedorm/common';
import {
Expand Down Expand Up @@ -213,6 +212,30 @@ test('creates entity and returns all attributes, including auto generated ones',
});
});

test('will infer the proper return type when creating an entity', async () => {
dcMock.put.mockReturnValue({
promise: () => ({}),
});

const user = new User();
user.id = '1';
user.name = 'Test User';
user.status = 'active';

const ret = await manager.create(user);

type Expect<T extends true> = T;
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y
? 1
: 2
? true
: false;

// would fail to compile if the types did not match
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type Assert = Expect<Equal<typeof ret, User>>;
});

/**
* Issue: #203
*/
Expand All @@ -236,9 +259,9 @@ test('will throw when called with a POJO rather than an instance of a entity cla
addresses: ['address a'],
};

await expect(
manager.create<User>(userProperties as EntityInstance)
).rejects.toBeInstanceOf(Error);
await expect(manager.create<User>(userProperties)).rejects.toBeInstanceOf(
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since there is now a relationship between the type parameter Entity and the first parameter, we actually can't keep this test exactly as is.

The options I see are:

  • drop the User type parameter altogether
  • drop the EntityInstance cast
  • use EntityInstance as the type parameter and keep the EntityInstance cast on the input
  • drop all the type parameters and rely on inference (which will be inferred as the POJO type)

I choose to drop the cast, as that felt the cleanest to me, in expressing the failure intent (you can't pass POJO objects in when an Entity instance is expected).

Error
);
});

/**
Expand Down
12 changes: 6 additions & 6 deletions packages/core/src/classes/manager/entity-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,11 @@ export class EntityManager {
* Creates new record in table with given entity
* @param entity Entity to add to table as a new record
*/
async create<Entity>(
entity: EntityInstance,
options?: EntityManagerCreateOptions<Entity>,
async create<TEntity extends EntityInstance>(
entity: TEntity,
options?: EntityManagerCreateOptions<TEntity>,
metadataOptions?: MetadataOptions
): Promise<Entity> {
): Promise<TEntity> {
if (!IsEntityInstance(entity)) {
throw new Error(
`Provided entity ${JSON.stringify(
Expand Down Expand Up @@ -231,7 +231,7 @@ export class EntityManager {
}

// by default dynamodb does not return attributes on create operation, so return one
const itemToReturn = this._entityTransformer.fromDynamoEntity<Entity>(
const itemToReturn = this._entityTransformer.fromDynamoEntity<TEntity>(
entityClass,
dynamoPutItemInput.Item as DocumentClientTypes.AttributeMap,
{
Expand All @@ -250,7 +250,7 @@ export class EntityManager {
returnConsumedCapacity: metadataOptions?.returnConsumedCapacity,
});

const itemToReturn = this._entityTransformer.fromDynamoEntity<Entity>(
const itemToReturn = this._entityTransformer.fromDynamoEntity<TEntity>(
entityClass,
// if create operation contains multiple items, first one will the original item
dynamoPutItemInput[0]?.Put?.Item ?? {},
Expand Down