@quenty/tie 10.6.0 → 10.7.0

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 (38) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +4 -0
  3. package/package.json +16 -15
  4. package/src/Shared/Members/Methods/TieMethodDefinition.lua +44 -0
  5. package/src/Shared/{Implementation → Members/Methods}/TieMethodImplementation.lua +4 -5
  6. package/src/Shared/Members/Methods/TieMethodInterfaceUtils.lua +58 -0
  7. package/src/Shared/{Interface → Members/Properties}/TiePropertyChangedSignalConnection.lua +1 -1
  8. package/src/Shared/Members/Properties/TiePropertyDefinition.lua +58 -0
  9. package/src/Shared/{Interface → Members/Properties}/TiePropertyInterface.lua +42 -59
  10. package/src/Shared/Members/Signals/TieSignalConnection.lua +63 -0
  11. package/src/Shared/Members/Signals/TieSignalDefinition.lua +38 -0
  12. package/src/Shared/{Implementation → Members/Signals}/TieSignalImplementation.lua +29 -15
  13. package/src/Shared/Members/Signals/TieSignalInterface.lua +90 -0
  14. package/src/Shared/Members/TieMemberDefinition.lua +104 -0
  15. package/src/Shared/Members/TieMemberInterface.lua +94 -0
  16. package/src/Shared/Realms/TieRealmUtils.lua +41 -0
  17. package/src/Shared/{Definition/Types → Realms}/TieRealms.lua +3 -4
  18. package/src/Shared/Services/TieRealmService.lua +31 -0
  19. package/src/Shared/TieDefinition.lua +708 -0
  20. package/src/Shared/TieImplementation.lua +167 -0
  21. package/src/Shared/TieInterface.lua +129 -0
  22. package/src/Shared/{Encoding → Utils}/TieUtils.lua +4 -1
  23. package/test/modules/Server/Action/Action.lua +4 -7
  24. package/test/modules/Server/Door.lua +5 -10
  25. package/test/scripts/Server/ServerMain.server.lua +3 -3
  26. package/src/Shared/Definition/TieDefinition.lua +0 -507
  27. package/src/Shared/Definition/TieMethodDefinition.lua +0 -61
  28. package/src/Shared/Definition/TiePropertyDefinition.lua +0 -64
  29. package/src/Shared/Definition/TieSignalDefinition.lua +0 -59
  30. package/src/Shared/Definition/Types/TieRealmUtils.lua +0 -60
  31. package/src/Shared/Implementation/TieImplementation.lua +0 -129
  32. package/src/Shared/Interface/TieInterface.lua +0 -122
  33. package/src/Shared/Interface/TieInterfaceUtils.lua +0 -70
  34. package/src/Shared/Interface/TieMethodInterfaceUtils.lua +0 -43
  35. package/src/Shared/Interface/TieSignalConnection.lua +0 -89
  36. package/src/Shared/Interface/TieSignalInterface.lua +0 -61
  37. /package/src/Shared/{Implementation → Members/Properties}/TiePropertyImplementation.lua +0 -0
  38. /package/src/Shared/{Implementation → Members/Properties}/TiePropertyImplementationUtils.lua +0 -0
