Skip to main content

TableManager

TableManager is a wrapper around luau tables that provide easy and automatic change tracking, validation, and listener management. TableManager is designed to handle the bulk of your volatile data management needs, emitting detailed change events and snapshots for any changes made to the managed table or its descendants — all without needing to manually fire events or manage listener connections.

What is TableManager good for?

  • Tracking changes to nested tables and arrays.
  • Emitting detailed change events for any modifications.
  • Providing snapshots of the current state for debugging or synchronization.
  • Integrating with ProfileStore for easy management of player data.

What TableManager is Not

  • TableManager is not a state management library, and does not include any opinionated features for structuring your data, managing side effects, or integrating with other systems. It is purely a change tracking and notification system for tables.
  • TableManager is not intended to be used with tables that are mutated by external code without going through TableManager's API.
  • TableManager is not meant for data with a high frequency of updates. It focuses on providing detailed and accurate change information, which can be expensive to generate for large or rapidly changing data.

Usage

local manager = TableManager.new({
	Player = { Name = "Alice", Health = 100 },
	Inventory = { "Sword", "Shield" },
})

-- Fire when a specific field changes.
manager:OnValueChange("Player.Health", function(health, oldHealth)
	print(`health: {oldHealth} -> {health}`)
end)

-- Fire for any change under a subtree (the field OR a descendant).
manager:OnChange("Player", function(_, _, metadata)
	print("player changed at:", table.concat(metadata.OriginPath, "."))
end)

manager:Set("Player.Health", 80)
-- prints "health: 100 -> 80"
--        "player changed at: Player.Health"

-- Array contents have their own methods and events.
manager:OnArrayInsert("Inventory", function(index, item)
	print(`picked up {item} (slot {index})`)
end)
manager:ArrayInsert("Inventory", "Potion") -- prints "picked up Potion (slot 3)"

Good Practices

  • If you have very large datastructures, consider utilizing Opaque wrappers to avoid deep cloning and unnecessary change detection.
  • Avoid mixed tables. This is general good practice.
  • Utilize pure string keys so the linter can try to infer your types.

Types

ChangeMetadata

interface ChangeMetadata {
DiffDiff.DiffNode--

Diff node for this callback level; nil for ancestor notifications.

OriginPathPath--

The path where the assignment operation occurred (captured path)

OriginDiffDiff.DiffNode--

Root diff node for the assignment operation.

SnapshotAncestorSnapshot--

Carries RootTable for ancestor value navigation.

}

OriginPath is the assignment origin for both leaf and ancestor callbacks.

ConfigDefaults

interface ConfigDefaults {
ListenerFireModeListenerFireMode?
SignalFireModeSignalFireMode?
FlushModeFlushMode?
DuplicateReferenceModeDuplicateReferenceMode?
}

The subset of TableManagerConfig whose defaults can be overridden at the class level via TableManager.SetDefaults. Every field is optional; only the fields you provide are changed.

DuplicateReferenceMode

type DuplicateReferenceMode = "allow" | "copy"

