expeditavoluptas
Version:
TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.
229 lines (184 loc) • 7.88 kB
text/typescript
import {
Entity,
PrimaryKey,
MikroORM,
ManyToOne,
PrimaryKeyType,
Property,
wrap,
Collection,
ManyToMany,
OptionalProps,
} from '@mikro-orm/core';
import { SqliteDriver } from '@mikro-orm/sqlite';
()
export class Order {
()
id!: number;
({ entity: () => Product, pivotEntity: () => OrderItem })
products = new Collection<Product>(this);
()
paid: boolean = false;
()
shipped: boolean = false;
()
created: Date = new Date();
}
()
export class Product {
()
id!: number;
()
name: string;
()
currentPrice: number;
(() => Order, o => o.products)
orders = new Collection<Order>(this);
constructor(name: string, currentPrice: number) {
this.name = name;
this.currentPrice = currentPrice;
}
}
()
export class OrderItem {
[OptionalProps]?: 'amount';
({ primary: true })
order: Order;
({ primary: true })
product: Product;
({ default: 1 })
amount!: number;
({ default: 0 })
offeredPrice: number;
[PrimaryKeyType]?: [number, number];
constructor(order: Order, product: Product) {
this.order = order;
this.product = product;
this.offeredPrice = product.currentPrice;
}
}
describe('custom pivot entity for m:n with additional properties (auto-discovered by reference)', () => {
let orm: MikroORM;
beforeAll(async () => {
orm = await MikroORM.init({
entities: [Order],
dbName: ':memory:',
driver: SqliteDriver,
});
await orm.schema.createSchema();
});
afterAll(() => orm.close(true));
beforeEach(() => orm.schema.clearDatabase());
test(`schema`, async () => {
const sql = await orm.schema.getCreateSchemaSQL();
expect(sql).toMatchSnapshot();
});
async function createEntities() {
const order1 = new Order();
const order2 = new Order();
const order3 = new Order();
const product1 = new Product('p1', 111);
const product2 = new Product('p2', 222);
const product3 = new Product('p3', 333);
const product4 = new Product('p4', 444);
const product5 = new Product('p5', 555);
orm.em.create(OrderItem, { order: order1, product: product1, offeredPrice: 123 });
orm.em.create(OrderItem, { order: order1, product: product2, offeredPrice: 3123 });
orm.em.create(OrderItem, { order: order2, product: product1, offeredPrice: 4123 });
orm.em.create(OrderItem, { order: order2, product: product2, offeredPrice: 1123 });
orm.em.create(OrderItem, { order: order2, product: product5, offeredPrice: 1263 });
orm.em.create(OrderItem, { order: order3, product: product3, offeredPrice: 7123 });
orm.em.create(OrderItem, { order: order3, product: product4, offeredPrice: 9123 });
orm.em.create(OrderItem, { order: order3, product: product5, offeredPrice: 5123 });
await orm.em.persistAndFlush([order1, order2, order3]);
orm.em.clear();
return { order1, order2, product1, product2, product3, product4, product5 };
}
test(`should work`, async () => {
const { order1, order2, product1, product2, product3, product4, product5 } = await createEntities();
const orders = await orm.em.find(Order, {}, { populate: true });
expect(orders).toHaveLength(3);
// test inverse side
const productRepository = orm.em.getRepository(Product);
let products = await productRepository.findAll();
expect(products).toBeInstanceOf(Array);
expect(products.length).toBe(5);
expect(products[0]).toBeInstanceOf(Product);
expect(products[0].name).toBe('p1');
expect(products[0].orders).toBeInstanceOf(Collection);
expect(products[0].orders.isInitialized()).toBe(true);
expect(products[0].orders.isDirty()).toBe(false);
expect(products[0].orders.count()).toBe(2);
expect(products[0].orders.length).toBe(2);
orm.em.clear();
products = await orm.em.find(Product, {});
expect(products[0].orders.isInitialized()).toBe(false);
expect(products[0].orders.isDirty()).toBe(false);
expect(() => products[0].orders.getItems()).toThrowError(/Collection<Order> of entity Product\[\d+] not initialized/);
expect(() => products[0].orders.remove(order1, order2)).toThrowError(/Collection<Order> of entity Product\[\d+] not initialized/);
expect(() => products[0].orders.removeAll()).toThrowError(/Collection<Order> of entity Product\[\d+] not initialized/);
expect(() => products[0].orders.contains(order1)).toThrowError(/Collection<Order> of entity Product\[\d+] not initialized/);
// test M:N lazy load
orm.em.clear();
products = await productRepository.findAll();
await products[0].orders.init();
expect(products[0].orders.count()).toBe(2);
expect(products[0].orders.getItems()[0]).toBeInstanceOf(Order);
expect(products[0].orders.getItems()[0].id).toBeDefined();
expect(wrap(products[0].orders.getItems()[0]).isInitialized()).toBe(true);
expect(products[0].orders.isInitialized()).toBe(true);
const old = products[0];
expect(products[1].orders.isInitialized()).toBe(false);
products = await productRepository.findAll({ populate: ['orders'] as const });
expect(products[1].orders.isInitialized()).toBe(true);
expect(products[0].id).toBe(old.id);
expect(products[0]).toBe(old);
expect(products[0].orders).toBe(old.orders);
// test M:N lazy load
orm.em.clear();
let order = (await orm.em.findOne(Order, { products: product1.id }))!;
expect(order.products.isInitialized()).toBe(false);
await order.products.init();
expect(order.products.isInitialized()).toBe(true);
expect(order.products.count()).toBe(2);
expect(order.products.getItems()[0]).toBeInstanceOf(Product);
expect(order.products.getItems()[0].id).toBeDefined();
expect(wrap(order.products.getItems()[0]).isInitialized()).toBe(true);
// test collection CRUD
// remove
expect(order.products.count()).toBe(2);
order.products.remove(t => t.id === product1.id); // we need to get reference as product1 is detached from current EM
await orm.em.persistAndFlush(order);
orm.em.clear();
order = (await orm.em.findOne(Order, order.id, { populate: ['products'] as const }))!;
expect(order.products.count()).toBe(1);
// add
order.products.add(productRepository.getReference(product1.id)); // we need to get reference as product1 is detached from current EM
const product6 = new Product('fresh', 555);
order.products.add(product6);
await orm.em.persistAndFlush(order);
orm.em.clear();
order = (await orm.em.findOne(Order, order.id, { populate: ['products'] as const }))!;
expect(order.products.count()).toBe(3);
// contains
expect(order.products.contains(productRepository.getReference(product1.id))).toBe(true);
expect(order.products.contains(productRepository.getReference(product2.id))).toBe(true);
expect(order.products.contains(productRepository.getReference(product3.id))).toBe(false);
expect(order.products.contains(productRepository.getReference(product4.id))).toBe(false);
expect(order.products.contains(productRepository.getReference(product5.id))).toBe(false);
expect(order.products.contains(productRepository.getReference(product6.id))).toBe(true);
// removeAll
order.products.removeAll();
await orm.em.persistAndFlush(order);
orm.em.clear();
order = (await orm.em.findOne(Order, order.id, { populate: ['products'] as const }))!;
expect(order.products.count()).toBe(0);
});
test(`search by m:n property and loadCount() works`, async () => {
await createEntities();
const res = await orm.em.find(Order, { products: { name: 'p1' } });
expect(res).toHaveLength(2);
const count = await res[0].products.loadCount();
expect(count).toBe(2);
});
});