Annotations
This section describes the various annotations supported by Noun & Verb, their purpose, use and customization capabilities if available.
We assume a familiarity with the Prisma schema. All annotations discussed here apply to either the model or fields of a model. Depending on the context, it's possible to have more than one annotation on a field. Invalid combinations of annotations should trigger an error. Please report any errors discovered.
Noun & Verb annotations are specified as doc comments in the Prisma schema. These have a special form - a triple-slash: ///.
Since doc comments can be multi-line strings, and we support multiple annotations per field, each annotation begins with an @ symbol immediately following the /// and the annotation keyword.
Some of the annotations support arguments.
1. @readOnly
- scope: field
- arguments: none
- constraints: cannot be used with
@writeOnly,@createOnlyor@hidden.
model User {
/// @readOnly
id String @id @default(cuid())
name String
username String
email String
password String
/// @readOnly
createdAt DateTime @default(now())
/// @readOnly
updatedAt DateTime @updatedAt
cart Cart?
}
Applies to fields created/updated on the server, but should never be written by a client.
Fields with the @readOnly annotation are missing from any create/update types as specified in the mirrored GraphQL API.
The field values can still be written on the server side.
Typical examples are Ids, createdAt/updatedAt timestamps etc.
Most @readOnly timestamps are generated automatically on the server, however that is not a constraint.
2. @writeOnly
- scope: field
- arguments: none
- constraints: cannot be used with
@readOnly,@createOnlyor@hidden.
model User {
id String @id @default(cuid())
name String
username String
email String
/// @writeOnly
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
cart Cart?
}
Applies to fields written by the client during create/update operations, but are not available in the read operations.
Fields with the @writeOnly annotation are missing from any read types as specified in the mirrored GraphQL API.
These fields are only absent on the read operation on the GraphQL API clients. They are still available on the server side to write to at developer discretion.
Typical example is the password field. Making it readable might be considered a security risk.
3. @createOnly
- scope: field
- arguments: none
- constraints: cannot be used with
@readOnly,@writeOnlyor@hidden.
model User {
id String @id @default(cuid())
name String
/// @createOnly
username String
email String
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
cart Cart?
}
Applies to fields written by the client during create operations, but are not available in update/read operations.
Fields with the @createOnly annotation are missing from any update or read types as specified in the mirrored GraphQL API.
Typical example is the username field. Changing a username might require the account to be deleted and recreated.
4. @hidden
- scope: field
- arguments: none
- constraints: cannot be used with
@readOnly,@writeOnlyor@createOnly.
model User {
id String @id @default(cuid())
name String
username String
email String
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
/// @hidden
serverSecret String
cart Cart?
}
Applies to fields that are only available on the server and completely absent from the client.
Typically used for storing server side state and/or hiding deprecated/unused fields from the client.
5. @scalar
- scope: field
- arguments: [required] name of scalar
- constraints: none
model User {
id String @id @default(cuid())
name String
username String
/// @scalar Email
email String
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
cart Cart?
}
Database type systems offer a limited set of capability, designed to help optimize for the storage and search. Application domains require a very rich set of types to be practical.GraphQL offers a very elegant solution to this problem in Scalars
Noun & Verb uses the scalar annotation - by specifying a scalar with a name, the necessary scaffolding for a GraphQL scalar is generated. This supports the ability to validate, serialize and deserialize a field value.
When the scalar names matches on of the values supported by Validator.js, Noun & Verb is able to support these 76 scalar types.
When an unknown scalar is detected, the necessary scaffolding is generated, with a file:line-number bread-crumb that allows
6. @seed
- scope: model
- arguments: none
- constraints: N/A
Database entity-relationship graphs are cyclical by definition. This makes it hard to populate the database with seed data, since it's not clear how to break cycles.
Noun & Verb relies on @seed annotation, applied to models, to define the starting points to root acyclic relationship trees.
It's legal to apply the @seed annotations to multiple models. Though for large graphs, caution is recommended, since this is a depth first walk and can result in very large amounts of data being generated, resulting in a long running seed-script.
That said, by default, @seed creates 100 rows at the root level and between 0-20 elements on each relationship encountered in the tree. This is done with an eye to enable testing UIs. 100 & 20 should typically trigger pagination at the primary and secondary UI levels. Also, 0 should trigger the empty case (only on the secondary UI level)
The seed annotation works with the @mock annotation described next to do it's magic.
7. @mock
- scope: field
- arguments: [required] faker.[faker-function-name] or mocker.[custom-mock-function-name]
- constraints: none
Similar to the @scalar annotation which validate field values across the API, the @mock annotation generates custom mock values for fields when requested by the seed script or tests.
@mock values are a bit more nuanced in that there are multiple hints available. Noun & Verb uses these in a sequence, with the intent of getting the most appropriate value that it can find.
if ( mockAnnotationExists ) {
if ( fakerOrBuiltInMethod ) {
return fakerOrBuiltInMethod()
} else {
return customMockerMethod()
}
} else if (defaultValueSpecified) {
return defaultValue
} else if (isAnEnum) {
return aRandomSelectionFromPossibleEnumValues
} else if (typeof field === 'String') {
return
}
return random(String|Int|Float|Boolean)
}
@mock is build to support faker.js out of the box. Similar to how custom @scalar fields are supported,