Explore

InstaML

Instant uses a Firebase-inspired interface for mutations. We call our mutation language InstaML

Why InstaML

We love Firebase's simple mutation API and how it provides optimistic updates and rollbacks for free. We wanted to bring the same experience with support for relations.

You can try out the examples below with this sandbox.

Transact

transact is used for committing transaction chunks. transact takes only one parameter, an array of tx transaction chunks. For example running the following

import { id } from '@instantdb/react'

const workoutId = id()
const proteinId = id()
const sleepId = id()
const standupId = id()
const reviewPRsId = id()
const focusId = id()
const healthId = id()
const workId = id()

transact([
  tx.todos[workoutId].update({ title: 'Go on a run' }),
  tx.todos[proteinId].update({ title: 'Drink protein' }),
  tx.todos[sleepId].update({ title: 'Go to bed early' }),
  tx.todos[standupId].update({ title: 'Do standup' }),
  tx.todos[reviewPRsId].update({ title: 'Review PRs' }),
  tx.todos[focusId].update({ title: 'Code a bunch' }),
  tx.goals[healthId]
    .update({ title: 'Get fit!' })
    .link({ todos: workoutId })
    .link({ todos: proteinId })
    .link({ todos: sleepId }),
  tx.goals[workId]
    .update({ title: 'Get promoted!' })
    .link({ todos: standupId })
    .link({ todos: reviewPRsId })
    .link({ todos: focusId }),
])

Will generate:

  • todos, with unique identifiers workoutId, proteinId, sleepId, standupId, reviewPRsId, and focusId
  • goals, with unique identifiers healthId and workId
  • todos workoutId, proteinId, and sleepId are associated with goal health
  • todos standupId, reviewPRsId, and focusId are associated with goal work

tx

tx is a proxy object which creates transaction chunks to be commited via transact. It follows the format

tx.NAMESPACE_LABEL[GLOBAL_UNIQUE_IDENTIFER].ACTION(ACTION_SPECIFIC_DATA)
  • NAMESPACE_LABEL refers to the namespace to commit (e.g. goals, todos)
  • GLOBAL_UNIQUE_IDENTIFER is the id to look up in the namespace. This id must be unique across all namespaces. Suppose we have a namespace authors, and editors. If we have id joe in authors, we cannot have id joe in editors as well.
  • ACTION is one of update, delete, link, unlink
  • ACTION_SPECIFIC_DATA depends on the action
    • update takes in an object of information to commit
    • delete is the only aciton that doesn't take in any data,
    • link and unlink takes an object of label-entity pairs to create/delete associations

Update

We use the update action to create entities.

transact([tx.goals[id()].update({ title: 'eat' })])

This creates a new goal with the following properties:

  • It's identified by a randomly generated id via the id() function.
  • It has an attribute title with value eat.

Similar to NoSQL, you don't need to use the same schema for each entity in a namespace. After creating the previous goal you can run the following:

transact([
  tx.goals[id()].update({
    priority: 'none',
    isSecret: true,
    value: 10,
    aList: [1, 2, 3],
    anObject: { foo: 'bar' },
  }),
])

You can store strings, numbers, booleans, arrays, and objects as values. You can also generate values via functions. Below is an example for picking a random goal title.

transact([
  tx.goals[id()].update({
    title: ['eat', sleepId, 'hack', 'repeat'][Math.floor(Math.random() * 4)],
  }),
])

The update action is also used for updating entities. Suppose we had created the following goal

const eatId = id()
transact([
  tx.goals[eatId].update({ priority: 'top', lastTimeEaten: 'Yesterday' }),
])

We eat some food and decide to update the goal. We can do that like so:

transact([tx.goals[eatId].update({ lastTimeEaten: 'Today' })])

This will only update the value of the lastTimeEaten attribute for entity eat.

Delete

The delete action is used for deleting entities.

transact([tx.goals[eatId].delete()])

You can generate an array of delete txs to delete all entities in a namespace



const { isLoading, error, data } = useQuery({goals: {}}
const { goals } = data;
...
transact(goals.map(g => tx.goals[g.id].delete()));

Calling delete on an entity also deletes its associations. So no need to worry about cleaning up previously created links.

link is used to create associations.

Suppose we create a goal and a todo.

transact([
  tx.todos[workoutId].update({ title: 'Go on a run' }),
  tx.goals[healthId].update({ title: 'Get fit!' }),
])

We can associate healthId with workoutId like so:

transact([tx.goals[healthId].link({ todos: workoutId })])

We could have done all this in one transact too via chaining transaction chunks.

transact([
  tx.todos[workoutId].update({ title: 'Go on a run' }),
  tx.goals[healthId].update({ title: 'Get fit!' }).link({ todos: workoutId }),
])

You can chain multiple links in one tx too.

transact([
  tx.todos[workoutId].update({ title: 'Go on a run' }),
  tx.todos[proteinId].update({ title: 'Drink protein' }),
  tx.todos[sleepId].update({ title: 'Go to bed early' }),
  tx.goals[healthId]
    .update({ title: 'Get fit!' })
    .link({ todos: workoutId })
    .link({ todos: proteinId })
    .link({ todos: sleepId }),
])

Order matters when creating links.

transact([tx.goals[healthId].link({ todos: workoutId })])

This creates:

  • A forward link from entity healthId to entity workoutId via goals -> todos
  • A reverse link from entity workoutId to entity healthId via todos -> goals

We can query associations in both directions via these links

const { isLoading, error, data } = useQuery({
  goals: { todos: {} },
  todos: { goals: {} },
})

const { goals, todos } = data

Links can be removed via unlink

transact([tx.goals[healthId].unlink({ todos: workoutId })])

This removes both:

  • The forward link from entity health to entity workout
  • The reverse link from entity workout to entity health

Similar to other actions, we can chain multiple unlink on a tx.

transact([
  tx.goals[healthId]
    .unlink({ todos: workoutId })
    .unlink({ todos: proteinId })
    .unlink({ todos: sleepId }),
  tx.goals[workId]
    .unlink({ todos: standupId })
    .unlink({ todos: reviewPRsId })
    .unlink({ todos: focusId }),
])

unlink can work in both directions. So this works:

transact([tx.goals[healthId].unlink({ todos: workoutId })])

And so does this!

transact([tx.todos[workoutId].unlink({ goals: healthId })])

More features to come!

Similar to InstaQL, We're actively building more features for InstaML. We love getting requests for new features!

Previous
InstaQL