{"id":"zac134/better-config","name":"better-config","scope":"zac134","platform":"roblox","description":"Roblox Configuration/Attribute/Dict Wrapper for getting value,setting value, observing changed signal","version":"2.2.0","latest":"2.2.0","versions":["0.1.1","1.0.0","1.0.1","2.0.0","2.1.0","2.1.1","2.1.2","2.2.0"],"license":"MIT","licenseRating":"safe","licenseCaveats":["The package archive does not include its license text; the license is declared in its manifest metadata."],"licenseVerified":false,"dependencies":{"sleitnick/signal":{"version":"^2.0.0","alias":"signal"}},"integrity":"907f31f4781bc88f44b3e8956324d9a0b09fe74f97943009830a856538732780","likes":0,"downloads":0,"install":"forest install zac134/better-config","url":"https://forest.dev/p/roblox/zac134/better-config","files":"https://api.forest.dev/ai/package/roblox/zac134/better-config/files","readme":"# BetterConfig\n\nUnified configuration management for Roblox using the Strategy Pattern.\n\n## Overview\n\nBetterConfig provides a single, consistent API for managing configuration data from multiple sources in Roblox. Whether your config comes from Configuration instances with ValueBase children, Instance Attributes, or plain Lua tables, BetterConfig abstracts the differences and gives you the same intuitive interface with full type safety and reactive change observation.\n\n**Problem**: Different config sources in Roblox have different APIs (Configuration→ValueBase, Instance→Attributes, plain tables), making code inconsistent and harder to maintain.\n\n**Solution**: BetterConfig automatically detects the source type and returns the appropriate implementation, giving you one unified interface with type-safe autocomplete.\n\n## Features\n\n- **Three config source strategies** with automatic detection\n- **Type-safe API** with Luau generics and `keyof<T>` for autocomplete\n- **Reactive value change observation** with Signal support\n- **Zero boilerplate** factory pattern\n- **Resource management** with `Destroy()` method\n\n## Installation\n\n### Via Wally\n\n```toml\n[dependencies]\nBetterConfig = \"zac134/better-config@2.0.0\"\n```\n\n### Manual Installation\n\nCopy the `Modules/BetterConfig/` directory to your project's ReplicatedStorage or ServerStorage.\n\n## Quick Start\n\n```lua\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\nlocal BetterConfig = require(ReplicatedStorage.Modules.BetterConfig)\n\n-- Factory automatically detects the config source type\nlocal config = BetterConfig.new(workspace.GameConfig)\n\n-- Get values\nlocal value = config:Get(\"max_players\")\n\n-- Set values\nconfig:Set(\"max_players\", 10)\n\n-- Observe changes\nconfig:GetValueChangedSignal(\"max_players\"):Connect(function(newValue)\n    print(\"max_players changed to:\", newValue)\nend)\n```\n\n## Configuration Sources\n\n### 1. Instance Attributes\n\nUse Attributes when you need simple key-value config on existing Instances.\n\n```lua\nlocal part = workspace.ConfigPart\npart:SetAttribute(\"max_items\", 10)\npart:SetAttribute(\"show_ui\", true)\n\nlocal config = BetterConfig.fromAttribute(part)\nlocal maxItems = config:Get(\"max_items\")\nconfig:Set(\"show_ui\", false)\n```\n\n**Benefits:**\n- Flexible types supported by Roblox Attributes\n- Easy to inspect and modify in Studio Properties panel\n- Lightweight (no child instances required)\n\n### 2. Configuration/ValueBase\n\nUse Configuration instances when you need specific ValueBase types or want visual Studio editing.\n\n```lua\nlocal configInstance = workspace.GameConfig  -- Configuration instance with ValueBase children\nlocal config = BetterConfig.fromConfiguration(configInstance)\n\nlocal maxPlayers = config:Get(\"max_players\")  -- Reads from IntValue child\nconfig:Set(\"max_players\", 8)  -- Updates IntValue.Value\n```\n\n**Benefits:**\n- Type-specific ValueBase children (IntValue, StringValue, BoolValue, etc.)\n- Visual hierarchy in Studio Explorer\n- Direct access to ValueBase properties\n\n**Supported ValueBase types:** `ObjectValue`, `IntValue`, `BoolValue`, `StringValue`, `NumberValue`, `Color3Value`, `CFrameValue`, `Vector3Value`, `BrickColorValue`, `RayValue`\n\n### 3. Dictionary/Table\n\nUse Dictionary config when you need runtime config generation, testing, or programmatic configs.\n\n```lua\nlocal config = BetterConfig.fromDict({\n    round_time = 120,\n    team_size = 4,\n    map_name = \"Grasslands\",\n})\n\nlocal roundTime = config:Get(\"round_time\")\nconfig:Set(\"round_time\", 180)\n```\n\n**Benefits:**\n- No Instance required, pure Lua\n- Perfect for testing and runtime configuration\n- Automatic type mapping (string→StringValue, number→NumberValue, etc.)\n- Creates internal Configuration instance with appropriate ValueBase children\n\n## Factory vs Explicit Methods\n\n### Auto-Detection (Recommended)\n\n```lua\nlocal config1 = BetterConfig.new(workspace.GameConfig)  -- Configuration detected\nlocal config2 = BetterConfig.new(workspace.Part)         -- Attribute detected\nlocal config3 = BetterConfig.new({key = \"value\"})        -- Dictionary detected\n```\n\nUse `BetterConfig.new()` when you want automatic source detection.\n\n### Explicit Methods\n\n```lua\nlocal config1 = BetterConfig.fromAttribute(instance)      -- Explicit Attribute config\nlocal config2 = BetterConfig.fromConfiguration(config)    -- Explicit Configuration config\nlocal config3 = BetterConfig.fromDict(table)               -- Explicit Dictionary config\n```\n\nUse explicit methods when:\n- You want to be explicit about the config type\n- You need better type inference\n- You're building a library and want clear contracts\n\n## Type-Safe Autocomplete\n\nDefine your config shape as a type for autocomplete support:\n\n```lua\ntype MyConfig = {\n    show_ui: boolean,\n    max_items: number,\n    welcome_message: string,\n}\n\n-- Type hint enables autocomplete for all methods\nlocal config = BetterConfig.fromAttribute(configPart)\n\n-- Get with autocomplete - valid keys suggested automatically\nlocal showUi = config:Get(\"show_ui\")        -- ✓ Autocomplete works\nlocal maxItems = config:Get(\"max_items\")    -- ✓ Autocomplete works\n-- config:Get(\"invalid_key\")                -- ✗ Type error if strict mode enabled\n```\n\nThe type definition enables your IDE/editor to suggest valid keys when calling `Get`, `Set`, and `GetValueChangedSignal`.\n\n## Observing Value Changes\n\n`GetValueChangedSignal` returns a [Signal](https://github.com/sleitnick/rbx-util/tree/main/modules/signal) that fires when a value changes:\n\n```lua\nlocal signal = config:GetValueChangedSignal(\"max_items\")\n\n-- Connect to listen for changes\nsignal:Connect(function(newValue)\n    print(\"max_items changed to:\", newValue)\nend)\n\n-- Fire once and disconnect\nsignal:Once(function(newValue)\n    print(\"First change detected:\", newValue)\nend)\n\n-- Wait for next change\ntask.spawn(function()\n    local newValue = signal:Wait()\n    print(\"Got new value:\", newValue)\nend)\n```\n\n### When Signals Fire\n\n- **AttributeConfig**: Fires when `Instance:SetAttribute()` is called or Attribute changes in Studio\n- **ConfigInstance**: Fires when the ValueBase child's `.Value` property changes\n- **DictionaryConfig**: Fires when `Set()` is called on the config\n\n## Resource Cleanup\n\nAlways call `Destroy()` when you're done with a config to clean up connections and signals:\n\n```lua\nlocal config = BetterConfig.new(workspace.GameConfig)\n\n-- Use the config...\nconfig:GetValueChangedSignal(\"max_items\"):Connect(handler)\n\n-- Clean up when done (important!)\nconfig:Destroy()\n```\n\n**Why it matters:** `GetValueChangedSignal` creates `RBXScriptConnection` objects and Signal instances. Calling `Destroy()` disconnects all connections and destroys all signals, preventing memory leaks.\n\n**When to call Destroy():**\n- When the config is no longer needed\n- Before removing the source Instance\n- In cleanup functions (e.g., `Maid:DoCleaning()`)\n\n## API Reference\n\n### Factory Methods\n\n#### `BetterConfig.new<T>(src: ConfigSrc): BaseConfigClass<T>`\nAuto-detects the config source type and returns the appropriate implementation.\n\n**Parameters:**\n- `src`: Configuration instance, Instance (uses Attributes), or Lua table\n\n**Returns:** Config object with `Get`, `Set`, `GetValueChangedSignal`, and `Destroy` methods\n\n---\n\n#### `BetterConfig.fromAttribute<T>(src: Instance): AttributeConfig<T>`\nCreates an Attribute-based config explicitly.\n\n**Parameters:**\n- `src`: Any Instance with Attributes\n\n**Returns:** AttributeConfig implementation\n\n---\n\n#### `BetterConfig.fromConfiguration<T>(src: Configuration): ConfigInstance<T>`\nCreates a Configuration/ValueBase-based config explicitly.\n\n**Parameters:**\n- `src`: Configuration instance with ValueBase children\n\n**Returns:** ConfigInstance implementation\n\n---\n\n#### `BetterConfig.fromDict<T>(src: {[string]: any}): DictionaryConfig<T>`\nCreates a Dictionary-based config explicitly. Internally creates a Configuration instance with appropriate ValueBase children.\n\n**Parameters:**\n- `src`: Lua table with string keys and number/string/boolean values\n\n**Returns:** DictionaryConfig implementation\n\n---\n\n### Instance Methods\n\nAll config implementations expose the same interface:\n\n#### `Get<T>(key: keyof<T>): index<T, keyof<T>>`\nRetrieves the current value for the specified key.\n\n**Parameters:**\n- `key`: Configuration key (autocomplete enabled with type hints)\n\n**Returns:** Current value\n\n---\n\n#### `Set<T>(key: keyof<T>, value: index<T, keyof<T>>): ()`\nSets a new value for the specified key.\n\n**Parameters:**\n- `key`: Configuration key (autocomplete enabled with type hints)\n- `value`: New value to set\n\n---\n\n#### `GetValueChangedSignal<T>(key: keyof<T>): Signal<index<T, keyof<T>>>`\nReturns a Signal that fires when the specified key's value changes.\n\n**Parameters:**\n- `key`: Configuration key (autocomplete enabled with type hints)\n\n**Returns:** Signal object with `Connect`, `Once`, `Wait` methods\n\n---\n\n#### `Destroy(): ()`\nCleans up all connections and signals. Call this when done with the config.\n\n---\n\n### Types\n\n```lua\nexport type BaseConfigClass<T = { [string]: any }> = {\n    ConfigSrc: ConfigSrc,\n    ConfigType: ConfigSrcType,\n\n    Get: (self: BaseConfigClass<T>, key: keyof<T>) -> index<T, keyof<T>>,\n    Set: (self: BaseConfigClass<T>, key: keyof<T>, value: index<T, keyof<T>>) -> (),\n    GetValueChangedSignal: (self: BaseConfigClass<T>, key: keyof<T>) -> Signal,\n    Destroy: (self: BaseConfigClass<T>) -> (),\n}\n\nexport type ConfigSrc = Configuration | Instance | { [string]: any }\nexport type ConfigSrcType = \"Configuration\" | \"Attribute\" | \"Dict\"\n```\n\n## Examples\n\nFull examples are available at:\n- [`/Examples/client/BetterConfig.client.luau`](../../Examples/client/BetterConfig.client.luau) - Comprehensive examples with Attributes and Dictionary configs\n\n### Example 1: Attribute Config with Change Observation\n\n```lua\nlocal BetterConfig = require(ReplicatedStorage.Modules.BetterConfig)\n\n-- Create a part with attributes\nlocal configPart = Instance.new(\"Part\")\nconfigPart:SetAttribute(\"show_ui\", true)\nconfigPart:SetAttribute(\"max_items\", 10)\n\n-- Create config\nlocal config = BetterConfig.fromAttribute(configPart)\n\n-- Get values\nprint(config:Get(\"show_ui\"))      -- true\nprint(config:Get(\"max_items\"))    -- 10\n\n-- Observe changes\nconfig:GetValueChangedSignal(\"max_items\"):Connect(function(newValue)\n    print(\"max_items changed to:\", newValue)\nend)\n\n-- Update value (fires the signal)\nconfig:Set(\"max_items\", 20)  -- Prints: \"max_items changed to: 20\"\n```\n\n### Example 2: Dictionary Config\n\n```lua\nlocal BetterConfig = require(ReplicatedStorage.Modules.BetterConfig)\n\n-- Create config from table\nlocal config = BetterConfig.fromDict({\n    round_time = 120,\n    team_size = 4,\n    map_name = \"Grasslands\",\n})\n\n-- Use like any other config\nprint(config:Get(\"round_time\"))  -- 120\n\n-- Observe and update\nconfig:GetValueChangedSignal(\"round_time\"):Connect(function(newValue)\n    print(\"Round time changed to:\", newValue)\nend)\n\nconfig:Set(\"round_time\", 180)  -- Prints: \"Round time changed to: 180\"\n```\n\n## Comparison with RbxConfig\n\n| Feature | BetterConfig | RbxConfig |\n|---------|--------------|-----------|\n| **Config Source** | Local (Configuration/Attributes/Tables) | Cloud (Roblox ConfigService) |\n| **Setup Complexity** | Simple (no external setup) | Requires ConfigService setup in Creator Dashboard |\n| **Server Required** | No | Yes |\n| **Remote Updates** | No (local only) | Yes (update without restart) |\n| **Player Targeting** | No | Yes (per-player configs) |\n| **Use Cases** | Local game config, flexible sources | A/B testing, remote config, feature flags |\n| **Update Speed** | Immediate (local) | Network dependent |\n\n**Use BetterConfig when:**\n- You need local configuration storage\n- You want flexible config sources (Attributes, Configuration, tables)\n- You don't need remote config updates\n- Setup simplicity is important\n\n**Use RbxConfig when:**\n- You need cloud-based configuration\n- You want to update config without restarting servers\n- You need player-specific targeting for A/B testing\n- You want feature flags controlled remotely\n\n## Migration from v1.x to v2.0\n\n### Breaking Changes\n\n1. **Complete refactor using Strategy Pattern**: The internal implementation was split into three separate strategy classes (ConfigInstance, AttributeConfig, DictionaryConfig)\n2. **Method renamed**: `getConfigType()` became `_getConfigType()` (now private/internal)\n3. **API now strongly typed**: All methods use Luau generics with `keyof<T>` and `index<T, K>`\n4. **New DictionaryConfig support**: v2.0 adds support for plain Lua tables as config sources\n\n### Migration Steps\n\n1. **Update Wally dependency** to version 2.0.0:\n   ```toml\n   [dependencies]\n   BetterConfig = \"zac134/better-config@2.0.0\"\n   ```\n\n2. **Add type definitions** for autocomplete (optional but recommended):\n   ```lua\n   type MyConfig = {\n       max_players: number,\n       show_ui: boolean,\n   }\n   ```\n\n3. **Update internal API usage** (if you used private methods):\n   - `config:getConfigType()` → Not available (use `config.ConfigType` property instead)\n   - If you relied on internal implementation details, review the new Strategy Pattern architecture\n\n### Benefits of Upgrading\n\n- Better type inference and autocomplete\n- Support for Dictionary configs\n- Cleaner internal architecture (easier to extend)\n- Better performance (lazy observation in AttributeConfig)\n\n## Dependencies\n\n- **[sleitnick/signal@^2.0](https://github.com/sleitnick/rbx-util/tree/main/modules/signal)** - Signal implementation for reactive change observation\n\n## License\n\nMIT License\n\n---\n\n# BetterConfig (日本語)\n\nRoblox用の統合設定管理ライブラリ。ストラテジーパターンを使用しています。\n\n## 概要\n\nBetterConfigは、Robloxの複数のソースからの設定データを管理するための、単一で一貫したAPIを提供します。設定がValueBase子要素を持つConfigurationインスタンス、インスタンス属性、または純粋なLuaテーブルのいずれから来ても、BetterConfigは違いを抽象化し、完全な型安全性とリアクティブな変更監視を備えた同じ直感的なインターフェースを提供します。\n\n**問題**: Robloxの異なる設定ソースは異なるAPI(Configuration→ValueBase、Instance→Attributes、プレーンテーブル)を持っており、コードが一貫性を欠き、保守が困難になります。\n\n**解決策**: BetterConfigはソースタイプを自動的に検出し、適切な実装を返すことで、型安全な自動補完を備えた統一されたインターフェースを提供します。\n\n## 機能\n\n- **3つの設定ソースストラテジー**と自動検出\n- **型安全なAPI**: Luauジェネリクスと`keyof<T>`による自動補完\n- **リアクティブな値変更監視**: Signalサポート\n- **ゼロボイラープレート**: ファクトリーパターン\n- **リソース管理**: `Destroy()`メソッド\n\n## インストール\n\n### Wally経由\n\n```toml\n[dependencies]\nBetterConfig = \"zac134/better-config@2.0.0\"\n```\n\n### 手動インストール\n\n`Modules/BetterConfig/`ディレクトリをプロジェクトのReplicatedStorageまたはServerStorageにコピーしてください。\n\n## クイックスタート\n\n```lua\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\nlocal BetterConfig = require(ReplicatedStorage.Modules.BetterConfig)\n\n-- ファクトリーが設定ソースタイプを自動検出\nlocal config = BetterConfig.new(workspace.GameConfig)\n\n-- 値を取得\nlocal value = config:Get(\"max_players\")\n\n-- 値を設定\nconfig:Set(\"max_players\", 10)\n\n-- 変更を監視\nconfig:GetValueChangedSignal(\"max_players\"):Connect(function(newValue)\n    print(\"max_playersが変更されました:\", newValue)\nend)\n```\n\n## 設定ソース\n\n### 1. インスタンス属性\n\n既存のインスタンスにシンプルなキーバリュー設定が必要な場合は、属性を使用します。\n\n```lua\nlocal part = workspace.ConfigPart\npart:SetAttribute(\"max_items\", 10)\npart:SetAttribute(\"show_ui\", true)\n\nlocal config = BetterConfig.fromAttribute(part)\nlocal maxItems = config:Get(\"max_items\")\nconfig:Set(\"show_ui\", false)\n```\n\n**利点:**\n- Roblox属性がサポートする柔軟な型\n- Studioのプロパティパネルで簡単に検査・変更可能\n- 軽量(子インスタンス不要)\n\n### 2. Configuration/ValueBase\n\n特定のValueBaseタイプが必要な場合、またはStudioでのビジュアル編集が必要な場合は、Configurationインスタンスを使用します。\n\n```lua\nlocal configInstance = workspace.GameConfig  -- ValueBase子要素を持つConfigurationインスタンス\nlocal config = BetterConfig.fromConfiguration(configInstance)\n\nlocal maxPlayers = config:Get(\"max_players\")  -- IntValue子要素から読み取り\nconfig:Set(\"max_players\", 8)  -- IntValue.Valueを更新\n```\n\n**利点:**\n- 型固有のValueBase子要素(IntValue、StringValue、BoolValueなど)\n- Studioエクスプローラーでのビジュアル階層\n- ValueBaseプロパティへの直接アクセス\n\n**サポートされているValueBaseタイプ:** `ObjectValue`、`IntValue`、`BoolValue`、`StringValue`、`NumberValue`、`Color3Value`、`CFrameValue`、`Vector3Value`、`BrickColorValue`、`RayValue`\n\n### 3. Dictionary/Table\n\nランタイム設定生成、テスト、またはプログラマティック設定が必要な場合は、Dictionary設定を使用します。\n\n```lua\nlocal config = BetterConfig.fromDict({\n    round_time = 120,\n    team_siz","readmeBorrowedFrom":{"version":"2.1.0","source":"wally"},"readmeTruncated":true,"readmeFull":"https://api.forest.dev/v1/package/zac134/roblox/better-config/2.2.0/readme"}