Releases: ericplane/Scribe
Release list
Scribe v2.0.0 - The Integrity Update
This release closes a long list of defects in the money, persistence and replication paths, and adds
derived fields, idempotent commands, narrowed float replication and a schema check for stored data.
It also changes a number of behaviours that existing games depend on, including the replication wire
format, so read the behaviour changes below before upgrading. A game that uses neither monetization
nor offline writes will find most of its risk in the wire format change and the template compile
rules.
Behaviour changes
-
The replication protocol version moved from 1 to 6, so a server and a client built from different
Scribe versions now refuse each other and logPROTOCOL_MISMATCHinstead of mis-decoding frames.
Deploy the server and the client together, because a mixed deploy leaves players unable to load. -
Scribe now ships its own patched copy of ProfileStore inside the package, so the ProfileStore
Wally dependency is no longer required. Remove it from yourwally.tomlwhen you upgrade. -
Game code can no longer write anywhere inside Scribe's reserved
_Scriberoot, and every mutator
on such a path now raises an error naming the path and the API that owns that state. Reading a
table inside that root also hands back a detached copy rather than the live stored table. -
A transaction can no longer touch a second player's data. Opening a transaction on another player,
or writing to one from inside an open transaction, now raises and rolls the transaction back, and
the error points at the durable outbox as the way to move value between players. -
A cross-server message is no longer acknowledged when nothing is connected to
Data.OnMessageor
when a handler raises. It stays on the key and is offered again on the player's next load, so a
handler must now tolerate seeing the same message twice. -
Data.RestoreVersionno longer rolls the reserved_Scriberoot back with the game data. Granted
receipts, paid gifts, perks, the purchase log and running cooldowns are carried across from the
live profile, and the newRollBackReservedoption restores the old behaviour when that root is
itself what needs repairing. -
A migration step that changes the reserved
_Scriberoot now has that change discarded and the
stored root kept, reported asMIGRATION_RESERVED_DISCARDED. A migration that rebuilt the profile
from its own key list used to destroy receipt idempotency and paid gifts in silence. -
Data.SendMessagenow returns false and logsMESSAGE_QUEUE_FULLwhen the recipient's offline
inbox is at its cap. It used to report success after throwing a message away. -
A template that declares a non-finite
MinorMaxonScribe.Number, or aMaxLengththat is
not a non-negative integer, now fails to compile. This fails at startup rather than in production,
and a negativeMaxLengthpreviously deleted the end of every value it was applied to. -
Data.UpdateOfflinenow commits as a compare-and-set, so the session check and the write are a
single DataStore call. It gained one refusal reason, that the profile changed while the update was
being prepared, and a refusal now writes nothing at all. -
Data.WaitForDatacan now answerstill-loadingwhere it used to answertimeout. A load that
is merely slow is worth retrying, so code branching ontimeoutshould handle both. -
Cooldown and claim keys passed to the public timed API are now refused if they contain invalid
UTF-8 or begin with@, which is reserved for Scribe's own idempotency claims. Rename any key of
yours that starts with that character. -
A write that would leave a container holding both array indices and string keys is now refused,
whether it arrives as a keyed write or as anInsert. That shape loses half its contents on save. -
A product grant that yields and then fails part way is now settled as granted, logged as
GRANT_PARTIALand counted inReceiptsPartial. It used to be retried, which compounded the
writes it had already made. -
Two pass names sharing one gamepass
Idnow fail to boot, matching the refusal products have
always had for a duplicateId. Give each pass its own gamepass or register it once, because an
in-experience purchase reports only the Id and used to credit whichever of the two names Scribe
registered last. -
tostringon aScribe.Bignow keeps the fractional part instead of rounding to a whole number,
so a third of ten prints as3.33333333333333. The numbers inside bounds error messages change
with it. -
Dividing a
Scribe.Bigby zero now raises instead of returning nil. -
A
Setthat writes the value a field already holds no longer firesChangedor queues a
replication op on aScribe.Big, a flags field or a datatype field. Those three used to fire
where the identical no-op on an integer cost nothing. -
SchemaPolicynow defaults toWarnunderDevModeand stays off on live servers, so a Studio
session reports stored data that no longer matches the template. An explicit setting still wins in
both directions. -
Data.Requestnow returnsScribe.RequestFailedas a third value whenever the refusal is
Scribe's rather than your handler's. Only a caller that forwards the results ofData.Request
straight into another call needs to change. -
In edit mode, meaning a storybook or the command bar, a bundle now builds the client half instead
of the server half. Building the server half used to create the transport folder and RemoteEvents
in ReplicatedStorage and leave the client stub raising.
Added
-
Scribe.Deriveddeclares a field that Scribe computes from other declared fields instead of
accepting writes. It is never persisted or migrated, it recomputes when an input changes, and
every mutator is absent from its type and raises at runtime. -
Client.RequestOncesends a command tagged with a caller-supplied idempotency key, so the server
runs the handler at most once per key and answers repeats with the original reply. Keys must be
non-empty, valid UTF-8 and at most 64 bytes. -
A command spec now accepts
Idempotent = true, which makes the command require a key sent through
RequestOnce. The requirement is symmetric, so a key sent to a plain command and a keyless call
to an idempotent one are both refused. -
PurchaseSpecgained an optionalIdempotencyKey, and a repeat under the same key returns
exactly what the first call returned and spends nothing. The newPurchaseClaimTTLand
MaxPurchaseClaimsoptions govern how long a claim is kept and how many may be live on one
profile. -
Data.Stopreleases everything a bundle holds on the process, including the background loops, the
Players and MarketplaceService listeners and the transport channel claim. A game never needs it,
but a test suite or a simulation that builds many bundles does. -
Scribe.Numbergained aPrecisionoption that narrows a replicated field to four, two or one
bytes. The server keeps the full double it was given and only the client copy is quantized, so do
not compare the two for equality. -
Scribe.CFramegainedPrecision = "exact", which packs every component bit for bit at 49 bytes
instead of the default 13 or 29.Scribe.Datatypes.Packtakes the same value as an optional third
argument. -
A
Scribe.Bigvalue now supportsPowfor a non-negative integer exponent andLog10. Both are
reads that return a new value, andPowrefuses a fractional, negative or non-finite exponent. -
The new
SchemaPolicyoption checks stored data against the template when a profile loads. Only a
table mixing array indices with string keys ends the session underReject, and under that
setting a boundedScribe.Bigoutside its bounds also refuses the load. -
An outbound frame larger than the outbound budget is now split into fragments and reassembled by
the client, where it previously could not be sent at all. A frame needing more than sixteen
fragments logsOUTBOUND_OVERSIZEonce per server. -
A custom transport may now declare
MaxFrameBytes, and Scribe keeps every frame under it. An
adapter whose own framing inflates the buffer can carry its ceiling with it instead of having to
be paired with a matchingMaxOutboundBytessetting. -
Scribe.GetPercentilesreturns the P50, P90 and P99 of each recorded metric, whichGetMetrics
could not report. It is computed over the most recent 256 samples per name, so it does not agree
with the all-time count. -
Scribe.GetBudgetSnapshotreports the DataStore request allowance the engine currently gives, by
request type. ItsAvailablefield is false when the engine could not be asked at all. -
Scribe.AddLogSinknow returns a function that removes the sink again, so a sink with a lifetime
no longer stays registered for the life of the server. -
Scribe.RequestReason,Scribe.PurchaseReasonandScribe.GiftReasonname the fixed refusals of
Data.Request,Data.PurchaseandData.PromptGift, each with a matching exported type. -
The new
ImportLegacyDataoption adopts data from another library once, before Scribe has ever
saved for that player. The adopted profile then runs the full migration chain. -
The new
LoadTimeoutoption bounds how long a profile load is given, defaulting to 120 seconds
with a floor of 60. -
The new
LogRingSizeoption sets how many recent entriesGetRecentLogsretains, which used to
be fixed at 512. -
The new
MaxOutboundBytesoption caps the bytes in one outbound frame before fragmentation,
defaulting to 65536 with a floor of 256. -
The new
MaxInboundRetainedBytesoption caps how much memory one inbound client frame may cause
the server to retain, defaulting to sixteen timesMaxInboundBytes. -
Th...
Scribe v1.3.2 - Security Hardening
- A hardening pass on the client boundary: command arguments decode only after the rate limit and every other gate, a pre-Ready session answers
not-readyfor every name so commands can't be enumerated, and newMaxInboundFrameRatecaps raw inbound frames per player - Receipt de-duplication evicts by age (
PurchaseIdTTL,MaxProcessedPurchaseIds) rather than count, warningPURCHASE_ID_EVICTEDwhen a still-retryable id is dropped - Command
Argsaccept Scribe declarators and nested shapes - New Security guide
Full Changelog: v1.3.1...v1.3.2
Scribe v1.3.1 - Visibility Hotfix
Scribe.ServerOnly(Scribe.Session(v))resolved toSessionand replicated that field to its owner, leaking one wrapped as a secret. Saving and replication are now independent: pairScribe.SessionwithScribe.ServerOnlyorScribe.Sharedin either order, while combining those two is a startup error.Mode = "Mock"also never reached leaderboards, so Studio play-tests read and wrote real OrderedDataStores.- Upgrade note: a leaderboard
Statthat can never rank (a non-numeric field, a whole container, or aScribe.Sessionfield) is now a boot error rather than a board that stays silently empty, so check your board configs. Plus a clearerPROFILE_UNPERSISTABLEfor a raw datatype written around the accessor, a new Commands & Requests guide, and 77 documentation corrections.
Full Changelog: v1.3.0...v1.3.1
Scribe v1.3.0 - Big, SetOf, MapOf, Flags Declerators
- Added
Scribe.SetOf(unique membership) - Added
Scribe.MapOf(declared key type, so integer keys survive the DataStore round trip) - Added
Scribe.Flags(up to 32 named booleans in one field) - Added
Scribe.Big(idle-game numbers past 2^53, rankable on a leaderboard, with a per-boardSigFigstrading exponent range for displayed resolution) - Added
EvictonArrayOffor a self-trimming history - Added
OnPlayerLeavingwhich runs before the final save, so a playtime tally written there persists OnCooldowntakes{ IncludeOfflineTime = false }for a cooldown that only ticks while the player is onlineBatchnow delivers the "one Changed pass" it always documented: a container fires once per batch rather than once per write, on both realms,Insert/Remove/Clearincluded.- Upgrade notes: a container
Changedtakes(new, old)and errors at connect on a thirdkeyparameter, so move that logic to the newOnChildChanged(key, new, old). Leaf listeners are unchanged. AScribe.Bigboard is server-only, and itsentry.Scoreis a big rather than a number.
Full Changelog: v1.2.1...v1.3.0
Scribe v1.2.1 - .Get() & .Clone() Fixes
- Root whole-data reads (
Get,Clone,Observe,Changed) now return exactly your declared roots: no internal_Scribe, andScribe.Sessionroots are finally included. Get()is frozen, so a strayGet().Coins = 1raises instead of writing silently.Scribe.ServerOnlyfields left the client type, so reading one there is a build error, not anil.- Use
Clone()to edit,Data.Exportfor internals.
Full Changelog: v1.2.0...v1.2.1
Scribe v1.2.0 - Leaderboard Improvements
- Leaderboards gain a per-board
RefreshInterval(default 60s, floored at 60) so a board can read less often - A server-side
OnLeaderboardsignal has been added that fires with(boardName, entries)when a board actually changes, including server-only boards, so you no longer need a polling loop
Full Changelog: v1.1.0...v1.2.0
Scribe v1.1.0 - OnCooldownEnded & Bug Fixes
- New
OnCooldownEndedsignal and aScribe.PlayerData<T>type for annotating one player's accessor tree. - A whole-container
Set/Clearnow firesChanged/Observeon the fields beneath it - A receipt
Grantthat throws no longer leaves partial writes that compound on retry Mode = "NoSave"dry runs actually run your migrations- Returning players whose data is still all defaults are no longer mistaken for new ones and skipped.
Value.Remove(i)with an out-of-range index is now a no-op instead of deleting a real element.- A product
Grantthat yields still works but loses rollback and logs an error; move async work outside it - New opt-in
MigrationShadowre-runs your migrations against the raw stored data in Studio and warns when a nil-guarded step silently no-ops because Reconcile already filled the field. - Offline writes and
RestoreVersionno longer report success for a save the DataStore silently dropped, which could bank a Robux grant that never persisted.
Full Changelog: v1.0.12...v1.1.0
Scribe v1.0.12 - Timeouts for WaitForData & Flush
WaitForDataandFlushnow take a timeout (60s and 15s by default), thanks to @ryancundiff in #9ProfileKeyPrefixnow accepts""for games adopting a database whose keys were bare user ids- Fixes
RestoreVersionreporting success when the profile had been erased - Fixes
Scribe.Configurewrongly refusing to run after a failedScribe()call
Full Changelog: v1.0.11...v1.0.12
Scribe v1.0.11 - Mode and strict HandleReciept
- New
Modeoption (Live,Mock,NoSave) that replaces the four separate persistence flags Scribe.Configurefor the process-wide autosave intervalTryHandleReceiptso an externalProcessReceiptrouter can fall through on products Scribe does not own.- Lifecycle failure reasons are now one typed set (
Scribe.LifecycleReason), which renamessession-endtosession-endedandlefttoplayer-left. (Changes required) - Fixes read-only profile views, which never finished loading.
Full Changelog: v1.0.10...v1.0.11
Scribe v1.0.10 - DictOf & ArrayOf Additions
- New
Scribe.ArrayOf,Scribe.DictOf, andScribe.Optional: arrays and dictionaries whose elements have a real schema, so Roblox datatypes finally work inside containers with no manualPack/Unpack, and elements get typing, bounds, andMaxItems/MaxKeyscaps. - Upgrade notes: element records are closed, so an undeclared field is a write error;
Set(nil)on a middle array index is refused (useRemove); a table can no longer mix array indices and string keys.
Full Changelog: v1.0.9...v1.0.10