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

Ask your questions at community or commercial support.

Client Example

Using Logux 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>
      }
    }

    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
        }
      }
    })