Skip to main content

Resource

Trait Resource 

pub trait Resource: Sized + 'static {
    type State: 'static;
    type Action: Action + Serialize + DeserializeOwned + Clone + 'static;

    const KIND: &'static str;
    const LABEL: &'static str;
    const ACTION_TYPE: &'static str = Self::KIND;
    const READABLE: bool = false;
    const VERSION: u32 = 1;
    const EDITS: bool = false;
Show 36 methods // Required method fn read(doc: &AutoCommit) -> Result<Self::State, ModelError>; // Provided methods fn refresh( state: &mut Self::State, doc: &AutoCommit, delta: &Delta, ) -> Result<(), ModelError> { ... } fn copy_state(_state: &Self::State) -> Option<Self::State> { ... } fn publication_cache_epoch(_state: &Self::State) -> Option<String> { ... } fn locate(state: &Self::State, obj: &ExId) -> Option<Vec<(ExId, Prop)>> { ... } fn load( state: &mut Self::State, doc: &AutoCommit, demand: &Demand, ) -> Result<(), ModelError> { ... } fn initialize(_doc: &mut AutoCommit) -> Result<(), ModelError> { ... } fn coalesces(previous: &Self::Action, next: &Self::Action) -> bool { ... } fn continuable(action: &Self::Action) -> bool { ... } fn lands(state: &Self::State, action: &Self::Action) -> Option<Place> { ... } fn field_of(action: &Self::Action) -> Option<&'static str> { ... } fn least_need() -> ActionNeed { ... } fn declarations() -> Vec<ActionDecl<Self>> { ... } fn action_schema() -> Option<Value> { ... } fn children(_state: &Self::State) -> Vec<Resource> { ... } fn item_effects(_field: &str, _item: &Id) -> Vec<ActionSpec> { ... } fn item_views(_field: &str, _item: &Id) -> Vec<ResourceViewSpec> { ... } fn children_parts(state: &Self::State) -> ChildrenParts { ... } fn children_offer_bound(state: &Self::State) -> Vec<&'static str> { ... } fn child(_state: &Self::State, _id: &str) -> Option<Resource> { ... } fn local_operations() -> Vec<ActionSpec> { ... } fn views(_state: &Self::State) -> Vec<ResourceViewSpec> { ... } fn search_scopes(_state: &Self::State) -> Vec<ScopeDecl> { ... } fn search(_state: &Self::State) -> Vec<SearchEntry> { ... } fn read_content( state: &Self::State, doc: &AutoCommit, item: Option<&str>, ) -> Result<Value, String> { ... } fn answer( _doc: &AutoCommit, _action: &str, _changes: &[Patch], ) -> Option<Value> { ... } fn shape() -> Option<Shape> { ... } fn migrate(doc: &mut AutoCommit, from: u32) -> Result<(), ModelError> { ... } fn surface() -> Surface { ... } fn edit_action(edit: Edit<Self>) -> Option<Self::Action> { ... } fn stamp(action: &mut Self::Action, now_ms: i64) { ... } fn op_schemas() -> Vec<(String, Value)> { ... } fn availability(state: &Self::State, action: &str) -> Availability { ... } fn seed() -> Option<Self::Action> { ... } fn kind_of(action: &Self::Action) -> ActionKind { ... } fn register(registry: &mut ActionRegistry) { ... }
}
Expand description

A resource type. Implement it by hand for a type with its own storage layout and merge semantics (a timeline, a rich-text document, a photo edit stack); #[resource] implements it for a #[derive(Model)] record.

The contract is deliberately small:

  • read — materialise the state views render, from the live document or from any historical version (time travel is just read on an older document);
  • refresh — optionally, bring a state up to date after a change by re-reading only what the change touched, so an edit costs as much as the edit rather than as much as the resource;
  • load — optionally, read on demand the parts of a large resource that views are looking at (the pages of a long document on screen), so opening or refreshing it never reads all of it;
  • Action — the replayable mutation vocabulary: a serializable value whose apply is a pure function of the document;
  • declarations — which mutations are offered by name to the palette, agents and scripts, with typed parameters.

