UNPKG

9.23 kBJavaScriptView Raw
1"use strict";
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
6Object.defineProperty(exports, "__esModule", { value: true });
7exports.addExtension = exports.extensionFor = exports.extensionFilter = exports.extensions = exports.extensionPoint = void 0;
8const context_1 = require("@loopback/context");
9const 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 */
26function extensionPoint(name, ...specs) {
27 return (0, context_1.injectable)({ tags: { [keys_1.CoreTags.EXTENSION_POINT]: name } }, ...specs);
28}
29exports.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 */
56function 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}
63exports.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 */
144function 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 */
154function 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 */
177function extensionFilter(...extensionPointNames) {
178 return (0, context_1.filterByTag)({
179 [keys_1.CoreTags.EXTENSION_FOR]: (0, context_1.includesTagValue)(...extensionPointNames),
180 });
181}
182exports.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 */
188function 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}
213exports.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 */
221function 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}
226exports.addExtension = addExtension;
227//# sourceMappingURL=extension-point.js.map
\No newline at end of file