Controls what happens when the same table is written to more than one path.

  • "allow" (default): the table becomes a SUPPORTED multi-location reference, not an error -- both paths share identity, and the write fires its own independent change events at the new path. The proxy graph still reports a single "primary" anchor (whichever path established the value's proxy first).
  • "copy": opt OUT of sharing for this write by deep-cloning the value first, so the new path gets its own independent identity instead.

ListenerFireMode

type ListenerFireMode = "immediate" | "deferred" | "bindable" | "coalesced"

How listener callbacks (OnChange, OnValueChange, Observe, OnKey*, OnArray*, For*) are scheduled when they fire.

  • "immediate": run now via the free-thread pool, like a signal in immediate mode.
  • "deferred": run via task.defer, like a signal in deferred mode.
  • "bindable": mirror the engine's actual Enum.SignalBehavior, resolved once at construction time.
  • "coalesced": like "deferred", but repeated fires of the SAME listener before the deferred flush collapse into one call carrying the latest event data.

SignalFireMode

type SignalFireMode = "immediate" | "deferred" | "bindable" | "coalesced"

How the per-change Signals (ValueChanged, KeyAdded, etc.) are dispatched. Resolved and driven by the manager's FireScheduler: "immediate"/"deferred" map onto Signal:Fire/:FireDeferred; "bindable" resolves once (at construction) to whichever of those the environment does natively; "coalesced" collapses repeated fires of the SAME signal within a frame into one carrying the latest values.

FlushMode

type FlushMode = "immediate" | "coalesced"

Controls WHEN the diff-and-fire cycle runs for a direct write.

  • "immediate" (default): runs synchronously with the write.
  • "coalesced": defers it to frame-end and merges every flush request made before then into one flush at their common ancestor path, so a frame with N writes under one subtree costs one diff/fire instead of N.

TableManagerConfig

interface TableManagerConfig {
SchemaSchemaCheck?
OnValidationFailed(
pathPathArray,
valueany,
errstring
) → ()?
ListenerFireModeListenerFireMode?--

Controls how listener callbacks (OnChange, OnValueChange, etc.) are scheduled when fired.

SignalFireModeSignalFireMode?--

Controls how the per-change Signals (ValueChanged, KeyAdded, etc.) are scheduled when fired.

FlushModeFlushMode?--

Controls WHEN a direct write's diff+fire+reconcile cycle runs.

DuplicateReferenceModeDuplicateReferenceMode?--

Defaults to "allow" (multi-location references are supported).

EnableProxiesboolean?--

Defaults to true. When false, Proxy/GetProxy are unavailable.

IgnoredPaths{Path}?--

Paths (and their descendants) that skip diff/snapshot/event work entirely.

FrozenTablesAreOpaqueboolean?--

Defaults to false. When true, a SHALLOWLY frozen table is also treated as opaque (no clone/walk, identity-compared only), trusting the freeze as an immutability assertion the same way Opaque is opt-in trust. A DEEPLY frozen table (every table-typed descendant also frozen) is always treated as opaque, regardless of this flag — that case is provably safe and needs no opt-in.

}
Implicit sharing

Linking is automatic: any table reachable (transparently) in two or more managers' trees is observed by all of them, and a write through one propagates to the others. Mark a region Opaque/OpaqueChildren to opt it out.

ForOptions

interface ForOptions {
FireForExistingboolean?--

Defaults to true: run the handler/transform for items already present at subscribe time.

Deferboolean?--

Defer the initial fire (honors the registry's deferred-fire mode either way).

}

Properties

TableManager.DefaultListenerFireMode

This item is read only and cannot be modified. Read OnlyStatic
TableManager.TableManager.DefaultListenerFireMode: ListenerFireMode

The ListenerFireMode used when TableManagerConfig.ListenerFireMode is omitted: it controls how listener callbacks (OnChange, OnValueChange, Observe, OnKey*, OnArray*, For*) are scheduled when they fire. Defaults to "bindable".

Read at construction time only -- changing it affects only TableManagers created afterwards. Read-only -- use TableManager.SetDefaults to change it.

TableManager.DefaultSignalFireMode

This item is read only and cannot be modified. Read OnlyStatic
TableManager.TableManager.DefaultSignalFireMode: SignalFireMode

The SignalFireMode used when TableManagerConfig.SignalFireMode is omitted: it controls how the public per-change Signals (ValueChanged, Changed, ArrayInserted, etc.) are scheduled when they fire. Defaults to "bindable".

Read at construction time only -- changing it affects only TableManagers created afterwards. Read-only -- use TableManager.SetDefaults to change it.

TableManager.DefaultFlushMode

This item is read only and cannot be modified. Read OnlyStatic
TableManager.TableManager.DefaultFlushMode: FlushMode

The FlushMode used when TableManagerConfig.FlushMode is omitted: it controls whether pending changes are flushed immediately or coalesced to the end of the frame. Defaults to "immediate".

Read at construction time only -- changing it affects only TableManagers created afterwards. Read-only -- use TableManager.SetDefaults to change it.

TableManager.DefaultDuplicateReferenceMode

This item is read only and cannot be modified. Read OnlyStatic
TableManager.TableManager.DefaultDuplicateReferenceMode: DuplicateReferenceMode

The DuplicateReferenceMode used when TableManagerConfig.DuplicateReferenceMode is omitted: it controls whether writing the same table to more than one path shares its identity ("allow") or stores an independent copy ("copy"). Defaults to "allow".

Read at construction time only -- changing it affects only TableManagers created afterwards. Read-only -- use TableManager.SetDefaults to change it.

Functions

SetDefaults

Static
TableManager.SetDefaults(defaultsConfigDefaults) → ()

Overrides the class-level construction defaults for the fields you provide, mirroring the corresponding TableManagerConfig keys. Only the fields present in defaults change; omitted fields keep their current value. Each value is validated and an unrecognized mode string errors without changing any default.

Because the defaults are read at construction time, this affects only TableManagers created afterwards -- never existing instances.

TableManager.SetDefaults({
	FlushMode = "coalesced",
	SignalFireMode = "deferred",
})

new

Static
TableManager.new(
initialDatatable,--

The initial table data to manage. Must be a table.

configTableManagerConfig?--

Optional configuration for the TableManager.

) → TableManager--

The newly created TableManager instance.

Creates a new TableManager instance.

IsDestroyed

Static
TableManager.IsDestroyed(managerTableManager) → boolean

Returns true once TableManager:Destroy has run on manager, false otherwise. Pairs with TableManager:OnDestroy for teardown-time checks.

TableManager.IsDestroyed(manager) -- safe before AND after manager:Destroy()

Opaque

Static
TableManager.Opaque(valueT) → OpaqueWrapper<T>

Wraps value so that, when written, this manager treats it as opaque: it is never cloned, frozen, or walked by the diff engine -- only identity-compared. The wrapper is unwrapped at write time, so the stored value is the bare inner value. Useful for large immutable blobs or foreign objects you don't want the diff engine to traverse. See the Opaque Values guide.

GlobalOpaque

Static
TableManager.GlobalOpaque(valueT) → OpaqueWrapper<T>

Like TableManager.Opaque, but registers value as opaque in a registry shared by every TableManager rather than just this one.

OpaqueChildren

Static
TableManager.OpaqueChildren(valueT) → OpaqueWrapper<T>

Wraps a container so that its direct children are treated as opaque (each child is identity-compared, never cloned/frozen/walked), while the container itself is still diffed normally.

GlobalOpaqueChildren

Static
TableManager.GlobalOpaqueChildren(valueT) → OpaqueWrapper<T>

Like TableManager.OpaqueChildren, but registers the children as opaque in a registry shared by every TableManager rather than just this one.

Get

TableManager:Get(
pathPath,
suppressNilPartialPathsboolean?--

Return nil instead of erroring when a segment along the path is not a table.

) → any--

The value at path.

Reads the value at path. The path may be a dot-string ("player.health") or a path array ({ "player", "health" }); an empty path returns the root table.

By default this errors if an intermediate segment is not a table (e.g. reading "a.b" when a is a number). Pass suppressNilPartialPaths = true to return nil in that case instead.

manager:Get("player.health")        -- dot-string path
manager:Get({ "player", "health" }) -- array path (equivalent)

GetMatching

TableManager:GetMatching(pathPath) → {{
Path{any},
Valueany,
WildcardMatches{any}?
}}--

One record per matched concrete path.

Reads every value matched by a path containing wildcard ("*") segments, returning one record per concrete match:

  • Path -- the fully concrete path array (every "*" replaced by the matched key).
  • Value -- the value at that path.
  • WildcardMatches -- the keys matched by each "*", left-to-right (the same convention as listener metadata.WildcardMatches).

Each "*" matches every key present at that level; multiple wildcards compose as the product of their branch factors. Branches where the remaining path cannot resolve are skipped, and only existing values are reported -- zero matches returns an empty array. Entry order follows table iteration order and is not deterministic for dictionary keys.

A path with no wildcards returns zero-or-one records, resolved exactly like TableManager:Get (including the error on a non-table intermediate segment; literal segments before the first "*" error the same way).

local manager = TableManager.new({
	Players = {
		p123 = { Health = 100 },
		p456 = { Health = 70 },
	},
})

local matches = manager:GetMatching("Players.*.Health")
-- matches (order not guaranteed):
-- {
--   { Path = { "Players", "p123", "Health" }, Value = 100, WildcardMatches = { "p123" } },
--   { Path = { "Players", "p456", "Health" }, Value = 70,  WildcardMatches = { "p456" } },
-- }

for _, match in matches do
	print(match.WildcardMatches[1], "has", match.Value, "health")
end

-- Multiple wildcards -> one WildcardMatches entry per "*", left-to-right:
for _, match in manager:GetMatching("Players.*.Stats.*") do
	local playerId, statName = match.WildcardMatches[1], match.WildcardMatches[2]
end

GetProxy

TableManager:GetProxy(
pathPath,
suppressNilPartialPathsboolean?--

Return nil instead of erroring when a segment along the path is not a table.

) → Proxy | any--

A proxy for the table at path, or the raw value if it is not a table.

Returns a live [Proxy] for the table at path, or the raw value when path resolves to a non-table. Reads through the proxy see live data, and writes through it route back into the manager exactly as TableManager:Set would.

CAUTION

Errors if this manager was created with Config.EnableProxies = false -- there is no proxy graph to hand out. See the Proxies & Direct Table Access guide.

GetLastEmitted

TableManager:GetLastEmitted(
pathPath,
suppressNilPartialPathsboolean?--

Return nil instead of erroring when a segment along the path is not a table (only consulted on the live fallback).

) → any--

The value last emitted for path (or the live value when path was never observed).

Reads the value at path as listeners last saw it -- the value carried by the most recent change event fired for path, rather than the current live value. Under the default immediate flush mode with no pending changes this is identical to TableManager:Get; the two diverge only when the live data has moved ahead of what has been emitted -- inside a Batch/Suspend window, or under FlushMode = "coalesced" / a deferred fire mode, before the pending flush runs.

If path has never been observed (no listener, Signal, link, or OnApplied subscriber ever covered it) there is no recorded "last emitted" value, so this falls back to a live TableManager:Get -- the same value a first listener would receive.

Last-emitted nil inside a pending window

The recorded baseline cannot distinguish "never observed" from "observed, and the last value emitted was itself nil" (e.g. after an emitted deletion) -- an absent key and a nil-valued key are indistinguishable in the baseline store. Both fall back to the live Get. That fallback is exact except in one narrow window: if the last emit for path was nil and the live value has since moved ahead of it inside a pending flush (a Batch/Suspend window, or FlushMode = "coalesced" / a deferred fire mode), this returns the pending live value rather than the emitted nil. Once that flush drains, it converges again.

Table results are snapshots

When the value at path is a table, the returned table is the internal baseline mirror of the last-emitted state: it follows the same stability rules as a listener's table oldValue (a stable snapshot only for the duration of the synchronous call -- copy it if you need to retain it). Scalar results are always safe to keep.

local manager = TableManager.new({ Score = 0 }, { FlushMode = "coalesced" })
manager:OnValueChange("Score", function() end) -- observe so a baseline is kept

manager:Set("Score", 10)
manager:Get("Score")            -- 10 (live)
manager:GetLastEmitted("Score") -- 0  (listeners have not been told yet)

manager:Flush("Score")
manager:GetLastEmitted("Score") -- 10 (now emitted)

Set

TableManager:Set(
pathPath,
valueany,--

The value to write. May be wrapped with TableManager.Opaque or similar.

buildTablesDynamicallyboolean?--

When true, missing intermediate tables along path are created rather than erroring. Not allowed with wildcard paths.

) → ()

Writes value at path, firing the events for whatever changed.

manager:Set("player.health", 80)          -- dot-string path
manager:Set({ "player", "health" }, 80)   -- array path (equivalent)
manager:Set("player.buff", nil)           -- removing a key

An empty path (Set({}, newTable) / Set("", newTable)) replaces the entire root table: its identity is swapped to newTable, stale proxies of the old tree are pruned, and root/child change listeners fire for the diff. The new root must be a table (the root cannot become a scalar or nil), and the root cannot be replaced while a batch is open.

The path may contain wildcard ("*") segments -- the same grammar the listener methods accept. Each "*" matches every key present at that level at call time (multiple wildcards compose as the product of their branch factors), and the write is applied once per matched concrete path, batched into one coherent flush when more than one path matches:

manager:Set("Players.*.Health", 100)   -- every player's Health (created where missing)
manager:Set("Players.*.Stats.*", 0)    -- every stat of every player
manager:Set("Players.*", nil)          -- mass delete: removes every player

Wildcard branches where the remaining path cannot resolve are skipped, and zero matches is a no-op. Writing nil only targets existing values; a non-nil write also creates a missing literal final key on each matched parent. buildTablesDynamically cannot be combined with a wildcard path. Note that a table value fanned to multiple paths is subject to [TableManagerConfig.DuplicateReferenceMode]: under "allow" (the default) every matched path shares the SAME table identity; use "copy" for independent clones.

Update

TableManager:Update(
pathPath,
updater(
oldValueany,
wildcardMatches{any}?,
path{any}?
) → any--

Receives the current value (and, on wildcard paths, the matched keys and concrete path); returns the new value.

) → any--

The new value that was written (nil for wildcard paths).

Reads the value at path, passes it through updater, and writes the result back. Equivalent to a TableManager:Get followed by TableManager:Set, so it fires the same change events as a plain Set.

The path may contain wildcard ("*") segments: updater then runs once per matched concrete path, the writes are batched into one coherent flush, and the method returns nil. On a wildcard path each invocation receives, after the current value, the keys matched by each "*" (left-to-right, one per wildcard) and the fully concrete path -- so an updater can tell which match it is handling:

manager:Update("Players.*.Stats.*", function(value, matches, path)
	-- matches[1] = playerId, matches[2] = statName
	-- path       = { "Players", playerId, "Stats", statName }
	return value + 1
end)

matches/path are nil for a non-wildcard Update. Only existing values are visited -- use TableManager:Set to create keys through a wildcard. See TableManager:GetMatching for the matching rules.

manager:Update("coins", function(current)
	return current * 2
end)

Increment

TableManager:Increment(
pathPath,
deltaany--

The amount to add (any value supporting + with the current value).

) → any--

The new value that was written (nil for wildcard paths).

Adds delta to the number at path and writes the result back. Shorthand for an TableManager:Update that returns oldValue + delta; errors if the current value is not addable.

The path may contain wildcard ("*") segments: delta is then added to every matched existing value (batched into one coherent flush) and the method returns nil. See TableManager:Update and TableManager:GetMatching.

manager:Increment("coins", 50)  -- coins += 50
manager:Increment("Players.*.Health", 5) -- everyone heals 5

ArrayInsert

TableManager:ArrayInsert(
pathOrProxyPath | Proxy,
...any--

Either value, or index, value.

) → ()

Inserts a value into the array at pathOrProxy. Two call shapes:

  • ArrayInsert(path, value) -- appends value to the end.
  • ArrayInsert(path, index, value) -- inserts value at index, shifting the existing elements at and after index one slot to the right.

Fires ArrayInserted (and ancestor change events) for the affected index. pathOrProxy may be a path or a [Proxy] of the array.

Aliased as Insert.

manager:ArrayInsert("items", "sword")   -- append
manager:ArrayInsert("items", 1, "shield") -- insert at index 1

ArrayRemove

TableManager:ArrayRemove(
pathOrProxyPath | Proxy,
indexnumber--

1-based index of the element to remove.

) → any--

The removed element.

Removes the element at index from the array at pathOrProxy, shifting the following elements one slot to the left. Fires ArrayRemoved (and ancestor change events) at the remove path.

Aliased as Remove.

ArrayRemoveFirstValue

TableManager:ArrayRemoveFirstValue(
pathOrProxyPath,
valueToFindany--

The value to search for (compared by equality).

) → number?--

The index it was removed from, or nil if not found.

Finds the first element equal to valueToFind and removes it via TableManager:ArrayRemove.

Aliased as RemoveFirstValue.

ArraySwapRemove

TableManager:ArraySwapRemove(
pathOrProxyPath | Proxy,
indexnumber--

1-based index of the element to remove.

) → any--

The removed element, or nil if index is out of range.

Removes the element at index in O(1) by moving the last element into its slot, instead of shifting every following element. Order is not preserved.

Because the last element backfills the hole, this emits two events rather than one: an ArraySet at index (the backfill) followed by an ArrayRemoved at the old last index (the shrink). When index is already the last element, only the ArrayRemoved fires.

Aliased as SwapRemove.

ArraySwapRemoveFirstValue

TableManager:ArraySwapRemoveFirstValue(
pathOrProxyPath | Proxy,
valueToFindany--

The value to search for (compared by equality).

) → number?--

The index it was removed from, or nil if not found.

Finds the first element equal to valueToFind and removes it via TableManager:ArraySwapRemove (O(1), order not preserved).

Aliased as SwapRemoveFirstValue.

UpdateKey

TableManager:UpdateKey(
pathPath,
keyany,--

The child key/index under path to update.

updater(oldValueany) → any--

Receives the current value; returns the new value.

) → any--

The new value that was written.

Like TableManager:Update, but for the child key under path -- it updates the value at path extended by key. Handy for a single dictionary entry or array index without building the combined path yourself.

Aliased as ArrayUpdate.

manager:UpdateKey("inventory", "gold", function(n) return n + 10 end)

IncrementKey

TableManager:IncrementKey(
pathPath,
keyany,--

The child key/index under path to increment.

deltaany--

The amount to add.

) → any--

The new value that was written.

Like TableManager:Increment, but for the child key under path -- it adds delta to the value at path extended by key.

Aliased as ArrayIncrement.

Batch

TableManager:Batch(fn() → ()) → ()

Holds off firing signals for the duration of the callback, then fires needed signals at the end. Useful for batch operations where you want to suppress intermediate signals and only fire final results.

Nested calls are no-ops: the outermost Batch window covers everything.

yielding

Yielding within a batch window will leave the TableManager in a suspended state, which can cause unexpected behavior.

Suspend

TableManager:Suspend() → ()

Suspends all signal and listener firing.

It is recommended to use :Batch for better ergonomics and safety, but Suspend/Resume can be used for more manual control if needed.

Pair with Resume(). Nested calls are no-ops (the outermost window wins).

Resume

TableManager:Resume() → ()

Resumes after Suspend() and flushes all pending changes.

Flush is a single array-aware pass: per dirty branch, CheckForChangesBetween diffs the pre-batch value against the current value, collecting array-like nodes out of the dict diff; those are then re-diffed via LCS (ArrayDiff.emitDiff) into shift-faithful Array* events. See BatchFlush.

Flush

TableManager:Flush(
pathPath?--

The subtree to flush; the whole tree when omitted.

) → ()

Diffs the persistent shadow baseline against the live value at path (the whole tree when omitted), fires any resulting events, then reconciles the shadow to the current live value. A no-op when nothing observes path (no registered listener covers it, no global Signal is connected, and no link group is active).

Outside a batch this runs immediately. Inside a Suspend/Batch window it just marks the branch dirty for Resume's flush instead of firing, so an explicit Flush can never break batch atomicity.

This is also the supported way to surface a mutation made by code that bypassed Set/Proxy and wrote directly into the underlying table.

MoveTo

TableManager:MoveTo(
currentPathPath | Proxy,
newPathPath | Proxy
) → ()

Move an element from one location to another within the same table. This unsets the value at the current path and sets it at the new path, firing appropriate notifications. This is specifically useful for moving tables around without breaking proxy references.

CopyTo

TableManager:CopyTo(
currentPathPath | Proxy,
newPathPath
) → ()

DeepCopy a value from one location to another within the same table. This sets the value at the new path to be the same as the value at the current path, firing appropriate notifications. This is specifically useful for copying tables around without breaking proxy references.

Swap

TableManager:Swap(
aPath | Proxy,
bPath | Proxy
) → ()

Swaps the values at paths a and b within this manager. The swap is applied as a single batched pair of writes, and proxy references are preserved across the move. If either write fails the reparenting is rolled back so neither path is left in a partial state.

Either argument may be a path or a [Proxy]. Errors if a path is the root or if one path is an ancestor/descendant of the other (an overlapping swap is not well-defined).

manager:Swap("slots.1", "slots.2")

OnValueChange

TableManager:OnValueChange(
pathPath,
callback(
newValueany,
oldValueany?,
metadataChangeMetadata
) → (),
optionsListenerOptions?
) → Connection

Fires ONLY when this exact path is directly reassigned (not when a descendant of it changes). Shorthand for OnChange with ListenDepth = 0, ListenDepthStyle = "==".

OnChange

TableManager:OnChange(
pathPath,
callback(
newValueany,
oldValueany?,
metadataChangeMetadata
) → (),
optionsListenerOptions?
) → Connection

Fires when this path is directly reassigned OR any descendant of it changes.

Observe

TableManager:Observe(
pathPath,
callback(
newValueany,
oldValueany?,
metadataChangeMetadata?
) → (),
optionsListenerOptions?
) → Connection

Immediately invokes callback with the current value at path (oldValue and metadata both nil), then behaves like OnValueChange for subsequent changes.

OnKeyAdd

TableManager:OnKeyAdd(
pathPath,
callback(
keyany,
newValueany,
metadataChangeMetadata
) → (),
optionsListenerOptions?
) → Connection

Fires when a new key appears in the table at path (a key that did not exist before is assigned a non-nil value).

manager:OnKeyAdd("inventory", function(key, newValue)
	print(`gained {key} = {newValue}`)
end)

OnKeyRemove

TableManager:OnKeyRemove(
pathPath,
callback(
keyany,
oldValueany,
metadataChangeMetadata
) → (),
optionsListenerOptions?
) → Connection

Fires when a key is removed from the table at path (an existing key is set to nil).

manager:OnKeyRemove("inventory", function(key, oldValue)
	print(`lost {key} (was {oldValue})`)
end)

OnKeyChange

TableManager:OnKeyChange(
pathPath,
callback(
keyany,
newValueany,
oldValueany,
metadataChangeMetadata
) → (),
optionsListenerOptions?
) → Connection

Fires when an existing key in the table at path is reassigned to a different value (not added or removed -- see TableManager:OnKeyAdd / TableManager:OnKeyRemove for those).

OnArrayInsert

TableManager:OnArrayInsert(
pathPath,
callback(
indexnumber,
newValueany,
metadataChangeMetadata
) → (),
optionsListenerOptions?
) → Connection

Fires when an element is inserted into the array at path, reporting the index it landed at. Driven by array-aware diffing, so it reflects true insertions with replay-faithful indices rather than a blanket "the array changed".

OnArrayRemove

TableManager:OnArrayRemove(
pathPath,
callback(
indexnumber,
oldValueany,
metadataChangeMetadata
) → (),
optionsListenerOptions?
) → Connection

Fires when an element is removed from the array at path, reporting the index it was removed from and its old value.

OnArraySet

TableManager:OnArraySet(
pathPath,
callback(
indexnumber,
newValueany,
oldValueany,
metadataChangeMetadata
) → (),
optionsListenerOptions?
) → Connection

Fires when an existing array slot at path is overwritten in place (its index kept, its value replaced), reporting the index, new value, and old value.

GetLinkedManagers

TableManager:GetLinkedManagers() → {TableManager}

Returns the other managers currently sharing (co-observing) at least one live table identity with this one — de-duplicated, excluding self.

IsLinkedWith

TableManager:IsLinkedWith(otherTableManager) → boolean

Returns true if this manager currently shares a live table identity with other.

ForKeys

unreleased
</>
TableManager:ForKeys(
pathPath,
handler(
itemJanitorJanitor,
keyany,
metadataChangeMetadata?
) → (),
optionsForOptions?
) → Connection

Reconciles the items found at path by KEY: handler runs once per key (passed a per-item Janitor, the key, and change metadata — nil on the initial fire), and is NOT re-run when the value at an existing key changes. The Janitor is destroyed when that key is removed or the connection disconnects.

ForValues

unreleased
</>
TableManager:ForValues(
pathPath,
handler(
itemJanitorJanitor,
valueany,
metadataChangeMetadata?
) → (),
optionsForOptions?
) → Connection

Reconciles the items found at path by VALUE (as a multiset — duplicate values are tracked as independent items): handler runs once per value occurrence and is NOT re-run when that value moves between keys/indices (e.g. an ArraySwapRemove). The Janitor is destroyed when that value occurrence is no longer present or the connection disconnects.

ForPairs

unreleased
</>
TableManager:ForPairs(
pathPath,
handler(
itemJanitorJanitor,
keyany,
valueany,
metadataChangeMetadata?
) → (),
optionsForOptions?
) → Connection

Reconciles the items found at path by KEY AND VALUE: handler re-runs (tearing down the previous item's Janitor first) whenever EITHER the key or the value changes — a value change at an existing key is treated as that item leaving and a new one appearing.

SetPathIgnored

unreleased
</>
TableManager:SetPathIgnored(
pathPath,
ignoredboolean
) → ()

Marks path (and every descendant of it) as ignored or not. An ignored write still happens — Get/Raw reflect it immediately — but skips all diff/snapshot/event work, so it never fires listeners/signals and is never cloned for change detection. See TableManagerConfig.IgnoredPaths for the config-time equivalent (seeded once at construction).

PromiseValue

unreleased
</>
TableManager:PromiseValue(
pathPath,
predicate((valueany?) → boolean)?
) → Promise

Returns a promise that resolves when the value at path satisfies predicate. If no predicate is provided, resolves when the value at path first changes.

OnDestroy

TableManager:OnDestroy(callback() → ()) → () → ()--

Call to unsubscribe.

Runs a callback when the TableManager is destroyed. Useful for cleaning up resources that are tied to the lifetime of the TableManager.

Destroy

TableManager:Destroy() → ()

Destroy the TableManager and clean up all resources.

Show raw api
{
    "functions": [
        {
            "name": "SetDefaults",
            "desc": "Overrides the class-level construction defaults for the fields you provide,\nmirroring the corresponding [TableManagerConfig] keys. Only the fields present\nin `defaults` change; omitted fields keep their current value. Each value is\nvalidated and an unrecognized mode string errors without changing any default.\n\nBecause the defaults are read at construction time, this affects only\nTableManagers created afterwards -- never existing instances.\n\n```lua\nTableManager.SetDefaults({\n\tFlushMode = \"coalesced\",\n\tSignalFireMode = \"deferred\",\n})\n```",
            "params": [
                {
                    "name": "defaults",
                    "desc": "",
                    "lua_type": "ConfigDefaults"
                }
            ],
            "returns": [],
            "function_type": "static",
            "tags": [
                "Static"
            ],
            "source": {
                "line": 259,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "new",
            "desc": "Creates a new TableManager instance.",
            "params": [
                {
                    "name": "initialData",
                    "desc": "The initial table data to manage. Must be a table.",
                    "lua_type": "table"
                },
                {
                    "name": "config",
                    "desc": "Optional configuration for the TableManager.",
                    "lua_type": "TableManagerConfig?"
                }
            ],
            "returns": [
                {
                    "desc": "The newly created TableManager instance.",
                    "lua_type": "TableManager"
                }
            ],
            "function_type": "static",
            "tags": [
                "Static"
            ],
            "source": {
                "line": 342,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "Get",
            "desc": "Reads the value at `path`. The path may be a dot-string (`\"player.health\"`)\nor a path array (`{ \"player\", \"health\" }`); an empty path returns the root\ntable.\n\nBy default this errors if an intermediate segment is not a table (e.g. reading\n`\"a.b\"` when `a` is a number). Pass `suppressNilPartialPaths = true` to return\n`nil` in that case instead.\n\n\n```lua\nmanager:Get(\"player.health\")        -- dot-string path\nmanager:Get({ \"player\", \"health\" }) -- array path (equivalent)\n```",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "suppressNilPartialPaths",
                    "desc": "Return `nil` instead of erroring when a segment along the path is not a table.",
                    "lua_type": "boolean?"
                }
            ],
            "returns": [
                {
                    "desc": "The value at `path`.",
                    "lua_type": "any"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 448,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "GetMatching",
            "desc": "Reads every value matched by a path containing wildcard (`\"*\"`) segments,\nreturning one record per concrete match:\n\n- `Path` -- the fully concrete path array (every `\"*\"` replaced by the matched key).\n- `Value` -- the value at that path.\n- `WildcardMatches` -- the keys matched by each `\"*\"`, left-to-right (the same\n  convention as listener `metadata.WildcardMatches`).\n\nEach `\"*\"` matches every key present at that level; multiple wildcards compose\nas the product of their branch factors. Branches where the remaining path\ncannot resolve are skipped, and only existing values are reported -- zero\nmatches returns an empty array. Entry order follows table iteration order and\nis not deterministic for dictionary keys.\n\nA path with no wildcards returns zero-or-one records, resolved exactly like\n[TableManager:Get] (including the error on a non-table intermediate segment;\nliteral segments before the first `\"*\"` error the same way).\n\n\n```lua\nlocal manager = TableManager.new({\n\tPlayers = {\n\t\tp123 = { Health = 100 },\n\t\tp456 = { Health = 70 },\n\t},\n})\n\nlocal matches = manager:GetMatching(\"Players.*.Health\")\n-- matches (order not guaranteed):\n-- {\n--   { Path = { \"Players\", \"p123\", \"Health\" }, Value = 100, WildcardMatches = { \"p123\" } },\n--   { Path = { \"Players\", \"p456\", \"Health\" }, Value = 70,  WildcardMatches = { \"p456\" } },\n-- }\n\nfor _, match in matches do\n\tprint(match.WildcardMatches[1], \"has\", match.Value, \"health\")\nend\n\n-- Multiple wildcards -> one WildcardMatches entry per \"*\", left-to-right:\nfor _, match in manager:GetMatching(\"Players.*.Stats.*\") do\n\tlocal playerId, statName = match.WildcardMatches[1], match.WildcardMatches[2]\nend\n```",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                }
            ],
            "returns": [
                {
                    "desc": "One record per matched concrete path.",
                    "lua_type": "{ { Path: { any }, Value: any, WildcardMatches: { any }? } }"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 517,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "GetProxy",
            "desc": "Returns a live [Proxy] for the table at `path`, or the raw value when `path`\nresolves to a non-table. Reads through the proxy see live data, and writes\nthrough it route back into the manager exactly as [TableManager:Set] would.\n\n:::caution\nErrors if this manager was created with `Config.EnableProxies = false` -- there\nis no proxy graph to hand out. See the Proxies & Direct Table Access guide.\n:::",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "suppressNilPartialPaths",
                    "desc": "Return `nil` instead of erroring when a segment along the path is not a table.",
                    "lua_type": "boolean?"
                }
            ],
            "returns": [
                {
                    "desc": "A proxy for the table at `path`, or the raw value if it is not a table.",
                    "lua_type": "Proxy | any"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 547,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "GetLastEmitted",
            "desc": "Reads the value at `path` as listeners **last saw it** -- the value carried by\nthe most recent change event fired for `path`, rather than the current live\nvalue. Under the default immediate flush mode with no pending changes this is\nidentical to [TableManager:Get]; the two diverge only when the live data has\nmoved ahead of what has been emitted -- inside a `Batch`/`Suspend` window, or\nunder `FlushMode = \"coalesced\"` / a deferred fire mode, before the pending\nflush runs.\n\nIf `path` has never been observed (no listener, Signal, link, or `OnApplied`\nsubscriber ever covered it) there is no recorded \"last emitted\" value, so this\nfalls back to a live [TableManager:Get] -- the same value a first listener\nwould receive.\n\n:::caution Last-emitted `nil` inside a pending window\nThe recorded baseline cannot distinguish \"never observed\" from \"observed, and\nthe last value emitted was itself `nil`\" (e.g. after an emitted deletion) -- an\nabsent key and a `nil`-valued key are indistinguishable in the baseline store.\nBoth fall back to the live `Get`. That fallback is exact except in one narrow\nwindow: if the last emit for `path` was `nil` **and** the live value has since\nmoved ahead of it inside a pending flush (a `Batch`/`Suspend` window, or\n`FlushMode = \"coalesced\"` / a deferred fire mode), this returns the pending live\nvalue rather than the emitted `nil`. Once that flush drains, it converges again.\n:::\n\n:::caution Table results are snapshots\nWhen the value at `path` is a table, the returned table is the internal\nbaseline mirror of the last-emitted state: it follows the same stability rules\nas a listener's table `oldValue` (a stable snapshot only for the duration of\nthe synchronous call -- copy it if you need to retain it). Scalar results are\nalways safe to keep.\n:::\n\n\n```lua\nlocal manager = TableManager.new({ Score = 0 }, { FlushMode = \"coalesced\" })\nmanager:OnValueChange(\"Score\", function() end) -- observe so a baseline is kept\n\nmanager:Set(\"Score\", 10)\nmanager:Get(\"Score\")            -- 10 (live)\nmanager:GetLastEmitted(\"Score\") -- 0  (listeners have not been told yet)\n\nmanager:Flush(\"Score\")\nmanager:GetLastEmitted(\"Score\") -- 10 (now emitted)\n```",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "suppressNilPartialPaths",
                    "desc": "Return `nil` instead of erroring when a segment along the path is not a table (only consulted on the live fallback).",
                    "lua_type": "boolean?"
                }
            ],
            "returns": [
                {
                    "desc": "The value last emitted for `path` (or the live value when `path` was never observed).",
                    "lua_type": "any"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 625,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "Set",
            "desc": "Writes `value` at `path`, firing the events for whatever changed.\n\n```lua\nmanager:Set(\"player.health\", 80)          -- dot-string path\nmanager:Set({ \"player\", \"health\" }, 80)   -- array path (equivalent)\nmanager:Set(\"player.buff\", nil)           -- removing a key\n```\n\nAn empty path (`Set({}, newTable)` / `Set(\"\", newTable)`) replaces the entire\nroot table: its identity is swapped to `newTable`, stale proxies of the old\ntree are pruned, and root/child change listeners fire for the diff. The new\nroot must be a table (the root cannot become a scalar or `nil`), and the root\ncannot be replaced while a batch is open.\n\nThe path may contain wildcard (`\"*\"`) segments -- the same grammar the\nlistener methods accept. Each `\"*\"` matches every key present at that level\nat call time (multiple wildcards compose as the product of their branch\nfactors), and the write is applied once per matched concrete path, batched\ninto one coherent flush when more than one path matches:\n\n```lua\nmanager:Set(\"Players.*.Health\", 100)   -- every player's Health (created where missing)\nmanager:Set(\"Players.*.Stats.*\", 0)    -- every stat of every player\nmanager:Set(\"Players.*\", nil)          -- mass delete: removes every player\n```\n\nWildcard branches where the remaining path cannot resolve are skipped, and\nzero matches is a no-op. Writing `nil` only targets existing values; a\nnon-nil write also creates a missing literal final key on each matched\nparent. `buildTablesDynamically` cannot be combined with a wildcard path.\nNote that a table value fanned to multiple paths is subject to\n[TableManagerConfig.DuplicateReferenceMode]: under `\"allow\"` (the default)\nevery matched path shares the SAME table identity; use `\"copy\"` for\nindependent clones.",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "value",
                    "desc": "The value to write. May be wrapped with [TableManager.Opaque] or similar.",
                    "lua_type": "any"
                },
                {
                    "name": "buildTablesDynamically",
                    "desc": "When `true`, missing intermediate tables along `path` are created rather than erroring. Not allowed with wildcard paths.",
                    "lua_type": "boolean?"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 684,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "Update",
            "desc": "Reads the value at `path`, passes it through `updater`, and writes the result\nback. Equivalent to a [TableManager:Get] followed by [TableManager:Set], so it\nfires the same change events as a plain `Set`.\n\nThe path may contain wildcard (`\"*\"`) segments: `updater` then runs once per\nmatched concrete path, the writes are batched into one coherent flush, and\nthe method returns `nil`. On a wildcard path each invocation receives, after\nthe current value, the keys matched by each `\"*\"` (left-to-right, one per\nwildcard) and the fully concrete path -- so an `updater` can tell which match\nit is handling:\n\n```lua\nmanager:Update(\"Players.*.Stats.*\", function(value, matches, path)\n\t-- matches[1] = playerId, matches[2] = statName\n\t-- path       = { \"Players\", playerId, \"Stats\", statName }\n\treturn value + 1\nend)\n```\n\n`matches`/`path` are `nil` for a non-wildcard `Update`. Only existing values\nare visited -- use [TableManager:Set] to create keys through a wildcard. See\n[TableManager:GetMatching] for the matching rules.\n\n\n```lua\nmanager:Update(\"coins\", function(current)\n\treturn current * 2\nend)\n```",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "updater",
                    "desc": "Receives the current value (and, on wildcard paths, the matched keys and concrete path); returns the new value.",
                    "lua_type": "(oldValue: any, wildcardMatches: { any }?, path: { any }?) -> any"
                }
            ],
            "returns": [
                {
                    "desc": "The new value that was written (`nil` for wildcard paths).",
                    "lua_type": "any"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 734,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "Increment",
            "desc": "Adds `delta` to the number at `path` and writes the result back. Shorthand for\nan [TableManager:Update] that returns `oldValue + delta`; errors if the current\nvalue is not addable.\n\nThe path may contain wildcard (`\"*\"`) segments: `delta` is then added to every\nmatched existing value (batched into one coherent flush) and the method\nreturns `nil`. See [TableManager:Update] and [TableManager:GetMatching].\n\n\n```lua\nmanager:Increment(\"coins\", 50)  -- coins += 50\nmanager:Increment(\"Players.*.Health\", 5) -- everyone heals 5\n```",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "delta",
                    "desc": "The amount to add (any value supporting `+` with the current value).",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "The new value that was written (`nil` for wildcard paths).",
                    "lua_type": "any"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 763,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "ArrayInsert",
            "desc": "Inserts a value into the array at `pathOrProxy`. Two call shapes:\n\n- `ArrayInsert(path, value)` -- appends `value` to the end.\n- `ArrayInsert(path, index, value)` -- inserts `value` at `index`, shifting\n  the existing elements at and after `index` one slot to the right.\n\nFires `ArrayInserted` (and ancestor change events) for the affected index.\n`pathOrProxy` may be a path or a [Proxy] of the array.\n\n*Aliased as `Insert`.*\n\n\n```lua\nmanager:ArrayInsert(\"items\", \"sword\")   -- append\nmanager:ArrayInsert(\"items\", 1, \"shield\") -- insert at index 1\n```",
            "params": [
                {
                    "name": "pathOrProxy",
                    "desc": "",
                    "lua_type": "Path | Proxy"
                },
                {
                    "name": "...",
                    "desc": "Either `value`, or `index, value`.",
                    "lua_type": "any"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 792,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "ArrayRemove",
            "desc": "Removes the element at `index` from the array at `pathOrProxy`, shifting the\nfollowing elements one slot to the left. Fires `ArrayRemoved` (and ancestor\nchange events) at the remove path.\n\n*Aliased as `Remove`.*",
            "params": [
                {
                    "name": "pathOrProxy",
                    "desc": "",
                    "lua_type": "Path | Proxy"
                },
                {
                    "name": "index",
                    "desc": "1-based index of the element to remove.",
                    "lua_type": "number"
                }
            ],
            "returns": [
                {
                    "desc": "The removed element.",
                    "lua_type": "any"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 854,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "ArrayRemoveFirstValue",
            "desc": "Finds the first element equal to `valueToFind` and removes it via\n[TableManager:ArrayRemove].\n\n*Aliased as `RemoveFirstValue`.*",
            "params": [
                {
                    "name": "pathOrProxy",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "valueToFind",
                    "desc": "The value to search for (compared by equality).",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "The index it was removed from, or `nil` if not found.",
                    "lua_type": "number?"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 896,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "ArraySwapRemove",
            "desc": "Removes the element at `index` in O(1) by moving the last element into its\nslot, instead of shifting every following element. **Order is not preserved.**\n\nBecause the last element backfills the hole, this emits two events rather than\none: an `ArraySet` at `index` (the backfill) followed by an `ArrayRemoved` at\nthe old last index (the shrink). When `index` is already the last element, only\nthe `ArrayRemoved` fires.\n\n*Aliased as `SwapRemove`.*",
            "params": [
                {
                    "name": "pathOrProxy",
                    "desc": "",
                    "lua_type": "Path | Proxy"
                },
                {
                    "name": "index",
                    "desc": "1-based index of the element to remove.",
                    "lua_type": "number"
                }
            ],
            "returns": [
                {
                    "desc": "The removed element, or `nil` if `index` is out of range.",
                    "lua_type": "any"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 927,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "ArraySwapRemoveFirstValue",
            "desc": "Finds the first element equal to `valueToFind` and removes it via\n[TableManager:ArraySwapRemove] (O(1), order not preserved).\n\n*Aliased as `SwapRemoveFirstValue`.*",
            "params": [
                {
                    "name": "pathOrProxy",
                    "desc": "",
                    "lua_type": "Path | Proxy"
                },
                {
                    "name": "valueToFind",
                    "desc": "The value to search for (compared by equality).",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "The index it was removed from, or `nil` if not found.",
                    "lua_type": "number?"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 993,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "UpdateKey",
            "desc": "Like [TableManager:Update], but for the child `key` under `path` -- it updates\nthe value at `path` extended by `key`. Handy for a single dictionary entry or\narray index without building the combined path yourself.\n\n*Aliased as `ArrayUpdate`.*\n\n\n```lua\nmanager:UpdateKey(\"inventory\", \"gold\", function(n) return n + 10 end)\n```",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "key",
                    "desc": "The child key/index under `path` to update.",
                    "lua_type": "any"
                },
                {
                    "name": "updater",
                    "desc": "Receives the current value; returns the new value.",
                    "lua_type": "(oldValue: any) -> any"
                }
            ],
            "returns": [
                {
                    "desc": "The new value that was written.",
                    "lua_type": "any"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 1029,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "IncrementKey",
            "desc": "Like [TableManager:Increment], but for the child `key` under `path` -- it adds\n`delta` to the value at `path` extended by `key`.\n\n*Aliased as `ArrayIncrement`.*",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "key",
                    "desc": "The child key/index under `path` to increment.",
                    "lua_type": "any"
                },
                {
                    "name": "delta",
                    "desc": "The amount to add.",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "The new value that was written.",
                    "lua_type": "any"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 1053,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "Batch",
            "desc": "Holds off firing signals for the duration of the callback, then fires needed signals at the end.\nUseful for batch operations where you want to suppress intermediate signals and only fire final results.\n\nNested calls are no-ops: the outermost Batch window covers everything.\n\n:::caution yielding\nYielding within a batch window will leave the TableManager in a suspended state, which\ncan cause unexpected behavior.\n:::",
            "params": [
                {
                    "name": "fn",
                    "desc": "",
                    "lua_type": "() -> ()"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 1078,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "Suspend",
            "desc": "Suspends all signal and listener firing.\n\nIt is recommended to use `:Batch` for better ergonomics and safety,\nbut `Suspend`/`Resume` can be used for more manual control if needed.\n\nPair with `Resume()`. Nested calls are no-ops (the outermost window wins).",
            "params": [],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 1103,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "Resume",
            "desc": "Resumes after `Suspend()` and flushes all pending changes.\n\nFlush is a single array-aware pass: per dirty branch, `CheckForChangesBetween`\ndiffs the pre-batch value against the current value, collecting array-like\nnodes out of the dict diff; those are then re-diffed via LCS\n(`ArrayDiff.emitDiff`) into shift-faithful `Array*` events. See `BatchFlush`.",
            "params": [],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 1118,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "_doFlush",
            "desc": "Internal, unconditional diff+fire+reconcile for `path`: diffs the\npersistent shadow's last-flushed value against the current live value,\nfires the resulting events via `CheckForChangesBetween`, then reconciles\nthe shadow to live. Callers (the immediate write path, `Flush`,\n`_NotifyApplied`, `BatchFlush.Resume`) are responsible for the coverage\ngate and batch-state check; this always runs.\n\n`forceFullDepth` bypasses the depth bound for this flush only -- used by\n`Flush` when the only reason to flush is an `OnApplied` subscriber (no\nlocal listener covers `path`, so there's no observed depth to bound to and\nthe op stream needs to discover the whole changed subtree). It never\naffects the general write path's depth bound.\n\n`precomputedMaxDepth` is the depth bound the caller already resolved for\nthis same write (`Coverage.ResolveWriteObservation`, forwarded through\n`CoalescedFlush.Request` on the immediate path only), with `math.huge`\nstanding in for \"unbounded\" so it stays distinguishable from \"no hint\".\nOmitted by every deferred/explicit caller, which must re-resolve here.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "TM_Internal<T>"
                },
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "PathArray"
                },
                {
                    "name": "forceFullDepth",
                    "desc": "",
                    "lua_type": "boolean?"
                },
                {
                    "name": "precomputedMaxDepth",
                    "desc": "",
                    "lua_type": "number?"
                }
            ],
            "returns": [],
            "function_type": "static",
            "private": true,
            "source": {
                "line": 1143,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "Flush",
            "desc": "Diffs the persistent shadow baseline against the live value at `path`\n(the whole tree when omitted), fires any resulting events, then\nreconciles the shadow to the current live value. A no-op when nothing\nobserves `path` (no registered listener covers it, no global Signal is\nconnected, and no link group is active).\n\nOutside a batch this runs immediately. Inside a `Suspend`/`Batch` window\nit just marks the branch dirty for `Resume`'s flush instead of firing, so\nan explicit `Flush` can never break batch atomicity.\n\nThis is also the supported way to surface a mutation made by code that\nbypassed `Set`/`Proxy` and wrote directly into the underlying table.",
            "params": [
                {
                    "name": "path",
                    "desc": "The subtree to flush; the whole tree when omitted.",
                    "lua_type": "Path?"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 1179,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "MoveTo",
            "desc": "Move an element from one location to another within the same table.\nThis unsets the value at the current path and sets it at the new path, firing appropriate notifications.\nThis is specifically useful for moving tables around without breaking proxy references.",
            "params": [
                {
                    "name": "currentPath",
                    "desc": "",
                    "lua_type": "Path | Proxy"
                },
                {
                    "name": "newPath",
                    "desc": "",
                    "lua_type": "Path | Proxy"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 1209,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "CopyTo",
            "desc": "DeepCopy a value from one location to another within the same table.\nThis sets the value at the new path to be the same as the value at the current path, firing appropriate notifications.\nThis is specifically useful for copying tables around without breaking proxy references.",
            "params": [
                {
                    "name": "currentPath",
                    "desc": "",
                    "lua_type": "Path | Proxy"
                },
                {
                    "name": "newPath",
                    "desc": "",
                    "lua_type": "Path"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 1227,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "Swap",
            "desc": "Swaps the values at paths `a` and `b` within this manager. The swap is applied\nas a single batched pair of writes, and proxy references are preserved across\nthe move. If either write fails the reparenting is rolled back so neither path\nis left in a partial state.\n\nEither argument may be a path or a [Proxy]. Errors if a path is the root or if\none path is an ancestor/descendant of the other (an overlapping swap is not\nwell-defined).\n\n\n```lua\nmanager:Swap(\"slots.1\", \"slots.2\")\n```",
            "params": [
                {
                    "name": "a",
                    "desc": "",
                    "lua_type": "Path | Proxy"
                },
                {
                    "name": "b",
                    "desc": "",
                    "lua_type": "Path | Proxy"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 1251,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "OnValueChange",
            "desc": "Fires ONLY when this exact path is directly reassigned (not when a\ndescendant of it changes). Shorthand for `OnChange` with\n`ListenDepth = 0, ListenDepthStyle = \"==\"`.",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "callback",
                    "desc": "",
                    "lua_type": "(newValue: any, oldValue: any?, metadata: ChangeMetadata) -> ()"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "ListenerOptions?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Connection"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 1271,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "OnChange",
            "desc": "Fires when this path is directly reassigned OR any descendant of it\nchanges.",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "callback",
                    "desc": "",
                    "lua_type": "(newValue: any, oldValue: any?, metadata: ChangeMetadata) -> ()"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "ListenerOptions?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Connection"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 1294,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "Observe",
            "desc": "Immediately invokes `callback` with the current value at `path`\n(`oldValue` and `metadata` both `nil`), then behaves like\n`OnValueChange` for subsequent changes.",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "callback",
                    "desc": "",
                    "lua_type": "(newValue: any, oldValue: any?, metadata: ChangeMetadata?) -> ()"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "ListenerOptions?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Connection"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 1315,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "OnKeyAdd",
            "desc": "Fires when a new key appears in the table at `path` (a key that did not exist\nbefore is assigned a non-nil value).\n\n\n```lua\nmanager:OnKeyAdd(\"inventory\", function(key, newValue)\n\tprint(`gained {key} = {newValue}`)\nend)\n```",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "callback",
                    "desc": "",
                    "lua_type": "(key: any, newValue: any, metadata: ChangeMetadata) -> ()"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "ListenerOptions?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Connection"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 1351,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "OnKeyRemove",
            "desc": "Fires when a key is removed from the table at `path` (an existing key is set to\n`nil`).\n\n\n```lua\nmanager:OnKeyRemove(\"inventory\", function(key, oldValue)\n\tprint(`lost {key} (was {oldValue})`)\nend)\n```",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "callback",
                    "desc": "",
                    "lua_type": "(key: any, oldValue: any, metadata: ChangeMetadata) -> ()"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "ListenerOptions?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Connection"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 1378,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "OnKeyChange",
            "desc": "Fires when an existing key in the table at `path` is reassigned to a different\nvalue (not added or removed -- see [TableManager:OnKeyAdd] /\n[TableManager:OnKeyRemove] for those).",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "callback",
                    "desc": "",
                    "lua_type": "(key: any, newValue: any, oldValue: any, metadata: ChangeMetadata) -> ()"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "ListenerOptions?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Connection"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 1400,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "OnArrayInsert",
            "desc": "Fires when an element is inserted into the array at `path`, reporting the index\nit landed at. Driven by array-aware diffing, so it reflects true insertions with\nreplay-faithful indices rather than a blanket \"the array changed\".",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "callback",
                    "desc": "",
                    "lua_type": "(index: number, newValue: any, metadata: ChangeMetadata) -> ()"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "ListenerOptions?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Connection"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 1422,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "OnArrayRemove",
            "desc": "Fires when an element is removed from the array at `path`, reporting the index\nit was removed from and its old value.",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "callback",
                    "desc": "",
                    "lua_type": "(index: number, oldValue: any, metadata: ChangeMetadata) -> ()"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "ListenerOptions?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Connection"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 1443,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "OnArraySet",
            "desc": "Fires when an existing array slot at `path` is overwritten in place (its index\nkept, its value replaced), reporting the index, new value, and old value.",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "callback",
                    "desc": "",
                    "lua_type": "(index: number, newValue: any, oldValue: any, metadata: ChangeMetadata) -> ()"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "ListenerOptions?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Connection"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 1464,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "_NotifyApplied",
            "desc": "Re-fires listeners/signals for an op that has ALREADY been applied to the\nshared raw (by another manager observing the same table identity), without\nmutating or snapshot-diffing. `op.Path` is in this manager's own coordinates.\nUsed only by cross-manager fan-out (see `Propagation`).",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "TM_Internal<T>"
                },
                {
                    "name": "op",
                    "desc": "",
                    "lua_type": "AppliedOp"
                }
            ],
            "returns": [],
            "function_type": "static",
            "private": true,
            "source": {
                "line": 1489,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "Extend",
            "desc": "Returns a plain `TableManager` rooted at the shared table found at `target`,\nlinked (via `Link`) to this manager so writes on either side fan out to the\nother. `target` may be:\n- a proxy obtained from this manager (preferred);\n- a raw table value that is already part of this manager's tree; or\n- a path (string or array) into this manager's tree.\n\nThe new TM is bound to the targeted table, not its path in the extending TM.",
            "params": [
                {
                    "name": "target",
                    "desc": "",
                    "lua_type": "Proxy | Path"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "TableManager"
                }
            ],
            "function_type": "method",
            "private": true,
            "source": {
                "line": 1558,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "GetLinkedManagers",
            "desc": "Returns the other managers currently sharing (co-observing) at least one\nlive table identity with this one — de-duplicated, excluding self.",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "{ TableManager }"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 1606,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "IsLinkedWith",
            "desc": "Returns true if this manager currently shares a live table identity with `other`.",
            "params": [
                {
                    "name": "other",
                    "desc": "",
                    "lua_type": "TableManager"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 1618,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "ForKeys",
            "desc": "Reconciles the items found at `path` by KEY: `handler` runs once per key\n(passed a per-item Janitor, the key, and change metadata — `nil` on the\ninitial fire), and is NOT re-run when the value at an existing key\nchanges. The Janitor is destroyed when that key is removed or the\nconnection disconnects.",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "handler",
                    "desc": "",
                    "lua_type": "(itemJanitor: Janitor, key: any, metadata: ChangeMetadata?) -> ()"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "ForOptions?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Connection"
                }
            ],
            "function_type": "method",
            "unreleased": true,
            "source": {
                "line": 1641,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "ForValues",
            "desc": "Reconciles the items found at `path` by VALUE (as a multiset — duplicate\nvalues are tracked as independent items): `handler` runs once per value\noccurrence and is NOT re-run when that value moves between keys/indices\n(e.g. an `ArraySwapRemove`). The Janitor is destroyed when that value\noccurrence is no longer present or the connection disconnects.",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "handler",
                    "desc": "",
                    "lua_type": "(itemJanitor: Janitor, value: any, metadata: ChangeMetadata?) -> ()"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "ForOptions?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Connection"
                }
            ],
            "function_type": "method",
            "unreleased": true,
            "source": {
                "line": 1668,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "ForPairs",
            "desc": "Reconciles the items found at `path` by KEY AND VALUE: `handler` re-runs\n(tearing down the previous item's Janitor first) whenever EITHER the key\nor the value changes — a value change at an existing key is treated as\nthat item leaving and a new one appearing.",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "handler",
                    "desc": "",
                    "lua_type": "(itemJanitor: Janitor, key: any, value: any, metadata: ChangeMetadata?) -> ()"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "ForOptions?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Connection"
                }
            ],
            "function_type": "method",
            "unreleased": true,
            "source": {
                "line": 1692,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "MapKeys",
            "desc": "Returns a live `TableManager` keyed by `transform(entryJanitor, key, value)`,\nwith values passed through unchanged. `transform` re-runs only when the\nsource key is added/removed; a value change at an existing key refreshes\nthe output value WITHOUT re-running `transform`. The output manager owns\nthe source subscription: destroying it disconnects from the source and\ntears down every entry's Janitor. Destroying the SOURCE does not\ncascade-destroy the returned manager (independent lifecycles).",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "transform",
                    "desc": "",
                    "lua_type": "(entryJanitor: Janitor, key: any, value: any) -> any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "TableManager"
                }
            ],
            "function_type": "method",
            "private": true,
            "unreleased": true,
            "source": {
                "line": 1724,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "MapValues",
            "desc": "Returns a live `TableManager` keyed by the SOURCE key, with each entry\nrecomputed via `transform(entryJanitor, value, key)` whenever its value\nchanges. Same ownership/lifecycle rules as `MapKeys`.",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "transform",
                    "desc": "",
                    "lua_type": "(entryJanitor: Janitor, value: any, key: any) -> any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "TableManager"
                }
            ],
            "function_type": "method",
            "private": true,
            "unreleased": true,
            "source": {
                "line": 1754,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "MapPairs",
            "desc": "Returns a live `TableManager` keyed by `transform(entryJanitor, key, value)`'s\nfirst return value (the second is the output value); the entry is\nrecomputed whenever the source key or value changes. On an output-key\ncollision between two source entries, the last write wins. Same\nownership/lifecycle rules as `MapKeys`.",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "transform",
                    "desc": "",
                    "lua_type": "(entryJanitor: Janitor, key: any, value: any) -> (any, any)"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "TableManager"
                }
            ],
            "function_type": "method",
            "private": true,
            "unreleased": true,
            "source": {
                "line": 1796,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "SetPathIgnored",
            "desc": "Marks `path` (and every descendant of it) as ignored or not. An ignored\nwrite still happens — `Get`/`Raw` reflect it immediately — but skips all\ndiff/snapshot/event work, so it never fires listeners/signals and is never\ncloned for change detection. See `TableManagerConfig.IgnoredPaths` for the\nconfig-time equivalent (seeded once at construction).",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "ignored",
                    "desc": "",
                    "lua_type": "boolean"
                }
            ],
            "returns": [],
            "function_type": "method",
            "unreleased": true,
            "source": {
                "line": 1828,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "PromiseValue",
            "desc": "Returns a promise that resolves when the value at `path` satisfies `predicate`.\nIf no predicate is provided, resolves when the value at `path` first changes.",
            "params": [
                {
                    "name": "path",
                    "desc": "",
                    "lua_type": "Path"
                },
                {
                    "name": "predicate",
                    "desc": "",
                    "lua_type": "((value: any?) -> boolean)?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Promise"
                }
            ],
            "function_type": "method",
            "unreleased": true,
            "source": {
                "line": 1843,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "OnApplied",
            "desc": "Only intended for power users.\n\nSubscribes to the pre-diff replication op stream: one `AppliedOp` per\nfinalized change (a minimal per-leaf delta when a diff already ran for\nsome other reason, otherwise the raw new value), with `BatchBegin`/\n`BatchEnd` markers framing a coalesced `Suspend`/`Resume` window.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "TM_Internal<T>"
                },
                {
                    "name": "callback",
                    "desc": "",
                    "lua_type": "(op: AppliedOp) -> ()"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "() -> ()\n"
                }
            ],
            "function_type": "static",
            "private": true,
            "source": {
                "line": 1887,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "OnDestroy",
            "desc": "Runs a callback when the TableManager is destroyed.\nUseful for cleaning up resources that are tied to the lifetime of the TableManager.",
            "params": [
                {
                    "name": "callback",
                    "desc": "",
                    "lua_type": "() -> ()"
                }
            ],
            "returns": [
                {
                    "desc": "Call to unsubscribe.",
                    "lua_type": "() -> ()"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 1901,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "Destroy",
            "desc": "Destroy the TableManager and clean up all resources.",
            "params": [],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 1912,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "IsDestroyed",
            "desc": "Returns `true` once [TableManager:Destroy] has run on `manager`, `false`\notherwise. Pairs with [TableManager:OnDestroy] for teardown-time checks.\n\n```lua\nTableManager.IsDestroyed(manager) -- safe before AND after manager:Destroy()\n```",
            "params": [
                {
                    "name": "manager",
                    "desc": "",
                    "lua_type": "TableManager"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "tags": [
                "Static"
            ],
            "source": {
                "line": 1976,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "Opaque",
            "desc": "Wraps `value` so that, when written, this manager treats it as opaque: it is\nnever cloned, frozen, or walked by the diff engine -- only identity-compared.\nThe wrapper is unwrapped at write time, so the stored value is the bare inner\nvalue. Useful for large immutable blobs or foreign objects you don't want the\ndiff engine to traverse. See the Opaque Values guide.",
            "params": [
                {
                    "name": "value",
                    "desc": "",
                    "lua_type": "T"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "OpaqueWrapper<T>"
                }
            ],
            "function_type": "static",
            "tags": [
                "Static"
            ],
            "source": {
                "line": 2000,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "GlobalOpaque",
            "desc": "Like [TableManager.Opaque], but registers `value` as opaque in a registry\nshared by every TableManager rather than just this one.",
            "params": [
                {
                    "name": "value",
                    "desc": "",
                    "lua_type": "T"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "OpaqueWrapper<T>"
                }
            ],
            "function_type": "static",
            "tags": [
                "Static"
            ],
            "source": {
                "line": 2011,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "OpaqueChildren",
            "desc": "Wraps a container so that its **direct children** are treated as opaque (each\nchild is identity-compared, never cloned/frozen/walked), while the container\nitself is still diffed normally.",
            "params": [
                {
                    "name": "value",
                    "desc": "",
                    "lua_type": "T"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "OpaqueWrapper<T>"
                }
            ],
            "function_type": "static",
            "tags": [
                "Static"
            ],
            "source": {
                "line": 2023,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "GlobalOpaqueChildren",
            "desc": "Like [TableManager.OpaqueChildren], but registers the children as opaque in a\nregistry shared by every TableManager rather than just this one.",
            "params": [
                {
                    "name": "value",
                    "desc": "",
                    "lua_type": "T"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "OpaqueWrapper<T>"
                }
            ],
            "function_type": "static",
            "tags": [
                "Static"
            ],
            "source": {
                "line": 2034,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        }
    ],
    "properties": [
        {
            "name": "TableManager.DefaultListenerFireMode",
            "desc": "The [ListenerFireMode] used when `TableManagerConfig.ListenerFireMode` is\nomitted: it controls how listener callbacks (`OnChange`, `OnValueChange`,\n`Observe`, `OnKey*`, `OnArray*`, `For*`) are scheduled when they fire.\nDefaults to `\"bindable\"`.\n\nRead at construction time only -- changing it affects only TableManagers\ncreated afterwards. Read-only -- use [TableManager.SetDefaults] to change it.",
            "lua_type": "ListenerFireMode",
            "tags": [
                "Static"
            ],
            "readonly": true,
            "source": {
                "line": 193,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "TableManager.DefaultSignalFireMode",
            "desc": "The [SignalFireMode] used when `TableManagerConfig.SignalFireMode` is omitted:\nit controls how the public per-change Signals (`ValueChanged`, `Changed`,\n`ArrayInserted`, etc.) are scheduled when they fire. Defaults to `\"bindable\"`.\n\nRead at construction time only -- changing it affects only TableManagers\ncreated afterwards. Read-only -- use [TableManager.SetDefaults] to change it.",
            "lua_type": "SignalFireMode",
            "tags": [
                "Static"
            ],
            "readonly": true,
            "source": {
                "line": 207,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "TableManager.DefaultFlushMode",
            "desc": "The [FlushMode] used when `TableManagerConfig.FlushMode` is omitted: it\ncontrols whether pending changes are flushed immediately or coalesced to the\nend of the frame. Defaults to `\"immediate\"`.\n\nRead at construction time only -- changing it affects only TableManagers\ncreated afterwards. Read-only -- use [TableManager.SetDefaults] to change it.",
            "lua_type": "FlushMode",
            "tags": [
                "Static"
            ],
            "readonly": true,
            "source": {
                "line": 221,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "TableManager.DefaultDuplicateReferenceMode",
            "desc": "The [DuplicateReferenceMode] used when\n`TableManagerConfig.DuplicateReferenceMode` is omitted: it controls whether\nwriting the same table to more than one path shares its identity (`\"allow\"`)\nor stores an independent copy (`\"copy\"`). Defaults to `\"allow\"`.\n\nRead at construction time only -- changing it affects only TableManagers\ncreated afterwards. Read-only -- use [TableManager.SetDefaults] to change it.",
            "lua_type": "DuplicateReferenceMode",
            "tags": [
                "Static"
            ],
            "readonly": true,
            "source": {
                "line": 236,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        }
    ],
    "types": [
        {
            "name": "ChangeMetadata",
            "desc": "`OriginPath` is the assignment origin for both leaf and ancestor callbacks.",
            "fields": [
                {
                    "name": "Diff",
                    "lua_type": "Diff.DiffNode",
                    "desc": "Diff node for this callback level; nil for ancestor notifications."
                },
                {
                    "name": "OriginPath",
                    "lua_type": "Path",
                    "desc": "The path where the assignment operation occurred (captured path)"
                },
                {
                    "name": "OriginDiff",
                    "lua_type": "Diff.DiffNode",
                    "desc": "Root diff node for the assignment operation."
                },
                {
                    "name": "Snapshot",
                    "lua_type": "AncestorSnapshot",
                    "desc": "Carries RootTable for ancestor value navigation."
                }
            ],
            "source": {
                "line": 155,
                "path": "lib/tablemanager/src/Diffing/ChangeDetector.luau"
            }
        },
        {
            "name": "ConfigDefaults",
            "desc": "The subset of [TableManagerConfig] whose defaults can be overridden at the\nclass level via [TableManager.SetDefaults]. Every field is optional; only the\nfields you provide are changed.",
            "fields": [
                {
                    "name": "ListenerFireMode",
                    "lua_type": "ListenerFireMode?",
                    "desc": ""
                },
                {
                    "name": "SignalFireMode",
                    "lua_type": "SignalFireMode?",
                    "desc": ""
                },
                {
                    "name": "FlushMode",
                    "lua_type": "FlushMode?",
                    "desc": ""
                },
                {
                    "name": "DuplicateReferenceMode",
                    "lua_type": "DuplicateReferenceMode?",
                    "desc": ""
                }
            ],
            "source": {
                "line": 173,
                "path": "lib/tablemanager/src/TableManager.luau"
            }
        },
        {
            "name": "DuplicateReferenceMode",
            "desc": "Controls what happens when the same table is written to more than one path.\n\n- `\"allow\"` (default): the table becomes a SUPPORTED multi-location reference,\n  not an error -- both paths share identity, and the write fires its own\n  independent change events at the new path. The proxy graph still reports a\n  single \"primary\" anchor (whichever path established the value's proxy first).\n- `\"copy\"`: opt OUT of sharing for this write by deep-cloning the value first,\n  so the new path gets its own independent identity instead.",
            "lua_type": "\"allow\" | \"copy\"",
            "source": {
                "line": 63,
                "path": "lib/tablemanager/src/TMTypes.luau"
            }
        },
        {
            "name": "ListenerFireMode",
            "desc": "How listener callbacks (`OnChange`, `OnValueChange`, `Observe`, `OnKey*`,\n`OnArray*`, `For*`) are scheduled when they fire.\n\n- `\"immediate\"`: run now via the free-thread pool, like a signal in immediate mode.\n- `\"deferred\"`: run via `task.defer`, like a signal in deferred mode.\n- `\"bindable\"`: mirror the engine's actual `Enum.SignalBehavior`, resolved once at construction time.\n- `\"coalesced\"`: like `\"deferred\"`, but repeated fires of the SAME listener before the deferred flush collapse into one call carrying the latest event data.",
            "lua_type": "\"immediate\" | \"deferred\" | \"bindable\" | \"coalesced\"",
            "source": {
                "line": 76,
                "path": "lib/tablemanager/src/TMTypes.luau"
            }
        },
        {
            "name": "SignalFireMode",
            "desc": "How the per-change Signals (`ValueChanged`, `KeyAdded`, etc.) are dispatched.\nResolved and driven by the manager's `FireScheduler`:\n`\"immediate\"`/`\"deferred\"` map onto `Signal:Fire`/`:FireDeferred`; `\"bindable\"`\nresolves once (at construction) to whichever of those the environment does\nnatively; `\"coalesced\"` collapses repeated fires of the SAME signal within a\nframe into one carrying the latest values.",
            "lua_type": "\"immediate\" | \"deferred\" | \"bindable\" | \"coalesced\"",
            "source": {
                "line": 88,
                "path": "lib/tablemanager/src/TMTypes.luau"
            }
        },
        {
            "name": "FlushMode",
            "desc": "Controls WHEN the diff-and-fire cycle runs for a direct write.\n\n- `\"immediate\"` (default): runs synchronously with the write.\n- `\"coalesced\"`: defers it to frame-end and merges every flush request made\n  before then into one flush at their common ancestor path, so a frame with N\n  writes under one subtree costs one diff/fire instead of N.",
            "lua_type": "\"immediate\" | \"coalesced\"",
            "source": {
                "line": 100,
                "path": "lib/tablemanager/src/TMTypes.luau"
            }
        },
        {
            "name": "TableManagerConfig",
            "desc": ":::note Implicit sharing\nLinking is automatic: any table reachable (transparently) in two or more\nmanagers' trees is observed by all of them, and a write through one propagates\nto the others. Mark a region `Opaque`/`OpaqueChildren` to opt it out.\n:::",
            "fields": [
                {
                    "name": "Schema",
                    "lua_type": "SchemaCheck?",
                    "desc": ""
                },
                {
                    "name": "OnValidationFailed",
                    "lua_type": "(path: PathArray, value: any, err: string) -> ()?",
                    "desc": ""
                },
                {
                    "name": "ListenerFireMode",
                    "lua_type": "ListenerFireMode?",
                    "desc": "Controls how listener callbacks (OnChange, OnValueChange, etc.) are scheduled when fired."
                },
                {
                    "name": "SignalFireMode",
                    "lua_type": "SignalFireMode?",
                    "desc": "Controls how the per-change Signals (ValueChanged, KeyAdded, etc.) are scheduled when fired."
                },
                {
                    "name": "FlushMode",
                    "lua_type": "FlushMode?",
                    "desc": "Controls WHEN a direct write's diff+fire+reconcile cycle runs."
                },
                {
                    "name": "DuplicateReferenceMode",
                    "lua_type": "DuplicateReferenceMode?",
                    "desc": "Defaults to \"allow\" (multi-location references are supported)."
                },
                {
                    "name": "EnableProxies",
                    "lua_type": "boolean?",
                    "desc": "Defaults to true. When false, `Proxy`/`GetProxy` are unavailable."
                },
                {
                    "name": "IgnoredPaths",
                    "lua_type": "{ Path }?",
                    "desc": "Paths (and their descendants) that skip diff/snapshot/event work entirely."
                },
                {
                    "name": "FrozenTablesAreOpaque",
                    "lua_type": "boolean?",
                    "desc": "Defaults to false. When true, a SHALLOWLY frozen table is also treated as opaque (no clone/walk, identity-compared only), trusting the freeze as an immutability assertion the same way `Opaque` is opt-in trust. A DEEPLY frozen table (every table-typed descendant also frozen) is always treated as opaque, regardless of this flag — that case is provably safe and needs no opt-in."
                }
            ],
            "source": {
                "line": 159,
                "path": "lib/tablemanager/src/TMTypes.luau"
            }
        },
        {
            "name": "ForOptions",
            "desc": "",
            "fields": [
                {
                    "name": "FireForExisting",
                    "lua_type": "boolean?",
                    "desc": "Defaults to true: run the handler/transform for items already present at subscribe time."
                },
                {
                    "name": "Defer",
                    "lua_type": "boolean?",
                    "desc": "Defer the initial fire (honors the registry's deferred-fire mode either way)."
                }
            ],
            "source": {
                "line": 177,
                "path": "lib/tablemanager/src/TMTypes.luau"
            }
        }
    ],
    "name": "TableManager",
    "desc": "TableManager is a wrapper around luau tables that provide easy and automatic \nchange tracking, validation, and listener management. TableManager is designed\nto handle the bulk of your volatile data management needs, emitting detailed change \nevents and snapshots for any changes made to the managed table or its descendants — all \nwithout needing to manually fire events or manage listener connections.\n\n### What is TableManager good for?\n- Tracking changes to nested tables and arrays.\n- Emitting detailed change events for any modifications.\n- Providing snapshots of the current state for debugging or synchronization.\n- Integrating with ProfileStore for easy management of player data.\n\n### What TableManager is Not\n- TableManager is not a state management library, and does not include any opinionated\nfeatures for structuring your data, managing side effects, or integrating with\nother systems. It is purely a change tracking and notification system for tables.\n- TableManager is not intended to be used with tables that are mutated by external code \nwithout going through TableManager's API.\n- TableManager is not meant for data with a high frequency of updates. It focuses on providing\ndetailed and accurate change information, which can be expensive to generate for large or \nrapidly changing data.\n\n\n### Usage\n\n```lua\nlocal manager = TableManager.new({\n\tPlayer = { Name = \"Alice\", Health = 100 },\n\tInventory = { \"Sword\", \"Shield\" },\n})\n\n-- Fire when a specific field changes.\nmanager:OnValueChange(\"Player.Health\", function(health, oldHealth)\n\tprint(`health: {oldHealth} -> {health}`)\nend)\n\n-- Fire for any change under a subtree (the field OR a descendant).\nmanager:OnChange(\"Player\", function(_, _, metadata)\n\tprint(\"player changed at:\", table.concat(metadata.OriginPath, \".\"))\nend)\n\nmanager:Set(\"Player.Health\", 80)\n-- prints \"health: 100 -> 80\"\n--        \"player changed at: Player.Health\"\n\n-- Array contents have their own methods and events.\nmanager:OnArrayInsert(\"Inventory\", function(index, item)\n\tprint(`picked up {item} (slot {index})`)\nend)\nmanager:ArrayInsert(\"Inventory\", \"Potion\") -- prints \"picked up Potion (slot 3)\"\n```\n\n## Good Practices\n- If you have very large datastructures, consider utilizing Opaque \n wrappers to avoid deep cloning and unnecessary change detection.\n- Avoid mixed tables. This is general good practice.\n- Utilize pure string keys so the linter can try to infer your types.",
    "source": {
        "line": 63,
        "path": "lib/tablemanager/src/TableManager.luau"
    }
}