InstaQL
Have questions? Join us on discord!

Context

There’s three parts to Instant:

(a) In Memory Store Users want a way to keep a normalized store for all data. They then want to query against this data, to draw different UI views. They also want to write mutations to the data, which update all views.

(b) Local Storage This In Memory Store is backed by some Local Storage (IndexedDB, SQLLite). This is so previous fetches from the server are cached. Mutations are also kept here, so if there are intermittent connection issues we can sync.

(c) Reactive Backend This all talks to a backend. The backend is responsible for reactivity and permission rules.

This document goes over just (a) In Memory Store. If we get this right, this can help us define the API that developers will use to talk to Instant.

Problem

(a) In Memory Store tries to solve three problems:

  1. Normalized State
  1. Folks want a simple way to plop a bunch of normalized data. This way they know they know all their UIs have a consistent view of the data.
  1. Derived Views
  1. They want to query this normalized state, to generate derived structures that are useful for their UI.
  1. Mutations
  1. The want a simple interface to update this normalized state. The update should be transparent enough, that it can run on both the client and the server.

Requirements

  1. Flexible Schema
  1. Hackers want to move quickly. When they first start out, they don’t know the schema. We should lean into this and make it the default experience.
  1. JS-Native QL
  1. I don’t think it make sense to invent a new language that needs to be compiled. Whatever we create, users should be able to write without changing their build. 
  1. Transparent Mutations
  1. It should be possible to infer the change a mutation causes strictly from the mutation object itself. i.e there should be no need to refer to anything else, and hence possible to run this mutation in any environment
  1. Failure
  1. We should support handling a failed transaction. In the case a transaction fails, we need a way to reset to the “latest” view of the data.
  1. Reactive Queries
  1. The query system should have a reactive interface. If any mutation changes a query, it should update automatically
  1. Optimized for Graph
  1. Most developers want to get graphs of nested data. We should make support for this as easy as possible. Common queries should be easy for developers

Considerations for a Query Language

SQL: Many people use SQL. However, I don’t think we should use it on the client. Mainly, SQL doesn’t support graph-like queries easily. I think we should think higher-level than this. If we start with a more limited set that makes the common operations easy, we can support it more simply and provide higher value. 

Datalog: Datalog can be highly expressive and comparatively simple to implement. However, I think it’s too foreign for developers. Whatever we should create, should be intuitive to the frontend engineer

So, what is intuitive?

I think something like GraphQL is the most intuitive. If we had something like GraphQL, but expressible in vanilla JS, with powerful where filters, we’d get really far.

Related Work

  • Datascript is an in-memory db with 4K+ stars. Lots of note-taking apps use them b/c of how easy it is to express relationships
  • DatalogUI is a typescript port of differential datalog. This means that the queries here are incremental! Pretty cool.
  • Firebase and Parse have great client-side abstractions for fetching data.

So, here’s an interesting experiment. Can we create a language that looks kind of like GraphQL, but lets you write powerful filters?

Init API

Before you can query or mutate, make sure useInit is called in your component tree.