Base64
Base64 is the buffer-native escape hatch for stores that cannot hold raw buffers. It sits underneath Serde — the schema/struct codec engine that packs records into buffers in the first place — so that a buffer Serde produced can still reach a store that only speaks JSON-safe text. Together they are the serialization layer under every persistence path in the framework.
Mental model
Section titled “Mental model”Base64 exists because some stores only hold JSON-safe text, not raw buffers. Base64 is implemented buffer-to-buffer (--!native, lookup tables in buffers, no string concatenation churn) — RFC 4648 with padding — so encoding a Serde buffer to text and back costs no string-concatenation churn.
Buffer support is never assumed anywhere it’s used — it’s checked at runtime, per store, rather than guessed from the platform. MemoryDriver is the clearest example: it feature-detects buffer support by probing a raw buffer write, and only a type error proves buffers unsupported (a throttle during the probe must not lock the driver into base64’s ~33% overhead). Fallback values carry a versioned CK1: prefix so raw strings written by other code are never misread, and ForceBase64 = true skips the probe entirely when you already know the answer.
local ReplicatedStorage = game:GetService("ReplicatedStorage")local Serde = require(ReplicatedStorage.ChloeKernel.Serde)local Base64 = require(ReplicatedStorage.ChloeKernel.Base64)
local Blob = Serde.encode({ Anything = true, Nested = { 1, 2.5, "x" } })local AsText = Base64.encode(Blob) -- buffer → string, +33% size, for stores that need stringslocal Again = Serde.decode(Base64.decode(AsText)) -- string → buffer; length must be % 4API reference
Section titled “API reference”| Member | Description |
|---|---|
Base64.encode(buffer) → string |
RFC 4648 with padding, buffer-native. Roughly +33% size over the source buffer. |
Base64.decode(string) → buffer |
RFC 4648 with padding, buffer-native. Asserts length is a multiple of 4. |
Consumers
Section titled “Consumers”| Consumer | Behavior |
|---|---|
| MemoryDriver | Buffer support is feature-detected per store by probing a raw buffer write; only a type error proves buffers unsupported (a throttle during the probe must not lock the driver into base64’s ~33% overhead). Fallback values carry a versioned CK1: prefix so raw strings written by other code are never misread. ForceBase64 = true skips the probe. |
| MessageDriver | Payloads are always Serde-packed then base64’d — MessagingService carries text. The ~1KB ceiling is checked against the packed length, with an error naming both sizes. |
| DataDriver | Codec’d profiles store as raw buffers on backends that support them (DataStores do, natively); on backends that do not, the save wraps as a { F = "B64", P = <base64> } envelope. See Backends. |
Design notes
Section titled “Design notes”- Base64 is a pure encode/decode utility with no state of its own — everything about when to use it (feature detection, forcing, envelopes) lives in the consumer driver, not in Base64 itself.
- The ~33% size overhead is the price of JSON-safe text; drivers only pay it when a store proves it needs to (see MemoryDriver’s probe) or unconditionally where the transport is inherently text-only (see MessageDriver).
Base64.decodeasserts the input length is a multiple of 4 — malformed or truncated text errors instead of decoding into garbage bytes.