1 // Copyright 2021-present 650 Industries. All rights reserved.
2 
3 /**
4  Base class for other definitions representing an object, such as `ModuleDefinition`.
5  */
6 public class ObjectDefinition: AnyDefinition, JavaScriptObjectBuilder {
7   /**
8    A dictionary of functions defined by the object.
9    */
10   let functions: [String: AnyFunction]
11 
12   /**
13    An array of constants definitions.
14    */
15   let constants: [ConstantsDefinition]
16 
17   /**
18    A map of dynamic properties defined by the object.
19    */
20   let properties: [String: AnyPropertyComponent]
21 
22   /**
23    A map of classes defined within the object.
24    */
25   let classes: [String: ClassComponent]
26 
27   /**
28    Default initializer receiving children definitions from the result builder.
29    */
30   init(definitions: [AnyDefinition]) {
31     self.functions = definitions
32       .compactMap { $0 as? AnyFunction }
33       .reduce(into: [String: AnyFunction]()) { dict, function in
34         dict[function.name] = function
35       }
36 
37     self.constants = definitions
38       .compactMap { $0 as? ConstantsDefinition }
39 
40     self.properties = definitions
41       .compactMap { $0 as? AnyPropertyComponent }
42       .reduce(into: [String: AnyPropertyComponent]()) { dict, property in
43         dict[property.name] = property
44       }
45 
46     self.classes = definitions
47       .compactMap { $0 as? ClassComponent }
48       .reduce(into: [String: ClassComponent]()) { dict, klass in
49         dict[klass.name] = klass
50       }
51   }
52 
53   /**
54    Merges all `constants` definitions into one dictionary.
55    */
56   func getConstants() -> [String: Any?] {
57     return constants.reduce(into: [String: Any?]()) { dict, definition in
58       dict.merge(definition.body()) { $1 }
59     }
60   }
61 
62   // MARK: - JavaScriptObjectBuilder
63 
64   public func build(appContext: AppContext) throws -> JavaScriptObject {
65     let object = try appContext.runtime.createObject()
66     try decorate(object: object, appContext: appContext)
67     return object
68   }
69 
70   public func decorate(object: JavaScriptObject, appContext: AppContext) throws {
71     let runtime = try appContext.runtime
72 
73     decorateWithConstants(object: object)
74     try decorateWithFunctions(object: object, appContext: appContext)
75     try decorateWithProperties(object: object, appContext: appContext)
76     try decorateWithClasses(object: object, appContext: appContext)
77   }
78 
79   // MARK: - Internals
80 
81   internal func decorateWithConstants(object: JavaScriptObject) {
82     for (key, value) in getConstants() {
83       object.setProperty(key, value: value)
84     }
85   }
86 
87   internal func decorateWithFunctions(object: JavaScriptObject, appContext: AppContext) throws {
88     for fn in functions.values {
89       object.setProperty(fn.name, value: try fn.build(appContext: appContext))
90     }
91   }
92 
93   internal func decorateWithProperties(object: JavaScriptObject, appContext: AppContext) throws {
94     for property in properties.values {
95       let descriptor = try property.buildDescriptor(appContext: appContext)
96       object.defineProperty(property.name, descriptor: descriptor)
97     }
98   }
99 
100   internal func decorateWithClasses(object: JavaScriptObject, appContext: AppContext) throws {
101     for klass in classes.values {
102       object.setProperty(klass.name, value: try klass.build(appContext: appContext))
103     }
104   }
105 }
106