@palmares/databases
Version:
Add support for working with databases with palmares framework
342 lines • 14.8 kB
TypeScript
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