Connection

Manages connection to a database

Parameters

(None)
(defined in /src/connection/index.ts:168)
constructor(adapter: 'mongodb' | typeof createMongoDBAdapter,
    settings: IConnectionSettings & IAdapterSettingsMongoDB);

See Also:

(defined in /src/connection/index.ts:118)
public _adapter: AdapterType;
(defined in /src/connection/index.ts:156)
private _applying_schemas: boolean = false;

Parameters

(None)

Returns

(Nothing)
(defined in /src/connection/index.ts:1005)
private _belongsTo(
    this_model: typeof BaseModel, target_model: typeof BaseModel,
    options?: IAssociationBelongsToOptions,
  ) {
    let foreign_key: any;
    if (options && options.foreign_key) {
      foreign_key = options.foreign_key;
    } else if (options && options.as) {
      foreign_key = options.as + '_id';
    } else {
      foreign_key = inflector.foreign_key(target_model._name);
    }
    this_model.column(foreign_key, {
      connection: target_model._connection,
      required: options && options.required,
      type: types.RecordID,
    } as IColumnProperty);
    const column = options && options.as || inflector.underscore(target_model._name);
    const columnCache = '__cache_' + column;
    const columnGetter = '__getter_' + column;
    this_model._associations[column] = { type: 'belongsTo', target_model };
    Object.defineProperty(this_model.prototype, column, {
      get() {
        let getter: any;
        // getter must be created per instance due to __scope
        if (!this.hasOwnProperty(columnGetter)) {
          getter = async (reload: any) => {
            // this is getter.__scope in normal case (this_model_instance.target_model_name()),
            // but use getter.__scope for safety
            const self = getter.__scope;
            if ((!self[columnCache] || reload) && self[foreign_key]) {
              const record = await target_model.find(self[foreign_key]);
              self[columnCache] = record;
              return record;
            } else {
              return self[columnCache];
            }
          };
          getter.__scope = this;
          Object.defineProperty(this, columnCache, { value: null, writable: true });
          Object.defineProperty(this, columnGetter, { value: getter });
        }
        return this[columnGetter];
      },
    });
  }
(defined in /src/connection/index.ts:153)
private _connected: boolean = false;

Parameters

(None)

Returns

(Nothing)
(defined in /src/connection/index.ts:886)
private _hasMany(
    this_model: typeof BaseModel, target_model: typeof BaseModel,
    options?: IAssociationHasManyOptions,
  ) {
    let foreign_key: string;
    if (options && options.foreign_key) {
      foreign_key = options.foreign_key;
    } else if (options && options.as) {
      foreign_key = options.as + '_id';
    } else {
      foreign_key = inflector.foreign_key(this_model._name);
    }
    target_model.column(foreign_key, {
      connection: this_model._connection,
      type: types.RecordID,
    } as IColumnProperty);
    const integrity = options && options.integrity || 'ignore';
    target_model._integrities.push({ type: 'child_' + integrity, column: foreign_key, parent: this_model });
    this_model._integrities.push({ type: 'parent_' + integrity, column: foreign_key, child: target_model });
    const column = options && options.as || inflector.tableize(target_model._name);
    const columnCache = '__cache_' + column;
    const columnGetter = '__getter_' + column;
    this_model._associations[column] = { type: 'hasMany', target_model, foreign_key };
    Object.defineProperty(this_model.prototype, column, {
      get() {
        let getter: any;
        // getter must be created per instance due to __scope
        if (!this.hasOwnProperty(columnGetter)) {
          getter = async (reload?: boolean) => {
            // this is getter.__scope in normal case (this_model_instance.target_model_name()),
            // but use getter.__scope for safety
            const self = getter.__scope;
            if ((!self[columnCache] || reload) && self.id) {
              const records = await target_model.where(_.zipObject([foreign_key], [self.id]));
              self[columnCache] = records;
              return records;
            } else {
              return self[columnCache] || [];
            }
          };
          getter.build = (data: any) => {
            // this is getter, so use getter.__scope instead
            const self = getter.__scope;
            const new_object: any = new target_model(data);
            new_object[foreign_key] = self.id;
            if (!self[columnCache]) {
              self[columnCache] = [];
            }
            self[columnCache].push(new_object);
            return new_object;
          };
          getter.__scope = this;
          Object.defineProperty(this, columnCache, { value: null, writable: true });
          Object.defineProperty(this, columnGetter, { value: getter });
        }
        return this[columnGetter];
      },
    });
  }

