UNPKG

@palmares/databases

Version:

Add support for working with databases with palmares framework

342 lines 14.8 kB
import { DefaultManager, Manager } from './manager'; import type { Field, ForeignKeyField } from './fields'; import type { CustomImportsForFieldType } from './fields/types'; import type { ManagersOfInstanceType, ModelFieldsType, ModelOptionsType } from './types'; import type { DatabaseAdapter } from '../engine'; import type { ExtractFieldsFromAbstracts, ExtractManagersFromAbstracts } from '../types'; declare global { var $PManagers: Map<string, Manager<any, any>> | undefined; } export declare class BaseModel { protected static $$type: string; protected __className: string; protected __stringfiedArgumentsOfEvents: Set<string>; protected __eventsUnsubscribers: (() => Promise<void>)[]; protected static __isState: boolean; protected static __fieldParsersByEngine: Map<string, { input: Set<string>; output: Set<string>; toIgnore: Set<string>; }>; protected static __associations: { [modelName: string]: { byRelationName: Map<string, ForeignKeyField<any, any, any>>; byRelatedName: Map<string, ForeignKeyField<any, any, any>>; }; }; protected static __directlyRelatedTo: { [modelName: string]: string[]; }; protected static __indirectlyRelatedTo: { [modelName: string]: string[]; }; protected static __indirectlyRelatedModels: { [modelName: string]: { [relatedModelName: string]: string[]; }; } & { $set: { [modelName: string]: () => void; }; }; protected static __primaryKeys: string[]; protected static __domainName: string; protected static __domainPath: string; protected static __callAfterAllModelsAreLoadedToSetupRelations: Map<string, (engineInstance: DatabaseAdapter) => void>; protected static __lazyOptions?: ModelOptionsType<any>; protected static __lazyFields?: ModelFieldsType; protected static __cachedHashedName?: string; protected static __cachedName: string; protected static __cachedOriginalName: string; protected static __cachedFields: ModelFieldsType | undefined; protected static __customOptions: any; protected static __cachedOptions: ModelOptionsType<any> | undefined; protected static __hasLoadedManagers: boolean; protected static __cachedManagers: ManagersOfInstanceType | undefined; protected static __hasLoadedAbstracts: boolean; protected static __instance: Model & BaseModel; protected static __initialized: { [engineName: string]: any; }; constructor(); protected __initializeManagers(engineInstance: DatabaseAdapter, modelInstance: Model & BaseModel, translatedModelInstance: { instance: any; modifyItself: (newTranslation: any) => void; }): Promise<void>; /** * This will add event listeners to the model. So when an event like `.set` or `.remove` is triggered, we will call * the event handler that was defined in the model using the `onSet` or `onRemove` options. * * By default we will take care to prevent the same data being triggered twice. * So we stringify the data and compare it, so for example if a model is trying to save the same data it * received through an event it will not trigger the event again by default. * * @param engineInstance - The current engine instance we are initializing this model instance */ protected __initializeEvents(engineInstance: DatabaseAdapter): Promise<void>; /** * Initializes the model and returns the model instance for the current engine instance that is being used. */ protected static __init(engineInstance: DatabaseAdapter, domainName: string, domainPath: string, lazyLoadFieldsCallback: (field: Field<any, any, any>, translatedField: any) => void, options?: { forceTranslate?: boolean; }): Promise<any>; /** * Compare this and another model to see if they are equal so we can create the migrations automatically for them. * You see that we do not compare the fields, for the fields we have a hole set of `CRUD` operations * if something changes there. * So it doesn't matter if two models don't have the same set of fields, if the options are equal, * then they are equal. * * @param model - The model to compare to the current model. * * @returns - Returns true if the models are equal and false otherwise. */ protected static __compareModels(engine: DatabaseAdapter, originalModel: typeof Model & typeof BaseModel, otherModel: typeof Model & typeof BaseModel): Promise<boolean>; /** * Since most data is private, we use this to extract all the data that the model has that might be useful for engine * instances. This way we don't need to expose the model to the engine */ protected __getModelAttributes(): { modelName: string; fields: Record<string, { $field: Field<any, any, any>; $model: (ModelType<any, any> & typeof Model & typeof BaseModel) | undefined; typeName: string; fieldName: string; modelName: string; isAuto: boolean; primaryKey: boolean; defaultValue: any; allowNull: boolean; unique: boolean; dbIndex: boolean; databaseName: string | undefined; underscored: boolean; customAttributes: unknown; }>; options: ModelOptionsType<any> | undefined; }; /** * Retrieves the managers from the model constructor. * * This is useful for getting the managers from an abstract model class. */ protected static __getManagers(): ManagersOfInstanceType; /** * This will load all of the abstract instances of the model. The abstracts will append 3 types of * data in the current model: * fields, options, managers and other abstracts * * So for fields we will just accept the ones not already defined in the field, if there * is any clash we will throw an error. * For options, we will only add them if the options are not already defined for the model. * Managers are similar to fields, we will not accept clashing managers with the same manager name. * * @param abstractInstance - The model class that we are instantiating. * @param composedAbstracts - We can have an abstract with an abstract and so on, for that a recursive approach * seems a good solution, this is an array with all of the abstracts that were already loaded for the current model. */ protected static __loadAbstract(abstractConstructor: typeof Model & typeof BaseModel, composedAbstracts: string[]): void; /** * Initializes all of the abstract classes of the model and loads them to the current model. * * With this we will have the model with all of the fields, options and managers as the other abstracts. */ protected static __initializeAbstracts(): void; /** * Get the options of the model. Use this to get the options of the model since here we will use the cached data * if it exists. */ protected static _options(modelInstance?: any): ModelOptionsType<any> | undefined; protected static _fields(modelInstance?: any): ModelFieldsType; protected static __originalName(): string; /** * We use this so the name of the models does not clash with the original ones during migration. * During migration we will have 2 instances of the same model running at the * same time: * * 1. The state model, built from the migration files. * 2. The original model. */ protected static __getName(): string; /** * We use the original model name to create a hash name of the model, a hash name of the model is used so * we can send events back and forth for the model between * multiple palmares instances. * * @returns - The hashed name of the model. */ protected static __hashedName(): string; protected static __fieldsToString(engine: DatabaseAdapter, fields: ModelFieldsType, indentation?: number): Promise<{ asString: string; customImports: CustomImportsForFieldType[]; }>; protected static __optionsToString(engine: DatabaseAdapter, indentation: number | undefined, options: ModelOptionsType): Promise<string>; } declare const BaseModelWithoutMethods: { new (): Pick<BaseModel, never>; }; /** * This class is used for initializing a model. This will work similar to django except that instead of * `objects` we use `instance` to make queries. So in other words, if you want to make queries directly * you will need to use. Also the instance will hold the actual instance of the model. * * >>> (await ModelName.getInstance()).findOne() * or * >>> (await ModelName.getInstance()).create() * * and so on. * * For creating Models it is simple, you've got 4 objects: `attributes`, `objects`, `managers` and `abstracts` * * The first one is obligatory, the rest is optional. * For `attributes` it is simple, just define the attributes of your model there as you would in sequelize * normally: * * Example: * In sequelize we define like: * >>> sequelize.define('User', { * firstName: { * type: DataTypes.STRING, * allowNull: false * }, * lastName: { * type: DataTypes.STRING * } * }, { * tableName: 'user' * }) * * Notice that 'User' is the name of the model, the second argument of the `.define()` function is the attributes, * it is exactly this object we will put in the attributes parameter. The second argument of the function is the * sequelize `options` sequelize parameter where we can define indexes, tableName and many other configuration. * You might want to check sequelize documentation for this: https://sequelize.org/master/manual/model-basics.html * * Okay so how do we rewrite this to something more concise and readable? * class User extends Model { * attributes = { * firstName: new model.fields.CharField(), * lastName: new model.fields.CharField() * } * * options = { * tableName: 'user' * } * * getFullName() { * return this.firstName + this.lastName * } * * custom = new CustomManager() * } * * Simple and elegant. You will notice the `attributes` is defined, the options is optional, so instead of defining an * empty object you can totally omit it if you want. * * The `.getFullName` function is an instance function it will be appended to the instance so you can make a query like * and then it will return an User model, this model will have the method. * * >>> const response = await User.instance.findOne() * >>> response.getFullName() * * We underline many stuff from sequelize so you, the programmer, don't need to worry about tooling, it will just work. * * Take a notice at manager. Manager is for building custom managers similar to django managers. * Instead of making queries through your code you can keep all of your queries inside of managers and just * define them in your model. * * For the CustomManager, this will be our definition of a custom manager * >>> class CustomManager extends Manager { * createUser(firstName, lastName) { * return this.instance.create({ firstName: firstName, lastName: lastName }) * } * } * * Okay so now we don't need to create a new user calling `.create` directly, instead we can use * * User.custom.createUser('Jane', 'Doe') * * This way we can keep queries more concise and representative by just making functions. Also * you can have the hole power of linting VSCode and other IDEs give you. */ export declare class Model extends BaseModelWithoutMethods { protected static $$type: string; fields: ModelFieldsType; options: ModelOptionsType<any> | undefined; abstracts: readonly (typeof Model & typeof BaseModel)[]; } /** * Actual model returned */ export type ModelType<TModel, TDefinitions extends { engineInstance: DatabaseAdapter; customOptions: any; } = { engineInstance: DatabaseAdapter; customOptions: any; }> = { default: DefaultManager<TModel>; appendFields: <TOtherFields extends ModelFieldsType>(fields: TOtherFields) => ModelType<TModel & { fields: TOtherFields; }, TDefinitions>; setCustomOptions: <TCustomOptions extends Parameters<TDefinitions['engineInstance']['models']['translate']>[5]>(customOptions: TCustomOptions) => ModelType<TModel, { engineInstance: TDefinitions['engineInstance']; customOptions: TCustomOptions; }>; setManagers: <TManagers extends Record<string, Manager<any>>>(managers: TManagers) => ModelType<TModel, TDefinitions> & TManagers; new (): { fields: TModel extends { fields: infer TFields; } ? TFields : any; options: TModel extends { options: infer TOptions; } ? TOptions : any; }; }; /** * This function is needed so we can add the type to the DefaultManager. This will help keeping the API simple for the * end user without complicating too much stuff. */ export declare function model<TModel, TDefinitions extends { engineInstance: DatabaseAdapter; customOptions: any; } = { engineInstance: DatabaseAdapter; customOptions: any; }>(): ModelType<TModel, TDefinitions>; /** * Used for creating a model from a function instead of needing to define a class. */ export declare function initialize<TTypeName extends string, TFields extends ModelFieldsType, const TAbstracts extends readonly { new (): { fields: any; options?: any; }; }[], const TOptions extends ModelOptionsType<{ fields: TFields; abstracts: TAbstracts; }>, const TManagers extends unknown | { [managerName: string]: Manager<any, any> | { [functionName: string]: (this: Manager<ReturnType<typeof model<{ fields: ExtractFieldsFromAbstracts<TFields, TAbstracts>; options: TOptions; }>> & { fields: ExtractFieldsFromAbstracts<TFields, TAbstracts>; options: TOptions; }, any>, ...args: any) => any; }; } = unknown>(modelName: TTypeName, args: { fields: TFields; options?: TOptions; abstracts?: TAbstracts; managers?: TManagers; }): ExtractManagersFromAbstracts<TAbstracts> & (unknown extends TManagers ? unknown : { [TManagerName in keyof TManagers]: Manager<any> & { [TFunctionName in keyof TManagers[TManagerName]]: TManagers[TManagerName][TFunctionName]; }; }) & ModelType<{ fields: ExtractFieldsFromAbstracts<TFields, TAbstracts>; options: TOptions; }>; export {}; //# sourceMappingURL=model.d.ts.map