Migration Guide for Function Hooks
If you have a schema that was authored prior to the introduction of the Action and Function Hooks syntax, luckily migrating your schema across to use the new syntax is straightforward.
Start here
You will need to upgrade your Keel CLI to the latest version:
brew update && brew upgrade keel
A New Schema Keyword
Previously, you'd define operations
and functions
within separate blocks:
model Post {
fields {
title Text
}
functions {
get getPost(id)
}
operations {
create createPost() with(title)
}
}
Now both operations
and functions
are combined into a single actions
block:
model Post {
fields {
title Text
}
actions {
get getPost(id) {
@function
}
create createPost() with(title)
}
}
You may also notice that the getPost
function has been annotated with the @function
attribute.
Any time you want to write your own code for an action instead of using the built-in behaviour, apply the @function
attribute, and run keel generate
to create the corresponding {name}.ts
file in your functions/
directory.
Migrating your functions code
With the up to date Keel CLI, you need to re-run Keel's code generator to get your project up to date:
keel generate
This will update any supporting JavaScript dependencies to the latest version, and regenerate Keel's SDK to reflect the hooks changes. After running keel generate
, there should be some changes to your package.json
and package-lock.json
files.
Introducing the Hooks
The new Hooks API introduces four new lifecycle methods that can be used to modify the way functions work. A custom function no longer takes a callback function where you would write your implementation; instead the function receives an object with the following properties that you can configure:
beforeWrite
: useful for when you want to modify the values due to be written to the database prior to insertion / update.afterWrite
: useful for when you want to perform side effects on the data that was successfully inserted/updated. Can be used to run custom permissions checks or create other data.beforeQuery
: can be used to extend the default query that will be executed against the database, or alternatively you can replace the query entirely with your own custom query.afterQuery
: can be used to run custom permissions checks on the data returned from the database, or to perform other side effects.
You can read more about each hook in the Hooks Documentation.
An example migration
Previously a function would look something like the below:
import { CreatePost, models } from '@teamkeel/sdk';
export default CreatePost(async (ctx, inputs) => {
const post = await models.post.create({
...inputs
});
return post;
})
Now most of the code above is written for you automatically! We have instead introduced the concept of Hooks, which allow you to "hook in" to the underlying default implementation.
The example function above isn't doing anything special that the default implementation of the function isn't doing already, so it would become:
import { CreatePost, models } from '@teamkeel/sdk';
export default CreatePost();
But what if you wanted to modify some of the request inputs prior to inserting into the database? You could hook in to the beforeWrite
hook to do that:
import { CreatePost, models } from '@teamkeel/sdk';
export default CreatePost({
beforeWrite: async (ctx, inputs) => {
return {
...inputs,
title: `${inputs.title} (${inputs.subTitle})`
}
}
})
FAQs
Q: How can I migrate my function that requires creation of other relationship data (such as a foreign key) prior to insertion?
A: Imagine the scenario where you have two models: Author
and Post
. A Post
needs to be associated with an author in our example, and we want to make it possible to create the Author
and Post
at the same time. You could use a beforeWrite
hook to create the author first, obtain the Author's ID and pass it in when creating the post:
import { CreatePostAndAuthor, models } from '@teamkeel/sdk';
export default CreatePostAndAuthor({
beforeWrite: async (ctx, inputs) => {
// the input definition in the schema would include author.name
const {
author: { name }
} = inputs;
const author = await models.author.create({ name });
// return the values to be written to the database
// including the id of the author we just created
return {
title: inputs.title,
authorId: author.id,
}
}
})
Q: How can I migrate my function that needs to create other data in the database after creating my model?
A: You can use the afterWrite
hook to perform side effects such as creating related data:
import { CreatePost, models } from '@teamkeel/sdk';
export default CreatePost({
afterWrite: async (ctx, inputs, post) => {
// the post parameter is the value retrieved from the database
// including its ID.
await models.comments.create({
postId: post.id,
content: 'I found this really interesting!',
identityId: ctx.identity.id,
})
}
})
Custom functions
If you have any Custom Functions defined in your schema, then no action is required to migrate these, as read
and write
action types do not leverage the Hooks API.
Console
You won't be able to use some functionality within the Console (opens in a new tab) such as the Schema Visualizer and API Explorer until you have fixed your schemas and deployed the changes.
Getting help
Please read over our Hooks guide, and if you have any questions, don't hesitate to reach out to us for a one-on-one session where we can help you migrate your Keel projects.