Parameters

(None)

Returns

(Nothing)
(defined in /src/connection/index.ts:949)
private _hasOne(
    this_model: typeof BaseModel, target_model: typeof BaseModel,
    options?: IAssociationHasOneOptions,
  ) {
    let foreign_key: any;
    if (options && options.foreign_key) {
      foreign_key = options.foreign_key;
    } else if (options && options.as) {
      foreign_key = options.as + '_id';
    } else {
      foreign_key = inflector.foreign_key(this_model._name);
    }
    target_model.column(foreign_key, {
      connection: this_model._connection,
      type: types.RecordID,
    } as IColumnProperty);
    const integrity = options && options.integrity || 'ignore';
    target_model._integrities.push({ type: 'child_' + integrity, column: foreign_key, parent: this_model });
    this_model._integrities.push({ type: 'parent_' + integrity, column: foreign_key, child: target_model });
    const column = options && options.as || inflector.underscore(target_model._name);
    const columnCache = '__cache_' + column;
    const columnGetter = '__getter_' + column;
    this_model._associations[column] = { type: 'hasOne', target_model };
    Object.defineProperty(this_model.prototype, column, {
      get() {
        let getter: any;
        // getter must be created per instance due to __scope
        if (!this.hasOwnProperty(columnGetter)) {
          getter = async (reload: any) => {
            // this is getter.__scope in normal case (this_model_instance.target_model_name()),
            // but use getter.__scope for safety
            const self = getter.__scope;
            if ((!self[columnCache] || reload) && self.id) {
              const records = await target_model.where(_.zipObject([foreign_key], [self.id]));
              if (records.length > 1) {
                throw new Error('integrity error');
              }
              const record = records.length === 0 ? null : records[0];
              self[columnCache] = record;
              return record;
            } else {
              return self[columnCache];
            }
          };
          getter.__scope = this;
          Object.defineProperty(this, columnCache, { value: null, writable: true });
          Object.defineProperty(this, columnGetter, { value: getter });
        }
        return this[columnGetter];
      },
    });
  }
(defined in /src/connection/index.ts:132)
public _logger!: ILogger;
(defined in /src/connection/index.ts:144)
private _pending_associations: IAssociation[];
(defined in /src/connection/index.ts:138)
public _promise_connection: Promise<void>;
(defined in /src/connection/index.ts:141)
private _promise_schema_applied?: Promise<void>;
(defined in /src/connection/index.ts:150)
private _redis_cache_client: any;
(defined in /src/connection/index.ts:147)
private _redis_cache_settings: IRedisCacheSettings;
(defined in /src/connection/index.ts:135)
public _schema_changed: boolean = false;

Parameters

(None)

Returns

(Nothing)

See Also:

(defined in /src/connection/index.ts:490)
public addAssociation(association: IAssociation) {
    this._pending_associations.push(association);
    this._schema_changed = true;
  }

Parameters

(None)

Returns

(Nothing)
(defined in /src/connection/index.ts:571)
public applyAssociations() {
    this._initializeModels();
    for (const item of this._pending_associations) {
      const this_model = item.this_model;
      const options = item.options;
      let target_model: typeof BaseModel;
      if (typeof item.target_model_or_column === 'string') {
        let models;
        if (options && options.connection) {
          models = options.connection.models;
        } else {
          models = this.models;
        }
        let target_model_name: string;
        if (options && options.type) {
          target_model_name = options.type;
          options.as = item.target_model_or_column;
        } else if (item.type === 'belongsTo' || item.type === 'hasOne') {
          target_model_name = inflector.camelize(item.target_model_or_column);
        } else {
          target_model_name = inflector.classify(item.target_model_or_column);
        }
        if (!models[target_model_name]) {
          throw new Error(`model ${target_model_name} does not exist`);
        }
        target_model = models[target_model_name];
      } else {
        target_model = item.target_model_or_column;
      }
      this['_' + item.type](this_model, target_model, options);
    }
    this._pending_associations = [];
  }

Parameters

(None)

Returns

(Nothing)

See Also:

