UNPKG

@palmares/databases

Version:

Add support for working with databases with palmares framework

1,210 lines (1,172 loc) 295 kB
var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/domain.ts import { domain as domain2 } from "@palmares/core"; // src/settings.ts function defaultSettings(settings) { if (settings.databases === void 0) settings.databases = {}; if (typeof settings.dismissNoMigrationsLog !== "boolean") settings.dismissNoMigrationsLog = false; return settings; } __name(defaultSettings, "defaultSettings"); // src/commands/make-migrations.ts async function makeMigrations(databases, { settings, domains, commandLineArgs }) { const databaseSettings = defaultSettings(settings); const databaseDomains = domains; await databases.makeMigrations(databaseSettings, databaseDomains, commandLineArgs.keywordArgs); await databases.close(); process.exit(0); } __name(makeMigrations, "makeMigrations"); // src/commands/migrate.ts async function migrate(databases, { settings, domains }) { const databaseSettings = defaultSettings(settings); const databaseDomains = domains; await databases.migrate(databaseSettings, databaseDomains); await databases.close(); process.exit(0); } __name(migrate, "migrate"); // src/databases.ts import { getSettings as getSettings2, retrieveDomains as retrieveDomains2 } from "@palmares/core"; // src/logging.ts import { Logger } from "@palmares/logging"; var databaseLogger = new Logger({ domainName: "@palmares/databases" }, { MODELS_NOT_FOUND: { category: "warn", handler: /* @__PURE__ */ __name(({ domainName }) => `Looks like the domain ${domainName} did not define any models. If that's not intended behavior, you should create the 'models.ts'/'models.js' file in the ${domainName} domain or add the 'getModels' to the domain class.`, "handler") }, DATABASE_CLOSING: { category: "info", handler: /* @__PURE__ */ __name(({ databaseName }) => `Closing the '${databaseName}' database connection.`, "handler") }, DATABASE_IS_NOT_CONNECTED: { category: "info", handler: /* @__PURE__ */ __name(({ databaseName }) => `Couldn't connect to the '${databaseName}' database.`, "handler") }, FAILED_TO_GET_LAST_MIGRATION: { category: "error", handler: /* @__PURE__ */ __name(({ databaseName, reason, stack }) => `Failed to get the last migration for the '${databaseName}' database. \x1B[1mReason:\x1B[0m ${reason} \x1B[1mStack:\x1B[0m ${stack}`, "handler") }, FAILED_TO_COMMIT_MIGRATION: { category: "error", handler: /* @__PURE__ */ __name(({ migrationName, databaseName, reason, stack }) => `Failed to get insert ran migration '${migrationName}' for the '${databaseName}' database. \x1B[1mReason:\x1B[0m ${reason} \x1B[1mStack:\x1B[0m ${stack}`, "handler") }, MIGRATIONS_NOT_FOUND: { category: "warn", handler: /* @__PURE__ */ __name(({ domainName }) => `No migrations were found for the '${domainName}', if this is your first time running this command, you can safely ignore this message. You can fully dismiss this message by setting 'DATABASES_DISMISS_NO_MIGRATIONS_LOG = true;' in 'settings.(ts/js)'`, "handler") }, MIGRATIONS_FILE_TITLE: { category: "info", handler: /* @__PURE__ */ __name(({ title }) => `- \x1B[36m${title}`, "handler") }, MIGRATIONS_FILE_DESCRIPTION: { category: "info", handler: /* @__PURE__ */ __name(({ database, lastMigrationName, lastDomainPath }) => `Generating migration on the \x1B[1m'${database}'\x1B[0m database` + (lastMigrationName !== "" && lastDomainPath !== "" ? ` that depends on the migration \x1B[1m'${lastMigrationName}'\x1B[0m that exists on \x1B[1m'${lastDomainPath}'\x1B[0m` : ""), "handler") }, MIGRATIONS_NO_NEW_MIGRATIONS: { category: "info", handler: /* @__PURE__ */ __name(({ databaseName }) => `There are no migrations to run for '${databaseName}'. If you made changes to your models, please run\x1B[1m makemigrations\x1B[0m command first.`, "handler") }, MIGRATIONS_RUNNING_FILE_NAME: { category: "info", handler: /* @__PURE__ */ __name(({ title }) => `Running migration: \x1B[36m${title}\x1B[0m`, "handler") }, MIGRATION_RUNNING_IN_BATCH: { category: "info", handler: /* @__PURE__ */ __name(({ databaseName }) => `The engine that you are using for '${databaseName}' implements a batch migrations, this means that instead of running each migration file one by one, we will let the chosen engine handle the migration runner.`, "handler") }, MIGRATIONS_ACTION_DESCRIPTION: { category: "info", handler: /* @__PURE__ */ __name(({ description }) => ` \u2022 ${description}`, "handler") }, NO_CHANGES_MADE_FOR_MIGRATIONS: { category: "info", handler: /* @__PURE__ */ __name(() => `No changes were found in your models.`, "handler") }, QUERY_NOT_PROPERLY_SET: { category: "warn", handler: /* @__PURE__ */ __name(({ modelName, invalidFields }) => { const errorsByField = Array.from(invalidFields).map(([_, isValidObject]) => { return `- ${isValidObject.reason}`; }).join("\n"); return `The fields on the query to retrieve '${modelName}' data was not set properly and contain wrong or missing data ${errorsByField}`; }, "handler") }, CREATE_PALMARES_DB_APP: { category: "info", handler: /* @__PURE__ */ __name(({ name, template }) => `Creating Palmares database app '${name}' using the template '${template}'`, "handler") }, DONE_CREATING_PALMARES_DB_APP: { category: "info", handler: /* @__PURE__ */ __name(({ name, template }) => `Done creating Palmares database app '${name}' using the template '${template}' Next steps: 1. cd ${name} 2. Install the dependencies with your favorite package manager: - $ pnpm i - $ yarn - $ npm i - $ bun i 3. Create the migrations: - $ pnpm run makemigrations - $ yarn run makemigrations - $ npm run makemigrations - $ bun run makemigrations 4. Apply the migrations: - $ pnpm run migrate - $ yarn run migrate - $ npm run migrate - $ bun run migrate 5. Start the application: - $ pnpm run dev - $ yarn run dev - $ npm run dev - $ bun run dev`, "handler") } }); // src/migrations/index.ts import { ERR_MODULE_NOT_FOUND, std as std4 } from "@palmares/core"; // src/migrations/makemigrations/index.ts import { FRAMEWORK_NAME, retrieveDomains, std as std3 } from "@palmares/core"; // src/migrations/makemigrations/asker.ts import { std } from "@palmares/core"; var Asker = class Asker2 { static { __name(this, "Asker"); } async theNewAttributeCantHaveNullDoYouWishToContinue(modelName, fieldName) { const question = `\x1B[0mIf the model \x1B[33m${modelName}\x1B[0m already have data, it can cause issues when migrating the new \x1B[36m${fieldName}\x1B[0m column because you didn't set a \x1B[33mdefaultValue\x1B[0m or \x1B[33mallowNull \x1B[0mis set to \x1B[33mfalse\x1B[0m. You can safely ignore this message if you didn't add any data to the table. Press any key to continue or 'CTRL+C' to stop and define the attributes yourself. `; const answer = await std.asker.ask(question); if (answer.toLowerCase() === "n") return false; else return true; } async didUserRename(modelOrFieldThatWasRenamed, renamedTo) { const question = ` Did you rename '${modelOrFieldThatWasRenamed}' to '${renamedTo}'? [y/n] `; const answer = await std.asker.ask(question); if ([ "y", "n" ].includes(answer)) return answer === "y"; else return false; } async didUserRenameToOneOption(valueThatWasRenamed, renamedToOptions) { const toOptions = renamedToOptions.map((renamedTo, index) => `${index + 1}. ${renamedTo}`); const explanation = "\nPlease type the corresponding number or leave blank if you have not renamed"; const question = ` Did you rename '${valueThatWasRenamed}' to one of the following options? ${toOptions.join("\n")} ${explanation} `; const answer = await std.asker.ask(question); if (answer === "") return null; else { try { return renamedToOptions[parseInt(answer) - 1]; } catch { return null; } } } }; var asker = new Asker(); // src/utils/constants.ts var PACKAGE_NAME = "@palmares/databases"; // src/utils/hash.ts function hashString(stringToHash) { const p = 53; const m = 1e9 + 9; let powerOfP = 1; let hashValue = 0; for (let i = 0; i < stringToHash.length; i++) { hashValue = (hashValue + (stringToHash[i].charCodeAt(0) - "a".charCodeAt(0) + 1) * powerOfP) % m; powerOfP = powerOfP * p % m; } return hashValue.toString(); } __name(hashString, "hashString"); // src/utils/index.ts function getUniqueCustomImports(customImports, customImportsToAppendDataTo = []) { for (const customImport of customImports || []) { const doesNotExistYet = customImportsToAppendDataTo.find((alreadyExistingCustom) => alreadyExistingCustom.packageName === customImport.packageName && alreadyExistingCustom.value === customImport.value) === void 0; if (doesNotExistYet) customImportsToAppendDataTo.push(customImport); } return customImportsToAppendDataTo; } __name(getUniqueCustomImports, "getUniqueCustomImports"); function generateUUID() { let date2 = (/* @__PURE__ */ new Date()).getTime(); import("perf_hooks"); const performance = globalThis.performance; let performanceDate = performance && performance.now && performance.now() * 1e3 || 0; return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (character) => { let randomNumber = Math.random() * 16; if (date2 > 0) { randomNumber = (date2 + randomNumber) % 16 | 0; date2 = Math.floor(date2 / 16); } else { randomNumber = (performanceDate + randomNumber) % 16 | 0; performanceDate = Math.floor(performanceDate / 16); } return (character === "x" ? randomNumber : randomNumber & 3 | 8).toString(16); }); } __name(generateUUID, "generateUUID"); // src/migrations/actions/operation.ts var Operation = class { static { __name(this, "Operation"); } /** * Function that will be used to construct and build the state of all of the models in the application so we * can compare to the original ones. * * @param state - A state instance that holds all of the models of the application before. * @param domainName - The name of the domain where this model was defined. * @param domainPath - The path of the domain where this model exists so we can add the migration file there. */ async stateForwards(_state, _domainName, _domainPath) { } /** * Method that runs when a migration is running on a migration file, when this happens we will call the exact * function of the engine migrations. * * We also have the fromState (which will be state when the state) which will be the state of the models * before running the migration and `toState` will be state AFTER running the migration */ async run(_migration, _engineInstance, _fromState, _toState, _returnOfInit) { } // eslint-disable-next-line ts/require-await static async defaultToGenerate(domainName, domainPath, modelName, data) { return { operation: this, domainName, domainPath, modelName, order: 0, dependsOn: [], data }; } // eslint-disable-next-line ts/require-await static async toString(_engine, indentation = 0, data) { return { asString: "" }; } // eslint-disable-next-line ts/require-await static async defaultToString(indentation = 0, customAttributesOfAction = "") { const ident = " ".repeat(indentation); return `${ident}new actions.${this.name}(${customAttributesOfAction !== "" ? ` ${customAttributesOfAction} ${ident}` : ""})`; } // eslint-disable-next-line ts/require-await static async describe(data) { return ""; } }; // src/migrations/actions/fields.ts var CreateField = class extends Operation { static { __name(this, "CreateField"); } modelName; fieldName; fieldDefinition; constructor(modelName, fieldName, fieldDefinition) { super(); this.modelName = modelName; this.fieldName = fieldName; this.fieldDefinition = fieldDefinition; } async stateForwards(state, domainName, domainPath) { const model2 = await state.get(this.modelName); const modelConstructor = model2.constructor; modelConstructor["__domainName"] = domainName; modelConstructor["__domainPath"] = domainPath; model2.fields[this.fieldName] = this.fieldDefinition; await state.set(this.modelName, model2); } async run(migration, engineInstance, fromState, toState, returnOfInit) { const toModel = toState[this.modelName]; const fromModel = fromState[this.modelName]; await engineInstance.migrations?.addField(engineInstance, toModel, fromModel, this.fieldName, migration, returnOfInit); } static async toGenerate(domainName, domainPath, modelName, data) { return super.defaultToGenerate(domainName, domainPath, modelName, data); } static async toString(engine, indentation = 0, data) { const ident = " ".repeat(indentation); return { asString: await super.defaultToString(indentation - 1, `${ident}"${data.modelName}", ${ident}"${data.data.fieldName}", ${await data.data.fieldDefinition["__toString"](engine)}`), customImports: await data.data.fieldDefinition["__getCustomImports"]() }; } // eslint-disable-next-line ts/require-await static async describe(data) { return `Created the field '${data.data.fieldName}' on the '${data.modelName}' model`; } }; var ChangeField = class extends Operation { static { __name(this, "ChangeField"); } modelName; fieldName; fieldDefinitionBefore; fieldDefinitionAfter; constructor(modelName, fieldName, fieldDefinitionBefore, fieldDefinitionAfter) { super(); this.modelName = modelName; this.fieldName = fieldName; this.fieldDefinitionBefore = fieldDefinitionBefore; this.fieldDefinitionAfter = fieldDefinitionAfter; } async stateForwards(state, domainName, domainPath) { const model2 = await state.get(this.modelName); const modelConstructor = model2.constructor; modelConstructor["__domainName"] = domainName; modelConstructor["__domainPath"] = domainPath; model2.fields[this.fieldName] = this.fieldDefinitionAfter; await state.set(this.modelName, model2); } async run(migration, engineInstance, fromState, toState, returnOfInit) { const fromModel = fromState[this.modelName]; const toModel = toState[this.modelName]; await engineInstance.migrations?.changeField(engineInstance, toModel, fromModel, this.fieldDefinitionBefore, this.fieldDefinitionAfter, migration, returnOfInit); } static async toGenerate(domainName, domainPath, modelName, data) { return super.defaultToGenerate(domainName, domainPath, modelName, data); } static async toString(engine, indentation = 0, data) { const ident = " ".repeat(indentation); return { asString: await super.defaultToString(indentation - 1, `${ident}"${data.modelName}", ${ident}"${data.data.fieldName}", ${await data.data.fieldDefinitionBefore["__toString"](engine)}, ${await data.data.fieldDefinitionAfter["__toString"](engine)}`), customImports: (await data.data.fieldDefinitionBefore["__getCustomImports"]()).concat(await data.data.fieldDefinitionAfter["__getCustomImports"]()) }; } // eslint-disable-next-line ts/require-await static async describe(data) { return `Changed the ${`attribute${data.data.changedAttributes.length > 1 ? "s" : ""} ${data.data.changedAttributes.map((attribute) => `'${attribute}'`).join(", ").replace(/,(?!.*,)/, " and")}`} of the '${data.data.fieldName}' field on the '${data.modelName}' model`; } }; var RenameField = class extends Operation { static { __name(this, "RenameField"); } modelName; fieldNameBefore; fieldNameAfter; fieldDefinition; constructor(modelName, fieldNameBefore, fieldNameAfter, fieldDefinition) { super(); this.modelName = modelName; this.fieldNameBefore = fieldNameBefore; this.fieldNameAfter = fieldNameAfter; this.fieldDefinition = fieldDefinition; } async stateForwards(state, domainName, domainPath) { const model2 = await state.get(this.modelName); const modelConstructor = model2.constructor; modelConstructor["__domainName"] = domainName; modelConstructor["__domainPath"] = domainPath; const hasNamesReallyChanged = this.fieldNameAfter !== this.fieldNameBefore; if (hasNamesReallyChanged) { model2.fields[this.fieldNameAfter] = model2.fields[this.fieldNameBefore]; delete model2.fields[this.fieldNameBefore]; } model2.fields[this.fieldNameAfter] = this.fieldDefinition; await state.set(this.modelName, model2); } async run(migration, engineInstance, fromState, toState, returnOfInit) { const fromModel = fromState[this.modelName]; const toModel = toState[this.modelName]; await engineInstance.migrations?.renameField(engineInstance, toModel, fromModel, this.fieldNameBefore, this.fieldNameAfter, migration, returnOfInit); } static async toGenerate(domainName, domainPath, modelName, data) { return super.defaultToGenerate(domainName, domainPath, modelName, data); } static async toString(engine, indentation = 0, data) { const ident = " ".repeat(indentation); return { asString: await super.defaultToString(indentation - 1, `${ident}"${data.modelName}", ${ident}"${data.data.fieldNameBefore}", ${ident}"${data.data.fieldNameAfter}", ${await data.data.fieldDefinition["__toString"](engine)}`), customImports: await data.data.fieldDefinition["__getCustomImports"]() }; } // eslint-disable-next-line ts/require-await static async describe(data) { return `Renamed the field '${data.data.fieldNameBefore}' to '${data.data.fieldNameAfter}' on the '${data.modelName}' model`; } }; var DeleteField = class extends Operation { static { __name(this, "DeleteField"); } modelName; fieldName; constructor(modelName, fieldName) { super(); this.modelName = modelName; this.fieldName = fieldName; } async stateForwards(state, domainName, domainPath) { const model2 = await state.get(this.modelName); const modelConstructor = model2.constructor; modelConstructor["__domainName"] = domainName; modelConstructor["__domainPath"] = domainPath; delete model2.fields[this.fieldName]; await state.set(this.modelName, model2); } async run(migration, engineInstance, fromState, toState, returnOfInit) { const fromModel = fromState[this.modelName]; const toModel = toState[this.modelName]; await engineInstance.migrations?.removeField(engineInstance, toModel, fromModel, this.fieldName, migration, returnOfInit); } static async toGenerate(domainName, domainPath, modelName, data) { return super.defaultToGenerate(domainName, domainPath, modelName, data); } static async toString(_engine, indentation = 0, data) { const ident = " ".repeat(indentation); return { asString: await super.defaultToString(indentation - 1, `${ident}"${data.modelName}", ${ident}"${data.data.fieldName}"`) }; } // eslint-disable-next-line ts/require-await static async describe(data) { return `Removed the field '${data.data.fieldName}' on the '${data.modelName}' model`; } }; // src/models/model.ts import { utils as utils2 } from "@palmares/core"; // src/models/exceptions.ts var ModelNoUniqueFieldsError = class _ModelNoUniqueFieldsError extends Error { static { __name(this, "ModelNoUniqueFieldsError"); } constructor(modelName) { super(`Model ${modelName} has no unique fields, it should have at least one unique field. If it's an abstract model, you need to set "abstract" to true in the model options.`); this.name = _ModelNoUniqueFieldsError.name; } }; var ModelNoPrimaryKeyFieldError = class _ModelNoPrimaryKeyFieldError extends Error { static { __name(this, "ModelNoPrimaryKeyFieldError"); } constructor(modelName) { super(`Model ${modelName} has no primary key field, it should have at least one primary key. If it's an abstract model, you need to set "abstract" to true in the model options.`); this.name = _ModelNoPrimaryKeyFieldError.name; } }; var ModelCircularAbstractError = class extends Error { static { __name(this, "ModelCircularAbstractError"); } constructor(originalModelName, abstractModelName) { super(`Model ${originalModelName} have a circular abstract dependency with ${abstractModelName}`); } }; var ManagerEngineInstanceNotFoundError = class _ManagerEngineInstanceNotFoundError extends Error { static { __name(this, "ManagerEngineInstanceNotFoundError"); } constructor(engineName) { super(`The engine ${engineName} is not found in the manager. Make sure that this model is available for that engine.`); this.name = _ManagerEngineInstanceNotFoundError.name; } }; var ShouldAssignAllInstancesException = class _ShouldAssignAllInstancesException extends Error { static { __name(this, "ShouldAssignAllInstancesException"); } constructor() { super("You have translated the model before. And you have assigned `instance` to the model options. You should assign `instance` to all model options."); this.name = _ShouldAssignAllInstancesException.name; } }; var EngineDoesNotSupportFieldTypeException = class _EngineDoesNotSupportFieldTypeException extends Error { static { __name(this, "EngineDoesNotSupportFieldTypeException"); } constructor(engineName, fieldType) { super(`The engine '${engineName}' does not support the field of type: '${fieldType}'. If you are using a custom field, make sure that you are using the 'TranslatableField' class.`); this.name = _EngineDoesNotSupportFieldTypeException.name; } }; var RelatedModelFromForeignKeyIsNotFromEngineException = class _RelatedModelFromForeignKeyIsNotFromEngineException extends Error { static { __name(this, "RelatedModelFromForeignKeyIsNotFromEngineException"); } constructor(engineName, modelName, foreignKeyFieldName, foreignKeyFieldModelName, fieldName) { super(`The related model '${modelName}' from the foreign key field '${foreignKeyFieldName}' of the model '${foreignKeyFieldModelName}' is not from the engine '${engineName}' that is being used. This is not a problem, but you need to make sure that the field '${fieldName}' it is relating to exists on the model '${modelName}' it is related to.`); this.name = _RelatedModelFromForeignKeyIsNotFromEngineException.name; } }; var ModelMissingException = class _ModelMissingException extends Error { static { __name(this, "ModelMissingException"); } constructor(modelName) { super(`The model ${modelName} was not found in the engine.`); this.name = _ModelMissingException.name; } }; var FieldFromModelMissingException = class _FieldFromModelMissingException extends Error { static { __name(this, "FieldFromModelMissingException"); } constructor(modelName, fieldName) { super(`The field ${fieldName} was not found in the model ${modelName}.`); this.name = _FieldFromModelMissingException.name; } }; // src/models/manager.ts import { getSettings, initializeDomains } from "@palmares/core"; // src/queries/exceptions.ts var RelationNameIsNotPartOfModelException = class _RelationNameIsNotPartOfModelException extends Error { static { __name(this, "RelationNameIsNotPartOfModelException"); } constructor(modelName, relationName) { super(`The relation name '${relationName}' is not part of the model '${modelName}'.`); this.name = _RelationNameIsNotPartOfModelException.name; } }; // src/queries/search.ts async function parseSearchField(engine, key, fieldData, inputFieldParser, translatedModelInstance, result) { if (typeof fieldData === "object" && fieldData !== null) { if (typeof fieldData.like === "object") { if (fieldData.like?.ignoreCase) { await engine.query.search.parseSearchFieldValue("like", key, translatedModelInstance, await inputFieldParser(fieldData.like?.ignoreCase), result, { ignoreCase: true }); } else if ("not" in fieldData.like) { if (typeof fieldData.like.not === "object") { await engine.query.search.parseSearchFieldValue("like", key, translatedModelInstance, await inputFieldParser(fieldData.like.not.ignoreCase), result, { isNot: true, ignoreCase: true }); } else { await engine.query.search.parseSearchFieldValue("like", key, translatedModelInstance, await inputFieldParser(fieldData.like.not), result, { isNot: true }); } } } if (fieldData.like !== void 0) { await engine.query.search.parseSearchFieldValue("like", key, translatedModelInstance, await inputFieldParser(fieldData.like), result); } if (fieldData.and !== void 0) { const isArrayAndAtLeastOneElement = Array.isArray(fieldData.and) && fieldData.and.length > 1; if (isArrayAndAtLeastOneElement) { await engine.query.search.parseSearchFieldValue("and", key, translatedModelInstance, await inputFieldParser(fieldData.and), result); } else return { isValid: false, code: "invalid_in", reason: `The field '${key}' must contain at least two elements when using the 'and' clause` }; } if (fieldData.or !== void 0) { const isArrayAndAtLeastTwoElements = Array.isArray(fieldData.or) && fieldData.or.length > 1; if (isArrayAndAtLeastTwoElements) { await engine.query.search.parseSearchFieldValue("or", key, translatedModelInstance, await inputFieldParser(fieldData.or), result); } else return { isValid: false, code: "invalid_in", reason: `The field '${key}' must contain at least two elements when using the 'or' clause` }; } if (fieldData.in !== void 0) { const isInArrayAndAtLeastOneElement = Array.isArray(fieldData.in) && fieldData.in.length > 0 || Array.isArray(fieldData.in?.not) && (fieldData.in?.not || []).length > 0; const isInNotArrayAndAtLeastOneElement = Array.isArray(fieldData.in?.not) && (fieldData.in?.not || []).length > 0; if (isInArrayAndAtLeastOneElement) { await engine.query.search.parseSearchFieldValue( "in", key, translatedModelInstance, // eslint-disable-next-line ts/no-unnecessary-condition await Promise.all((fieldData.in || []).map((inValue) => inputFieldParser(inValue))), result ); } else if (isInNotArrayAndAtLeastOneElement) { await engine.query.search.parseSearchFieldValue( "in", key, translatedModelInstance, // eslint-disable-next-line ts/no-unnecessary-condition await Promise.all((fieldData.in?.not || []).map((inValue) => inputFieldParser(inValue))), result, { isNot: true } ); } else return { isValid: false, code: "invalid_in", reason: `The field '${key}' must contain at least one element when using the 'in' clause` }; } if (fieldData.between !== void 0) { const isBetweenAnArrayAndExactlyTwoElements = ( // eslint-disable-next-line ts/no-unnecessary-condition Array.isArray(fieldData.between) && fieldData.between.length === 2 ); const isBetweenNotAnArrayAndExactlyTwoElements = Array.isArray(fieldData.between?.not) && (fieldData.between?.not || []).length === 2; if (isBetweenAnArrayAndExactlyTwoElements) { await engine.query.search.parseSearchFieldValue( "between", key, translatedModelInstance, // eslint-disable-next-line ts/no-unnecessary-condition await Promise.all((fieldData.between || []).map((betweenValue) => inputFieldParser(betweenValue))), result ); } else if (isBetweenNotAnArrayAndExactlyTwoElements) { await engine.query.search.parseSearchFieldValue( "between", key, translatedModelInstance, // eslint-disable-next-line ts/no-unnecessary-condition await Promise.all((fieldData.between?.not || []).map((betweenValue) => inputFieldParser(betweenValue))), result, { isNot: true } ); } else return { isValid: false, code: "invalid_between", reason: `The field '${key}' must have exactly two elements when using the 'between' clause` }; } if (fieldData.is && fieldData.is.not !== void 0) { await engine.query.search.parseSearchFieldValue("is", key, translatedModelInstance, await inputFieldParser(fieldData.is.not), result, { isNot: true }); } else if (fieldData.is !== void 0) await engine.query.search.parseSearchFieldValue("is", key, translatedModelInstance, await inputFieldParser(fieldData.is), result); if (fieldData.greaterThan && fieldData.greaterThan.equal !== void 0) { await engine.query.search.parseSearchFieldValue("greaterThan", key, translatedModelInstance, await inputFieldParser(fieldData.greaterThan.equal), result, { equals: true }); } else if (fieldData.greaterThan !== void 0) await engine.query.search.parseSearchFieldValue("greaterThan", key, translatedModelInstance, await inputFieldParser(fieldData.greaterThan), result); if (fieldData.lessThan && fieldData.lessThan.equal !== void 0) { await engine.query.search.parseSearchFieldValue("lessThan", key, translatedModelInstance, await inputFieldParser(fieldData.lessThan), result, { equals: true }); } else if (fieldData.lessThan !== void 0) await engine.query.search.parseSearchFieldValue("lessThan", key, translatedModelInstance, await inputFieldParser(fieldData.lessThan.equal), result); return; } return await engine.query.search.parseSearchFieldValue("eq", key, translatedModelInstance, await inputFieldParser(fieldData), result); } __name(parseSearchField, "parseSearchField"); // src/models/utils.ts import { std as std2 } from "@palmares/core"; // src/models/fields/field.ts import { utils } from "@palmares/core"; // src/models/fields/utils.ts async function defaultToStringCallback(engine, field, _, customParams = void 0) { let customImports = []; let stringifiedCustomAttributes = "{}"; if (engine.fields.fieldToString) { const stringifiedField = engine.fields.fieldToString(field["__customAttributes"]); customImports = stringifiedField.imports; stringifiedCustomAttributes = stringifiedField.result; } const stringfieldDefaultValue = field["__defaultValue"] === void 0 ? void 0 : JSON.stringify(field["__defaultValue"]); return { stringfied: `models.fields.${field["__typeName"]}.new(${customParams?.constructorParams ? `${customParams.constructorParams}` : ""})${typeof field["__primaryKey"] === "boolean" ? `.primaryKey(${field["__primaryKey"]})` : ""}${stringfieldDefaultValue !== void 0 ? `.default(${typeof field["__defaultValue"] === "string" ? `"${stringfieldDefaultValue}"` : stringfieldDefaultValue})` : ""}${typeof field["__allowNull"] === "boolean" ? `.allowNull(${field["__allowNull"]})` : ""}${typeof field["__unique"] === "boolean" ? `.unique(${field["__unique"]})` : ""}${typeof field["__dbIndex"] === "boolean" ? `.dbIndex(${field["__dbIndex"]})` : ""}${typeof field["__databaseName"] === "string" ? `.databaseName("${field["__databaseName"]}")` : ""}${typeof field["__underscored"] === "boolean" ? `.underscored(${field["__underscored"]})` : ""}${typeof stringifiedCustomAttributes === "string" ? `.setCustomAttributes(${stringifiedCustomAttributes})` : ""}${customParams?.builderParams ? `${customParams.builderParams}` : ""}`, customImports }; } __name(defaultToStringCallback, "defaultToStringCallback"); function defaultCompareCallback(engine, existingField, newField, _) { let isCustomAttributesEqual = true; if (engine.fields.compare) { const areCustomAttributesEqual = engine.fields.compare(existingField["__customAttributes"], newField["__customAttributes"]); isCustomAttributesEqual = areCustomAttributesEqual; } const isTypeNameEqual = existingField["__typeName"] === newField["__typeName"]; const isAllowNullEqual = existingField["__allowNull"] === newField["__allowNull"]; const isPrimaryKeyEqual = existingField["__primaryKey"] === newField["__primaryKey"]; const isDefaultValueEqual = existingField["__defaultValue"] === newField["__defaultValue"]; const isUniqueEqual = existingField["__unique"] === newField["__unique"]; const isDbIndexEqual = existingField["__dbIndex"] === newField["__dbIndex"]; const isDatabaseNameEqual = existingField["__databaseName"] === newField["__databaseName"]; const isUnderscoredEqual = existingField["__underscored"] === newField["__underscored"]; const changedAttributes = [ !isTypeNameEqual && "typeName", !isAllowNullEqual && "allowNull", !isCustomAttributesEqual && "customAttributes", !isPrimaryKeyEqual && "primaryKey", !isDefaultValueEqual && "defaultValue", !isUniqueEqual && "unique", !isDbIndexEqual && "dbIndex", !isDatabaseNameEqual && "databaseName", !isUnderscoredEqual && "underscored" ].filter((attr) => typeof attr === "string"); return [ changedAttributes.length === 0, changedAttributes ]; } __name(defaultCompareCallback, "defaultCompareCallback"); function defaultOptionsCallback(setFieldValue, oldField, _) { setFieldValue("__allowNull", "allowNull", oldField["__allowNull"]); setFieldValue("__customAttributes", "customAttributes", oldField["__customAttributes"]); setFieldValue("__defaultValue", "defaultValue", oldField["__defaultValue"]); setFieldValue("__dbIndex", "dbIndex", oldField["__dbIndex"]); setFieldValue("__databaseName", "databaseName", oldField["__databaseName"]); setFieldValue("__primaryKey", "primaryKey", oldField["__primaryKey"]); setFieldValue("__underscored", "underscored", oldField["__underscored"]); setFieldValue("__unique", "unique", oldField["__unique"]); setFieldValue("__isAuto", "isAuto", oldField["__isAuto"]); setFieldValue("__fieldName", "fieldName", oldField["__fieldName"]); setFieldValue("__model", void 0, oldField["__model"]); } __name(defaultOptionsCallback, "defaultOptionsCallback"); function defaultNewInstanceArgumentsCallback(_field, _defaultNewInstanceArgumentsCallback) { return []; } __name(defaultNewInstanceArgumentsCallback, "defaultNewInstanceArgumentsCallback"); function getRelatedToAsString(field) { const relatedTo = field["__relatedTo"]; const relatedToAsString = field["__relatedToAsString"]; if (typeof relatedToAsString !== "string") { if (typeof relatedTo === "function" && relatedTo["$$type"] !== "$PModel") field["__relatedToAsString"] = relatedTo()["__getName"](); else if (typeof relatedTo === "string") field["__relatedToAsString"] = relatedTo; else field["__relatedToAsString"] = relatedTo["__getName"](); } } __name(getRelatedToAsString, "getRelatedToAsString"); function defaultGetArgumentsCallback(field, _) { return { $field: field, $model: field["__model"], typeName: field["__typeName"], fieldName: field["__fieldName"], modelName: field["__model"]?.["__getName"]?.() || "", isAuto: field["__isAuto"], primaryKey: field["__primaryKey"], defaultValue: field["__defaultValue"], allowNull: field["__allowNull"], unique: field["__unique"], dbIndex: field["__dbIndex"], databaseName: field["__databaseName"], underscored: field["__underscored"], customAttributes: field["__customAttributes"] }; } __name(defaultGetArgumentsCallback, "defaultGetArgumentsCallback"); // src/models/fields/field.ts var Field = class { static { __name(this, "Field"); } $$type = "$PField"; __typeName = "Field"; __isAuto = false; __hasDefaultValue = false; __primaryKey = false; __defaultValue = void 0; __allowNull = false; __unique = false; __dbIndex = false; __databaseName = void 0; __underscored = true; __customAttributes; __allowedQueryOperations = /* @__PURE__ */ new Set([ "eq", "is", "greaterThan", "lessThan", "like", "between", "and", "or" ]); // eslint-disable-next-line ts/require-await __toStringCallback = defaultToStringCallback; // eslint-disable-next-line ts/require-await __compareCallback = defaultCompareCallback; __optionsCallback = defaultOptionsCallback; __newInstanceCallback = defaultNewInstanceArgumentsCallback; __customImports = []; __getArgumentsCallback = defaultGetArgumentsCallback; __inputParsers = /* @__PURE__ */ new Map(); __outputParsers = /* @__PURE__ */ new Map(); __model; __fieldName; constructor(..._args) { } /** * Supposed to be used by library maintainers. * * When you custom create a field, you might want to take advantage of the builder pattern we already support. * This let's you create functions that can be chained together to create a new field. It should be used * alongside the `_setPartialAttributes` method like * * @example * ```ts * const customBigInt = TextField.overrideType< * { create: bigint; read: bigint; update: bigint }, * { * customAttributes: { name: string }; * unique: boolean; * auto: boolean; * allowNull: true; * dbIndex: boolean; * isPrimaryKey: boolean; * defaultValue: any; * typeName: string; * engineInstance: DatabaseAdapter; * } * >({ * typeName: 'CustomBigInt' * }); * * const customBuilder = <TParams extends { name: string }>(params: TParams) => { * const field = customBigInt.new(params); * return field._setNewBuilderMethods({ * test: <TTest extends { age: number }>(param: TTest) => * // This will union the type `string` with what already exists in the field 'create' type * field._setPartialAttributes<{ create: string }, { create: 'union' }>(param) * }); * }; * * // Then your user can use it like: * * const field = customBuilder({ name: 'test' }).test({ age: 2 }); * ``` * * **Important**: `customBuilder` will be used by the end user and you are responsible for documenting it. */ _setNewBuilderMethods(functions) { if (functions === void 0) return this; const propertiesOfBase = Object.getOwnPropertyNames(Object.getPrototypeOf(functions)); for (const key of propertiesOfBase) { if (key === "constructor") continue; this[key] = functions[key].bind(this); } return this; } /** * FOR LIBRARY MAINTAINERS ONLY * * Focused for library maintainers that want to support a custom field type not supported by palmares. * This let's them partially update the custom attributes of the field. By default setCustomAttributes * will override the custom attributes entirely. */ _setPartialAttributes() { return (partialCustomAttributes) => { if (partialCustomAttributes !== void 0) { if (this.__customAttributes === void 0) this.__customAttributes = {}; this.__customAttributes = { ...this.__customAttributes, ...partialCustomAttributes }; } return this; }; } setCustomAttributes(customAttributes) { this.__customAttributes = customAttributes; return this; } unique(isUnique) { if (typeof isUnique !== "boolean") isUnique = true; this.__unique = isUnique; return this; } allowNull(isNull) { if (typeof isNull !== "boolean") isNull = true; this.__allowNull = isNull; return this; } /** * This method is used to create an index on the database for this field. */ dbIndex(dbIndex) { if (typeof dbIndex !== "boolean") dbIndex = true; this.__dbIndex = dbIndex; return this; } underscored(isUnderscored) { if (typeof isUnderscored !== "boolean") isUnderscored = true; this.__underscored = isUnderscored; return this; } primaryKey(isPrimaryKey) { if (typeof isPrimaryKey !== "boolean") isPrimaryKey = true; this.__primaryKey = isPrimaryKey; return this; } auto(isAuto) { if (typeof isAuto !== "boolean") isAuto = true; this.__isAuto = isAuto; return this; } default(defaultValue) { this.__defaultValue = defaultValue; return this; } databaseName(databaseName) { this.__databaseName = databaseName; return this; } /** * This method can be used to override the type of a field. This is useful for library * maintainers that want to support the field type but the default type provided by palmares * is not the one that the user want to use. * * @example * ```ts * const MyCustomDatabaseAutoField = AutoField.overrideType<{ input: string; output: string }>(); * * // then the user can use as normal: * * const autoField = MyCustomDatabaseAutoField.new(); * * // now the type inferred for the field will be a string instead of a number. * ``` * * ### Note * * Your library should provide documentation of the fields that are supported. */ static _overrideType(args) { this.new = (params) => { const newInstance = new this(params); newInstance.__customImports = args.customImports || []; newInstance.__toStringCallback = args.toStringCallback || defaultToStringCallback; newInstance.__compareCallback = args.compareCallback || defaultCompareCallback; newInstance.__optionsCallback = args.optionsCallback || defaultOptionsCallback; newInstance.__newInstanceCallback = args.newInstanceCallback || defaultNewInstanceArgumentsCallback; newInstance.__typeName = args.typeName; newInstance.__allowedQueryOperations = /* @__PURE__ */ new Set([ "is", "eq", "greaterThan", "lessThan", "like", "between" ]); newInstance["__customAttributes"] = params; return newInstance; }; return this; } /** * This method enables the framework to automatically import the files when generating the migrations. * * This is generally useful for custom field types. * * @return - Returns a list of packages that we want to import in the migration file. */ // eslint-disable-next-line ts/require-await async __getCustomImports() { return this["__customImports"]; } __init(fieldName, model2) { const isAlreadyInitialized = this.__model !== void 0 && typeof this.__fieldName === "string"; if (isAlreadyInitialized) return; const isUnderscored = (this.__underscored || model2.__cachedOptions?.underscored) === true; this.__fieldName = fieldName; this.__model = model2; if (this.__primaryKey) model2["__primaryKeys"].push(this.__fieldName); if (isUnderscored) this.__databaseName = utils.camelCaseToHyphenOrSnakeCase(this.__fieldName); else this.__databaseName = this.__fieldName; } /** * Gets all of the arguments to pass to the field during migration, translation, etc. * * Everything in the field is protected, this way end users don't access the internal implementation. */ __getArguments() { return this.__getArgumentsCallback(this, defaultGetArgumentsCallback); } // eslint-disable-next-line ts/require-await async __toString(engine) { return this.__toStringCallback(engine, this, defaultToStringCallback, void 0); } /** * Used for comparing one field with the other so we are able to tell if they are different or not. * * This is obligatory to add if you create any custom fields. * * For custom fields you will call this super method before continuing, we first check if they are the same type. * * @param field - The field to compare to. * * @return - Returns true if the fields are equal and false otherwise */ // eslint-disable-next-line ts/require-await __compare(engine, field) { return this.__compareCallback(engine, this, field, defaultCompareCallback); } /** * Used for cloning the field to a new field. * * @param oldField - The field to clone. If not provided it will use the current field. * * @returns - Returns the cloned field. */ __clone(oldField, args) { const argumentsToPass = oldField.__newInstanceCallback(oldField, defaultNewInstanceArgumentsCallback); const overridenArguments = args?.newInstanceOverrideCallback?.(argumentsToPass) || argumentsToPass; const newInstanceOfField = oldField.constructor.new(...Array.isArray(overridenArguments) ? overridenArguments : []); oldField.__optionsCallback((hiddenAttributeName, getAttributesAttributeName, value) => { let actualValueToSet = value; if (getAttributesAttributeName && args?.optionsOverrideCallback?.[getAttributesAttributeName] !== void 0) actualValueToSet = (args?.optionsOverrideCallback)[getAttributesAttributeName](value); newInstanceOfField[hiddenAttributeName] = actualValueToSet; }, oldField, defaultOptionsCallback); return newInstanceOfField; } /** * You should not use this method directly, it is used internally by the framework. * * This method is used to create a new instance of the field with the same options as the old field. */ static new(..._args) { return new this(..._args); } }; // src/models/fields/date.ts var DateField = class extends Field { static { __name(this, "DateField"); } $$type = "$PDateField"; __typeName = "DateField"; __allowedQueryOperations = /* @__PURE__ */ new Set([ "eq", "is", "greaterThan", "lessThan", "between", "and", "or" ]); __autoNow = false; __autoNowAdd = false; __inputParsers = /* @__PURE__ */ new Map(); __outputParsers = /* @__PURE__ */ new Map(); __compareCallback = /* @__PURE__ */ __name((engine, oldField, newField, defaultCompareCallback2) => { const oldFieldAsTextField = oldField; const newFieldAsTextField = newField; const isAutoNowEqual = oldFieldAsTextField["__autoNow"] === newFieldAsTextField["__autoNow"]; const isAutoNowAddEqual = oldFieldAsTextField["__autoNowAdd"] === newFieldAsTextField["__autoNowAdd"]; const [isEqual, changedAttributes] = defaultCompareCallback2(engine, oldField, newField, defaultCompareCallback2); if (!isAutoNowEqual) changedAttributes.push("autoNow"); if (!isAutoNowAddEqual) changedAttributes.push("autoNowAdd"); return [ isAutoNowAddEqual && isAutoNowEqual && isEqual, changedAttributes ]; }, "__compareCallback"); __optionsCallback = /* @__PURE__ */ __name((setFieldValue, oldField, defaultOptionsCallback2) => { const oldFieldAsTextField = oldField; defaultOptionsCallback2(setFieldValue, oldField, defaultOptionsCallback2); setFieldValue("__autoNow", "autoNow", oldFieldAsTextField["__autoNow"]); setFieldValue("__autoNowAdd", "autoNowAdd", oldFieldAsTextField["__autoNowAdd"]); }, "__optionsCallback"); __getArgumentsCallback = /* @__PURE__ */ __name((field, defaultCallback) => { const fieldAsDateField = field; const autoNow = fieldAsDateField["__autoNow"]; const autoNowAdd = fieldAsDateField["__autoNowAdd"]; return { ...defaultCallback(field, defaultCallback), autoNow, autoNowAdd }; }, "__getArgumentsCallback"); /** * This is used internally by the engine to convert the field to string. * You can override this if you want to extend the ForeignKeyField class. */ __toStringCallback = /* @__PURE__ */ __name(async (engine, field, defaultToStringCallback2, _customParams = void 0) => { const fieldAsDateField = field; return await defaultToStringCallback2(engine, field, defaultToStringCallback2, { builderParams: `${typeof fieldAsDateField["__autoNow"] === "boolean" ? `.autoNow(${fieldAsDateField["__autoNow"]})` : ""}${typeof fieldAsDateField["__autoNowAdd"] === "boolean" ? `.autoNowAdd(${fieldAsDateField["__autoNowAdd"]})` : ""}` }); }, "__toStringCallback"); /** * Supposed to be used by library maintainers. * * When you custom create a field, you might want to take advantage of the builder pattern we already support. * This let's you create functions that can be chained together to create a new field. It should be used * alongside the `_setPartialAttributes` method like * * @example * ```ts * const customBigInt = DateField.overrideType< * { create: bigint; read: bigint; update: bigint }, * { * customAttributes: { name: string }; * unique: boolean; * auto: boolean; * allowNull: true; * dbIndex: boolean; * isPrimaryKey: boolean; * defaultValue: any; * typeName: string; * engineInstance: DatabaseAdapter; * } * >({ * typeName: 'CustomBigInt' * }); * * const customBuilder = <TParams extends { name: string }>(params: TParams) => { * const field = customBigInt.new(params); * return field._setNewBuilderMethods({ * test: <TTest extends { age: number }>(param: TTest) => * // This will union the type `string` with what already exists in the field 'create' type * field._setPartialAttributes<{ create: string }, { create: 'union' }>(param) * }); * }; * * // Then your user can use it like: * * const field = customBuilder({ name: 'test' }).test({ age: 2 }); * ``` * * **Important**: `customBuilder` will be used by the end user and you are responsible for documenting it. */ _setNewBuilderMethods(functions) { if (functions === void 0) return this; const propertiesOfBase = Object.getOwnPropertyNames(Object.getPrototypeOf(functions)); for (const key of propertiesOfBase) { if (key === "constructor") continue; this[key] = functions[key].bind(this); } return this; } /** * FOR LIBRARY MAINTAINERS ONLY * * Focused for library maintainers that want to support a custom field ty