Skip to content

Scribe

The module you require and call. Scribe(options) compiles your template and returns a { Server, Client } bundle. Use .Server on the server and .Client on the client from the same shared module.

Scribe also exposes the field declarators (Scribe.Int, Scribe.Vector3, …) you use inside a template, the visibility wrappers (Scribe.ServerOnly, Scribe.Shared, Scribe.Session), and library-wide diagnostics.

local Scribe = require(ReplicatedStorage.Packages.Scribe)

return Scribe({
    Template = { Coins = Scribe.Int(0, { Min = 0 }) },
    ProfileStoreIndex = "PlayerData",
    ProfileKeyPrefix = "PLAYER_",
})

Setup

.Version

property
Scribe.Version: string

The library version (e.g. "1.0.0").

.new

Scribe.new(options: ScribeOptions)  Bundle

Compiles the template and builds the { Server, Client } bundle. Calling the module directly (Scribe(options)) is sugar for this. Require the same shared module on both the server and the client and use the matching half.

Visibility

.ServerOnly

Scribe.ServerOnly(value: T)  T

Marks a root field server-only: it persists and is readable on the server, but is never replicated to any client. May also wrap a nested field to keep just that subtree server-side.

.Shared

Scribe.Shared(value: T)  T

Marks a root field shared: it replicates to every client (not just the owner), for public info like a display name or team.

.Session

Scribe.Session(value: T)  T

Marks a root field session-only: replicated to the owner but never persisted. Ideal for runtime state like InCombat that should reset on rejoin.

Field Types

.Int

Scribe.Int(default: number, meta: { Min: number?, Max: number? }?)  number

Declares an integer field. Non-integer writes round (or reject under BoundsPolicy = "Reject"); out-of-range writes clamp. Bounded ints also pack to a smaller wire width.

.Number

Scribe.Number(default: number, meta: { Min: number?, Max: number? }?)  number

Declares a floating-point field with optional bounds.

.String

Scribe.String(default: string, meta: { MaxLength: number? }?)  string

Declares a string field, optionally length-capped.

.Enum

Scribe.Enum(default: string, members: { string })  string

Declares a string field restricted to a fixed set of members. Writes outside the set are rejected, and the value packs to a single byte.

.Timed

Scribe.Timed(default: T)  T

Declares a field with an expiry. Set it with field.SetTimed(value, seconds); it auto-clears back to default when the timer lapses (firing Changed). Great for boosters and temporary buffs.

.Dynamic

Scribe.Dynamic(factory: () -> T)  T

Declares a field whose default is produced by factory per profile rather than frozen once at server start. Use it for values that must be evaluated for each profile, like a creation timestamp:

CREATION_UNIX = Scribe.Dynamic(os.time),
JOINED_AT     = Scribe.Dynamic(function() return DateTime.now() end), -- datatype, packed for you

Returning players keep their stored value; the factory only runs to seed the field on a brand-new profile (or one that gains the field after you add it to the template later). The field types as the factory's return type.

The factory must be pure. It is also called once at declaration time (while your template module loads) to sample its return type, so it must be side-effect-free and must neither yield nor error; keep it to plain producers like os.time / DateTime.now. Anything that reserves an id, reads a store, or has other side effects would fire that effect at module load with no profile attached. Player-specific defaults (based on player.Name, a lookup, and so on) belong in OnPlayerInit.

Scribe.Dynamic cannot sit inside a Scribe.Session root (a startup error): Session data is rebuilt from its default every session, so a one-time seed has nowhere to live. Use OnPlayerInit for per-session values.

.Vector3

Scribe.Vector3(default: Vector3)  Vector3

Declares a Roblox datatype field. The value is stored and replicated as a compact packed buffer; Get/Set convert at the boundary, so your code only ever sees the real Vector3.

.Vector2

Scribe.Vector2(default: Vector2)  Vector2

Declares a Roblox datatype field. The value is stored and replicated as a compact packed buffer; Get/Set convert at the boundary, so your code only ever sees the real Vector2.

.Vector3int16

Scribe.Vector3int16(default: Vector3int16)  Vector3int16

Declares a Roblox datatype field. The value is stored and replicated as a compact packed buffer; Get/Set convert at the boundary, so your code only ever sees the real Vector3int16.

.Vector2int16

Scribe.Vector2int16(default: Vector2int16)  Vector2int16

Declares a Roblox datatype field. The value is stored and replicated as a compact packed buffer; Get/Set convert at the boundary, so your code only ever sees the real Vector2int16.

.CFrame

Scribe.CFrame(default: CFrame)  CFrame

Declares a Roblox datatype field. The value is stored and replicated as a compact packed buffer; Get/Set convert at the boundary, so your code only ever sees the real CFrame.

