lo-fi
Scroll down!
Subway-tolerant apps
A holistic local-first web toolkit
Build a schema
- Define model fields, defaults, and query indexes
- Schemas are TypeScript, so you can use familiar code and modules
- Create migrations as your schema evolves
schema.ts
import { collection, schema} from '@lo-fi/web';import cuid from 'cuid';const posts = collection({ name: 'post', primaryKey: 'id', fields: { id: { type: 'string', default: cuid, }, title: { type: 'string', }, body: { type: 'string', }, createdAt: { type: 'number', default: Date.now, }, image: { type: 'file', nullable: true, } },});export default schema({ version: 1, collections: { posts }});
Generate a client
- Generated code is type-safe to your schema
- Queries and models are reactive
- React hook bindings included
client.ts
import { ClientDescriptor, createHooks, migrations} from './generated.js';export const clientDescriptor = new ClientDescriptor({ migrations, namespace: 'demo', })export const hooks = createHooks();async function getAllPosts() { const client = await clientDescriptor.open(); const posts = await client.posts .findAll().resolved; return posts;}
Store local data
- Store data in IndexedDB, no server or internet connection required
- Undo/redo and optimistic updates out of the box
- Assign files directly to model fields
app.tsx
import { hooks, clientDescriptor} from './client.js';export function App() { return ( <Suspense> <hooks.Provider value={clientDescriptor}> <Posts /> </hooks.Provider> </Suspense> );}function Posts() { const posts = hooks.useAllPosts(); return ( <ul> {posts.map((post) => ( <Post key={post.get('id')} post={post} /> ))} </ul> );}function Post({ post }) { const { title, body, image } = hooks.useWatch(post); return ( <li> <input value={title} onChange={(e) => post.set( 'title', e.target.value )} /> <textarea value={body} onChange={(e) => post.set( 'body', e.target.value )} /> <input type="file" onChange={(e) => post.set( 'image', e.target.files[0] )} /> {image && <img src={image.url} /> } </li> );}
Sync with a server
- Access data across devices
- Share a library with collaborators
- Peer presence data and profile info
- Works realtime, pull-based, even switching dynamically
server.ts
import { Server } from '@lo-fi/server';const server = new Server({ databaseFile: 'db.sqlite', tokenSecret: 'secret', profiles: { get: async (userId: string) => { return { id: userId, } } }});server.listen(3000);