Motivations & Design Decisions
No single point of failure
The primary goal of Lasco is to store photos without a single point of failure. That means storing data across multiple locations and not depending on a single provider. To achieve this, all application logic lives in the client, which can connect to multiple storage backends (cloud providers, local disks, external hard drives) using the same primitives.
This also reduces the risk of things going wrong: no server to deploy or maintain means fewer mistakes compared to self-hosting a server application. It also limits exposure to compromised dependencies or new vulnerabilities. However, this simplicity comes at a cost: without a server to coordinate writes, the system must handle conflicts on its own.
Shared access model
To simplify implementation and encryption management, all files are considered accessible by all users of a library.
Git-inspired remote model
Inspired by Git, Lasco introduces the concepts of a remote, push, and fetch:
- A remote is any storage backend (a cloud drive, a local disk, an external hard drive).
- Push uploads local state to a remote.
- Fetch downloads remote state to the local device.
This model covers two important use cases:
Replication. There is no dedicated "backup" operation. Instead, replication is achieved by pushing to multiple remotes. Adding a photo and pushing to two storage backends is all it takes to have two copies. The push/fetch abstraction is the only primitive needed.
Sporadic backups. Remotes do not need to be permanently connected. An external hard drive can serve as a backup target: connect it, push, and disconnect. To restore from it later, connect it again and fetch. This works naturally within the same model.
Two remarks
Push and fetch are not symmetric operations. Fetch never downloads media files. It only retrieves what is needed to reconstruct the library state: administrative files and operation files. Each photo and thumbnail is downloaded individually, on demand, as its own explicit operation.
Since media are rarely present locally, a push often involves downloading from one remote and uploading to another. Lasco therefore tracks which media each remote already contains.
Conflict-free design
Because no server coordinates writes, concurrent modifications from multiple clients could produce conflicts. For files stored on a remote—an S3 server, a hard drive, or another storage backend—Lasco reduces the risk of concurrent writes with two simple rules:
- Unique random names. Every new file is given a randomly generated name, making name collisions effectively impossible.
- Immutability. Once created, a file is never modified in place. The one exception is the local pending-operations file, which accumulates operations before they are uploaded.
Lasco stores every action using two CRDTs:
- The operation set. Each action is represented by one immutable operation. Together, operations form a growing-set CRDT: new operations are added, never changed or removed. Merging two operation sets is simply taking their union, so it is straightforward and independent of which device observed an operation first.
- The library state. Operations are applied to
CrdtState, which represents the library itself: its media, albums, groups, and their properties. This state is also a CRDT. Its merge rules are designed and checked so the result is correct regardless of the order in which operations are applied.
The library state can therefore be persisted and cached locally. When a device obtains new operations, it applies them to its existing state instead of rebuilding the library from scratch.