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
10 changes: 7 additions & 3 deletions content/docs/guides/define-a-type.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,11 @@ A replacement **changes no item you already hold**. An item meets a property you

Send the `version` every time: a replacement without one sets it back to 0. You can't replace or delete a type Marfa ships.

**A type's identifier never changes.** To rename a type, register the new one, [move the items](/guides/move-items-to-another-type) to it, then delete the old one. A move also changes the folders, views and webhooks that name the old type.

## Delete the type

Marfa deletes a type that **no item holds**, in any state and the bin included, and that no other type names as its parent or in `compatible_with`:
Marfa deletes a type that **no item holds**, in any state and the trash included, that no folder, view or webhook names, and that no other type names as its parent or in `compatible_with`:

```bash tab="CLI" tab-group="client"
marfa types delete app.example.receipt
Expand All @@ -176,10 +178,12 @@ await marfa.DELETE("/types/{id}", {
});
```

Otherwise it refuses with `409 type_in_use` when items hold the type, `409 type_has_subtypes` when a type names it as its parent, and `422 compatible_with_violation` when a type names it in `compatible_with`.
Otherwise it refuses with `409 type_in_use` when items hold the type or a setting names it, `409 type_has_subtypes` when a type names it as its parent, and `422 compatible_with_violation` when a type names it in `compatible_with`.

A `type_in_use` refusal counts the items in `details.item_count` and names each folder, view and webhook setting in `details.settings`, without the `id` of a setting your key can't read. **[Move the items to another type](/guides/move-items-to-another-type) first:** the move changes those settings too. `details.grants` lists the keys and apps whose type map names the type, if your key may manage them. They don't stop a delete, so change them through [Manage keys](/guides/manage-keys) when you're ready.