(defined in /src/connection/index.ts:235)
public async applySchemas(options: { verbose?: boolean } = {}) {
    this._initializeModels();
    if (!this._schema_changed) {
      return;
    }
    this.applyAssociations();
    if (this._applying_schemas) {
      return this._promise_schema_applied;
    }
    this._applying_schemas = true;
    this._checkArchive();
    if (options.verbose) {
      console.log('Applying schemas');
    }
    this._promise_schema_applied = this._promise_connection.then(async () => {
      try {
        const current = await this._adapter.getSchemas();

        // tslint:disable-next-line:forin
        for (const model in this.models) {
          const modelClass = this.models[model];
          const currentTable = current.tables && current.tables[modelClass.table_name];
          if (!currentTable || currentTable === 'NO SCHEMA') {
            continue;
          }
          // tslint:disable-next-line:forin
          for (const column in modelClass._schema) {
            const property = modelClass._schema[column];
            if (!currentTable[property._dbname_us]) {
              if (options.verbose) {
                console.log(`Adding column ${column} to ${modelClass.table_name}`);
              }
              await this._adapter.addColumn(model, property);
            }
          }
        }

        // tslint:disable-next-line:forin
        for (const model in this.models) {
          const modelClass = this.models[model];
          if (!current.tables[modelClass.table_name]) {
            if (options.verbose) {
              console.log(`Creating table ${modelClass.table_name}`);
            }
            await this._adapter.createTable(model);
          }
        }

        // tslint:disable-next-line:forin
        for (const model_name in this.models) {
          const modelClass = this.models[model_name];
          for (const index of modelClass._indexes) {
            if (!(current.indexes && current.indexes[modelClass.table_name]
              && current.indexes[modelClass.table_name][index.options.name!])) {
              if (options.verbose) {
                console.log(`Creating index on ${modelClass.table_name} ${Object.keys(index.columns)}`);
              }
              await this._adapter.createIndex(model_name, index);
            }
          }
        }

        // tslint:disable-next-line:forin
        for (const model in this.models) {
          const modelClass = this.models[model];
          for (const integrity of modelClass._integrities) {
            let type = '';
            if (integrity.type === 'child_nullify') {
              type = 'nullify';
            } else if (integrity.type === 'child_restrict') {
              type = 'restrict';
            } else if (integrity.type === 'child_delete') {
              type = 'delete';
            }
            if (type) {
              const current_foreign_key = current.foreign_keys && current.foreign_keys[modelClass.table_name]
                && current.foreign_keys[modelClass.table_name][integrity.column];
              if (!(current_foreign_key && current_foreign_key === integrity.parent.table_name)) {
                if (options.verbose) {
                  const table_name = modelClass.table_name;
                  const parent_table_name = integrity.parent.table_name;
                  console.log(`Adding foreign key ${table_name}.${integrity.column} to ${parent_table_name}`);
                }
                await this._adapter.createForeignKey(model, integrity.column, type, integrity.parent);
              }
            }
          }
        }
      } finally {
        if (options.verbose) {
          console.log('Applying schemas done');
        }
        this._applying_schemas = false;
        this._schema_changed = false;
      }
    });
    return this._promise_schema_applied;
  }

Parameters

(None)

Returns

(Nothing)
(defined in /src/connection/index.ts:216)
public close() {
    if (Connection.defaultConnection === this) {
      Connection.defaultConnection = undefined;
    }
    this._adapter.close();
    (this._adapter as any) = undefined;
  }

See Also:

(defined in /src/connection/index.ts:111)
public static defaultConnection?: Connection;

Parameters

(None)

Returns

(Nothing)
(defined in /src/connection/index.ts:409)
public async dropAllModels() {
    for (const model of this._getModelNamesByAssociationOrder()) {
      await this.models[model].drop();
    }
  }

Parameters

(None)

Returns

(Nothing)
(defined in /src/connection/index.ts:531)
public async fetchAssociated(records: any, column: any, select?: any, options?: any) {
    if ((select != null) && typeof select === 'object') {
      options = select;
      select = null;
    } else if (options == null) {
      options = {};
    }
    await this._checkSchemaApplied();
    const record = Array.isArray(records) ? records[0] : records;
    if (!record) {
      return;
    }
    let association;
    if (options.target_model) {
      association = {
        foreign_key: options.foreign_key,
        target_model: options.target_model,
        type: options.type || 'belongsTo',
      };
    } else if (options.model) {
      association = options.model._associations && options.model._associations[column];
    } else {
      association = record.constructor._associations && record.constructor._associations[column];
    }
    if (!association) {
      throw new Error(`unknown column '${column}'`);
    }
    if (association.type === 'belongsTo') {
      return await this._fetchAssociatedBelongsTo(records, association.target_model, column, select, options);
    } else if (association.type === 'hasMany') {
      return await this._fetchAssociatedHasMany(records, association.target_model, association.foreign_key,
        column, select, options);
    } else {
      throw new Error(`unknown column '${column}'`);
    }
  }

