Decorators Reference
Entity Decorators
These decorators are imported from @kinotic-ai/persistence.
@Entity(multiTenancyType?, entityType?)
Marks a class as a Kinotic entity. Required for any class that should be persisted and managed by the entity service system.
import { Entity, MultiTenancyType, EntityType } from '@kinotic-ai/persistence'
@Entity()
export class Product {
// ...
}
@Entity(MultiTenancyType.SHARED)
export class TenantProduct {
// ...
}
@Entity(MultiTenancyType.NONE, EntityType.STREAM)
export class SensorReading {
// ...
}
Parameters:
multiTenancyType—MultiTenancyType.NONE(default) orMultiTenancyType.SHAREDentityType—EntityType.TABLE(default) orEntityType.STREAM
ID Decorators
@AutoGeneratedId
Marks a field as a server-generated ID. The field type must be string | null since the ID is null before the entity is saved.
import { Entity, AutoGeneratedId } from '@kinotic-ai/persistence'
@Entity()
export class Product {
@AutoGeneratedId
id: string | null = null
}
@Id
Marks a field as a manually-assigned ID. The field type is string since the caller is responsible for providing the value.
import { Entity, Id } from '@kinotic-ai/persistence'
@Entity()
export class Product {
@Id
sku: string = ''
}
Field Decorators
@NotNull
Marks a field as required. The persistence layer will reject entities where this field is missing or null.
import { Entity, AutoGeneratedId, NotNull } from '@kinotic-ai/persistence'
@Entity()
export class Product {
@AutoGeneratedId
id: string | null = null
@NotNull
name: string = ''
}
@Text
Enables full-text search indexing on a string field. By default, string fields are indexed as keywords (exact match only). Adding @Text enables tokenized search.
import { Entity, AutoGeneratedId, Text } from '@kinotic-ai/persistence'
@Entity()
export class Article {
@AutoGeneratedId
id: string | null = null
@Text
body: string = ''
}
@Precision(PrecisionType)
Sets the numeric precision for a number field. Controls how the value is stored in Elasticsearch.
import { Entity, AutoGeneratedId, Precision, PrecisionType } from '@kinotic-ai/persistence'
@Entity()
export class Measurement {
@AutoGeneratedId
id: string | null = null
@Precision(PrecisionType.DOUBLE)
value: number = 0
@Precision(PrecisionType.LONG)
timestamp: number = 0
}
Precision types: PrecisionType.INT, PrecisionType.SHORT, PrecisionType.LONG, PrecisionType.FLOAT, PrecisionType.DOUBLE
@NotIndexed
Excludes a field from Elasticsearch indexing. The field is still stored and returned in queries, but cannot be searched or filtered on.
import { Entity, AutoGeneratedId, NotIndexed } from '@kinotic-ai/persistence'
@Entity()
export class Document {
@AutoGeneratedId
id: string | null = null
@NotIndexed
rawData: string = ''
}
@Flattened
Uses Elasticsearch flattened mapping for an object field. All values in the object are indexed as keywords, enabling filtering without the overhead of dynamic field mappings.
import { Entity, AutoGeneratedId, Flattened } from '@kinotic-ai/persistence'
@Entity()
export class Event {
@AutoGeneratedId
id: string | null = null
@Flattened
metadata: Record<string, string> = {}
}
@Nested
Uses Elasticsearch nested mapping for an array of objects. Preserves the relationship between fields within each object, enabling accurate queries across object boundaries.
import { Entity, AutoGeneratedId, Nested } from '@kinotic-ai/persistence'
@Entity()
export class Order {
@AutoGeneratedId
id: string | null = null
@Nested
lineItems: LineItem[] = []
}
Relationship
@Discriminator(propertyName)
Marks a field as a polymorphic type discriminator. Used when a field can contain different types and the system needs to determine the concrete type during deserialization.
import { Entity, AutoGeneratedId, Discriminator } from '@kinotic-ai/persistence'
@Entity()
export class Notification {
@AutoGeneratedId
id: string | null = null
@Discriminator('type')
payload: EmailPayload | SmsPayload | null = null
}
Parameters:
propertyName— The name of the property within the object that identifies its type
Multi-tenancy
@TenantId
Marks a field as the tenant identifier. Used with MultiTenancyType.SHARED entities to partition data by tenant.
import { Entity, AutoGeneratedId, TenantId, MultiTenancyType } from '@kinotic-ai/persistence'
@Entity(MultiTenancyType.SHARED)
export class Invoice {
@AutoGeneratedId
id: string | null = null
@TenantId
tenantId: string = ''
}
Versioning
@Version
Enables optimistic locking for the entity. The field type must be string | null. The persistence layer uses this field to detect concurrent modifications.
import { Entity, AutoGeneratedId, Version } from '@kinotic-ai/persistence'
@Entity()
export class Product {
@AutoGeneratedId
id: string | null = null
@Version
version: string | null = null
name: string = ''
}
Time Series
@TimeReference
Marks a field as the time reference for stream entities. Used with EntityType.STREAM to identify the timestamp field.
import { Entity, AutoGeneratedId, TimeReference, EntityType, MultiTenancyType } from '@kinotic-ai/persistence'
@Entity(MultiTenancyType.NONE, EntityType.STREAM)
export class SensorReading {
@AutoGeneratedId
id: string | null = null
@TimeReference
timestamp: Date = new Date()
value: number = 0
}
Query
@Query(statement)
Method decorator that defines a named query on a repository class. The statement uses a SQL-like syntax with :paramName parameter binding, and the method body is left empty -- kinotic generate fills in the implementation. Declare the method on the repository subclass that extends the generated base class (the subclass is generated once and never overwritten). Currently only aggregate queries (COUNT, SUM, AVG, MIN, MAX) are supported.
import { Query } from '@kinotic-ai/persistence'
export class ProductRepository extends BaseProductRepository {
@Query('SELECT COUNT(*) FROM Product WHERE category = :category')
countByCategory(category: string): Promise<number> {
// Implementation is generated by the CLI
}
}
Service Decorators
These decorators are imported from @kinotic-ai/core.
@Publish(namespace?, name?, advertise?)
Publishes a class as a remotely accessible service. The service becomes available to clients via the RPC gateway, addressed inside the client's zone (see CRI Format).
import { Publish } from '@kinotic-ai/core'
@Publish('com.example')
class UserService {
async findUser(id: string): Promise<User> {
// ...
}
}
@Publish('com.example', 'CustomName')
class MyService {
// Published as com.example.CustomName
}
@Publish('com.example', undefined, true)
class OrderService {
// Also advertised in the service directory
}
Parameters:
namespace— Optional service namespace (e.g.,'com.example')name— Optional custom service name. Defaults to the class name.advertise— Whentrue, the service advertises itself in the platform service directory, so it appears in directory listings for browsing and invocation. Defaults tofalse: the service is callable over RPC but not listed.
@Zone(zone)
Declares the zone a service is addressable in, relative to the client's Kinotic.zonePrefix. When absent, Kinotic.defaultZone (typically loaded from the project package.json kinotic.zone field) applies. The zone is one or more dot-separated labels of lowercase letters, digits, and interior dashes.
import { Publish, Zone } from '@kinotic-ai/core'
@Zone('billing')
@Publish()
class InvoiceService {
// With zonePrefix app.acme-org.orders-app → srv://app.acme-org.orders-app.billing~InvoiceService
}
@Version(version)
Sets the semantic version for a published service. Enables versioned service routing.
import { Publish, Version } from '@kinotic-ai/core'
@Publish('com.example')
@Version('2.0.0')
class UserService {
// ...
}
@Scope
Marks the getter or method that provides the scope for service routing. Scope targets requests at one specific instance of a service, such as the copy running on a particular node.
import { Publish, Scope } from '@kinotic-ai/core'
@Publish('com.example')
class NodeManager {
nodeId: string = ''
@Scope
get scope(): string {
return this.nodeId
}
}
@Context
Marks a method that receives the request context, which carries metadata about the current request.
The context parameter must be the method's final parameter. Callers never pass it — the platform appends it after the caller-supplied arguments, so a context parameter anywhere else will receive a caller argument instead.
import { Publish, Context } from '@kinotic-ai/core'
@Publish('com.example')
class AuditService {
@Context
async logAction(action: string, context: any): Promise<void> {
// context contains request metadata
}
}