DocumentService
DocumentService is an open-source Luau library for saving data with Roblox DataStores.
It can be used for sesssion-locked data, such as player data, and for non-session-locked data, like
shared groups or houses. It has several advantages over other available libraries, such as being fully strictly typed.
Links
- Repository on GitHub for downloading & viewing the source code, reporting bugs and making contributions.
- Documentation & examples for help with using DocumentService.
- API reference
- There is also a thread in the Roblox OSS discord server under “projects”.
- TypeScript version: coming soon!! (not maintained by myself)
Features
- Fully strictly typed internals and API. This means you get full intellisense and typechecking on your data and on every API method. Your schema defines the type of your data.
- Superior Rust-inspired error handling, with a Result type for each method
that provides unique intellisense on which errors you need to handle. - Session-locking: Documents can be session-locked, or not (to allow multi-server editing). Session-locking simply extends the API, so it is easy to use DocumentService for non-player data.
- Migrations, inspired by Lapis by @nezuo, which allow you to mutate your schema over time.
- Validate your data against your schema with support for runtime and static typechecking.
- Immutable cache and autosaves - this prevents bugs caused by updates committing too early and accidental mutations.
- Run hooks before and after operations. You could use this to implement your own logging or serialisation.
- Automatic retry with exponential backoff.
- Checks your data can be stored in DataStores to avoid silent errors.
- No dependencies (like Promise). Use whatever abstractions you like, and install easily.
- You can inject a MockDataStore of your choosing. I recommend this one.
Installation
Method 1: Wally
Add DocumentService = "anthony0br/documentservice@LATEST_VERSION"
to your wally.toml
.
Method 2: Manual
DocumentService has no dependencies so you can just copy and paste the contents of
target/roblox
into your project.
Method 3: Roblox Asset Library
You can get it as a model here. Note that this may not be as well-maintained as the other methods!
Frequently Asked Questions
Why use this over ProfileService?
- DocumentService has far superior error handling, inspired by Rust’s Result type. Intellisense tells you which errors you need to handle for each method - and only the ones relevant to that method. This helps you write better code, faster.
- It’s fully typed checked. This means you’re less likely to make mistakes and don’t need to spend time cross-referencing the documentation!
- It’s just as powerful, but with a much simpler API that follows SOLID principles.
- Validation - ProfileService does not validate your data. This means an accidental mistake could cause data inconsistency or corruption, and if data is manually changed you could get weird errors and side effects. DocumentService lets you validate data before you save it and as you load it, preventing bugs. DocumentService will also error if your data is unsavable, making issues obvious to you before you publish your game - ProfileService will not.
- Migrations - in ProfileService, if you want to change how your data is formatted, you can’t. You can only add new stuff! This can limit long-term maintenance. DocumentService supports migrations (inspired by Lapis), which allow you to mutate your schema over time and mark changes as not backwards compatible.
- ProfileService’s API is very player-centric, which makes it difficult to work with data that isn’t session-locked. DocumentService is designed so that session-locking extends the API, rather than defines it. You can turn it off and still use methods that don’t involve caching in the exact same way. DocumentService also has a method designed specifically to work with non-session-locked data efficiently called
:OpenAndUpdate
. - Cache updates are immutable in DocumentService, which helps prevent bugs. In ProfileService, they are mutable.
- ProfileService is outdated and hasn’t been maintained in several years - for example it still imposes a 7 second wait time between requests, which is no longer needed, and was written in Lua 5.1 rather than Luau.
- DocumentService’s source code is easier to read and maintain.
But what about MetaTags and GlobalUpdates?
You don’t need MetaTags. Just create a new table in your data. You can implement GlobalUpdates by first checking if the player is in the current server and :IsOpenAvailable
. Then,
- In the case they are in the same server - just directly update their data. Otherwise:
- In the case
:IsOpenAvailable
returns false, use MessagingService to update their data from the server where their data is open. - In the case
:IsOpenAvailable
returns true - use:OpenAndUpdate
to open and update their data and then close the document.
Give me an example!
Sure, here are some:
How does it compare to other popular alternatives - Lapis, DataKeep, etc?
Lapis and DataKeep are focussed on session-locked data and are not fully strictly typed. They return Promises, whereas DocumentService will yield and return a Result type. DataKeep also does not feature migrations, but has natively implemented GlobalUpdates. I would highly recommend these two projects as alternatives to ProfileService / Suphi’s if you don’t like DocumentService. DocumentSevice also has more powerful hooks, with the ability to run hooks on any main operation and cancel them at any time.
It’s also easier to migrate an existing game to DocumentService than it is with most other libraries, since DocumentService will automatically reformat existing keys.
I have an existing game - how do I start using this?
If you try to open a Document on a key which already contains data, but has not been used with DocumentService before, DocumentService will try use the existing data, so it’s easy to migrate - especially if you are not using any library. I’d recommend starting out by following the examples and writing a file for your schema (including your types and check function) based on your existing data. You could also use migrations to tidy up your data!
Disclaimer
This is a new release and, although it has been thoroughly unit tested, it hasn’t yet been used
in a live production game - as with any open source software, use it at your own risk! I am
working on adding it to the games I work on, which currently peak at ~2k CCU, so this won’t be a concern for too long.
Special thanks to @nezuo and @kineticwallet for helping to review my code and API, and for creating a mock DataStoreService which has been incredibly useful.