Skip to content

Commit

Permalink
sample recreation of issue in memory
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinm-facsware committed Jul 1, 2024
1 parent df74290 commit 5020a5a
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 34 deletions.
16 changes: 13 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,22 @@
"author": "Martin Adamek",
"license": "MIT",
"dependencies": {
"@mikro-orm/core": "next",
"@mikro-orm/sqlite": "next",
"reflect-metadata": "^0.1.13"
"@mikro-orm/cli": "^6.2.9",
"@mikro-orm/core": "^6.2.9",
"@mikro-orm/entity-generator": "^6.2.9",
"@mikro-orm/migrations": "^6.2.9",
"@mikro-orm/nestjs": "^6.0.2",
"@mikro-orm/postgresql": "^6.2.9",
"@mikro-orm/reflection": "^6.2.9",
"@mikro-orm/seeder": "^6.2.9",
"@mikro-orm/sql-highlighter": "^1.0.1",
"@mikro-orm/sqlite": "^6.2.9",
"reflect-metadata": "^0.1.13",
"uuid": "^10.0.0"
},
"devDependencies": {
"@types/jest": "^29.5.8",
"@types/uuid": "^10.0.0",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"typescript": "^5.2.2"
Expand Down
30 changes: 30 additions & 0 deletions src/entities/account-user.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Entity, ManyToOne, OneToMany, Collection, ManyToMany, PrimaryKeyProp } from "@mikro-orm/core";
import { Account } from "./account.entity";
import { Project } from "./project.entity";
import { User } from "./user.entity";
import { Document } from "./document.entity";


@Entity({
tableName: 'accounts_users',
})
export class AccountUser {
@ManyToOne({ entity: () => Account, primary: true })
account: Account;

@ManyToOne({ entity: () => User, primary: true, updateRule: 'cascade' })
user: User;

@OneToMany(() => Document, (document) => document.assignee)
assignedDocuments = new Collection<Document>(this);

@ManyToMany(() => Project, (project) => project.assignedUsers)
assignedProjects = new Collection<Project>(this);

[PrimaryKeyProp]?: ['account', 'user']; // this is needed for proper type checks in `FilterQuery`

constructor(account: Account, user: User) {
this.account = account;
this.user = user;
}
}
22 changes: 22 additions & 0 deletions src/entities/account.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Entity, PrimaryKey, ManyToMany, Collection, OneToMany } from "@mikro-orm/core";
import { v4 } from "uuid";
import { AccountUser } from "./account-user.entity";
import { Project } from "./project.entity";
import { User } from "./user.entity";

@Entity()
export class Account {
@PrimaryKey({ unique: true })
id: string = v4();

// An Org has users so owner
@ManyToMany({
entity: () => User,
pivotEntity: () => AccountUser,
type: User,
})
users = new Collection<User>(this);

@OneToMany(() => Project, (project) => project.account)
projects = new Collection<Project>(this);
}
22 changes: 22 additions & 0 deletions src/entities/document.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Entity, PrimaryKey, ManyToOne } from "@mikro-orm/core";
import { v4 } from "uuid";
import { AccountUser } from "./account-user.entity";
import { Account } from "./account.entity";
import { Project } from "./project.entity";

@Entity({
tableName: 'documents',
})
export class Document {
@PrimaryKey({ unique: true })
id: string = v4();

@ManyToOne(() => Project, { index: true })
project!: Project;

@ManyToOne(() => Account, { hidden: true, index: true })
account!: Account;

@ManyToOne(() => AccountUser, { nullable: true })
assignee!: AccountUser;
}
25 changes: 25 additions & 0 deletions src/entities/project.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Entity, PrimaryKey, Property, ManyToOne, OneToMany, Collection, ManyToMany } from "@mikro-orm/core";
import { v4 } from "uuid";
import { AccountUser } from "./account-user.entity";
import { Account } from "./account.entity";
import { Document } from "./document.entity";

