Skip to content

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.

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 strings
local Again = Serde.decode(Base64.decode(AsText)) -- string → buffer; length must be % 4
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.
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.
  • 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.decode asserts the input length is a multiple of 4 — malformed or truncated text errors instead of decoding into garbage bytes.