discord_rda 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +398 -0
  4. data/lib/discord_rda/bot.rb +842 -0
  5. data/lib/discord_rda/cache/configurable_cache.rb +283 -0
  6. data/lib/discord_rda/cache/entity_cache.rb +184 -0
  7. data/lib/discord_rda/cache/memory_store.rb +143 -0
  8. data/lib/discord_rda/cache/redis_store.rb +136 -0
  9. data/lib/discord_rda/cache/store.rb +56 -0
  10. data/lib/discord_rda/connection/gateway_client.rb +383 -0
  11. data/lib/discord_rda/connection/invalid_bucket.rb +205 -0
  12. data/lib/discord_rda/connection/rate_limiter.rb +280 -0
  13. data/lib/discord_rda/connection/request_queue.rb +340 -0
  14. data/lib/discord_rda/connection/reshard_manager.rb +328 -0
  15. data/lib/discord_rda/connection/rest_client.rb +316 -0
  16. data/lib/discord_rda/connection/rest_proxy.rb +165 -0
  17. data/lib/discord_rda/connection/scalable_rest_client.rb +526 -0
  18. data/lib/discord_rda/connection/shard_manager.rb +223 -0
  19. data/lib/discord_rda/core/async_runtime.rb +108 -0
  20. data/lib/discord_rda/core/configuration.rb +194 -0
  21. data/lib/discord_rda/core/logger.rb +188 -0
  22. data/lib/discord_rda/core/snowflake.rb +121 -0
  23. data/lib/discord_rda/entity/attachment.rb +88 -0
  24. data/lib/discord_rda/entity/base.rb +103 -0
  25. data/lib/discord_rda/entity/channel.rb +446 -0
  26. data/lib/discord_rda/entity/channel_builder.rb +280 -0
  27. data/lib/discord_rda/entity/color.rb +253 -0
  28. data/lib/discord_rda/entity/embed.rb +221 -0
  29. data/lib/discord_rda/entity/emoji.rb +89 -0
  30. data/lib/discord_rda/entity/factory.rb +99 -0
  31. data/lib/discord_rda/entity/guild.rb +619 -0
  32. data/lib/discord_rda/entity/member.rb +263 -0
  33. data/lib/discord_rda/entity/message.rb +405 -0
  34. data/lib/discord_rda/entity/message_builder.rb +369 -0
  35. data/lib/discord_rda/entity/role.rb +157 -0
  36. data/lib/discord_rda/entity/support.rb +294 -0
  37. data/lib/discord_rda/entity/user.rb +231 -0
  38. data/lib/discord_rda/entity/value_objects.rb +263 -0
  39. data/lib/discord_rda/event/auto_moderation.rb +294 -0
  40. data/lib/discord_rda/event/base.rb +986 -0
  41. data/lib/discord_rda/event/bus.rb +225 -0
  42. data/lib/discord_rda/event/scheduled_event.rb +257 -0
  43. data/lib/discord_rda/hot_reload_manager.rb +303 -0
  44. data/lib/discord_rda/interactions/application_command.rb +436 -0
  45. data/lib/discord_rda/interactions/command_system.rb +484 -0
  46. data/lib/discord_rda/interactions/components.rb +464 -0
  47. data/lib/discord_rda/interactions/interaction.rb +553 -0
  48. data/lib/discord_rda/plugin/analytics_plugin.rb +528 -0
  49. data/lib/discord_rda/plugin/base.rb +190 -0
  50. data/lib/discord_rda/plugin/registry.rb +126 -0
  51. data/lib/discord_rda/version.rb +5 -0
  52. data/lib/discord_rda.rb +70 -0
  53. metadata +302 -0
