1 | ;
|
2 | // Copyright IBM Corp. and LoopBack contributors 2017,2020. All Rights Reserved.
|
3 | // Node module: @loopback/core
|
4 | // This file is licensed under the MIT License.
|
5 | // License text available at https://opensource.org/licenses/MIT
|
6 | Object.defineProperty(exports, "__esModule", { value: true });
|
7 | exports.addExtension = exports.extensionFor = exports.extensionFilter = exports.extensions = exports.extensionPoint = void 0;
|
8 | const context_1 = require("@loopback/context");
|
9 | const keys_1 = require("./keys");
|
10 | /**
|
11 | * Decorate a class as a named extension point. If the decoration is not
|
12 | * present, the name of the class will be used.
|
13 | *
|
14 | * @example
|
15 | * ```ts
|
16 | * import {extensionPoint} from '@loopback/core';
|
17 | *
|
18 | * @extensionPoint(GREETER_EXTENSION_POINT_NAME)
|
19 | * export class GreetingService {
|
20 | * // ...
|
21 | * }
|
22 | * ```
|
23 | *
|
24 | * @param name - Name of the extension point
|
25 | */
|
26 | function extensionPoint(name, ...specs) {
|
27 | return (0, context_1.injectable)({ tags: { [keys_1.CoreTags.EXTENSION_POINT]: name } }, ...specs);
|
28 | }
|
29 | exports.extensionPoint = extensionPoint;
|
30 | /**
|
31 | * Shortcut to inject extensions for the given extension point.
|
32 | *
|
33 | * @example
|
34 | * ```ts
|
35 | * import {Getter} from '@loopback/context';
|
36 | * import {extensionPoint, extensions} from '@loopback/core';
|
37 | *
|
38 | * @extensionPoint(GREETER_EXTENSION_POINT_NAME)
|
39 | * export class GreetingService {
|
40 | * constructor(
|
41 | * @extensions() // Inject extensions for the extension point
|
42 | * private getGreeters: Getter<Greeter[]>,
|
43 | * // ...
|
44 | * ) {
|
45 | * // ...
|
46 | * }
|
47 | * ```
|
48 | *
|
49 | * @param extensionPointName - Name of the extension point. If not supplied, we
|
50 | * use the `name` tag from the extension point binding or the class name of the
|
51 | * extension point class. If a class needs to inject extensions from multiple
|
52 | * extension points, use different `extensionPointName` for different types of
|
53 | * extensions.
|
54 | * @param metadata - Optional injection metadata
|
55 | */
|
56 | function extensions(extensionPointName, metadata) {
|
57 | return (0, context_1.inject)('', { ...metadata, decorator: '@extensions' }, (ctx, injection, session) => {
|
58 | (0, context_1.assertTargetType)(injection, Function, 'Getter function');
|
59 | const bindingFilter = filterByExtensionPoint(injection, session, extensionPointName);
|
60 | return (0, context_1.createViewGetter)(ctx, bindingFilter, injection.metadata.bindingComparator, { ...metadata, ...(0, context_1.asResolutionOptions)(session) });
|
61 | });
|
62 | }
|
63 | exports.extensions = extensions;
|
64 | (function (extensions) {
|
65 | /**
|
66 | * Inject a `ContextView` for extensions of the extension point. The view can
|
67 | * then be listened on events such as `bind`, `unbind`, or `refresh` to react
|
68 | * on changes of extensions.
|
69 | *
|
70 | * @example
|
71 | * ```ts
|
72 | * import {extensionPoint, extensions} from '@loopback/core';
|
73 | *
|
74 | * @extensionPoint(GREETER_EXTENSION_POINT_NAME)
|
75 | * export class GreetingService {
|
76 | * constructor(
|
77 | * @extensions.view() // Inject a context view for extensions of the extension point
|
78 | * private greetersView: ContextView<Greeter>,
|
79 | * // ...
|
80 | * ) {
|
81 | * // ...
|
82 | * }
|
83 | * ```
|
84 | * @param extensionPointName - Name of the extension point. If not supplied, we
|
85 | * use the `name` tag from the extension point binding or the class name of the
|
86 | * extension point class. If a class needs to inject extensions from multiple
|
87 | * extension points, use different `extensionPointName` for different types of
|
88 | * extensions.
|
89 | * @param metadata - Optional injection metadata
|
90 | */
|
91 | function view(extensionPointName, metadata) {
|
92 | return (0, context_1.inject)('', { ...metadata, decorator: '@extensions.view' }, (ctx, injection, session) => {
|
93 | (0, context_1.assertTargetType)(injection, context_1.ContextView);
|
94 | const bindingFilter = filterByExtensionPoint(injection, session, extensionPointName);
|
95 | return ctx.createView(bindingFilter, injection.metadata.bindingComparator, metadata);
|
96 | });
|
97 | }
|
98 | extensions.view = view;
|
99 | /**
|
100 | * Inject an array of resolved extension instances for the extension point.
|
101 | * The list is a snapshot of registered extensions when the injection is
|
102 | * fulfilled. Extensions added or removed afterward won't impact the list.
|
103 | *
|
104 | * @example
|
105 | * ```ts
|
106 | * import {extensionPoint, extensions} from '@loopback/core';
|
107 | *
|
108 | * @extensionPoint(GREETER_EXTENSION_POINT_NAME)
|
109 | * export class GreetingService {
|
110 | * constructor(
|
111 | * @extensions.list() // Inject an array of extensions for the extension point
|
112 | * private greeters: Greeter[],
|
113 | * // ...
|
114 | * ) {
|
115 | * // ...
|
116 | * }
|
117 | * ```
|
118 | * @param extensionPointName - Name of the extension point. If not supplied, we
|
119 | * use the `name` tag from the extension point binding or the class name of the
|
120 | * extension point class. If a class needs to inject extensions from multiple
|
121 | * extension points, use different `extensionPointName` for different types of
|
122 | * extensions.
|
123 | * @param metadata - Optional injection metadata
|
124 | */
|
125 | function list(extensionPointName, metadata) {
|
126 | return (0, context_1.inject)('', { ...metadata, decorator: '@extensions.instances' }, (ctx, injection, session) => {
|
127 | (0, context_1.assertTargetType)(injection, Array);
|
128 | const bindingFilter = filterByExtensionPoint(injection, session, extensionPointName);
|
129 | const viewForExtensions = new context_1.ContextView(ctx, bindingFilter, injection.metadata.bindingComparator);
|
130 | return viewForExtensions.resolve({
|
131 | ...metadata,
|
132 | ...(0, context_1.asResolutionOptions)(session),
|
133 | });
|
134 | });
|
135 | }
|
136 | extensions.list = list;
|
137 | })(extensions || (exports.extensions = extensions = {}));
|
138 | /**
|
139 | * Create a binding filter for `@extensions.*`
|
140 | * @param injection - Injection object
|
141 | * @param session - Resolution session
|
142 | * @param extensionPointName - Extension point name
|
143 | */
|
144 | function filterByExtensionPoint(injection, session, extensionPointName) {
|
145 | extensionPointName =
|
146 | extensionPointName !== null && extensionPointName !== void 0 ? extensionPointName : inferExtensionPointName(injection.target, session.currentBinding);
|
147 | return extensionFilter(extensionPointName);
|
148 | }
|
149 | /**
|
150 | * Infer the extension point name from binding tags/class name
|
151 | * @param injectionTarget - Target class or prototype
|
152 | * @param currentBinding - Current binding
|
153 | */
|
154 | function inferExtensionPointName(injectionTarget, currentBinding) {
|
155 | if (currentBinding) {
|
156 | const name = currentBinding.tagMap[keys_1.CoreTags.EXTENSION_POINT] ||
|
157 | currentBinding.tagMap[context_1.ContextTags.NAME];
|
158 | if (name)
|
159 | return name;
|
160 | }
|
161 | let target;
|
162 | if (typeof injectionTarget === 'function') {
|
163 | // Constructor injection
|
164 | target = injectionTarget;
|
165 | }
|
166 | else {
|
167 | // Injection on the prototype
|
168 | target = injectionTarget.constructor;
|
169 | }
|
170 | return target.name;
|
171 | }
|
172 | /**
|
173 | * A factory function to create binding filter for extensions of a named
|
174 | * extension point
|
175 | * @param extensionPointNames - A list of names of extension points
|
176 | */
|
177 | function extensionFilter(...extensionPointNames) {
|
178 | return (0, context_1.filterByTag)({
|
179 | [keys_1.CoreTags.EXTENSION_FOR]: (0, context_1.includesTagValue)(...extensionPointNames),
|
180 | });
|
181 | }
|
182 | exports.extensionFilter = extensionFilter;
|
183 | /**
|
184 | * A factory function to create binding template for extensions of the given
|
185 | * extension point
|
186 | * @param extensionPointNames - Names of the extension point
|
187 | */
|
188 | function extensionFor(...extensionPointNames) {
|
189 | return binding => {
|
190 | if (extensionPointNames.length === 0)
|
191 | return;
|
192 | let extensionPoints = binding.tagMap[keys_1.CoreTags.EXTENSION_FOR];
|
193 | // Normalize extensionPoints to string[]
|
194 | if (extensionPoints == null) {
|
195 | extensionPoints = [];
|
196 | }
|
197 | else if (typeof extensionPoints === 'string') {
|
198 | extensionPoints = [extensionPoints];
|
199 | }
|
200 | // Add extension points
|
201 | for (const extensionPointName of extensionPointNames) {
|
202 | if (!extensionPoints.includes(extensionPointName)) {
|
203 | extensionPoints.push(extensionPointName);
|
204 | }
|
205 | }
|
206 | if (extensionPoints.length === 1) {
|
207 | // Keep the value as string for backward compatibility
|
208 | extensionPoints = extensionPoints[0];
|
209 | }
|
210 | binding.tag({ [keys_1.CoreTags.EXTENSION_FOR]: extensionPoints });
|
211 | };
|
212 | }
|
213 | exports.extensionFor = extensionFor;
|
214 | /**
|
215 | * Register an extension for the given extension point to the context
|
216 | * @param context - Context object
|
217 | * @param extensionPointName - Name of the extension point
|
218 | * @param extensionClass - Class or a provider for an extension
|
219 | * @param options - Options Options for the creation of binding from class
|
220 | */
|
221 | function addExtension(context, extensionPointName, extensionClass, options) {
|
222 | const binding = (0, context_1.createBindingFromClass)(extensionClass, options).apply(extensionFor(extensionPointName));
|
223 | context.add(binding);
|
224 | return binding;
|
225 | }
|
226 | exports.addExtension = addExtension;
|
227 | //# sourceMappingURL=extension-point.js.map |
\ | No newline at end of file |