The Model API

The @teamkeel/sdk package is generated based on your schema and contains type-safe APIs for interacting with your models. These APIs are all available on the exported models object.

import { models } from "@teamkeel/sdk";

Given the following Keel schema:

model Post {
	fields {
     title Text
  }
 
  actions {
    write createPost()
  }
}
 
model Author {
  fields {
     name Text
  }
}

models.post and models.author will be available.

Creating a record

.create() receives a single parameter:

  • params - object representing all of the data you would like to insert. This must include all required fields on the model.

Fields that have a @default value in the schema will have their default values populated if no value is provided.

For @unique fields, inserted data will be checked for uniqueness.

import { models } from "@teamkeel/sdk";
 
const post = await models.post.create({
  title: "a post about star wars",
});
 
console.log(post.title); // => a post about star wars

The return type is Promise<Model>

Updating a record

.update() receives two parameters:

  • where - the query conditions to lookup records by.
  • values - the set of changes you want to update the record(s) with.

For @unique fields, updated data will be checked for uniqueness.

import { models } from "@teamkeel/sdk";
 
const where = {
  id: "123",
};
 
const values = {
  title: "a post about star wars",
};
 
const post = await models.post.update(where, values);
 
console.log(`Updated ${post.id}'s title`);

The return type is Promise<Model>

Deleting a record

.delete() receives a single parameter:

  • where - the conditions applied to the database query that will determine which records will be deleted
import { models } from "@teamkeel/sdk";
 
const deletedId = models.post.delete({
  id: "123",
});

Reading a single record

.findOne() receives a single parameter:

  • where - an object where the keys are any unique field on a model and the values are the unique values you want to look up by
import { models } from "@teamkeel/sdk";
 
const post = await models.post.findOne({
  id: "abc",
});

The return type is Promise<Model | null>

Reading multiple records

.findMany receives a single parameter:

  • params - Object of type FindManyParams

FindManyParams

Consists of four top level properties:

  • limit (number) - the number of rows to limit the result set by
    import { models } from "@teamkeel/sdk";
     
    const results = await models.post.findMany({
      limit: 5,
    });
  • offset (number) - the number of rows to offset by. Usually combined with limit and orderBy to return individual pages of data.
    import { models } from "@teamkeel/sdk";
     
    const results = await models.post.findMany({
      limit: 10,
      offset: 2,
      orderBy: {
        title: "asc",
      },
    });
  • where (Where) - an object where the keys are one of the fields on the model and the values are objects conforming to the WhereConditions type. See the WhereConditions reference for a full table of the available options for different field types below.
    import { models } from "@teamkeel/sdk";
     
    const postsBeginningWithA = await models.post.findMany({
      {
        where: {
          title: {
            startsWith: 'a'
          }
        }
      }
    });
  • orderBy (OrderBy) - an object used to define the sort order of the results returned from the database. The object must contain one property where the key is a known field name on the model, and the value must either be asc, desc.
    import { models } from "@teamkeel/sdk";
     
    const orderedByTitle = await models.post.findMany(
      orderBy: {
        title: 'asc'
      }
    );

WhereConditions

The WhereConditions type can be used with the findMany method to filter by a range of different operators:

Operator NameField TypesDescriptionUsage
startsWithTextMatch values beginning with the search value{ title: { startsWith: 'abc' }}
endsWithTextMatch values ending with the search value{ title: { endsWith: 'xyz' }}
oneOfTextMatch values against an array of possible values{ title: { oneOf: ['apple', 'orange'] }}
containsTextMatch values against a partial match within a string{ title: { contains: 'apple' } }
notEqualsText, Number, BooleanFind values where not matching search value{ title: { notEquals: 'apple' }}
equalsText, Number, Date, ID, BooleanFind values matching the search value{ title: { equals: 'orange' }}
greaterThanNumberFind values where the value is greater than the search value{ rating: { greaterThan: 5 }}
greaterThanOrEqualsNumberFind values where the value is greater than or equal to the search value{ rating: { greaterThanOrEquals: 5 }}
lessThanNumberFind values where the value is less than the search value{ rating: { lessThan: 5 }}
lessThanOrEqualsNumberFind values where the value is less than or equal to the search value{ rating: { lessThanOrEquals: 5 }}
beforeDateFind values where the date is before the search date{ createdAt: { before: new Date(2020, 1, 1) }}
onOrBeforeDateFind values where the date is equal to or before the search date{ createdAt: { onOrBefore: new Date(2020, 1, 1) }}
afterDateFind values where the date is after the search date{ createdAt: { after: new Date(2020, 1, 1) }}
onOrAfterDateFind values where the date is equal to or after the search date{ createdAt: { onOrAfter: new Date(2020, 1, 1) }}

Nested Create / Update

⚠️

The Model API doesn't currently support creating or updating nested relations, but will in the near future.

Until then, you can create related models yourself using the Model API:

functions/createPost.ts
import { CreatePost, models } from '@teamkeel/sdk';
 
export default CreatePost({
  beforeWrite: async (ctx, inputs) => {
    const {
      author: { name }
    } = inputs;
 
    // create the new author prior to creating the post
    const author = await models.author.create({ name });
 
    return {
      ...inputs,
      authorId: author.id
    }
  }
});

Rich Data Types

The SDK also provides class implementation for certain field types. These further simplify the interaction with these rich data types:

Field TypeDescriptionSDK Class
DurationA time interval (ISO 8601 format)Duration
FileA file input, supplied as a data URLFile and InlineFile

Duration

This class provides utility methods for working with durations, such as:

  • Duration.fromISOString(s): Static constructor used to create an instance of Duration.
  • toISOString(): Converts the duration to an ISO 8601 string.
  • toPostgres(): Converts the duration to PostgreSQL's interval format.

Creating and retrieving records with durations:

import { WriteCustomFunction, models, Duration } from "@teamkeel/sdk";
 
export default WriteCustomFunction(async (ctx, inputs) => {
  const post = await models.post.create({ readTime: Duration.fromISOString("PT1H") });
  const readPost = await models.post.findOne({ id: post.id });
  return {
    readTime: readPost.readTime.toISOString(),
  };
});

When using a duration field as an input to a custom function, the input will automatically be an instance of the Duration class:

message DurationInput {
    duration Duration
}
export default DurationInputFunction(async (ctx, inputs) => {
  console.log(inputs.duration.toISOString());
 
  const post = await models.post.create({ readTime: inputs.duration });
  return {
    readTime: post.readTime.toISOString(),
  };
});

Duration fields are also returned as an instance of the Duration class when using the query builder:

const posts = await useDatabase()
.selectFrom("posts")
.selectAll()
.execute();
 
return {
  readTime: posts[0].readTime.toISOString()
};

File and InlineFile

Keel provides a File TypeScript class in the functions runtime and full support for reading and writing files, which means that they can be used in read and write custom functions, action hooks, jobs and subscriber functions. For more information please refer to the Files documentation.