<Callout type="warn">
`force=true` (the CLI's `--force`), sent with a key, deletes a type that items still hold. It doesn't override a subtype or a `compatible_with` claim. The items stay, but until the type is registered again, Marfa refuses any write to their properties, tier or time. You can still tag them, archive them or move them to the bin.
`force=true` (the CLI's `--force`), sent with a key, deletes a type that items still hold or a setting names. It doesn't override a subtype or a `compatible_with` claim. The items and settings stay as they are, but until the type is registered again, Marfa refuses any write to the items' properties, tier or time. You can still tag them, archive them or move them to the trash.
</Callout>

The owner can remove an unused type without a key: with a [sign-in from the last five minutes](/guides/sign-in-as-the-owner#confirm-its-you), select **Remove type** on the type's page under **Items** in the owner's console, or send the deletion through the private socket. Marfa makes the same checks, and refuses `force=true` from the owner with `403 forbidden`.
Expand Down
1 change: 1 addition & 0 deletions content/docs/guides/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"ai-agents",
"connect-a-web-page",
"define-a-type",
"move-items-to-another-type",
"list-filter-search",
"read-a-view",
"write-safely",
Expand Down
135 changes: 135 additions & 0 deletions content/docs/guides/move-items-to-another-type.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
---
title: Move items to another type
description: Move every item of one type to another in one step, and change the folders, views and webhooks that name it.
---

A type's identifier never changes. **To rename a type, or to fold one type into another, register the new type, move the items, then delete the old type.** Each item keeps its ID, edges, tags and files; only its type changes. [Define a type](/guides/define-a-type) shows how to register and delete a type.

The examples move every `acme.deal` item to a registered type, `acme.contract`.

## Check what a move does

A dry run answers what the move would change, and changes nothing:

```bash tab="CLI" tab-group="client"
marfa types move acme.deal acme.contract --dry-run
```

```http tab="HTTP"
POST /types/acme.deal/move
Authorization: Bearer <key>
Content-Type: application/json

{"to":"acme.contract","dry_run":true}
```

```ts tab="TypeScript"
const { data, error } = await marfa.POST("/types/{id}/move", {
params: { path: { id: "acme.deal" } },
body: { to: "acme.contract", dry_run: true },
});
```

Marfa answers `200` with the items it would move, the settings it would change and the keys that name the type:

```json
{
"dry_run": true,
"from": "acme.deal",
"to": "acme.contract",
"matched": 2,
"ids": ["01a16f80-1c2e-7d4b-9a10-3f5e8b2c6d01", "01a16f80-1c31-7a0e-8b44-6c2d1e9f0a12"],
"settings": [
{ "kind": "folder", "id": "01a16f80-1d02-7c55-9e21-0b7a4c3d2e13", "field": "query.types" },
{ "kind": "webhook", "id": "01a16f80-1d4f-7b18-a3c6-5d9e2f1a0b24", "field": "type_filter" }
],
"grants": [
{ "kind": "key", "id": "01a16f80-1b9a-7e63-8f02-2a4b6c8d0e35", "label": "Sales app" }
]
}
```

**A move takes every item of exactly that type that your key can see, in any state.** An archived item stays archived, and an item in the trash moves and stays in the trash. A type that names `acme.deal` as its `parent` keeps its own items. An item your key's source filter hides isn't moved or counted in `matched`. One move takes up to 50,000 items.

## Move the items

Send the same request without `dry_run`. The CLI waits for the move to finish and says what moved:

```bash tab="CLI" tab-group="client"
marfa types move acme.deal acme.contract
```

```http tab="HTTP"
POST /types/acme.deal/move
Authorization: Bearer <key>
Content-Type: application/json

{"to":"acme.contract"}
```

```ts tab="TypeScript"
const { data, error } = await marfa.POST("/types/{id}/move", {
params: { path: { id: "acme.deal" } },
body: { to: "acme.contract" },
});
```

Over HTTP, Marfa answers `202` with a bulk-action job. Read it with [Get a bulk action job](/api-reference/bulk-actions/getBulkActionJob) until its `status` is `completed`.

**Marfa checks each item against the new type,** as it checks any write. To add a property the new type requires, send it in `properties`, which Marfa merges into every item as it moves, as [Update an item](/api-reference/items/updateItem) does:

```json
{"to":"acme.contract","properties":{"signed":false}}
```

The CLI takes the same object as `--properties '{"signed":false}'`.

An item the new type refuses stays where it is. The job counts them in `result.errored` and lists the first 100 in `result.errors`, each with the reason.

**When the job finishes, it reports what is still of the old type:**

- `result.remaining` counts the items of the old type your key can see, in any state. It includes the items the new type refused and any item written with the old type while the move ran.
- `result.remaining_hidden` is `true` when an item your key can't see is still of the old type. Marfa doesn't say how many.

The CLI names the refused items, says what remains, and exits with a nonzero status, saying why, whenever it leaves the settings unchanged. Deal with those items, or move the hidden ones with a key that can see them, then run the move again: it moves what remains.

## What changes with the items

Once every item has moved, **Marfa changes the settings that name the old type** to name the new one, in one step:

| Setting | What changes |
| --- | --- |
| Folder | `query.types`, `defaults.type`, the type's key in `first_placement`, and each `type eq` or `type neq` term in `query.filter` |
| View | `query.types`, and each `type eq` or `type neq` term in `query.filter` |
| Webhook | `type_filter`, when it's exactly the type. A pattern, such as `acme.*`, stays as it is |

While any item of the old type remains, including one your key can't see, Marfa changes no setting, so a folder or view never stops showing items that haven't moved. The job's `result.settings` lists the settings, and `result.settings_rewritten` says whether Marfa changed them. A folder keeps its files; [Sync a folder](/guides/sync-a-folder#change-a-folders-settings) says what changes on disk.

**When the type has subtypes,** a folder or view keeps the old type in `query.types` beside the new one, because the subtypes' items still need it, and a folder's `first_placement` keeps the old type's key too.

### When a setting blocks the move

Marfa refuses a move when a setting names the type in a way it can't rewrite, or when your key can't change the setting, with `409 type_move_blocked`, and moves nothing. The dry run is refused the same way. `details.settings` names each setting, with a `reason`:

| `reason` | What to change first |
| --- | --- |
| `filter_term` | A `query.filter` term on `type`, other than `type eq` or `type neq` naming the type, whose answer the move would change, such as `type starts_with "acme.d"`. Also a stored filter that names the type and doesn't parse, or one that would be longer than a filter may be once it names the new type |
| `placement_conflict` | A folder's `first_placement` that places the two types in different directories |
| `subtypes` | A webhook whose `type_filter` names a type with subtypes, since one filter can't name both types |
| `not_permitted` | A setting your key can't change, as below |

Change each setting, then move again.

## What your key needs

A move needs a key whose type map grants write on both types. Marfa refuses it `403 type_not_permitted` otherwise.

The key also needs what each setting's own operation needs: write on `system.folder` to change a folder and write on `system.view` to change a view. Only the key or app that made a webhook changes it, and it needs the `webhooks.manage` permission. A setting the key can't change blocks the move with `not_permitted`. When your key can't read the setting either, Marfa names it by `kind` and `field` only, without its `id`, in a move's answer and in a `type_in_use` refusal.

**A move never changes a key's or an app's type map.** The dry run lists in `grants` each key whose type map names the old type, if your key has `keys.manage`, and each app, if it has `grants.manage`. Give them the new type through [Manage keys](/guides/manage-keys) before you delete the old one.

## Delete the old type

Once no item and no setting names the old type, delete it as [Define a type](/guides/define-a-type#delete-the-type) shows.

The rules behind this guide are in [Moving a type's items](https://github.com/withmarfa/marfa/blob/main/conformance/spec/types.md#moving-a-types-items) in the contract.
2 changes: 2 additions & 0 deletions content/docs/guides/sync-a-folder.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ Content-Type: application/json

Use the version you read in place of `<n>` or `1`. The CLI also takes the JSON directly through `--body`. **To repair a folder that still stores `search`, use a server at v0.0.33 or later** and send the intended `query`, leaving out `search`. Copy every part of the old selection you want to keep: the server removes `search` but never translates it into `query`. Read the item again to check the accepted settings, then sync the folder. Editing `.marfa/folder.yaml` alone cannot remove a stored setting that prevents the folder from opening.

**Moving a type's items changes the folder too.** When you [move items to another type](/guides/move-items-to-another-type), Marfa changes `query.types`, `defaults.type`, `first_placement` and the `type eq` and `type neq` terms in `query.filter` in every folder that names the old type. Each item keeps its file, and the next sync writes the new type into the file's frontmatter.

## Replace a folder's key

A new key with the same `--source` is refused `409 conflict` while the old key is live. [Revoke the old key](/guides/manage-keys#revoke-a-key), run the `keys create` command from the first step again, then run `marfa keys keep` so the folder reads the new key from the keychain.
Expand Down
Loading