Everything else has a default.

Required Associated Constants§

const KIND: &'static str

Stable kind of record this resource lives in, e.g. "notes.notebook".

const LABEL: &'static str

Human name of one resource of this kind, e.g. "Notebook".

Provided Associated Constants§

const ACTION_TYPE: &'static str = Self::KIND

The registry type id every action of this resource is replayed under.

const READABLE: bool = false

Whether read_content answers. A resource that does is published with the framework’s read operation, so a script, an agent or an assistive tool reads what it holds as typed data through the same door, permissions and audit as every other action — never from a screenshot.

const VERSION: u32 = 1

The storage version documents of this resource are written at; #[resource(version = N)] raises it with each incompatible change and Self::migrate brings older documents up to it.

const EDITS: bool = false

Whether the resource’s actions include field edits (Edit), published as FIELD_EDIT. #[resource] records have them.

Required Associated Types§

type State: 'static

What views render. For a record resource this is the record itself.

type Action: Action + Serialize + DeserializeOwned + Clone + 'static

The replayable mutation. Must be deterministic: no clock, no RNG, no local state — use ActionCx for ids and time.

Required Methods§

fn read(doc: &AutoCommit) -> Result<Self::State, ModelError>

Read the state from a document.

§Errors

Returns [ModelError] when the document does not hold this resource.

Provided Methods§

fn refresh( state: &mut Self::State, doc: &AutoCommit, delta: &Delta, ) -> Result<(), ModelError>

Bring state, read earlier from this document, up to date after the changes in delta. Re-read only what they touched: this runs on every keystroke, local or a collaborator’s. The default reads everything again, which is right for small resources.

§Errors

Returns [ModelError] when the document does not hold this resource.

fn copy_state(_state: &Self::State) -> Option<Self::State>

A copy of state for refresh to bring up to date while the state the reader holds is shared (a view still holds the last one): refreshing a copy reads only what a change touched, where without one the state is read whole. #[resource] copies a Clone record; the default has none.

fn publication_cache_epoch(_state: &Self::State) -> Option<String>

Opt in to reusing the document-owned publication between changes.

Return an epoch for every non-document input used by declarations, children, views or local operations (for example the local date of a daily-note action). The framework also invalidates on state and locale revisions; surface bindings and effect availability remain live. None preserves publication on every request for dynamic resources.

fn locate(state: &Self::State, obj: &ExId) -> Option<Vec<(ExId, Prop)>>

Where obj sits in the document, when the state already knows: the path from the root to it. A change to an object the state has read (the paragraph being typed in) is then placed without asking the document, which for a large document is the costly part of reading a change. The default knows nothing, and the document is asked.

fn load( state: &mut Self::State, doc: &AutoCommit, demand: &Demand, ) -> Result<(), ModelError>

Make sure the parts of the state views asked for are read, and drop parts nobody asks for any more. For a resource too large to read whole, read reads a spine (the order of a document’s paragraphs, the rows of a sheet) and this reads the parts on screen. Called after every read and refresh and whenever the demand changes, so it must be cheap when everything demanded is already there. The default reads nothing: the state is read whole.

§Errors

Returns [ModelError] when the document does not hold this resource.

fn initialize(_doc: &mut AutoCommit) -> Result<(), ModelError>

Write the initial content of a brand-new record: its genesis.

The genesis is made under an actor derived from the record’s identity, at time zero, so it is the same change byte for byte on every device: two people who each create the same record before they sync (a room’s default record, the record of one file) converge on one record rather than two competing roots. It must therefore depend on nothing but the resource type — not on who creates it, the device, the clock or a file. Content that does depend on those (the creator as owner, a first-run import of settings that already exist) is the app’s seed, applied as actions right after the genesis (ResourceApp::seed in tilo-app).

§Errors

Returns the document’s write error.

fn coalesces(previous: &Self::Action, next: &Self::Action) -> bool

