Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

@enc-protocol/cli-sdk-base — Base SDK classes

The base classes that all @enc-protocol/<app>-cli packages extend.

Registry: https://npm-registry.ocrybit.workers.dev/ Published: @enc-protocol/cli-sdk-base

Install

echo "@enc-protocol:registry=https://npm-registry.ocrybit.workers.dev/" >> .npmrc
npm install @enc-protocol/cli-sdk-base

You usually consume this transitively via a per-app SDK like @enc-protocol/hello-cli. Direct use is for advanced cases (building a new app SDK, embedding the engine in custom tooling).

Exports

import { AppSdk }    from '@enc-protocol/cli-sdk-base/app-sdk'
import { AppClient } from '@enc-protocol/cli-sdk-base/app-client'
import { DataView, loadDataView } from '@enc-protocol/cli-sdk-base/dataview'

Or the barrel:

import { AppSdk } from '@enc-protocol/cli-sdk-base'   // = app-sdk

AppSdk

App-driven SDK class. Reads apps/<id>/{app.json, schema.json} at runtime, composes an AppClient, and adds app-level submit/query that resolve via tableMap to enclave events.

const sdk = new AppSdk({
  appId: 'super',
  mode: 'mem',
  repoRoot: '/path/to/impl-cli',
})
await sdk.init()
await sdk.submit('moments', { body: 'wow' })   // → Personal.public
const events = await sdk.query('moments')

Constructor

new AppSdk(opts: {
  appId: string                // matches apps/<id>/
  mode: 'mem' | 'cf'
  repoRoot: string             // path to find apps/ + enclaves/
  identity?: Identity          // required for cf
  nodeUrl?: string             // cf mode
  encHome?: string             // state dir; defaults to ~/.enc
  forceReadable?: boolean      // test-only Public-R escape (cf)
})

Methods

MethodDescription
async init()Load app definition, register all enclaves, wire dataview.
async submit(name, args)Submit. name is a data_type (resolved via tableMap) OR an enclave event directly.
async query(name)Query. cross_enclave: true reads go to the in-memory DataView; other reads resolve via tableMap.
raw()Returns the underlying AppClient for low-level access.
whoami()Identity + registered enclaves + dataview info.

Resolution

_resolve(name) returns { enclave, event }:

  1. If name is a key in schema.tableMap → enclave found by event name in the map's value.
  2. Else if name matches an enclave event directly (fallback) → that enclave + event.
  3. Otherwise throws.

AppClient

Multi-enclave coordinator. Holds one identity and N enclave adapters.

const client = new AppClient({ appId: 'super', mode: 'mem', repoRoot })
await client.init()   // registers all 3 enclaves: DM, Group, Personal
await client.submit('Personal', 'public', { body: 'wow' })

Methods

MethodDescription
async init()Read apps/<id>/app.json, register each enclave.
async addEnclave(name, opts?)Add an enclave manually (init does this automatically).
async submit(enclaveName, event, args)Write to one enclave's adapter.
async query(enclaveName, event)Read from one enclave.
whoami()Identity, mode, list of registered enclaves with ids.

For mem mode, identity is provisioned per enclave by PureAdapter. For cf mode, an externally-supplied identity is used for all enclaves; each is minted on the node with that identity as owner.

DataView

In-memory dataview for cross_enclave: true reads. Mirrors the production CF Durable Object semantics (impl-emulator/dataview-workers/_shared/dataview-do.js) without SQLite.

Loading

const dataview = loadDataView('super', '/path/to/impl-cli')
// Reads apps/super/infra.json's manifest.cross_enclave_reads
// e.g. { profiles: { from: 'Personal.Shared(profile)', via: 'dataview', key: 'id_pub' } }

Methods

MethodDescription
has(viewName)Whether this view is configured.
ingest(enclaveName, ev)Called by AppClient.submit for every successful event; routes to matching views.
query(viewName)Returns rows.
watchedEnclaves()Set of enclave names this dataview cares about.

Storage shape

  • Append-only by default (most events) — stored as array.
  • UPSERT keyed by from for Shared(<key>) slot events — keyed Map.
  • UPSERT keyed by id field for registry snapshots (reg_identityid_pub, reg_enclaveenclave_id, reg_nodeseq_pub).

Per-app subclassing

A per-app SDK (@enc-protocol/hello-cli etc.) extends AppSdk:

import { AppSdk } from '@enc-protocol/cli-sdk-base'
 
export class HelloSdk extends AppSdk {
  constructor(opts) { super({ ...opts, appId: 'hello' }) }
 
  async _encrypt(dataType, args) { return args }   // default pass-through
 
  async submitMessages(args) {
    args = await this._encrypt('messages', args)
    return this.submit('messages', args)
  }
 
  async queryMessages() { return this.query('messages') }
}

The _encrypt(dataType, args) hook is per-app: pass-through by default, subclass to add MLS / AEAD / your-protocol encryption.

Source

Lives in impl-cli/lib/client/. Each module:

  • app-sdk.mjsAppSdk class.
  • app-client.mjsAppClient (multi-enclave coordinator).
  • dataview.mjsDataView + loadDataView.

Plus lib/protocol-runtime/manifest-loader.mjs for the canonical enclave-config translation.

See also