Parameters

(None)

Returns

(Nothing)
(defined in /src/connection/index.ts:498)
public async getInconsistencies() {
    await this._checkSchemaApplied();
    const result: any = {};
    const promises = Object.keys(this.models).map(async (model) => {
      const modelClass = this.models[model];
      const integrities = modelClass._integrities.filter((integrity) => integrity.type.substr(0, 7) === 'parent_');
      if (integrities.length > 0) {
        let records = await modelClass.select('').exec();
        const ids = records.map((record: any) => record.id);
        const sub_promises = integrities.map(async (integrity) => {
          const query = integrity.child.select('');
          query.where(_.zipObject([integrity.column], [{ $not: { $in: ids } }]));
          const property = integrity.child._schema[integrity.column];
          if (!property.required) {
            query.where(_.zipObject([integrity.column], [{ $not: null }]));
          }
          records = await query.exec();
          if (records.length > 0) {
            const array = result[integrity.child._name] || (result[integrity.child._name] = []);
            array.push(...records.map((record: any) => record.id));
            _.uniq(array);
          }
        });
        await Promise.all(sub_promises);
      }
    });
    await Promise.all(promises);
    return result;
  }

Parameters

(None)

Returns

(Nothing)
(defined in /src/connection/index.ts:418)
public log(model: string, type: string, data: object) { /**/ }

Parameters

(None)

Returns

(Nothing)
(defined in /src/connection/index.ts:427)
public async manipulate(commands: ManipulateCommand[]): Promise<{ [id: string]: any }> {
    this.log('<conn>', 'manipulate', commands);
    await this._checkSchemaApplied();
    const id_to_record_map: { [id: string]: any } = {};
    if (!Array.isArray(commands)) {
      commands = [commands];
    }
    for (const command of commands) {
      let key;
      let data;
      if (typeof command === 'object') {
        key = Object.keys(command);
        if (key.length === 1) {
          key = key[0];
          data = command[key];
        } else {
          key = undefined;
        }
      } else if (typeof command === 'string') {
        key = command;
      }
      if (!key) {
        throw new Error('invalid command: ' + JSON.stringify(command));
      } else if (key.substr(0, 7) === 'create_') {
        const model = key.substr(7);
        const id = data.id;
        delete data.id;
        this._manipulateConvertIds(id_to_record_map, model, data);
        const record = await this._manipulateCreate(model, data);
        if (id) {
          id_to_record_map[id] = record;
        }
      } else if (key.substr(0, 7) === 'delete_') {
        const model = key.substr(7);
        await this._manipulateDelete(model, data);
      } else if (key === 'deleteAll') {
        await this._manipulateDeleteAllModels();
      } else if (key.substr(0, 5) === 'drop_') {
        const model = key.substr(5);
        await this._manipulateDropModel(model);
      } else if (key === 'dropAll') {
        await this._manipulateDropAllModels();
      } else if (key.substr(0, 5) === 'find_') {
        const model = key.substr(5);
        const id = data.id;
        delete data.id;
        if (!id) {
          continue;
        }
        const records = await this._manipulateFind(model, data);
        id_to_record_map[id] = records;
      } else {
        throw new Error('unknown command: ' + key);
      }
    }
    return id_to_record_map;
  }

Parameters

(None)

Returns

(Nothing)
(defined in /src/connection/index.ts:227)
public model(name: string, schema: IModelSchema): typeof BaseModel {
    return BaseModel.newModel(this, name, schema);
  }

See Also:

(defined in /src/connection/index.ts:129)
public models: { [name: string]: typeof BaseModel };

Parameters

(None)

Returns

(Nothing)
(defined in /src/connection/index.ts:198)
public setLogger(logger?: 'console' | 'color-console' | ILogger) {
    if (logger) {
      if (logger === 'console') {
        this._logger = new ConsoleLogger();
      } else if (logger === 'color-console') {
        this._logger = new ColorConsoleLogger();
      } else {
        this._logger = logger;
      }
    } else {
      this._logger = new EmptyLogger();
    }
  }
Fork me on GitHub