Whether next, applied right after previous by the same person, continues it as one undo step — the characters of a word typed one keystroke at a time, the values a slider passes through in one drag. The default keeps every action its own step.

Immediate edits (Dispatcher::apply_now in tilo-app) and native keyboard/IME edits from action-bound text fields may continue one another. Programmatic/agent invocations remain separate steps. A surface ends an immediate pointer gesture (Dispatcher::settle) when the pointer is let go, so the next drag of the same slider is a step of its own.

fn continuable(action: &Self::Action) -> bool

Whether a later action may continue action (coalesces), so its undo step stays open for it. One that never continues — a paragraph split after a typed word, a block moved — is a step of its own, recorded at once. The default keeps every step open for what may follow.

fn lands(state: &Self::State, action: &Self::Action) -> Option<Place>

Where action, just applied, leaves the participant who took it, read off the state after it: the paragraph an append wrote and the caret at its end. Every collaborator’s view draws that participant there, so one with no pointer or caret of its own (an agent) shows where it works. The default places it nowhere in particular.

fn field_of(action: &Self::Action) -> Option<&'static str>

The one field of the record action writes, when it writes only one: a reply or a note writes the comments, so a grant on that field (what someone who may only comment holds) reaches it. None by default. A declared action that builds it names the same field with ActionDecl::below_field.

fn least_need() -> ActionNeed

The least any of the resource’s actions is (kind_of, field_of): what the generic APPLY and group actions are offered under, each invocation then decided by what its value is. An undoable change of the record by default; an undoable change of its comments for a resource someone who may only comment can still reply in.

fn declarations() -> Vec<ActionDecl<Self>>

Mutations offered by name. The generic APPLY action is always offered in addition, so any typed value can reach the host.

fn action_schema() -> Option<Value>

The JSON Schema of Action’s serde form, published as the schema of the generic APPLY operation’s argument, so agents and scripts can read the edit vocabulary from the descriptor and binding refuses a malformed edit by name. A variant that only restates a declaration is published as a reference to it. Return Some(<Self::Action as JsonSchema>::json_schema()) for an action type deriving JsonSchema; #[resource] describes its operations from their declarations. The default publishes no schema.

fn children(_state: &Self::State) -> Vec<Resource>

Resources nested inside this one (a track’s clips, a list’s items, a page’s rich-text body), each exporting actions with their arguments already bound. Published beneath the root resource for the palette, agents and scripts — the same door as the root, not a second tree.

fn item_effects(_field: &str, _item: &Id) -> Vec<ActionSpec>

