Replies: 5 comments 1 reply
-
Hi @brudil ! I've only done a few ~6 months stints with Rails/AR, so don't deeply understand the rails gem ecosystem, but agree that in principle the Joist/ORM entities should be usable in a generic way. What specifically about codegen makes this hard? I guess looking at https://github.com/buhrmi/actionstore, they are adding methods to the entity, i.e. Which, yeah, not sure that is a Joist limitation per se, and more that TypeScript doen't have that sort of magic yet (microsoft/TypeScript#4881. :-( I see you're prototyping the Personally, I would probably just not add the methods to the entities themselves, but do something like Googling around a bit, the internet suggestions that something like: type Constructor<T = {}> = new (...args: any[]) => T;
interface ActionStore {
pushAppend(): void;
}
function ActionStore<TBase extends Constructor>(base: TBase, context: ClassDecoratorContext) {
return class extends base {
pushAppend = () => console.log("New method added!");
};
}
@ActionStore
class MyClass {}
interface MyClass extends ActionStore {}
const c = new MyClass();
c.pushAppend(); With the new TC39 stage 3 decorators--the only con being that just But, dunno, those are my two initial ideas (a I noticed your diagram also mentioned " We don't have our production FE code using the Joist domain types (it uses GraphQL), but we have been putting the
And then using those on the frontend to write "fixtures" that are just |
Beta Was this translation helpful? Give feedback.
-
Appreciate the response, apologies for that slightly terse rambling dump of thoughts. I think I might have failed to explain the intent/idea here. The gist is
I'll be honest I've way more experience in Django than rails, so I might have confused things referencing ActionStore which I haven't used. Django as a framework consists of 'apps' - these contain the cross section of a feature of your app. I'll expand on an example from the django docs;
A django project contains multiple apps which fit this shape, say a Todo project might have The bit that I'm curious in is the fact that Django Apps can be external packages. This is really nice because it means you can use django-notifications and have a Notification Model that you import from that package and can use! And it's more or less turn-key. I can take This isn't just great for open sourcing reusable apps, but modularising internal apps. Obviously there's a fair bit of frameworkly stuff going on here, handling migrations etc which is out of scope for Joist. For orms like Django's which is entirely class based, the external Notification Model is a class using the Django API, hand written; class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30) So as long as the externalised model is compatible with the Django version it's used with, you can just import it and go. Where I see this breaking down in a joist world. I'll use Notifications as an example; let's say in this magical joist-first framework which supports Django-like apps. I'll call it Zest. So I want to use Joist needs a schema to generate from. And codegen is entirely coupled to each joist version (i.e don't expect codegen output from This leads to the following assumptions:
So there's a couple of goals here:
This is what my first idea was (badly) attempting to denote. This is where the mixin idea came in.
The user-land class - which extends from the codegen class need to support both the external codegen (types + tests), and the root project codegen. As a super simple example; This would be the output in a normal joist app: class Notification extends NotificationCodegen {
get isRead() { return this.readAt !== undefined }
} My line of solutionising was thinking about how we can make the codegen extends dynamic. In the external package // NotificationMixin.ts
function NotificationMixin(codegen) {
return class extends codegen {
get isRead() { return this.readAt !== undefined }
}
} In the root project, we would be able to utilise the minxin class Notification extends NotificationMixin(NotificationCodegen) {
// and custom stuff the root project cares about that is not known to the external app.
} Then the external app, is provided with the root project This doesn't touch upon the frontend, but external modules exposing entities which they utilise themselves. Hopefully I've cleared the problem space up a bit. If you couldn't care less about this, please don't feel the need to spend time thinking about this - I'm just playing around here because it's what I think is an interesting problem to solve. |
Beta Was this translation helpful? Give feedback.
-
Ah I got it; I have actually thought of similar ideas, i.e. shipping a "comments" module or "attachments" module that, if you include these tables in your schema, a Is that a good articulation of the use case? Like:
I agree it would be really neat to support this sort of setup, and I'm interested in making it happen. Fwiw besides annotations & wrapper functions, I did forget another option: codegen plugins. I.e. we already have a joist-graphql-codegen plugin that admittedly doesn't touch the entities themselves, but just does further scaffolding of GraphQL artifacts based on the schema. So, dunno, maybe we could have in As you said (and assuming I'm following the use case correctly), we'd need to make sure the Just given how coupled the Am I following the use case so far/well enough? |
Beta Was this translation helpful? Give feedback.
-
Yeah, I think we're on the same page. If we take it back from implementation details, and consider the holy grail; Say I have a system that say handles attachments, and I want to make it reusable across multiple projects. This package is able to provide:
So for example;
// migrations.ts - joist doesn't care about this, but might be part of the framework, or it's part of the documents "framework-attachments" expects x table, with at least x, y columns.
export migrations = [knexmigration, knexmigration]; // Attachment.ts
class MailRecord extends ?? {
// holds domain logic for the entity
// methods
loadBuffer() { return FileAdapter.from(this).loadBuffer() }
// getters
get size() { return FileSize.fromKB(this.bytes) }
// joist api too, such as reactive field etc
}
// config api too // logic.ts - this is frameworky but to demostrate how a package could use the entity too
export function sendMail(mailer) {
// send mail etc
em.create(MailRecord, mailer);
} // route.ts - this is frameworky but to demostrate how a package could use the entity too
export rotues = [
{
path: 'webhook/mailer/ingest',
handler: (req, reply) => {
const mail = await em.load(MailRecord, req.body.mailId);
mail.status = req.body.status;
return reply.code(200);
}
}
]; Hopefully I haven't over complicated thing here, but imo the challenge is utilising the project codegen file for consumers, but generating another for package authors, for tests and typing. The nature of classes makes this hard, hence codegen, mixins or some other magic might be required. |
Beta Was this translation helpful? Give feedback.
-
Moving the package's entity file (zest-comment/Comment.ts) to the project's codegen creates a few concerns for me around special semantics for module scoping; Comment.ts might
Perhaps it's the best way forward, but it definitely would add a fair bit of friction to package authors and consumers which would be better to avoid. Latter question is an interesting one. I can see it being used for both cases. I've imagined a "subset" stratergy - essentially inline with TypeScript's structual typing, where the external package should be able to operate on the schema it defines, but if there is additional domain/schema defined on the entity by the main project, it wouldn't cause issues and is usable by the main project. This could be very powerful, where a external package could expect logic to be defined by the project. I've started to consider some ideas around codegen for just types which I'll contine to play around with. |
Beta Was this translation helpful? Give feedback.
-
I'll just preface this by saying this is really only for discussion, it's not entirely joist scope here.
We've been reworking our application packaging. One of the great things about Rails and Django is the ability for gems/apps to utilise the their orm. Things like ActionStore and django-notifications-hq.
Obviously there's a ton of stuff outside of Joist here, but the nature of codegen within Joist does make this more tricky.
Just curious if there's been any thoughts/patterns around this. I've got a couple of ideas to play around with, largely tied to our IaC container, but nothing that feels right just yet.
Pure ideation here
Beta Was this translation helpful? Give feedback.
All reactions