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
Data Model Classes #2092
Data Model Classes #2092
Conversation
High-level but is there a restriction or constraint that requires us to have models defined within |
Nice thing about it being in context is that we can use it from anywhere, not just within request handlers. I originally put them under |
Yeah, that's pretty important.
I actually don't think that's verbose at all. We will likely destructure the context object like so: const someBusinessLogicFn = ({ org, models }: ReqContext, exp: ExperimentInterface) => {
// ...
} Or const someBusinessLogicFn = (context: ReqContext, exp: ExperimentInterface) => {
const { models: { factMetrics }, org } = context;
const factMetric = await factMetrics.findById(...);
// ...
} |
Your preview environment pr-2092-bttf has been deployed. Preview environment endpoints are available at: |
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.
Non-blocking comments / questions
export type BaseSchema = z.ZodObject< | ||
{ | ||
id: z.ZodString; | ||
organization: z.ZodString; |
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.
How do we foresee using this with the User model or Organization model?
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.
Yeah, those won't work with the current design. Maybe we can make a common base class that does core things like managing indexes and querying, but doesn't do any validation or have any assumptions about data structure.
>; | ||
limit?: number; | ||
skip?: number; | ||
} = {} |
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.
Would we not want to enforce a limit by default here?
For the queries we use today that don't enforce a limit, each could individually specify limit: undefined
Overview
New data model classes to enforce best practices and reduce boilerplate code required to create new models. For an initial test, this PR converts the
FactMetricModel
to use the new class.Example
Everything starts from a single Zod schema, representing the whole object:
From this, you can export types to use throughout the front-end and back-end like we do today.
Create the data model class based on the schema.
Add it to the Request Context class:
And then, you can access it anywhere you have context and use the built-in methods:
Features
Built-in Public Methods
The following public methods are created automatically:
getAll()
getAllByProject(project)
getById(id)
getByIds(ids)
create(data)
update(existing, changes)
updateById(id, changes)
delete(existing)
deleteById(id)
The
create
,update
, andupdateById
methods accept either strongly typed arguments orunknown
. Everything is validated before saving, so this is safe. This is useful when you want to pass inreq.body
directly without manually doing validation.Custom Methods
You can add more tailored data fetching methods as needed by referencing the
_findOne
and_find
methods. There are similar protected methods for write operations, although those are rarely needed.Here's an example:
Note: Permission checks, migrations, etc. are all done automatically within the
_find
method, so you don't need to repeat any of that in your custom methods. Also, theorganization
field is automatically added to every query, so it will always be multi-tenant safe.Hooks
The following hooks are available, letting you add additional validation or perform trigger-like behavior without messing with data model internals. All of these besides
migrate
are async. Define these in your child class and they will be called at appropriate times.migrate(legacyObj): newObj
customValidation(obj)
(called for both update/create flows)beforeCreate(newObj)
afterCreate(newObj)
beforeUpdate(existing, updates, newObj)
afterUpdate(existing, updates, newObj)
beforeDelete(existing)
afterDelete(existing)
Here's an example:
Built-In Best Practices
Includes the following best practices:
organization
to every query)dateUpdated
anddateCreated
up-to-dateFuture TODOs
shared
packagedatasource
)Testing