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