Expensify

Stars and mentions

Last 30 days, Oct 5 – Nov 3

GitHub stars33
mentions2
01234Oct 5Oct 19Nov 3
UTC days · repositories Expensify owns5 repositories

Package downloads

Cumulative recorded downloads · since 2025-10-29

npm2.8M
0705.8K1.4M2.1M2.8MOct 5Oct 19Nov 3
Oct 27npm: 1/1 packages observed

Across 1 known packages in repositories Expensify owns. Cumulative totals include recorded downloads before the selected range. Missing observations leave gaps; totals may be incomplete.

Top repositories

Expensify’s repositories ranked by new forks, as on trending.

Loading

Contributes to

Repositories outside Expensify’s account where Expensify opened or reviewed pull requests in the past 365 days.

Who noticed Expensify

Everyone who starred or forked Expensify’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

2posts
Nov 40 posts
  1. rqlite, dqlite y Litestream: SQLite distribuido En septiembre de 2014, Philip O'Toole subió a GitHub un proyecto de fin de semana: combinar SQLite con un log de Raft para que las escrituras se replicaran en un clúster. Doce años después, ese experimento dio nombre a una categoría entera de software, el SQLite distribuido, con dos adquisiciones y financiamiento de riesgo detrás. O'Toole publicó este año una retrospectiva completa de la historia, desde rqlite hasta las plataformas gestionadas actuales (ver Referencias). Este artículo toma ese repaso como punto de partida para explicar la categoría y comparar las opciones vigentes: rqlite, dqlite, Litestream, LiteFS y Turso/libSQL, y cuándo conviene cada una. TL;DR - SQLite distribuido combina SQLite con replicación (Raft, streaming de WAL o CRDTs) para sobrevivir a la caída de un nodo.- rqlite, publicado en 2014, fue el primer proyecto abierto en sumar Raft a SQLite para formar un clúster CP.- dqlite ofrece el mismo patrón que rqlite pero como librería en C, embebida en LXD y MicroK8s de Canonical.- Litestream prescinde del consenso: solo transmite el WAL a S3 para recuperar un nodo único tras una caída.- LiteFS y Turso/libSQL muestran las otras dos rutas: réplicas por sistema de archivos y un fork del motor para el edge. ¿Qué es SQLite distribuido? SQLite distribuido es la categoría de sistemas que toman el motor de base de datos SQLite, diseñado para un solo archivo en un solo proceso, y le agregan una capa de replicación entre varios nodos para que la caída de una máquina no implique perder datos ni disponibilidad del servicio. La propuesta no es nueva en el diseño, pero sí en el empaquetado: antes de 2014 la replicación de bases relacionales exigía Postgres, MySQL o un sistema distribuido completo como Cassandra. El SQLite distribuido ofrece lo mismo a una escala mucho menor, con un binario único y sin un equipo de operaciones dedicado. Por qué importa la replicación de SQLite con Raft SQLite guarda toda la base en un solo archivo y un solo proceso escribe a la vez. Es rápido, simple y perfecto para una app de escritorio, un sitio con tráfico moderado o un dispositivo embebido. Pero si la máquina que lo corre se cae, el archivo se pierde con ella salvo que exista un backup reciente. Ese problema concreto fue el que O'Toole enfrentó en 2014 en jut.io: un sistema en clúster necesitaba almacenamiento relacional chico, y no había forma clara de decidir qué nodo debía guardarlo. Replicar SQLite con un protocolo de consenso resolvía la pregunta sin montar una base de datos completa aparte. La necesidad era específica, no un rediseño general de la arquitectura. Doce años después, el mismo argumento sigue vigente para microservicios pequeños, agentes de borde y aplicaciones que no justifican operar Postgres en alta disponibilidad, pero sí necesitan sobrevivir la pérdida de un nodo sin intervención manual. Cómo funciona: Raft, streaming de WAL y CRDTs La retrospectiva de O'Toole identifica tres enfoques distintos para resolver el mismo problema, y los tres aparecieron en orden cronológico. flowchart TD A["SQLite + replicación"] --> B["Consenso: rqlite, dqlite"] A --> C["Streaming de WAL: Litestream, LiteFS"] A --> D["Fusión sin líder: cr-sqlite"] B --> E["CP: consistente, pausa en partición"] C --> F["Un escritor, recuperación barata"] D --> G["AP: siempre escribible, fusiona después"] El primer enfoque: consenso con Raft rqlite y dqlite usan Raft, un protocolo de consenso donde un nodo líder recibe las escrituras, las replica a los demás nodos del clúster y solo confirma la operación cuando una mayoría la reconoce. Si el líder cae, el clúster elige uno nuevo entre los nodos con el log más actualizado. Esto hace que ambos sean sistemas CP según el teorema CAP: priorizan la consistencia sobre la disponibilidad, así que una partición de red donde el clúster pierde la mayoría deja de aceptar escrituras hasta que la mayoría se restablece. sequenceDiagram participant C as Cliente participant L as "Nodo líder" participant F1 as "Nodo seguidor 1" participant F2 as "Nodo seguidor 2" C->>L: POST /db/execute L->>F1: replica entrada de log L->>F2: replica entrada de log F1-->>L: ack F2-->>L: ack Note over L: mayoría confirmó, commit L-->>C: 200 OK El segundo enfoque: streaming del WAL Litestream no usa consenso. Observa el write-ahead log (WAL) de SQLite, el archivo donde el motor anota cada cambio antes de aplicarlo a la base, y lo transmite de forma continua a un almacenamiento como S3. Si el nodo único se cae, la base se reconstruye desde ese WAL guardado: no hay clúster ni quórum, solo un escritor y una copia casi en tiempo real. LiteFS, construido por el mismo autor de Litestream (Ben Johnson) tras ser contratado por Fly.io, replica a nivel de sistema de archivos y usa leases de Consul para decidir qué nodo es el primario en cada momento. flowchart LR A["SQLite local"] --> B["Escribe en el WAL"] B --> C["Litestream observa el WAL"] C --> D[("S3 u otro almacenamiento de objetos")] E["Nodo cae"] --> F["Restaura desde el WAL guardado"] D --> F F --> A El tercer enfoque: fusión sin líder cr-sqlite agrega CRDTs a SQLite como extensión: varios nodos pueden escribir al mismo tiempo sin coordinarse y fusionar sus cambios después sin conflictos irreconciliables. Es el corner AP del diseño, siempre disponible para escribir, a costa de una consistencia que se resuelve después, no en el momento. El mapa completo: de rqlite a las plataformas gestionadas Antes de rqlite, Expensify ya replicaba SQLite internamente con un protocolo estilo Paxos y lo liberó como Bedrock en 2016. ActorDB, un sistema en Erlang, replicaba SQLite por actor casi al mismo tiempo que rqlite. Ninguno de los dos se adoptó masivamente fuera de su origen, pero confirman que la idea ya estaba en el aire en 2014. Canonical construyó dqlite en 2017 para darle a LXD y luego a MicroK8s un almacén replicado embebido sin depender de etcd. dqlite nació en Go y se reescribió en C con su propia librería de Raft. La diferencia con rqlite es sobre todo de empaquetado: dqlite es una librería que se embebe dentro de otro programa, rqlite es un servidor que corre al lado de cualquier aplicación. Ben Johnson publicó Litestream a comienzos de 2021. Su argumento, documentado en la retrospectiva de O'Toole, fue que la mayoría de las aplicaciones no necesitan un clúster: corren en una sola máquina y solo necesitan una forma de recuperarse si esa máquina muere. Fly.io lo contrató en 2022 y lanzó LiteFS, la primera versión de SQLite replicado como función de plataforma en lugar de algo que el desarrollador despliega por su cuenta. libSQL tomó un camino distinto: en vez de agregar una capa externa, bifurcó el propio motor de SQLite para modificar su núcleo, algo que el proyecto SQLite, cerrado a contribuciones externas, no permite. ChiselStrike construyó sobre ese fork un servicio con réplicas en el edge y se convirtió en Turso, que levantó capital de riesgo con esa propuesta. En 2026, Supabase adquirió Turso. SQLite Cloud, en paralelo, ofrece SQLite alojado con replicación Raft, la opción más parecida al diseño original de rqlite pero envuelta como servicio administrado. Cloudflare siguió otro camino: anunció D1 en 2022 como SQLite sin servidor dentro de su red de borde y más tarde agregó almacenamiento SQLite a Durable Objects. En ese punto el clúster de SQLite dejó de ser algo que alguien corre y pasó a ser un producto que alguien compra. rqlite fue publicado en 2014, el primer proyecto que combinó SQLite con Raft. Comparativa: rqlite, dqlite, LiteFS, Litestream y Turso/libSQL La elección depende del tipo de disponibilidad que necesita la aplicación: tolerar la caída de un nodo con recuperación casi instantánea, o apenas necesitar una copia de seguridad barata. La tabla resume cuándo conviene cada opción. OpciónCuándo usarlaVentajaLimitaciónrqliteClúster chico que necesita seguir escribiendo si un nodo cae, vía HTTPServidor standalone, API HTTP simple, sin dependencias externasEscrituras solo por el líder, latencia de red en cada confirmacióndqliteEmbeber un almacén replicado dentro de otro programa (como LXD)Librería en C, se integra sin levantar un proceso apartePensado para integrarse en un producto, no para usarse sueltoLitestreamUn solo servidor que solo necesita recuperación ante desastreCero coordinación, corre casi gratis junto a la appNo da alta disponibilidad: restaurar desde S3 toma tiempoLiteFSApps en Fly.io con un escritor primario y réplicas de lecturaReplicación a nivel de archivo, transparente para SQLiteAtada al ecosistema de Fly.io y a Consul para los leasesTurso / libSQLLecturas de baja latencia desde el edge, con escritura centralizadaRéplicas globales gestionadas, fork con funciones propiasDepende de un proveedor y de un fork que diverge del SQLite oficial Cómo empezar con rqlite De esta lista, rqlite es la más simple de probar en una laptop porque es un solo binario sin dependencias. Esta guía usa Linux; en macOS alcanza con brew install rqlite, y en Windows lo más simple es correrlo dentro de WSL con los mismos pasos. Descargá el binario desde la página de releases en GitHub (ajustá versión y arquitectura) y extraelo: curl -LO https://github.com/rqlite/rqlite/releases/latest/download/rqlite-linux-amd64.tar.gz tar xzf rqlite-linux-amd64.tar.gz cd rqlite-*/ Arrancá un primer nodo. El flag -node-id identifica al nodo dentro del clúster y el último argumento es el directorio donde guarda sus datos: ./rqlited -node-id 1 ~/node1 Con el nodo corriendo, abrí otra terminal y creá una tabla y una fila con la API HTTP sobre el puerto 4001: curl -XPOST localhost:4001/db/execute -H "Content-Type: application/json" -d '[ "CREATE TABLE registros (id INTEGER PRIMARY KEY, nodo TEXT)", "INSERT INTO registros (nodo) VALUES (\"node1\")" ]' La respuesta esperada es un JSON con el resultado de cada statement y cuánto tardó en confirmarse: {"results":[{},{"last_insert_id":1,"rows_affected":1}],"time":0.00234} Para leer el dato, usá el endpoint de consulta: curl -G 'localhost:4001/db/query' --data-urlencode 'q=SELECT * FROM registros' Que devuelve algo como: {"results":[{"columns":["id","nodo"],"types":["integer","text"],"values":[[1,"node1"]]}],"time":0.0004} Para confirmar que el nodo participa de un clúster de Raft (aunque sea de uno solo) y quién es el líder, consultá el endpoint de estado: curl localhost:4001/status | python3 -m json.tool | grep -A2 '"raft"' Debería mostrar "state": "Leader" en un nodo único. Si sumás un segundo nodo con -join http://localhost:4001 y volvés a consultar su status, ese campo pasa a "state": "Follower", confirmando que la replicación está activa. Casos de uso reales - Clúster de borde chico: rqlite o dqlite para un servicio que debe seguir atendiendo lecturas y escrituras si cae una de tres máquinas, sin levantar Postgres en alta disponibilidad.- Apps de un solo servidor con backup barato: Litestream corriendo junto a una app Rails o Django que vive en una sola VM, para no perder datos si la VM se destruye.- Multi-región con un escritor primario: LiteFS para apps en Fly.io que leen desde la región más cercana al usuario pero escriben siempre contra el primario.- Producto embebido en otro software: dqlite dentro de LXD o MicroK8s, donde el desarrollador final nunca interactúa directamente con el almacén replicado.- Lecturas globales de baja latencia: Turso o SQLite Cloud cuando el equipo prefiere pagar por un servicio gestionado en vez de operar el clúster. Fly.io lanzó LiteFS en 2022, ocho años después de la primera propuesta de rqlite. Errores comunes y buenas prácticas El error más frecuente es tratar a rqlite o dqlite como reemplazo transparente del archivo SQLite local. No lo es: las escrituras pasan por una API HTTP (rqlite) o por la librería embebida (dqlite), no por el driver sqlite3 de siempre. Migrar una app existente implica cambiar la capa de acceso a datos, no solo el archivo de configuración. Otro error es ignorar los niveles de consistencia de lectura. rqlite permite leer con nivel none (de cualquier nodo, puede estar desactualizado), weak (confirma que el nodo sigue siendo líder) o strong (pasa por el log de Raft, más lento pero garantiza la última escritura confirmada). Usar none en un reporte financiero porque es la opción por defecto es el tipo de bug que no aparece hasta que un follower se atrasa. > ⚠️ Ojo: un clúster de rqlite o dqlite con número par de nodos no mejora la tolerancia a fallas frente a tener uno menos: Raft necesita mayoría, y 4 nodos tolera la misma caída que 3, con más latencia de red por cada escritura. Con Litestream, el error típico es asumir que reemplaza a un clúster: solo da recuperación ante desastre de un solo nodo, no alta disponibilidad continua. Si la VM se cae, hay un hueco real (el tiempo de restaurar desde S3 y levantar el proceso) antes de volver a servir tráfico. Con libSQL y Turso, conviene revisar qué extensiones del fork usa la app antes de asumir compatibilidad total con SQLite estándar: al ser un fork, algunas funciones no existen en el SQLite original y viceversa. Profundizando: por qué el teorema CAP importa acá Raft garantiza que, mientras una mayoría de nodos esté disponible y se comunique, el clúster tiene exactamente un líder y todas las escrituras confirmadas sobreviven incluso si ese líder cae después. Esa garantía tiene un costo: si la red se particiona y ningún lado tiene mayoría, ningún lado acepta escrituras hasta que la partición se resuelve. rqlite y dqlite eligen ese costo de forma explícita, la decisión de diseño que O'Toole señala como la que no cambió en doce años. El corner de streaming (Litestream, LiteFS) no enfrenta ese dilema porque no hay múltiples escritores compitiendo por confirmar al mismo tiempo: hay un escritor y una cola de cambios que se replica hacia afuera. La contrapartida es que la recuperación no es instantánea, depende de cuánto WAL hay que reproducir. El corner de fusión (cr-sqlite) resuelve el problema de otra forma: en vez de impedir escrituras concurrentes, las permite y resuelve los conflictos con una regla determinista según el CRDT elegido. Es el diseño correcto para apps local-first, donde cada dispositivo escribe sin conexión y sincroniza después, pero sería un mal ajuste para un caso que necesita una única verdad inmediata, como un balance bancario. 📖 Resumen en Telegram: Ver resumen Tu próximo paso: levantá un clúster de rqlite de tres nodos en tu laptop, matá el proceso del líder a mitad de una escritura y confirmá con curl localhost:4003/status que otro nodo asumió el rol antes de los diez segundos default de elección. Preguntas frecuentes ¿rqlite reemplaza a Postgres o MySQL? No para cargas grandes. rqlite sirve para volúmenes de datos chicos donde se necesita alta disponibilidad simple, no el conjunto completo de funciones (índices avanzados, extensiones, tamaños grandes) de una base relacional completa. ¿dqlite se puede usar fuera de LXD o MicroK8s? Sí, es una librería en C de uso general, pero su diseño y documentación están pensados para embeberse dentro de otro programa, no para correr como servicio independiente del modo en que corre rqlite. ¿Litestream da alta disponibilidad? No. Litestream da recuperación ante desastre de un solo nodo: si ese nodo se cae, hay que restaurar desde el almacenamiento externo antes de volver a servir tráfico, con el tiempo que eso tome. ¿LiteFS depende de Fly.io para funcionar? LiteFS es open source y se puede correr fuera de Fly.io, pero su integración de leases con Consul y su documentación asumen ese entorno; fuera de él hay que resolver esa coordinación por cuenta propia. ¿Turso y libSQL son compatibles al 100% con SQLite? libSQL es un fork de SQLite, así que agrega y modifica funciones que el proyecto SQLite original no acepta como contribución externa. La compatibilidad es alta pero no total: conviene revisar qué extensiones usa la app antes de migrar. ¿Cuándo conviene cr-sqlite en lugar de rqlite? Cuando la app necesita que varios dispositivos escriban sin conexión y sincronicen después (local-first), no cuando necesita una única fuente de verdad inmediata como sí ofrece rqlite con Raft. Referencias - Twelve years of distributed SQLite, Philip O'Toole: la retrospectiva completa que inspira este artículo.- rqlite en GitHub: repositorio oficial, documentación de la API HTTP y releases para descargar.- dqlite en GitHub: repositorio oficial de Canonical con la librería en C.- Litestream, sitio oficial: documentación de instalación y configuración contra S3.- libSQL en GitHub: el fork de SQLite que usa Turso.- Write-Ahead Logging, documentación oficial de SQLite: cómo funciona el WAL que observan Litestream y LiteFS. 📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.

  2. How I Debugged Expensify Bugs & Got Paid --- title: "How I Debugged Expensify Bugs & Got Paid" published: true description: "Two merged PRs, $500 in bounties, and what it actually takes to land fixes in a 10M-user React Native codebase as an outside contributor." tags: opensource, reactnative, debugging, career canonical_url: tusharbhendarkar.com cover_image: tusharbhendarkar.com --- Expensify paid me $500 for fixing two bugs in their app. I don't work there. Nobody assigned me the work. I found the bugs on GitHub, wrote up the root causes, and my fixes were merged into a React Native app used by more than 10 million people. The money is the hook, but it's not the story. The story is what it takes to get a fix merged into a codebase that size when nobody knows who you are. Here is how both bugs went down, plus two investigations that never became PRs but taught me just as much. (The contracts, payments and review are in the Expensify case study.) How Expensify's bounty system works Expensify runs one of the most open contributor programs of any production app. Bugs are filed as public GitHub issues, most carry a bounty (typically $250), and anyone can submit a proposal: a written explanation of the root cause and the fix you intend to make. If your proposal is approved, you implement it, go through review like any employee would, and get paid when it ships. The catch: proposals compete. The way you win is not by being first, it's by being the person who actually understood the bug. Bug 1: The onboarding modal that wouldn't go away The symptom: a user connects Xero (accounting software) to their Expensify account, has two-factor authentication disabled, and refreshes the page. The onboarding modal pops up behind the 2FA setup prompt. This user finished onboarding long ago. The modal had no business being there (issue #46026). The obvious place to look was the modal component. That was the wrong place. The network tab told the real story. On app load, the client calls an OpenApp API to fetch initial user data into Onyx, Expensify's client-side state store. When Xero is connected and 2FA is disabled, the server refuses with error code 666 ("Your organization requires Two Factor Authentication") and returns empty data. That means the nvp_onboarding key, the flag saying "this user finished onboarding", never arrives on the client. From there the dominoes fall in order: the hasCompletedGuidedSetupFlow selector reads the missing key and returns false, the navigation root sees an "un-onboarded" user, and it calculates the onboarding route as the app's initial state. The UI was doing exactly what it was told. The data was the problem. !Data flow of Expensify issue 46026: OpenApp returns error 666 with empty data, the onboarding flag never arrives, and navigation starts at onboarding. The fix makes the initial state aware of the pending 2FA requirement. Every step did what it was told. The missing onboarding flag, not the modal, was the bug. My fix (PR #46750, three files) made the initial-state calculation aware of the pending 2FA gate: if the 2FA requirement modal is about to show, don't treat missing onboarding data as "user needs onboarding". Merged, $250. The lesson: when a screen does something impossible, ask what data it was missing, not which component misbehaved. Bug 2: The chat that vanished when you pressed the down arrow The symptom: in Search, navigating chats with the keyboard scrolled the selected chat out of view whenever the chats were large (issue #48902). An accessibility bug: if you can't see what's focused, keyboard navigation is useless. The cause lived in React Native's SectionList virtualization. The shared list component computes getItemLayout offsets by hand, and the list header and footer were accounted for differently than SectionList expects. Combine that with SectionList's default behavior of keeping exactly one item above the focused one, and items that can grow taller than the screen, and the focused chat ends up off-screen. Here is the part that made this bug hard: the component, BaseSelectionList, is shared by a huge surface of the app. The reviewer suggested only pushing section header layouts conditionally. I tested that branch across my accounts and found it broke the virtualized list entirely on one of them, and I posted a video proving it (the discussion). We converged on viewOffsetToKeepFocusedItemInView: admittedly a workaround, and I said so in the thread, but it was the change whose failure modes we could actually enumerate. My fix (PR #51754) shipped. Merged, $250. The lesson: in a shared component, the best fix is not the most elegant one. It's the one whose blast radius you can defend in review. The investigations that never became PRs Two more bugs ate my evenings without paying out, and they were still worth it. The back button that went home(issue #47562): creating a workspace from the search page, then pressing back, dumped you at the inbox instead of where you came from. The existing code "fixed" this with Navigation.resetToHome(), which is a mask, not a fix. I traced the real cause into the core routing logic: an extra route was being pushed into the root navigation state because a flag (isCentralPaneAndBottomTabMandatory) stayed true during full-screen navigator actions. I proposed both the targeted fix and the deeper flag change, with file-and-line links for every claim. The mis-styled system message(issue #47487): a system message ("exported this report to CSV") rendered faded with an error indicator. Everyone's instinct said frontend styling bug. I clicked "Reply in thread" and watched the backend return an error for exactly this message type and no other. The styling was the app correctly rendering a server-side data problem. That analysis redirected the fix to the PHP backend and saved someone a week of patching the wrong layer. What transfers to any large codebase - Reproduce before you read code. Both merged fixes started with a reliable reproduction, not a theory. - Write the root cause down with evidence. Every proposal linked the exact lines involved. Maintainers can verify a claim in minutes instead of re-deriving it. - Trace the data, not the symptom. One bug looked like a modal problem and was a missing API payload. Another looked like CSS and was a backend error. The same rule explains why AI chatbots give wrong answers: the model is rarely the problem, the data it receives is. - Respect the blast radius. When a component is shared everywhere, choose the fix you can test the edges of, and show your testing. If you want to break into a large open source codebase, find a project that pays for fixed bugs and treat the proposal like the product. The bounty is $250. The proof that you can walk into 10 million users' worth of unfamiliar code and leave it better is worth a lot more. Common questions How does the Expensify bug bounty program work? Expensify files bugs as public GitHub issues, and most carry a bounty, typically $250. Anyone can submit a proposal explaining the root cause and the intended fix. If the proposal is approved, you implement it, go through code review like an employee would, and get paid when the fix ships. How much does Expensify pay for a bug fix? Most issues carry a bounty of about $250. The two fixes in this post paid $250 each, $500 in total. Do you need to work at Expensify to fix their bugs? No. Proposals are open to anyone. Both fixes here came from an outside contributor, working as a contractor through Upwork. How do you get a proposal accepted on a large open source project? Reproduce the bug reliably first, write the root cause down with links to the exact lines involved, trace the data rather than the screen where the symptom appears, and choose a fix whose side effects you can test and explain. --- This post first appeared on tusharbhendarkar.com. I write about the engineering behind what I ship. If something here is broken for you, email me at tusharbhendarkar44@gmail.com.

    1 comment
Trending awards, every completed day and week
Gold
01st place finishes
Silver
12nd place finishes
Bronze
03rd place finishes
Awarded repositories
1Owned by Expensify now

Repositories

Ordered by gold, then silver, then bronze.

  1. AppBest: 2nd by PR authors, 2 PR authors010

Every award

Top-three finishes of these repositories in completed trending days and weeks, newest first.

  1. #2 by PR authorsDaily trending
    2 PR authors
    App

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.