UNPKG

14.7 kBJavaScriptView Raw
1"use strict";
2// Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
3// Node module: @loopback/repository
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.createModelClassBinding = exports.RepositoryMixinDoc = exports.RepositoryMixin = void 0;
8const tslib_1 = require("tslib");
9const core_1 = require("@loopback/core");
10const debug_1 = tslib_1.__importDefault(require("debug"));
11const keys_1 = require("../keys");
12const repositories_1 = require("../repositories");
13const debug = (0, debug_1.default)('loopback:repository:mixin');
14/* eslint-enable @typescript-eslint/no-unused-vars */
15/**
16 * A mixin class for Application that creates a .repository()
17 * function to register a repository automatically. Also overrides
18 * component function to allow it to register repositories automatically.
19 *
20 * @example
21 * ```ts
22 * class MyApplication extends RepositoryMixin(Application) {}
23 * ```
24 *
25 * Please note: the members in the mixin function are documented in a dummy class
26 * called <a href="#RepositoryMixinDoc">RepositoryMixinDoc</a>
27 *
28 * @param superClass - Application class
29 * @returns A new class that extends the super class with repository related
30 * methods
31 *
32 * @typeParam T - Type of the application class as the target for the mixin
33 *
34 */
35function RepositoryMixin(superClass) {
36 return class extends superClass {
37 /**
38 * Add a repository to this application.
39 *
40 * @param repoClass - The repository to add.
41 * @param nameOrOptions - Name or options for the binding
42 *
43 * @example
44 * ```ts
45 *
46 * class NoteRepo {
47 * model: any;
48 *
49 * constructor() {
50 * const ds: juggler.DataSource = new juggler.DataSource({
51 * name: 'db',
52 * connector: 'memory',
53 * });
54 *
55 * this.model = ds.createModel(
56 * 'note',
57 * {title: 'string', content: 'string'},
58 * {}
59 * );
60 * }
61 * };
62 *
63 * app.repository(NoteRepo);
64 * ```
65 */
66 // eslint-disable-next-line @typescript-eslint/no-explicit-any
67 repository(repoClass, nameOrOptions) {
68 const binding = (0, core_1.createBindingFromClass)(repoClass, {
69 namespace: keys_1.RepositoryBindings.REPOSITORIES,
70 type: keys_1.RepositoryTags.REPOSITORY,
71 defaultScope: core_1.BindingScope.TRANSIENT,
72 ...toOptions(nameOrOptions),
73 }).tag(keys_1.RepositoryTags.REPOSITORY);
74 this.add(binding);
75 return binding;
76 }
77 /**
78 * Retrieve the repository instance from the given Repository class
79 *
80 * @param repo - The repository class to retrieve the instance of
81 */
82 // eslint-disable-next-line @typescript-eslint/no-explicit-any
83 async getRepository(repo) {
84 return this.get(`repositories.${repo.name}`);
85 }
86 /**
87 * Add the dataSource to this application.
88 *
89 * @param dataSource - The dataSource to add.
90 * @param nameOrOptions - The binding name or options of the datasource;
91 * defaults to dataSource.name
92 *
93 * @example
94 * ```ts
95 *
96 * const ds: juggler.DataSource = new juggler.DataSource({
97 * name: 'db',
98 * connector: 'memory',
99 * });
100 *
101 * app.dataSource(ds);
102 *
103 * // The datasource can be injected with
104 * constructor(@inject('datasources.db') dataSource: DataSourceType) {
105 *
106 * }
107 * ```
108 */
109 dataSource(dataSource, nameOrOptions) {
110 var _a;
111 const options = toOptions(nameOrOptions);
112 // We have an instance of
113 if (dataSource instanceof repositories_1.juggler.DataSource) {
114 const name = options.name || dataSource.name;
115 const namespace = (_a = options.namespace) !== null && _a !== void 0 ? _a : keys_1.RepositoryBindings.DATASOURCES;
116 const key = `${namespace}.${name}`;
117 return this.bind(key).to(dataSource).tag(keys_1.RepositoryTags.DATASOURCE);
118 }
119 else if (typeof dataSource === 'function') {
120 options.name = options.name || dataSource.dataSourceName;
121 const binding = (0, core_1.createBindingFromClass)(dataSource, {
122 namespace: keys_1.RepositoryBindings.DATASOURCES,
123 type: keys_1.RepositoryTags.DATASOURCE,
124 defaultScope: core_1.BindingScope.SINGLETON,
125 ...options,
126 });
127 this.add(binding);
128 return binding;
129 }
130 else {
131 throw new Error('not a valid DataSource.');
132 }
133 }
134 /**
135 * Register a model class as a binding in the target context
136 * @param modelClass - Model class
137 */
138 model(modelClass) {
139 const binding = createModelClassBinding(modelClass);
140 this.add(binding);
141 return binding;
142 }
143 /**
144 * Add a component to this application. Also mounts
145 * all the components repositories.
146 *
147 * @param component - The component to add.
148 * @param nameOrOptions - Name or options for the binding.
149 *
150 * @example
151 * ```ts
152 *
153 * export class ProductComponent {
154 * controllers = [ProductController];
155 * repositories = [ProductRepo, UserRepo];
156 * providers = {
157 * [AUTHENTICATION_STRATEGY]: AuthStrategy,
158 * [AUTHORIZATION_ROLE]: Role,
159 * };
160 * };
161 *
162 * app.component(ProductComponent);
163 * ```
164 */
165 // Unfortunately, TypeScript does not allow overriding methods inherited
166 // from mapped types. https://github.com/microsoft/TypeScript/issues/38496
167 // eslint-disable-next-line @typescript-eslint/ban-ts-comment
168 // @ts-ignore
169 component(componentCtor, nameOrOptions) {
170 const binding = super.component(componentCtor, nameOrOptions);
171 const instance = this.getSync(binding.key);
172 this.mountComponentRepositories(instance);
173 this.mountComponentModels(instance);
174 return binding;
175 }
176 /**
177 * Get an instance of a component and mount all it's
178 * repositories. This function is intended to be used internally
179 * by `component()`.
180 *
181 * NOTE: Calling `mountComponentRepositories` with a component class
182 * constructor is deprecated. You should instantiate the component
183 * yourself and provide the component instance instead.
184 *
185 * @param componentInstanceOrClass - The component to mount repositories of
186 * @internal
187 */
188 mountComponentRepositories(
189 // accept also component class to preserve backwards compatibility
190 // TODO(semver-major) Remove support for component class constructor
191 componentInstanceOrClass) {
192 const component = resolveComponentInstance(this);
193 if (component.repositories) {
194 for (const repo of component.repositories) {
195 this.repository(repo);
196 }
197 }
198 // `Readonly<Application>` is a hack to remove protected members
199 // and thus allow `this` to be passed as a value for `ctx`
200 function resolveComponentInstance(ctx) {
201 if (typeof componentInstanceOrClass !== 'function')
202 return componentInstanceOrClass;
203 const componentName = componentInstanceOrClass.name;
204 const componentKey = `${core_1.CoreBindings.COMPONENTS}.${componentName}`;
205 return ctx.getSync(componentKey);
206 }
207 }
208 /**
209 * Bind all model classes provided by a component.
210 * @param component
211 * @internal
212 */
213 mountComponentModels(component) {
214 if (!component.models)
215 return;
216 for (const m of component.models) {
217 this.model(m);
218 }
219 }
220 /**
221 * Update or recreate the database schema for all repositories.
222 *
223 * **WARNING**: By default, `migrateSchema()` will attempt to preserve data
224 * while updating the schema in your target database, but this is not
225 * guaranteed to be safe.
226 *
227 * Please check the documentation for your specific connector(s) for
228 * a detailed breakdown of behaviors for automigrate!
229 *
230 * @param options - Migration options, e.g. whether to update tables
231 * preserving data or rebuild everything from scratch.
232 */
233 async migrateSchema(options = {}) {
234 var _a;
235 const operation = options.existingSchema === 'drop' ? 'automigrate' : 'autoupdate';
236 // Instantiate all repositories to ensure models are registered & attached
237 // to their datasources
238 const repoBindings = this.findByTag('repository');
239 await Promise.all(repoBindings.map(b => this.get(b.key)));
240 // Look up all datasources and update/migrate schemas one by one
241 const dsBindings = this.findByTag(keys_1.RepositoryTags.DATASOURCE);
242 for (const b of dsBindings) {
243 const ds = await this.get(b.key);
244 const disableMigration = (_a = ds.settings.disableMigration) !== null && _a !== void 0 ? _a : false;
245 if (operation in ds &&
246 typeof ds[operation] === 'function' &&
247 !disableMigration) {
248 debug('Migrating dataSource %s', b.key);
249 await ds[operation](options.models);
250 }
251 else {
252 debug('Skipping migration of dataSource %s', b.key);
253 }
254 }
255 }
256 };
257}
258exports.RepositoryMixin = RepositoryMixin;
259/**
260 * Normalize name or options to `BindingFromClassOptions`
261 * @param nameOrOptions - Name or options for binding from class
262 */
263function toOptions(nameOrOptions) {
264 if (typeof nameOrOptions === 'string') {
265 return { name: nameOrOptions };
266 }
267 return nameOrOptions !== null && nameOrOptions !== void 0 ? nameOrOptions : {};
268}
269/**
270 * A dummy class created to generate the tsdoc for the members in repository
271 * mixin. Please don't use it.
272 *
273 * The members are implemented in function
274 * <a href="#RepositoryMixin">RepositoryMixin</a>
275 */
276class RepositoryMixinDoc {
277 // eslint-disable-next-line @typescript-eslint/no-explicit-any
278 constructor(...args) {
279 throw new Error('This is a dummy class created for apidoc!' + 'Please do not use it!');
280 }
281 /**
282 * Add a repository to this application.
283 *
284 * @param repo - The repository to add.
285 *
286 * @example
287 * ```ts
288 *
289 * class NoteRepo {
290 * model: any;
291 *
292 * constructor() {
293 * const ds: juggler.DataSource = new juggler.DataSource({
294 * name: 'db',
295 * connector: 'memory',
296 * });
297 *
298 * this.model = ds.createModel(
299 * 'note',
300 * {title: 'string', content: 'string'},
301 * {}
302 * );
303 * }
304 * };
305 *
306 * app.repository(NoteRepo);
307 * ```
308 */
309 // eslint-disable-next-line @typescript-eslint/no-explicit-any
310 repository(repo) {
311 throw new Error();
312 }
313 /**
314 * Retrieve the repository instance from the given Repository class
315 *
316 * @param repo - The repository class to retrieve the instance of
317 */
318 // eslint-disable-next-line @typescript-eslint/no-explicit-any
319 async getRepository(repo) {
320 return new repo();
321 }
322 /**
323 * Add the dataSource to this application.
324 *
325 * @param dataSource - The dataSource to add.
326 * @param name - The binding name of the datasource; defaults to dataSource.name
327 *
328 * @example
329 * ```ts
330 *
331 * const ds: juggler.DataSource = new juggler.DataSource({
332 * name: 'db',
333 * connector: 'memory',
334 * });
335 *
336 * app.dataSource(ds);
337 *
338 * // The datasource can be injected with
339 * constructor(@inject('datasources.db') dataSource: DataSourceType) {
340 *
341 * }
342 * ```
343 */
344 dataSource(dataSource, name) {
345 throw new Error();
346 }
347 /**
348 * Add a component to this application. Also mounts
349 * all the components repositories.
350 *
351 * @param component - The component to add.
352 *
353 * @example
354 * ```ts
355 *
356 * export class ProductComponent {
357 * controllers = [ProductController];
358 * repositories = [ProductRepo, UserRepo];
359 * providers = {
360 * [AUTHENTICATION_STRATEGY]: AuthStrategy,
361 * [AUTHORIZATION_ROLE]: Role,
362 * };
363 * };
364 *
365 * app.component(ProductComponent);
366 * ```
367 */
368 component(component) {
369 throw new Error();
370 }
371 /**
372 * Get an instance of a component and mount all it's
373 * repositories. This function is intended to be used internally
374 * by component()
375 *
376 * @param component - The component to mount repositories of
377 */
378 mountComponentRepository(component) { }
379 /**
380 * Update or recreate the database schema for all repositories.
381 *
382 * **WARNING**: By default, `migrateSchema()` will attempt to preserve data
383 * while updating the schema in your target database, but this is not
384 * guaranteed to be safe.
385 *
386 * Please check the documentation for your specific connector(s) for
387 * a detailed breakdown of behaviors for automigrate!
388 *
389 * @param options - Migration options, e.g. whether to update tables
390 * preserving data or rebuild everything from scratch.
391 */
392 async migrateSchema(options) { }
393}
394exports.RepositoryMixinDoc = RepositoryMixinDoc;
395/**
396 * Create a binding for the given model class
397 * @param modelClass - Model class
398 */
399function createModelClassBinding(modelClass) {
400 return core_1.Binding.bind(`${keys_1.RepositoryBindings.MODELS}.${modelClass.name}`)
401 .to(modelClass)
402 .tag(keys_1.RepositoryTags.MODEL);
403}
404exports.createModelClassBinding = createModelClassBinding;
405//# sourceMappingURL=repository.mixin.js.map
\No newline at end of file