Jinaga

Application-agnostic back end for web applications.

Typically, all layers of an app are custom.In Jinaga, only the front end is.

Build Apps Faster

Typical web app architectures build domain knowledge into custom database schemas, APIs, and front ends. That means you have to write the same idea in three different ways. That slows you down.

Write your domain logic once. Jinaga provides an application-agnostic API and database.

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.

class Tweet {
    static Type = "Tweet" as const;
    type = Tag.Type;

    constructor(
        public body: string,
        public sender: User,
        public sent: Date | string
    ) { }
}

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

Creating facts

Whenever the user does something, like send a tweet, your app creates a fact. Use the function j.fact to do that.

await j.fact(new Tweet(
    "Twas Brillig, and the slithy toves did gyre and gimble in the wabe.",
    lewiscarroll,
    new Date()
));

Call the j.fact function within the browser 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 for that.

Finding facts

To find a set of facts, first define a specification function. These are usually static functions on the fact class.

class Tweet {
    // ...
    static fromSender(sender: User) {
        return j.match<Tweet>({
            type: Tweet.Type,
            sender
        });
    }
}

The query method returns all facts matching the specification.

const tweets = await j.query(lewiscarroll, j.for(Tweet.fromSender));

Again, run this code in the browser. 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 tweet is posted, compose a projection specification.

const tweetSpec = specificationFor(Tweet, {
    body: field(t => t.body),
    sender: property(
        j.for(Tweet.sender)
            .then(User.names),
        n => n.value,
        "<sender>"
    )
});

Then map the specification to props of a component.

const tweetMapping = mapProps(tweetSpec).to(({ body, sender }) => (
    <div>
        <p>{body}</p>
        <p>{sender}</p>
    </div>
));

Composing components

Reference mappings inside of specifications to compose the application.

const feedSpec = specificationFor(User, {
    Tweets: collection(
        j.for(Follow.byUser)
            .then(Follow.sender)
            .then(Tweet.fromSender),
        tweetMapping
    )
});

const feedMapping = mapProps(feedSpec).to(({ Tweets }) => (
    <div>
        <Tweets />
    </div>
));

Top it all off with a container.

const FeedContainer = jinagaContainer(j, feedMapping);

function App({}) {
    const [ user, setUser ] = useState<User | null>(null);

    useEffect(() => {
        j.login().then(({ userFact }) => {
            setUser(userFact);
        });
    }, []);

    return (
        <FeedContainer fact={user} />
    );
}

Subscribing to facts

To have new tweets pushed to the browser, call subscribe.

j.subscribe(lewiscarroll, j.for(Tweets.fromSender));

And with this, facts created in one browser make their way to other browsers. 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 browser, to server, and back again. It persists them durably, transmits them reliably, and updates the view automatically.

Demos