Skip to content

Writing

Writes are Matter commands, made as the member using the app. They show at once in every read on this computer, and resolve when the server accepts them.

const deals = matter.table('Deals')
const id = await deals.insert({ title: 'Acme', values: { Stage: 'Lead', Owner: '$viewer' } })
await deals.update(id, { Stage: 'Won' }, { if: { Stage: 'Lead' } })
await deals.upsert('by_email', [{ values: { Email: 'sam@acme.com', Stage: 'Lead' } }])
await deals.delete(id)
Write What it does
insert(row) or insert(rows) Creates rows. Resolves to the new row’s id, or a list of ids.
update(id, values, { title?, if? }) Changes the values you give, and the title if you give one.
updateMany([{ id, title?, values?, if? }]) Several updates at once.
upsert(on, rows) Matches each row on a unique index: updates the rows it matches and creates the rest. Resolves to their ids.
delete(id) or delete(ids) Deletes rows.

A row to insert or upsert is { title?, values }. Each write takes at most 500 rows at a time. Every write but delete also takes { createOptions: true } (see Options).

The title is the row’s name: the table’s title field, Name say. Give it as title, as here, or in values by the field’s name; reads give it as both row.title and row.values.Name.

Values use names, the same as reads:

  • options by name: see Options.
  • people by id or name, or '$viewer' for the member writing.
  • linked rows by id or title.
  • dates as YYYY-MM-DD.
  • null clears a value.

A field that holds one person or linked row takes it alone, or as a list of one.

A write names options the field has. Naming one it doesn’t have fails with invalid, listing the field’s options, so a typo or an option renamed in Matter doesn’t quietly grow the field. To add the options a write names, say so:

await deals.update(id, { Stage: 'Churned' }, { createOptions: true })
await tags.insert([{ values: { Labels: ['urgent', 'new'] } }], { createOptions: true })
await matter.batch(ops, { createOptions: true }) // or createOptions on one op

With typed tables, a write’s option names are checked against the field’s, unless it passes createOptions: true.

update with if applies only when the row still holds those values. Otherwise it fails with precondition_failed, and nothing changes. Use it so two runs can’t take the same work:

try {
await actions.update(id, { Status: 'sending' }, { if: { Status: 'approved' } })
} catch (error) {
if (error.code === 'precondition_failed') return // someone else took it
throw error
}

upsert(on, rows) matches on a unique index, by its name, or on a field that has a unique index of its own:

await people.upsert('by_email', [
{ title: 'Sam Lee', values: { Email: 'sam@acme.com', Company: 'Acme' } },
{ title: 'Ana Ruiz', values: { Email: 'ana@initech.com', Company: 'Initech' } },
])

A write that would give a unique index a second row with the same values fails with conflict.

matter.batch(ops) runs several writes in order. It isn’t atomic: a failure stops the batch, and the writes before it stay.

await matter.batch([
{ table: 'Deals', op: 'update', rows: [{ id, values: { Stage: 'Won' } }] },
{ table: 'Activity', op: 'insert', rows: [{ title: 'Won Acme', values: { Deal: id } }] },
])

Each op is insert, update or upsert with rows (and on for upsert), or delete with ids.

When the computer is offline, or the server doesn’t answer, a write resolves after 20 seconds anyway: update, updateMany and delete resolve { pending: true }, and insert and upsert resolve to the new ids. The write is already in every read on this computer, and it lands once the computer reconnects.