Browse Source

docs: removes logo and english doc

e22m4u 4 weeks ago
parent
commit
eda5f8e43f
4 changed files with 0 additions and 2960 deletions
  1. 0 2696
      README-en.md
  2. 0 9
      README.md
  3. BIN
      assets/logo.png
  4. 0 255
      assets/logo.svg

+ 0 - 2696
README-en.md

@@ -1,2696 +0,0 @@
-## @e22m4u/js-repository
-
-![npm version](https://badge.fury.io/js/@e22m4u%2Fjs-repository.svg)
-![license](https://img.shields.io/badge/license-mit-blue.svg)
-
-[Русский](./README.md) | English
-
-<br/>
-<div align="center">
-  <img alt="logo" src="assets/logo.png"/>
-</div>
-<br/>
-<br/>
-
-An implementation of the Repository pattern for working with databases in
-Node.js.
-
-- [Installation](#installation)
-- [Importing](#importing)
-- [Description](#description)
-- [Example](#example)
-- [Schema](#schema)
-- [Datasource](#datasource)
-- [Model](#model)
-- [Properties](#properties)
-- [Validators](#validators)
-  - [Global Validators](#global-validators)
-  - [Registering Global Validators](#registering-global-validators)
-  - [Local Validators](#local-validators)
-- [Transformers](#transformers)
-  - [Global Transformers](#global-transformers)
-  - [Registering Global Transformers](#registering-global-transformers)
-  - [Local Transformers](#local-transformers)
-- [Empty Values](#empty-values)
-  - [Overriding Empty Values](#overriding-empty-values)
-- [Repository](#repository)
-  - [create](#repositorycreate)
-  - [replaceById](#repositoryreplacebyid)
-  - [replaceOrCreate](#repositoryreplaceorcreate)
-  - [patchById](#repositorypatchbyid)
-  - [patch](#repositorypatch)
-  - [find](#repositoryfind)
-  - [findOne](#repositoryfindone)
-  - [findById](#repositoryfindbyid)
-  - [delete](#repositorydelete)
-  - [deleteById](#repositorydeletebyid)
-  - [exists](#repositoryexists)
-  - [count](#repositorycount)
-- [Filtering](#filtering)
-- [Relations](#relations)
-  - [Belongs To](#belongs-to)
-  - [Has One](#has-one)
-  - [Has Many](#has-many)
-  - [References Many](#references-many)
-  - [Belongs To (polymorphic)](#belongs-to-polymorphic-version)
-  - [Has One (polymorphic)](#has-one-polymorphic-version)
-  - [Has Many (polymorphic)](#has-many-polymorphic-version)
-- [Extending](#extending)
-- [TypeScript](#typescript)
-- [Tests](#tests)
-- [License](#license)
-
-## Installation
-
-```bash
-npm install @e22m4u/js-repository
-```
-
-Optionally, install the required adapter.
-
-| adapter   | description                                  | installation                                                               |
-|-----------|----------------------------------------------|----------------------------------------------------------------------------|
-| `memory`  | An in-process memory database                | *built-in*                                                                 |
-| `mongodb` | MongoDB is a document-oriented database | [npm](https://www.npmjs.com/package/@e22m4u/js-repository-mongodb-adapter) |
-
-## Importing
-
-The module supports both ESM and CommonJS standards.
-
-*ESM*
-
-```js
-import {DatabaseSchema} from '@e22m4u/js-repository';
-```
-
-*CommonJS*
-
-```js
-const {DatabaseSchema} = require('@e22m4u/js-repository');
-```
-
-## Description
-
-The module allows you to abstract away from various database interfaces
-by presenting them as named *datasources* connected to *models*.
-A *Model* describes a database table (or collection), whose columns are the
-model's properties. Model properties can have a specific *type* of allowed
-value, a set of *validators*, and *transformers* that data passes through
-before being written to the database. Additionally, a *model* can define
-classic "one-to-one," "one-to-many," and other types of relationships
-between models.
-
-Data is read and written directly through a *repository*, which is available
-for every model with a declared *datasource*. The repository can filter
-queried documents, validate properties according to the model definition,
-and embed related data into the query results.
-
-- *Datasource* - defines how to connect to the database.
-- *Model* - describes the document structure and relations to other models.
-- *Repository* - performs read and write operations for model documents.
-
-```mermaid
-flowchart TD
-
-  A[Schema]
-  subgraph Databases
-    B[Datasource 1]
-    C[Datasource 2]
-  end
-  A-->B
-  A-->C
-
-  subgraph Collections
-    D[Model A]
-    E[Model B]
-    F[Model C]
-    G[Model D]
-  end
-  B-->D
-  B-->E
-  C-->F
-  C-->G
-
-  H[Repository A]
-  I[Repository B]
-  J[Repository C]
-  K[Repository D]
-  D-->H
-  E-->I
-  F-->J
-  G-->K
-```
-
-## Example
-
-This example demonstrates creating a schema instance, declaring a datasource,
-and a `country` model. Then, using the model's repository, a new document
-(a country) is added to the collection and logged to the console.
-
-```
-   Country (country)
-┌─────────────────────────┐
-│  id: 1                  │
-│  name: "Russia"         │
-│  population: 143400000  │
-└─────────────────────────┘
-```
-
-```js
-import {DataType} from '@e22m4u/js-repository';
-import {DatabaseSchema} from '@e22m4u/js-repository';
-
-// create a DatabaseSchema instance
-const dbs = new DatabaseSchema();
-
-// declare the "myDb" datasource
-dbs.defineDatasource({
-  name: 'myDb',      // name of the new datasource
-  adapter: 'memory', // chosen adapter
-});
-
-// declare the "country" model
-dbs.defineModel({
-  name: 'country',    // name of the new model
-  datasource: 'myDb', // chosen datasource
-  properties: {       // model properties
-    name: DataType.STRING,       // type "string"
-    population: DataType.NUMBER, // type "number"
-  },
-})
-
-// get the model's repository
-const countryRep = dbs.getRepository('country');
-
-// add a new document to the collection
-const country = await countryRep.create({
-  name: 'Russia',
-  population: 143400000,
-});
-
-// log the new document
-console.log(country);
-// {
-//   id: 1,
-//   name: 'Russia',
-//   population: 143400000,
-// }
-```
-
-The next block defines a `city` model with a `belongsTo` relation to the
-`country` model from the example above. Then, a new city document is created,
-linked to the previously created country. After creating the new document, a
-query is executed to retrieve this city with its related country included.
-
-```
-   Country (country)                  City (city)
-┌─────────────────────────┐       ┌─────────────────────────┐
-│  id: 1  <───────────────│───┐   │  id: 1                  │
-│  name: "Russia"         │   │   │  name: "Moscow"         │
-│  population: 143400000  │   └───│─ countryId: 1           │
-└─────────────────────────┘       └─────────────────────────┘
-```
-
-```js
-// declare the "city" model with a relation to "country"
-dbs.defineModel({
-  name: 'city',
-  datasource: 'myDb',
-  properties: {
-    name: DataType.STRING,
-    countryId: DataType.NUMBER,
-    // defining the "countryId" foreign key is not strictly necessary,
-    // but it is recommended for type validation before writing to the
-    // database, as the "memory" adapter creates numeric IDs by default
-  },
-  relations: {
-    // defining the "country" relation allows for automatic inclusion
-    // of related documents using the "include" option in repository methods
-    country: {
-      type: RelationType.BELONGS_TO, // relation type: belongs to...
-      model: 'country',              // target model name
-      foreignKey: 'countryId',       // foreign key field (optional)
-      // if the foreign key follows the `relationName` + `Id` convention,
-      // the `foreignKey` option is not required.
-    },
-  },
-});
-
-// get the repository for the "city" model
-const cityRep = dbs.getRepository('city');
-
-// create a new city and link it to the country via country.id
-const city = await cityRep.create({
-  name: 'Moscow',
-  countryId: country.id, // use the id from the previously created country
-});
-
-console.log(city);
-// {
-//   id: 1,
-//   name: 'Moscow',
-//   countryId: 1,
-// }
-
-// retrieve the city by its id, including the related country
-const cityWithCountry = await cityRep.findById(city.id, {
-  include: 'country',
-});
-
-console.log(cityWithCountry);
-// {
-//   id: 1,
-//   name: 'Moscow',
-//   countryId: 1,
-//   country: {
-//     id: 1,
-//     name: 'Russia',
-//     population: 143400000
-//   }
-// }
-```
-
-## Schema
-
-An instance of the `DatabaseSchema` class stores datasource and model
-definitions.
-
-**Methods**
-
-- `defineDatasource(datasourceDef: object): this` - add a datasource
-- `defineModel(modelDef: object): this` - add a model
-- `getRepository(modelName: string): Repository` - get a repository
-
-**Examples**
-
-Import the class and create a schema instance.
-
-```js
-import {DatabaseSchema} from '@e22m4u/js-repository';
-
-const dbs = new DatabaseSchema();
-```
-
-Define a new datasource.
-
-```js
-dbs.defineDatasource({
-  name: 'myDb',      // name of the new datasource
-  adapter: 'memory', // chosen adapter
-});
-```
-
-Define a new model.
-
-```js
-dbs.defineModel({
-  name: 'product',   // name of the new model
-  datasource: 'myDb',// chosen datasource
-  properties: {      // model properties
-    name: DataType.STRING,
-    weight: DataType.NUMBER,
-  },
-});
-```
-
-Get a repository by the model name.
-
-```js
-const productRep = dbs.getRepository('product');
-```
-
-## Datasource
-
-A datasource stores the name of the selected adapter and its settings.
-A new datasource is defined using the `defineDatasource` method of a
-`DatabaseSchema` instance.
-
-**Parameters**
-
-- `name: string` a unique name
-- `adapter: string` the chosen adapter
-- adapter-specific parameters (if any)
-
-**Examples**
-
-Define a new datasource.
-
-```js
-dbs.defineDatasource({
-  name: 'myDb',      // name of the new datasource
-  adapter: 'memory', // chosen adapter
-});
-```
-
-Passing additional parameters, using the MongoDB adapter as an example
-(*[install](https://www.npmjs.com/package/@e22m4u/js-repository-mongodb-adapter)*).
-
-```js
-dbs.defineDatasource({
-  name: 'myDb',
-  adapter: 'mongodb',
-  // parameters for the "mongodb" adapter
-  host: '127.0.0.1',
-  port: 27017,
-  database: 'myDatabase',
-});
-```
-
-## Model
-
-Describes the structure of a collection's documents and its relations to
-other models. A new model is defined using the `defineModel` method of a
-`DatabaseSchema` instance.
-
-**Parameters**
-
-- `name: string` model name (required)
-- `base: string` name of the base model to inherit from
-- `tableName: string` name of the collection in the database
-- `datasource: string` the chosen datasource
-- `properties: object` property definitions (see [Properties](#properties))
-- `relations: object` relation definitions (see [Relations](#relations))
-
-**Examples**
-
-Define a model with properties of specified types.
-
-```js
-dbs.defineModel({
-  name: 'user', // name of the new model
-  properties: { // model properties
-    name: DataType.STRING,
-    age: DataType.NUMBER,
-  },
-});
-```
-
-## Properties
-
-The `properties` parameter in a model definition accepts an object where keys
-are property names and values are either the property type or an object with
-additional parameters.
-
-**Data Types**
-
-- `DataType.ANY` any value is allowed
-- `DataType.STRING` only `string` values
-- `DataType.NUMBER` only `number` values
-- `DataType.BOOLEAN` only `boolean` values
-- `DataType.ARRAY` only `array` values
-- `DataType.OBJECT` only `object` values
-
-**Parameters**
-
-- `type: string` the allowed value type (required)
-- `itemType: string` array item type (for `type: 'array'`)
-- `model: string` object model (for `type: 'object'`)
-- `primaryKey: boolean` declares the property as a primary key
-- `columnName: string` overrides the column name
-- `columnType: string` column type (defined by the adapter)
-- `required: boolean` declares the property as required
-- `default: any` default value
-- `validate: string | Function | array | object` see [Validators](#validators)
-- `unique: boolean | string` check value for uniqueness
-
-**The `unique` Parameter**
-
-If the `unique` parameter is `true` or `'strict'`, a strict uniqueness
-check is performed. In this mode, [empty values](#empty-values) are also
-checked, where `null` and `undefined` are considered values that must be
-unique.
-
-The `'sparse'` mode only checks values with a payload, excluding
-[empty values](#empty-values), the list of which varies depending on the
-property type. For example, for a `string` type, empty values are
-`undefined`, `null`, and `''` (an empty string).
-
-- `unique: true | 'strict'` strict uniqueness check
-- `unique: 'sparse'` exclude [empty values](#empty-values) from the check
-- `unique: false | 'nonUnique'` do not check for uniqueness (default)
-
-You can use predefined constants as equivalents for the string values
-`strict`, `sparse`, and `nonUnique`.
-
-- `PropertyUniqueness.STRICT`
-- `PropertyUniqueness.SPARSE`
-- `PropertyUniqueness.NON_UNIQUE`
-
-**Examples**
-
-Short-form property definition.
-
-```js
-dbs.defineModel({
-  name: 'city',
-  properties: { // model properties
-    name: DataType.STRING,       // property type "string"
-    population: DataType.NUMBER, // property type "number"
-  },
-});
-```
-
-Full-form property definition.
-
-```js
-dbs.defineModel({
-  name: 'city',
-  properties: { // model properties
-    name: {
-      type: DataType.STRING, // property type "string" (required)
-      required: true,        // disallows undefined and null values
-    },
-    population: {
-      type: DataType.NUMBER, // property type "number" (required)
-      default: 0,            // default value
-    },
-    code: {
-      type: DataType.NUMBER,             // property type "number" (required)
-      unique: PropertyUniqueness.STRICT, // check for uniqueness
-    },
-  },
-});
-```
-
-Factory default value. The function's return value will be determined at the
-time the document is written.
-
-```js
-dbs.defineModel({
-  name: 'article',
-  properties: { // model properties
-    tags: {
-      type: DataType.ARRAY,     // property type "array" (required)
-      itemType: DataType.STRING,// item type "string"
-      default: () => [],        // factory value
-    },
-    createdAt: {
-      type: DataType.STRING, // property type "string" (required)
-      default: () => new Date().toISOString(), // factory value
-    },
-  },
-});
-```
-
-## Validators
-
-Validators are used to check a property's value before writing it to the
-database. Validation occurs immediately after the type check specified in the
-model's property definition. [Empty values](#empty-values) bypass validators
-as they have no payload.
-
-### Global Validators
-
-The module comes with a set of global validators:
-
-- `regexp` validates against a regular expression,
-  *parameter: `string | RegExp` - the regular expression;*
-
-- `maxLength` maximum length for a string or array,
-  *parameter: `number` - the maximum length;*
-
-- `minLength` minimum length for a string or array,
-  *parameter: `number` - the minimum length;*
-
-The validators below are under development:
-
-- `isLowerCase` checks for lowercase letters only;
-- `isUpperCase` checks for uppercase letters only;
-- `isEmail` checks for a valid email format;
-
-**Examples**
-
-Using a global validator.
-
-```js
-dbs.defineModel({
-  name: 'user',
-  properties: {
-    email: {
-      type: DataType.STRING,
-      validate: 'isEmail',
-    },
-  },
-});
-```
-
-Using global validators as an array.
-
-```js
-dbs.defineModel({
-  name: 'user',
-  properties: {
-    email: {
-      type: DataType.STRING,
-      validate: [
-        'isEmail',
-        'isLowerCase',
-      ],
-    },
-  },
-});
-```
-
-Using global validators with arguments.
-
-```js
-dbs.defineModel({
-  name: 'user',
-  properties: {
-    name: {
-      type: DataType.STRING,
-      validate: {
-        minLength: 2,
-        maxLength: 24,
-        regexp: /^[a-zA-Z-']+$/,
-      },
-    },
-  },
-});
-```
-
-Global validators without parameters can accept any arguments.
-
-```js
-dbs.defineModel({
-  name: 'user',
-  properties: {
-    email: {
-      type: DataType.STRING,
-      validate: {
-        maxLength: 100,
-        // since the "isEmail" validator has no parameters,
-        // its definition allows any value to be passed
-        // as an argument
-        isEmail: true,
-      },
-    },
-  },
-});
-```
-
-### Registering Global Validators
-
-A validator is a function that receives the value of the corresponding field
-before it is written to the database. If the function returns `false` during
-validation, a standard error is thrown. You can replace the standard error
-by throwing a custom error directly inside the validation function.
-
-A global validator is registered using the `addValidator` method of the
-`PropertyValidatorRegistry` service, which takes the validator name and the
-validation function.
-
-**Examples**
-
-Registering a global validator to check UUID format.
-
-```js
-import {createError} from 'http-errors';
-import {format} from '@e22m4u/js-format';
-import {Errorf} from '@e22m4u/js-format';
-import {PropertyValidatorRegistry} from '@e22m4u/js-repository';
-
-// get the service instance
-const pvr = dbs.getService(PropertyValidatorRegistry);
-
-// regular expressions for different UUID versions
-const uuidRegex = {
-  any: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
-  v4: /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
-};
-
-// register the global validator "isUuid",
-// which accepts a settings object with a "version" property.
-pvr.addValidator('isUuid', (value, options, context) => {
-  // value   - the value being validated;
-  // options - validator parameters;
-  // context - information about the property being validated;
-  console.log(options);
-  // {
-  //   version: 'v4'
-  // }
-  console.log(context);
-  // {
-  //   validatorName: 'isUuid',
-  //   modelName: 'device',
-  //   propName: 'deviceId'
-  // }
-
-  // empty values are not passed to validators
-  // (the condition below will never trigger)
-  if (typeof value !== 'string') return false;
-  // find the regex for the specified UUID version
-  // (from the validator's options)
-  const version = options?.version || 'any';
-  const regex = uuidRegex[version];
-  // if the regex is not found,
-  // an internal error is thrown
-  if (!regex)
-    throw new Errorf(
-      'Invalid UUID version %v specified for validator.',
-      version,
-    );
-  // if validation fails, a 400 BadRequest
-  // error is thrown
-  if (!regex.test(value)) {
-    const versionString = version !== 'any' ? ` (version ${version})` : '';
-    throw createError(400, format(
-      'The property %v of the model %v must be a valid UUID%s.',
-      context.propName,
-      context.modelName,
-      versionString,
-    ));
-  }
-  // on successful validation, return true;
-  // otherwise, a standard validation error
-  // will be thrown
-  return true;
-});
-```
-
-Using the global validator in a property definition.
-
-```js
-// define the "device" model
-dbs.defineModel({
-  name: 'device',
-  properties: {
-    deviceId: {
-      type: DataType.STRING,
-      required: true,
-      validate: {
-        // the value {version: 'v4'} will be passed as the second
-        // argument to the validator function
-        isUuid: {version: 'v4'},
-      },
-    },
-  },
-});
-```
-
-### Local Validators
-
-A validator function can be passed directly in the property definition without
-prior registration. To do this, simply pass the function to the `validate`
-parameter as a value or as an array element alongside other validators.
-
-**Examples**
-
-Using a local validator to check password strength.
-
-```js
-// the `passwordStrength` validator checks password complexity
-function passwordStrength(value, options, context) {
-  // value   - the value being validated;
-  // options - not used;
-  // context - information about the property;
-  console.log(context);
-  // {
-  //   validatorName: 'passwordStrength',
-  //   modelName: 'user',
-  //   propName: 'password'
-  // }
-  const errors = [];
-  if (value.length < 8)
-    errors.push('must be at least 8 characters long');
-  if (!/\d/.test(value))
-    errors.push('must contain at least one number');
-  if (!/[a-zA-Z]/.test(value))
-    errors.push('must contain at least one letter');
-  // if any condition is met,
-  // an error is thrown
-  if (errors.length > 0)
-    throw createError(400, format(
-      'Value of the property %v of the model %v %s.',
-      context.propName,
-      context.modelName,
-      errors.join(', '),
-    ));
-  // on successful validation, return true;
-  // otherwise, a standard validation error
-  // will be thrown
-  return true;
-}
-
-// define the "user" model
-dbs.defineModel({
-  name: 'user',
-  properties: {
-    password: {
-      type: DataType.STRING,
-      required: true,
-      validate: passwordStrength, // <=
-      // or
-      // validate: [passwordStrength, ...]
-    },
-  },
-});
-```
-
-Using an anonymous validator function to check a slug.
-
-```js
-// define the "article" model
-dbs.defineModel({
-  name: 'article',
-  properties: {
-    slug: {
-      type: DataType.STRING,
-      validate: (value) => {
-        const re = /^[a-z0-9]+(-[a-z0-9]+)*$/;
-        return re.test(value);
-      },
-    },
-  },
-});
-```
-
-## Transformers
-
-Transformers are used to modify a property's value before type checking and
-sending the data to the database. Transformers allow for automatic cleaning
-or formatting of data. [Empty values](#empty-values) are not passed to
-transformers as they have no payload.
-
-### Global Transformers
-
-The module comes with a set of global transformers:
-
-- `trim` removes whitespace from the beginning and end of a string;
-- `toUpperCase` converts a string to uppercase;
-- `toLowerCase` converts a string to lowercase;
-
-The transformers below are under development:
-
-- `cut` truncates a string or array to the specified length,
-  *parameter: `number` - the maximum length;*
-
-- `truncate` truncates a string and appends an ellipsis,
-  *parameter: `number` - the maximum length;*
-
-- `capitalize` converts the first letter of each word to uppercase,
-  *parameter: `{firstWordOnly?: boolean}`;*
-
-**Examples**
-
-Using a global transformer.
-
-```js
-dbs.defineModel({
-  name: 'user',
-  properties: {
-    username: {
-      type: DataType.STRING,
-      transform: 'toLowerCase',
-    },
-  },
-});
-```
-
-Using global transformers as an array.
-
-```js
-dbs.defineModel({
-  name: 'user',
-  properties: {
-    firstName: {
-      type: DataType.STRING,
-      transform: [
-        'trim',
-        'capitalize',
-      ],
-    },
-  },
-});
-```
-
-Using global transformers with arguments.
-
-```js
-dbs.defineModel({
-  name: 'article',
-  properties: {
-    annotation: {
-      type: DataType.STRING,
-      transform: {
-        truncate: 200,
-        capitalize: {firstWordOnly: true},
-      },
-    },
-  },
-});
-```
-
-Global transformers without parameters can accept any arguments.
-
-```js
-dbs.defineModel({
-  name: 'user',
-  properties: {
-    firstName: {
-      type: DataType.STRING,
-      transform: {
-        cut: 60,
-        // since the "trim" transformer has no parameters,
-        // its definition allows any value to be passed
-        // as an argument
-        trim: true,
-      },
-    },
-  },
-});
-```
-
-### Registering Global Transformers
-
-A transformer is a function that takes a property's value and returns a new
-value. The function can be either synchronous or asynchronous (return a
-`Promise`).
-
-A global transformer is registered using the `addTransformer` method of the
-`PropertyTransformerRegistry` service, which takes the transformer name and
-the function itself.
-
-**Examples**
-
-Registering a global transformer to remove HTML tags.
-
-```js
-import {PropertyTransformerRegistry} from '@e22m4u/js-repository';
-
-// get the service instance
-const ptr = dbs.getService(PropertyTransformerRegistry);
-
-// register the global transformer "stripTags"
-ptr.addTransformer('stripTags', (value, options, context) => {
-  // value   - the value being transformed;
-  // options - transformer settings (if provided);
-  // context - information about the property;
-  console.log(context);
-  // {
-  //   transformerName: 'stripTags',
-  //   modelName: 'comment',
-  //   propName: 'text'
-  // }
-  
-  if (typeof value !== 'string')
-    return value; // return as is if not a string
-  
-  return value.replace(/<[^>]*>?/gm, '');
-});
-```
-
-Using the global transformer in a model definition.
-
-```js
-dbs.defineModel({
-  name: 'comment',
-  properties: {
-    text: {
-      type: DataType.STRING,
-      transform: 'stripTags',
-    },
-  },
-});
-```
-
-### Local Transformers
-
-A transformer function can be passed directly in the property definition
-without prior registration. To do this, simply pass the function to the
-`transform` parameter as a value or as an array element.
-
-**Examples**
-
-Using a local transformer to normalize names.
-
-```js
-// function to normalize a name
-function normalizeName(value, options, context) {
-  // value   - the value being transformed
-  // options - not used
-  // context - information about the property
-  if (!value || typeof value !== 'string') return value;
-  return value
-    .trim()        // remove leading/trailing whitespace
-    .toLowerCase() // convert to lowercase
-    .split(' ')    // split into words
-    // capitalize the first letter of each word
-    .map(word => word.charAt(0).toUpperCase() + word.slice(1))
-    .join(' ');    // join the array back into a string
-}
-
-// define the "user" model
-dbs.defineModel({
-  name: 'user',
-  properties: {
-    firstName: {
-      type: DataType.STRING,
-      transform: normalizeName, // <=
-    },
-    lastName: {
-      type: DataType.STRING,
-      transform: normalizeName, // <=
-    },
-  },
-});
-```
-
-Using a local asynchronous transformer to hash a password.
-
-```js
-import * as bcrypt from 'bcrypt';
-
-// asynchronous function to hash a value
-async function hash(value, options, context) {
-  // value   - the value being transformed
-  // options - not used
-  // context - information about the property
-  console.log(context);
-  // {
-  //   transformerName: 'hash',
-  //   modelName: 'user',
-  //   propName: 'password'
-  // }
-  const saltRounds = 10;
-  return bcrypt.hash(value, saltRounds);
-}
-
-// define the "user" model
-dbs.defineModel({
-  name: 'user',
-  properties: {
-    password: {
-      type: DataType.STRING,
-      transform: hash, // <=
-      // or
-      // transform: [hash, ...]
-    },
-  },
-});
-```
-
-Using an anonymous transformer function to correct a slug.
-
-```js
-dbs.defineModel({
-  name: 'article',
-  properties: {
-    slug: {
-      type: DataType.STRING,
-      transform: (value) => {
-        if (typeof value !== 'string') return value;
-        return value.toLowerCase().replace(/\s+/g, '-');
-      },
-    },
-  },
-});
-```
-
-## Empty Values
-
-Different property types have their own sets of empty values. These sets are
-used to determine if a property's value has a payload. For example, the
-`default` parameter in a property definition sets a default value only if the
-incoming value is empty. The `required` parameter disallows empty values by
-throwing an error. And the `unique` parameter in `sparse` mode allows
-duplicate empty values for a unique property, as they are not included in the
-uniqueness check.
-
-| type        | empty values              |
-|-------------|---------------------------|
-| `'any'`     | `undefined`, `null`       |
-| `'string'`  | `undefined`, `null`, `''` |
-| `'number'`  | `undefined`, `null`, `0`  |
-| `'boolean'` | `undefined`, `null`       |
-| `'array'`   | `undefined`, `null`, `[]` |
-| `'object'`  | `undefined`, `null`, `{}` |
-
-### Overriding Empty Values
-
-The set of empty values for any data type can be overridden. These sets are
-managed through a special service provided by the
-[@e22m4u/js-empty-values](https://www.npmjs.com/package/@e22m4u/js-empty-values)
-module (no installation required).
-
-**EmptyValuesService**
-
-To override empty values, you need to get an instance of the
-`EmptyValuesService` class from the schema's container and call the method
-that accepts the data type and an array of new values.
-
-Interface:
-
-```ts
-class EmptyValuesService {
-  /**
-   * Set empty values
-   * for a specific data type.
-   * 
-   * @param dataType    The data type.
-   * @param emptyValues An array of new empty values.
-   */
-  setEmptyValuesOf(
-    dataType: DataType,
-    emptyValues: unknown[],
-  ): this;
-}
-```
-
-**Example**
-
-By default, the value `0` is considered empty for numeric properties. The
-following example shows how to change this behavior, leaving only `undefined`
-and `null` as empty values.
-
-```js
-import {DataType} from '@e22m4u/js-repository';
-import {DatabaseSchema} from '@e22m4u/js-repository';
-import {EmptyValuesService} from '@e22m4u/js-empty-values';
-
-const dbs = new DatabaseSchema();
-
-// get the service for working with empty values
-const emptyValuesService = dbs.getService(EmptyValuesService);
-
-// override empty values for the DataType.NUMBER type
-emptyValuesService.setEmptyValuesOf(DataType.NUMBER, [undefined, null]);
-```
-
-After this, the value `0` for properties of type `DataType.NUMBER` will no
-longer be considered empty, will pass validator checks, and will not be
-replaced by a default value.
-
-## Repository
-
-The repository performs read and write operations for a specific model's data.
-It acts as an intermediary between the application's business logic and the
-database.
-
-**Methods**
-
-- [`create(data, filter = undefined)`](#repositorycreate) creates a new document;
-- [`replaceById(id, data, filter = undefined)`](#repositoryreplacebyid) completely replaces a document;
-- [`replaceOrCreate(data, filter = undefined)`](#repositoryreplaceorcreate) replaces or creates a new document;
-- [`patchById(id, data, filter = undefined)`](#repositorypatchbyid) partially updates a document;
-- [`patch(data, where = undefined)`](#repositorypatch) updates all or matching documents;
-- [`find(filter = undefined)`](#repositoryfind) finds all or matching documents;
-- [`findOne(filter = undefined)`](#repositoryfindone) finds the first matching document;
-- [`findById(id, filter = undefined)`](#repositoryfindbyid) finds a document by its ID;
-- [`delete(where = undefined)`](#repositorydelete) deletes all or matching documents;
-- [`deleteById(id)`](#repositorydeletebyid) deletes a document by its ID;
-- [`exists(id)`](#repositoryexists) checks for existence by ID;
-- [`count(where = undefined)`](#repositorycount) counts all or matching documents;
-
-**Arguments**
-
-- `id: number|string` identifier (primary key)
-- `data: object` document data (used for writing)
-- `where: object` filter conditions (see [Filtering](#filtering))
-- `filter: object` query parameters (see [Filtering](#filtering))
-
-**Getting a Repository**
-
-You can get a repository using the `getRepository()` method of a
-`DatabaseSchema` instance. The method takes the model name as an argument.
-The model must have a defined [datasource](#datasource), as the repository
-interacts directly with the database through the adapter specified in the
-datasource.
-
-```js
-// declare a datasource
-dbs.defineDatasource({
-  name: 'myDatasource',
-  adapter: 'memory', // adapter
-});
-
-// declare a model
-dbs.defineModel({
-  name: 'myModel',
-  datasource: 'myDatasource',
-  // properties: { ... },
-  // relations: { ... }
-});
-
-// get the model's repository
-const modelRep = dbs.getRepository('myModel');
-```
-
-The first time `getRepository('myModel')` is called, a new repository instance
-is created and cached. All subsequent calls with the same model name will
-return the existing instance.
-
-### repository.create
-
-Creates a new document in the collection based on the provided data. Returns
-the created document with an assigned identifier.
-
-Signature:
-
-```ts
-create(
-  data: WithOptionalId<FlatData, IdName>,
-  filter?: ItemFilterClause<FlatData>,
-): Promise<FlatData>;
-```
-
-**Examples**
-
-Create a new document.
-
-```js
-const newProduct = await productRep.create({
-  name: 'Laptop',
-  price: 1200,
-});
-console.log(newProduct);
-// {
-//   id: 1,
-//   name: 'Laptop',
-//   price: 1200,
-// }
-```
-
-Create a document, returning only specific fields.
-
-```js
-const product = await productRep.create(
-  {name: 'Mouse', price: 25},
-  {fields: ['id', 'name']},
-);
-console.log(product);
-// {
-//   id: 2,
-//   name: 'Mouse',
-// }
-```
-
-Create a document, including related data in the result.
-
-```js
-// assumes the Product model has a "category" relation
-// (the "include" option only affects the returned result)
-const product = await productRep.create(
-  {name: 'Keyboard', price: 75, categoryId: 10},
-  {include: 'category'},
-);
-console.log(product);
-// {
-//   id: 3,
-//   name: 'Keyboard',
-//   price: 75,
-//   categoryId: 10,
-//   category: {id: 10, name: 'Electronics'}
-// }
-```
-
-### repository.replaceById
-
-Completely replaces an existing document by its identifier. All previous data
-in the document, except the identifier, is removed. Fields not provided in
-`data` will be absent from the final document (unless they have a default
-value).
-
-Signature:
-
-```ts
-replaceById(
-  id: IdType,
-  data: WithoutId<FlatData, IdName>,
-  filter?: ItemFilterClause<FlatData>,
-): Promise<FlatData>;
-```
-
-**Examples**
-
-Replace a document by its identifier.
-
-```js
-// original document
-// {
-//   id: 1,
-//   name: 'Laptop',
-//   price: 1200,
-//   inStock: true
-// }
-
-const updatedProduct = await productRep.replaceById(1, {
-  name: 'Laptop Pro',
-  price: 1500,
-});
-console.log(updatedProduct);
-// {
-//   id: 1,
-//   name: 'Laptop Pro',
-//   price: 1500
-// }
-// the "inStock" property is removed
-```
-
-### repository.replaceOrCreate
-
-Replaces an existing document if the provided data includes an identifier that
-already exists in the collection. Otherwise, if the identifier is not
-provided or not found, it creates a new document.
-
-Signature:
-
-```ts
-replaceOrCreate(
-  data: WithOptionalId<FlatData, IdName>,
-  filter?: ItemFilterClause<FlatData>,
-): Promise<FlatData>;
-```
-
-**Examples**
-
-Create a new document if `id: 3` does not exist.
-
-```js
-const product = await productRep.replaceOrCreate({
-  id: 3,
-  name: 'Keyboard',
-  price: 75,
-});
-console.log(product);
-// {
-//   id: 3,
-//   name: 'Keyboard',
-//   price: 75,
-// }
-```
-
-Replace an existing document with `id: 1`.
-
-```js
-const updatedProduct = await productRep.replaceOrCreate({
-  id: 1,
-  name: 'Laptop Pro',
-  price: 1500,
-});
-console.log(updatedProduct);
-// {
-//   id: 1,
-//   name: 'Laptop Pro',
-//   price: 1500,
-// }
-```
-
-### repository.patchById
-
-Partially updates an existing document by its identifier, modifying only the
-provided fields. The other fields of the document remain unchanged.
-
-Signature:
-
-```ts
-patchById(
-  id: IdType,
-  data: PartialWithoutId<FlatData, IdName>,
-  filter?: ItemFilterClause<FlatData>,
-): Promise<FlatData>;
-```
-
-**Examples**
-
-Partially update a document by its identifier.
-
-```js
-// original document with id: 1
-// {
-//   id: 1,
-//   name: 'Laptop Pro',
-//   price: 1500
-// }
-
-const updatedProduct = await productRep.patchById(1, {
-  price: 1450,
-});
-console.log(updatedProduct);
-// {
-//   id: 1,
-//   name: 'Laptop Pro',
-//   price: 1450
-// }
-```
-
-### repository.patch
-
-Partially updates one or more documents that match the `where` conditions.
-Only the provided fields are changed; the rest remain unchanged. Returns the
-number of updated documents. If `where` is not specified, it updates all
-documents in the collection.
-
-Signature:
-
-```ts
-patch(
-  data: PartialWithoutId<FlatData, IdName>,
-  where?: WhereClause<FlatData>,
-): Promise<number>;
-```
-
-**Examples**
-
-Update documents based on a condition.
-
-```js
-// updates all products with a price less than 30
-const updatedCount = await productRep.patch(
-  {inStock: false},
-  {price: {lt: 30}},
-);
-```
-
-Update all documents.
-
-```js
-// adds or updates the updatedAt field for all documents
-const totalCount = await productRep.patch({
-  updatedAt: new Date(),
-});
-```
-
-### repository.find
-
-Finds all documents that match the filter conditions and returns them as an
-array. If no filter is specified, it returns all documents in the
-collection.
-
-Signature:
-
-```ts
-find(filter?: FilterClause<FlatData>): Promise<FlatData[]>;
-```
-
-**Examples**
-
-Find all documents.
-
-```js
-const allProducts = await productRep.find();
-```
-
-Find documents by a `where` condition.
-
-```js
-const cheapProducts = await productRep.find({
-  where: {price: {lt: 100}},
-});
-```
-
-Find with sorting and limiting the result set.
-
-```js
-const latestProducts = await productRep.find({
-  order: 'createdAt DESC',
-  limit: 10,
-});
-```
-
-### repository.findOne
-
-Finds the first document that matches the filter conditions. Returns
-`undefined` if no documents are found.
-
-Signature:
-
-```ts
-findOne(
-  filter?: FilterClause<FlatData>,
-): Promise<FlatData | undefined>;
-```
-
-**Examples**
-
-Find a single document by a condition.
-
-```js
-const expensiveProduct = await productRep.findOne({
-  where: {price: {gt: 1000}},
-  order: 'price DESC',
-});
-```
-
-Handling the case where a document is not found.
-
-```js
-const product = await productRep.findOne({
-  where: {name: 'Non-existent Product'},
-});
-if (!product) {
-  console.log('Product not found.');
-}
-```
-
-### repository.findById
-
-Finds a single document by its unique identifier. Throws an error if the
-document is not found.
-
-Signature:
-
-```ts
-findById(
-  id: IdType,
-  filter?: ItemFilterClause<FlatData>,
-): Promise<FlatData>;
-```
-
-**Examples**
-
-Find a document by `id`.
-
-```js
-try {
-  const product = await productRep.findById(1);
-  console.log(product);
-} catch (error) {
-  console.error('Product with id 1 is not found.');
-}
-```
-
-Find a document including related data.
-
-```js
-const product = await productRep.findById(1, {
-  include: 'category',
-});
-```
-
-### repository.delete
-
-Deletes one or more documents that match the `where` conditions. Returns the
-number of deleted documents. If `where` is not specified, it deletes all
-documents in the collection.
-
-Signature:
-
-```ts
-delete(where?: WhereClause<FlatData>): Promise<number>;
-```
-
-**Examples**
-
-Delete documents based on a condition.
-
-```js
-const deletedCount = await productRep.delete({
-  inStock: false,
-});
-```
-
-Delete all documents.
-
-```js
-const totalCount = await productRep.delete();
-```
-
-### repository.deleteById
-
-Deletes a single document by its unique identifier. Returns `true` if the
-document was found and deleted, otherwise `false`.
-
-Signature:
-
-```ts
-deleteById(id: IdType): Promise<boolean>;
-```
-
-**Examples**
-
-Delete a document by `id`.
-
-```js
-const wasDeleted = await productRep.deleteById(1);
-if (wasDeleted) {
-  console.log('The document was deleted.');
-} else {
-  console.log('No document found to delete.');
-}
-```
-
-### repository.exists
-
-Checks for the existence of a document with the specified identifier.
-Returns `true` if the document exists, otherwise `false`.
-
-Signature:
-
-```ts
-exists(id: IdType): Promise<boolean>;
-```
-
-**Examples**
-
-Check if a document exists by `id`.
-
-```js
-const productExists = await productRep.exists(1);
-if (productExists) {
-  console.log('A document with id 1 exists.');
-}
-```
-
-### repository.count
-
-Counts the number of documents that match the `where` conditions. If `where`
-is not specified, it returns the total number of documents in the collection.
-
-Signature:
-
-```ts
-count(where?: WhereClause<FlatData>): Promise<number>;
-```
-
-**Examples**
-
-Count documents based on a condition.
-
-```js
-const cheapCount = await productRep.count({
-  price: {lt: 100},
-});
-```
-
-Count all documents.
-
-```js
-const totalCount = await productRep.count();
-```
-
-## Filtering
-
-Some repository methods accept a settings object that affects the returned
-result. The `find` method's first parameter has the widest set of these
-settings, expecting an object with the options listed below.
-
-- `where: object` conditions to filter documents by their properties;
-- `order: string|string[]` sort by specified properties;
-- `limit: number` limit the number of documents;
-- `skip: number` skip documents (for pagination);
-- `fields: string|string[]` select necessary model properties;
-- `include: object` include related data in the result;
-
-**Example**
-
-```js
-// the "find" repository method is used for the query,
-// with a filter object passed as the first argument
-const news = await newsRepository.find({
-  where: {
-    title: {like: '%Moscow%'},
-    publishedAt: {gte: '2025-10-15T00:00:00.000Z'},
-    tags: {inq: ['world', 'politic']},
-    hidden: false,
-  },
-  order: 'publishedAt DESC',
-  limit: 12,
-  skip: 24,
-  fields: ['title', 'annotation', 'body'],
-  include: ['author', 'category'],
-})
-```
-
-### where
-
-This parameter accepts an object with query conditions and supports the
-following set of comparison operators.
-
-- [Search by value (shorthand)](#search-by-value-shorthand)
-- [`eq`](#eq-strict-equality) (strict equality)
-- [`neq`](#neq-inequality) (inequality)
-- [`gt`](#gt-greater-than) (greater than)
-- [`lt`](#lt-less-than) (less than)
-- [`gte`](#gte-greater-than-or-equal-to) (greater than or equal to)
-- [`lte`](#lte-less-than-or-equal-to) (less than or equal to)
-- [`inq`](#inq-in-a-list) (in a list)
-- [`nin`](#nin-not-in-a-list) (not in a list)
-- [`between`](#between-range) (range)
-- [`exists`](#exists-property-existence) (property existence)
-- [`like`](#like-pattern-matching) (pattern matching)
-- [`nlike`](#nlike-excluding-pattern) (excluding pattern)
-- [`ilike`](#ilike-case-insensitive-pattern) (case-insensitive pattern)
-- [`nilike`](#nilike-case-insensitive-excluding-pattern) (case-insensitive excluding pattern)
-- [`regexp`](#regexp-regular-expression) (regular expression)
-
-Conditions can be combined with logical operators:
-
-- [`and`](#and-logical-and) (logical AND)
-- [`or`](#or-logical-or) (logical OR)
-
-#### Search by value (shorthand)
-
-Finds documents where the value of the specified property is exactly equal
-to the provided value. This is a shorthand for the `{eq: ...}` operator.
-
-```js
-// finds all documents where age is 21
-const res = await rep.find({
-  where: {
-    age: 21,
-  },
-});
-```
-
-#### `eq` (strict equality)
-
-Finds documents where the property value is equal to the specified value.
-
-```js
-// finds all documents where age is 21
-const res = await rep.find({
-  where: {
-    age: {eq: 21},
-  },
-});
-```
-
-#### `neq` (inequality)
-
-Finds documents where the property value is not equal to the specified value.
-
-```js
-// finds all documents where age is not 21
-const res = await rep.find({
-  where: {
-    age: {neq: 21},
-  },
-});
-```
-
-#### `gt` (greater than)
-
-Finds documents where the property value is strictly greater than the
-specified value.
-
-```js
-// finds documents where age is greater than 30
-const res = await rep.find({
-  where: {
-    age: {gt: 30},
-  },
-});
-```
-
-#### `lt` (less than)
-
-Finds documents where the property value is strictly less than the
-specified value.
-
-```js
-// finds documents where age is less than 30
-const res = await rep.find({
-  where: {
-    age: {lt: 30},
-  },
-});
-```
-
-#### `gte` (greater than or equal to)
-
-Finds documents where the property value is greater than or equal to the
-specified value.
-
-```js
-// finds documents where age is greater than or equal to 30
-const res = await rep.find({
-  where: {
-    age: {gte: 30},
-  },
-});
-```
-
-#### `lte` (less than or equal to)
-
-Finds documents where the property value is less than or equal to the
-specified value.
-
-```js
-// finds documents where age is less than or equal to 30
-const res = await rep.find({
-  where: {
-    age: {lte: 30},
-  },
-});
-```
-
-#### `inq` (in a list)
-
-Finds documents where the property value matches one of the values in the
-provided array.
-
-```js
-// finds documents where name is 'John' or 'Mary'
-const res = await rep.find({
-  where: {
-    name: {inq: ['John', 'Mary']},
-  },
-});
-```
-
-#### `nin` (not in a list)
-
-Finds documents where the property value is not in the provided array.
-
-```js
-// finds all documents except those where name is 'John' or 'Mary'
-const res = await rep.find({
-  where: {
-    name: {nin: ['John', 'Mary']},
-  },
-});
-```
-
-#### `between` (range)
-
-Finds documents where the property value is within the specified range
-(inclusive).
-
-```js
-// finds documents where age is between 20 and 30, inclusive
-const res = await rep.find({
-  where: {
-    age: {between: [20, 30]},
-  },
-});
-```
-
-#### `exists` (property existence)
-
-Checks for the presence or absence of a property in a document. Does not
-check the property's value.
-
-- `true` the property must exist (even if its value is `null`);
-- `false` the property must be absent;
-
-```js
-// finds documents that have the 'nickname' property
-const res1 = await rep.find({
-  where: {
-    nickname: {exists: true},
-  },
-});
-
-// finds documents that do not have the 'nickname' property
-const res2 = await rep.find({
-  where: {
-    nickname: {exists: false},
-  },
-});
-```
-
-#### `like` (pattern matching)
-
-Performs a case-sensitive pattern match (see [more details](#pattern-matching-operators)).
-
-```js
-// finds {name: 'John Doe'}, but not {name: 'john doe'}
-const res = await rep.find({
-  where: {
-    name: {like: 'John%'},
-  },
-});
-```
-
-#### `nlike` (excluding pattern)
-
-Finds documents that do not match the case-sensitive pattern
-(see [more details](#pattern-matching-operators)).
-
-```js
-// finds everything except names starting with 'John'
-const res = await rep.find({
-  where: {
-    name: {nlike: 'John%'},
-  },
-});
-```
-
-#### `ilike` (case-insensitive pattern)
-
-Performs a case-insensitive pattern match (see [more details](#pattern-matching-operators)).
-
-```js
-// finds both {name: 'John Doe'} and {name: 'john doe'}
-const res = await rep.find({
-  where: {
-    name: {ilike: 'john%'},
-  },
-});
-```
-
-#### `nilike` (case-insensitive excluding pattern)
-
-Finds strings that do not match the pattern, case-insensitively
-(see [more details](#pattern-matching-operators)).
-
-```js
-// finds everything except names starting with 'John' or 'john'
-const res = await rep.find({
-  where: {
-    name: {nilike: 'john%'},
-  },
-});
-```
-
-#### `regexp` (regular expression)
-
-Finds documents where the string property's value matches the specified
-regular expression. Can be passed as a string or a `RegExp` object.
-
-```js
-// finds documents where name starts with 'J'
-const res1 = await rep.find({
-  where: {
-    name: {regexp: '^J'},
-  },
-});
-
-// finds documents where name starts with 'J' or 'j' (case-insensitive)
-const res2 = await rep.find({
-  where: {
-    name: {regexp: '^j', flags: 'i'},
-  },
-});
-```
-
-#### `and` (logical AND)
-
-Combines multiple conditions into an array, requiring every condition to be
-met.
-
-```javascript
-// finds documents where surname is 'Smith' AND age is 21
-const res = await rep.find({
-  where: {
-    and: [
-      {surname: 'Smith'},
-      {age: 21}
-    ],
-  },
-});
-```
-
-#### `or` (logical OR)
-
-Combines multiple conditions into an array, requiring at least one of them
-to be met.
-
-```javascript
-// finds documents where name is 'James' OR age is greater than 30
-const res = await rep.find({
-  where: {
-    or: [
-      {name: 'James'},
-      {age: {gt: 30}}
-    ],
-  },
-});
-```
-
-#### Pattern Matching Operators
-
-The `like`, `nlike`, `ilike`, and `nilike` operators are designed for filtering
-string properties based on pattern matching, similar to the `LIKE` operator
-in SQL. They allow finding values that match a certain structure using
-special characters.
-
-**`%`** matches any sequence of zero or more characters:
-
-- `'A%'` finds all strings starting with "A";
-- `'%a'` finds all strings ending with "a";
-- `'%word%'` finds all strings containing "word" anywhere;
-
-**`_`** matches exactly one of any character:
-
-- `'c_t'` finds "cat", "cot", but not "cart" or "ct";
-- `'cat_'` finds "cats", "cat1", but not "cat" or "catch";
-
-To find the literal characters `%` or `_`, they must be escaped with a
-backslash `\`:
-
-- `'100\%'` finds the string "100%";
-- `'file\_name'` finds the string "file_name";
-- `'path\\to'` finds the string "path\to";
-
-### order
-
-This parameter sorts the result set by the specified model properties. You can
-specify descending order with the `DESC` postfix.
-
-**Examples**
-
-Sort by the `createdAt` field.
-
-```js
-const res = await rep.find({
-  order: 'createdAt',
-});
-```
-
-Sort by the `createdAt` field in descending order.
-
-```js
-const res = await rep.find({
-  order: 'createdAt DESC',
-});
-```
-
-Sort by multiple properties in different directions.
-
-```js
-const res = await rep.find({
-  order: [
-    'title',
-    'price ASC',
-    'featured DESC',
-  ],
-});
-```
-
-*i. The `ASC` sort direction is optional.*
-
-### include
-
-This parameter includes related documents in the method's result. The names
-of the included relations must be defined in the current model
-(see [Relations](#relations)).
-
-**Examples**
-
-Include a relation by its name.
-
-```js
-const res = await rep.find({
-  include: 'city',
-});
-```
-
-Include nested relations.
-
-```js
-const res = await rep.find({
-  include: {
-    city: 'country',
-  },
-});
-```
-
-Include multiple relations using an array.
-
-```js
-const res = await rep.find({
-  include: [
-    'city',
-    'address',
-    'employees'
-  ],
-});
-```
-
-Using filtering for included documents.
-
-```js
-const res = await rep.find({
-  include: {
-    relation: 'employees', // relation name
-    scope: { // filter for "employees" documents
-      where: {hidden: false}, // query conditions
-      order: 'id', // document order
-      limit: 10, // limit the number of documents
-      skip: 5, // skip documents
-      fields: ['name', 'surname'], // only these fields
-      include: 'city', // include relations for "employees"
-    },
-  },
-});
-```
-
-## Relations
-
-Relations describe relationships between models, allowing for automatic
-embedding of related data using the `include` option in repository methods.
-The example below shows automatic relation resolution when using the
-`findById` method.
-
-```
-         Role (role)
-      ┌────────────────────┐
-      │  id: 3  <──────────│────┐
-      │  name: 'Manager'   │    │
-      └────────────────────┘    │
-                                │
-     User (user)                │
-  ┌────────────────────────┐    │
-  │  id: 1                 │    │
-  │  name: 'John Doe'      │    │
-  │  roleId: 3   ──────────│────┘
-  │  cityId: 24  ──────────│────┐
-  └────────────────────────┘    │
-                                │
-         City (city)            │
-      ┌────────────────────┐    │
-      │  id: 24  <─────────│────┘
-      │  name: 'Moscow'    │
-      └────────────────────┘
-```
-
-```js
-// query a document from the "users" collection,
-// including related data (role and city)
-const user = await userRep.findById(1, {
-  include: ['role', 'city'],
-});
-
-console.log(user);
-// {
-//   id: 1,
-//   name: 'John Doe',
-//   roleId: 3,
-//   role: {
-//     id: 3,
-//     name: 'Manager'
-//   },
-//   cityId: 24,
-//   city: {
-//     id: 24,
-//     name: 'Moscow'
-//   }
-// }
-```
-
-### Defining a Relation
-
-The `relations` property in a model definition accepts an object where keys
-are relation names and values are their parameters. The relation name can
-then be used in the `include` option of repository methods.
-
-```js
-import {
-  DataType,
-  RelationType,
-  DatabaseSchema,
-} from '@e22m4u/js-repository';
-
-dbs.defineModel({
-  name: 'user',
-  datasource: 'memory',
-  properties: {
-    name: DataType.STRING,
-  },
-  relations: {
-    // relation role -> parameters
-    role: {
-      type: RelationType.BELONGS_TO,
-      model: 'role',
-    },
-    // relation city -> parameters
-    city: {
-      type: RelationType.BELONGS_TO,
-      model: 'city',
-    },
-  },
-});
-```
-
-**Core Parameters**
-
-- `type: string` the relation type (required);
-- `model: string` the target model name (required for some types);
-- `foreignKey: string` the property in the current model for the target ID;
-
-*i. For Belongs To and References Many types, the `foreignKey` parameter
-can be omitted as it's automatically generated based on the relation name.*
-
-**Polymorphic Mode**
-
-- `polymorphic: boolean|string` declares a polymorphic relation;
-- `discriminator: string` the property in the current model for the target's name;
-
-*i. Polymorphic mode allows dynamically determining the target model by its
-name, which is stored in the document's discriminator property.*
-
-### Relation Types
-
-- [Belongs To](#belongs-to)
-  The current model references the target model by its ID.
-  `type: "belongsTo"` or `type: RelationType.BELONGS_TO`
-
-- [Has One](#has-one)
-  The inverse of `belongsTo` for a "one-to-one" relationship.
-  `type: "hasOne"` or `type: RelationType.HAS_ONE`
-
-- [Has Many](#has-many)
-  The inverse of `belongsTo` for a "one-to-many" relationship.
-  `type: "hasMany"` or `type: RelationType.HAS_MANY`
-
-- [References Many](#references-many)
-  The current model references the target model via an array of IDs.
-  `type: "referencesMany"` or `type: RelationType.REFERENCES_MANY`
-
-Polymorphic versions:
-
-- [Belongs To (polymorphic)](#belongs-to-polymorphic-version)
-- [Has One (polymorphic)](#has-one-polymorphic-version)
-- [Has Many (polymorphic)](#has-many-polymorphic-version)
-
-The `type` parameter in a relation definition accepts a string with the type
-name. To avoid typos, it's recommended to use the constants from the
-`RelationType` object listed below.
-
-- `RelationType.BELONGS_TO`
-- `RelationType.HAS_ONE`
-- `RelationType.HAS_MANY`
-- `RelationType.REFERENCES_MANY`
-
-#### Belongs To
-
-The current model references the target model by its identifier.
-
-```
-    Current (user)                  Target (role)
-┌─────────────────────────┐       ┌─────────────────────────┐
-│   id: 1                 │   ┌───│─> id: 5                 │
-│   roleId: 5  ───────────│───┤   │   ...                   │
-│   ...                   │   │   └─────────────────────────┘
-└─────────────────────────┘   │ 
-┌─────────────────────────┐   │   
-│   id: 2                 │   │
-│   roleId: 5  ───────────│───┘   
-│   ...                   │       
-└─────────────────────────┘       
-```
-
-Relation definition:
-
-```js
-dbs.defineModel({
-  name: 'user',
-  relations: {
-    role: { // relation name
-      type: RelationType.BELONGS_TO, // current model references target
-      model: 'role', // target model name
-      foreignKey: 'roleId', // foreign key (optional)
-      // if "foreignKey" is not specified, the foreign key property
-      // is formed based on the relation name with an "Id" postfix
-    },
-  },
-});
-```
-
-Example:
-
-```js
-import {
-  DataType,
-  RelationType,
-  DatabaseSchema,
-} from '@e22m4u/js-repository';
-
-const dbs = new DatabaseSchema();
-
-// datasource
-dbs.defineDatasource({
-  name: 'myDb',
-  adapter: 'memory',
-});
-
-// role model
-dbs.defineModel({
-  name: 'role',
-  datasource: 'myDb',
-  properties: {
-    name: DataType.STRING,
-  },
-});
-
-// user model
-dbs.defineModel({
-  name: 'user',
-  datasource: 'myDb',
-  properties: {
-    name: DataType.STRING,
-    roleId: DataType.NUMBER, // optional
-  },
-  relations: {
-    role: {
-      type: RelationType.BELONGS_TO,
-      model: 'role',
-      foreignKey: 'roleId', // optional
-    },
-  },
-});
-
-// create a role
-const roleRep = dbs.getRepository('role');
-const role = await roleRep.create({
-  id: 5,
-  name: 'Manager',
-});
-console.log(role);
-// {
-//   id: 5,
-//   name: 'Manager'
-// }
-
-// create a user
-const userRep = dbs.getRepository('user');
-const user = await userRep.create({
-  id: 1,
-  name: 'John Doe',
-  roleId: role.id,
-});
-console.log(user);
-// {
-//   id: 1,
-//   name: 'John Doe',
-//   roleId: 5
-// }
-
-// fetch user with related role ("include" option)
-const userWithRole = await userRep.findById(user.id, {include: 'role'});
-console.log(userWithRole);
-// {
-//   id: 1,
-//   name: 'John Doe',
-//   roleId: 5,
-//   role: {
-//     id: 5,
-//     name: 'Manager'
-//   }
-// }
-```
-
-#### Has One
-
-The inverse of `belongsTo` for a "one-to-one" relationship.
-
-```
-    Current (profile)               Target (user)
-┌─────────────────────────┐       ┌─────────────────────────┐
-│   id: 5  <──────────────│───┐   │   id: 1                 │
-│   ...                   │   └───│── profileId: 5          │
-└─────────────────────────┘       │   ...                   │
-                                  └─────────────────────────┘
-```
-
-Relation definition:
-
-```js
-// dbs.defineModel({
-//   name: 'user',
-//   relations: {
-//     profile: {
-//       type: RelationType.BELONGS_TO,
-//       model: 'profile',
-//     },
-//   },
-// });
-
-dbs.defineModel({
-  name: 'profile',
-  relations: {
-    user: { // relation name
-      type: RelationType.HAS_ONE, // target model references current
-      model: 'user', // target model name
-      foreignKey: 'profileId', // foreign key from target to current
-    },
-  },
-});
-```
-
-#### Has Many
-
-The inverse of `belongsTo` for a "one-to-many" relationship.
-
-```
-    Current (role)                  Target (user)
-┌─────────────────────────┐       ┌─────────────────────────┐
-│   id: 5  <──────────────│───┐   │   id: 1                 │
-│   ...                   │   ├───│── roleId: 5             │
-└─────────────────────────┘   │   │   ...                   │
-                              │   └─────────────────────────┘
-                              │   ┌─────────────────────────┐
-                              │   │   id: 2                 │
-                              └───│── roleId: 5             │
-                                  │   ...                   │
-                                  └─────────────────────────┘
-```
-
-Relation definition:
-
-```js
-// dbs.defineModel({
-//   name: 'user',
-//   relations: {
-//     role: {
-//       type: RelationType.BELONGS_TO,
-//       model: 'role',
-//     },
-//   },
-// });
-
-dbs.defineModel({
-  name: 'role',
-  relations: {
-    users: { // relation name
-      type: RelationType.HAS_MANY, // target model references current
-      model: 'user', // target model name
-      foreignKey: 'roleId', // foreign key in the target model
-    },
-  },
-});
-```
-
-#### References Many
-
-The current model references the target model via an array of identifiers.
-
-```
-    Current (article)                 Target (category)
-┌─────────────────────────┐       ┌─────────────────────────┐
-│   id: 1                 │   ┌───│─> id: 5                 │
-│   categoryIds: [5, 6] ──│───┤   │   ...                   │
-│   ...                   │   │   └─────────────────────────┘
-└─────────────────────────┘   │   ┌─────────────────────────┐
-                              └───│─> id: 6                 │
-                                  │   ...                   │
-                                  └─────────────────────────┘
-```
-
-Relation definition:
-
-```js
-// dbs.defineModel({name: 'category', ...
-
-dbs.defineModel({
-  name: 'article',
-  relations: {
-    categories: { // relation name
-      type: RelationType.REFERENCES_MANY, // relation via array of IDs
-      model: 'category', // target model name
-      foreignKey: 'categoryIds', // foreign key (optional)
-      // if "foreignKey" is not specified, the foreign key property
-      // is formed based on the relation name with an "Ids" postfix
-    },
-  },
-});
-```
-
-#### Belongs To (polymorphic version)
-
-The current model references a target model by its identifier. The target
-model's name is determined by a discriminator property.
-
-```
-    Current (file)               ┌──────> Target 1 (letter)
-┌─────────────────────────────┐  │    ┌─────────────────────────┐
-│   id: 1                     │  │ ┌──│─> id: 10                │
-│   referenceType: 'letter'  ─│──┘ │  │   ...                   │
-│   referenceId: 10  ─────────│────┘  └─────────────────────────┘
-└─────────────────────────────┘
-                                 ┌──────> Target 2 (user)
-┌─────────────────────────────┐  │    ┌─────────────────────────┐
-│   id: 2                     │  │ ┌──│─> id: 5                 │
-│   referenceType: 'user'  ───│──┘ │  │   ...                   │
-│   referenceId: 5  ──────────│────┘  └─────────────────────────┘
-└─────────────────────────────┘
-```
-
-Relation definition:
-
-```js
-dbs.defineModel({
-  name: 'file',
-  relations: {
-    reference: { // relation name
-      type: RelationType.BELONGS_TO, // current model references target
-      // polymorphic mode allows storing the target model's name
-      // in a discriminator property, which is formed based on
-      // the relation name with a "Type" postfix, in this case,
-      // the target model's name is stored in "referenceType",
-      // and the document's identifier is in "referenceId"
-      polymorphic: true,
-    },
-  },
-});
-```
-
-Relation definition with explicit properties:
-
-```js
-dbs.defineModel({
-  name: 'file',
-  relations: {
-    reference: { // relation name
-      type: RelationType.BELONGS_TO, // current model references target
-      polymorphic: true, // target model name is in a discriminator
-      foreignKey: 'referenceId', // property for the target's identifier
-      discriminator: 'referenceType', // property for the target's name
-    },
-  },
-});
-```
-
-#### Has One (polymorphic version)
-
-The inverse of a polymorphic `belongsTo` for a "one-to-one" relationship.
-
-```
-    Current (company)  <───────┐      Target (license)
-┌─────────────────────────┐    │  ┌─────────────────────────┐
-│   id: 10  <─────────────│──┐ │  │   id: 1                 │
-│   ...                   │  │ └──│── ownerType: 'company'  │
-└─────────────────────────┘  └────│── ownerId: 10           │
-                                  └─────────────────────────┘
-```
-
-Relation definition specifying the target's relation name:
-
-```js
-// dbs.defineModel({
-//   name: 'license',
-//   relations: {
-//     owner: {
-//       type: RelationType.BELONGS_TO,
-//       polymorphic: true,
-//     },
-//   },
-// });
-
-dbs.defineModel({
-  name: 'company',
-  relations: {
-    license: { // relation name
-      type: RelationType.HAS_ONE, // target model references current
-      model: 'license', // target model name
-      polymorphic: 'owner', // polymorphic relation name in target model
-    },
-  },
-});
-```
-
-Relation definition specifying the target model's properties:
-
-```js
-// dbs.defineModel({
-//   name: 'license',
-//   relations: {
-//     owner: {
-//       type: RelationType.BELONGS_TO,
-//       polymorphic: true,
-//       foreignKey: 'ownerId',
-//       discriminator: 'ownerType',
-//     },
-//   },
-// });
-
-dbs.defineModel({
-  name: 'company',
-  relations: {
-    license: { // relation name
-      type: RelationType.HAS_ONE, // target model references current
-      model: 'license', // target model name
-      polymorphic: true, // current model name is in discriminator
-      foreignKey: 'ownerId', // property in target for current's identifier
-      discriminator: 'ownerType', // property in target for current's name
-    },
-  },
-});
-```
-
-#### Has Many (polymorphic version)
-
-The inverse of a polymorphic `belongsTo` for a "one-to-many" relationship.
-
-```
-    Current (letter)  <─────────┐      Target (file)
-┌──────────────────────────┐    │  ┌────────────────────────────┐
-│   id: 10  <──────────────│──┐ │  │   id: 1                    │
-│   ...                    │  │ ├──│── referenceType: 'letter'  │
-└──────────────────────────┘  ├─│──│── referenceId: 10          │
-                              │ │  └────────────────────────────┘
-                              │ │  ┌────────────────────────────┐
-                              │ │  │   id: 2                    │
-                              │ └──│── referenceType: 'letter'  │
-                              └────│── referenceId: 10          │
-                                   └────────────────────────────┘
-```
-
-Relation definition specifying the target's relation name:
-
-```js
-// dbs.defineModel({
-//   name: 'file',
-//   relations: {
-//     reference: {
-//       type: RelationType.BELONGS_TO,
-//       polymorphic: true,
-//     },
-//   },
-// });
-
-dbs.defineModel({
-  name: 'letter',
-  relations: {
-    attachments: { // relation name
-      type: RelationType.HAS_MANY, // target model references current
-      model: 'file', // target model name
-      polymorphic: 'reference', // polymorphic relation name in target
-    },
-  },
-});
-```
-
-Relation definition specifying the target model's properties:
-
-```js
-// dbs.defineModel({
-//   name: 'file',
-//   relations: {
-//     reference: {
-//       type: RelationType.BELONGS_TO,
-//       polymorphic: true,
-//       foreignKey: 'referenceId',
-//       discriminator: 'referenceType',
-//     },
-//   },
-// });
-
-dbs.defineModel({
-  name: 'letter',
-  relations: {
-    attachments: { // relation name
-      type: RelationType.HAS_MANY, // target model references current
-      model: 'file', // target model name
-      polymorphic: true, // current model name is in discriminator
-      foreignKey: 'referenceId', // property in target for current's identifier
-      discriminator: 'referenceType', // property in target for current's name
-    },
-  },
-});
-```
-
-## Extending
-
-The `getRepository` method of a `DatabaseSchema` instance checks for an
-existing repository for the specified model and returns it. Otherwise, a new
-instance is created and cached for subsequent calls.
-
-```js
-import {Repository} from '@e22m4u/js-repository';
-import {DatabaseSchema} from '@e22m4u/js-repository';
-
-// const dbs = new DatabaseSchema();
-// dbs.defineDatasource ...
-// dbs.defineModel ...
-
-const rep1 = dbs.getRepository('model');
-const rep2 = dbs.getRepository('model');
-console.log(rep1 === rep2); // true
-```
-
-You can replace the default repository constructor using the `setRepositoryCtor`
-method of the `RepositoryRegistry` service, which is available in the
-`DatabaseSchema` instance's service container. After that, all new
-repositories will be created using the specified constructor instead of the
-default one.
-
-```js
-import {Repository} from '@e22m4u/js-repository';
-import {DatabaseSchema} from '@e22m4u/js-repository';
-import {RepositoryRegistry} from '@e22m4u/js-repository';
-
-class MyRepository extends Repository {
-  /*...*/
-}
-
-// const dbs = new DatabaseSchema();
-// dbs.defineDatasource ...
-// dbs.defineModel ...
-
-dbs.getService(RepositoryRegistry).setRepositoryCtor(MyRepository);
-const rep = dbs.getRepository('model');
-console.log(rep instanceof MyRepository); // true
-```
-
-*i. Since repository instances are cached, you should replace the
-constructor before calling the `getRepository` method.*
-
-## TypeScript
-
-Getting a typed repository with a specified model interface.
-
-```ts
-import {DataType} from '@e22m4u/js-repository';
-import {RelationType} from '@e22m4u/js-repository';
-import {DatabaseSchema} from '@e22m4u/js-repository';
-
-// const dbs = new DatabaseSchema();
-// dbs.defineDatasource ...
-
-// define the "city" model
-dbs.defineModel({
-  name: 'city',
-  datasource: 'myDatasource',
-  properties: {
-    name: DataType.STRING,
-    timeZone: DataType.STRING,
-  },
-});
-
-// define the "city" interface
-interface City {
-  id: number;
-  name?: string;
-  timeZone?: string;
-}
-
-// when getting a repository for a model,
-// you can specify the document type
-const cityRep = dbs.getRepository<City>('city');
-
-// now, repository methods return
-// the City type instead of Record<string, unknown>
-const city: City = await cityRep.create({
-  name: 'Moscow',
-  timeZone: 'Europe/Moscow',
-});
-```
-
-For defining models using TypeScript classes, it is recommended to use the
-specialized version of this module,
-[@e22m4u/ts-repository](https://www.npmjs.com/package/@e22m4u/ts-repository),
-which comes with a set of TypeScript decorators and additional tools for
-working in a TypeScript environment.
-
-## Tests
-
-```bash
-npm run test
-```
-
-## License
-
-MIT

+ 0 - 9
README.md

@@ -3,15 +3,6 @@
 ![npm version](https://badge.fury.io/js/@e22m4u%2Fjs-repository.svg)
 ![npm version](https://badge.fury.io/js/@e22m4u%2Fjs-repository.svg)
 ![license](https://img.shields.io/badge/license-mit-blue.svg)
 ![license](https://img.shields.io/badge/license-mit-blue.svg)
 
 
-Русский | [English](./README-en.md)
-
-<br/>
-<div align="center">
-  <img alt="logo" src="assets/logo.png"/>
-</div>
-<br/>
-<br/>
-
 Реализация паттерна «Репозиторий» для работы с базами данных в Node.js
 Реализация паттерна «Репозиторий» для работы с базами данных в Node.js
 
 
 - [Установка](#установка)
 - [Установка](#установка)

BIN
assets/logo.png


File diff suppressed because it is too large
+ 0 - 255
assets/logo.svg


Some files were not shown because too many files changed in this diff