@Entity({
tableName: 'projects',
})
export class Project {
@PrimaryKey({ unique: true })
id: string = v4();

@Property()
name!: string;

@ManyToOne(() => Account, { nullable: true, hidden: true, index: true })
account?: Account;

@OneToMany(() => Document, (document) => document.project)
documents = new Collection<Document>(this);

@ManyToMany(() => AccountUser, 'assignedProjects', { owner: true })
assignedUsers = new Collection<AccountUser>(this);
}
16 changes: 16 additions & 0 deletions src/entities/user.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Entity, PrimaryKey, Property, ManyToMany, Collection } from "@mikro-orm/core";
import { v4 } from "uuid";
import { Account } from "./account.entity";

@Entity()
export class User {

@PrimaryKey({ unique: true })
id: string = v4();

@Property({ unique: true })
email!: string;

@ManyToMany({ entity: () => Account, mappedBy: (o) => o.users })
accounts = new Collection<Account>(this);
}
98 changes: 67 additions & 31 deletions src/example.test.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,87 @@
import { Entity, MikroORM, PrimaryKey, Property } from '@mikro-orm/sqlite';
import { AccountUser } from './entities/account-user.entity';
import { Account } from './entities/account.entity';
import { Project } from './entities/project.entity';
import { User } from './entities/user.entity';
import { Document } from './entities/document.entity';
import { MikroORM } from '@mikro-orm/sqlite';

@Entity()
class User {

@PrimaryKey()
id!: number;

@Property()
name: string;

@Property({ unique: true })
email: string;

constructor(name: string, email: string) {
this.name = name;
this.email = email;
}

}

let orm: MikroORM;

beforeAll(async () => {
orm = await MikroORM.init({
dbName: ':memory:',
entities: [User],
entities: [User, Account, AccountUser, Project, Document],
debug: ['query', 'query-params'],
allowGlobalContext: true, // only for testing
});
await orm.schema.refreshDatabase();
});


afterAll(async () => {
await orm.close(true);
});

test('basic CRUD example', async () => {
orm.em.create(User, { name: 'Foo', email: 'foo' });
await orm.em.flush();
orm.em.clear();

const user = await orm.em.findOneOrFail(User, { email: 'foo' });
expect(user.name).toBe('Foo');
user.name = 'Bar';
orm.em.remove(user);
await orm.em.flush();
// Doing this in a pattern to simulate our api calls
// Create a new account and a user that belongs to account
// This is a many to many relationship as a user can belong to multiple accounts
const account = new Account();
const user = new User();
user.email = '[email protected]';
account.users.add(user);
await orm.em.persistAndFlush(account);


const count = await orm.em.count(User, { email: 'foo' });
expect(count).toBe(0);

// Create a quick sample project and assign the user to it
const project1 = new Project();
project1.name = 'My First Project';
const foundUser = await orm.em.findOneOrFail(User, {
email: '[email protected]',
});
const accountUser = await orm.em.findOne(AccountUser, {
user: foundUser,
});
if(!accountUser){
return;
}
project1.assignedUsers.add(accountUser);
await orm.em.persistAndFlush(project1);

// This breads below, refetching the user to simulate this being a new request
// and even clearing the cache
orm.em.clear();
const document = new Document();
const document1 = new Document();
document.account = account;
document1.account = account;
const project = await orm.em.findOne(Project, {
name: 'My First Project',
});
if(!project){
return;
}
const broken_user = await orm.em.findOneOrFail(User, {
email: '[email protected]',
});
const broken_account_user = await orm.em.findOne(
AccountUser,
{
user: broken_user,
},
{
// Spefically populating the relation causes this blow up of "assigned Projects"
populate: ['assignedProjects', 'assignedDocuments'],
},
);
if(!broken_account_user){
return;
}
document.assignee = broken_account_user;
document1.assignee = broken_account_user;
project.documents.add(document);
project.documents.add(document1);
await orm.em.persistAndFlush(project);
});

0 comments on commit 5020a5a

Please sign in to comment.