-
-
Notifications
You must be signed in to change notification settings - Fork 496
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
feat(entity-generator): generate OptionalProps
symbols
#3482
feat(entity-generator): generate OptionalProps
symbols
#3482
Conversation
…ies that define a default value
Codecov ReportBase: 99.89% // Head: 99.89% // Increases project coverage by
Additional details and impacted files@@ Coverage Diff @@
## master #3482 +/- ##
=======================================
Coverage 99.89% 99.89%
=======================================
Files 205 205
Lines 12814 12822 +8
Branches 2972 2974 +2
=======================================
+ Hits 12801 12809 +8
Misses 13 13
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report at Codecov. |
OptionalProps
symbols
OptionalProps
symbolsOptionalProps
symbols
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just one nit, otherwise looking good, thanks!
Btw this PR got me thinking, maybe its about time to provide @mikro-orm/client
package or something similar with schema first codegen approach.
if (optionalProperties.length > 0) { | ||
this.coreImports.add('OptionalProps'); | ||
const optionalPropertyNames = optionalProperties.map(prop => `'${prop.name}'`).sort(); | ||
ret += `\n\n${' '.repeat(2)}[OptionalProps]: ${optionalPropertyNames.join(' | ')};`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the symbol can/should be defined as optional
ret += `\n\n${' '.repeat(2)}[OptionalProps]: ${optionalPropertyNames.join(' | ')};`; | |
ret += `\n\n${' '.repeat(2)}[OptionalProps]?: ${optionalPropertyNames.join(' | ')};`; |
(this will require regenererating snapshots)
optionalProperties.push(prop); | ||
} | ||
}); | ||
if (optionalProperties.length > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (optionalProperties.length > 0) { | |
if (optionalProperties.length > 0) { |
Btw I did some PoC of that generated client I mentioned in the tweet, and found few more things we first need to support in the generator. If you like this part of the codebase, I'll be happy to accept PRs for following:
Last step would be that "generated client". I have this simple script I used for the PoC, it generates entities, repositories and a client file which initializes the ORM and exports a map of services: import { MikroORM } from '@mikro-orm/core';
import { dirname } from 'node:path';
import fs from 'fs-extra';
import { fileURLToPath } from 'node:url';
// TODO
// - generate repositories in separate files
// - provide way to generate .js + .d.ts files
// - generate experimental client
// - allow detecting m:n with AI PK
// - import EM/repo from driver package instead of core
const orm = await MikroORM.init();
const dirName = dirname(fileURLToPath(import.meta.url));
const driver = { package: '@mikro-orm/better-sqlite', className: 'BetterSqliteDriver' };
// clean up everything in the folder except dot files
await fs.emptyDir(dirName + '/entities');
await fs.emptyDir(dirName + '/repositories');
const entityGenerator = orm.config.get('entityGenerator');
entityGenerator.identifiedReferences = true;
entityGenerator.bidirectionalRelations = true;
// orm.config.set('entityGenerator', entityGenerator);
const ret = await orm.entityGenerator.generate({
baseDir: dirName + '/entities',
save: true,
});
const metadata = orm.getMetadata().getAll();
const entities = Object.values(metadata).filter(meta => !meta.pivotTable && !meta.embeddable && !meta.virtual);
for (const meta of entities) {
const code = [
`import { EntityRepository } from '${driver.package}';`,
`import { ${meta.className} } from '../entities/${meta.className}.js';`,
'',
`export class ${meta.className}Repository extends EntityRepository<${meta.className}> { }`,
];
await fs.writeFile(`${dirName}/repositories/${meta.className}Repository.ts`, code.join('\n'));
}
const client: string[] = [];
const coreImports: string[] = [];
client.push(`import { ${driver.className} } from '${driver.package}';`);
for (const meta of entities) {
client.push(`import { ${meta.className} } from './entities/${meta.className}.js';`);
client.push(`import { ${meta.className}Repository } from './repositories/${meta.className}Repository.js';`);
}
client.push('');
coreImports.push('MikroORM');
client.push(`const orm = await MikroORM.init<${driver.className}>();`);
// we will need something like this, but that itself won't allow extension, we would have to check if something was
// discovered, discover the generated entities only if they are not provided and discovered already. user would extend
// the generated entities, this means we might have to mark them as base entities somehow, to get around duplicity warnings
client.push(`await orm.discoverEntity([${entities.map(meta => meta.className).join(', ')}]);`);
client.push(`const client = {`);
client.push(` orm,`);
client.push(` em: orm.em,`);
client.push(` schema: orm.schema,`);
client.push(` seeder: orm.seeder,`);
client.push(` migrator: orm.migrator,`);
function lcfirst(word: string) {
return word[0].toLowerCase() + word.substring(1);
}
for (const meta of entities) {
client.push(` ${lcfirst(meta.className)}: orm.em.getRepository(${meta.className}) as ${meta.className}Repository,`);
}
client.push(`};`);
client.push(`export default client;`);
client.push('');
client.unshift(`import { ${coreImports.join(', ')} } from '@mikro-orm/core';`);
console.log(client.join('\n'));
await fs.writeFile(dirName + '/client.ts', client.join('\n'));
console.info('Database client generated');
await orm.close(); This would work only when used with the generated entities, important bit here is finding a way how to make this work dynamically with user provided entitites (that would extend the generated ones). |
@B4nan thanks for merging. i am still getting familiar with this package's code. could potentially help with your idea, but would need to understand the use-case first. i saw you mentioned schema-first code generation like prisma. sounds interesting. i will say i've tried prisma briefly and am not completely sold on the idea of schema-first for an orm. in the case of prisma it is a new domain-specific language that they needed to support and a language-server to support it (not sure if first or third-party). anyways. i might not have understood your idea, correctly. i'll read through your tweet later. kinda swamped with work atm, heh. i'm sure that is familiar territory for you. |
i had another observation. i noticed that for nullable columns you are generating: @Property({ nullable: true })
aProperty?: string; should that be |
This really depends on the user, personally I dont like to define properties that way, it breaks reflect-metadata, and to be consistent, it kinda requires the property initializer. I dont mind having a config toggle for this, but I dont think we should change the defaults now. But maybe I just had some kind of aversion to this, in context of entity generator we can easily provide the type explicitly (so we dont have to care about reflect-metadata at all). I guess the form I dislike is Btw I made an issue based on the tweet, with initial PoC, maybe that will help you understand better: #3484 The point is to have the very same ORM support, just without creating the entities manually. Kinda like the prisma client, we would generate the entities (probably via CLI), the question is where and how it would work with IDE support if we put it into import { client } from '@mikro-orm/client';
const user = client.user.create({ name: '...', email: '...' });
await client.em.persist(user).flush(); Things like this would be possible, without much setup, just the ORM config so the CLI knows what to build. The |
@B4nan, for `first_name` varchar(255) null would this make the most sense? [OptionalProps]?: 'firstName';
@Property({ type: 'varchar', length: 255, nullable: true })
firstName!: string | null; |
If you want to use the @Property({ type: 'varchar', length: 255, nullable: true })
firstName: string | null = null; Personally I just go with optional properties and don't use |
My thinking is I want to represent more accurately what the orm will return once I run a select query. The @Property({ type: 'varchar', length: 255, nullable: true, default: 'hello' })
firstName: string | null ='hello'; Might be problematic for many cases. |
Keep in mind that you can still end up with |
Oh we are talking about what the entity generator does? I thought its a general question. I dont mind supporthing this in the generor, but rather opt-in. It should already produce the correct database default too. |
What do you mean by "It should already produce the correct database default too."? Can you give an example? |
But I guess we should also do the runtime default there for such properties. In latest version (maybe not yet released) its also possible to infer the runtime defaults (as long as the entity instance can be created without any arguments via constructor). edit: I guess its only about string defaults https://github.com/mikro-orm/mikro-orm/blob/master/tests/features/entity-generator/__snapshots__/EntityGenerator.test.ts.snap#L790-L794 |
…ies that define a default value