@enc-protocol/ui-kit -- SDK API Reference
Pure UI Kit SDK. Algebra engine, page resolution, actions, theme. No React, no DOM.
Install
npm config set @enc-protocol:registry https://npm-registry.ocrybit.workers.dev/
npm install @enc-protocol/ui-kitimport { resolvePageTree, applyAction, createTestRunner } from '@enc-protocol/ui-kit'
import { TG } from '@enc-protocol/ui-kit/theme'Table of Contents
- app-adaptor.ts -- Platform-Agnostic Adaptor
- algebra.js -- State Machine Engine
- tree-utils.ts -- Tree Utilities
- resolve-bindings.ts -- Page Resolution
- theme.ts -- Color Tokens
- sql-algebra.js -- SQL Query Engine
- synth.js -- App Synthesizer
- Re-exported Constants
1. app-adaptor.ts -- Platform-Agnostic Adaptor
The central module that connects algebra state to UI rendering. Every platform (React DOM, React Native, terminal, Chrome extension) imports from here.
Source: sdk-ui-kit/app-adaptor.ts
resolvePageTree(hostApp, state, allApps?) -> ResolvedPage
Given an app and current state, produce the renderable tree for the current page.
Parameters:| Param | Type | Default | Description |
|---|---|---|---|
hostApp | any (App definition) | required | The host app definition object |
state | Record<string, any> | required | Current state object |
allApps | any[] | ENC_APPS | All available app definitions |
Returns: ResolvedPage
interface ResolvedPage {
tree: any // The renderable tree (JSON structure)
page: any // The matched page definition
app: any // The app being rendered (may differ from hostApp if mini-app is open)
path: string // The resolved path
}- Checks
state.last_open_action?.app_idfor mini-app context switching. If set, switches to rendering the opened app fromallApps. - Also checks
state._renderAppIdfor test runner context. - Applies gates: iterates through the render app's gates. First matching gate overrides the current path.
- Calls
findPage()to locate the page definition for the current path. - Calls
buildPageTree()to construct the renderable tree with styles and bindings applied. - Returns the tree, page, rendering app, and resolved path.
import { resolvePageTree } from '@enc-protocol/ui-kit'
const { tree, page, app, path } = resolvePageTree(helloApp, currentState)
// tree is ready for platform-specific rendering
// page contains data bindings, actions, etc.
// app is the actual app being rendered (may be a mini-app)- If no page matches the current path, falls back to
pages[default_page](usually index 0). - If
state.last_open_actionreferences an unknown app ID, falls back to the host app. - Returns
{ tree: null, ... }if no page definition has a matching component in COMPONENTS.
applyAction(app, state, clickPath, walletPub, allApps?, inputValue?) -> Record<string, any>
Translate a user interaction (click/tap) into a state transition.
Parameters:| Param | Type | Default | Description |
|---|---|---|---|
app | any | required | App definition |
state | Record<string, any> | required | Current state |
clickPath | string | required | The data-path of the clicked element |
walletPub | string | required | Current wallet public key |
allApps | any[] | ENC_APPS | All available apps |
inputValue | string | undefined | Value from input onChange |
Returns: New state object (or current state if no action matched).
Click Path Tokenization:The clickPath is $-delimited. The function parses it to determine the
action key:
| Click Path Pattern | Resolved Action Key | Description |
|---|---|---|
...$keylt;stateKey> | (auto-save) | Form field keystroke |
...$listlt;idx> | #list.select | List item click |
...lt;nodeId>$avatarlt;idx> | #<nodeId>.avatar | Avatar click in list |
...lt;nodeId>$acceptlt;idx> | #<nodeId>.accept | Accept button in list |
...lt;nodeId>$rejectlt;idx> | #<nodeId>.reject | Reject button in list |
...lt;nodeId>$likelt;idx> | #<nodeId>.like | Like button in list |
...lt;tabId>$selectlt;val> | #<tabId>.select | Tab selection |
...lt;nodeId>$submit | #<nodeId>.submit | Submit action |
...lt;nodeId>$input | #<nodeId>.input | Input change |
...lt;name> | #<name> | Simple click |
- Resolves the current page via
resolvePageTree. - Tokenizes the click path.
- Looks up the action expression in
page.actions. - If the action expression is a mock name, executes via the mock system.
- Otherwise, executes via
execActionfrom algebra.js. - Calls
synthIfNeededto handle mini-app synthesis if applicable. - Returns the new state.
import { applyAction } from '@enc-protocol/ui-kit'
// User clicked the Post button
const nextState = applyAction(app, state, 'content$bar$submit', walletPub)
// User typed in an input
const nextState2 = applyAction(app, state, 'content$bar$input', walletPub, ENC_APPS, 'hello')
// User selected a tab
const nextState3 = applyAction(app, state, 'content$tabs$select$my_posts', walletPub)- Form field auto-save: If click path contains
$keylt;stateKey>, the input value is directly written tostate[stateKey]without looking up an action expression. - Auto-syncs profiles table when name fields (
profile_name,display_name,my_name) change. - If the action expression is a function (legacy), calls it directly.
- Falls back to simpler action key if compound key has no match (e.g.,
#content.submitfalls back to#submit).
createTestRunner(app, walletPub, opts?) -> runTest
Create a test runner for an app that exercises workflow tests in a rendered environment.
Parameters:| Param | Type | Default | Description |
|---|---|---|---|
app | any | required | App definition with tests |
walletPub | string | required | Wallet public key for testing |
opts.allApps | any[] | ENC_APPS | All available apps |
opts.delay | number | 400 | Delay between steps (ms) |
Returns: An async function:
async function runTest(
testIdx: number,
setState: (state: Record<string, any>) => void,
probeDOM: () => { text: string; inputValues: string },
delay?: number,
): Promise<StepResult[]>interface StepResult {
status: 'pass' | 'fail'
label: string // e.g., "do: #bar.submit" or "path, title"
error: string | null // e.g., '[#title] "Hello" not visible'
}- Runs the algebra workflow (
runWorkflow) to get expected states. - Resets the app to initial state.
- For each step:
a. Syncs the algebra state to the renderer via
setState. b. Waitsdelayms for rendering. c. CallsprobeDOM()to get visible text. d. Checks eachseeassertion against the visible text. e. Skips$assertions (state-only, not visible). f. Skips/values (path assertions, not visible text).
const runner = createTestRunner(helloApp, ALICE_WALLET)
const results = await runner(
0, // test index
(state) => setReactState(state),
() => ({
text: document.querySelector('[data-testid="phone"]')?.textContent || '',
inputValues: '',
}),
300,
)
// results: [{ status: 'pass', label: 'do: #button', error: null }, ...]matchPath(pattern, path) -> boolean
Match a route pattern against a concrete path.
Parameters:| Param | Type | Description |
|---|---|---|
pattern | string | Route pattern (e.g., /nodes/:id) |
path | string | Concrete path (e.g., /nodes/abc) |
Returns: true if the pattern matches the path.
Behavior: Splits both on /. Each segment must match exactly, except
segments starting with : which match any value. Both must have the same
number of segments.
matchPath('/nodes/:id', '/nodes/abc123') // true
matchPath('/nodes/:id', '/nodes/abc/sub') // false (different length)
matchPath('/feed', '/feed') // true
matchPath('/feed', '/my_posts') // falsefindPage(pages, path, defaultIdx?) -> any
Find the page definition for a given path.
Parameters:| Param | Type | Default | Description |
|---|---|---|---|
pages | any[] | required | Array of page definitions |
path | string | required | Path to match |
defaultIdx | number | 0 | Fallback page index |
Returns: The matched page definition, or pages[defaultIdx].
- Exact match:
page.path === path - Try with
/prefix: if path doesn't start with/, try/+ path - Parameter match:
matchPath(page.path, path) - Parameter match with
/prefix - Fallback:
pages[defaultIdx]
findPage(app.pages, '/feed') // exact match
findPage(app.pages, 'feed') // tries '/feed' as well
findPage(app.pages, '/nodes/abc123') // matches /nodes/:id
findPage(app.pages, '/unknown', 0) // returns pages[0]pageTitle(page, app) -> string
Extract the display title for a page.
Parameters:| Param | Type | Description |
|---|---|---|
page | any | Page definition |
app | any | App definition |
Returns: Title string.
Resolution order:page.data['#header.title']page.data['#title']app.name''
mergeServerFeed(state, table, serverRows) -> Record<string, any>
Merge server-sourced feed data into local state with deduplication.
Parameters:| Param | Type | Description |
|---|---|---|
state | Record<string, any> | Current state |
table | string | State key (e.g., 'messages') |
serverRows | any[] | Rows from server |
Returns: New state with merged table.
Behavior:- Gets existing array from
state[table]. - Identifies server rows by
bodytext. - Filters local-only rows (not in server set).
- Merges: local-only first, then server rows.
- Sorts by
_tsdescending (newest first).
parseDataViewRow(raw, myPub?) -> any
Parse a DataView row into a flat state row.
Parameters:| Param | Type | Default | Description |
|---|---|---|---|
raw | any | required | Raw DataView row |
myPub | string | undefined | Current user's public key |
Returns: Flat row object:
{
from: string, // author pubkey
body: string, // content body
media: string, // same as from (for avatar)
trailing: string, // relative time
outgoing: boolean, // true if from === myPub
_ts: number, // created_at timestamp
_id: string, // event_id
}Behavior: Parses raw.content as JSON if it's a string. Extracts
from, body, trailing from content. Sets outgoing based on whether
from === myPub.
shouldIngest(table, eventFrom, myPub?) -> boolean
Check if an event should be ingested into a state table.
Parameters:| Param | Type | Description |
|---|---|---|
table | string | Target table name |
eventFrom | string | Event author pubkey |
myPub | string | Current user's pubkey (optional) |
Returns: boolean
Rule: Tables prefixed with my_ only accept events where
eventFrom === myPub. All other tables accept all events.
2. algebra.js -- State Machine Engine
The pure-function state machine. No DOM, no React, no framework. Runs identically in Node.js tests and in the browser.
Source: sdk-ui-kit/algebra.js
execAction(state, actionExpr, walletPub?, inputValue?) -> Record<string, any>
Execute set() action expressions against state.
| Param | Type | Default | Description |
|---|---|---|---|
state | Record<string, any> | required | Current state |
actionExpr | string | required | +-separated set() expressions |
walletPub | string | '' | Current wallet pubkey |
inputValue | string | '' | Input value ($value/$input) |
Returns: New state object (shallow copy with modifications).
Supported Value Forms inset(key, value):
| Value | Resolution |
|---|---|
null | null |
[] | Empty array [] |
$wallet | walletPub parameter |
$pub | inputValue parameter |
$value | inputValue parameter |
$input | inputValue parameter (alias) |
/path | Literal string (navigation path) |
push(key) | Prepend state[key] as new item to target array |
delete(key) | Remove items from array where pub/from matches |
insert(key) | Add item with pub field to array |
push(key) Details:
Creates a new item: { from: shortPub, body: state[key], media: shortPub, trailing: 'now', outgoing: true } and prepends it to the target array.
delete(key) Details:
If key starts with $, resolves from state. Filters out items where
item.pub or item.from matches the resolved value.
insert(key) Details:
Creates a minimal row using the template of existing items. Sets pub
to the resolved value and trailing to 'now'.
Side Effect: Auto-syncs profiles table when profile_name,
display_name, or my_name changes and state.identity exists:
state.profiles[state.identity.slice(0, 16)] = { name: newValue }import { execAction } from '@enc-protocol/ui-kit/algebra'
// Navigate
execAction(state, 'set(path, /feed)')
// Connect wallet + navigate
execAction(state, 'set(identity, $wallet) + set(path, /feed)', '79be...')
// Post a message (push from draft)
execAction(state, 'set(messages, push(draft)) + set(draft, null)', '79be...')
// Clear
execAction(state, 'set(draft, null) + set(messages, [])')
// Delete by pub
execAction(state, 'set(contacts, delete($pub))', '', 'c6047f...')
// Insert by pub
execAction(state, 'set(following, insert($pub))', '', 'c6047f...')apply(state, mock, ctx?) -> Record<string, any>
Execute a mock operation against state.
Parameters:| Param | Type | Default | Description |
|---|---|---|---|
state | Record<string, any> | required | Current state |
mock | any | required | Mock definition (see below) |
ctx | object | undefined | Context (inputValue, etc.) |
- Function (legacy):
(state, ctx) => patchObject. Called with state and context. Return value is merged into state. - Plain object (legacy):
{ key: value, ... }. Noopfield. Deep-cloned and merged into state. - Single structured op:
{ op: 'insert'|'delete'|'update'|'query'|'set', ... } - Compound ops:
{ ops: [op1, op2, ...] }. Executed sequentially.
insert
{ op: 'insert', table: 'messages', values: { body: 'hello' }, append?: boolean }Prepends (or appends if append: true) a row to state[table].
delete
{ op: 'delete', table: 'invites', where: { pub: 'abc' } }Removes rows where ALL where fields match.
update
{ op: 'update', table: 'users', where: { pub: 'abc' }, set: { role: 'admin' } }Merges set fields into all matching rows.
query
{ op: 'query', table: 'profiles', where: { key: '$target' }, into: 'found' }For array tables: filters rows matching where, stores in state[into].
For object tables: looks up by first where value, stores in state[into].
set
{ op: 'set', values: { path: '/feed', draft: '' } }Merges values directly into state.
All values support $key references resolved from state.
currentPage(app, state) -> any
Find the current page from gates + path.
Parameters:| Param | Type | Description |
|---|---|---|
app | any | App definition |
state | any | Current state |
Returns: Page definition object.
Behavior:- Check gates in order. First matching gate returns
app.pages[gate.page]. - Exact path match:
page.path === state.path. - Variable path match:
/nodes/:node_idmatches/nodes/abc. - Fallback:
app.pages[0].
resolve(app, state, path) -> any
Resolve #path to a value from page bindings + state.
| Param | Type | Description |
|---|---|---|
app | any | App definition |
state | any | Current state |
path | string | Binding path (e.g., #header.title) |
Returns: Resolved value (string, array, object, or undefined).
Resolution Forms:| Path Form | Resolution |
|---|---|
#node.param | Look up page.data['#node.param'], resolve refs |
#node | Look up page.data['#node'] |
#list[N].field | Look up list binding, access item N, map field |
Resolver handling: If the value starts with @ and contains :,
it's a cross-table resolver: @table.field:stateKey ->
state[table][state[stateKey]][field].
State key handling: If the value matches /^[a-z_]+$/ and exists in
state, returns state[value].
#list[N].field):
- Finds the list binding (entry containing
listId<). - Gets the source array from
state[stateKey]. - Accesses item at index N.
- Maps the field through the field map.
- If the mapped field starts with
@, performs a resolver lookup.
runStep(app, state, step, walletPub?) -> Record<string, any>
Run a single do/see step. Execute mock or UI action, return new state.
Parameters:| Param | Type | Default | Description |
|---|---|---|---|
app | any | required | App definition |
state | Record<string, any> | required | Current state |
step | any | required | Step definition |
walletPub | string | '' | Wallet pubkey |
{
set: { key: value, ... }, // Optional: state patches applied FIRST
do: '#action' | 'mock', // Optional: action to execute
see: { path: expected }, // Optional: assertions (checked by caller)
idx: number, // Optional: target list item index
in: 'app_id', // Optional: target app for cross-app tests
}Returns: New state object after applying set patches and executing
the do action.
- Apply
step.setpatches (with$walletresolution and profiles sync). - If
step.dostarts with#: look up action expression from current page'sactionsmap.- Tab/filter select: resolves
$valueto next tab item (cycling). Explicitstep.idxpicks a specific tab. - List item actions: resolves
$pubfrom clicked item. Default idx=0. - If action expression is a mock name, executes via mock system.
- Post detail navigation: populates
post_body,post_liked,post_action_label,post_stats,post_comments. - Profile navigation: populates
profile_bio,profile_action,profile_posts,profile_stats,browsing_following,browsing_followers.
- Tab/filter select: resolves
- If
step.dodoesn't start with#: execute as mock fromapp.mocks. - If
step.action(legacy): execute viaexecAction.
runWorkflow(app, steps, walletPub?, opts?) -> { state, states, results }
Run a full test workflow. Execute all steps and return results.
Parameters:| Param | Type | Default | Description |
|---|---|---|---|
app | any | required | Host app definition |
steps | any[] | required | Array of step definitions |
walletPub | string | '' | Wallet pubkey |
opts.allApps | any[] | [app] | All available apps |
opts.synthesize | function | undefined | synthesizeApp function |
{
state: { ... }, // Final state of the host app
states: { // Per-app final states (keyed by app.id)
'hello': { ... },
'mini_feed': { ... },
},
results: [ // Per-step results
{
step: { ... }, // Original step
state: { ... }, // State after this step (deep clone)
status: 'pass'|'fail',
error: null|string, // e.g., '[in: hello] #title: expected "X" got "Y"'
},
]
}step.in: Run step against a different app's state.last_synth_manifest: Auto-synthesizes mini-app and registers in allApps.last_open_action.app_id: Auto-initializes target app with host identity (for synthesized mini-apps only; standalone apps keep their own gate).- Per-app states are maintained independently throughout the workflow.
assertSee(app, state, see, assertFn) -> void
Assert UI tree values match expected.
Parameters:| Param | Type | Description |
|---|---|---|
app | any | App definition |
state | any | Current state |
see | object | { path: expectedValue } pairs |
assertFn | function | (actual, expected, message) => void |
Behavior: For each path-value pair:
$keypaths: checkstate[key].- Other paths: call
resolve(app, state, path). - Convert to string, compare, call
assertFn.
Event-Centric Pathway
These functions mirror Enc.UI.Interp in Lean.
resolveValue(ctx, v) -> any | undefined
Resolve a WriteOp Value.
| Value Form | Result |
|---|---|
{ lit: s } | String s |
{ litInt: n } | Number n |
{ litNull: true } | null |
{ placeholder: k } | ctx.bindings[k] or ctx.wallet |
{ binOp: {op,lhs,rhs}} | String concat (` |
Returns undefined for func, subquery, bare col.
resolveWhereVal(ctx, wv) -> any | undefined
Restricted subset of resolveValue (no binOp/func/subquery).
toEvent(ctx, op) -> UIEvent | null
WriteOp -> UIEvent. Resolves all values up-front.
UIEvent Shape:{
kind: 'created' | 'updated' | 'deleted',
table: string,
from: string, // ctx.wallet
row?: [col, value][], // for 'created'
sets?: [col, value][], // for 'updated'
whereClause?: [col, op, val][], // for 'updated'/'deleted'
}applyEvent(state, event) -> Record<string, any>
UIEvent -> state. Pure function.
created: Prepend row to table.updated: Map rows, apply sets to matching.deleted: Filter out matching rows.
applyWriteOp(ctx, state, op) -> Record<string, any>
Bridge: toEvent then applyEvent. Returns unchanged state if toEvent
returns null.
applyWriteSeq(ctx, state, writeSeq) -> Record<string, any>
Apply list of WriteOps left-to-right.
setExprToWriteOp(key, rawValue) -> WriteOp
Desugar set(key, value) to UPDATE against _state table.
setExprToWriteOp('draft', '$value')
// { update: { table: '_state',
// sets: [['value', { placeholder: 'value' }]],
// whereClause: [['key', '=', { lit: 'draft' }]] } }SDK Codegen Functions
pascalize(s) -> string
snake_case -> PascalCase. pascalize('send_message') -> 'SendMessage'.
kindSuffix(kind) -> string
'created' -> 'Created', 'updated' -> 'Updated', 'deleted' -> 'Deleted'.
writeOpKind(op) -> string | null
Extract kind from WriteOp.
writeOpTable(op) -> string | null
Extract table name from WriteOp.
defaultEventName(writeName, seqLen, op) -> string
Single-op: PascalCase(writeName). Multi-op:
PascalCase(writeName) + PascalCase(table) + Suffix.
writeOpParams(op) -> string[]
INSERT: col list. UPDATE: SET cols + WHERE cols. DELETE: WHERE cols.
lookupParamType(manifest, table, col) -> [string, boolean]
Returns [type, encrypted]. Supports bare primitives ("string"),
qualified primitives ({ type: "string", encrypted: true }).
Falls back to ['string', false].
typedParams(manifest, op) -> [string, string, boolean][]
[name, type, encrypted] triples for all parameters.
emitSdk(appName, manifest) -> { name, methods }
Generate SDK class. Returns { name: string, methods: SdkMethod[] }.
interface SdkMethod {
name: string // PascalCase event name
event: string // target table
kind: string // 'created'|'updated'|'deleted'
paramList: [string, string, boolean][] // [name, type, encrypted]
}emitEnclaveEvents(manifest) -> EventDecl[]
Generate enclave event declarations.
interface EventDecl {
eventName: string
tableName: string
kind: string
fields: [string, string, boolean][]
}extractEvents(ctx, astWrites) -> [[string, UIEvent[]]]
Extract UIEvents from astWrites. Accepts array or object form.
extractFromManifest(ctx, manifest) -> [[string, UIEvent[]]]
Convenience: extractEvents(ctx, manifest.astWrites).
3. tree-utils.ts -- Tree Utilities
Source: sdk-ui-kit/tree-utils.ts
applyStyles(node, styles, path?) -> any
Merge a styles map into a tree.
Parameters:| Param | Type | Default | Description |
|---|---|---|---|
node | any | required | Tree node |
styles | Record<string, any> | required | Path-to-style map |
path | string | '' | Current path (for recursion) |
Returns: New tree with styles applied (does not mutate input).
Path Matching:.matches root (whenpath === '')#idmatches node with that id#parent>[N]matches N-th anonymous child
- String: resolved via
resolveStyle(hash)from style registry - Object: merged with spread into existing
node.style
validateTree(node, path?) -> string[]
Check tree for unknown atoms/components.
Parameters:| Param | Type | Default | Description |
|---|---|---|---|
node | any | required | Tree root |
path | string | '' | Current path |
Returns: Array of error strings. Empty = valid.
Checks:node.atomin ATOM_NAMES (str, media, input, select, multiselect, bool, slider, arr, null)node.componentfound in COMPONENTS array- Recursively validates
node['./']children
4. resolve-bindings.ts -- Page Resolution
Source: sdk-ui-kit/resolve-bindings.ts
resolveBindings(tree, bindings, state) -> any
Resolve page data bindings into a render tree.
Parameters:| Param | Type | Description |
|---|---|---|
tree | any | Component tree (deep-cloned) |
bindings | Record<string, any> | page.data bindings |
state | Record<string, any> | Current app state |
Returns: New tree with bindings injected.
Binding Processing (Phase 1 -- collect per-node data):-
Arrow bindings (
#node<stateKey): Mapsstate[stateKey]array through the field map. SetsnodeData[node].itemsand.messages. -
Function bindings (value contains
(): Skipped (action expressions are not data). -
Dotted bindings (
#node.param): SetsnodeData[node][param]. String values: resolver (@table.field:stateKey) or state key (/^[a-z_]+$/test) or literal. Array values: badge-count substitution ({key}->(N)). -
Direct bindings (
#node): For arrays, sets.itemsand.fields. For strings, sets._direct.
Walks the tree. For each node with a matching id in nodeData:
_directvalues: mapped by atom type (str->value, media->src, input->label, component->title).- Other values: set directly on
node.data[key].
Recurses into node['./'] children.
buildPageTree(page, state, components, applyStyles, opts?) -> any
Build a renderable tree for one page.
Parameters:| Param | Type | Description |
|---|---|---|
page | object | { page: string, data: object } |
state | object | Current state |
components | object[] | COMPONENTS catalog |
applyStyles | function | applyStyles function |
opts.stripHeader | boolean | Remove header comp (default true) |
- Find component by
page.pagename. - Apply component's styles via
applyStyles. - Resolve bindings via
resolveBindings. - Optionally strip header (for hosts that render their own).
5. theme.ts -- Color Tokens
Source: sdk-ui-kit/theme.ts
TG Object
Telegram-dark theme tokens. Single source of truth for all UI Kit colors.
const TG = {
bg: '#0e0e1a',
headerBg: '#1a1a2e',
chatBg: '#0e0e1a',
bubbleOut: '#00564a',
bubbleOutText: '#e0f0ec',
bubbleIn: '#1e1e38',
bubbleInText: '#e0e0e0',
inputBg: '#1a1a30',
textPrimary: '#e6e6f0',
textSecondary: '#7e7e96',
separator: 'rgba(255,255,255,0.06)',
accent: '#00d4aa',
danger: '#ff4444',
warning: '#f0a030',
iconPlaceholder: '#333',
} as constPaletteKey Type
type PaletteKey = keyof typeof TGUse instead of string for compile-time color token validation.
6. sql-algebra.js -- SQL Query Engine
Source: sdk-ui-kit/sql-algebra.js
init() -> Promise<Database>
Initialize SQLite (async). Browser: WASM. Node.js: asm.js. Cached.
initSync() -> Database
Synchronous init (Node.js only, uses require).
loadState(state) -> void
Load JS state into SQLite. Drops all tables first.
Mapping:- Arrays -> tables (columns from first row,
_rowidautoincrement) - Objects -> KV tables (
keyPRIMARY KEY + expanded fields) - Scalars ->
_state (key, value)rows - Booleans are stored as
'1'/'0'strings
query(sql, params?) -> object[]
Execute SELECT. Returns rows as objects. Resolves $key from params
then from _state.
mutate(sql, params?) -> boolean
Execute INSERT/UPDATE/DELETE. Same param resolution. Returns success flag.
getScalar(key) -> string | null
Read from _state table.
setScalar(key, value) -> void
Write to _state table (INSERT OR REPLACE).
exportState() -> object
Reverse mapping: SQLite -> JS state.
_staterows -> scalars (with boolean/null conversion)- KV tables (with
keycolumn) -> objects - Array tables -> arrays (with
'1'/'0'boolean conversion)
schema() -> string
Returns all CREATE TABLE statements as a string.
7. synth.js -- App Synthesizer
Source: sdk-ui-kit/synth.js
synthesizeApp(manifest) -> AppDefinition
Expand manifest into full app definition.
Manifest shape: { id: string, name: string, template: 'feed'|'chat'|'form', params?: object }
Throws on missing id/name or unknown template.
Template params documented in main spec (docs/ui-kit.md section 18).Output: Complete app definition with synthesized: true, including
pages, mocks, tests, initial_state, preset_state. No /connect gate
(mini-apps inherit identity from host).
TEMPLATE_CATALOG
[
{ id: 'feed', name: 'Feed', description: 'List of posts with a compose bar.' },
{ id: 'chat', name: 'Chat', description: 'Contacts list + 1:1 chat threads.' },
{ id: 'form', name: 'Form', description: 'Form that collects submissions.' },
]8. Re-exported Constants
From app-adaptor.ts:
COMPONENTS-- fromconfig/comps/index.ts. Array of 18 ComponentDef.ENC_APPS-- fromconfig/apps/index.js. Array of 10 app definitions.resolveBindings-- fromresolve-bindings.ts.buildPageTree-- fromresolve-bindings.ts.
From config/apps/index.js:
ALICE_PUB = '79be667ef9dcbbac'
ALICE_WALLET = '79be667ef9dcbbac55a06295ce870b07'
BOB_PUB = 'c6047f9441ed7d6d'
CHARLIE_PUB = 'f9308a019258c310'Each app is now stored as config/apps/<id>/ui.json (pure data) plus an
optional config/apps/<id>/mocks.js sidecar (function-valued mocks). The
index.js loader is dual-mode — it uses import.meta.glob under Vite and
falls back to node:fs under Node, then merges ui.json + mocks.js back into
the canonical ENC_APPS[] shape. Downstream consumers see the same flat
array they did before the split.
From index.ts (package entry):
export { resolvePageTree, applyAction, createTestRunner, getVisibleText } from './app-adaptor'
export { TG, type PaletteKey } from './theme'