@@ -0,0 +1,253 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DiscordRDA
4
+ # Represents a Discord color.
5
+ # Provides conversion between RGB, hex, and integer representations.
6
+ #
7
+ # @example Creating colors
8
+ # Color.new(0xFF5733) # From integer
9
+ # Color.from_rgb(255, 87, 51) # From RGB
10
+ # Color.from_hex('#FF5733') # From hex string
11
+ # Color.teal # Named color
12
+ #
13
+ class Color
14
+ # Default Discord colors
15
+ DEFAULTS = {
16
+ default: 0,
17
+ white: 0xFFFFFF,
18
+ aqua: 0x1ABC9C,
19
+ green: 0x57F287,
20
+ blue: 0x3498DB,
21
+ yellow: 0xFEE75C,
22
+ purple: 0x9B59B6,
23
+ luminous_vivid_pink: 0xE91E63,
24
+ gold: 0xF1C40F,
25
+ orange: 0xE67E22,
26
+ red: 0xED4245,
27
+ grey: 0x95A5A6,
28
+ navy: 0x34495E,
29
+ dark_aqua: 0x11806A,
30
+ dark_green: 0x1F8B4C,
31
+ dark_blue: 0x206694,
32
+ dark_purple: 0x71368A,
33
+ dark_vivid_pink: 0xAD1457,
34
+ dark_gold: 0xC27C0E,
35
+ dark_orange: 0xA84300,
36
+ dark_red: 0x992D22,
37
+ dark_grey: 0x979C9F,
38
+ darker_grey: 0x7F8C8D,
39
+ light_grey: 0xBCC0C0,
40
+ dark_navy: 0x2C3E50,
41
+ blurple: 0x5865F2,
42
+ greyple: 0x99AAB5,
43
+ dark_but_not_black: 0x2C2F33,
44
+ not_quite_black: 0x23272A
45
+ }.freeze
46
+
47
+ # @return [Integer] The color value
48
+ attr_reader :value
49
+
50
+ class << self
51
+ # Create from RGB values
52
+ # @param r [Integer] Red (0-255)
53
+ # @param g [Integer] Green (0-255)
54
+ # @param b [Integer] Blue (0-255)
55
+ # @return [Color] New color
56
+ def from_rgb(r, g, b)
57
+ new((r.to_i << 16) | (g.to_i << 8) | b.to_i)
58
+ end
59
+
60
+ # Create from hex string
61
+ # @param hex [String] Hex string (#RRGGBB or RRGGBB)
62
+ # @return [Color] New color
63
+ def from_hex(hex)
64
+ hex = hex.to_s.delete_prefix('#')
65
+ new(hex.to_i(16))
66
+ end
67
+
68
+ # Create from HSL values
69
+ # @param h [Float] Hue (0-360)
70
+ # @param s [Float] Saturation (0-1)
71
+ # @param l [Float] Lightness (0-1)
72
+ # @return [Color] New color
73
+ def from_hsl(h, s, l)
74
+ h = h.to_f / 360.0
75
+ s = s.to_f
76
+ l = l.to_f
77
+
78
+ r, g, b = if s == 0
79
+ [l, l, l]
80
+ else
81
+ q = l < 0.5 ? l * (1 + s) : l + s - l * s
82
+ p = 2 * l - q
83
+ [hue_to_rgb(p, q, h + 1.0 / 3),
84
+ hue_to_rgb(p, q, h),
85
+ hue_to_rgb(p, q, h - 1.0 / 3)]
86
+ end
87
+
88
+ from_rgb((r * 255).round, (g * 255).round, (b * 255).round)
89
+ end
90
+
91
+ # Generate random color
92
+ # @return [Color] Random color
93
+ def random
94
+ new(rand(0xFFFFFF))
95
+ end
96
+
97
+ # Named color methods
98
+ DEFAULTS.each do |name, value|
99
+ define_method(name) { new(value) }
100
+ end
101
+
102
+ private
103
+
104
+ def hue_to_rgb(p, q, t)
105
+ t += 1 if t < 0
106
+ t -= 1 if t > 1
107
+ return p + (q - p) * 6 * t if t < 1.0 / 6
108
+ return q if t < 1.0 / 2
109
+ return p + (q - p) * (2.0 / 3 - t) * 6 if t < 2.0 / 3
110
+ p
111
+ end
112
+ end
113
+
114
+ # Initialize with a color value
115
+ # @param value [Integer] Color value (0-0xFFFFFF)
116
+ def initialize(value = 0)
117
+ @value = value.to_i & 0xFFFFFF
118
+ end
119
+
120
+ # Get red component
121
+ # @return [Integer] Red (0-255)
122
+ def r
123
+ (@value >> 16) & 0xFF
124
+ end
125
+ alias red r
126
+
127
+ # Get green component
128
+ # @return [Integer] Green (0-255)
129
+ def g
130
+ (@value >> 8) & 0xFF
131
+ end
132
+ alias green g
133
+
134
+ # Get blue component
135
+ # @return [Integer] Blue (0-255)
136
+ def b
137
+ @value & 0xFF
138
+ end
139
+ alias blue b
140
+
141
+ # Get RGB array
142
+ # @return [Array<Integer>] RGB values
143
+ def rgb
144
+ [r, g, b]
145
+ end
146
+
147
+ # Get hex string
148
+ # @param prefix [Boolean] Include # prefix
149
+ # @return [String] Hex string
150
+ def to_hex(prefix: true)
151
+ hex = @value.to_s(16).upcase.rjust(6, '0')
152
+ prefix ? "##{hex}" : hex
153
+ end
154
+
155
+ # Convert to RGB tuple string
156
+ # @return [String] RGB string
157
+ def to_rgb_string
158
+ "rgb(#{r}, #{g}, #{b})"
159
+ end
160
+
161
+ # Get integer value
162
+ # @return [Integer] Color value
163
+ def to_i
164
+ @value
165
+ end
166
+
167
+ # Convert to decimal color string (for Discord)
168
+ # @return [String] Decimal string
169
+ def to_s
170
+ @value.to_s
171
+ end
172
+
173
+ # Check if color is valid (non-zero)
174
+ # @return [Boolean] True if has color
175
+ def valid?
176
+ @value > 0
177
+ end
178
+
179
+ # Check if color is the default (0)
180
+ # @return [Boolean] True if default
181
+ def default?
182
+ @value == 0
183
+ end
184
+
185
+ # Get brightness (0-255)
186
+ # @return [Integer] Brightness
187
+ def brightness
188
+ (r * 299 + g * 587 + b * 114) / 1000
189
+ end
190
+
191
+ # Check if color is light (brightness > 128)
192
+ # @return [Boolean] True if light
193
+ def light?
194
+ brightness > 128
195
+ end
196
+
197
+ # Check if color is dark (brightness <= 128)
198
+ # @return [Boolean] True if dark
199
+ def dark?
200
+ brightness <= 128
201
+ end
202
+
203
+ # Blend with another color
204
+ # @param other [Color] Other color
205
+ # @param ratio [Float] Blend ratio (0-1)
206
+ # @return [Color] Blended color
207
+ def blend(other, ratio = 0.5)
208
+ r = (self.r * (1 - ratio) + other.r * ratio).round
209
+ g = (self.g * (1 - ratio) + other.g * ratio).round
210
+ b = (self.b * (1 - ratio) + other.b * ratio).round
211
+ self.class.from_rgb(r, g, b)
212
+ end
213
+
214
+ # Darken the color
215
+ # @param amount [Float] Amount to darken (0-1)
216
+ # @return [Color] Darkened color
217
+ def darken(amount = 0.2)
218
+ blend(self.class.new(0), amount)
219
+ end
220
+
221
+ # Lighten the color
222
+ # @param amount [Float] Amount to lighten (0-1)
223
+ # @return [Color] Lightened color
224
+ def lighten(amount = 0.2)
225
+ blend(self.class.new(0xFFFFFF), amount)
226
+ end
227
+
228
+ # Get complementary color
229
+ # @return [Color] Complementary color
230
+ def complementary
231
+ self.class.from_rgb(255 - r, 255 - g, 255 - b)
232
+ end
233
+
234
+ # Check equality
235
+ # @param other [Object] Other object
236
+ # @return [Boolean] True if equal
237
+ def ==(other)
238
+ other.is_a?(Color) && @value == other.value
239
+ end
240
+
241
+ # Hash code
242
+ # @return [Integer] Hash code
243
+ def hash
244
+ @value.hash
245
+ end
246
+
247
+ # Inspect
248
+ # @return [String] Inspect string
249
+ def inspect
250
+ "#<Color #{to_hex}>"
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DiscordRDA
4
+ # Represents a Discord message embed.
5
+ #
6
+ class Embed < Entity
7
+ # Embed types
8
+ TYPES = %w[rich image video gifv article link].freeze
9
+
10
+ attribute :title, type: :string
11
+ attribute :type, type: :string, default: 'rich'
12
+ attribute :description, type: :string
13
+ attribute :url, type: :string
14
+ attribute :timestamp, type: :time
15
+ attribute :color, type: :integer, default: 0
16
+ attribute :footer, type: :hash
17
+ attribute :image, type: :hash
18
+ attribute :thumbnail, type: :hash
19
+ attribute :video, type: :hash
20
+ attribute :provider, type: :hash
21
+ attribute :author, type: :hash
22
+ attribute :fields, type: :array, default: []
23
+
24
+ # Get color as Color object
25
+ # @return [Color] Embed color
26
+ def color_object
27
+ Color.new(color)
28
+ end
29
+
30
+ # Check if embed has fields
31
+ # @return [Boolean] True if has fields
32
+ def has_fields?
33
+ fields.any?
34
+ end
35
+
36
+ # Get fields
37
+ # @return [Array<EmbedField>] Fields
38
+ def embed_fields
39
+ (@raw_data['fields'] || []).map { |f| EmbedField.new(f) }
40
+ end
41
+
42
+ # Get footer
43
+ # @return [EmbedFooter, nil] Footer
44
+ def embed_footer
45
+ EmbedFooter.new(@raw_data['footer']) if @raw_data['footer']
46
+ end
47
+
48
+ # Get image
49
+ # @return [EmbedImage, nil] Image
50
+ def embed_image
51
+ EmbedImage.new(@raw_data['image']) if @raw_data['image']
52
+ end
53
+
54
+ # Get thumbnail
55
+ # @return [EmbedThumbnail, nil] Thumbnail
56
+ def embed_thumbnail
57
+ EmbedThumbnail.new(@raw_data['thumbnail']) if @raw_data['thumbnail']
58
+ end
59
+
60
+ # Get video
61
+ # @return [EmbedVideo, nil] Video
62
+ def embed_video
63
+ EmbedVideo.new(@raw_data['video']) if @raw_data['video']
64
+ end
65
+
66
+ # Get provider
67
+ # @return [EmbedProvider, nil] Provider
68
+ def embed_provider
69
+ EmbedProvider.new(@raw_data['provider']) if @raw_data['provider']
70
+ end
71
+
72
+ # Get author
73
+ # @return [EmbedAuthor, nil] Author
74
+ def embed_author
75
+ EmbedAuthor.new(@raw_data['author']) if @raw_data['author']
76
+ end
77
+
78
+ # Check if embed has image or thumbnail
79
+ # @return [Boolean] True if has media
80
+ def has_media?
81
+ @raw_data['image'] || @raw_data['thumbnail']
82
+ end
83
+
84
+ # Check if embed is empty
85
+ # @return [Boolean] True if empty
86
+ def empty?
87
+ title.nil? && description.nil? && !has_fields?
88
+ end
89
+
90
+ # Builder class for creating embeds
91
+ class Builder
92
+ def initialize
93
+ @data = {}
94
+ end
95
+
96
+ def title(value)
97
+ @data['title'] = value
98
+ self
99
+ end
100
+
101
+ def description(value)
102
+ @data['description'] = value
103
+ self
104
+ end
105
+
106
+ def url(value)
107
+ @data['url'] = value
108
+ self
109
+ end
110
+
111
+ def timestamp(value)
112
+ @data['timestamp'] = value.iso8601
113
+ self
114
+ end
115
+
116
+ def color(value)
117
+ @data['color'] = value.is_a?(Color) ? value.to_i : value
118
+ self
119
+ end
120
+
121
+ def field(name:, value:, inline: false)
122
+ @data['fields'] ||= []
123
+ @data['fields'] << { 'name' => name, 'value' => value, 'inline' => inline }
124
+ self
125
+ end
126
+
127
+ def footer(text:, icon_url: nil)
128
+ @data['footer'] = { 'text' => text }
129
+ @data['footer']['icon_url'] = icon_url if icon_url
130
+ self
131
+ end
132
+
133
+ def image(url:, proxy_url: nil, height: nil, width: nil)
134
+ @data['image'] = { 'url' => url }
135
+ @data['image']['proxy_url'] = proxy_url if proxy_url
136
+ @data['image']['height'] = height if height
137
+ @data['image']['width'] = width if width
138
+ self
139
+ end
140
+
141
+ def thumbnail(url:, proxy_url: nil, height: nil, width: nil)
142
+ @data['thumbnail'] = { 'url' => url }
143
+ @data['thumbnail']['proxy_url'] = proxy_url if proxy_url
144
+ @data['thumbnail']['height'] = height if height
145
+ @data['thumbnail']['width'] = width if width
146
+ self
147
+ end
148
+
149
+ def author(name:, url: nil, icon_url: nil)
150
+ @data['author'] = { 'name' => name }
151
+ @data['author']['url'] = url if url
152
+ @data['author']['icon_url'] = icon_url if icon_url
153
+ self
154
+ end
155
+
156
+ def build
157
+ Embed.new(@data)
158
+ end
159
+
160
+ def to_h
161
+ @data
162
+ end
163
+ end
164
+ end
165
+
166
+ # Embed field
167
+ class EmbedField < Entity
168
+ attribute :name, type: :string
169
+ attribute :value, type: :string
170
+ attribute :inline, type: :boolean, default: false
171
+
172
+ def inline?
173
+ inline
174
+ end
175
+ end
176
+
177
+ # Embed footer
178
+ class EmbedFooter < Entity
179
+ attribute :text, type: :string
180
+ attribute :icon_url, type: :string
181
+ attribute :proxy_icon_url, type: :string
182
+ end
183
+
184
+ # Embed image
185
+ class EmbedImage < Entity
186
+ attribute :url, type: :string
187
+ attribute :proxy_url, type: :string
188
+ attribute :height, type: :integer
189
+ attribute :width, type: :integer
190
+ end
191
+
192
+ # Embed thumbnail
193
+ class EmbedThumbnail < Entity
194
+ attribute :url, type: :string
195
+ attribute :proxy_url, type: :string
196
+ attribute :height, type: :integer
197
+ attribute :width, type: :integer
198
+ end
199
+
200
+ # Embed video
201
+ class EmbedVideo < Entity
202
+ attribute :url, type: :string
203
+ attribute :proxy_url, type: :string
204
+ attribute :height, type: :integer
205
+ attribute :width, type: :integer
206
+ end
207
+
208
+ # Embed provider
209
+ class EmbedProvider < Entity
210
+ attribute :name, type: :string
211
+ attribute :url, type: :string
212
+ end
213
+
214
+ # Embed author
215
+ class EmbedAuthor < Entity
216
+ attribute :name, type: :string
217
+ attribute :url, type: :string
218
+ attribute :icon_url, type: :string
219
+ attribute :proxy_icon_url, type: :string
220
+ end
221
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DiscordRDA
4
+ # Represents a Discord emoji (custom or Unicode).
5
+ #
6
+ class Emoji < Entity
7
+ attribute :name, type: :string
8
+ attribute :roles, type: :array, default: []
9
+ attribute :user, type: :hash
10
+ attribute :require_colons, type: :boolean, default: true
11
+ attribute :managed, type: :boolean, default: false
12
+ attribute :animated, type: :boolean, default: false
13
+ attribute :available, type: :boolean, default: true
14
+
15
+ # Get the guild ID
16
+ # @return [Snowflake, nil] Guild ID
17
+ def guild_id
18
+ @raw_data['guild_id'] ? Snowflake.new(@raw_data['guild_id']) : nil
19
+ end
20
+
21
+ # Check if emoji is animated
22
+ # @return [Boolean] True if animated
23
+ def animated?
24
+ animated
25
+ end
26
+
27
+ # Check if emoji requires colons
28
+ # @return [Boolean] True if requires colons
29
+ def require_colons?
30
+ require_colons
31
+ end
32
+
33
+ # Check if emoji is managed by integration
34
+ # @return [Boolean] True if managed
35
+ def managed?
36
+ managed
37
+ end
38
+
39
+ # Check if emoji is available (not blocked by role restrictions)
40
+ # @return [Boolean] True if available
41
+ def available?
42
+ available
43
+ end
44
+
45
+ # Get the emoji creator
46
+ # @return [User, nil] Creator
47
+ def user
48
+ @user ||= User.new(@raw_data['user']) if @raw_data['user']
49
+ end
50
+
51
+ # Get role restrictions
52
+ # @return [Array<Snowflake>] Role IDs
53
+ def role_ids
54
+ (@raw_data['roles'] || []).map { |r| Snowflake.new(r) }
55
+ end
56
+
57
+ # Get the emoji URL
58
+ # @return [String] Emoji URL
59
+ def url
60
+ ext = animated? ? 'gif' : 'png'
61
+ "https://cdn.discordapp.com/emojis/#{id}.#{ext}"
62
+ end
63
+
64
+ # Get the emoji mention string
65
+ # @return [String] Emoji mention
66
+ def mention
67
+ prefix = animated? ? 'a' : ''
68
+ "<#{prefix}:#{name}:#{id}>"
69
+ end
70
+
71
+ # Check if this is a Unicode emoji (no ID)
72
+ # @return [Boolean] True if Unicode emoji
73
+ def unicode?
74
+ id.nil?
75
+ end
76
+
77
+ # Check if this is a custom emoji
78
+ # @return [Boolean] True if custom emoji
79
+ def custom?
80
+ !id.nil?
81
+ end
82
+
83
+ # Get creation time (custom emojis only)
84
+ # @return [Time, nil] Creation time
85
+ def created_at
86
+ id&.timestamp
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DiscordRDA
4
+ # Factory for creating Discord entities from API data.
5
+ # Supports registration of custom entity types.
6
+ #
7
+ # @example Basic usage
8
+ # user = EntityFactory.create(:user, api_data)
9
+ # guild = EntityFactory.create(:guild, api_data)
10
+ #
11
+ # @example Custom entity registration
12
+ # class CustomGuild < Guild
13
+ # # custom implementation
14
+ # end
15
+ # EntityFactory.register(:guild, CustomGuild)
16
+ #
17
+ class EntityFactory
18
+ # Default entity mappings
19
+ DEFAULT_ENTITIES = {
20
+ user: 'DiscordRDA::User',
21
+ guild: 'DiscordRDA::Guild',
22
+ channel: 'DiscordRDA::Channel',
23
+ message: 'DiscordRDA::Message',
24
+ role: 'DiscordRDA::Role',
25
+ member: 'DiscordRDA::Member',
26
+ emoji: 'DiscordRDA::Emoji',
27
+ attachment: 'DiscordRDA::Attachment',
28
+ embed: 'DiscordRDA::Embed',
29
+ webhook: 'DiscordRDA::Webhook'
30
+ }.freeze
31
+
32
+ class << self
33
+ # Register an entity type
34
+ # @param type [Symbol] Entity type identifier
35
+ # @param klass [Class] Entity class
36
+ # @return [void]
37
+ def register(type, klass)
38
+ registry[type.to_sym] = klass
39
+ end
40
+
41
+ # Create an entity from data
42
+ # @param type [Symbol] Entity type
43
+ # @param data [Hash] API data
44
+ # @return [Entity] Created entity
45
+ def create(type, data)
46
+ klass = registry[type.to_sym]
47
+ raise ArgumentError, "Unknown entity type: #{type}" unless klass
48
+
49
+ klass.new(data)
50
+ end
51
+
52
+ # Create multiple entities from array data
53
+ # @param type [Symbol] Entity type
54
+ # @param data_array [Array<Hash>] Array of API data
55
+ # @return [Array<Entity>] Created entities
56
+ def create_many(type, data_array)
57
+ return [] unless data_array.is_a?(Array)
58
+
59
+ data_array.map { |data| create(type, data) }
60
+ end
61
+
62
+ # Check if an entity type is registered
63
+ # @param type [Symbol] Entity type
64
+ # @return [Boolean] True if registered
65
+ def registered?(type)
66
+ registry.key?(type.to_sym)
67
+ end
68
+
69
+ # Get all registered types
70
+ # @return [Array<Symbol>] Registered type names
71
+ def registered_types
72
+ registry.keys
73
+ end
74
+
75
+ # Unregister an entity type
76
+ # @param type [Symbol] Entity type to unregister
77
+ # @return [void]
78
+ def unregister(type)
79
+ registry.delete(type.to_sym)
80
+ end
81
+
82
+ # Reset to default registrations
83
+ # @return [void]
84
+ def reset!
85
+ @registry = nil
86
+ end
87
+
88
+ private
89
+
90
+ def registry
91
+ @registry ||= build_default_registry
92
+ end
93
+
94
+ def build_default_registry
95
+ DEFAULT_ENTITIES.transform_values { |class_name| Object.const_get(class_name) }
96
+ end
97
+ end
98
+ end
99
+ end