@flamework-experimental/core 2.0.0-alpha.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 (65) hide show
  1. package/README.md +66 -0
  2. package/flamework.build +17 -0
  3. package/out/dependency.d.ts +18 -0
  4. package/out/dependency.luau +32 -0
  5. package/out/flamework.d.ts +78 -0
  6. package/out/flamework.luau +138 -0
  7. package/out/index.d.ts +28 -0
  8. package/out/init.luau +40 -0
  9. package/out/injectable.d.ts +13 -0
  10. package/out/injectable.luau +23 -0
  11. package/out/lifecycle/lifecycleInterfaces.d.ts +94 -0
  12. package/out/lifecycle/lifecycleInterfaces.luau +55 -0
  13. package/out/lifecycle/lifecyclePlugin.d.ts +85 -0
  14. package/out/lifecycle/lifecyclePlugin.luau +424 -0
  15. package/out/modding.d.ts +171 -0
  16. package/out/modding.luau +66 -0
  17. package/out/module/defaultModule.d.ts +5 -0
  18. package/out/module/defaultModule.luau +28 -0
  19. package/out/module/module.d.ts +78 -0
  20. package/out/module/module.luau +811 -0
  21. package/out/module/moduleBuilder.d.ts +88 -0
  22. package/out/module/moduleBuilder.luau +169 -0
  23. package/out/module/moduleDefinition.d.ts +105 -0
  24. package/out/module/moduleDefinition.luau +79 -0
  25. package/out/module/moduleHooks.d.ts +23 -0
  26. package/out/module/moduleHooks.luau +19 -0
  27. package/out/module/providerRegistration.d.ts +22 -0
  28. package/out/module/providerRegistration.luau +80 -0
  29. package/out/module/scopes.d.ts +40 -0
  30. package/out/module/scopes.luau +159 -0
  31. package/out/plugin/pluginDefinition.d.ts +128 -0
  32. package/out/plugin/pluginDefinition.luau +61 -0
  33. package/out/prelude.d.ts +8 -0
  34. package/out/prelude.luau +14 -0
  35. package/out/provider.d.ts +27 -0
  36. package/out/provider.luau +26 -0
  37. package/out/reflect.d.ts +60 -0
  38. package/out/reflect.luau +310 -0
  39. package/out/serialization/types.d.ts +86 -0
  40. package/out/serialization/types.luau +9 -0
  41. package/out/utility/constructors.d.ts +4 -0
  42. package/out/utility/constructors.luau +12 -0
  43. package/out/utility/convertConciseDependencyInfo.d.ts +5 -0
  44. package/out/utility/convertConciseDependencyInfo.luau +29 -0
  45. package/out/utility/getClassImplements.d.ts +7 -0
  46. package/out/utility/getClassImplements.luau +27 -0
  47. package/out/utility/getClassesInPath.d.ts +23 -0
  48. package/out/utility/getClassesInPath.luau +91 -0
  49. package/out/utility/globs.d.ts +9 -0
  50. package/out/utility/globs.luau +64 -0
  51. package/out/utility/metadata.d.ts +12 -0
  52. package/out/utility/metadata.luau +43 -0
  53. package/out/utility/pathRoot.d.ts +17 -0
  54. package/out/utility/pathRoot.luau +105 -0
  55. package/out/utility/recycleThread.d.ts +1 -0
  56. package/out/utility/recycleThread.luau +31 -0
  57. package/out/utility/runtimeConfig.d.ts +59 -0
  58. package/out/utility/runtimeConfig.luau +25 -0
  59. package/out/utility/tsImport.d.ts +6 -0
  60. package/out/utility/tsImport.luau +15 -0
  61. package/out/utility/types.d.ts +16 -0
  62. package/out/utility/types.luau +22 -0
  63. package/out/utility/writable.d.ts +4 -0
  64. package/out/utility/writable.luau +2 -0
  65. package/package.json +33 -0
