Skip to content

Repository files navigation

Logux

Logux is a flexible JS framework to make local-first sync engine with real-time updates, offline-first, CRDT, an optimistic UI.

  • Instead of other local-first solutions, it is not a database, but a framework to build sync engines with specific needs of your project.
  • No vendor lock-in. It works with any database and in any cloud.
  • Great TypeScript support with end-to-end type checking from client to server.
  • We thought about many production-ready problems like monitoring, scaling, outdated clients, authentication, rich test API.
  • Optional end-to-end encryption.
  • Just extra 7 KB in client-side JS bundle.

Ask your questions at community or commercial support.

Next chapter


  Logux Docs is built by Evil Martians, an American design and engineering consultancy for developer tools, AI, and cybersecurity startups.


Client Example

Using Logux Client:

React client
import { syncMapTemplate } from '@logux/client'

export type TaskValue = {
  finished: boolean
  text: string
  authorId: string
}

export const Task = syncMapTemplate<TaskValue>('tasks')
export const ToDo = ({ userId }) => {
  const tasks = useFilter(Task, { authorId: userId })
  if (tasks.isLoading) {
    return <Loader />
  } else {
    return <ul>
      {tasks.map(task => <li>{task.text}</li>)}
    </ul>
  }
}
export const TaskPage = ({ id }) => {
  const client = useClient()
  const task = useSync(Task, id)
  if (task.isLoading) {
    return <Loader />
  } else {
    return <form>
      <input type="checkbox" checked={task.finished} onChange={e => {
        changeSyncMapById(client, Task, id, { finished: e.target.checked })
      }}>
      <input type="text" value={task.text} onChange={e => {
        changeSyncMapById(client, Task, id, { text: e.target.value })
      }} />
    </form>
  }
}
Vue client

Using Logux Vuex:

<template>
  <h1 v-if="isSubscribing">Loading</h1>
  <div v-else>
    <h1>{{ counter }}</h1>
    <button @click="increment"></button>
  </div>
</template>

<script>
  import { computed } from 'vue'
  import { useStore, useSubscription } from '@logux/vuex'

  export default {
    setup() {
      // Inject store into the component
      let store = useStore()
      // Retrieve counter state from store
      let counter = computed(() => store.state.counter)
      // Load current counter from server and subscribe to counter changes
      let isSubscribing = useSubscription(['counter'])

      function increment() {
        // Send action to the server and all tabs in this browser
        store.commit.sync({ type: 'INC' })
      }

      return {
        counter,
        increment,
        isSubscribing
      }
    }
  }
</script>
Pure JS client

You can use Logux Client API with any framework:

client.type('INC', (action, meta) => {
  counter.innerHTML = parseInt(counter.innerHTML) + 1
})

increase.addEventListener('click', () => {
  client.sync({ type: 'INC' })
})

loading.classList.add('is-show')
await client.sync({ type: 'logux/subscribe' channel: 'counter' })
loading.classList.remove('is-show')

Server Example

Using Logux Server:

addSyncMap<TaskValue>(server, 'tasks', {
  async access (ctx, id) {
    const task = await Task.find(id)
    return ctx.userId === task.authorId
  },
  async load (ctx, id, since) {
    const task = await Task.find(id)
    if (!task) throw new LoguxNotFoundError()
    return {
      id: task.id,
      text: ChangedAt(task.text, task.textChanged),
      finished: ChangedAt(task.finished, task.finishedChanged),
    }
  },
  async create (ctx, id, fields, time) {
    await Task.create({
      id,
      authorId: ctx.userId,
      text: fields.text,
      textChanged: time,
      finished: fields.finished,
      finishedChanged: time
    })
  },
  async change (ctx, id, fields, time) {
    const task = await Task.find(id)
    if ('text' in fields) {
      if (task.textChanged < time) {
        await task.update({
          text: fields.text,
          textChanged: time
        })
      }
    }
    if ('finished' in fields) {
      if (task.finishedChanged < time) {
        await task.update({
          finished: fields.finished,
          finishedChanged: time
        })
      }
    }
  }
  async delete (ctx, id) {
    await Task.delete(id)
  }
})

addSyncMapFilter<TaskValue>(server, 'tasks', {
  access (ctx, filter) {
    return true
  },
  initial (ctx, filter, since) {
    let tasks = await Tasks.where({ ...filter, authorId: ctx.userId })
    return tasks.map(task => ({
      id: task.id,
      text: ChangedAt(task.text, task.textChanged),
      finished: ChangedAt(task.finished, task.finishedChanged),
    }))
  },
  actions (filterCtx, filter) {
    return (actionCtx, action, meta) => {
      return actionCtx.userId === filterCtx.userId
    }
  }
})

About

Guide, recipes, and protocol specifications for Logux

Resources

Stars

358 stars

Watchers

13 watching

Forks

Used by

Contributors

Languages