UNPKG

@sequelize/core

Version:

Sequelize is a promise-based Node.js ORM tool for Postgres, MySQL, MariaDB, SQLite, Microsoft SQL Server, Amazon Redshift, Snowflake’s Data Cloud, Db2, and IBM i. It features solid transaction support, relations, eager and lazy loading, read replication a

1,250 lines 144 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var model_exports = {}; __export(model_exports, { Model: () => Model }); module.exports = __toCommonJS(model_exports); var import_utils = require("@sequelize/utils"); var import_dottie = __toESM(require("dottie")); var import_assignWith = __toESM(require("lodash/assignWith")); var import_cloneDeep = __toESM(require("lodash/cloneDeep")); var import_defaults = __toESM(require("lodash/defaults")); var import_difference = __toESM(require("lodash/difference")); var import_each = __toESM(require("lodash/each")); var import_flattenDepth = __toESM(require("lodash/flattenDepth")); var import_forEach = __toESM(require("lodash/forEach")); var import_forIn = __toESM(require("lodash/forIn")); var import_get = __toESM(require("lodash/get")); var import_intersection = __toESM(require("lodash/intersection")); var import_isEmpty = __toESM(require("lodash/isEmpty")); var import_isEqual = __toESM(require("lodash/isEqual")); var import_isObject = __toESM(require("lodash/isObject")); var import_isPlainObject = __toESM(require("lodash/isPlainObject")); var import_mapValues = __toESM(require("lodash/mapValues")); var import_omit = __toESM(require("lodash/omit")); var import_omitBy = __toESM(require("lodash/omitBy")); var import_pick = __toESM(require("lodash/pick")); var import_pickBy = __toESM(require("lodash/pickBy")); var import_remove = __toESM(require("lodash/remove")); var import_union = __toESM(require("lodash/union")); var import_unionBy = __toESM(require("lodash/unionBy")); var import_uniq = __toESM(require("lodash/uniq")); var import_without = __toESM(require("lodash/without")); var import_node_assert = __toESM(require("node:assert")); var import_node_util = __toESM(require("node:util")); var import_data_types = require("./abstract-dialect/data-types"); var import_associations = require("./associations"); var import_helpers = require("./associations/helpers"); var DataTypes = __toESM(require("./data-types")); var SequelizeErrors = __toESM(require("./errors")); var import_base_sql_expression = require("./expression-builders/base-sql-expression.js"); var import_instance_validator = require("./instance-validator"); var import_model_internals = require("./model-internals"); var import_model_typescript = require("./model-typescript"); var import_operators = require("./operators"); var import_query_types = require("./query-types"); var import_array = require("./utils/array"); var import_deprecations = require("./utils/deprecations"); var import_dialect = require("./utils/dialect"); var import_format = require("./utils/format"); var import_logger = require("./utils/logger"); var import_model_utils = require("./utils/model-utils"); var import_object = require("./utils/object"); var import_query_builder_utils = require("./utils/query-builder-utils"); var import_string = require("./utils/string.js"); var import_where = require("./utils/where.js"); const validQueryKeywords = /* @__PURE__ */ new Set([ "where", "attributes", "paranoid", "include", "order", "limit", "offset", "transaction", "lock", "raw", "logging", "benchmark", "having", "searchPath", "rejectOnEmpty", "plain", "scope", "group", "through", "defaults", "distinct", "primary", "exception", "type", "hooks", "force", "name" ]); const nonCascadingOptions = [ "include", "attributes", "originalAttributes", "order", "where", "limit", "offset", "plain", "group", "having" ]; const CONSTRUCTOR_SECRET = Symbol("model-constructor-secret"); class Model extends import_model_typescript.ModelTypeScript { /** * Builds a new model instance. * * Cannot be used directly. Use {@link Model.build} instead. * * @param {object} [values={}] an object of key value pairs * @param {object} [options] instance construction options * @param {boolean} [options.raw=false] If set to true, values will ignore field and virtual setters. * @param {boolean} [options.isNewRecord=true] Is this a new record * @param {Array} [options.include] an array of include options - Used to build prefetched/included model instances. See * `set` * @param {symbol} secret Secret used to ensure Model.build is used instead of new Model(). Don't forget to pass it up if * you define a custom constructor. */ constructor(values = {}, options = {}, secret) { super(); if (secret !== CONSTRUCTOR_SECRET) { (0, import_deprecations.noNewModel)(); } this.constructor.assertIsInitialized(); options = { isNewRecord: true, _schema: this.modelDefinition.table.schema, _schemaDelimiter: this.modelDefinition.table.delimiter, ...options, model: this.constructor }; if (options.attributes) { options.attributes = options.attributes.map((attribute) => { return Array.isArray(attribute) ? attribute[1] : attribute; }); } if (!options.includeValidated) { this.constructor._conformIncludes(options, this.constructor); if (options.include) { this.constructor._expandIncludeAll(options); (0, import_model_internals._validateIncludedElements)(options); } } this.dataValues = {}; this._previousDataValues = {}; this.uniqno = 1; this._changed = /* @__PURE__ */ new Set(); this._options = (0, import_omit.default)(options, ["comesFromDatabase"]); this.isNewRecord = options.isNewRecord; this._initValues(values, options); } _initValues(values, options) { values = { ...values }; if (options.isNewRecord) { const modelDefinition = this.modelDefinition; const defaults2 = modelDefinition.defaultValues.size > 0 ? (0, import_mapValues.default)((0, import_object.getObjectFromMap)(modelDefinition.defaultValues), (getDefaultValue) => { const value = getDefaultValue(); return value && value instanceof import_base_sql_expression.BaseSqlExpression ? value : (0, import_cloneDeep.default)(value); }) : /* @__PURE__ */ Object.create(null); if (modelDefinition.primaryKeysAttributeNames.size > 0) { for (const primaryKeyAttribute of modelDefinition.primaryKeysAttributeNames) { if (!Object.hasOwn(defaults2, primaryKeyAttribute)) { defaults2[primaryKeyAttribute] = null; } } } const { createdAt: createdAtAttrName, deletedAt: deletedAtAttrName, updatedAt: updatedAtAttrName } = modelDefinition.timestampAttributeNames; if (createdAtAttrName && defaults2[createdAtAttrName]) { this.dataValues[createdAtAttrName] = (0, import_dialect.toDefaultValue)(defaults2[createdAtAttrName]); delete defaults2[createdAtAttrName]; } if (updatedAtAttrName && defaults2[updatedAtAttrName]) { this.dataValues[updatedAtAttrName] = (0, import_dialect.toDefaultValue)(defaults2[updatedAtAttrName]); delete defaults2[updatedAtAttrName]; } if (deletedAtAttrName && defaults2[deletedAtAttrName]) { this.dataValues[deletedAtAttrName] = (0, import_dialect.toDefaultValue)(defaults2[deletedAtAttrName]); delete defaults2[deletedAtAttrName]; } for (const key in defaults2) { if (values[key] === void 0) { this.set(key, (0, import_dialect.toDefaultValue)(defaults2[key]), { raw: true }); delete values[key]; } } } this.set(values, options); } // validateIncludedElements should have been called before this method static _paranoidClause(model, options = {}) { if (options.include) { for (const include of options.include) { this._paranoidClause(include.model, include); } } if ((0, import_get.default)(options, "groupedLimit.on.through.model.options.paranoid")) { const throughModel = (0, import_get.default)(options, "groupedLimit.on.through.model"); if (throughModel) { options.groupedLimit.through = this._paranoidClause( throughModel, options.groupedLimit.through ); } } if (!model.options.timestamps || !model.options.paranoid || options.paranoid === false) { return options; } const modelDefinition = model.modelDefinition; const deletedAtCol = modelDefinition.timestampAttributeNames.deletedAt; const deletedAtAttribute = modelDefinition.attributes.get(deletedAtCol); const deletedAtObject = /* @__PURE__ */ Object.create(null); let deletedAtDefaultValue = deletedAtAttribute.defaultValue ?? null; deletedAtDefaultValue ||= { [import_operators.Op.eq]: null }; deletedAtObject[deletedAtAttribute.field || deletedAtCol] = deletedAtDefaultValue; if ((0, import_query_builder_utils.isWhereEmpty)(options.where)) { options.where = deletedAtObject; } else { options.where = { [import_operators.Op.and]: [deletedAtObject, options.where] }; } return options; } /** * Returns the attributes of the model. * * @returns {object|any} */ static getAttributes() { return (0, import_object.getObjectFromMap)(this.modelDefinition.attributes); } get validators() { throw new Error( "Model#validators has been removed. Use the validators option on Model.modelDefinition.attributes instead." ); } static get _schema() { throw new Error("Model._schema has been removed. Use Model.modelDefinition instead."); } static get _schemaDelimiter() { throw new Error("Model._schemaDelimiter has been removed. Use Model.modelDefinition instead."); } static _getAssociationDebugList() { return `The following associations are defined on "${this.name}": ${Object.keys( this.associations ).map((associationName) => `"${associationName}"`).join(", ")}`; } static getAssociation(associationName) { if (!Object.hasOwn(this.associations, associationName)) { throw new Error(`Association with alias "${associationName}" does not exist on ${this.name}. ${this._getAssociationDebugList()}`); } return this.associations[associationName]; } static _getAssociationsByModel(model) { const matchingAssociations = []; for (const associationName of Object.keys(this.associations)) { const association = this.associations[associationName]; if (!(0, import_model_utils.isSameInitialModel)(association.target, model)) { continue; } matchingAssociations.push(association); } return matchingAssociations; } static _normalizeIncludes(options, associationOwner) { this._conformIncludes(options, associationOwner); this._expandIncludeAll(options, associationOwner); } static _conformIncludes(options, associationOwner) { if (!options.include) { return; } if (!Array.isArray(options.include)) { options.include = [options.include]; } else if (options.include.length === 0) { delete options.include; return; } options.include = options.include.map( (include) => this._conformInclude(include, associationOwner) ); } static _conformInclude(include, associationOwner) { if (!include) { (0, import_model_internals.throwInvalidInclude)(include); } if (!associationOwner || !(0, import_model_utils.isModelStatic)(associationOwner)) { throw new TypeError( `Sequelize sanity check: associationOwner must be a model subclass. Got ${import_node_util.default.inspect(associationOwner)} (${typeof associationOwner})` ); } if (include._pseudo) { return include; } if (include.all) { this._conformIncludes(include, associationOwner); return include; } if (!(0, import_isPlainObject.default)(include)) { if ((0, import_model_utils.isModelStatic)(include)) { include = { model: include }; } else { include = { association: include }; } } else { include = { ...include }; } if (include.as && !include.association) { include.association = include.as; } if (!include.association) { include.association = associationOwner.getAssociationWithModel(include.model, include.as); } else if (typeof include.association === "string") { include.association = associationOwner.getAssociation(include.association); } else { if (!(include.association instanceof import_associations.Association)) { (0, import_model_internals.throwInvalidInclude)(include); } if (!(0, import_model_utils.isSameInitialModel)(include.association.source, associationOwner)) { throw new Error(`Invalid Include received: the specified association "${include.association.as}" is not defined on model "${associationOwner.name}". It is owned by model "${include.association.source.name}". ${associationOwner._getAssociationDebugList()}`); } } if (!include.model) { include.model = include.association.target; } if (!(0, import_model_utils.isSameInitialModel)(include.model, include.association.target)) { throw new TypeError( `Invalid Include received: the specified "model" option ("${include.model.name}") does not match the target ("${include.association.target.name}") of the "${include.association.as}" association.` ); } if (!include.as) { include.as = include.association.as; } this._conformIncludes(include, include.model); return include; } static _expandIncludeAllElement(includes, include) { let { all, nested, ...includeOptions } = include; if (Object.keys(includeOptions).length > 0) { throw new Error( '"include: { all: true }" does not allow extra options (except for "nested") because they are unsafe. Select includes one by one if you want to specify more options.' ); } if (all !== true) { if (!Array.isArray(all)) { all = [all]; } const validTypes = { BelongsTo: true, HasOne: true, HasMany: true, One: ["BelongsTo", "HasOne"], Has: ["HasOne", "HasMany"], Many: ["HasMany"] }; for (let i = 0; i < all.length; i++) { const type = all[i]; if (type === "All") { all = true; break; } const types = validTypes[type]; if (!types) { throw new SequelizeErrors.EagerLoadingError( `include all '${type}' is not valid - must be BelongsTo, HasOne, HasMany, One, Has, Many or All` ); } if (types !== true) { all.splice(i, 1); i--; for (const type_ of types) { if (!all.includes(type_)) { all.unshift(type_); i++; } } } } } const visitedModels = []; const addAllIncludes = (parent, includes2) => { (0, import_forEach.default)(parent.associations, (association) => { if (all !== true && !all.includes(association.associationType)) { return; } if (association.parentAssociation instanceof import_associations.BelongsToManyAssociation && association === association.parentAssociation.fromSourceToThroughOne) { return; } if (includes2.some((existingInclude) => existingInclude.association === association)) { return; } const newInclude = { association }; const model = association.target; if (nested && visitedModels.includes(model)) { return; } const normalizedNewInclude = this._conformInclude(newInclude, parent); includes2.push(normalizedNewInclude); if (nested) { visitedModels.push(parent); const subIncludes = []; addAllIncludes(model, subIncludes); visitedModels.pop(); if (subIncludes.length > 0) { normalizedNewInclude.include = subIncludes; } } }); }; addAllIncludes(this, includes); } static _validateIncludedElement(include, tableNames, options) { tableNames[include.model.table] = true; if (include.attributes && !options.raw) { include.model._expandAttributes(include); include.originalAttributes = include.model._injectDependentVirtualAttributes( include.attributes ); include = (0, import_format.mapFinderOptions)(include, include.model); if (include.attributes.length > 0) { (0, import_each.default)(include.model.primaryKeys, (attr, key) => { if (!include.attributes.some((includeAttr) => { if (attr.field !== key) { return Array.isArray(includeAttr) && includeAttr[0] === attr.field && includeAttr[1] === key; } return includeAttr === key; })) { include.attributes.unshift(key); } }); } } else { include = (0, import_format.mapFinderOptions)(include, include.model); } if (include._pseudo) { if (!include.attributes) { include.attributes = Object.keys(include.model.tableAttributes); } return (0, import_format.mapFinderOptions)(include, include.model); } const association = include.association || this.getAssociationWithModel(include.model, include.as); include.association = association; include.as ||= association.as; if (association instanceof import_associations.BelongsToManyAssociation) { if (!include.include) { include.include = []; } const through = include.association.through; include.through = (0, import_defaults.default)(include.through || {}, { model: through.model, // Through Models are a special case: we always want to load them as the name of the model, not the name of the association as: through.model.name, association: { isSingleAssociation: true }, _pseudo: true, parent: include }); if (through.scope) { include.through.where = include.through.where ? { [import_operators.Op.and]: [include.through.where, through.scope] } : through.scope; } include.include.push(include.through); tableNames[through.tableName] = true; } let model; if (include.model.scoped === true) { model = include.model; } else { model = include.association.target.name === include.model.name ? include.association.target : include.association.source; } model._injectScope(include); if (!include.attributes) { include.attributes = Object.keys(include.model.tableAttributes); } include = (0, import_format.mapFinderOptions)(include, include.model); if (include.required === void 0) { include.required = Boolean(include.where); } if (include.association.scope) { include.where = include.where ? { [import_operators.Op.and]: [include.where, include.association.scope] } : include.association.scope; } if (include.limit && include.separate === void 0) { include.separate = true; } if (include.separate === true) { if (!(include.association instanceof import_associations.HasManyAssociation)) { throw new TypeError("Only HasMany associations support include.separate"); } include.duplicating = false; if (options.attributes && options.attributes.length > 0 && !(0, import_flattenDepth.default)(options.attributes, 2).includes(association.sourceKey)) { options.attributes.push(association.sourceKey); } if (include.attributes && include.attributes.length > 0 && !(0, import_flattenDepth.default)(include.attributes, 2).includes(association.foreignKey)) { include.attributes.push(association.foreignKey); } } if (Object.hasOwn(include, "include")) { (0, import_model_internals._validateIncludedElements)(include, tableNames); } return include; } static _expandIncludeAll(options, associationOwner) { const includes = options.include; if (!includes) { return; } for (let index = 0; index < includes.length; index++) { const include = includes[index]; if (include.all) { includes.splice(index, 1); index--; associationOwner._expandIncludeAllElement(includes, include); } } for (const include of includes) { this._expandIncludeAll(include, include.model); } } static _baseMerge(...args) { (0, import_assignWith.default)(...args); return args[0]; } static _mergeFunction(objValue, srcValue, key) { if (key === "include") { return (0, import_model_internals.combineIncludes)(objValue, srcValue); } if (Array.isArray(objValue) && Array.isArray(srcValue)) { return (0, import_union.default)(objValue, srcValue); } if (["where", "having"].includes(key)) { return combineWheresWithAnd(objValue, srcValue); } else if (key === "attributes" && (0, import_isPlainObject.default)(objValue) && (0, import_isPlainObject.default)(srcValue)) { return (0, import_assignWith.default)(objValue, srcValue, (objValue2, srcValue2) => { if (Array.isArray(objValue2) && Array.isArray(srcValue2)) { return (0, import_union.default)(objValue2, srcValue2); } }); } if (srcValue) { return (0, import_object.cloneDeep)(srcValue, true); } return srcValue === void 0 ? objValue : srcValue; } static _assignOptions(...args) { return this._baseMerge(...args, this._mergeFunction); } static _defaultsOptions(target, opts) { return this._baseMerge(target, opts, (srcValue, objValue, key) => { return this._mergeFunction(objValue, srcValue, key); }); } /** * Remove attribute from model definition. * Only use if you know what you're doing. * * @param {string} attribute name of attribute to remove */ static removeAttribute(attribute) { delete this.modelDefinition.rawAttributes[attribute]; this.modelDefinition.refreshAttributes(); } /** * Merges new attributes with the existing ones. * Only use if you know what you're doing. * * Warning: Attributes are not replaced, they are merged. * * @param {object} newAttributes */ static mergeAttributesDefault(newAttributes) { const rawAttributes = this.modelDefinition.rawAttributes; (0, import_object.mergeDefaults)(rawAttributes, newAttributes); this.modelDefinition.refreshAttributes(); return rawAttributes; } /** * Sync this Model to the DB, that is create the table. * See {@link Sequelize#sync} for options * * @param {object} [options] sync options * * @returns {Promise<Model>} */ static async sync(options) { options = { ...this.options, ...options }; options.hooks = options.hooks === void 0 ? true : Boolean(options.hooks); const modelDefinition = this.modelDefinition; const physicalAttributes = (0, import_object.getObjectFromMap)(modelDefinition.physicalAttributes); const columnDefs = (0, import_object.getObjectFromMap)(modelDefinition.columns); if (options.hooks) { await this.hooks.runAsync("beforeSync", options); } const tableName = { ...this.table }; if (options.schema && options.schema !== tableName.schema) { if (tableName.schema !== this.sequelize.dialect.getDefaultSchema()) { throw new Error( `The "schema" option in sync can only be used on models that do not already specify a schema, or that are using the default schema. Model ${this.name} already specifies schema ${tableName.schema}` ); } tableName.schema = options.schema; } delete options.schema; let tableExists; if (options.force) { await this.drop({ ...options, cascade: this.sequelize.dialect.supports.dropTable.cascade || void 0 }); tableExists = false; } else { tableExists = await this.queryInterface.tableExists(tableName, options); } if (!tableExists) { await this.queryInterface.createTable(tableName, physicalAttributes, options, this); } else { await this.queryInterface.ensureEnums(tableName, physicalAttributes, options, this); } if (tableExists && options.alter) { const tableInfos = await Promise.all([ this.queryInterface.describeTable(tableName, options), this.queryInterface.showConstraints(tableName, { ...options, constraintType: "FOREIGN KEY" }) ]); const columns = tableInfos[0]; const foreignKeyReferences = tableInfos[1]; const removedConstraints = {}; for (const columnName in physicalAttributes) { if (!Object.hasOwn(physicalAttributes, columnName)) { continue; } if (!columns[columnName] && !columns[physicalAttributes[columnName].field]) { await this.queryInterface.addColumn( tableName, physicalAttributes[columnName].field || columnName, physicalAttributes[columnName], options ); } } if (options.alter === true || typeof options.alter === "object" && options.alter.drop !== false) { for (const columnName in columns) { if (!Object.hasOwn(columns, columnName)) { continue; } const currentAttribute = columnDefs[columnName]; if (!currentAttribute) { await this.queryInterface.removeColumn(tableName, columnName, options); continue; } if (currentAttribute.primaryKey) { continue; } const references = currentAttribute.references; if (currentAttribute.references) { const schema = tableName.schema; const database = this.sequelize.options.replication.write.database; const foreignReferenceSchema = currentAttribute.references.table.schema; const foreignReferenceTableName = typeof references.table === "object" ? references.table.tableName : references.table; for (const foreignKeyReference of foreignKeyReferences) { const constraintName = foreignKeyReference.constraintName; if (constraintName && (foreignKeyReference.tableCatalog ? foreignKeyReference.tableCatalog === database : true) && (schema ? foreignKeyReference.tableSchema === schema : true) && foreignKeyReference.referencedTableName === foreignReferenceTableName && foreignKeyReference.referencedColumnNames.includes(references.key) && (foreignReferenceSchema ? foreignKeyReference.referencedTableSchema === foreignReferenceSchema : true) && !removedConstraints[constraintName] || this.sequelize.dialect.name === "ibmi") { await this.queryInterface.removeConstraint(tableName, constraintName, options); removedConstraints[constraintName] = true; } } } await this.queryInterface.changeColumn(tableName, columnName, currentAttribute, options); } } } const existingIndexes = await this.queryInterface.showIndex(tableName, options); const missingIndexes = this.getIndexes().filter((item1) => !existingIndexes.some((item2) => item1.name === item2.name)).sort((index1, index2) => { if (this.sequelize.dialect.name === "postgres") { if (index1.concurrently === true) { return 1; } if (index2.concurrently === true) { return -1; } } return 0; }); for (const index of missingIndexes) { await this.queryInterface.addIndex(tableName, index, options); } if (options.hooks) { await this.hooks.runAsync("afterSync", options); } return this; } /** * Drop the table represented by this Model * * @param {object} [options] drop options * @returns {Promise} */ static async drop(options) { return await this.queryInterface.dropTable(this, options); } /** * @param {object | string} schema * @deprecated use {@link Sequelize#dropSchema} or {@link QueryInterface#dropSchema} */ // TODO [>=2023-01-01]: remove me in Sequelize >= 8 static async dropSchema(schema) { (0, import_deprecations.noModelDropSchema)(); return await this.queryInterface.dropSchema(schema); } /** * Returns a copy of this model with the corresponding table located in the specified schema. * * For postgres, this will actually place the schema in front of the table name (`"schema"."tableName"`), * while the schema will be prepended to the table name for mysql and sqlite (`'schema.tablename'`). * * This method is intended for use cases where the same model is needed in multiple schemas. * In such a use case it is important to call {@link Model.sync} (or use migrations!) for each model created by this method * to ensure the models are created in the correct schema. * * If a single default schema per model is needed, set the {@link ModelOptions.schema} option instead. * * @param {string|object} schema The name of the schema * * @returns {Model} */ static withSchema(schema) { if (arguments.length > 1) { throw new TypeError( "Unlike Model.schema, Model.withSchema only accepts 1 argument which may be either a string or an option bag." ); } const schemaOptions = typeof schema === "string" || schema === null ? { schema } : schema; schemaOptions.schema ||= this.sequelize.options.schema || this.sequelize.dialect.getDefaultSchema(); return this.getInitialModel()._withScopeAndSchema(schemaOptions, this._scope, this._scopeNames); } // TODO [>=2023-01-01]: remove in Sequelize 8 static schema(schema, options) { (0, import_deprecations.schemaRenamedToWithSchema)(); return this.withSchema({ schema, schemaDelimiter: typeof options === "string" ? options : options?.schemaDelimiter }); } /** * Returns the initial model, the one returned by {@link Model.init} or {@link Sequelize#define}, * before any scope or schema was applied. */ static getInitialModel() { return this._initialModel ?? this; } /** * Add a new scope to the model * * This is especially useful for adding scopes with includes, when the model you want to * include is not available at the time this model is defined. * * By default, this will throw an error if a scope with that name already exists. * Use {@link AddScopeOptions.override} in the options object to silence this error. * * See {@link https://sequelize.org/docs/v7/other-topics/scopes/} to learn more about scopes. * * @param {string} name The name of the scope. Use `defaultScope` to override the default scope * @param {object|Function} scope scope or options * @param {object} [options] scope options */ static addScope(name, scope, options) { if (this !== this.getInitialModel()) { throw new Error( `Model.addScope can only be called on the initial model. Use "${this.name}.getInitialModel()" to access the initial model.` ); } options = { override: false, ...options }; if ((name === "defaultScope" && Object.keys(this.options.defaultScope).length > 0 || name in this.options.scopes) && options.override === false) { throw new Error( `The scope ${name} already exists. Pass { override: true } as options to silence this error` ); } if (name === "defaultScope") { this.options.defaultScope = this._scope = scope; } else { this.options.scopes[name] = scope; } } // TODO [>=2023-01-01]: remove in Sequelize 8 static scope(...options) { (0, import_deprecations.scopeRenamedToWithScope)(); return this.withScope(...options); } /** * Creates a copy of this model, with one or more scopes applied. * * See {@link https://sequelize.org/docs/v7/other-topics/scopes/} to learn more about scopes. * * @param {?Array|object|string} [scopes] The scope(s) to apply. Scopes can either be passed as consecutive arguments, or * as an array of arguments. To apply simple scopes and scope functions with no arguments, pass them as strings. For * scope function, pass an object, with a `method` property. The value can either be a string, if the method does not * take any arguments, or an array, where the first element is the name of the method, and consecutive elements are * arguments to that method. Pass null to remove all scopes, including the default. * * @example To invoke scope functions you can do * ```ts * Model.withScope({ method: ['complexFunction', 'dan@sequelize.com', 42]}).findAll() * // WHERE email like 'dan@sequelize.com%' AND access_level >= 42 * ``` * * @returns {Model} A reference to the model, with the scope(s) applied. Calling scope again on the returned model will * clear the previous scope. */ static withScope(...scopes) { scopes = scopes.flat().filter(Boolean); const initialModel = this.getInitialModel(); const mergedScope = {}; const scopeNames = []; for (const option of scopes) { let scope = null; let scopeName = null; if ((0, import_isPlainObject.default)(option)) { if (option.method) { if (Array.isArray(option.method) && Boolean(initialModel.options.scopes[option.method[0]])) { scopeName = option.method[0]; scope = initialModel.options.scopes[scopeName].apply( initialModel, option.method.slice(1) ); } else if (initialModel.options.scopes[option.method]) { scopeName = option.method; scope = initialModel.options.scopes[scopeName].apply(initialModel); } } else { scope = option; } } else if (option === "defaultScope" && (0, import_isPlainObject.default)(initialModel.options.defaultScope)) { scope = initialModel.options.defaultScope; } else { scopeName = option; scope = initialModel.options.scopes[scopeName]; if (typeof scope === "function") { scope = scope(); } } if (!scope) { throw new SequelizeErrors.SequelizeScopeError( `"${this.name}.withScope()" has been called with an invalid scope: "${scopeName}" does not exist.` ); } this._conformIncludes(scope, this); this._assignOptions(mergedScope, (0, import_object.cloneDeep)(scope) ?? {}); scopeNames.push(scopeName ? scopeName : "defaultScope"); } const modelDefinition = this.modelDefinition; return initialModel._withScopeAndSchema( { schema: modelDefinition.table.schema || "", schemaDelimiter: modelDefinition.table.delimiter || "" }, mergedScope, scopeNames ); } // TODO [>=2023-01-01]: remove in Sequelize 8 /** * Returns a model without scope. The default scope is also omitted. * * See {@link https://sequelize.org/docs/v7/other-topics/scopes/} to learn more about scopes. */ static unscoped() { (0, import_deprecations.scopeRenamedToWithScope)(); return this.withoutScope(); } /** * Returns a model without scope. The default scope is also omitted. * * See {@link https://sequelize.org/docs/v7/other-topics/scopes/} to learn more about scopes. */ static withoutScope() { return this.withScope(null); } /** * Returns the base model, with its initial scope. */ static withInitialScope() { const initialModel = this.getInitialModel(); const modelDefinition = this.modelDefinition; const initialModelDefinition = initialModel.modelDefinition; if (modelDefinition.table.schema !== initialModelDefinition.table.schema || modelDefinition.table.delimiter !== initialModelDefinition.table.delimiter) { return initialModel.withSchema({ schema: modelDefinition.table.schema, schemaDelimiter: modelDefinition.table.delimiter }); } return initialModel; } static _withScopeAndSchema(schemaOptions, mergedScope, scopeNames) { if (!this._modelVariantRefs) { this._modelVariantRefs = /* @__PURE__ */ new Set([new WeakRef(this)]); } const newTable = this.queryGenerator.extractTableDetails({ tableName: this.modelDefinition.table.tableName, schema: schemaOptions.schema, delimiter: schemaOptions.delimiter }); for (const modelVariantRef of this._modelVariantRefs) { const modelVariant = modelVariantRef.deref(); if (!modelVariant) { this._modelVariantRefs.delete(modelVariantRef); continue; } const variantTable = modelVariant.table; if (variantTable.schema !== newTable.schema) { continue; } if (variantTable.delimiter !== newTable.delimiter) { continue; } if (!(0, import_isEqual.default)(modelVariant._scopeNames, scopeNames)) { continue; } if (!(0, import_isEqual.default)(modelVariant._scope, mergedScope)) { continue; } return modelVariant; } const clone = this._createModelVariant({ schema: schemaOptions.schema, schemaDelimiter: schemaOptions.schemaDelimiter }); this._modelVariantRefs.add(new WeakRef(clone)); clone._scope = mergedScope; clone._scopeNames = scopeNames; if (scopeNames.length !== 1 || scopeNames[0] !== "defaultScope") { clone.scoped = true; } return clone; } static _createModelVariant(optionOverrides) { const model = class extends this { }; model._initialModel = this; Object.defineProperty(model, "name", { value: this.name }); model.init(this.modelDefinition.rawAttributes, { ...this.options, ...optionOverrides }); Object.assign(model.modelDefinition.associations, this.modelDefinition.associations); return model; } /** * Search for multiple instances. * See {@link https://sequelize.org/docs/v7/core-concepts/model-querying-basics/} for more information about querying. * * __Example of a simple search:__ * ```js * Model.findAll({ * where: { * attr1: 42, * attr2: 'cake' * } * }) * ``` * * See also: * - {@link Model.findOne} * - {@link Sequelize#query} * * @param {object} options * @returns {Promise} A promise that will resolve with the array containing the results of the SELECT query. */ static async findAll(options) { if (options !== void 0 && !(0, import_isPlainObject.default)(options)) { throw new SequelizeErrors.QueryError( "The argument passed to findAll must be an options object, use findByPk if you wish to pass a single primary key value" ); } if (options !== void 0 && options.attributes && !Array.isArray(options.attributes) && !(0, import_isPlainObject.default)(options.attributes)) { throw new SequelizeErrors.QueryError( "The attributes option must be an array of column names or an object" ); } const modelDefinition = this.modelDefinition; this._warnOnInvalidOptions(options, Object.keys(modelDefinition.attributes)); const tableNames = {}; tableNames[this.table] = true; options = (0, import_object.cloneDeep)(options) ?? {}; (0, import_model_internals.setTransactionFromCls)(options, this.sequelize); (0, import_defaults.default)(options, { hooks: true, model: this }); options.rejectOnEmpty = Object.hasOwn(options, "rejectOnEmpty") ? options.rejectOnEmpty : this.options.rejectOnEmpty; this._conformIncludes(options, this); this._injectScope(options); if (options.hooks) { await this.hooks.runAsync("beforeFind", options); this._conformIncludes(options, this); } this._expandAttributes(options); this._expandIncludeAll(options, options.model); if (options.hooks) { await this.hooks.runAsync("beforeFindAfterExpandIncludeAll", options); } options.originalAttributes = this._injectDependentVirtualAttributes(options.attributes); if (options.include) { options.hasJoin = true; (0, import_model_internals._validateIncludedElements)(options, tableNames); if (options.attributes && !options.raw && this.primaryKeyAttribute && !options.attributes.includes(this.primaryKeyAttribute) && (!options.group || !options.hasSingleAssociation || options.hasMultiAssociation)) { options.attributes = [this.primaryKeyAttribute].concat(options.attributes); } } if (!options.attributes) { options.attributes = Array.from(modelDefinition.attributes.keys()); options.originalAttributes = this._injectDependentVirtualAttributes(options.attributes); } (0, import_format.mapFinderOptions)(options, this); options = this._paranoidClause(this, options); if (options.hooks) { await this.hooks.runAsync("beforeFindAfterOptions", options); } const selectOptions = { ...options, tableNames: Object.keys(tableNames) }; const results = await this.queryInterface.select(this, this.table, selectOptions); if (options.hooks) { await this.hooks.runAsync("afterFind", results, options); } if ((0, import_isEmpty.default)(results) && options.rejectOnEmpty) { if (typeof options.rejectOnEmpty === "function") { throw new options.rejectOnEmpty(); } if (typeof options.rejectOnEmpty === "object") { throw options.rejectOnEmpty; } throw new SequelizeErrors.EmptyResultError(); } return await Model._findSeparate(results, options); } static _warnOnInvalidOptions(options, validColumnNames) { if (!(0, import_isPlainObject.default)(options)) { return; } const unrecognizedOptions = Object.keys(options).filter((k) => !validQueryKeywords.has(k)); const unexpectedModelAttributes = (0, import_intersection.default)(unrecognizedOptions, validColumnNames); if (!options.where && unexpectedModelAttributes.length > 0) { import_logger.logger.warn( `Model attributes (${unexpectedModelAttributes.join(", ")}) passed into finder method options of model ${this.name}, but the options.where object is empty. Did you forget to use options.where?` ); } } static _injectDependentVirtualAttributes(attributes) { const modelDefinition = this.modelDefinition; if (modelDefinition.virtualAttributeNames.size === 0) { return attributes; } if (!attributes || !Array.isArray(attributes)) { return attributes; } for (const attribute of attributes) { if (modelDefinition.virtualAttributeNames.has(attribute) && modelDefinition.attributes.get(attribute).type.attributeDependencies) { attributes = attributes.concat( modelDefinition.attributes.get(attribute).type.attributeDependencies ); } } attributes = (0, import_uniq.default)(attributes); return attributes; } static async _findSeparate(results, options) { if (!options.include || options.raw || !results) { return results; } const original = results; if (options.plain) { results = [results]; } if (!Array.isArray(results) || results.length === 0) { return original; } await Promise.all( options.include.map(async (include) => { if (!include.separate) { return await Model._findSeparate( results.reduce((memo, result) => { let associations = result.get(include.association.as); if (!associations) { return memo; } if (!Array.isArray(associations)) { associations = [associations]; } for (let i = 0, len = associations.length; i !== len; ++i) { memo.push(associations[i]); } return memo; }, []), { ...(0, import_omit.default)( options, "include", "attributes", "order", "where", "limit", "offset", "plain", "scope" ), include: include.include || [] } ); } const map = await include.association.get(results, { ...(0, import_omit.default)(options, nonCascadingOptions), ...(0, import_omit.default)(include, ["parent", "association", "as", "originalAttributes"]) }); for (const result of results) { result.set(include.association.as, map.get(result.get(include.association.sourceKey)), { raw: true }); } }) ); return original; } /** * Search for a single instance. * * Returns the first instance corresponding matching the query. * If not found, returns null or throws an error if {@link FindOptions.rejectOnEmpty} is set. * * @param {object} [options] A hash of options to describe the scope of the search * @returns {Promise<Model|null>} */ static async findOne(options) { if (options !== void 0 && !(0, import_isPlainObject.default)(options)) { throw new Error( "The argument passed to findOne must be an options object, use findByPk if you wish to pass a single primary key value" ); } options = (0, import_object.cloneDeep)(options) ?? {}; if (options.limit === void 0) { options.limit = 1; } return await Model.findAll.call( this, (0, import_defaults.default)(options, { model: this, plain: true }) ); } /** * Run an aggregation method on the specified field. * * Returns the aggregate result cast to {@link AggregateOptions.dataType}, * unless `options.plain` is false, in which case the complete data result is returned. * * @param {string} attribute The attribute to aggregate over. Can be a field name or * * @param {string} aggregateFunction The function to use for aggregation, e.g. sum, max etc. * @param {object} [options] Query options. See sequelize.query for full options * * @returns {Promise<DataTypes|object>} */ static async aggregate(attribute, aggregateFunction, options) { options = (0, import_object.cloneDeep)(options) ?? {}; options.model = this; const prevAttributes = options.attributes; this._injectScope(options); options.attributes = prevAttributes; this._conformIncludes(options, this); if (options.include) { this._expandIncludeAll(options); (0, import_model_internals._validateIncludedElements)(options); } const attrOptions = this.getAttributes()[attribute]; const field = attrOptions && attrOptions.field || attribute; let aggregateColumn = this.sequelize.col(field); if (options.distinct) { aggregateColumn = this.sequelize.fn("DISTINCT", aggregateColumn); } let { group } = options; if (Array.isArray(group) && Array.isArray(group[0])) { (0, import_deprecations.noDoubleNestedGroup)(); group = group.flat(); } options.attributes = (0, import_unionBy.default)( options.attributes, group, [[this.sequelize.fn(aggregateFunction, aggregateColumn), aggregateFunction]], (a) => Array.isArray(a) ? a[1] : a ); if (!options.dataType) { if (attrOptions) { options.dataType = attrOptions.type; } else { options.dataType = new DataTypes.FLOAT(); } } else { options.dataType = this.sequelize.normalizeDataType(options.dataType); } (0, import_format.mapOptionFieldNames)(options, this); options = this._paranoidClause(this, options); const value = await this.queryInterface.rawSelect(this.table, options, aggregateFunction, this); return value; } /** * Count the number of records matching the provided where clause. * * If you provide an `include` option, the number of matching associations will be counted instead. * * @param {object} [options] options * @returns {Promise<number>} */ static async count(options) { options = (0, import_object.cloneDeep)(options) ?? {}; options = (0, import_defaults.default)(options, { hooks: true }); (0, import_model_internals.setTransactionFromCls)(options, this.sequelize); options.raw = true; if (options.hooks) { await this.hooks.runAsync("beforeCount", options); } options.plain = !options.group; options.dataType = new DataTypes.INTEGER(); options.includeIgnoreAttributes = false; op