Skip to main content

Fields

A field represents each unique item that's part of the object. Each implementation of field configures its representation in the database, TypeScript generated code, and in GraphQL.

The framework comes with the following built-in fields:

  • UUIDField for uuid types
  • IntegerField for int types
  • FloatField for float types
  • BooleanField for bool types
  • StringField for string types
  • TimestampField for storing timestamps with and without timezone via the accessors TimestampType and TimestamptzType
  • TimeField for storing time with and without timezone via the accessors TimeType and TimetzType
  • DateField for storing dates
  • EnumField for storing an enum e.g. restricting the value to a preconfigured list of strings

Each implementation has the ability to validate and format data in a consistent format so that the code to handle common transformations or higher level types is written only once.

This is manifested in the following types which exist in other packages that ship with the framework

  • EmailType for validating and formatting emails. It uses the email-addresses npm package and always stores the email in lower case regardless of the case inputted by the user
  • PhoneNumberType which uses the libphonenumber-js package to validate the phone number and stores it in a consistent format.
  • PasswordType which hashes and salts passwords by default using bcrypt before storing in the database.

It's easy to write your own custom types that can be written once and shared across different schemas and/or projects.

It's possible to configure fields based on the options provided. For example, a password field with a minimum length:

  password: PasswordType().minLen(10);

or a username field configured as follows:

  username: StringType({ minLen:3, maxLen:20 }).toLowerCase().trim(),

Because we want to support chaining and make the API intuitive, there tends to be an associated FooType factory that goes with the FooField to make things like above readable and easy to use.

Options

nullable

If field is nullable. If so, a NULL modifier is added to the database column. The GraphQL field generated is also nullable and the generated TypeScript type is nullable e.g. string | null .

storageKey

If provided, used as the name of the database column. Otherwise, a snake_case version of the name is used. e.g. first_name for FirstName or firstName or firstname .

Can also be used to rename a field and not affect the database e.g. from StringType({name:"userID"}) to StringType({name:"accountID", storageKey:"user_id"}) changes the field to accountID in the Ent but keeps the column as user_id .

serverDefault

default value stored on the database server. e.g.

needsHelp: BooleanType({ serverDefault: "FALSE" }),

//or

createdTime: TimetzType({serverDefault: "NOW()"}),

unique

The database column is unique across the table. adds a unique index to the database

hideFromGraphQL

field is not exposed to GraphQL

private

This field shouldn't be exposed in the public API of the ent. As an implementation detail, it's protected in the generated base class so that any subclasses can assess it.

sensitive

This field shouldn't be logged if we're logging fields e.g. password fields, social security or any other sensitive data where we don't want the value to show up in the logs.

graphqlName

If provided, used as the name of the field in GraphQL. Otherwise, a lowerPascalCase version of the name is used. e.g. firstName for FirstName or firstName or firstname .

index

Adds an index on this column to the database.

foreignKey

Adds a foreign key to another column in another table.

creatorID: UUIDType({ foreignKey: { schema: "User", column: "id" } }),

adds a foreignKey on the creator_id column on the source table that references the id column in the users table.

By default, foreignKey creates an index on the source table because we expect to be able to query via this foreign

fieldEdge

Only currently works with UUIDType . Indicates that an accessor on the source schema should be generated pointing to the other schema.

For example, given the following schemas:

src/schema/user_schema.ts
const UserSchema = new EntSchema({
fields: {},
edges: [
{
name: "createdEvents",
schemaName: "Event",
}
],
});
export default UserSchema;

src/schema/event_schema.ts
const EventSchema = new EntSchema({
fields: {
creatorID: UUIDType({
fieldEdge: { schema: "User", inverseEdge: "createdEvents" },
}),
},
});
export default EventSchema;
  • we have a 1-many Edge from User to Event for events the User has created.
  • we store the creator of the Event in the creatorID field of the Event.

The fieldEdge tells us that this field references schema User and edge createdEvents in that schema. That ends up generating a creator accessor in the Ent and GraphQL instead of creatorID accessor.

const event = await event.loadCreator();
type Event implements Node {
creator: User
}

primaryKey

adds this column as a primary key on the table. There can be only one primary key on a table so if using EntSchema or EntSchemaWithTZ, can't use this.

disableUserEditable

indicates that this can't be edited by the user. must have a defaultValueOnCreate field if set. If set, we don't generate a field in the action or GraphQL mutation.

defaultValueOnCreate

method that returns a default value if none is provided when creating a new instance of the object. For example, a completed field in a simple todo app with a default value of false:

  completed: BooleanType({
index: true,
defaultValueOnCreate: () => {
return false;
},
}),

The defaultValueOnCreate method is passed 2 arguments that can be used to compute the value:

This can be used to compute a value at runtime. For example, to default to the Viewer in the todo app above, you can do:

  creatorID: UUIDType({
foreignKey: { schema: "Account", column: "id" },
defaultValueOnCreate: (builder) => builder.viewer.viewerID,
}),

This can simplify your API so that you don't have to expose the creatorID above in your GraphQL mutation.

PS: It's recommended to either use implicit typing here or if using explicit typing, to type with Builder<Ent, Viewer> or Builder<NameOfEnt, Viewer> as opposed to the generated FooBuilder so as to not run into issues with circular dependencies.

defaultValueOnEdit

method that returns a default value if none is provided when editing an instance of the object.

Like defaultValueOnCreate above, it's passed the builder and input.

defaultToViewerOnCreate

Boolean. Shorthand to default to the viewer when creating an object if field not provided. The following are equivalent:

  creatorID: UUIDType({
foreignKey: { schema: "Account", column: "id" },
defaultToViewerOnCreate: true,
}),
  creatorID: UUIDType({
foreignKey: { schema: "Account", column: "id" },
defaultValueOnCreate: (builder) => builder.viewer.viewerID,
}),

This exists because it's a common enough pattern for a field to default to the logged in Viewer.

polymorphic

Only currently works with UUIDType . Indicates that this id field can represent different types and we need to keep track of the type so that we know how to find it.

We end up generating a derivedField to represent the type of the object set.

If not true and a list of types is instead passed, only types that matches the given types are allowed to be passed in.

derivedFields

fields that are derived from this one. very esoteric. see polymorphic

Field interface

The Field interface is as follows:

interface Field extends FieldOptions {
// type of field: db, typescript, graphql types encoded in here
type: Type;

// optional valid and format to validate and format before storing
valid?(val: any): boolean;
format?(val: any): any;

// value to be logged
logValue(val: any): any;
}

type

Determines database, TypeScript, and GraphQL types.

valid

If implemented, validates that the data passed to the field is valid

format

If implemented, formats the value passed to the field before storing in the database. This ensures that we have consistent and normalized formats for fields.

logValue

Provides the value to be logged when the field is logged. For sensitive values like passwords, SSNs, it doesn't log the sensitive value.

Postscript

PS: the PasswordType field is private, hidden from GraphQL and sensitive by default.