decentrl.
API Reference

@decentrl/sdk

Declarative SDK wrapping identity, crypto, event store, and transport into a single typed API.

npm install @decentrl/sdk zod

defineDecentrlApp(config)

Creates a typed Decentrl app configuration.

import { defineDecentrlApp } from '@decentrl/sdk'

function defineDecentrlApp<TEvents, TState>(
  config: DecentrlAppConfig<TEvents, TState>,
): DecentrlApp<TEvents, TState>

Returns: { config, createClient } — an inert configuration plus a factory method.

interface DecentrlAppConfig<TEvents, TState> {
  events: TEvents       // Record<string, { schema: ZodType; tags: string[] }>
  state: TState          // Record<string, StateSliceDefinition>
}

Event Definitions

type EventDefinitions = Record<string, {
  schema: z.ZodType     // validated at publish time
  tags: string[]         // template strings like "chat:${chatId}"
}>

State Definitions

interface StateSliceDefinition<TSlice, TEvents> {
  initial: TSlice
  reduce: {
    [K in keyof TEvents]?: (
      state: TSlice,
      data: z.infer<TEvents[K]['schema']>,
      meta: EventMeta,
    ) => TSlice
  }
}

DecentrlClient

createClient(clientConfig)

Creates a client instance from an app definition.

const client = app.createClient({
  mediatorDid: 'did:web:mediator.decentrl.io',
  persist: { key: 'myapp' },         // optional localStorage persistence
  transport: customTransport,         // optional custom transport
})
interface DecentrlClientConfig {
  mediatorDid: string
  persist?: { key: string }
  transport?: DecentrlTransport
}

Publishing

await client.publish<K extends keyof TEvents>(
  eventType: K,
  data: z.infer<TEvents[K]['schema']>,
  options?: { recipient?: string; ephemeral?: boolean },
): Promise<void>

Full type safety — the data parameter is inferred from the Zod schema of the event type.

State

client.getState(): InferState<TState>
client.subscribe(listener: (state) => void): () => void

Querying

client.query(options?: QueryOptions): Promise<PaginatedResult<EventEnvelope>>
client.queryAndProcess(options?: QueryOptions): Promise<{ processed: number; total: number }>
client.onEvent(listener: (envelope: EventEnvelope) => void): () => void

Sync

client.startSync(options?: SyncOptions): void
client.stopSync(): void
client.sync(): Promise<void>
client.getConnectionStatus(): ConnectionStatus
client.onConnectionStatusChange(listener: (status) => void): () => void
interface SyncOptions {
  intervalMs?: number
  fallbackIntervalMs?: number
  onError?: (error: unknown) => void
  websocket?: boolean
  autoRenew?: { enabled: boolean; threshold?: number }
}

type ConnectionStatus = 'disconnected' | 'connecting' | 'authenticating' | 'connected'

Lifecycle

client.reset(): void

IdentityManager

Accessible via client.identity.

client.identity.getIdentity(): IdentityState | null
client.identity.requireIdentity(): IdentityState
client.identity.create({ alias, mediatorDid }): Promise<IdentityState>
client.identity.load(serialized: SerializedIdentity): IdentityState
client.identity.serialize(): SerializedIdentity
client.identity.reset(): void
client.identity.onChange(listener): () => void

ContractManager

Accessible via client.contracts.

client.contracts.request(recipientDid, expiresIn?): Promise<void>
client.contracts.getPending(): Promise<PendingContractRequest[]>
client.contracts.accept(pendingId, encryptedPayload, ephemeralPubKey): Promise<void>
client.contracts.refresh(): Promise<void>
client.contracts.getActiveContracts(): StoredSignedContract[]
client.contracts.getContractByDid(did): StoredSignedContract | undefined
client.contracts.processAutoRenewals(threshold?): Promise<void>
client.contracts.processContractCleanup(): Promise<void>
client.contracts.onChange(listener): () => void

Types

Event Types

interface EventMeta {
  senderDid: string
  timestamp: number
  eventId: string
  ephemeral?: boolean
}

interface EventEnvelope {
  type: string
  data: unknown
  meta: EventMeta
  tags?: string[]
  _mediatorEventId?: string
}

Identity Types

interface IdentityState {
  did: string
  alias: string
  mediatorDid: string
  mediatorEndpoint: string
  keys: DecentrlIdentityKeys
  mediatorContract: SignedCommunicationContract | null
}

interface SerializedIdentity {
  did: string
  alias: string
  mediatorDid: string
  mediatorEndpoint: string
  keys: { signing: SerializedKeyPair; encryption: SerializedKeyPair; storageKey: string }
  mediatorContract: SignedCommunicationContract | null
}

Contract Types

interface StoredSignedContract {
  id: string
  participantDid: string
  participantAlias?: string
  signedCommunicationContract: SignedCommunicationContract
  rootSecret: string
  createdAt: number
  status: 'active' | 'expired' | 'superseded'
}

interface PendingContractRequest {
  id: string
  senderDid: string
  encryptedPayload: string
  requestorEphemeralPublicKey: string
}

Error Handling

class DecentrlSDKError extends Error {
  code: DecentrlSDKErrorCode
  details?: unknown
}

type DecentrlSDKErrorCode =
  | 'IDENTITY_NOT_INITIALIZED'
  | 'IDENTITY_ALREADY_EXISTS'
  | 'CONTRACT_NOT_FOUND'
  | 'SCHEMA_VALIDATION_FAILED'
  | 'UNKNOWN_EVENT_TYPE'
  | 'SYNC_FAILED'
  | 'PUBLISH_FAILED'
  | 'QUERY_FAILED'
  | 'QUERY_NOT_SUPPORTED'
  | 'MEDIATOR_ERROR'
  | 'NO_TRANSPORT'

Transport

The DecentrlTransport interface is pluggable. The default DirectTransport uses HTTP + WebSocket. See Transport Layer for custom implementations.