octonom
Version:
Object Document Mapper for any database
92 lines (70 loc) • 2.98 kB
text/typescript
import { Collection } from '../collection';
import { SanitizationError } from '../errors';
import { Model } from '../model';
import { ISanitizeOptions, ISchema, ISchemaInstance, ISchemaOptions,
IToObjectOptions, PopulateReference, validate,
} from './schema';
export interface IReferenceInstance<TModel extends Model = Model> extends ISchemaInstance<TModel | string> {
id: string;
}
export type ReferenceInstance<TModel extends Model = Model> = IReferenceInstance<TModel>;
export interface IReferenceOptions<TModel extends Model = Model> extends ISchemaOptions<ReferenceInstance<TModel>> {
collection: () => Collection<TModel>;
}
export class ReferenceSchema<TModel extends Model = Model> implements
ISchema<TModel | string, ReferenceInstance<TModel>, Partial<TModel> | string> {
constructor(public options: IReferenceOptions<TModel>) {}
public create(value: any, sanitizeOptions: ISanitizeOptions = {}): ReferenceInstance<TModel> {
const sanitizedValue = this.sanitize(value, sanitizeOptions);
if (sanitizedValue === undefined) {
return undefined;
}
const idField = this.options.collection().modelIdField;
const id = typeof sanitizedValue === 'string'
? sanitizedValue
: sanitizedValue[idField];
if (id === undefined) {
throw new SanitizationError('Reference id is undefined.', 'no-id', sanitizeOptions.parent);
}
const instance: ReferenceInstance<TModel> = {
value: sanitizedValue,
id,
schema: this,
};
return instance;
}
public async populate(instance: ReferenceInstance<TModel>, populateReference: PopulateReference<TModel>) {
const modelInstance = await this.options.collection().findById(instance.id);
if (populateReference !== true) {
await modelInstance.populate(populateReference);
}
instance.value = modelInstance;
return instance.value;
}
public toObject(instance: ReferenceInstance<TModel>, options: IToObjectOptions = {}): Partial<TModel> | string {
if (options.unpopulate) {
return instance.id;
}
if (typeof instance.value === 'string') {
return instance.value;
}
return instance.value.toObject(options);
}
public async validate(instance: ReferenceInstance<TModel>) {
await validate(this.options, instance);
}
protected sanitize(value: any, sanitizeOptions: ISanitizeOptions = {}): TModel | string {
if (value === undefined || typeof value === 'string') {
return value;
}
// If it's not an id we expect a model. We don't instantiate from
// a plain object because an object coming from a database will only
// contain the id and if it has been populated it will already be a model.
const model = this.options.collection().model;
if (!(value instanceof model)) {
throw new SanitizationError('Value is not an instance or an id.');
}
// clone model instance
return new model(value, sanitizeOptions);
}
}