This repository has been archived by the owner on Feb 26, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 95
/
providers.js
156 lines (130 loc) · 5.31 KB
/
providers.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import {
ClassProvider as ClassProviderAnnotation,
FactoryProvider as FactoryProviderAnnotation,
SuperConstructor as SuperConstructorAnnotation,
readAnnotations,
hasAnnotation
} from './annotations';
import {isFunction, isObject, toString, isUpperCase, ownKeys} from './util';
function isClass(clsOrFunction) {
if (hasAnnotation(clsOrFunction, ClassProviderAnnotation)) {
return true
}
else if(hasAnnotation(clsOrFunction, FactoryProviderAnnotation)) {
return false
}
else if (clsOrFunction.name) {
return isUpperCase(clsOrFunction.name.charAt(0));
} else {
return ownKeys(clsOrFunction.prototype).length > 0;
}
}
// Provider is responsible for creating instances.
//
// responsibilities:
// - create instances
//
// communication:
// - exposes `create()` which creates an instance of something
// - exposes `params` (information about which arguments it requires to be passed into `create()`)
//
// Injector reads `provider.params` first, create these dependencies (however it wants),
// then calls `provider.create(args)`, passing in these arguments.
var EmptyFunction = Object.getPrototypeOf(Function);
// ClassProvider knows how to instantiate classes.
//
// If a class inherits (has parent constructors), this provider normalizes all the dependencies
// into a single flat array first, so that the injector does not need to worry about inheritance.
//
// - all the state is immutable (constructed)
//
// TODO(vojta): super constructor - should be only allowed during the constructor call?
class ClassProvider {
constructor(clazz, params, isPromise) {
// TODO(vojta): can we hide this.provider? (only used for hasAnnotation(provider.provider))
this.provider = clazz;
this.isPromise = isPromise;
this.params = [];
this._constructors = [];
this._flattenParams(clazz, params);
this._constructors.unshift([clazz, 0, this.params.length - 1]);
}
// Normalize params for all the constructors (in the case of inheritance),
// into a single flat array of DependencyDescriptors.
// So that the injector does not have to worry about inheritance.
//
// This function mutates `this.params` and `this._constructors`,
// but it is only called during the constructor.
// TODO(vojta): remove the annotations argument?
_flattenParams(constructor, params) {
var SuperConstructor;
var constructorInfo;
for (var param of params) {
if (param.token === SuperConstructorAnnotation) {
SuperConstructor = Object.getPrototypeOf(constructor);
if (SuperConstructor === EmptyFunction) {
throw new Error(`${toString(constructor)} does not have a parent constructor. Only classes with a parent can ask for SuperConstructor!`);
}
constructorInfo = [SuperConstructor, this.params.length];
this._constructors.push(constructorInfo);
this._flattenParams(SuperConstructor, readAnnotations(SuperConstructor).params);
constructorInfo.push(this.params.length - 1);
} else {
this.params.push(param);
}
}
}
// Basically the reverse process to `this._flattenParams`:
// We get arguments for all the constructors as a single flat array.
// This method generates pre-bound "superConstructor" wrapper with correctly passing arguments.
_createConstructor(currentConstructorIdx, context, allArguments) {
var constructorInfo = this._constructors[currentConstructorIdx];
var nextConstructorInfo = this._constructors[currentConstructorIdx + 1];
var argsForCurrentConstructor;
if (nextConstructorInfo) {
argsForCurrentConstructor = allArguments
.slice(constructorInfo[1], nextConstructorInfo[1])
.concat([this._createConstructor(currentConstructorIdx + 1, context, allArguments)])
.concat(allArguments.slice(nextConstructorInfo[2] + 1, constructorInfo[2] + 1));
} else {
argsForCurrentConstructor = allArguments.slice(constructorInfo[1], constructorInfo[2] + 1);
}
return function InjectedAndBoundSuperConstructor() {
// TODO(vojta): throw if arguments given
return constructorInfo[0].apply(context, argsForCurrentConstructor);
};
}
// It is called by injector to create an instance.
create(args) {
var context = Object.create(this.provider.prototype);
var constructor = this._createConstructor(0, context, args);
var returnedValue = constructor();
if (isFunction(returnedValue) || isObject(returnedValue)) {
return returnedValue;
}
return context;
}
}
// FactoryProvider knows how to create instance from a factory function.
// - all the state is immutable
class FactoryProvider {
constructor(factoryFunction, params, isPromise) {
this.provider = factoryFunction;
this.params = params;
this.isPromise = isPromise;
for (var param of params) {
if (param.token === SuperConstructorAnnotation) {
throw new Error(`${toString(factoryFunction)} is not a class. Only classes with a parent can ask for SuperConstructor!`);
}
}
}
create(args) {
return this.provider.apply(undefined, args);
}
}
export function createProviderFromFnOrClass(fnOrClass, annotations) {
if (isClass(fnOrClass)) {
return new ClassProvider(fnOrClass, annotations.params, annotations.provide.isPromise);
}
return new FactoryProvider(fnOrClass, annotations.params, annotations.provide.isPromise);
}