Jinaga

Local-first web and mobile application framework

Choose your language:

A Jinaga mobile or web app works with local data first. The app or PWA works offline. When the device comes online, the data synchronizes with the server.

But that's just the start. The developer experience is incredible.

Imagine coding a mobile app as if the data were already on the device. Or writing a web app that loads data from the local store, not the server. Imagine not maintaining a REST API.

How much faster could you build your app?

Write code only on the edge using Jinaga. Deploy a network of Jinaga Replicators to move data between devices and back-end services.

Replicators

You will never have to write a REST API for your app. You won't need to maintain a database schema. And you won't need to set up queues or topics to get data to your services.

Learn the concepts

Facts

A Jinaga data model is made of facts. A fact is an immutable JavaScript object which has a type field. Write a TypeScript or JavaScript class to help you create them. Here's an example in TypeScript.

export class Post {
  static Type = "Blog.Post" as const;
  public type = Post.Type;

  constructor(
    public createdAt: Date | string,
    public site: Site
  ) { }
}

See that Site type? That's another fact. That's how facts are related to each other.

Creating facts

Whenever the user does something, like create a blog post, your app creates a fact. Use the function j.fact to do that.

await j.fact(new Post(
  new Date(),
  site
));

Call the j.fact function within the browser or mobile app whenever you want to save something. It will be sent to the server, where it will be stored. You don't need a custom API.

Finding facts

To find a set of facts, first write a specification.

const postsInSite = model.given(Site).match(site =>
  site.successors(Post, post => post.site)
    .select(post => ({
      hash: j.hash(post),
      titles: post.successors(PostTitle, title => title.post)
        .select(title => title.value)
    }))
);

The specification finds all facts of a certain type related to the given starting point. It then selects the fields you want to use.

The query method returns all facts matching the specification.

const posts = await j.query(postsInSite, site);

Again, run this code in the browser or mobile client. There is no need to set up an API to perform this query on the server.

Displaying facts

A query is a one-time operation. If you want to update the UI every time a post is created, call the useSpecification hook.

const { loading, data, error } = useSpecification(j, postsInSite, site);

The data variable will contain the results of the query. Render the results in your component.

  return (
    <div>
      <h1>{site?.domain}</h1>
      { loading ? <p>Loading...</p> : null }
      { error ? <p>Error: {error.message}</p> : null }
      { data ? <ul>
        { data.map(post =>
          <li key={post.hash}>{post.titles.join(', ')}</li>
        ) }
      </ul> : null }
    </div>
  );

Conclusion

And with this, facts created by one user make their way to other users. You didn't write a custom API. You didn't set up a Web Socket listener. You didn't define a custom database schema.

Jinaga synchronizes immutable facts from client, to server, and back again. It persists them durably, transmits them reliably, and updates the view automatically.

Jinaga is a product of Jinaga LLC.

Michael L Perry, President