Skip to main content

Binder

Binds a constructor to Roblox instances for their whole lifetime. A Binder watches the game (or a set of ancestors) for instances that match its filters, calls your constructor for each one with a dedicated Trove, and cleans that Trove up again when the instance is removed, loses its tag, or is unbound manually.

Filters are combined: an instance has to carry one of the Tags, be one of the ClassNames, sit under one of the Ancestors and pass the Predicate. Any filter you leave out is skipped. Values returned by the predicate (after the boolean) are forwarded to your constructor after the Trove, which is how the Predicates factories hand you the attribute, child or player they found.

local Binder = require(path.to.Binder)

local Door = {}
Door.__index = Door

function Door.new(instance: Model, trove, openTime: number)
	local self = setmetatable({ Instance = instance, OpenTime = openTime }, Door)

	trove:Connect(instance.PrimaryPart.Touched, function()
		self:Open()
	end)

	return self
end

function Door:Open()
	print(`Opening {self.Instance.Name} for {self.OpenTime}s`)
end

function Door:Destroy()
	-- called automatically when the door is unbound
end

local doors = Binder.new(Door, {
	Tags = { "Door" },
	ClassNames = { "Model" },
	Ancestors = { workspace },
	Predicate = Binder.Predicates.Attribute("OpenTime", "number", "DoorBinder"),
	AutoStart = true,
})

doors.InstanceBound:Connect(function(instance, trove, door)
	print("Bound", instance:GetFullName())
end)

The module also exposes the whole Predicates module through its metatable, so Binder.Predicates.Attribute(...), Binder.doPredicateWarning and Binder.T are the same values you would get from requiring Predicates directly.

