Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions .github/scripts/sync-from-marfa.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,8 @@ catalog_json="$(cat <<'EOF'
},
{
"id": "parent-of",
"label": "Parent of",
"label": "Children",
"reverse_label": "Parent",
"description": "Source is the parent of the target — once.",
"cardinality": "one-to-many",
"source_type_constraints": ["*"],
Expand All @@ -377,6 +378,7 @@ catalog_json="$(cat <<'EOF'
{
"id": "in-folder",
"label": "In folder",
"reverse_label": "Folder contents",
"description": "Placement of an item in a folder.",
"cardinality": "many-to-many",
"source_type_constraints": ["*"],
Expand Down Expand Up @@ -438,11 +440,11 @@ folder_expected="$(cat <<'EOF'
EOF
)"
edge_table_expected="$(cat <<'EOF'
| Edge type | Reverse name | Cardinality | On delete | Target types |
| --------------- | ------------ | -------------- | --------- | ---------------------------------- |
| `in-collection` | None | `many-to-many` | `orphan` | Any type with the role `container` |
| `parent-of` | `child-of` | `one-to-many` | `cascade` | Any type |
| `in-folder` | None | `many-to-many` | `orphan` | `system.folder` |
| Edge type | Label | Reverse label | Reverse name | Cardinality | On delete | Target types |
| --------------- | ------------- | --------------- | ------------ | -------------- | --------- | ---------------------------------- |
| `in-collection` | In collection | In collection | None | `many-to-many` | `orphan` | Any type with the role `container` |
| `parent-of` | Children | Parent | `child-of` | `one-to-many` | `cascade` | Any type |
| `in-folder` | In folder | Folder contents | None | `many-to-many` | `orphan` | `system.folder` |
EOF
)"
catalog_edges_expected="$(cat <<'EOF'
Expand Down Expand Up @@ -797,7 +799,7 @@ check "a core type's file has its fields and settings, a subtype's section only
"${note_expected}" "$(cat "${catalog_dir}/core.note.md")"
check "a system type's file has its fields, as a core type's does" \
"${folder_expected}" "$(cat "${catalog_dir}/system.folder.md")"
check "the edge types are a table, with a Source types column only when an edge type constrains its source, a reverse name or None, and the contract's wording for a target" \
check "the edge types are a table, with a Source types column only when an edge type constrains its source, a label, a reverse label that is the label where none is named, a reverse name or None, and the contract's wording for a target" \
"${edge_table_expected}" "$(cat "${catalog_dir}/edge-types.md")"
check "each edge type is a section with its properties, and where it is written only when that is its target" \
"${catalog_edges_expected}" "$(cat "${catalog_dir}/edges.md")"
Expand Down Expand Up @@ -1125,6 +1127,8 @@ refuse_catalog "a merge strategy the sync does not know" "${cat_file} has a merg
refuse_catalog "display hints the sync does not write" "${cat_file} has display hints for core.note that the sync does not write. Nothing has changed." "$(catalog_with 'c["types"][0]["display_hints"]["icon_field"] = "title"')"
refuse_catalog "an edge type with no description" "${cat_file} has an edge type with no id or description. Nothing has changed." "$(catalog_with 'del c["edge_types"][0]["description"]')"
refuse_catalog "an edge type with a key the sync does not write" "${cat_file} has a key 'weight' on the edge type in-collection that the sync does not write. Nothing has changed." "$(catalog_with 'c["edge_types"][0]["weight"] = 1')"
refuse_catalog "an edge type with no label" "${cat_file} has the edge type in-collection with no label. Nothing has changed." "$(catalog_with 'del c["edge_types"][0]["label"]')"
refuse_catalog "an edge type with an empty reverse label" "${cat_file} has the edge type in-collection with a reverse_label that is not a label. Nothing has changed." "$(catalog_with 'c["edge_types"][0]["reverse_label"] = ""')"
refuse_catalog "an edge type written nowhere the sync knows" "${cat_file} has the edge type in-collection with a written_at the sync does not know. Nothing has changed." "$(catalog_with 'c["edge_types"][0]["written_at"] = "both"')"
refuse_catalog "an edge type written at its target with no reverse name" "${cat_file} has the edge type parent-of written at its target with no reverse_name. Nothing has changed." "$(catalog_with 'del c["edge_types"][1]["reverse_name"]')"
refuse_catalog "an edge type with a cardinality the sync does not know" "${cat_file} has the edge type in-collection with a cardinality the sync does not know. Nothing has changed." "$(catalog_with 'c["edge_types"][0]["cardinality"] = "some-to-some"')"
Expand All @@ -1147,7 +1151,7 @@ check "a default of both copies and a subtype with no field to add: the script s
check "a default of both copies names the fields that do not keep both, as values" 1 \
"$(grep -cF '| [`merge_policy`](/catalog/your-own-types#merge-policy) | `last_writer_wins` for `title` and `language`; `keep_both_copies` for every other field |' "${work}/shapes/docs/src/generated/catalog/core.note.md")"
check "an edge type that constrains its source adds a Source types column, and several types are joined with or" \
"$(printf '%s\n%s\n%s\n%s\n%s' '| Edge type | Reverse name | Cardinality | On delete | Source types | Target types |' '| --------------- | ------------ | -------------- | --------- | ------------------------------- | ---------------------------------- |' '| `in-collection` | None | `many-to-many` | `orphan` | Any type | Any type with the role `container` |' '| `parent-of` | `child-of` | `one-to-many` | `cascade` | Any type | Any type |' '| `in-folder` | None | `many-to-many` | `orphan` | `core.note` or `core.note.memo` | `system.folder` |')" \
"$(printf '%s\n%s\n%s\n%s\n%s' '| Edge type | Label | Reverse label | Reverse name | Cardinality | On delete | Source types | Target types |' '| --------------- | ------------- | --------------- | ------------ | -------------- | --------- | ------------------------------- | ---------------------------------- |' '| `in-collection` | In collection | In collection | None | `many-to-many` | `orphan` | Any type | Any type with the role `container` |' '| `parent-of` | Children | Parent | `child-of` | `one-to-many` | `cascade` | Any type | Any type |' '| `in-folder` | In folder | Folder contents | None | `many-to-many` | `orphan` | `core.note` or `core.note.memo` | `system.folder` |')" \
"$(cat "${work}/shapes/docs/src/generated/catalog/edge-types.md")"
check "a subtype with nothing to add is a heading and its description" \
"$(printf '### core.note.bare [#core-note-bare]\n\nA note.')" "$(sed -n '/^### core.note.bare \[#core-note-bare\]$/,$p' "${work}/shapes/docs/src/generated/catalog/core.note.md")"
Expand Down
2 changes: 1 addition & 1 deletion content/docs/(overview)/devices.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ An app built on the [Swift SDK](/swift-sdk) is a device, and so is the [CLI](/cl

A device chooses the **slice** of the instance it keeps: a list of types, a tier, and the edge types it holds. For example, every `core.note` and `core.task` in the library. It fills its copy once by reading every item in the slice, then keeps it current from the instance's [event stream](/events). To hold every type except `system.*`, declare `*` alone: the device reads the whole slice in one pass, with no need to read the catalog and name its types first. `*` beside `system.*` types adds them, and `*` beside any other type is refused `invalid` before anything is read.

Reads come from the copy, with no connection needed. A file's bytes are there once the device has fetched them. The core also [reads saved views](/guides/read-a-view), including layout order, groups, counts and sums. **A view's item or group read needs the whole query held** and is refused with a reason if the copy cannot answer it completely.
Reads come from the copy, with no connection needed. A file's bytes are there once the device has fetched them. The core also [reads saved views](/guides/read-a-view), including sort order, groups and counts. **A view's item or group read needs the whole query held** and is refused with a reason if the copy cannot answer it completely.

**While a device fills its copy, it answers direct item reads from what has arrived.** A list or search without a saved view returns the items the copy holds so far and marks the answer `partial`; once the copy is whole, the same read is marked `complete`. A read by ID answers for an item that has arrived, and is refused `hydration_incomplete` for one that hasn't. Until the copy is whole, edge reads are refused `hydration_incomplete`, because an edge's two ends can arrive on different pages. A write to an item that has arrived, or a new item, is queued against the version the copy holds and sent once the copy is whole. This includes a file added on its own or attached to an item that has arrived: the device keeps its bytes and uploads them with the rest of the queue. A write to an item that hasn't arrived, or a file attached to one, is refused `hydration_incomplete`, because there is no version to base it on, and a drain is refused `hydration_incomplete` until the copy is whole. **Edge writes are refused `hydration_incomplete` until the copy is whole**, because a page still to arrive can bring the instance's edge in place of the one a write was made against. An edit that changes an item's body is still queued, and the device reads the body's links and embeds and makes their edges once the server answers the edit. The device's status reports how many items the fill has brought so far and, where the instance counted them, how many the slice held when the fill began, so an app can show how far the fill has come. Compare those two figures, not the count of items the copy holds, which also includes pinned items and creates that are still queued. The Swift SDK returns these answers as `Rows` with a `completeness`.

Expand Down
2 changes: 2 additions & 0 deletions content/docs/(overview)/glossary.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Marfa uses each word below in one sense only, in its code, its API and these doc
| [**edge**](/properties-metadata-and-edges#edges) | A typed link from one item to another. |
| [**edge type**](/properties-metadata-and-edges#edges) | What an edge is, such as `about`, and the rules its edges follow. |
| [**reverse name**](/properties-metadata-and-edges#edges) | An edge's name read from its target: `child-of` for `parent-of`. |
| [**label**](/properties-metadata-and-edges#edges) | The heading a type or an edge type shows for itself. An edge type's label heads the source's end of an edge, such as `Children` for `parent-of`. |
| [**reverse label**](/properties-metadata-and-edges#edges) | The heading an edge type shows for the target's end, such as `Parent`. Where an edge type names none, its label names both ends. |
| [**file**](/files) | An item that names its bytes. |
| [**blob**](/files) | A file's bytes, stored once under a hash of their content. |
| [**state**](/items-and-types#an-items-life) | `active`, `archived` or `trashed`; `active` or `revoked` for a `system` item. |
Expand Down
4 changes: 2 additions & 2 deletions content/docs/(overview)/properties-metadata-and-edges.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Content-Type: application/json
{ "source_id": "<note id>", "target_id": "<book id>", "edge_type": "about" }
```

The **edge type** sets the rules: the properties its edges hold, how many edges each item can hold (a task has at most one parent), which types each end can be, and what moving an item to the bin does across the edge. Read from the other end, an edge has a reverse name: `parent-of` reads as `child-of` from the child. You can [register edge types](/api-reference/edge-types/registerEdgeType) of your own.
The **edge type** sets the rules: the properties its edges hold, how many edges each item can hold (a task has at most one parent), which types each end can be, and what moving an item to the bin does across the edge. Each edge type has a label for the source's end, such as `Children`, and a reverse label for the target's end, such as `Parent`; an edge type with no reverse label uses its label for both ends. Read from the other end, an edge also has a reverse name: `parent-of` reads as `child-of` from the child. You can [register edge types](/api-reference/edge-types/registerEdgeType) of your own.

Each of Marfa's edge types either **cascades** or **orphans** when an item goes to the bin:

Expand All @@ -83,6 +83,6 @@ The figure shows a note with a child note, linked by `parent-of`, and a book the

<BinCascade />

The Catalog's [Edges](/catalog/edges) page lists every edge type Marfa ships, with its reverse name, cardinality and what it does on delete.
The Catalog's [Edges](/catalog/edges) page lists every edge type Marfa ships, with its label, reverse label, reverse name, cardinality and what it does on delete.

The rules behind this page are in the [items](https://github.com/withmarfa/marfa/blob/main/conformance/spec/items.md) and [edges](https://github.com/withmarfa/marfa/blob/main/conformance/spec/edges.md) chapters of the contract.
4 changes: 2 additions & 2 deletions content/docs/(reference)/api-reference/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@ The reference groups operations by what they act on. The groups and the operatio
| Edges | Typed, directed relationships between items | [Create an edge](/api-reference/edges/createEdge) |
| Search | Full-text search across items | [Search items](/api-reference/search/searchItems) |
| Types | The types items can have: those Marfa ships and those registered on this instance | [Register a type](/api-reference/types/registerType) |
| Edge types | The types edges can have, each with its cardinality and cascade rule | [Register an edge type](/api-reference/edge-types/registerEdgeType) |
| Edge types | The types edges can have, each with its labels, cardinality and cascade rule | [Register an edge type](/api-reference/edge-types/registerEdgeType) |
| Blobs | Bytes stored by content hash, and the stores that hold them | [Upload a blob](/api-reference/blobs/uploadBlob) |
| Event stream | Changes to items and edges, streamed as they happen | [Stream change events](/api-reference/event-stream/streamEvents) |
| Webhooks | Changes sent to a URL you choose, and each delivery's record | [Create a webhook](/api-reference/webhooks/createWebhook) |
| Folders | A folder's settings, held as a `system.folder` item | [Create a folder](/api-reference/folders/createFolder) |
| Views | Saved lists of items and how each app shows them, held as `system.view` items | [Create a view](/api-reference/views/createView) |
| Views | Saved lists of items with their sort and grouping, held as `system.view` items | [Create a view](/api-reference/views/createView) |
| Connectors | Processes outside the server that read and write on your behalf, with their heartbeats and runs | [Register a connector](/api-reference/connectors/registerConnector) |
| Connector state | What the instance keeps for a connector's source: its hold, its state and its agreements | [Take a connector hold](/api-reference/connector-state/takeConnectorHold) |
| Inbound webhooks | Addresses where a service sends requests to a connector, and the deliveries Marfa stores for it | [Create an inbound endpoint](/api-reference/inbound-webhooks/createInboundEndpoint) |
Expand Down
6 changes: 3 additions & 3 deletions content/docs/(reference)/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -232,14 +232,14 @@ That's how the owner makes the first key in the [Quickstart](/quickstart). These

## Read a view from a working copy

The `marfa device views` commands read saved views from the database named by `--db`. **They need no server connection.** [Read a view offline](/guides/read-a-view) shows how to fill the copy and read a layout.
The `marfa device views` commands read saved views from the database named by `--db`. **They need no server connection.** [Read a view offline](/guides/read-a-view) shows how to fill the copy and read its items and groups.

| Command | Answer |
| --- | --- |
| `marfa device views list` | Views held in the copy. |
| `marfa device views get <id>` | One view's settings and version. |
| `marfa device views groups <id> --layout <id>` | A layout's groups, counts and sums; `--time-zone` selects the time zone for date buckets. |
| `marfa device items list --view <id> --layout <id>` | The view's items in layout order; `--limit` and `--offset` select a page. |
| `marfa device views groups <id>` | The view's groups and counts; `--time-zone` selects the time zone for date buckets. |
| `marfa device items list --view <id>` | The view's items in its sort order; `--limit` and `--offset` select a page. |
| `marfa device search <text> --view <id>` | Search matches within the view's query. |

Each command takes `--json`. A view whose whole query or grouping is not held is refused with a reason.
Expand Down
2 changes: 1 addition & 1 deletion content/docs/catalog/(core)/file.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: File
description: "A file or binary reference: the generic fallback for non-media files."
description: "A file or binary reference: the generic fallback for any file that has no more specific type."
identifier: core.file
---

Expand Down
2 changes: 1 addition & 1 deletion content/docs/catalog/(system)/view.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: View
description: "A saved list of items, held here and shared by every app: which items, and how each layout shows them. Written only through /views: created, changed at a version, and deleted, which removes it at once with no trash. Lifecycle bounded to active | revoked. Has no tier."
description: "A saved list of items, held here and shared by every app: which items, in what order and how they group. How an app shows them is the app's own. Written only through /views: created, changed at a version, and deleted, which removes it at once with no trash. Lifecycle bounded to active | revoked. Has no tier."
identifier: system.view
---

Expand Down
2 changes: 1 addition & 1 deletion content/docs/catalog/edges.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ An edge is a typed, directed link from one item, its source, to another, its tar

<include cwd>src/generated/catalog/edge-types.md</include>

The reverse name is the edge's name read from its target. In a [synced folder](/guides/sync-a-folder), the source item's file writes the edge, unless its section below gives `written_at`: `target` puts it in the target item's file under its reverse name, and `none` in no file.
Each edge type has two headings. The label names the source's end of an edge and the reverse label the target's end: `parent-of` lists a note's `Children` and, read from the child, its `Parent`. A label is optional when you register an edge type: without one, Marfa makes it from the last segment of the ID, such as `List Member` for `acme.list-member`. A reverse label is optional too. When you change a registered type, a label you leave out stays as it is, and so does the reverse label. To make a type two-way, send a reverse label equal to the label. An edge type with no reverse label names both ends alike, such as `Related to`, and an app shows one list for both directions. Both headings are for people. The reverse name is different: it is the edge's name read from its target, used as a frontmatter key. In a [synced folder](/guides/sync-a-folder), the source item's file writes the edge, unless its section below gives `written_at`: `target` puts it in the target item's file under its reverse name, and `none` in no file.

## Fields every edge has

Expand Down
Loading
Loading