@@ -0,0 +1,811 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local Reflect = TS.import(script, script.Parent.Parent, "reflect").Reflect
4
+ local convertConciseDependencyInfo = TS.import(script, script.Parent.Parent, "utility", "convertConciseDependencyInfo").convertConciseDependencyInfo
5
+ local getClassImplements = TS.import(script, script.Parent.Parent, "utility", "getClassImplements").getClassImplements
6
+ local getClassesInPath = TS.import(script, script.Parent.Parent, "utility", "getClassesInPath").getClassesInPath
7
+ local getClassesInGlob = TS.import(script, script.Parent.Parent, "utility", "globs").getClassesInGlob
8
+ local clearDefaultModule = TS.import(script, script.Parent, "defaultModule").clearDefaultModule
9
+ local HookPriority = TS.import(script, script.Parent, "moduleHooks").HookPriority
10
+ local _providerRegistration = TS.import(script, script.Parent, "providerRegistration")
11
+ local getProviderClassId = _providerRegistration.getProviderClassId
12
+ local getProviderClassScope = _providerRegistration.getProviderClassScope
13
+ local normalizeProviderConfig = _providerRegistration.normalizeProviderConfig
14
+ local _scopes = TS.import(script, script.Parent, "scopes")
15
+ local NO_CONDITION = _scopes.NO_CONDITION
16
+ local describeConditions = _scopes.describeConditions
17
+ local hasCondition = _scopes.hasCondition
18
+ local holdsCondition = _scopes.holdsCondition
19
+ local holdsEveryCondition = _scopes.holdsEveryCondition
20
+ --* What a lookup through a module and its imports found: where, and which class when it is a class provider.
21
+ local ModuleInitState
22
+ do
23
+ local _inverse = {}
24
+ ModuleInitState = setmetatable({}, {
25
+ __index = _inverse,
26
+ })
27
+ ModuleInitState.Created = 0
28
+ _inverse[0] = "Created"
29
+ ModuleInitState.PreIgniting = 1
30
+ _inverse[1] = "PreIgniting"
31
+ ModuleInitState.Igniting = 2
32
+ _inverse[2] = "Igniting"
33
+ ModuleInitState.Ignited = 3
34
+ _inverse[3] = "Ignited"
35
+ ModuleInitState.Extinguishing = 4
36
+ _inverse[4] = "Extinguishing"
37
+ ModuleInitState.Extinguished = 5
38
+ _inverse[5] = "Extinguished"
39
+ end
40
+ local MODULE_ID = "$:module/module@Module"
41
+ --* The class-and-path registration forms take their options as a separate argument; here they join the config.
42
+ local function withOptions(config, options)
43
+ local _result
44
+ if options ~= nil then
45
+ local _object = {}
46
+ if config then
47
+ for _k, _v in config do
48
+ _object[_k] = _v
49
+ end
50
+ end
51
+ for _k, _v in options do
52
+ _object[_k] = _v
53
+ end
54
+ _result = _object
55
+ else
56
+ _result = config
57
+ end
58
+ return _result
59
+ end
60
+ local function createModuleInstantiation(state, options)
61
+ --* The module's own scope condition, from `ignite`. Applies to everything it registers.
62
+ local moduleScope = if options ~= nil and (options.activeIn ~= nil or options.inactiveIn ~= nil) then {
63
+ activeIn = options.activeIn,
64
+ inactiveIn = options.inactiveIn,
65
+ } else NO_CONDITION
66
+ --* Every registration: the definition's providers, followed by whatever the plugins register.
67
+ local _array = {}
68
+ local _length = #_array
69
+ local _array_1 = state.providers
70
+ table.move(_array_1, 1, #_array_1, _length + 1, _array)
71
+ local registered = _array
72
+ --* The registrations whose conditions hold, decided once every plugin has been set up.
73
+ local providers = {}
74
+ --* The registrations left out, by id, with the conditions that were judged: for the error a miss gets.
75
+ local skipped = {}
76
+ --* Modules searched after this one's own providers, in order. Ignited before this one, and extinguished after.
77
+ local _result = options
78
+ if _result ~= nil then
79
+ _result = _result.imports
80
+ end
81
+ local _condition = _result
82
+ if _condition == nil then
83
+ _condition = {}
84
+ end
85
+ local imports = _condition
86
+ --* Modules that import this one. They go down before this one does.
87
+ local importers = {}
88
+ local instantiatedProviders = {}
89
+ --* Objects handed over by `provideInstance`, attached to their interfaces once every plugin is set up.
90
+ local providedInstances = {}
91
+ local observers = {}
92
+ local hooks = {}
93
+ local includedPlugins = {}
94
+ local filledSlots = {}
95
+ local temporaryInstances = {}
96
+ local moduleInitState = ModuleInitState.Created
97
+ local switchInitState = function(from, to)
98
+ if moduleInitState ~= from then
99
+ error(`module '{state.debugName}' is in invalid state when transitioning to '{ModuleInitState[to]}', got '{ModuleInitState[moduleInitState]}' when '{ModuleInitState[from]}' was expected.`)
100
+ end
101
+ moduleInitState = to
102
+ end
103
+ local assertAlive = function(action)
104
+ if moduleInitState >= ModuleInitState.Extinguishing then
105
+ error(`module '{state.debugName}' has been extinguished, cannot {action}`)
106
+ end
107
+ end
108
+ local assertNotProvided = function(injectionId)
109
+ local _injectionId = injectionId
110
+ if instantiatedProviders[_injectionId] ~= nil then
111
+ error(`module '{state.debugName}': provider ID was registered more than once: {injectionId}`)
112
+ end
113
+ end
114
+ local registerClassProvider = function(provider, registrationOptions)
115
+ local injectionId = getProviderClassId(provider)
116
+ local _arg0 = {
117
+ config = normalizeProviderConfig(withOptions({
118
+ type = "class",
119
+ value = provider,
120
+ }, registrationOptions)),
121
+ injectionId = injectionId,
122
+ }
123
+ table.insert(registered, _arg0)
124
+ end
125
+ --* Own metadata only, as the builder does: an undecorated subclass of a provider is skipped.
126
+ local registerProviderClasses = function(classes, registrationOptions)
127
+ for _, provider in classes do
128
+ if Reflect.hasOwnMetadata(provider, "flamework:provider") then
129
+ registerClassProvider(provider, registrationOptions)
130
+ end
131
+ end
132
+ end
133
+ --[[
134
+ *
135
+ * Sets a plugin up against this module, once. A plugin reached again -- included by the module
136
+ * and by a plugin, or by two plugins -- is skipped, and so is one that includes itself through
137
+ * another: it is marked before its setup runs, so the ring stops on the second arrival.
138
+ *
139
+ * An inclusion given a scope condition that does not hold is skipped entirely: the setup does
140
+ * not run, so nothing it would have registered exists. The plugin itself is not marked, since
141
+ * it is the inclusion that was scoped, not the plugin.
142
+
143
+ ]]
144
+ local pluginTarget
145
+ local includePlugin = function(plugin, scope)
146
+ local _plugin = plugin
147
+ if includedPlugins[_plugin] ~= nil then
148
+ return nil
149
+ end
150
+ if not holdsCondition(scope) then
151
+ return nil
152
+ end
153
+ -- The builder swaps a slotted plugin for the one in its slot, so by the time a module
154
+ -- ignites the only way to reach a second one is through a plugin. That is refused rather
155
+ -- than run: two lifecycle plugins would tick everything twice.
156
+ if plugin.slot ~= nil then
157
+ local _slot = plugin.slot
158
+ local occupant = filledSlots[_slot]
159
+ if occupant ~= nil then
160
+ error(`module '{state.debugName}': plugin '{plugin.name}' fills the '{plugin.slot}' slot that plugin '{occupant.name}' already fills; include it on the module instead`)
161
+ end
162
+ local _slot_1 = plugin.slot
163
+ local _plugin_1 = plugin
164
+ filledSlots[_slot_1] = _plugin_1
165
+ end
166
+ local _plugin_1 = plugin
167
+ includedPlugins[_plugin_1] = true
168
+ plugin.setup(pluginTarget)
169
+ end
170
+ local lookupInImports = function(injectionId)
171
+ for _, imported in imports do
172
+ local found = imported.lookupProvider(injectionId)
173
+ if found ~= nil then
174
+ return found
175
+ end
176
+ end
177
+ end
178
+ --[[
179
+ *
180
+ * Decides which registrations this ignition keeps: those whose conditions -- the module's, the
181
+ * registration's own, and the class's decorator -- all hold. Two registrations may share an id
182
+ * as long as at most one of them is kept, which is how a fake takes a real provider's place
183
+ * under one scope.
184
+ *
185
+ * A class an import already resolves to is not constructed again here: the import's instance
186
+ * answers, so the registration is dropped unless it asked to be isolated. A different class
187
+ * under the same id is kept, and answers ahead of the import's.
188
+
189
+ ]]
190
+ local activateProviders = function()
191
+ local active = {}
192
+ local seen = {}
193
+ for _, entry in registered do
194
+ local conditions = { moduleScope, entry.config, getProviderClassScope(entry.config) }
195
+ if not holdsEveryCondition(conditions) then
196
+ local _injectionId = entry.injectionId
197
+ skipped[_injectionId] = conditions
198
+ continue
199
+ end
200
+ if entry.config.type == "class" and entry.config.isolated ~= true then
201
+ local found = lookupInImports(entry.injectionId)
202
+ if found ~= nil and found.classValue == entry.config.value then
203
+ continue
204
+ end
205
+ end
206
+ assertNotProvided(entry.injectionId)
207
+ local _injectionId = entry.injectionId
208
+ if seen[_injectionId] ~= nil then
209
+ error(`module '{state.debugName}': provider ID was registered more than once: {entry.injectionId}`)
210
+ end
211
+ local _injectionId_1 = entry.injectionId
212
+ seen[_injectionId_1] = true
213
+ table.insert(active, entry)
214
+ end
215
+ providers = active
216
+ end
217
+ local registerHook = function(phase, callback, priority)
218
+ local _object = {
219
+ phase = phase,
220
+ callback = callback,
221
+ }
222
+ local _left = "priority"
223
+ local _condition_1 = priority
224
+ if _condition_1 == nil then
225
+ _condition_1 = HookPriority.Normal
226
+ end
227
+ _object[_left] = _condition_1
228
+ table.insert(hooks, _object)
229
+ end
230
+ local sortedHooks = function(phase)
231
+ -- ▼ ReadonlyArray.filter ▼
232
+ local _newValue = {}
233
+ local _callback = function(hook)
234
+ return hook.phase == phase
235
+ end
236
+ local _length_1 = 0
237
+ for _k, _v in hooks do
238
+ if _callback(_v, _k - 1, hooks) == true then
239
+ _length_1 += 1
240
+ _newValue[_length_1] = _v
241
+ end
242
+ end
243
+ -- ▲ ReadonlyArray.filter ▲
244
+ local matching = _newValue
245
+ -- `table.sort` is not stable, so hooks of equal priority are ordered by the position they
246
+ -- were registered at to keep registration order meaningful.
247
+ local order = {}
248
+ -- ▼ ReadonlyArray.forEach ▼
249
+ local _callback_1 = function(hook, index)
250
+ local _hook = hook
251
+ local _index = index
252
+ order[_hook] = _index
253
+ return order
254
+ end
255
+ for _k, _v in matching do
256
+ _callback_1(_v, _k - 1, matching)
257
+ end
258
+ -- ▲ ReadonlyArray.forEach ▲
259
+ table.sort(matching, function(a, b)
260
+ if a.priority ~= b.priority then
261
+ return a.priority < b.priority
262
+ end
263
+ local _a = a
264
+ local _exp = order[_a]
265
+ local _b = b
266
+ return _exp < order[_b]
267
+ end)
268
+ return matching
269
+ end
270
+ local module
271
+ local runHooks = function(phase)
272
+ for _, hook in sortedHooks(phase) do
273
+ hook.callback(module)
274
+ end
275
+ end
276
+ --[[
277
+ *
278
+ * Runs one step of a teardown, reporting a raise rather than letting it out, so that the steps
279
+ * after it still run: one hook or removal callback that fails must not leave the module holding
280
+ * the rest -- the guarantee the lifecycle plugin gives for its own handlers, made the module's.
281
+
282
+ ]]
283
+ local guarded = function(what, callback)
284
+ local success, err = pcall(callback)
285
+ if not success then
286
+ warn(`[Flamework] module '{state.debugName}': {what} failed: {tostring(err)}`)
287
+ end
288
+ end
289
+ --[[
290
+ *
291
+ * Attaches an object to every observer of every interface it implements, all or nothing: an
292
+ * observer that raises from its `onAdded` has the ones before it told `onRemoved`, and the
293
+ * error comes out, so a refused object is attached nowhere -- rather than left ticking in the
294
+ * lifecycle's sets, with no handle to remove it by.
295
+
296
+ ]]
297
+ local registerClassInterfaces = function(instance, kind)
298
+ local added = {}
299
+ local success, err = pcall(function()
300
+ for _, interfaceId in getClassImplements(instance) do
301
+ local interested = observers[interfaceId]
302
+ if not interested then
303
+ continue
304
+ end
305
+ for _1, observer in interested do
306
+ local _result_1 = observer.onAdded
307
+ if _result_1 ~= nil then
308
+ _result_1(instance, {
309
+ interfaceId = interfaceId,
310
+ kind = kind,
311
+ })
312
+ end
313
+ local _arg0 = { interfaceId, observer }
314
+ table.insert(added, _arg0)
315
+ end
316
+ end
317
+ end)
318
+ if not success then
319
+ -- Undone in reverse, each step guarded, so that one `onRemoved` raising does not leave
320
+ -- the rest attached; the observer's error is what comes out.
321
+ for i = #added - 1, 0, -1 do
322
+ local _binding = added[i + 1]
323
+ local interfaceId = _binding[1]
324
+ local observer = _binding[2]
325
+ guarded("undoing an attachment", function()
326
+ local _result_1 = observer.onRemoved
327
+ if _result_1 ~= nil then
328
+ _result_1 = _result_1(instance, {
329
+ interfaceId = interfaceId,
330
+ kind = kind,
331
+ })
332
+ end
333
+ return _result_1
334
+ end)
335
+ end
336
+ error(err, 0)
337
+ end
338
+ end
339
+ local unregisterClassInterfaces = function(instance, kind)
340
+ for _, interfaceId in getClassImplements(instance) do
341
+ local interested = observers[interfaceId]
342
+ if not interested then
343
+ continue
344
+ end
345
+ for _1, observer in interested do
346
+ local _result_1 = observer.onRemoved
347
+ if _result_1 ~= nil then
348
+ _result_1(instance, {
349
+ interfaceId = interfaceId,
350
+ kind = kind,
351
+ })
352
+ end
353
+ end
354
+ end
355
+ end
356
+ local resolveDependencyWithOrigin
357
+ local instantiateClassWithDependencies = function(constructor, resolve)
358
+ local dependencies = Reflect.getMetadata(constructor, "flamework:dependencies") or {}
359
+ local resolvedParameters = {}
360
+ for _, dependency in dependencies do
361
+ local _result_1 = resolve
362
+ if _result_1 ~= nil then
363
+ _result_1 = _result_1(dependency)
364
+ end
365
+ local _condition_1 = _result_1
366
+ if _condition_1 == nil then
367
+ _condition_1 = resolveDependencyWithOrigin(dependency, constructor)
368
+ end
369
+ table.insert(resolvedParameters, _condition_1)
370
+ end
371
+ return constructor.new(unpack(resolvedParameters))
372
+ end
373
+ local tryResolveDependency
374
+ tryResolveDependency = function(info, requestingOrigin)
375
+ if moduleInitState <= ModuleInitState.PreIgniting then
376
+ error(`module '{state.debugName}' is in pre-ignite phase, dependency cannot be resolved: {info.id}`)
377
+ end
378
+ -- Extinguished hooks and removal callbacks may still resolve siblings while extinguishing,
379
+ -- so only a fully extinguished module refuses.
380
+ if moduleInitState == ModuleInitState.Extinguished then
381
+ error(`module '{state.debugName}' has been extinguished, dependency cannot be resolved: {info.id}`)
382
+ end
383
+ local _id = info.id
384
+ local instantiatedProvider = instantiatedProviders[_id]
385
+ if instantiatedProvider ~= nil then
386
+ return instantiatedProvider
387
+ end
388
+ -- The Module type always refers to the current module instantiation.
389
+ if info.id == MODULE_ID then
390
+ return module
391
+ end
392
+ -- ▼ ReadonlyArray.find ▼
393
+ local _callback = function(v)
394
+ return v.injectionId == info.id
395
+ end
396
+ local _result_1
397
+ for _i, _v in providers do
398
+ if _callback(_v, _i - 1, providers) == true then
399
+ _result_1 = _v
400
+ break
401
+ end
402
+ end
403
+ -- ▲ ReadonlyArray.find ▲
404
+ local moduleProvider = _result_1
405
+ if moduleProvider then
406
+ local config = moduleProvider.config
407
+ if config.type == "class" then
408
+ local instantiatedProvider = instantiateClassWithDependencies(config.value)
409
+ local _id_1 = info.id
410
+ instantiatedProviders[_id_1] = instantiatedProvider
411
+ registerClassInterfaces(instantiatedProvider, "provider")
412
+ return instantiatedProvider
413
+ elseif config.type == "function" then
414
+ -- Function providers are not cached.
415
+ -- It is up to the provider to decide whether to cache dependency resolution, based on the injection context.
416
+ return config.callback({
417
+ injectionId = info.id,
418
+ dependencyInfo = info,
419
+ module = module,
420
+ origin = requestingOrigin,
421
+ })
422
+ elseif config.type == "alias" then
423
+ return tryResolveDependency(convertConciseDependencyInfo(config.injectionId), requestingOrigin)
424
+ end
425
+ end
426
+ -- Through the owner's resolver, so that a lazy provider is constructed by the module that
427
+ -- registered it and joins that module's interfaces, not this one's.
428
+ for _, imported in imports do
429
+ local found = imported.tryResolveDependency(info, requestingOrigin)
430
+ if found ~= nil then
431
+ return found
432
+ end
433
+ end
434
+ end
435
+ resolveDependencyWithOrigin = function(info, requestingOrigin)
436
+ local dependency = tryResolveDependency(info, requestingOrigin)
437
+ if dependency == nil then
438
+ local _result_1
439
+ if #imports > 0 then
440
+ -- ▼ ReadonlyArray.map ▼
441
+ local _newValue = table.create(#imports)
442
+ local _callback = function(v)
443
+ return v.debugName
444
+ end
445
+ for _k, _v in imports do
446
+ _newValue[_k] = _callback(_v, _k - 1, imports)
447
+ end
448
+ -- ▲ ReadonlyArray.map ▲
449
+ _result_1 = `; not found in imports [{table.concat(_newValue, ", ")}]`
450
+ else
451
+ _result_1 = ""
452
+ end
453
+ local searched = _result_1
454
+ local _id = info.id
455
+ local inactive = skipped[_id]
456
+ if inactive ~= nil then
457
+ error(`module '{state.debugName}' could not resolve dependency '{info.id}': it is registered but inactive ({describeConditions(inactive)}){searched}`)
458
+ end
459
+ error(`module '{state.debugName}' could not resolve dependency '{info.id}'{searched}`)
460
+ end
461
+ return dependency
462
+ end
463
+ local lookupProvider = function(injectionId)
464
+ -- ▼ ReadonlyArray.find ▼
465
+ local _callback = function(v)
466
+ return v.injectionId == injectionId
467
+ end
468
+ local _result_1
469
+ for _i, _v in providers do
470
+ if _callback(_v, _i - 1, providers) == true then
471
+ _result_1 = _v
472
+ break
473
+ end
474
+ end
475
+ -- ▲ ReadonlyArray.find ▲
476
+ local own = _result_1
477
+ if own ~= nil then
478
+ return {
479
+ module = module,
480
+ classValue = if own.config.type == "class" then own.config.value else nil,
481
+ }
482
+ end
483
+ -- A provided instance is an object, not a class, so nothing registered here is the same class as it.
484
+ local _injectionId = injectionId
485
+ if instantiatedProviders[_injectionId] ~= nil then
486
+ return {
487
+ module = module,
488
+ }
489
+ end
490
+ return lookupInImports(injectionId)
491
+ end
492
+ local resolveDependency = function(info)
493
+ local _arg0 = info ~= nil
494
+ assert(_arg0)
495
+ return resolveDependencyWithOrigin(convertConciseDependencyInfo(info))
496
+ end
497
+ local createClassInstance = function(constructor, config)
498
+ assertAlive("create class instances")
499
+ local _exp = constructor
500
+ local _result_1 = config
501
+ if _result_1 ~= nil then
502
+ _result_1 = _result_1.overrideDependency
503
+ end
504
+ local instance = instantiateClassWithDependencies(_exp, _result_1)
505
+ -- Attached before it is held: an observer that refuses it leaves it attached nowhere, so
506
+ -- there is nothing for the module to hold, or to release on extinguish.
507
+ registerClassInterfaces(instance, "instance")
508
+ temporaryInstances[instance] = true
509
+ return instance
510
+ end
511
+ local removeClassInstance = function(instance)
512
+ local _instance = instance
513
+ if not (temporaryInstances[_instance] ~= nil) then
514
+ return nil
515
+ end
516
+ unregisterClassInterfaces(instance, "instance")
517
+ local _instance_1 = instance
518
+ temporaryInstances[_instance_1] = nil
519
+ end
520
+ local listen = function(param, metaId, metaKey)
521
+ local _arg0 = metaId ~= nil
522
+ assert(_arg0)
523
+ assertAlive("listen for lifecycle events")
524
+ local listener
525
+ if metaKey == nil then
526
+ -- Non-shorthand
527
+ -- We create a proxy object so that we have a unique reference for this specific listener.
528
+ -- It stands in for the object without being it: a method reached through it runs with
529
+ -- the object as `self`, so what the method writes to `this` lands on the object; and
530
+ -- its `__index` is a function rather than the object, which is where the metadata walk
531
+ -- stops -- through the object it went on to the object's class, and attached the proxy
532
+ -- to every interface the class implements instead of the one named here.
533
+ local target = param
534
+ listener = setmetatable({}, {
535
+ __index = function(_, key)
536
+ local value = target[key]
537
+ if type(value) == "function" then
538
+ return function(_self, ...)
539
+ local args = { ... }
540
+ return value(target, unpack(args))
541
+ end
542
+ end
543
+ return value
544
+ end,
545
+ })
546
+ -- The class's identifier is what the object was profiled under through the old walk.
547
+ local identifier = Reflect.getMetadata(target, "identifier")
548
+ if identifier ~= nil then
549
+ Reflect.defineMetadata(listener, "identifier", identifier)
550
+ end
551
+ else
552
+ local _arg0_1 = type(param) == "function"
553
+ assert(_arg0_1)
554
+ listener = {
555
+ [metaKey] = function(self, ...)
556
+ local args = { ... }
557
+ return param(unpack(args))
558
+ end,
559
+ }
560
+ end
561
+ -- Register the lifecycle event
562
+ Reflect.defineMetadata(listener, "flamework:implements", { metaId })
563
+ -- Attached before it is held, as in `createClassInstance`.
564
+ registerClassInterfaces(listener, "instance")
565
+ local _listener = listener
566
+ temporaryInstances[_listener] = true
567
+ return function()
568
+ local _arg0_1 = listener ~= nil
569
+ assert(_arg0_1, "listeners cannot be destructed more than once")
570
+ removeClassInstance(listener)
571
+ listener = nil
572
+ end
573
+ end
574
+ local release
575
+ local ignite = function()
576
+ -- Checked before anything changes: an import that is not ready leaves this module as it was,
577
+ -- and `ignite()` being synchronous is what makes "ignite it first" a line order.
578
+ for _, imported in imports do
579
+ if not imported.isIgnited() then
580
+ error(`module '{state.debugName}': imported module '{imported.debugName}' is not ignited; ignite it first`)
581
+ end
582
+ end
583
+ -- Raises on a module that has ignited already: a definition is what gets ignited twice.
584
+ switchInitState(ModuleInitState.Created, ModuleInitState.PreIgniting)
585
+ -- From here on the module holds things: what the plugins set up, then the providers, then
586
+ -- what the hooks connect. A raise anywhere in it takes the module down the way `extinguish`
587
+ -- does before it comes out, so that a failed ignition holds nothing. The lifecycle plugin's
588
+ -- postIgnite hook connects the RunService signals, and a hook after it that raised used to
589
+ -- leave those ticking a module stuck in `Igniting`, which nothing could extinguish.
590
+ local success, err = pcall(function()
591
+ -- Plugins are set up before any provider exists, so a setup that resolves is refused the
592
+ -- same way an `onPreIgnite` hook is.
593
+ for _, inclusion in state.plugins do
594
+ includePlugin(inclusion.plugin, inclusion.scope)
595
+ end
596
+ runHooks("preIgnite")
597
+ -- Every registration is in by now, the plugins' included, so this is where the conditions
598
+ -- are judged and the ids checked for collisions among what is kept.
599
+ activateProviders()
600
+ switchInitState(ModuleInitState.PreIgniting, ModuleInitState.Igniting)
601
+ -- Provided instances exist already; here they join the interfaces they implement, now that
602
+ -- every plugin's observers are in place.
603
+ for _, instance in providedInstances do
604
+ registerClassInterfaces(instance, "provider")
605
+ end
606
+ for _, provider in providers do
607
+ -- Lazy providers are constructed the first time they are resolved instead.
608
+ if provider.config.type == "class" and provider.config.lazy ~= true then
609
+ resolveDependency(provider.injectionId)
610
+ end
611
+ end
612
+ runHooks("postIgnite")
613
+ end)
614
+ if not success then
615
+ moduleInitState = ModuleInitState.Extinguishing
616
+ release()
617
+ error(err, 0)
618
+ end
619
+ switchInitState(ModuleInitState.Igniting, ModuleInitState.Ignited)
620
+ -- Told last, once this module is whole: an importer only counts once it can be extinguished.
621
+ for _, imported in imports do
622
+ imported.addImporter(module)
623
+ end
624
+ return module
625
+ end
626
+ local extinguish = function()
627
+ switchInitState(ModuleInitState.Ignited, ModuleInitState.Extinguishing)
628
+ -- Importers hold this module's instances, so they go first, and each takes its own importers
629
+ -- down before it returns: the deepest goes first. Copied, since each removes itself.
630
+ local _array_2 = {}
631
+ local _length_1 = #_array_2
632
+ for _v in importers do
633
+ _length_1 += 1
634
+ _array_2[_length_1] = _v
635
+ end
636
+ for _, importer in _array_2 do
637
+ if not importer.isExtinguished() then
638
+ importer.extinguish()
639
+ end
640
+ end
641
+ table.clear(importers)
642
+ release()
643
+ end
644
+ --[[
645
+ *
646
+ * Lets go of everything this instantiation holds, from `Extinguishing`: the extinguished hooks
647
+ * run, the instances it created and the providers it constructed leave their interfaces, and
648
+ * the default is released. Shared by `extinguish` and by an ignition that raised, so the hooks
649
+ * that ran before the raise are undone by the same hooks that undo them on extinguish.
650
+ *
651
+ * Every step is guarded: one that raises is warned about and the rest still run, so the module
652
+ * cannot get stuck half-extinguished -- which used to hold it, and everything in it, for good,
653
+ * as the default `Dependency<T>()` answered from.
654
+
655
+ ]]
656
+ release = function()
657
+ for _, hook in sortedHooks("extinguished") do
658
+ guarded("an onExtinguished hook", function()
659
+ return hook.callback(module)
660
+ end)
661
+ end
662
+ -- Copied first: removal callbacks may themselves remove instances.
663
+ local _array_2 = {}
664
+ local _length_1 = #_array_2
665
+ for _v in temporaryInstances do
666
+ _length_1 += 1
667
+ _array_2[_length_1] = _v
668
+ end
669
+ for _, temporaryInstance in _array_2 do
670
+ guarded("removing an instance", function()
671
+ return removeClassInstance(temporaryInstance)
672
+ end)
673
+ end
674
+ -- Providers join their interfaces when they are instantiated, so they have to leave them
675
+ -- too. Without this, a plugin such as the lifecycle plugin keeps holding (and ticking)
676
+ -- providers that belong to an extinguished module.
677
+ for _, provider in instantiatedProviders do
678
+ guarded("removing a provider", function()
679
+ return unregisterClassInterfaces(provider, "provider")
680
+ end)
681
+ end
682
+ -- A removal that raised leaves its instance behind, and nothing can add one any more.
683
+ table.clear(temporaryInstances)
684
+ table.clear(instantiatedProviders)
685
+ switchInitState(ModuleInitState.Extinguishing, ModuleInitState.Extinguished)
686
+ for _, imported in imports do
687
+ imported.removeImporter(module)
688
+ end
689
+ -- Released last, once nothing in here can resolve any more, so that the next root ignited
690
+ -- becomes the default rather than `Dependency<T>()` answering from a dead module.
691
+ clearDefaultModule(module)
692
+ end
693
+ local isExtinguished = function()
694
+ return moduleInitState >= ModuleInitState.Extinguishing
695
+ end
696
+ local isIgnited = function()
697
+ return moduleInitState == ModuleInitState.Ignited
698
+ end
699
+ module = {
700
+ debugName = state.debugName,
701
+ tryResolveDependency = tryResolveDependency,
702
+ resolveDependency = resolveDependency,
703
+ listen = listen,
704
+ createClassInstance = createClassInstance,
705
+ removeClassInstance = removeClassInstance,
706
+ ignite = ignite,
707
+ extinguish = extinguish,
708
+ isExtinguished = isExtinguished,
709
+ isIgnited = isIgnited,
710
+ lookupProvider = lookupProvider,
711
+ addImporter = function(importer)
712
+ local _importer = importer
713
+ importers[_importer] = true
714
+ return importers
715
+ end,
716
+ removeImporter = function(importer)
717
+ local _importer = importer
718
+ -- ▼ Set.delete ▼
719
+ local _valueExisted = importers[_importer] ~= nil
720
+ importers[_importer] = nil
721
+ -- ▲ Set.delete ▲
722
+ return _valueExisted
723
+ end,
724
+ }
725
+ --* What a plugin's setup is handed. Everything registers into this instantiation.
726
+ pluginTarget = {
727
+ module = module,
728
+ scope = if hasCondition(moduleScope) then moduleScope else nil,
729
+ isActive = function(...)
730
+ local conditions = { ... }
731
+ local _array_2 = { moduleScope }
732
+ local _length_1 = #_array_2
733
+ table.move(conditions, 1, #conditions, _length_1 + 1, _array_2)
734
+ return holdsEveryCondition(_array_2)
735
+ end,
736
+ registerClassProvider = registerClassProvider,
737
+ registerProviders = function(_path, registrationOptions, resolved)
738
+ local _arg0 = resolved ~= nil
739
+ assert(_arg0)
740
+ registerProviderClasses(getClassesInPath(resolved), registrationOptions)
741
+ end,
742
+ registerProvidersGlob = function(_glob, registrationOptions, resolved)
743
+ local _arg0 = resolved ~= nil
744
+ assert(_arg0)
745
+ registerProviderClasses(getClassesInGlob(resolved), registrationOptions)
746
+ end,
747
+ registerProvider = function(config, injectionId)
748
+ local _arg0 = injectionId ~= nil
749
+ assert(_arg0)
750
+ local _arg0_1 = {
751
+ config = normalizeProviderConfig(config),
752
+ injectionId = injectionId,
753
+ }
754
+ table.insert(registered, _arg0_1)
755
+ end,
756
+ provideInstance = function(instance, injectionId)
757
+ local _arg0 = injectionId ~= nil
758
+ assert(_arg0)
759
+ assertNotProvided(injectionId)
760
+ local _injectionId = injectionId
761
+ local _instance = instance
762
+ instantiatedProviders[_injectionId] = _instance
763
+ local _instance_1 = instance
764
+ table.insert(providedInstances, _instance_1)
765
+ end,
766
+ includePlugin = includePlugin,
767
+ onPreIgnite = function(callback, hookOptions)
768
+ local _exp = callback
769
+ local _result_1 = hookOptions
770
+ if _result_1 ~= nil then
771
+ _result_1 = _result_1.priority
772
+ end
773
+ return registerHook("preIgnite", _exp, _result_1)
774
+ end,
775
+ onPostIgnite = function(callback, hookOptions)
776
+ local _exp = callback
777
+ local _result_1 = hookOptions
778
+ if _result_1 ~= nil then
779
+ _result_1 = _result_1.priority
780
+ end
781
+ return registerHook("postIgnite", _exp, _result_1)
782
+ end,
783
+ onExtinguished = function(callback, hookOptions)
784
+ local _exp = callback
785
+ local _result_1 = hookOptions
786
+ if _result_1 ~= nil then
787
+ _result_1 = _result_1.priority
788
+ end
789
+ return registerHook("extinguished", _exp, _result_1)
790
+ end,
791
+ observe = function(config, interfaceId)
792
+ local _arg0 = interfaceId ~= nil
793
+ assert(_arg0)
794
+ local _interfaceId = interfaceId
795
+ local interested = observers[_interfaceId]
796
+ if not interested then
797
+ local _exp = interfaceId
798
+ interested = {}
799
+ local _interested = interested
800
+ observers[_exp] = _interested
801
+ end
802
+ local _interested = interested
803
+ local _config = config
804
+ table.insert(_interested, _config)
805
+ end,
806
+ }
807
+ return module
808
+ end
809
+ return {
810
+ createModuleInstantiation = createModuleInstantiation,
811
+ }