Typed Document API
Five names carry most of Vyuh Document. It is pure Dart, so the same code runs in a Flutter app, on a server and in an AI agent.
dart
import 'package:vyuh_document/vyuh_document.dart';
import 'package:vyuh_document/elements.dart';A complete program
dart
import 'package:vyuh_document/elements.dart';
import 'package:vyuh_document/vyuh_document.dart';
/// Builds, edits and checks a document without Flutter.
void main() {
final configuration = DocumentConfiguration(
elements: [Paragraph.element, Heading.element],
);
// Document.of returns a Result: Failed when a block isn't configured.
final report = Document.of(configuration, [
Heading('Quarterly report'),
Paragraph('Revenue grew.'),
]).valueOrNull!;
final controller = DocumentController(configuration, document: report);
final summary = controller.document.body.first<Paragraph>();
// Every command returns a typed Result; nothing throws for a caller mistake.
final result = controller.run(
InsertText(Position(summary, offset: 13), ' Margins held.'),
);
switch (result) {
case Done():
print(controller.document.plainText);
case Failed(:final error):
print('${error.message} ${error.hint ?? ''}');
}
// A dry run: the document the command would produce, nothing applied.
final preview = controller.preview(SetAlignment(summary, Alignment.center));
print(preview.isDone);
controller.run(
ToggleMark(
const Bold(),
Range(Position(summary, offset: 0), Position(summary, offset: 7)),
),
);
controller.run(const Undo());
controller.close();
}The five names
| Name | What it is |
|---|---|
DocumentConfiguration | The elements and marks a document may use. Immutable; shared by every controller. Built-in: richText (paragraphs with every mark); notesConfiguration and documentConfiguration come from vyuh_document_elements. |
Document | Immutable content read through its configuration: body, section(i), footnotes, plainText, toJson(). |
DocumentController | One participant's live session: document, state, run, preview, select, join, changes, close. |
Command<R> | One edit with a typed result (InsertRow is a Command<Ref<Row>>). |
Result<T> | Done(value) or Failed(error). The API never throws for a caller mistake. |
Create a document
| Call | Returns |
|---|---|
Document.of(configuration, [Heading('Title'), Paragraph('Text')]) | Result<Document>: Failed(NotAllowed) if a block's element is not configured |
Document.fromJson(configuration, json) | Result<Document>: Failed(InvalidDocument) with the JSON path of the first problem |
DocumentController(configuration) | An empty paragraph |
DocumentController.open(configuration, document: doc) | Result<DocumentController> instead of throwing on an invalid document |
dart
final configuration = DocumentConfiguration(
elements: [Paragraph.element, Heading.element, Table.element],
marks: const [Bold(), Italic()],
);Elements reachable through another element's content rule (a table's rows and cells) are added automatically.
Run, preview and undo
| Method | Does |
|---|---|
run(command) | Applies the command; returns its typed result or the error. A failed command changes nothing. |
preview(command) | Result<Document>: what the command would produce; nothing is applied. |
run(const Undo()), run(const Redo()) | Undo or redo this controller's own changes. |
state | DocumentState(document, revision, selection, canUndo, canRedo) |
select(range) | Sets this controller's selection; Failed(NotFound) if the range is stale. |
Handle results
dart
switch (controller.run(InsertRow(table, at: 1))) {
case Done(:final value):
print('new row $value');
case Failed(:final error):
print(error); // NotFound: Table t1 in the body doesn't exist ...
}
// Or chain: then() runs only after a Done.
controller.run(InsertRow(table, at: 1)).then((row) => controller.run(const Undo()));Result also has valueOrNull, errorOrNull, isDone and map.
Errors
Every DocumentError carries message, where, expected, found and an optional hint, and has a JSON form (toJson() / DocumentError.fromJson).
| Error | When |
|---|---|
NotFound | A reference, story or offset does not exist (or was deleted). |
NotAllowed | The grammar refuses it: a table inside a heading, an unconfigured element, nothing to undo. |
InvalidAttribute | An attribute value is out of range: Divider.thickness 99. |
Unsupported | No configured element handles the command, or the controller is closed. |
Conflict | Undo would overwrite another participant's later edit of the same text. |
InvalidDocument | JSON that does not match the configuration. |
UnknownCommand | A command name that is not declared; carries didYouMean. |
In Flutter
DocumentEditor(controller:) from package:vyuh_document_editor/document_editor.dart shows and edits a controller. Edits typed in the editor are changes of that controller. See Editor widget.
Relation to the editor runtime
The typed API wraps the canonical model and engine that the editor runtime (DocumentEditorController) uses. types.dart and vyuh_document.dart both export a Document; prefix one when a file needs both (import 'package:vyuh_document/types.dart' as core;).
Next
- Addressing:
Ref,Position,Rangeand stories. - Commands: every command family.
- Collaboration and Agents.