Primitives.org.ai

Nouns

Entity type definitions with linguistic derivation

Nouns define entity types with full linguistic support. Every noun includes singular, plural, and slug forms that are automatically derived from the name.

What is a Noun?

A Noun is a type definition that describes an entity category:

interface Noun {
  name: string        // 'Post', 'Author', 'BlogPost'
  singular: string    // 'post', 'author', 'blog post'
  plural: string      // 'posts', 'authors', 'blog posts'
  slug: string        // 'post', 'author', 'blog-post'
  description?: string
  schema?: Record<string, FieldDefinition>
  createdAt: Date
}

Linguistic Derivation

Nouns are automatically derived from PascalCase names:

import { deriveNoun } from 'digital-objects'

deriveNoun('Post')
// { singular: 'post', plural: 'posts', slug: 'post' }

deriveNoun('BlogPost')
// { singular: 'blog post', plural: 'blog posts', slug: 'blog-post' }

deriveNoun('Category')
// { singular: 'category', plural: 'categories', slug: 'category' }

deriveNoun('Person')
// { singular: 'person', plural: 'persons', slug: 'person' }

Pluralization Rules

The pluralization engine handles common English rules:

RuleExampleResult
Defaultpostposts
-s, -x, -z, -ch, -shboxboxes
Consonant + ycategorycategories
-fleafleaves
-feknifeknives
Irregularpersonpeople
import { pluralize, singularize } from 'digital-objects'

// Standard pluralization
pluralize('post')      // 'posts'
pluralize('box')       // 'boxes'
pluralize('category')  // 'categories'
pluralize('leaf')      // 'leaves'

// Irregular plurals
pluralize('person')    // 'people'
pluralize('child')     // 'children'
pluralize('mouse')     // 'mice'

// Multi-word phrases (pluralizes last word)
pluralize('blog post')     // 'blog posts'
pluralize('product category') // 'product categories'

// Reverse operation
singularize('posts')   // 'post'
singularize('people')  // 'person'
singularize('categories') // 'category'

Defining Nouns

Basic Definition

import { MemoryProvider } from 'digital-objects'

const provider = new MemoryProvider()

// Minimal definition - forms are auto-derived
const post = await provider.defineNoun({ name: 'Post' })
// { name: 'Post', singular: 'post', plural: 'posts', slug: 'post', ... }

Custom Forms

Override auto-derived forms when needed:

// Custom singular/plural for irregular words
await provider.defineNoun({
  name: 'Person',
  singular: 'person',
  plural: 'people',  // Override auto-derived 'persons'
})

// Custom description
await provider.defineNoun({
  name: 'BlogPost',
  description: 'A blog article or story',
})

With Schema

Define the shape of Things that implement this Noun:

await provider.defineNoun({
  name: 'Post',
  schema: {
    title: 'string',
    content: 'markdown',
    publishedAt: 'datetime?',  // Optional field
    author: 'Author.posts',     // Relation to Author
    tags: '[Tag.posts]',        // Array relation to Tag
  },
})

Field Types

Schemas support various field types:

TypeDescriptionExample
stringText content'title'
numberNumeric value'views'
booleanTrue/false'published'
dateDate only'birthday'
datetimeDate and time'createdAt'
jsonJSON object'metadata'
markdownMarkdown text'content'
urlURL string'website'

Optional Fields

Append ? for optional fields:

schema: {
  title: 'string',       // Required
  subtitle: 'string?',   // Optional
}

Relations

Define relationships using the NounName.backref syntax:

// One-to-many: Post belongs to Author, Author has many posts
await provider.defineNoun({
  name: 'Post',
  schema: {
    author: 'Author.posts',  // Post -> Author, backref is 'posts'
  },
})

// Many-to-many via array relation
await provider.defineNoun({
  name: 'Post',
  schema: {
    tags: '[Tag.posts]',  // Post has many Tags, Tag has many posts
  },
})

Querying Nouns

Get a Noun

const noun = await provider.getNoun('Post')
if (noun) {
  console.log(noun.singular)  // 'post'
  console.log(noun.plural)    // 'posts'
  console.log(noun.schema)    // { title: 'string', ... }
}

List All Nouns

const nouns = await provider.listNouns()
for (const noun of nouns) {
  console.log(`${noun.name}: ${noun.description ?? 'No description'}`)
}

Use Cases

Auto-Generated UI Labels

const noun = await provider.getNoun('BlogPost')

// Form labels
const label = noun.singular           // 'blog post'
const pluralLabel = noun.plural       // 'blog posts'

// URL paths
const listPath = `/${noun.slug}s`     // '/blog-posts'
const createPath = `/${noun.slug}/new` // '/blog-post/new'

API Route Generation

const nouns = await provider.listNouns()

for (const noun of nouns) {
  // Generate REST endpoints
  app.get(`/api/${noun.slug}s`, listHandler(noun))
  app.get(`/api/${noun.slug}s/:id`, getHandler(noun))
  app.post(`/api/${noun.slug}s`, createHandler(noun))
  app.patch(`/api/${noun.slug}s/:id`, updateHandler(noun))
  app.delete(`/api/${noun.slug}s/:id`, deleteHandler(noun))
}

Schema Introspection

const noun = await provider.getNoun('Post')

if (noun.schema) {
  for (const [field, type] of Object.entries(noun.schema)) {
    console.log(`${field}: ${type}`)
    // title: string
    // content: markdown
    // author: Author.posts
  }
}

AI Content Generation

const noun = await provider.getNoun('BlogPost')

const prompt = `Generate a ${noun.singular} with the following fields:
${Object.entries(noun.schema ?? {})
  .map(([field, type]) => `- ${field} (${type})`)
  .join('\n')
}

Description: ${noun.description ?? 'A blog post'}
`

NounDefinition Type

The input type for defining nouns:

interface NounDefinition {
  name: string              // Required: PascalCase name
  singular?: string         // Optional: override singular form
  plural?: string           // Optional: override plural form
  description?: string      // Optional: human-readable description
  schema?: Record<string, FieldDefinition>  // Optional: field definitions
}

Best Practices

Naming Conventions

// Good: PascalCase, singular
await provider.defineNoun({ name: 'Post' })
await provider.defineNoun({ name: 'BlogPost' })
await provider.defineNoun({ name: 'UserProfile' })

// Avoid: already plural
await provider.defineNoun({ name: 'Posts' })     // Use 'Post'

// Avoid: lowercase
await provider.defineNoun({ name: 'post' })      // Use 'Post'

// Avoid: snake_case
await provider.defineNoun({ name: 'blog_post' }) // Use 'BlogPost'

Schema Design

// Prefer explicit types over json
await provider.defineNoun({
  name: 'Post',
  schema: {
    title: 'string',
    content: 'markdown',
    metadata: 'json',  // Only when structure is truly dynamic
  },
})

// Use relations instead of storing IDs
await provider.defineNoun({
  name: 'Post',
  schema: {
    author: 'Author.posts',  // Not: authorId: 'string'
  },
})

Descriptions

// Always add descriptions for AI-assisted features
await provider.defineNoun({
  name: 'Post',
  description: 'A blog article written by an author',
  schema: {
    title: 'string',     // Field descriptions come from the noun
    content: 'markdown',
  },
})

Next Steps

  • Verbs - Define actions with conjugations
  • Things - Create instances of nouns
  • Actions - Record events between things
Was this page helpful?

On this page