Services

Publishing Services

How to publish TypeScript services in Kinotic using decorators.

Use the @Publish decorator to expose any TypeScript class as a remote service. The namespace you pass to @Publish combined with the class name forms the service identifier that callers use to reach it.

Basic Example

import { Publish, Version } from '@kinotic-ai/core'

@Version('1.0.0')
@Publish('com.example')
class GreetingService {
    hello(name: string): string {
        return `Hello, ${name}!`
    }

    add(a: number, b: number): number {
        return a + b
    }
}

This registers the service as com.example.GreetingService at version 1.0.0, in the zone the client is configured for (see Zones below — an application's services register under its app.<org>.<app> zone). Every public method on the class becomes a remotely callable operation.

Zones

Every service address lives in a zone — the isolation boundary the gateway validates on each call (see CRI Format). An application's services always register inside the application's own zone: the client's zonePrefix is set from static project configuration before services are instantiated, and declared zones are appended to it, so a service can never register outside its application.

import { Kinotic } from '@kinotic-ai/core'
import { appZone } from '@kinotic-ai/os-api'
import config from './.config/kinotic.config'

// In your application's entry point, before any @Publish class is instantiated
Kinotic.zonePrefix = appZone(config.organization, config.application)
@Publish()                       // → srv://app.acme-org.orders-app.OrderService
class OrderService { ... }

@Zone('billing')                 // → srv://app.acme-org.orders-app.billing.InvoiceService
@Publish()
class InvoiceService { ... }

A default zone for every service in the project can also be declared in package.json; a class-level @Zone overrides it:

{ "kinotic": { "zone": "billing" } }
import pkg from './package.json' with { type: 'json' }
Kinotic.defaultZone = pkg.kinotic?.zone ?? null

The gateway rejects any send or subscribe outside the zones the authenticated participant may address, so a wrong or missing zone routes nowhere — it can never reach another application.

Decorators

@Publish(namespace?, name?)

Marks a class for publication. The full service identifier becomes namespace.ClassName; both parts are optional (the name defaults to the class name).

@Zone(zone)

Declares the zone the service is addressable in, relative to the client's zonePrefix. The zone is one or more dot-separated labels of lowercase letters, digits, and interior dashes.

@Version(version)

Sets the semantic version for the service in X.Y.Z format. Callers can pin to a specific version when resolving the service.

@Scope

A getter or method decorator that marks the member providing the scope identifier. Scope targets requests at one specific instance of a service — for example the copy running on a particular node or device.

import { Publish, Scope } from '@kinotic-ai/core'

@Publish('com.example')
class DeviceService {
    deviceId: string

    @Scope
    get scope(): string {
        return this.deviceId
    }

    constructor(deviceId: string) {
        this.deviceId = deviceId
    }

    getStatus(): string {
        return `Status for device ${this.deviceId}`
    }
}

When a caller targets a specific scope (e.g., device-42), the platform routes the request to the instance whose deviceId matches.

@Context

A method decorator that marks a method as receiving the request context. The context carries information about the caller, such as the authenticated participant.

The context parameter must be the method's final parameter. Callers never pass it — the platform appends it after the caller-supplied arguments.

import { Publish, Context } from '@kinotic-ai/core'

@Publish('com.example')
class AuditService {
    @Context
    logAction(action: string, ctx: any): void {
        console.log(`${ctx.participant.id} performed ${action}`)
    }
}

The context parameter is invisible to callers. They do not pass it as an argument; the platform injects it automatically.

@AbacPolicy(expression)

Enforces attribute-based access control before the method is invoked. The expression can reference properties of the caller (participant) and the method arguments. Multiple @AbacPolicy decorators on the same method are combined with AND semantics -- all policies must pass.

import { Publish, AbacPolicy } from '@kinotic-ai/core'

@Publish('com.example')
class OrderService {
    @AbacPolicy("participant.roles contains 'finance' and order.amount < 50000")
    placeOrder(order: Order): void {
        // Only reached if caller has 'finance' role AND order under 50k
    }
}

If any policy expression evaluates to false, the platform rejects the call before it reaches the service.

Copyright © 2026