@@ -0,0 +1,708 @@
1
+ --[=[
2
+ Constructs a new interface declaration which allows for interface usage
3
+ between both Roblox API users and OOP users, as well as without accessing a
4
+ [ServiceBag].
5
+
6
+ Also allows for extensibility via implementing interfaces.
7
+
8
+ ```lua
9
+ local require = require(script.Parent.loader).load(script)
10
+
11
+ local TieDefinition = require("TieDefinition")
12
+
13
+ return TieDefinition.new("GlobalLeaderboard", {
14
+ -- Modification
15
+ [TieDefinition.Realms.SERVER] = {
16
+ RemoveAllEntries = TieDefinition.Types.METHOD;
17
+ SetEntryValueForUserId = TieDefinition.Types.METHOD;
18
+ IncrementEntryForUserId = TieDefinition.Types.METHOD;
19
+ CreateEntry = TieDefinition.Types.METHOD;
20
+ };
21
+
22
+ -- List
23
+ ObserveEntriesBrio = TieDefinition.Types.METHOD;
24
+ GetEntryList = TieDefinition.Types.METHOD;
25
+
26
+ -- Single
27
+ ObserveEntryByUserId = TieDefinition.Types.METHOD;
28
+ GetEntryForUserId = TieDefinition.Types.METHOD;
29
+
30
+ -- Plural
31
+ ObserveEntriesByUserIdBrio = TieDefinition.Types.METHOD;
32
+ GetEntriesForUserId = TieDefinition.Types.METHOD;
33
+
34
+ -- Rendering
35
+ ObserveTopCount = TieDefinition.Types.METHOD;
36
+ ObserveFormatType = TieDefinition.Types.METHOD;
37
+ ObserveTitleTranslationKey = TieDefinition.Types.METHOD;
38
+ ObserveEntryTranslationKey = TieDefinition.Types.METHOD;
39
+ })
40
+ ```
41
+
42
+ @class TieDefinition
43
+ ]=]
44
+
45
+ local require = require(script.Parent.loader).load(script)
46
+
47
+ local Brio = require("Brio")
48
+ local Maid = require("Maid")
49
+ local Observable = require("Observable")
50
+ local Rx = require("Rx")
51
+ local RxBrioUtils = require("RxBrioUtils")
52
+ local RxCollectionServiceUtils = require("RxCollectionServiceUtils")
53
+ local RxInstanceUtils = require("RxInstanceUtils")
54
+ local RxStateStackUtils = require("RxStateStackUtils")
55
+ local String = require("String")
56
+ local Symbol = require("Symbol")
57
+ local Table = require("Table")
58
+ local TieImplementation = require("TieImplementation")
59
+ local TieInterface = require("TieInterface")
60
+ local TieMethodDefinition = require("TieMethodDefinition")
61
+ local TiePropertyDefinition = require("TiePropertyDefinition")
62
+ local TieRealms = require("TieRealms")
63
+ local TieRealmUtils = require("TieRealmUtils")
64
+ local TieSignalDefinition = require("TieSignalDefinition")
65
+ local ValueObject = require("ValueObject")
66
+
67
+ local UNSET_VALUE = Symbol.named("unsetValue")
68
+
69
+ local TieDefinition = {}
70
+ TieDefinition.ClassName = "TieDefinition"
71
+ TieDefinition.__index = TieDefinition
72
+
73
+ TieDefinition.Types = Table.readonly({
74
+ METHOD = Symbol.named("method");
75
+ SIGNAL = Symbol.named("signal");
76
+ PROPERTY = Symbol.named("property"); -- will default to nil
77
+ })
78
+
79
+ TieDefinition.Realms = TieRealms
80
+
81
+ --[=[
82
+ Constructs a new TieDefinition with the given members
83
+
84
+ @param definitionName string
85
+ @param members any
86
+ @return TieDefinition
87
+ ]=]
88
+ function TieDefinition.new(definitionName, members)
89
+ local self = setmetatable({}, TieDefinition)
90
+
91
+ self._definitionName = assert(definitionName, "No definitionName")
92
+ self._memberMap = {}
93
+ self._defaultTieRealm = TieRealms.SHARED
94
+
95
+ -- Start in shared world
96
+ self:_addMembers(members, TieRealms.SHARED)
97
+
98
+ self.Server = setmetatable({
99
+ _defaultTieRealm = TieRealms.SERVER
100
+ }, {
101
+ __index = self;
102
+ })
103
+
104
+ self.Client = setmetatable({
105
+ _defaultTieRealm = TieRealms.CLIENT
106
+ }, {
107
+ __index = self;
108
+ })
109
+
110
+ return self
111
+ end
112
+
113
+ function TieDefinition:_addMembers(members, realm)
114
+ for memberName, memberTypeOrDefaultValue in pairs(members) do
115
+ if TieRealmUtils.isTieRealm(memberName) then
116
+
117
+ self:_addMembers(memberTypeOrDefaultValue, memberName)
118
+ elseif type(memberName) == "string" then
119
+ self:_addMember(memberName, memberTypeOrDefaultValue, realm)
120
+ else
121
+ error(string.format("[TieDefinition] - Bad memberName %q, expected either string or TieRealm.", tostring(memberName)))
122
+ end
123
+ end
124
+ end
125
+
126
+ function TieDefinition:_addMember(memberName, memberTypeOrDefaultValue, realm)
127
+ if memberTypeOrDefaultValue == TieDefinition.Types.METHOD then
128
+ self._memberMap[memberName] = TieMethodDefinition.new(self, memberName, realm)
129
+ elseif memberTypeOrDefaultValue == TieDefinition.Types.SIGNAL then
130
+ self._memberMap[memberName] = TieSignalDefinition.new(self, memberName, realm)
131
+ elseif memberTypeOrDefaultValue == TieDefinition.Types.PROPERTY then
132
+ self._memberMap[memberName] = TiePropertyDefinition.new(self, memberName, nil, realm)
133
+ else
134
+ self._memberMap[memberName] = TiePropertyDefinition.new(self, memberName, memberTypeOrDefaultValue, realm)
135
+ end
136
+ end
137
+
138
+ --[=[
139
+ Gets all valid interfaces for this adornee
140
+ @param adornee Instance
141
+ @param tieRealm TieRealm?
142
+ @return { TieInterface }
143
+ ]=]
144
+ function TieDefinition:GetImplementations(adornee: Instance, tieRealm)
145
+ assert(typeof(adornee) == "Instance", "Bad adornee")
146
+ assert(TieRealmUtils.isTieRealm(tieRealm) or tieRealm == nil, "Bad tieRealm")
147
+
148
+ tieRealm = tieRealm or self._defaultTieRealm
149
+
150
+ local implementations = {}
151
+
152
+ for _, item in pairs(self:GetImplementationParents(adornee, tieRealm)) do
153
+ table.insert(implementations, TieInterface.new(self, item, nil, tieRealm))
154
+ end
155
+
156
+ return implementations
157
+ end
158
+
159
+ function TieDefinition:GetImplClass()
160
+ return "Camera"
161
+ end
162
+
163
+ function TieDefinition:GetImplementationParents(adornee, tieRealm)
164
+ assert(typeof(adornee) == "Instance", "Bad adornee")
165
+ assert(TieRealmUtils.isTieRealm(tieRealm) or tieRealm == nil, "Bad tieRealm")
166
+
167
+ tieRealm = tieRealm or self._defaultTieRealm
168
+
169
+ local validContainerNameSet = self:GetValidContainerNameSet(tieRealm)
170
+
171
+ local implParents = {}
172
+
173
+ for _, implParent in pairs(adornee:GetChildren()) do
174
+ if validContainerNameSet[implParent.Name] then
175
+ if self:IsImplementation(implParent) then
176
+ table.insert(implParents, implParent)
177
+ end
178
+ end
179
+ end
180
+
181
+ return implParents
182
+ end
183
+
184
+ --[=[
185
+ Observes all the children implementations for this adornee
186
+
187
+ @param adornee Instance
188
+ @param tieRealm TieRealm?
189
+ @return Observable<Brio<TieInterface>>
190
+ ]=]
191
+ function TieDefinition:ObserveChildrenBrio(adornee: Instance, tieRealm)
192
+ assert(typeof(adornee) == "Instance", "Bad adornee")
193
+ assert(TieRealmUtils.isTieRealm(tieRealm) or tieRealm == nil, "Bad tieRealm")
194
+
195
+ return RxInstanceUtils.observeChildrenBrio(adornee):Pipe({
196
+ RxBrioUtils.flatMapBrio(function(child)
197
+ return self:ObserveBrio(child, tieRealm)
198
+ end)
199
+ })
200
+ end
201
+
202
+ --[=[
203
+ Promises the implementation
204
+
205
+ @param adornee Adornee
206
+ @param tieRealm TieRealm?
207
+ @return Promise<TieInterface>
208
+ ]=]
209
+ function TieDefinition:Promise(adornee, tieRealm)
210
+ assert(typeof(adornee) == "Instance", "Bad adornee")
211
+ assert(TieRealmUtils.isTieRealm(tieRealm) or tieRealm == nil, "Bad tieRealm")
212
+
213
+ -- TODO: Support cancellation cleanup here.
214
+
215
+ return Rx.toPromise(self:Observe(adornee, tieRealm):Pipe({
216
+ Rx.where(function(value)
217
+ return value ~= nil
218
+ end)
219
+ }))
220
+ end
221
+
222
+ --[=[
223
+ Gets all valid interfaces for this adornee's children
224
+
225
+ @param adornee Instance
226
+ @param tieRealm TieRealm?
227
+ @return { TieInterface }
228
+ ]=]
229
+ function TieDefinition:GetChildren(adornee: Instance, tieRealm)
230
+ assert(TieRealmUtils.isTieRealm(tieRealm) or tieRealm == nil, "Bad tieRealm")
231
+ assert(typeof(adornee) == "Instance", "Bad adornee")
232
+
233
+ local implementations = {}
234
+
235
+ -- TODO: Make this faster
236
+ for _, item in pairs(adornee:GetChildren()) do
237
+ for _, option in pairs(self:GetImplementations(item, tieRealm)) do
238
+ table.insert(implementations, option)
239
+ end
240
+ end
241
+
242
+ return implementations
243
+ end
244
+
245
+ --[=[
246
+ Finds the implementation on the adornee. Alais for [FindFirstImplementation]
247
+
248
+ @param adornee Adornee
249
+ @param tieRealm TieRealm?
250
+ @return TieInterface | nil
251
+ ]=]
252
+ function TieDefinition:Find(adornee: Instance, tieRealm)
253
+ assert(typeof(adornee) == "Instance", "Bad adornee")
254
+ assert(TieRealmUtils.isTieRealm(tieRealm) or tieRealm == nil, "Bad tieRealm")
255
+
256
+ return self:FindFirstImplementation(adornee, tieRealm)
257
+ end
258
+
259
+ --[=[
260
+ Observes all implementations that are tagged with the given tag name
261
+
262
+ @param tagName string
263
+ @param tieRealm TieRealm?
264
+ @return TieInterface | nil
265
+ ]=]
266
+ function TieDefinition:ObserveAllTaggedBrio(tagName, tieRealm)
267
+ assert(type(tagName) == "string", "Bad tagName")
268
+ assert(TieRealmUtils.isTieRealm(tieRealm) or tieRealm == nil, "Bad tieRealm")
269
+
270
+ return RxCollectionServiceUtils.observeTaggedBrio(tagName):Pipe({
271
+ RxBrioUtils.flatMapBrio(function(instance)
272
+ return self:ObserveBrio(instance, tieRealm)
273
+ end)
274
+ })
275
+ end
276
+
277
+ --[=[
278
+ Finds the first valid interfaces for this adornee
279
+ @param adornee Instance
280
+ @param tieRealm TieRealm?
281
+ @return TieInterface
282
+ ]=]
283
+ function TieDefinition:FindFirstImplementation(adornee: Instance, tieRealm)
284
+ assert(typeof(adornee) == "Instance", "Bad adornee")
285
+ assert(TieRealmUtils.isTieRealm(tieRealm) or tieRealm == nil, "Bad tieRealm")
286
+
287
+ tieRealm = tieRealm or self._defaultTieRealm
288
+
289
+ local validContainerNameSet = self:GetValidContainerNameSet(tieRealm)
290
+ for _, item in pairs(adornee:GetChildren()) do
291
+ if validContainerNameSet[item.Name] then
292
+ if self:IsImplementation(item, tieRealm) then
293
+ return TieInterface.new(self, item, nil, tieRealm)
294
+ end
295
+ end
296
+ end
297
+
298
+ return nil
299
+ end
300
+
301
+ --[=[
302
+ Returns true if the adornee implements the interface, and false otherwise.
303
+ @param adornee Instance
304
+ @param tieRealm TieRealm?
305
+ @return boolean
306
+ ]=]
307
+ function TieDefinition:HasImplementation(adornee: Instance, tieRealm)
308
+ assert(typeof(adornee) == "Instance", "Bad adornee")
309
+ assert(TieRealmUtils.isTieRealm(tieRealm) or tieRealm == nil, "Bad tieRealm")
310
+
311
+ tieRealm = tieRealm or self._defaultTieRealm
312
+
313
+ -- TODO: Maybe something faster
314
+ for containerName, _ in pairs(self:GetValidContainerNameSet(tieRealm)) do
315
+ local implParent = adornee:FindFirstChild(containerName)
316
+ if not implParent then
317
+ continue
318
+ end
319
+
320
+ if self:IsImplementation(implParent, tieRealm) then
321
+ return true
322
+ end
323
+ end
324
+
325
+ return false
326
+ end
327
+
328
+ --[=[
329
+ Observes whether the adornee implements the interface.
330
+ @param adornee Instance
331
+ @param tieRealm TieRealm?
332
+ @return Observable<boolean>>
333
+ ]=]
334
+ function TieDefinition:ObserveIsImplemented(adornee: Instance, tieRealm): boolean
335
+ assert(typeof(adornee) == "Instance", "Bad adornee")
336
+ assert(TieRealmUtils.isTieRealm(tieRealm) or tieRealm == nil, "Bad tieRealm")
337
+
338
+ return self:ObserveLastImplementationBrio(adornee, tieRealm)
339
+ :Pipe({
340
+ RxBrioUtils.map(function(result)
341
+ return result and true or false
342
+ end);
343
+ RxBrioUtils.emitOnDeath(false);
344
+ Rx.defaultsTo(false);
345
+ Rx.distinct();
346
+ })
347
+ end
348
+
349
+ --[=[
350
+ Observes whether the implParent is a valid implementation
351
+ @param implParent Instance
352
+ @param tieRealm TieRealm?
353
+ @return Observable<boolean>>
354
+ ]=]
355
+ function TieDefinition:ObserveIsImplementation(implParent: Instance, tieRealm)
356
+ assert(typeof(implParent) == "Instance", "Bad implParent")
357
+ assert(TieRealmUtils.isTieRealm(tieRealm) or tieRealm == nil, "Bad tieRealm")
358
+
359
+ tieRealm = tieRealm or self._defaultTieRealm
360
+
361
+ return self:_observeImplementation(implParent, tieRealm)
362
+ :Pipe({
363
+ RxBrioUtils.map(function(result)
364
+ return result and true or false
365
+ end);
366
+ RxBrioUtils.emitOnDeath(false);
367
+ Rx.defaultsTo(false);
368
+ Rx.distinct();
369
+ })
370
+ end
371
+
372
+ --[=[
373
+ Observes whether the implParent is a valid implementation on the given adornee
374
+ @param implParent Instance
375
+ @param adornee Instance
376
+ @param tieRealm TieRealm?
377
+ @return Observable<boolean>>
378
+ ]=]
379
+ function TieDefinition:ObserveIsImplementedOn(implParent: Instance, adornee: Instance, tieRealm)
380
+ assert(typeof(implParent) == "Instance", "Bad implParent")
381
+ assert(typeof(adornee) == "Instance", "Bad adornee")
382
+ assert(TieRealmUtils.isTieRealm(tieRealm) or tieRealm == nil, "Bad tieRealm")
383
+
384
+ tieRealm = tieRealm or self._defaultTieRealm
385
+
386
+ return RxInstanceUtils.observePropertyBrio(implParent, "Parent", function(parent)
387
+ return parent == adornee
388
+ end):Pipe({
389
+ RxBrioUtils.switchMapBrio(function()
390
+ return self:_observeImplementation(implParent, tieRealm)
391
+ end);
392
+ RxBrioUtils.map(function(result)
393
+ return result and true or false
394
+ end);
395
+ RxBrioUtils.emitOnDeath(false);
396
+ Rx.defaultsTo(false);
397
+ Rx.distinct();
398
+ })
399
+ end
400
+
401
+ --[=[
402
+ Observes a valid implementation wrapped in a brio if it exists.
403
+
404
+ @param adornee Instance
405
+ @param tieRealm TieRealm?
406
+ @return Observable<Brio<TieImplementation<T>>>
407
+ ]=]
408
+ function TieDefinition:ObserveBrio(adornee: Instance, tieRealm)
409
+ assert(typeof(adornee) == "Instance", "Bad adornee")
410
+ assert(TieRealmUtils.isTieRealm(tieRealm) or tieRealm == nil, "Bad tieRealm")
411
+
412
+ tieRealm = tieRealm or self._defaultTieRealm
413
+
414
+ return self:ObserveValidContainerChildrenBrio(adornee, tieRealm)
415
+ :Pipe({
416
+ RxBrioUtils.switchMapBrio(function(implParent)
417
+ return self:_observeImplementation(implParent, tieRealm)
418
+ end);
419
+ RxBrioUtils.onlyLastBrioSurvives();
420
+ })
421
+ end
422
+
423
+ --[=[
424
+ Observes a valid implementation if it exists, or nil
425
+
426
+ @param adornee Instance
427
+ @param tieRealm TieRealm?
428
+ @return Observable<TieImplementation<T> | nil>>
429
+ ]=]
430
+ function TieDefinition:Observe(adornee: Instance, tieRealm)
431
+ assert(typeof(adornee) == "Instance", "Bad adornee")
432
+ assert(TieRealmUtils.isTieRealm(tieRealm) or tieRealm == nil, "Bad tieRealm")
433
+
434
+ return self:ObserveBrio(adornee, tieRealm):Pipe({
435
+ RxStateStackUtils.topOfStack();
436
+ })
437
+ end
438
+
439
+ TieDefinition.ObserveLastImplementation = TieDefinition.Observe
440
+ TieDefinition.ObserveLastImplementationBrio = TieDefinition.ObserveBrio
441
+
442
+ --[=[
443
+ Observes valid implementations wrapped in a brio if it exists.
444
+ @param adornee Instance
445
+ @param tieRealm TieRealm?
446
+ @return Observable<Brio<TieImplementation<T>>>
447
+ ]=]
448
+ function TieDefinition:ObserveImplementationsBrio(adornee: Instance, tieRealm)
449
+ assert(typeof(adornee) == "Instance", "Bad adornee")
450
+ assert(TieRealmUtils.isTieRealm(tieRealm) or tieRealm == nil, "Bad tieRealm")
451
+
452
+ tieRealm = tieRealm or self._defaultTieRealm
453
+
454
+ return self:ObserveValidContainerChildrenBrio(adornee, tieRealm)
455
+ :Pipe({
456
+ RxBrioUtils.flatMapBrio(function(implParent)
457
+ return self:_observeImplementation(implParent, tieRealm)
458
+ end)
459
+ })
460
+ end
461
+
462
+ function TieDefinition:ObserveValidContainerChildrenBrio(adornee, tieRealm)
463
+ assert(typeof(adornee) == "Instance", "Bad adornee")
464
+ assert(TieRealmUtils.isTieRealm(tieRealm), "Bad tieRealm")
465
+
466
+ local validContainerNameSet = self:GetValidContainerNameSet(tieRealm)
467
+
468
+ return RxInstanceUtils.observeChildrenBrio(adornee, function(value)
469
+ -- Just assume our name doesn't change
470
+ return value:IsA(self:GetImplClass()) and validContainerNameSet[value.Name] and true or false
471
+ end)
472
+ end
473
+
474
+ function TieDefinition:_observeImplementation(implParent, tieRealm)
475
+ assert(TieRealmUtils.isTieRealm(tieRealm), "Bad tieRealm")
476
+
477
+ return Observable.new(function(sub)
478
+ -- Bind to all children, instead of individually. This is a
479
+ -- performance gain.
480
+
481
+ local maid = Maid.new()
482
+
483
+ local update
484
+ do
485
+ local isImplemented = maid:Add(ValueObject.new(UNSET_VALUE))
486
+
487
+ maid:GiveTask(isImplemented.Changed:Connect(function()
488
+ maid._brio = nil
489
+
490
+ if not sub:IsPending() then
491
+ return
492
+ end
493
+
494
+ if isImplemented.Value then
495
+ local brio = Brio.new(TieInterface.new(self, implParent, nil, tieRealm))
496
+ sub:Fire(brio)
497
+ maid._brio = brio
498
+ else
499
+ maid._brio = nil
500
+ end
501
+ end))
502
+
503
+ function update()
504
+ isImplemented.Value = self:IsImplementation(implParent, tieRealm)
505
+ end
506
+ end
507
+
508
+ maid:GiveTask(implParent.ChildAdded:Connect(function(child)
509
+ maid[child] = child:GetPropertyChangedSignal("Name"):Connect(update)
510
+ update()
511
+ end))
512
+
513
+ for memberName, member in pairs(self._memberMap) do
514
+ if not member:IsAllowedOnInterface(tieRealm) then
515
+ continue
516
+ end
517
+
518
+ if member.ClassName == "TiePropertyDefinition" then
519
+ maid:GiveTask(implParent:GetAttributeChangedSignal(memberName):Connect(update))
520
+ end
521
+ end
522
+
523
+ maid:GiveTask(implParent.ChildRemoved:Connect(function(child)
524
+ maid[child] = nil
525
+ update()
526
+ end))
527
+
528
+ for _, child in pairs(implParent:GetChildren()) do
529
+ maid[child] = child:GetPropertyChangedSignal("Name"):Connect(update)
530
+ end
531
+
532
+ update()
533
+
534
+ return maid
535
+ end)
536
+ end
537
+
538
+ --[=[
539
+ Ensures implementation of the object, binding table values and Lua OOP objects
540
+ to Roblox objects that can be invoked generally.
541
+
542
+ ```lua
543
+
544
+ ```
545
+
546
+ @param adornee Instance -- Adornee to implement interface on
547
+ @param implementer table? -- Table with all interface values or nil
548
+ @param tieRealm TieRealm?
549
+ @return TieImplementation<T>
550
+ ]=]
551
+ function TieDefinition:Implement(adornee: Instance, implementer, tieRealm)
552
+ assert(typeof(adornee) == "Instance", "Bad adornee")
553
+ assert(type(implementer) == "table" or implementer == nil, "Bad implementer")
554
+ assert(TieRealmUtils.isTieRealm(tieRealm) or tieRealm == nil, "Bad tieRealm")
555
+
556
+ tieRealm = tieRealm or self._defaultTieRealm
557
+
558
+ return TieImplementation.new(self, adornee, implementer, tieRealm)
559
+ end
560
+
561
+ --[=[
562
+ Gets an interface to the tie definition. Not this can be done
563
+ on any Roblox instance. If the instance does not implement the interface,
564
+ invoking interface methods, or querying the interface will result
565
+ in errors.
566
+
567
+ ```tip
568
+ Probably use :Find() instead of Get, since this always returns an interface.
569
+ ```
570
+
571
+ @param adornee Instance -- Adornee to get interface on
572
+ @param tieRealm TieRealm?
573
+ @return TieInterface<T>
574
+ ]=]
575
+ function TieDefinition:Get(adornee: Instance, tieRealm)
576
+ assert(typeof(adornee) == "Instance", "Bad adornee")
577
+ assert(TieRealmUtils.isTieRealm(tieRealm) or tieRealm == nil, "Bad tieRealm")
578
+
579
+ tieRealm = tieRealm or self._defaultTieRealm
580
+
581
+ return TieInterface.new(self, nil, adornee, tieRealm)
582
+ end
583
+
584
+ --[=[
585
+ Gets the name of the definition
586
+ @return string
587
+ ]=]
588
+ function TieDefinition:GetName(): string
589
+ return self._definitionName
590
+ end
591
+
592
+ --[=[
593
+ Gets the valid container name set for the tie definition
594
+
595
+ @param tieRealm TieRealm
596
+ @return { [string]: boolean }
597
+ ]=]
598
+ function TieDefinition:GetValidContainerNameSet(tieRealm)
599
+ -- TODO: Still generate unique datamodel key here?
600
+
601
+ if tieRealm == TieRealms.CLIENT then
602
+ -- Shared implements both...
603
+ return {
604
+ [self._definitionName .. "Client"] = true;
605
+ [self._definitionName .. "Shared"] = true;
606
+ }
607
+ elseif tieRealm == TieRealms.SERVER then
608
+ return {
609
+ [self._definitionName] = true;
610
+ [self._definitionName .. "Shared"] = true;
611
+ }
612
+ elseif tieRealm == TieRealms.SHARED then
613
+ -- Technically on the implementation shared is very strict,
614
+ -- but we allow any calls here for discovery
615
+ return {
616
+ [self._definitionName] = true;
617
+ [self._definitionName .. "Client"] = true;
618
+ [self._definitionName .. "Shared"] = true;
619
+ }
620
+ end
621
+ end
622
+
623
+ --[=[
624
+ Gets a container name for a new container. See [GetValidContainerNameSet]
625
+ for the full set of valid container names for the tie definition.
626
+
627
+ @param tieRealm TieRealm
628
+ @return string
629
+ ]=]
630
+ function TieDefinition:GetNewContainerName(tieRealm): string
631
+ assert(TieRealmUtils.isTieRealm(tieRealm), "Bad tieRealm")
632
+
633
+ -- TODO: Handle server/actor
634
+
635
+ if tieRealm == TieRealms.CLIENT then
636
+ return self._definitionName .. "Client"
637
+ elseif tieRealm == TieRealms.SERVER then
638
+ return self._definitionName
639
+ elseif tieRealm == TieRealms.SHARED then
640
+ -- Shared contains both server and client
641
+ return self._definitionName .. "Shared"
642
+ else
643
+ error("Bad tieRealm")
644
+ end
645
+ end
646
+
647
+ function TieDefinition:GetMemberMap()
648
+ return self._memberMap
649
+ end
650
+
651
+ --[=[
652
+ Returns true if the implParent is an implementation
653
+
654
+ @param implParent Instance
655
+ @param tieRealm TieRealm? -- Optional tie realm
656
+ @return boolean
657
+ ]=]
658
+ function TieDefinition:IsImplementation(implParent, tieRealm)
659
+ assert(typeof(implParent) == "Instance", "Bad implParent")
660
+ assert(TieRealmUtils.isTieRealm(tieRealm) or tieRealm == nil, "Bad tieRealm")
661
+
662
+ tieRealm = tieRealm or self._defaultTieRealm
663
+
664
+ local attributes = implParent:GetAttributes()
665
+ local children = {}
666
+ for _, item in pairs(implParent:GetChildren()) do
667
+ children[item.Name] = item
668
+ end
669
+
670
+ for memberName, member in pairs(self._memberMap) do
671
+ if not member:IsRequiredForInterface(tieRealm) then
672
+ continue
673
+ end
674
+
675
+ local found = children[memberName]
676
+ if not found then
677
+ if member.ClassName == "TiePropertyDefinition" then
678
+ if attributes[memberName] == nil then
679
+ return false
680
+ else
681
+ continue
682
+ end
683
+ end
684
+
685
+ return false
686
+ end
687
+
688
+ if member.ClassName == "TieMethodDefinition" then
689
+ if not found:IsA("BindableFunction") then
690
+ return false
691
+ end
692
+ elseif member.ClassName == "TieSignalDefinition" then
693
+ if not found:IsA("BindableEvent") then
694
+ return false
695
+ end
696
+ elseif member.ClassName == "TiePropertyDefinition" then
697
+ if not (found:IsA("BindableFunction") or String.endsWith(found.ClassName, "Value")) then
698
+ return false
699
+ end
700
+ else
701
+ error("[TieDefinition.IsImplementation] - Unknown member type")
702
+ end
703
+ end
704
+
705
+ return true
706
+ end
707
+
708
+ return TieDefinition