The effects offered on the item item of the collection field, bound to it (#[resource(effects = E)] with #[effect(item = field)] variants): published on the item resource beside its item verbs.

fn item_views(_field: &str, _item: &Id) -> Vec<ResourceViewSpec>

The views the item item of the collection field offers (#[resource(item_views = path)]): published on the item resource, so a link to the item opens it in one, each opened by one of the item’s own actions (item_effects).

fn children_parts(state: &Self::State) -> ChildrenParts

children split into the children that are not items and the items of the record’s collections, so a publisher can keep the item children it built for an earlier version and build only those whose label changed. others followed by item_children of the rest must be exactly children. The default puts every child in others; #[resource] splits off its item collections.

fn children_offer_bound(state: &Self::State) -> Vec<&'static str>

The ids of the declarations that children offer on some child with arguments already bound (an item action on each item of a list), which the resource then publishes for scripts rather than the palette.

publish_head asks this to publish the resource’s own actions without building its children, so a resource with many children answers from what it knows about them: #[resource] from how many items each collection holds. The default builds the children and looks.

fn child(_state: &Self::State, _id: &str) -> Option<Resource>

The nested resource id, when children leaves it out. A resource with more children than a published tree should carry (a book of contacts, a long history) publishes a bounded page from children and resolves the rest here, so an action addressed to any of them still reaches it through the same door. The default resolves nothing beyond children.

fn local_operations() -> Vec<ActionSpec>

View and effect operations on this resource: local presentation and jobs that have no document delta (show a week, start a sync drain, open a system file picker). They are published on the same tree as declarations. A document mutation does not belong here — it is a declarations entry or APPLY. publish drops a Document spec so it cannot become a second write path.

fn views(_state: &Self::State) -> Vec<ResourceViewSpec>

Named presentations of this resource (editor, reader) with stable ids. Not an action called open: ordinary presentation toggles never become composable views by accident. Defaults to none.

fn search_scopes(_state: &Self::State) -> Vec<ScopeDecl>

Entries for the system search index.

fn search(_state: &Self::State) -> Vec<SearchEntry>

fn read_content( state: &Self::State, doc: &AutoCommit, item: Option<&str>, ) -> Result<Value, String>

What the resource holds, as JSON a caller can act on: with no item, the resource itself (a notebook’s pages); with item, the nested resource of that id from children (one page’s title and blocks). Read from doc, the committed document, so the answer is never older than the action that preceded it. Called only when READABLE is set.

§Errors

A message naming why: no such item, or a document that cannot be read.

fn answer(_doc: &AutoCommit, _action: &str, _changes: &[Patch]) -> Option<Value>

What declared action action (one ActionDecl::answering) made, answered to its caller once it applied to doc as changes: the ids it minted, the area it selected, a warning that what it drew is hidden. A compact summary, so the caller needs no second read to see what happened. The default answers nothing.

fn shape() -> Option<Shape>

The stored layout of this resource’s content, for services that act on it generically: History restores a past version through it, field by field and item by item. #[resource] returns the record’s Model::SHAPE; the default opts out.

fn migrate(doc: &mut AutoCommit, from: u32) -> Result<(), ModelError>

Bring a document stored at version from up to Self::VERSION, reading its old shape with the model helpers. Called once per document by [crate::evolution::migrate].

§Errors

Returns why the document cannot be migrated.

fn surface() -> Surface

The public surface: verbs, their parameters and kinds, and the stored fields ([crate::surface]).

fn edit_action(edit: Edit<Self>) -> Option<Self::Action>

The action a field edit of this resource is, when it has field edits (Self::EDITS).

fn stamp(action: &mut Self::Action, now_ms: i64)

Stamp the origin’s clock on an action as the door admits it. A caller never supplies the time an action carries.

fn op_schemas() -> Vec<(String, Value)>

The schemas of the own vocabularies of the record’s fields (Operations), by field path pattern (* for every item): what a field edit in that vocabulary carries.

fn availability(state: &Self::State, action: &str) -> Availability

Whether the declared action action can be taken in state, and why not when it cannot. #[resource(available = path)] answers it.

fn seed() -> Option<Self::Action>

The first content of a new record, written by its creator once it is created: nobody’s undo step. #[resource(seed = path)] provides it.

fn kind_of(action: &Self::Action) -> ActionKind

What kind of action action is: whether it only reads, changes only the document, or reaches outside it. Derived by #[resource] from what each verb is able to do, never declared.

fn register(registry: &mut ActionRegistry)

Register this resource’s replay decoder. Override to register an existing vocabulary under additional type ids.

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementors§

Source§

impl Resource for FeedbackDraft

Source§

const KIND: &'static str = "tilo.feedback-draft"

Source§

const LABEL: &'static str = "Feedback"

Source§

const EDITS: bool = true

Source§

type State = FeedbackDraft

Source§

type Action = Stamped<FeedbackDraftAction>

§

impl Resource for TextAssistancePrefs

§

const KIND: &'static str = "settings.text-assistance"

§

const LABEL: &'static str = "Spelling and text"

§

const EDITS: bool = true

§

type State = TextAssistancePrefs

§

type Action = Stamped<TextAssistancePrefsAction>

§

impl Resource for PlainText

§

const KIND: &'static str = "tilo.text"

§

const LABEL: &'static str = "Text"

§

const ACTION_TYPE: &'static str = TEXT_ACTION_TYPE

§

type State = String

§

type Action = TextEdit