Credits: Trove and Signal are by sleitnick (sleitnick's RbxUtil); the predicate filters come from my own Predicates package. Wally installs all three.

Installation and guide: Binder package page.

Types​

Constructable​

type Constructable = {new: (
instance: any,
trove: Trove,
...any
) → A} | (
instance: any,
trove: Trove,
...any
) → A

What a Binder constructs for each bound instance. Either a table with a .new function (a typical class module) or a plain function. It is called with the instance, a Trove that is cleaned up when the instance is unbound, and any extra values the predicate returned. It must return a non-nil value; if the value has a Destroy method it is added to the Trove so it is destroyed automatically on unbind. If you have nothing to return, return the Trove.

Predicate​

type Predicate = (any) → (
boolean,
...any
)

Re-export of Predicates.Predicate. Receives the candidate instance and returns whether it may be bound, followed by any extra values to pass to the constructor.

BinderConfig​

interface BinderConfig {
Tags: {string}?--

CollectionService tags. An instance needs at least one of them to bind, and Bind adds all of them to the instance. Also drives automatic binding: tagging/untagging an instance binds/unbinds it.

ClassNames: {string}?--

Class names the instance must match (checked with IsA, so superclasses such as "BasePart" work).

Ancestors: {Instance}?--

Only descendants of one of these instances may bind, and only these are watched for DescendantAdded/DescendantRemoving. When omitted the whole game is watched and the instance must be a descendant of game.

Predicate: Predicate?--

Final check run on every candidate. Must return true or false; extra return values are passed to the constructor after the Trove. Errors inside it are reported and count as false.

Priority: number?--

When set, automatic binds/unbinds are queued and flushed on the next Heartbeat, ordered so that lower numbers run first across all Binders. When omitted they run immediately.

AutoStart: boolean?--

Call Start for you right after construction (in a new thread). Cannot be combined with ManualBindingOnly.

ManualBindingOnly: boolean?--

Disable automatic binding entirely: Start errors, Bind skips the tag/class/ancestor checks (the predicate still runs) and errors instead of returning nil when the predicate fails.

RemoveTagsOnCleanup: boolean?--

Remove every Tags entry from the instance when it is unbound. Defaults to false.

}

Configuration table accepted by Binder.new. Every field is optional; table fields are frozen once passed in and a field with the wrong type raises an error.

Properties​

Trove​

This item is read only and cannot be modified. Read Only
Binder.Trove: Trove

The Binder's root Trove. Every per-instance Trove is an extension of it, so cleaning it (which is what Binder:Destroy does) unbinds every instance and disconnects the signals. You may add your own cleanup tasks to it.

InstanceBound​

This item is read only and cannot be modified. Read Only
Binder.InstanceBound: Signal<Instance,Trove,A>

Fires after an instance has been bound, whether automatically or through Binder:Bind. Receives the instance, the Trove created for it and the value your constructor returned.

InstanceUnbound​

This item is read only and cannot be modified. Read Only
Binder.InstanceUnbound: Signal<Instance>

Fires after an instance has been unbound and its Trove cleaned, whether because it was removed, lost its tag, Binder:Unbind was called or the Binder was destroyed. Receives the instance.

Functions​

new​

Binder.new(
constructable: Constructable<A>,--

Class table with a .new function, or a plain function, called for each bound instance.

config: BinderConfig?--

Filters and behaviour flags; see BinderConfig.

) → Binder<A>--

The new Binder.

Creates a Binder that constructs constructable for every instance matching config.

What happens next depends on the config:

  • No config, or ManualBindingOnly = true: nothing is watched. Use Binder:Bind and Binder:Unbind yourself.
  • AutoStart = true: Binder:Start is called for you in a new thread, so existing instances are bound shortly after this returns.
  • Otherwise the filters are stored but nothing happens until you call Binder:Start.

Errors

TypeDescription
"Invalid config"`config` is not a table.
"Invalid type passed, expected <type>"A config field has the wrong type.
"Binder cannot have both AutoStart and ManualBindingOnly set to true"Both flags were set.

CanBind​

Binder:CanBind(
instance: Instance--

The instance to test.

) → (
boolean,--

Whether the instance passes every configured filter.

{any}?--

Packed extra values from the predicate, or nil when there is no predicate.

)

Runs the filters against instance without binding it. The checks are, in order: Tags (any one), ClassNames (any one, via IsA), Ancestors (any one, or "is a descendant of game" when no ancestors are configured) and finally Predicate. When the Binder is ManualBindingOnly the first three checks are skipped and only the predicate runs.

If a predicate is configured and passes, the second return value is a packed table of the extra values the predicate returned; these are what Binder:Bind unpacks into the constructor. A predicate that errors or does not return a boolean first is reported through error in a separate thread and treated as false.

GetTrove​

Binder:GetTrove(
binding: A--

The object returned by the constructor for some bound instance.

) → Trove?--

The object's Trove, or nil if it is not (or no longer) bound.

Returns the Trove that was created for a bound object. Note that the key is the value your constructor returned, not the instance; use Binder:GetBinding first if you only have the instance. Cleaning this Trove unbinds the object.

GetBinding​

Binder:GetBinding(
instance: Instance--

A possibly bound instance.

) → A?--

The bound object, or nil if the instance is not bound by this Binder.

Returns the object the constructor produced for instance.

GetInstances​

Binder:GetInstances() → {Instance}--

All bound instances.

Returns a new array of every instance currently bound by this Binder. The order is not defined.

Bind​

Binder:Bind(
instance: Instance,--

The instance to bind.

...: any--

Reserved. Currently ignored; the constructor receives the predicate's extra values instead.

) → A?--

The constructed object (or the existing one if already bound), or nil if the filters rejected the instance.

Binds instance right now. This is what automatic binding calls internally, and what you call yourself on a ManualBindingOnly Binder.

Steps, in order:

  1. If the instance is already bound, a warning is printed and the existing object is returned.
  2. Binder:CanBind is run. On failure this returns nil, or errors when the Binder is ManualBindingOnly.
  3. Every configured tag is added to the instance.
  4. A Trove is extended from Binder.Trove and the constructable is called with (instance, trove, ...predicateValues). If the result has a Destroy method it is added to the Trove. Returning nil from the constructable is an error.
  5. Binder.InstanceBound fires with (instance, trove, object).

When the Trove is later cleaned (by Binder:Unbind, removal, or Binder:Destroy) the tags are removed if RemoveTagsOnCleanup is set and Binder.InstanceUnbound fires.

Errors

TypeDescription
"Unable to bind instance"The Binder is `ManualBindingOnly` and the predicate rejected the instance.
"Nothing was returned by the constructable..."The constructable returned nil.
"Add a .new constructor method to your constructor"The constructable is a table without a `new` field.

Unbind​

Binder:Unbind(
instance: Instance--

The instance to unbind.

) → ()

Unbinds instance by cleaning the Trove that was created for it. This destroys the bound object (if it has a Destroy method), disconnects anything you connected through that Trove, removes the tags when RemoveTagsOnCleanup is set and fires Binder.InstanceUnbound. Does nothing if the instance is not bound. The instance itself is not destroyed.

Start​

Binder:Start() → ()

Starts automatic binding. Any previous listeners are stopped first, so calling this twice is safe. Not needed when the Binder was created with AutoStart = true.

Listeners connected:

  • For each Tags entry: CollectionService:GetInstanceAddedSignal binds and GetInstanceRemovedSignal unbinds.
  • For each Ancestors entry: DescendantAdded binds and DescendantRemoving unbinds, and every existing descendant is bound in its own thread.
  • With no Ancestors, every existing descendant of game is tried once (in its own thread); afterwards only tag changes trigger binds, so give the Binder Tags or Ancestors if you want to react to new instances.

Every candidate still has to pass Binder:CanBind, so tag and ancestor listeners can be combined freely.

Priority queue. Without a Priority, binds and unbinds happen as soon as the event fires. With a Priority, each bind/unbind is appended to a queue shared by all Binders in the same VM; on the next Heartbeat the queue is sorted ascending by priority and flushed, so a Binder with priority 1 binds before one with priority 2 even when their instances appear in the same frame. The first Binder that needs the queue owns its Heartbeat connection until that Binder is stopped or destroyed, after which the next queued bind claims it again.

Errors

TypeDescription
"Binder is manual only because ManualBindingOnly is set to true..."The Binder was created with `ManualBindingOnly`.

Stop​

Binder:Stop() → ()

Disconnects the listeners created by Binder:Start so no new instances are bound or unbound automatically. Instances that are already bound stay bound; use Binder:Destroy to clean those up as well. Safe to call on a Binder that was never started.

Destroy​

Binder:Destroy() → ()

Destroys the Binder by cleaning Binder.Trove. This stops automatic binding, unbinds every bound instance (firing Binder.InstanceUnbound for each) and destroys the InstanceBound/InstanceUnbound signals. The Binder should not be used afterwards.

Show raw api
{
    "functions": [
        {
            "name": "new",
            "desc": "Creates a Binder that constructs `constructable` for every instance matching `config`.\n\nWhat happens next depends on the config:\n- No `config`, or `ManualBindingOnly = true`: nothing is watched. Use [Binder:Bind](#Bind) and\n  [Binder:Unbind](#Unbind) yourself.\n- `AutoStart = true`: [Binder:Start](#Start) is called for you in a new thread, so existing\n  instances are bound shortly after this returns.\n- Otherwise the filters are stored but nothing happens until you call [Binder:Start](#Start).",
            "params": [
                {
                    "name": "constructable",
                    "desc": "Class table with a `.new` function, or a plain function, called for each bound instance.",
                    "lua_type": "Constructable<A>"
                },
                {
                    "name": "config",
                    "desc": "Filters and behaviour flags; see [BinderConfig](#BinderConfig).",
                    "lua_type": "BinderConfig?"
                }
            ],
            "returns": [
                {
                    "desc": "The new Binder.",
                    "lua_type": "Binder<A>"
                }
            ],
            "function_type": "static",
            "errors": [
                {
                    "lua_type": "\"Invalid config\"",
                    "desc": "`config` is not a table."
                },
                {
                    "lua_type": "\"Invalid type passed, expected <type>\"",
                    "desc": "A config field has the wrong type."
                },
                {
                    "lua_type": "\"Binder cannot have both AutoStart and ManualBindingOnly set to true\"",
                    "desc": "Both flags were set."
                }
            ],
            "source": {
                "line": 265,
                "path": "packages/src/Binder/init.luau"
            }
        },
        {
            "name": "CanBind",
            "desc": "Runs the filters against `instance` without binding it. The checks are, in order: `Tags`\n(any one), `ClassNames` (any one, via `IsA`), `Ancestors` (any one, or \"is a descendant of\n`game`\" when no ancestors are configured) and finally `Predicate`. When the Binder is\n`ManualBindingOnly` the first three checks are skipped and only the predicate runs.\n\nIf a predicate is configured and passes, the second return value is a packed table of the extra\nvalues the predicate returned; these are what [Binder:Bind](#Bind) unpacks into the constructor.\nA predicate that errors or does not return a boolean first is reported through `error` in a\nseparate thread and treated as `false`.",
            "params": [
                {
                    "name": "instance",
                    "desc": "The instance to test.",
                    "lua_type": "Instance"
                }
            ],
            "returns": [
                {
                    "desc": "Whether the instance passes every configured filter.",
                    "lua_type": "boolean"
                },
                {
                    "desc": "Packed extra values from the predicate, or nil when there is no predicate.",
                    "lua_type": "{ any }?"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 344,
                "path": "packages/src/Binder/init.luau"
            }
        },
        {
            "name": "GetTrove",
            "desc": "Returns the Trove that was created for a bound object. Note that the key is the value your\nconstructor returned, not the instance; use [Binder:GetBinding](#GetBinding) first if you only\nhave the instance. Cleaning this Trove unbinds the object.",
            "params": [
                {
                    "name": "binding",
                    "desc": "The object returned by the constructor for some bound instance.",
                    "lua_type": "A"
                }
            ],
            "returns": [
                {
                    "desc": "The object's Trove, or nil if it is not (or no longer) bound.",
                    "lua_type": "Trove?"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 450,
                "path": "packages/src/Binder/init.luau"
            }
        },
        {
            "name": "GetBinding",
            "desc": "Returns the object the constructor produced for `instance`.",
            "params": [
                {
                    "name": "instance",
                    "desc": "A possibly bound instance.",
                    "lua_type": "Instance"
                }
            ],
            "returns": [
                {
                    "desc": "The bound object, or nil if the instance is not bound by this Binder.",
                    "lua_type": "A?"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 462,
                "path": "packages/src/Binder/init.luau"
            }
        },
        {
            "name": "GetInstances",
            "desc": "Returns a new array of every instance currently bound by this Binder. The order is not\ndefined.",
            "params": [],
            "returns": [
                {
                    "desc": "All bound instances.",
                    "lua_type": "{ Instance }"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 474,
                "path": "packages/src/Binder/init.luau"
            }
        },
        {
            "name": "Bind",
            "desc": "Binds `instance` right now. This is what automatic binding calls internally, and what you call\nyourself on a `ManualBindingOnly` Binder.\n\nSteps, in order:\n1. If the instance is already bound, a warning is printed and the existing object is returned.\n2. [Binder:CanBind](#CanBind) is run. On failure this returns `nil`, or errors when the Binder\n   is `ManualBindingOnly`.\n3. Every configured tag is added to the instance.\n4. A Trove is extended from [Binder.Trove](#Trove) and the constructable is called with\n   `(instance, trove, ...predicateValues)`. If the result has a `Destroy` method it is added to\n   the Trove. Returning `nil` from the constructable is an error.\n5. [Binder.InstanceBound](#InstanceBound) fires with `(instance, trove, object)`.\n\nWhen the Trove is later cleaned (by [Binder:Unbind](#Unbind), removal, or\n[Binder:Destroy](#Destroy)) the tags are removed if `RemoveTagsOnCleanup` is set and\n[Binder.InstanceUnbound](#InstanceUnbound) fires.",
            "params": [
                {
                    "name": "instance",
                    "desc": "The instance to bind.",
                    "lua_type": "Instance"
                },
                {
                    "name": "...",
                    "desc": "Reserved. Currently ignored; the constructor receives the predicate's extra values instead.",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "The constructed object (or the existing one if already bound), or nil if the filters rejected the instance.",
                    "lua_type": "A?"
                }
            ],
            "function_type": "method",
            "errors": [
                {
                    "lua_type": "\"Unable to bind instance\"",
                    "desc": "The Binder is `ManualBindingOnly` and the predicate rejected the instance."
                },
                {
                    "lua_type": "\"Nothing was returned by the constructable...\"",
                    "desc": "The constructable returned nil."
                },
                {
                    "lua_type": "\"Add a .new constructor method to your constructor\"",
                    "desc": "The constructable is a table without a `new` field."
                }
            ],
            "source": {
                "line": 511,
                "path": "packages/src/Binder/init.luau"
            }
        },
        {
            "name": "Unbind",
            "desc": "Unbinds `instance` by cleaning the Trove that was created for it. This destroys the bound object\n(if it has a `Destroy` method), disconnects anything you connected through that Trove, removes\nthe tags when `RemoveTagsOnCleanup` is set and fires [Binder.InstanceUnbound](#InstanceUnbound).\nDoes nothing if the instance is not bound. The instance itself is not destroyed.",
            "params": [
                {
                    "name": "instance",
                    "desc": "The instance to unbind.",
                    "lua_type": "Instance"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 576,
                "path": "packages/src/Binder/init.luau"
            }
        },
        {
            "name": "Start",
            "desc": "Starts automatic binding. Any previous listeners are stopped first, so calling this twice is\nsafe. Not needed when the Binder was created with `AutoStart = true`.\n\nListeners connected:\n- For each `Tags` entry: `CollectionService:GetInstanceAddedSignal` binds and\n  `GetInstanceRemovedSignal` unbinds.\n- For each `Ancestors` entry: `DescendantAdded` binds and `DescendantRemoving` unbinds, and\n  every existing descendant is bound in its own thread.\n- With no `Ancestors`, every existing descendant of `game` is tried once (in its own thread);\n  afterwards only tag changes trigger binds, so give the Binder `Tags` or `Ancestors` if you\n  want to react to new instances.\n\nEvery candidate still has to pass [Binder:CanBind](#CanBind), so tag and ancestor listeners\ncan be combined freely.\n\n**Priority queue.** Without a `Priority`, binds and unbinds happen as soon as the event fires.\nWith a `Priority`, each bind/unbind is appended to a queue shared by all Binders in the same\nVM; on the next `Heartbeat` the queue is sorted ascending by priority and flushed, so a Binder\nwith priority `1` binds before one with priority `2` even when their instances appear in the\nsame frame. The first Binder that needs the queue owns its `Heartbeat` connection until that\nBinder is stopped or destroyed, after which the next queued bind claims it again.",
            "params": [],
            "returns": [],
            "function_type": "method",
            "errors": [
                {
                    "lua_type": "\"Binder is manual only because ManualBindingOnly is set to true...\"",
                    "desc": "The Binder was created with `ManualBindingOnly`."
                }
            ],
            "source": {
                "line": 620,
                "path": "packages/src/Binder/init.luau"
            }
        },
        {
            "name": "Stop",
            "desc": "Disconnects the listeners created by [Binder:Start](#Start) so no new instances are bound or\nunbound automatically. Instances that are already bound stay bound; use [Binder:Destroy](#Destroy)\nto clean those up as well. Safe to call on a Binder that was never started.",
            "params": [],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 723,
                "path": "packages/src/Binder/init.luau"
            }
        },
        {
            "name": "Destroy",
            "desc": "Destroys the Binder by cleaning [Binder.Trove](#Trove). This stops automatic binding, unbinds\nevery bound instance (firing [Binder.InstanceUnbound](#InstanceUnbound) for each) and destroys\nthe `InstanceBound`/`InstanceUnbound` signals. The Binder should not be used afterwards.",
            "params": [],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 740,
                "path": "packages/src/Binder/init.luau"
            }
        }
    ],
    "properties": [
        {
            "name": "Trove",
            "desc": "The Binder's root Trove. Every per-instance Trove is an extension of it, so cleaning it (which\nis what [Binder:Destroy](#Destroy) does) unbinds every instance and disconnects the signals.\nYou may add your own cleanup tasks to it.",
            "lua_type": "Trove",
            "readonly": true,
            "source": {
                "line": 103,
                "path": "packages/src/Binder/init.luau"
            }
        },
        {
            "name": "InstanceBound",
            "desc": "Fires after an instance has been bound, whether automatically or through [Binder:Bind](#Bind).\nReceives the instance, the Trove created for it and the value your constructor returned.",
            "lua_type": "Signal<Instance, Trove, A>",
            "readonly": true,
            "source": {
                "line": 111,
                "path": "packages/src/Binder/init.luau"
            }
        },
        {
            "name": "InstanceUnbound",
            "desc": "Fires after an instance has been unbound and its Trove cleaned, whether because it was removed,\nlost its tag, [Binder:Unbind](#Unbind) was called or the Binder was destroyed. Receives the\ninstance.",
            "lua_type": "Signal<Instance>",
            "readonly": true,
            "source": {
                "line": 120,
                "path": "packages/src/Binder/init.luau"
            }
        }
    ],
    "types": [
        {
            "name": "Constructable",
            "desc": "What a Binder constructs for each bound instance. Either a table with a `.new` function (a\ntypical class module) or a plain function. It is called with the instance, a Trove that is\ncleaned up when the instance is unbound, and any extra values the predicate returned. It must\nreturn a non-nil value; if the value has a `Destroy` method it is added to the Trove so it is\ndestroyed automatically on unbind. If you have nothing to return, return the Trove.",
            "lua_type": "{ new: (instance: any, trove: Trove, ...any) -> A } | (instance: any, trove: Trove, ...any) -> A",
            "source": {
                "line": 86,
                "path": "packages/src/Binder/init.luau"
            }
        },
        {
            "name": "Predicate",
            "desc": "Re-export of [Predicates.Predicate](/api/Predicates#Predicate). Receives the candidate\ninstance and returns whether it may be bound, followed by any extra values to pass to the\nconstructor.",
            "lua_type": "(any) -> (boolean, ...any)",
            "source": {
                "line": 94,
                "path": "packages/src/Binder/init.luau"
            }
        },
        {
            "name": "BinderConfig",
            "desc": "Configuration table accepted by [Binder.new](#new). Every field is optional; table fields are\nfrozen once passed in and a field with the wrong type raises an error.",
            "fields": [
                {
                    "name": "Tags",
                    "lua_type": "{ string }?",
                    "desc": "CollectionService tags. An instance needs at least one of them to bind, and `Bind` adds all of them to the instance. Also drives automatic binding: tagging/untagging an instance binds/unbinds it."
                },
                {
                    "name": "ClassNames",
                    "lua_type": "{ string }?",
                    "desc": "Class names the instance must match (checked with `IsA`, so superclasses such as `\"BasePart\"` work)."
                },
                {
                    "name": "Ancestors",
                    "lua_type": "{ Instance }?",
                    "desc": "Only descendants of one of these instances may bind, and only these are watched for `DescendantAdded`/`DescendantRemoving`. When omitted the whole `game` is watched and the instance must be a descendant of `game`."
                },
                {
                    "name": "Predicate",
                    "lua_type": "Predicate?",
                    "desc": "Final check run on every candidate. Must return `true` or `false`; extra return values are passed to the constructor after the Trove. Errors inside it are reported and count as `false`."
                },
                {
                    "name": "Priority",
                    "lua_type": "number?",
                    "desc": "When set, automatic binds/unbinds are queued and flushed on the next `Heartbeat`, ordered so that lower numbers run first across all Binders. When omitted they run immediately."
                },
                {
                    "name": "AutoStart",
                    "lua_type": "boolean?",
                    "desc": "Call `Start` for you right after construction (in a new thread). Cannot be combined with `ManualBindingOnly`."
                },
                {
                    "name": "ManualBindingOnly",
                    "lua_type": "boolean?",
                    "desc": "Disable automatic binding entirely: `Start` errors, `Bind` skips the tag/class/ancestor checks (the predicate still runs) and errors instead of returning `nil` when the predicate fails."
                },
                {
                    "name": "RemoveTagsOnCleanup",
                    "lua_type": "boolean?",
                    "desc": "Remove every `Tags` entry from the instance when it is unbound. Defaults to `false`."
                }
            ],
            "source": {
                "line": 159,
                "path": "packages/src/Binder/init.luau"
            }
        }
    ],
    "name": "Binder",
    "desc": "Binds a constructor to Roblox instances for their whole lifetime. A Binder watches the game\n(or a set of ancestors) for instances that match its filters, calls your constructor for each\none with a dedicated [Trove](https://sleitnick.github.io/RbxUtil/api/Trove/), and cleans that\nTrove up again when the instance is removed, loses its tag, or is unbound manually.\n\nFilters are combined: an instance has to carry one of the `Tags`, be one of the `ClassNames`,\nsit under one of the `Ancestors` **and** pass the `Predicate`. Any filter you leave out is\nskipped. Values returned by the predicate (after the boolean) are forwarded to your constructor\nafter the Trove, which is how the [Predicates](/api/Predicates) factories hand you the\nattribute, child or player they found.\n\n```lua\nlocal Binder = require(path.to.Binder)\n\nlocal Door = {}\nDoor.__index = Door\n\nfunction Door.new(instance: Model, trove, openTime: number)\n\tlocal self = setmetatable({ Instance = instance, OpenTime = openTime }, Door)\n\n\ttrove:Connect(instance.PrimaryPart.Touched, function()\n\t\tself:Open()\n\tend)\n\n\treturn self\nend\n\nfunction Door:Open()\n\tprint(`Opening {self.Instance.Name} for {self.OpenTime}s`)\nend\n\nfunction Door:Destroy()\n\t-- called automatically when the door is unbound\nend\n\nlocal doors = Binder.new(Door, {\n\tTags = { \"Door\" },\n\tClassNames = { \"Model\" },\n\tAncestors = { workspace },\n\tPredicate = Binder.Predicates.Attribute(\"OpenTime\", \"number\", \"DoorBinder\"),\n\tAutoStart = true,\n})\n\ndoors.InstanceBound:Connect(function(instance, trove, door)\n\tprint(\"Bound\", instance:GetFullName())\nend)\n```\n\nThe module also exposes the whole [Predicates](/api/Predicates) module through its metatable,\nso `Binder.Predicates.Attribute(...)`, `Binder.doPredicateWarning` and `Binder.T` are the same\nvalues you would get from requiring Predicates directly.\n\n**Credits:** [Trove](https://sleitnick.github.io/RbxUtil/api/Trove/) and [Signal](https://sleitnick.github.io/RbxUtil/api/Signal/) are by [sleitnick](https://github.com/Sleitnick) ([sleitnick's RbxUtil](https://sleitnick.github.io/RbxUtil/)); the predicate filters come from my own [Predicates](/api/Predicates) package. Wally installs all three.\n\nInstallation and guide: [Binder package page](/docs/packages/binder).",
    "source": {
        "line": 75,
        "path": "packages/src/Binder/init.luau"
    }
}