Overview
Dynamic types are automatically generated from your ExuluContext instances. Each context you define creates a complete set of GraphQL types, queries, and mutations tailored to your data structure.
Dynamic types make Exulu IMPβs GraphQL API adapt to your implementation without requiring manual schema updates.
Type generation
When you create an ExuluContext:
const documentationContext = new ExuluContext({
id: "documentation",
name: "Documentation",
description: "Product documentation",
tableName: "docs_items",
fields: [
{ name: "title", type: "string", required: true },
{ name: "url", type: "string" },
{ name: "category", type: "string" }
],
embedder: myEmbedder
});
Exulu IMP generates:
- Main type -
documentation
- Input type -
documentationInput
- Filter type -
FilterDocumentation
- Queries -
documentationById, documentationPagination, documentationVectorSearch, etc.
- Mutations -
documentationCreateOne, documentationUpdateOne, etc.
- Pagination result -
DocumentationPaginationResult
- Vector search types -
documentationVectorSearchResult, documentationVectorSearchChunk
Generated types
Main type
The main resource type includes your custom fields plus standard fields:
type {contextId} {
# Your custom fields
{fieldName}: {fieldType}
# Standard fields (always included)
id: ID!
createdAt: Date!
updatedAt: Date!
# Vector search fields (if embedder configured)
averageRelevance: Float
totalRelevance: Float
chunks: [ItemChunks]
# RBAC field (if enabled on context)
RBAC: RBACData
}
Example for documentation context:
type documentation {
# Custom fields
title: String!
url: String
category: String
# Standard fields
id: ID!
createdAt: Date!
updatedAt: Date!
# Vector search fields
averageRelevance: Float
totalRelevance: Float
chunks: [ItemChunks]
}
For creating and updating resources:
input {contextId}Input {
{fieldName}: {fieldType}
RBAC: RBACInput # If RBAC enabled
}
Example:
input documentationInput {
title: String!
url: String
category: String
RBAC: RBACInput
}
Filter type
For filtering queries with operators:
input Filter{ContextId} {
{fieldName}: FilterOperator{FieldType}
}
Example:
input FilterDocumentation {
title: FilterOperatorString
url: FilterOperatorString
category: FilterOperatorString
createdAt: FilterOperatorDate
}
Field type mapping
ExuluContext fields are mapped to GraphQL types:
| ExuluContext type | GraphQL type | Filter operators |
|---|
string | String | eq, ne, in, contains, and, or |
number | Float | eq, ne, in, lte, gte, and, or |
boolean | Boolean | eq, ne, in, and, or |
date | Date | lte, gte, and, or |
json | JSON | eq, ne, in, contains |
enum | {fieldName}Enum | eq, ne, in, and, or |
file | String | eq, ne, in, contains, and, or |
Enum field types
Enum fields generate dedicated enum types:
const ticketContext = new ExuluContext({
id: "tickets",
name: "Support Tickets",
fields: [
{
name: "status",
type: "enum",
enumValues: ["open", "in-progress", "resolved", "closed"]
},
{
name: "priority",
type: "enum",
enumValues: ["low", "medium", "high", "urgent"]
}
]
});
Generates:
enum statusEnum {
OPEN
IN_PROGRESS
RESOLVED
CLOSED
}
enum priorityEnum {
LOW
MEDIUM
HIGH
URGENT
}
type tickets {
status: statusEnum
priority: priorityEnum
# ...
}
input FilterTickets {
status: FilterOperatorstatusEnum
priority: FilterOperatorpriorityEnum
}
Generated queries
Each context generates these queries:
{contextId}ById(id: ID!): {contextId}
Example:
query {
documentationById(id: "doc-123") {
id
title
url
category
}
}
By IDs (batch)
{contextId}ByIds(ids: [ID!]!): [{contextId}]!
Example:
query {
documentationByIds(ids: ["doc-1", "doc-2", "doc-3"]) {
id
title
}
}
One (filtered)
{contextId}One(
filters: [Filter{ContextId}]
sort: SortBy
): {contextId}
Example:
query {
documentationOne(
filters: [{ category: { eq: "getting-started" } }]
sort: { field: "createdAt", direction: DESC }
) {
id
title
}
}
{contextPlural}Pagination(
limit: Int
page: Int
filters: [Filter{ContextId}]
sort: SortBy
): {ContextId}PaginationResult
Example:
query {
documentationPagination(
limit: 20
page: 1
filters: [{ title: { contains: "install" } }]
sort: { field: "createdAt", direction: DESC }
) {
items {
id
title
url
}
pageInfo {
currentPage
pageCount
hasNextPage
}
}
}
Statistics
{contextPlural}Statistics(
filters: [Filter{ContextId}]
groupBy: String
limit: Int
): [StatisticsResult]!
Example:
query {
documentationStatistics(
groupBy: "category"
limit: 10
) {
group
count
}
}
{contextPlural}VectorSearch(
query: String!
method: VectorMethodEnum!
itemFilters: [Filter{ContextId}]
cutoffs: SearchCutoffs
expand: SearchExpand
): {contextId}VectorSearchResult
Example:
query {
documentationVectorSearch(
query: "How do I deploy the application?"
method: cosineDistance
cutoffs: { cosineDistance: 0.7 }
expand: { before: 1, after: 1 }
) {
chunks {
chunk_content
chunk_cosine_distance
item_name
item_id
}
context {
name
embedder
}
}
}
{contextId}ChunkById(id: ID!): {contextId}VectorSearchChunk
Example:
query {
documentationChunkById(id: "chunk-123") {
chunk_content
chunk_index
item_name
item_id
}
}
Generated mutations
Each context generates these mutations:
Create one
{contextPlural}CreateOne(
input: {contextId}Input!
upsert: Boolean
): {contextId}MutationPayload
Example:
mutation {
documentationCreateOne(
input: {
title: "Getting Started"
url: "https://docs.example.com/start"
category: "tutorials"
}
) {
item {
id
title
}
job # If embeddings are generated
}
}
Copy one
{contextPlural}CopyOneById(id: ID!): {contextId}MutationPayload
Example:
mutation {
documentationCopyOneById(id: "doc-123") {
item {
id
title # Will be "Original Title (Copy)"
}
}
}
Update one (by filter)
{contextPlural}UpdateOne(
where: [Filter{ContextId}]
input: {contextId}Input!
): {contextId}MutationPayload
Example:
mutation {
documentationUpdateOne(
where: [{ url: { eq: "https://old-url.com" } }]
input: {
url: "https://new-url.com"
}
) {
item {
id
url
}
job # If embeddings regenerated
}
}
Update one by ID
{contextPlural}UpdateOneById(
id: ID!
input: {contextId}Input!
): {contextId}MutationPayload
Example:
mutation {
documentationUpdateOneById(
id: "doc-123"
input: {
title: "Updated Title"
category: "advanced"
}
) {
item {
id
title
category
}
}
}
Remove one
{contextPlural}RemoveOne(where: JSON!): {contextId}
{contextPlural}RemoveOneById(id: ID!): {contextId}
Example:
mutation {
documentationRemoveOneById(id: "doc-123") {
id
title
}
}
Embedder-specific mutations
Contexts with embedders get additional mutations:
Generate chunks
{contextId}GenerateChunks(
where: [Filter{ContextId}]
limit: Int
): {contextId}GenerateChunksReturnPayload
type {contextId}GenerateChunksReturnPayload {
message: String!
items: Int!
jobs: [String!]
}
Example:
mutation {
documentationGenerateChunks(
where: [{ category: { eq: "tutorials" } }]
limit: 50
) {
message
items
jobs
}
}
Delete chunks
{contextId}DeleteChunks(
where: [Filter{ContextId}]
limit: Int
): {contextId}DeleteChunksReturnPayload
type {contextId}DeleteChunksReturnPayload {
message: String!
items: Int!
jobs: [String!]
}
Example:
mutation {
documentationDeleteChunks(
where: [{ category: { eq: "deprecated" } }]
) {
message
items
}
}
Source-specific mutations
Contexts with sources get:
Execute source
{contextId}ExecuteSource(
source: ID!
inputs: JSON!
): {contextId}ExecuteSourceReturnPayload
type {contextId}ExecuteSourceReturnPayload {
message: String!
jobs: [String!]
items: [String!]
}
Example:
mutation {
documentationExecuteSource(
source: "github_docs_sync"
inputs: {
repository: "myorg/myrepo"
branch: "main"
}
) {
message
items
jobs
}
}
Processor-specific mutations
Contexts with processors get:
Process item
{contextId}ProcessItem(item: ID!): {contextId}ProcessItemFieldReturnPayload
type {contextId}ProcessItemFieldReturnPayload {
message: String!
results: [String]
jobs: [String]
}
Process items (batch)
{contextId}ProcessItems(
limit: Int
filters: [Filter{ContextId}]
sort: SortBy
): {contextId}ProcessItemFieldReturnPayload
Example:
mutation {
documentationProcessItems(
limit: 10
filters: [{ category: { eq: "api" } }]
) {
message
results
jobs
}
}
Vector search types
VectorSearchResult
type {contextId}VectorSearchResult {
chunks: [{contextId}VectorSearchChunk!]!
context: VectorSearchResultContext!
itemFilters: JSON!
chunkFilters: JSON!
query: String!
method: VectorMethodEnum!
}
VectorSearchChunk
type {contextId}VectorSearchChunk {
chunk_content: String
chunk_index: Int
chunk_id: String
chunk_source: String
chunk_metadata: JSON
chunk_created_at: Date
chunk_updated_at: Date
item_updated_at: Date
item_created_at: Date
item_id: String!
item_external_id: String
item_name: String!
chunk_cosine_distance: Float
chunk_fts_rank: Float
chunk_hybrid_score: Float
}
Search methods
enum VectorMethodEnum {
cosineDistance # Vector similarity (requires embedder)
hybridSearch # Vector + full-text search
tsvector # PostgreSQL full-text search only
}
Search cutoffs
input SearchCutoffs {
cosineDistance: Float # 0-1, lower is more similar
hybrid: Float
tsvector: Float
}
Search expand
input SearchExpand {
before: Int # Include N chunks before match
after: Int # Include N chunks after match
}
type {ContextId}PaginationResult {
pageInfo: PageInfo!
items: [{contextId}]!
}
type PageInfo {
pageCount: Int!
itemCount: Int!
currentPage: Int!
hasPreviousPage: Boolean!
hasNextPage: Boolean!
}
Mutation payload types
type {contextId}MutationPayload {
item: {contextId}!
job: String # Job ID if background processing triggered
}
Complete example
Hereβs a full example showing all generated types for a context:
const ticketsContext = new ExuluContext({
id: "tickets",
name: "Support Tickets",
tableName: "support_tickets",
fields: [
{ name: "subject", type: "string", required: true },
{ name: "description", type: "string" },
{ name: "status", type: "enum", enumValues: ["open", "closed"] },
{ name: "priority", type: "number" }
],
embedder: myEmbedder
});
Generates schema:
# Enums
enum statusEnum {
OPEN
CLOSED
}
# Main type
type tickets {
subject: String!
description: String
status: statusEnum
priority: Float
id: ID!
createdAt: Date!
updatedAt: Date!
averageRelevance: Float
totalRelevance: Float
chunks: [ItemChunks]
}
# Input type
input ticketsInput {
subject: String!
description: String
status: statusEnum
priority: Float
}
# Filter type
input FilterTickets {
subject: FilterOperatorString
description: FilterOperatorString
status: FilterOperatorstatusEnum
priority: FilterOperatorFloat
createdAt: FilterOperatorDate
}
# Queries
type Query {
ticketsById(id: ID!): tickets
ticketsByIds(ids: [ID!]!): [tickets]!
ticketsOne(filters: [FilterTickets], sort: SortBy): tickets
ticketsPagination(limit: Int, page: Int, filters: [FilterTickets], sort: SortBy): TicketsPaginationResult
ticketsStatistics(filters: [FilterTickets], groupBy: String, limit: Int): [StatisticsResult]!
ticketsVectorSearch(query: String!, method: VectorMethodEnum!, itemFilters: [FilterTickets], cutoffs: SearchCutoffs, expand: SearchExpand): ticketsVectorSearchResult
ticketsChunkById(id: ID!): ticketsVectorSearchChunk
}
# Mutations
type Mutation {
ticketsCreateOne(input: ticketsInput!, upsert: Boolean): ticketsMutationPayload
ticketsCopyOneById(id: ID!): ticketsMutationPayload
ticketsUpdateOne(where: [FilterTickets], input: ticketsInput!): ticketsMutationPayload
ticketsUpdateOneById(id: ID!, input: ticketsInput!): ticketsMutationPayload
ticketsRemoveOne(where: JSON!): tickets
ticketsRemoveOneById(id: ID!): tickets
ticketsGenerateChunks(where: [FilterTickets], limit: Int): ticketsGenerateChunksReturnPayload
ticketsDeleteChunks(where: [FilterTickets], limit: Int): ticketsDeleteChunksReturnPayload
}
Best practices
Use descriptive context IDs - They become GraphQL type names, so choose clear, singular nouns (e.g., document, ticket, article)
Plan field names carefully - They become GraphQL field names and cannot easily be changed without migration
Enum values must be valid GraphQL identifiers - Theyβre automatically converted to uppercase and sanitized
Enable embedders for semantic search - Vector search types are only generated when an embedder is configured
Next steps