.Color3

Scribe.Color3(default: Color3)  Color3

Declares a Roblox datatype field. The value is stored and replicated as a compact packed buffer; Get/Set convert at the boundary, so your code only ever sees the real Color3.

.BrickColor

Scribe.BrickColor(default: BrickColor)  BrickColor

Declares a Roblox datatype field. The value is stored and replicated as a compact packed buffer; Get/Set convert at the boundary, so your code only ever sees the real BrickColor.

.UDim

Scribe.UDim(default: UDim)  UDim

Declares a Roblox datatype field. The value is stored and replicated as a compact packed buffer; Get/Set convert at the boundary, so your code only ever sees the real UDim.

.UDim2

Scribe.UDim2(default: UDim2)  UDim2

Declares a Roblox datatype field. The value is stored and replicated as a compact packed buffer; Get/Set convert at the boundary, so your code only ever sees the real UDim2.

.Rect

Scribe.Rect(default: Rect)  Rect

Declares a Roblox datatype field. The value is stored and replicated as a compact packed buffer; Get/Set convert at the boundary, so your code only ever sees the real Rect.

.NumberRange

Scribe.NumberRange(default: NumberRange)  NumberRange

Declares a Roblox datatype field. The value is stored and replicated as a compact packed buffer; Get/Set convert at the boundary, so your code only ever sees the real NumberRange.

.NumberSequence

Scribe.NumberSequence(default: NumberSequence)  NumberSequence

Declares a Roblox datatype field. The value is stored and replicated as a compact packed buffer; Get/Set convert at the boundary, so your code only ever sees the real NumberSequence.

.ColorSequence

Scribe.ColorSequence(default: ColorSequence)  ColorSequence

Declares a Roblox datatype field. The value is stored and replicated as a compact packed buffer; Get/Set convert at the boundary, so your code only ever sees the real ColorSequence.

.DateTime

Scribe.DateTime(default: DateTime)  DateTime

Declares a Roblox datatype field. The value is stored and replicated as a compact packed buffer; Get/Set convert at the boundary, so your code only ever sees the real DateTime.

.EnumItem

Scribe.EnumItem(default: EnumItem)  EnumItem

Declares a Roblox datatype field. The value is stored and replicated as a compact packed buffer; Get/Set convert at the boundary, so your code only ever sees the real EnumItem.

.Font

Scribe.Font(default: Font)  Font

Declares a Roblox datatype field. The value is stored and replicated as a compact packed buffer; Get/Set convert at the boundary, so your code only ever sees the real Font.

.PhysicalProperties

Scribe.PhysicalProperties(default: PhysicalProperties)  PhysicalProperties

Declares a Roblox datatype field. The value is stored and replicated as a compact packed buffer; Get/Set convert at the boundary, so your code only ever sees the real PhysicalProperties.

.Datatypes

property
Scribe.Datatypes: { Pack: (name: string, value: any) -> buffer, Unpack: (name: string, b: buffer) -> any }

Low-level pack/unpack helpers for the 17 datatypes, exposed so migrations can convert legacy representations to the packed form.

Diagnostics

.GetStatus

Scribe.GetStatus()  "Healthy" | "Degraded" | "Outage"

The current data-service health, derived from ProfileStore's error signals with hysteresis. Also broadcast to clients via Client:GetServiceStatus.

.OnStatusChanged

signal
Scribe.OnStatusChanged: Signal

Fires with the new status ("Healthy" | "Degraded" | "Outage") whenever service health changes.

.OnIssue

signal
Scribe.OnIssue: Signal

Fires for every Error/Fatal log entry: the single hook to wire up developer alerting (Discord webhook, PagerDuty, …) from your own game code.

.AddLogSink

Scribe.AddLogSink(fn: (entry: LogEntry) -> ())

Registers an extra log sink. Scribe never sends logs anywhere itself. Add a sink to forward them to your own pipeline. Keep secrets in game code.

.GetRecentLogs

Scribe.GetRecentLogs(filter: { Level: LogLevel?, Category: LogCategory?, Code: LogCode?, Limit: number? }?)  { LogEntry }

Returns entries from the 512-entry ring buffer, newest first, optionally filtered by level/category/code. Code, Level, and Category are typed string unions, so editors autocomplete valid values (see the Log Code Reference for the full list of codes).

.GetMetrics

Scribe.GetMetrics()  { [string]: number | { Count: number, Average: number, Max: number } }

A snapshot of internal counters (saves, loads, receipt outcomes, queue depths, …) for admin panels and load tests. Plain counters are numbers; timing and size distributions are { Count, Average, Max } records.