1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | import debugFactory from 'debug';
|
7 | import {BindingFilter} from './binding-filter';
|
8 | import {BindingAddress} from './binding-key';
|
9 | import {BindingComparator} from './binding-sorter';
|
10 | import {Context} from './context';
|
11 | import {InvocationResult} from './invocation';
|
12 | import {transformValueOrPromise, ValueOrPromise} from './value-promise';
|
13 | const debug = debugFactory('loopback:context:interceptor-chain');
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | export type NonVoid = string | number | boolean | null | undefined | object;
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 | export type Next = () => ValueOrPromise<NonVoid>;
|
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 | export type GenericInterceptor<C extends Context = Context> = (
|
62 | context: C,
|
63 | next: Next,
|
64 | ) => ValueOrPromise<NonVoid>;
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 | export type GenericInterceptorOrKey<C extends Context = Context> =
|
73 | | BindingAddress<GenericInterceptor<C>>
|
74 | | GenericInterceptor<C>;
|
75 |
|
76 |
|
77 |
|
78 |
|
79 | class InterceptorChainState<C extends Context = Context> {
|
80 | private _index = 0;
|
81 | |
82 |
|
83 |
|
84 |
|
85 |
|
86 | constructor(
|
87 | public readonly interceptors: GenericInterceptorOrKey<C>[],
|
88 | public readonly finalHandler: Next = () => undefined,
|
89 | ) {}
|
90 |
|
91 | |
92 |
|
93 |
|
94 | get index() {
|
95 | return this._index;
|
96 | }
|
97 |
|
98 | |
99 |
|
100 |
|
101 | done() {
|
102 | return this._index === this.interceptors.length;
|
103 | }
|
104 |
|
105 | |
106 |
|
107 |
|
108 | next() {
|
109 | if (this.done()) {
|
110 | throw new Error('No more interceptor is in the chain');
|
111 | }
|
112 | return this.interceptors[this._index++];
|
113 | }
|
114 | }
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 | export class GenericInterceptorChain<C extends Context = Context> {
|
122 | |
123 |
|
124 |
|
125 | protected getInterceptors: () => GenericInterceptorOrKey<C>[];
|
126 |
|
127 | |
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 | constructor(context: C, interceptors: GenericInterceptorOrKey<C>[]);
|
134 |
|
135 | /**
|
136 | * Create an invocation interceptor chain with a binding filter and comparator.
|
137 | * The interceptors are discovered from the context using the binding filter and
|
138 | * sorted by the comparator (if provided).
|
139 | *
|
140 | * @param context - Context object
|
141 | * @param filter - A binding filter function to select interceptors
|
142 | * @param comparator - An optional comparator to sort matched interceptor bindings
|
143 | */
|
144 | constructor(
|
145 | context: C,
|
146 | filter: BindingFilter,
|
147 | comparator?: BindingComparator,
|
148 | );
|
149 |
|
150 | // Implementation
|
151 | constructor(
|
152 | private context: C,
|
153 | interceptors: GenericInterceptorOrKey<C>[] | BindingFilter,
|
154 | comparator?: BindingComparator,
|
155 | ) {
|
156 | if (typeof interceptors === 'function') {
|
157 | const interceptorsView = context.createView(interceptors, comparator);
|
158 | this.getInterceptors = () => {
|
159 | const bindings = interceptorsView.bindings;
|
160 | if (comparator) {
|
161 | bindings.sort(comparator);
|
162 | }
|
163 | return bindings.map(b => b.key);
|
164 | };
|
165 | } else if (Array.isArray(interceptors)) {
|
166 | this.getInterceptors = () => interceptors;
|
167 | }
|
168 | }
|
169 |
|
170 | |
171 |
|
172 |
|
173 | invokeInterceptors(finalHandler?: Next): ValueOrPromise<InvocationResult> {
|
174 |
|
175 | const state = new InterceptorChainState<C>(
|
176 | this.getInterceptors(),
|
177 | finalHandler,
|
178 | );
|
179 | return this.next(state);
|
180 | }
|
181 |
|
182 | |
183 |
|
184 |
|
185 | asInterceptor(): GenericInterceptor<C> {
|
186 | return (ctx, next) => {
|
187 | return this.invokeInterceptors(next);
|
188 | };
|
189 | }
|
190 |
|
191 | |
192 |
|
193 |
|
194 | private next(
|
195 | state: InterceptorChainState<C>,
|
196 | ): ValueOrPromise<InvocationResult> {
|
197 | if (state.done()) {
|
198 |
|
199 | return state.finalHandler();
|
200 | }
|
201 |
|
202 | return this.invokeNextInterceptor(state);
|
203 | }
|
204 |
|
205 | |
206 |
|
207 |
|
208 | private invokeNextInterceptor(
|
209 | state: InterceptorChainState<C>,
|
210 | ): ValueOrPromise<InvocationResult> {
|
211 | const index = state.index;
|
212 | const interceptor = state.next();
|
213 | const interceptorFn = this.loadInterceptor(interceptor);
|
214 | return transformValueOrPromise(interceptorFn, fn => {
|
215 |
|
216 | if (debug.enabled) {
|
217 | debug('Invoking interceptor %d (%s) on %s', index, fn.name);
|
218 | }
|
219 | return fn(this.context, () => this.next(state));
|
220 | });
|
221 | }
|
222 |
|
223 | |
224 |
|
225 |
|
226 |
|
227 |
|
228 |
|
229 | private loadInterceptor(interceptor: GenericInterceptorOrKey<C>) {
|
230 | if (typeof interceptor === 'function') return interceptor;
|
231 | debug('Resolving interceptor binding %s', interceptor);
|
232 | return this.context.getValueOrPromise(interceptor) as ValueOrPromise<
|
233 | GenericInterceptor<C>
|
234 | >;
|
235 | }
|
236 | }
|
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|
242 |
|
243 | export function invokeInterceptors<
|
244 | C extends Context = Context,
|
245 | T = InvocationResult,
|
246 | >(
|
247 | context: C,
|
248 | interceptors: GenericInterceptorOrKey<C>[],
|
249 | ): ValueOrPromise<T | undefined> {
|
250 | const chain = new GenericInterceptorChain(context, interceptors);
|
251 | return chain.invokeInterceptors();
|
252 | }
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 | export function composeInterceptors<C extends Context = Context>(
|
259 | ...interceptors: GenericInterceptorOrKey<C>[]
|
260 | ): GenericInterceptor<C> {
|
261 | return (ctx, next) => {
|
262 | const interceptor = new GenericInterceptorChain(
|
263 | ctx,
|
264 | interceptors,
|
265 | ).asInterceptor();
|
266 | return interceptor(ctx, next);
|
267 | };
|
268 | }
|