Lens System API
Functions
function applyLens(doc: DocumentJSON, spec: LensSpec): DocumentJSON
function applyLensAsync(doc: DocumentJSON, spec: LensSpec): Promise<DocumentJSON>
function applyLensDebug(doc: DocumentJSON, spec: LensSpec): LensDebugResult
function validateLensSQL(spec: LensSpec): LensSqlValidationError[]
function inverseLens(spec: LensSpec): LensSpec // throws LensInversionError if not invertible
function composeLenses(first: LensSpec, second: LensSpec): LensSpec | null
function registerLens(spec: LensSpec, opts?: { autoApply?: boolean }): void
function findLens(sourceNs: string, targetNs: string): LensSpec | null
function transformDocument(doc: DocumentJSON, sourceNs: string, targetNs: string): DocumentJSON | nullapplyLens
Apply a lens synchronously using the SQL-backed facet engine.
import { applyLens } from 'relational-text'
const result = applyLens(doc.toJSON(), myLensSpec)applyLensAsync
Apply a lens asynchronously. Use when the lens spec includes a wasmModule (custom WASM transform blob).
const result = await applyLensAsync(doc.toJSON(), lensWithWasm)applyLensDebug
Apply a lens and return both the result and a per-rule trace. Useful for authoring and debugging lenses.
const { result, trace } = applyLensDebug(doc.toJSON(), spec)
trace.forEach(entry => {
console.log(`rule ${entry.ruleIndex}: ${entry.action} (${entry.matched} features)`)
})validateLensSQL
Validate the SQL rules in a lens spec without running them. Returns an empty array if all rules are valid.
const errors = validateLensSQL(spec)
if (errors.length > 0) {
errors.forEach(e => console.error(`rule ${e.ruleIndex}: ${e.message}`))
}inverseLens
Compute the inverse of a lens. Throws LensInversionError if the lens was declared invertible: false or contains SQL rules.
const forward = myLens
const backward = inverseLens(forward) // throws if not invertiblecomposeLenses
Compose two lenses into one. Returns null if no rules survive composition (e.g., all rules resolve to contradictions).
const composed = composeLenses(aToB, bToC) // LensSpec from A → C, or nullregisterLens
Register a lens in the global lensGraph. When autoApply is true, the lens is applied automatically by autoTransform calls targeting its namespace.
registerLens(myLensSpec, { autoApply: true })findLens
Search the lens graph for a path between two namespaces. Returns a composed LensSpec if a path exists, null otherwise.
const spec = findLens('org.quilljs.delta.facet', 'org.w3c.html.facet')transformDocument
Apply the lens graph path from sourceNs to targetNs. Returns null if no path exists.
const transformed = transformDocument(doc.toJSON(), 'com.slack.mrkdwn.facet', 'org.relationaltext.facet')lensGraph
const lensGraph: LensGraphGlobal singleton. All registerLens calls update this instance.
LensGraph class
class LensGraph {
register(spec: LensSpec, opts?: { autoApply?: boolean }): void
findPath(sourceNs: string, targetNs: string): LensSpec | null
autoTransform(jsonStr: string, targetNs: string): string
}autoTransform accepts a serialized DocumentJSON string, inspects the namespaces present in its facets, finds the shortest lens path to targetNs, and returns a transformed JSON string. Returns the input unchanged if no path is found or the document already uses targetNs.
const htmlJson = lensGraph.autoTransform(JSON.stringify(quillDoc), 'org.w3c.html.facet')Type Definitions
interface LensSpec {
$type: 'org.relationaltext.lens'
id: string
source: string
target: string
passthrough?: 'keep' | 'drop'
invertible?: boolean
rules: LensRule[]
wasmModule?: WasmLensRef
}
interface LensRule {
match?: FeaturePattern
replace?: FeatureReplacement | null // null = drop matched features
sql?: string // mutually exclusive with match/replace
join?: JoinRule // mutually exclusive with match/replace
}
interface FeaturePattern {
name?: string
matchAttrs?: Record<string, unknown>
}
interface FeatureReplacement {
name?: string
typeId?: string
renameAttrs?: Record<string, string>
addAttrs?: Record<string, unknown>
dropAttrs?: string[]
mapAttrValue?: { attr: string; ops: AttrValueOp[] }
}
type AttrValueOp =
| { op: 'plus'; value: number | string }
| { op: 'ltrim'; chars: string }
| { op: 'rtrim'; chars: string }
interface LensDebugResult {
result: DocumentJSON
trace: TraceEntry[]
}
interface TraceEntry {
ruleIndex: number
matched: number
action: 'transform' | 'drop' | 'passthrough'
}
interface LensSqlValidationError {
ruleIndex: number
message: string
}
class LensInversionError extends Error {}
interface WasmLensRef {
data: string // base64-encoded WASM module
}Lens authoring example
import { registerLens, applyLens } from 'relational-text'
const myLens: LensSpec = {
$type: 'org.relationaltext.lens',
id: 'com.myapp.lens/v1',
source: 'com.myapp.facet',
target: 'org.relationaltext.facet',
passthrough: 'drop',
invertible: true,
rules: [
{
match: { name: 'emphasis' },
replace: { name: 'italic' },
},
{
match: { name: 'weight', matchAttrs: { value: 'bold' } },
replace: { name: 'bold', dropAttrs: ['value'] },
},
],
}
registerLens(myLens, { autoApply: true })
const rtDoc = applyLens(sourceDoc.toJSON(), myLens)