{"id":19988763,"url":"https://github.com/e22m4u/js-repository","last_synced_at":"2025-05-04T08:32:05.698Z","repository":{"id":233559572,"uuid":"773022799","full_name":"e22m4u/js-repository","owner":"e22m4u","description":"Реализация репозитория для работы с базами данных в Node.js","archived":false,"fork":false,"pushed_at":"2025-04-24T00:55:27.000Z","size":1949,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-24T01:02:01.865Z","etag":null,"topics":["database","datasource","db","mongodb","nodejs","odm","orm","relations","repository"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/e22m4u.png","metadata":{"files":{"readme":"README-ru.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-16T14:29:24.000Z","updated_at":"2025-04-24T00:55:27.000Z","dependencies_parsed_at":"2024-04-22T19:29:42.287Z","dependency_job_id":"c51801a3-f59f-4dad-bc30-86a105ca5fa1","html_url":"https://github.com/e22m4u/js-repository","commit_stats":null,"previous_names":["e22m4u/js-repository"],"tags_count":58,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/e22m4u%2Fjs-repository","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/e22m4u%2Fjs-repository/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/e22m4u%2Fjs-repository/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/e22m4u%2Fjs-repository/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/e22m4u","download_url":"https://codeload.github.com/e22m4u/js-repository/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252308238,"owners_count":21727153,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","datasource","db","mongodb","nodejs","odm","orm","relations","repository"],"created_at":"2024-11-13T04:44:04.178Z","updated_at":"2025-05-04T08:32:04.140Z","avatar_url":"https://github.com/e22m4u.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## @e22m4u/js-repository\n\nES-модуль для работы с базами данных для Node.js\n\n- [Установка](#Установка)\n- [Импорт](#Импорт)\n- [Описание](#Описание)\n- [Пример](#Пример)\n- [Схема](#Схема)\n- [Источник данных](#Источник-данных)\n- [Модель](#Модель)\n- [Свойства](#Свойства)\n- [Валидаторы](#Валидаторы)\n- [Трансформеры](#Трансформеры)\n- [Пустые значения](#Пустые-значения)\n- [Репозиторий](#Репозиторий)\n- [Фильтрация](#Фильтрация)\n- [Связи](#Связи)\n- [Расширение](#Расширение)\n- [TypeScript](#TypeScript)\n- [Тесты](#Тесты)\n- [Лицензия](#Лицензия)\n\n## Установка\n\n```bash\nnpm install @e22m4u/js-repository\n```\n\nОпционально устанавливаем адаптер.\n\n|           | описание                                                                                                                       |\n|-----------|--------------------------------------------------------------------------------------------------------------------------------|\n| `memory`  | виртуальная база в памяти процесса (не требует установки)                                                                      |\n| `mongodb` | MongoDB - система управления NoSQL базами (*[установка](https://www.npmjs.com/package/@e22m4u/js-repository-mongodb-adapter))* |\n\n## Импорт\n\nМодуль поддерживает ESM и CommonJS стандарты.\n\n*ESM*\n\n```js\nimport {Schema} from '@e22m4u/js-repository';\n```\n\n*CommonJS*\n\n```js\nconst {Schema} = require('@e22m4u/js-repository');\n```\n\n## Описание\n\nМодуль позволяет абстрагироваться от различных интерфейсов баз данных,\nпредставляя их как именованные *источники данных*, подключаемые к *моделям*.\n*Модель* же описывает таблицу базы, колонки которой являются свойствами\nмодели. Свойства модели могут иметь определенный *тип* допустимого значения,\nнабор *валидаторов* и *трансформеров*, через которые проходят данные перед\nзаписью в базу. Кроме того, *модель* может определять классические связи\n«один к одному», «один ко многим» и другие типы отношений между моделями.\n\nНепосредственно чтение и запись данных производится с помощью *репозитория*,\nкоторый имеет каждая модель с объявленным *источником данных*. Репозиторий\nможет фильтровать запрашиваемые документы, выполнять валидацию свойств\nсогласно определению модели, и встраивать связанные данные в результат\nвыборки.\n\n- *Источник данных* - определяет способ подключения к базе\n- *Модель* - описывает структуру документа и связи к другим моделям\n- *Репозиторий* - выполняет операции чтения и записи документов модели\n\n```mermaid\nflowchart TD\n\n  A[Схема]\n  subgraph Базы данных\n    B[Источник данных 1]\n    C[Источник данных 2]\n  end\n  A--\u003eB\n  A--\u003eC\n\n  subgraph Коллекции\n    D[Модель A]\n    E[Модель Б]\n    F[Модель В]\n    G[Модель Г]\n  end\n  B--\u003eD\n  B--\u003eE\n  C--\u003eF\n  C--\u003eG\n\n  H[Репозиторий A]\n  I[Репозиторий Б]\n  J[Репозиторий В]\n  K[Репозиторий Г]\n  D--\u003eH\n  E--\u003eI\n  F--\u003eJ\n  G--\u003eK\n```\n\n## Пример\n\nОбъявление источника данных, модели и добавление нового документа в коллекцию.\n\n```js\nimport {Schema} from '@e22m4u/js-repository';\nimport {DataType} from '@e22m4u/js-repository';\n\n// создание экземпляра Schema\nconst schema = new Schema();\n\n// объявление источника \"myMemory\"\nschema.defineDatasource({\n  name: 'myMemory', // название нового источника\n  adapter: 'memory', // выбранный адаптер\n});\n\n// объявление модели \"country\"\nschema.defineModel({\n  name: 'country', // название новой модели\n  datasource: 'myMemory', // выбранный источник\n  properties: { // свойства модели\n    name: DataType.STRING, // тип \"string\"\n    population: DataType.NUMBER, // тип \"number\"\n  },\n})\n\n// получение репозитория модели \"country\"\nconst countryRep = schema.getRepository('country');\n\n// добавление нового документа в коллекцию \"country\"\nconst country = await countryRep.create({\n  name: 'Russia',\n  population: 143400000,\n});\n\n// вывод нового документа\nconsole.log(country);\n// {\n//   \"id\": 1,\n//   \"name\": \"Russia\",\n//   \"population\": 143400000,\n// }\n```\n\n## Схема\n\nЭкземпляр класса `Schema` хранит определения источников данных и моделей.\n\n**Методы**\n\n- `defineDatasource(datasourceDef: object): this` - добавить источник\n- `defineModel(modelDef: object): this` - добавить модель\n- `getRepository(modelName: string): Repository` - получить репозиторий\n\n**Примеры**\n\nИмпорт класса и создание экземпляра схемы.\n\n```js\nimport {Schema} from '@e22m4u/js-repository';\n\nconst schema = new Schema();\n```\n\nОпределение нового источника.\n\n```js\nschema.defineDatasource({\n  name: 'myMemory', // название нового источника\n  adapter: 'memory', // выбранный адаптер\n});\n```\n\nОпределение новой модели.\n\n```js\nschema.defineModel({\n  name: 'product', // название новой модели\n  datasource: 'myMemory', // выбранный источник\n  properties: { // свойства модели\n    name: DataType.STRING,\n    weight: DataType.NUMBER,\n  },\n});\n```\n\nПолучение репозитория по названию модели.\n\n```js\nconst productRep = schema.getRepository('product');\n```\n\n## Источник данных\n\nИсточник хранит название выбранного адаптера и его настройки. Определение\nнового источника выполняется методом `defineDatasource` экземпляра схемы.\n\n**Параметры**\n\n- `name: string` уникальное название\n- `adapter: string` выбранный адаптер\n- параметры адаптера (если имеются)\n\n**Примеры**\n\nОпределение нового источника.\n\n```js\nschema.defineDatasource({\n  name: 'myMemory', // название нового источника\n  adapter: 'memory', // выбранный адаптер\n});\n```\n\nПередача дополнительных параметров адаптера.\n\n```js\nschema.defineDatasource({\n  name: 'myMongodb',\n  adapter: 'mongodb',\n  // параметры адаптера \"mongodb\"\n  host: '127.0.0.1',\n  port: 27017,\n  database: 'myDatabase',\n});\n```\n\n## Модель\n\nОписывает структуру документа коллекции и связи к другим моделям. Определение\nновой модели выполняется методом `defineModel` экземпляра схемы.\n\n**Параметры**\n\n- `name: string` название модели (обязательно)\n- `base: string` название наследуемой модели\n- `tableName: string` название коллекции в базе\n- `datasource: string` выбранный источник данных\n- `properties: object` определения свойств (см. [Свойства](#Свойства))\n- `relations: object` определения связей (см. [Связи](#Связи))\n\n**Примеры**\n\nОпределение модели со свойствами указанного типа.\n\n```js\nschema.defineModel({\n  name: 'user', // название новой модели\n  properties: { // свойства модели\n    name: DataType.STRING,\n    age: DataType.NUMBER,\n  },\n});\n```\n\n## Свойства\n\nПараметр `properties` находится в определении модели и принимает объект, ключи\nкоторого являются свойствами этой модели, а значением тип свойства или объект\nс дополнительными параметрами.\n\n**Тип данных**\n\n- `DataType.ANY` разрешено любое значение\n- `DataType.STRING` только значение типа `string`\n- `DataType.NUMBER` только значение типа `number`\n- `DataType.BOOLEAN` только значение типа `boolean`\n- `DataType.ARRAY` только значение типа `array`\n- `DataType.OBJECT` только значение типа `object`\n\n**Параметры**\n\n- `type: string` тип допустимого значения (обязательно)\n- `itemType: string` тип элемента массива (для `type: 'array'`)\n- `model: string` модель объекта (для `type: 'object'`)\n- `primaryKey: boolean` объявить свойство первичным ключом\n- `columnName: string` переопределение названия колонки\n- `columnType: string` тип колонки (определяется адаптером)\n- `required: boolean` объявить свойство обязательным\n- `default: any` значение по умолчанию\n- `validate: string | array | object` см. [Валидаторы](#Валидаторы)\n- `unique: boolean | string` проверять значение на уникальность\n\n**Параметр `unique`**\n\nЕсли значением параметра `unique` является `true` или `'strict'`, то выполняется\nстрогая проверка на уникальность. В этом режиме [пустые значения](#Пустые-значения)\nтак же подлежат проверке, где `null` и `undefined` не могут повторяться более одного\nраза.\n\nРежим `'sparse'` проверяет только значения с полезной нагрузкой, исключая\n[пустые значения](#Пустые-значения), список которых отличается в зависимости\nот типа свойства. Например, для типа `string` пустым значением будет `undefined`,\n`null` и `''` (пустая строка).\n\n- `unique: true | 'strict'` строгая проверка на уникальность\n- `unique: 'sparse'` исключить из проверки [пустые значения](#Пустые-значения)\n- `unique: false | 'nonUnique'` не проверять на уникальность (по умолчанию)\n\nВ качестве значений параметра `unique` можно использовать предопределенные\nконстанты как эквивалент строковых значений `strict`, `sparse` и `nonUnique`.\n\n- `PropertyUniqueness.STRICT`\n- `PropertyUniqueness.SPARSE`\n- `PropertyUniqueness.NON_UNIQUE`\n\n**Примеры**\n\nКраткое определение свойств модели.\n\n```js\nschema.defineModel({\n  name: 'city',\n  properties: { // свойства модели\n    name: DataType.STRING, // тип свойства \"string\"\n    population: DataType.NUMBER, // тип свойства \"number\"\n  },\n});\n```\n\nРасширенное определение свойств модели.\n\n```js\nschema.defineModel({\n  name: 'city',\n  properties: { // свойства модели\n    name: {\n      type: DataType.STRING, // тип свойства \"string\" (обязательно)\n      required: true, // исключение значений undefined и null\n    },\n    population: {\n      type: DataType.NUMBER, // тип свойства \"number\" (обязательно)\n      default: 0, // значение по умолчанию\n    },\n    code: {\n      type: DataType.NUMBER, // тип свойства \"number\" (обязательно)\n      unique: PropertyUniqueness.UNIQUE, // проверять уникальность\n    },\n  },\n});\n```\n\nФабричное значение по умолчанию. Возвращаемое значение функции будет\nопределено в момент записи документа.\n\n```js\nschema.defineModel({\n  name: 'article',\n  properties: { // свойства модели\n    tags: {\n      type: DataType.ARRAY, // тип свойства \"array\" (обязательно)\n      itemType: DataType.STRING, // тип элемента \"string\"\n      default: () =\u003e [], // фабричное значение\n    },\n    createdAt: {\n      type: DataType.STRING, // тип свойства \"string\" (обязательно)\n      default: () =\u003e new Date().toISOString(), // фабричное значение\n    },\n  },\n});\n```\n\n## Валидаторы\n\nКроме проверки типа, дополнительные условия можно задать с помощью\nвалидаторов, через которые будет проходить значение свойства перед\nзаписью в базу. Исключением являются [пустые значения](#Пустые-значения),\nкоторые не подлежат проверке.\n\n- `minLength: number` минимальная длинна строки или массива\n- `maxLength: number` максимальная длинна строки или массива\n- `regexp: string | RegExp` проверка по регулярному выражению\n\n**Пример**\n\nВалидаторы указываются в объявлении свойства модели параметром\n`validate`, который принимает объект с их названиями и настройками.\n\n```js\nschema.defineModel({\n  name: 'user',\n  properties: {\n    name: {\n      type: DataType.STRING,\n      validate: { // валидаторы свойства \"name\"\n        minLength: 2, // минимальная длинна строки\n        maxLength: 24, // максимальная длинна строки\n      },\n    },\n  },\n});\n```\n\n### Пользовательские валидаторы\n\nВалидатором является функция, в которую передается значение соответствующего\nполя перед записью в базу. Если во время проверки функция возвращает `false`,\nто выбрасывается стандартная ошибка. Подмена стандартной ошибки возможна\nс помощью выброса пользовательской ошибки непосредственно внутри функции.\n\nРегистрация пользовательского валидатора выполняется методом `addValidator`\nсервиса `PropertyValidatorRegistry`, который принимает новое название\nи функцию для проверки значения.\n\n**Пример**\n\n```js\n// создание валидатора для запрета\n// всех символов кроме чисел\nconst numericValidator = (input) =\u003e {\n  return /^[0-9]+$/.test(String(input));\n}\n\n// регистрация валидатора \"numeric\"\nschema\n  .get(PropertyValidatorRegistry)\n  .addValidator('numeric', numericValidator);\n\n// использование валидатора в определении\n// свойства \"code\" для новой модели\nschema.defineModel({\n  name: 'document',\n  properties: {\n    code: {\n      type: DataType.STRING,\n      validate: 'numeric',\n    },\n  },\n});\n```\n\n## Трансформеры\n\nС помощью трансформеров производится модификация значений определенных\nполей перед записью в базу. Трансформеры позволяют указать какие изменения\nнужно производить с входящими данными. Исключением являются\n[пустые значения](#Пустые-значения), которые не подлежат трансформации.\n\n- `trim` удаление пробельных символов с начала и конца строки\n- `toUpperCase` перевод строки в верхний регистр\n- `toLowerCase` перевод строки в нижний регистр\n- `toTitleCase` перевод строки в регистр заголовка\n\n**Пример**\n\nТрансформеры указываются в объявлении свойства модели параметром\n`transform`, который принимает название трансформера. Если требуется\nуказать несколько названий, то используется массив. Если трансформер\nимеет настройки, то используется объект, где ключом является название\nтрансформера, а значением его параметры.\n\n```js\nschema.defineModel({\n  name: 'user',\n  properties: {\n    name: {\n      type: DataType.STRING,\n      transform: [ // трансформеры свойства \"name\"\n        'trim', // удалить пробелы в начале и конце строки\n        'toTitleCase', // перевод строки в регистр заголовка\n      ],\n    },\n  },\n});\n```\n\n## Пустые значения\n\nРазные типы свойств имеют свои наборы пустых значений. Эти наборы\nиспользуются для определения наличия полезной нагрузки в значении\nсвойства. Например, параметр `default` в определении свойства\nустанавливает значение по умолчанию, только если входящее значение\nявляется пустым. Параметр `required` исключает пустые значения\nвыбрасывая ошибку. А параметр `unique` в режиме `sparse` наоборот\nдопускает дублирование пустых значений уникального свойства.\n\n| тип         | пустые значения           |\n|-------------|---------------------------|\n| `'any'`     | `undefined`, `null`       |\n| `'string'`  | `undefined`, `null`, `''` |\n| `'number'`  | `undefined`, `null`, `0`  |\n| `'boolean'` | `undefined`, `null`       |\n| `'array'`   | `undefined`, `null`, `[]` |\n| `'object'`  | `undefined`, `null`, `{}` |\n\n## Репозиторий\n\nВыполняет операции чтения и записи документов определенной модели.\nПолучить репозиторий можно методом `getRepository` экземпляра схемы.\n\n**Методы**\n\n- `create(data, filter = undefined)` добавить новый документ\n- `replaceById(id, data, filter = undefined)` заменить весь документ\n- `replaceOrCreate(data, filter = undefined)` заменить или создать новый\n- `patchById(id, data, filter = undefined)` частично обновить документ\n- `patch(data, where = undefined)` обновить все документы или по условию\n- `find(filter = undefined)` найти все документы или по условию\n- `findOne(filter = undefined)` найти первый документ или по условию\n- `findById(id, filter = undefined)` найти документ по идентификатору\n- `delete(where = undefined)` удалить все документы или по условию\n- `deleteById(id)` удалить документ по идентификатору\n- `exists(id)` проверить существование по идентификатору\n- `count(where = undefined)` подсчет всех документов или по условию\n\n**Аргументы**\n\n- `id: number|string` идентификатор (первичный ключ)\n- `data: object` объект отражающий состав документа\n- `where: object` параметры выборки (см. [Фильтрация](#Фильтрация))\n- `filter: object` параметры возвращаемого результата (см. [Фильтрация](#Фильтрация))\n\n**Примеры**\n\nПолучение репозитория по названию модели.\n\n```js\nconst countryRep = schema.getRepository('country');\n```\n\nДобавление нового документа в коллекцию.\n\n```js\nconst res = await countryRep.create({\n  name: 'Russia',\n  population: 143400000,\n});\n\nconsole.log(res);\n// {\n//   \"id\": 1,\n//   \"name\": \"Russia\",\n//   \"population\": 143400000,\n// }\n```\n\nПоиск документа по идентификатору.\n\n```js\nconst res = await countryRep.findById(1);\n\nconsole.log(res);\n// {\n//   \"id\": 1,\n//   \"name\": \"Russia\",\n//   \"population\": 143400000,\n// }\n```\n\nУдаление документа по идентификатору.\n\n```js\nconst res = await countryRep.deleteById(1);\n\nconsole.log(res); // true\n```\n\n## Фильтрация\n\nНекоторые методы репозитория принимают объект настроек влияющий\nна возвращаемый результат. Максимально широкий набор таких настроек\nимеет первый параметр метода `find`, где ожидается объект содержащий\nнабор опций указанных ниже.\n\n- `where: object` объект выборки\n- `order: string[]` указание порядка\n- `limit: number` ограничение количества документов\n- `skip: number` пропуск документов\n- `fields: string[]` выбор необходимых свойств модели\n- `include: object` включение связанных данных в результат\n\n### where\n\nПараметр принимает объект с условиями выборки и поддерживает широкий\nнабор операторов сравнения.\n\n`{foo: 'bar'}` поиск по значению свойства `foo`  \n`{foo: {eq: 'bar'}}` оператор равенства `eq`  \n`{foo: {neq: 'bar'}}` оператор неравенства `neq`  \n`{foo: {gt: 5}}` оператор \"больше\" `gt`  \n`{foo: {lt: 10}}` оператор \"меньше\" `lt`  \n`{foo: {gte: 5}}` оператор \"больше или равно\" `gte`  \n`{foo: {lte: 10}}` оператор \"меньше или равно\" `lte`  \n`{foo: {inq: ['bar', 'baz']}}` равенство одного из значений `inq`  \n`{foo: {nin: ['bar', 'baz']}}` исключение значений массива `nin`  \n`{foo: {between: [5, 10]}}` оператор диапазона `between`  \n`{foo: {exists: true}}` оператор наличия значения `exists`  \n`{foo: {like: 'bar'}}` оператор поиска подстроки `like`  \n`{foo: {ilike: 'BaR'}}` регистронезависимая версия `ilike`  \n`{foo: {nlike: 'bar'}}` оператор исключения подстроки `nlike`  \n`{foo: {nilike: 'BaR'}}` регистронезависимая версия `nilike`  \n`{foo: {regexp: 'ba.+'}}` оператор регулярного выражения `regexp`  \n`{foo: {regexp: 'ba.+', flags: 'i'}}` флаги регулярного выражения\n\n*i. Условия можно объединять операторами `and`, `or` и `nor`.*\n\n**Примеры**\n\nПрименение условий выборки при подсчете документов.\n\n```js\nconst res = await rep.count({\n  authorId: 251,\n  publishedAt: {\n    lte: '2023-12-02T14:00:00.000Z',\n  },\n});\n```\n\nПрименение оператора `or` при удалении документов.\n\n```js\nconst res = await rep.delete({\n  or: [\n    {draft: true},\n    {title: {like: 'draft'}},\n  ],\n});\n```\n\n### order\n\nПараметр упорядочивает выборку по указанным свойствам модели. Обратное\nнаправление порядка можно задать постфиксом `DESC` в названии свойства.\n\n**Примеры**\n\nУпорядочить по полю `createdAt`\n\n```js\nconst res = await rep.find({\n  order: 'createdAt',\n});\n```\n\nУпорядочить по полю `createdAt` в обратном порядке.\n\n```js\nconst res = await rep.find({\n  order: 'createdAt DESC',\n});\n```\n\nУпорядочить по нескольким свойствам в разных направлениях.\n\n```js\nconst res = await rep.find({\n  order: [\n    'title',\n    'price ASC',\n    'featured DESC',\n  ],\n});\n```\n\n*i. Направление порядка `ASC` указывать необязательно.*\n\n### include\n\nПараметр включает связанные документы в результат вызываемого метода.\nНазвания включаемых связей должны быть определены в текущей модели.\n(см. [Связи](#Связи))\n\n**Примеры**\n\nВключение связи по названию.\n\n```js\nconst res = await rep.find({\n  include: 'city',\n});\n```\n\nВключение вложенных связей.\n\n```js\nconst res = await rep.find({\n  include: {\n    city: 'country',\n  },\n});\n```\n\nВключение нескольких связей массивом.\n\n```js\nconst res = await rep.find({\n  include: [\n    'city',\n    'address',\n    'employees'\n  ],\n});\n```\n\nИспользование фильтрации включаемых документов.\n\n```js\nconst res = await rep.find({\n  include: {\n    relation: 'employees', // название связи\n    scope: { // фильтрация документов \"employees\"\n      where: {hidden: false}, // условия выборки\n      order: 'id', // порядок документов\n      limit: 10, // ограничение количества\n      skip: 5, // пропуск документов\n      fields: ['name', 'surname'], // только указанные поля\n      include: 'city', // включение связей для \"employees\"\n    },\n  },\n});\n```\n\n## Связи\n\nПараметр `relations` находится в определении модели и принимает\nобъект, ключ которого является названием связи, а значением объект\nс параметрами.\n\n**Параметры**\n\n- `type: string` тип связи\n- `model: string` название целевой модели\n- `foreignKey: string` свойство текущей модели для идентификатора цели\n- `polymorphic: boolean|string` объявить связь полиморфной*\n- `discriminator: string` свойство текущей модели для названия целевой*\n\n*i. Полиморфный режим позволяет динамически определять целевую модель\nпо ее названию, которое хранит документ в свойстве-дискриминаторе.*\n\n**Тип связи**\n\n- `belongsTo` - текущая модель содержит свойство для идентификатора цели\n- `hasOne` - обратная сторона `belongsTo` по принципу \"один к одному\"\n- `hasMany` - обратная сторона `belongsTo` по принципу \"один ко многим\"\n- `referencesMany` - документ содержит массив с идентификаторами целевой модели\n\n**Примеры**\n\nОбъявление связи `belongsTo`\n\n```js\nschema.defineModel({\n  name: 'user',\n  relations: {\n    role: { // название связи\n      type: RelationType.BELONGS_TO, // текущая модель ссылается на целевую\n      model: 'role', // название целевой модели\n      foreignKey: 'roleId', // внешний ключ (необязательно)\n      // если \"foreignKey\" не указан, то свойство внешнего\n      // ключа формируется согласно названию связи\n      // с добавлением постфикса \"Id\"\n    },\n  },\n});\n```\n\nОбъявление связи `hasMany`\n\n```js\nschema.defineModel({\n  name: 'role',\n  relations: {\n    users: { // название связи\n      type: RelationType.HAS_MANY, // целевая модель ссылается на текущую\n      model: 'user', // название целевой модели\n      foreignKey: 'roleId', // внешний ключ из целевой модели на текущую\n    },\n  },\n});\n```\n\nОбъявление связи `referencesMany`\n\n```js\nschema.defineModel({\n  name: 'article',\n  relations: {\n    categories: { // название связи\n      type: RelationType.REFERENCES_MANY, // связь через массив идентификаторов\n      model: 'category', // название целевой модели\n      foreignKey: 'categoryIds', // внешний ключ (необязательно)\n      // если \"foreignKey\" не указан, то свойство внешнего\n      // ключа формируется согласно названию связи\n      // с добавлением постфикса \"Ids\"\n    },\n  },\n});\n```\n\nПолиморфная версия `belongsTo`\n\n```js\nschema.defineModel({\n  name: 'file',\n  relations: {\n    reference: { // название связи\n      type: RelationType.BELONGS_TO, // текущая модель ссылается на целевую\n      // полиморфный режим позволяет хранить название целевой модели\n      // в свойстве-дискриминаторе, которое формируется согласно\n      // названию связи с постфиксом \"Type\", и в данном случае\n      // название целевой модели хранит \"referenceType\",\n      // а идентификатор документа \"referenceId\"\n      polymorphic: true,\n    },\n  },\n});\n```\n\nПолиморфная версия `belongsTo` с указанием свойств.\n\n```js\nschema.defineModel({\n  name: 'file',\n  relations: {\n    reference: { // название связи\n      type: RelationType.BELONGS_TO, // текущая модель ссылается на целевую\n      polymorphic: true, // название целевой модели хранит дискриминатор\n      foreignKey: 'referenceId', // свойство для идентификатора цели\n      discriminator: 'referenceType', // свойство для названия целевой модели\n    },\n  },\n})\n```\n\nПолиморфная версия `hasMany` с указанием названия связи целевой модели.\n\n```js\nschema.defineModel({\n  name: 'letter',\n  relations: {\n    attachments: { // название связи\n      type: RelationType.HAS_MANY, // целевая модель ссылается на текущую\n      model: 'file', // название целевой модели\n      polymorphic: 'reference', // название полиморфной связи целевой модели\n    },\n  },\n})\n```\n\nПолиморфная версия `hasMany` с указанием свойств целевой модели.\n\n```js\nschema.defineModel({\n  name: 'letter',\n  relations: {\n    attachments: { // название связи\n      type: RelationType.HAS_MANY, // целевая модель ссылается на текущую\n      model: 'file', // название целевой модели\n      polymorphic: true, // название текущей модели находится в дискриминаторе\n      foreignKey: 'referenceId', // свойство целевой модели для идентификатора\n      discriminator: 'referenceType', // свойство целевой модели для названия текущей\n    },\n  },\n})\n```\n\n## Расширение\n\nМетод `getRepository` экземпляра схемы проверяет наличие существующего\nрепозитория для указанной модели и возвращает его. В противном случае\nсоздается новый экземпляр, который будет сохранен для последующих\nобращений к методу.\n\n```js\nimport {Schema} from '@e22m4u/js-repository';\nimport {Repository} from '@e22m4u/js-repository';\n\n// const schema = new Schema();\n// schema.defineDatasource ...\n// schema.defineModel ...\n\nconst rep1 = schema.getRepository('model');\nconst rep2 = schema.getRepository('model');\nconsole.log(rep1 === rep2); // true\n```\n\nПодмена стандартного конструктора репозитория выполняется методом\n`setRepositoryCtor` сервиса `RepositoryRegistry`, который находится\nв контейнере экземпляра схемы. После чего все новые репозитории будут\nсоздаваться указанным конструктором вместо стандартного.\n\n```js\nimport {Schema} from '@e22m4u/js-repository';\nimport {Repository} from '@e22m4u/js-repository';\nimport {RepositoryRegistry} from '@e22m4u/js-repository';\n\nclass MyRepository extends Repository {\n  /*...*/\n}\n\n// const schema = new Schema();\n// schema.defineDatasource ...\n// schema.defineModel ...\n\nschema.get(RepositoryRegistry).setRepositoryCtor(MyRepository);\nconst rep = schema.getRepository('model');\nconsole.log(rep instanceof MyRepository); // true\n```\n\n*i. Так как экземпляры репозитория кэшируется, то замену конструктора\nследует выполнять до обращения к методу `getRepository`.*\n\n## TypeScript\n\nПолучение типизированного репозитория с указанием интерфейса модели.\n\n```ts\nimport {Schema} from '@e22m4u/js-repository';\nimport {DataType} from '@e22m4u/js-repository';\nimport {RelationType} from '@e22m4u/js-repository';\n\n// const schema = new Schema();\n// schema.defineDatasource ...\n// schema.defineModel ...\n\n// определение модели \"city\"\nschema.defineModel({\n  name: 'city',\n  datasource: 'myDatasource',\n  properties: {\n    title: DataType.STRING,\n    timeZone: DataType.STRING,\n  },\n  relations: {\n    country: {\n      type: RelationType.BELONGS_TO,\n      model: 'country',\n    },\n  },\n});\n\n// определение интерфейса \"city\"\ninterface City {\n  id: number;\n  title?: string;\n  timeZone?: string;\n  countryId?: number;\n  country?: Country;\n  // открыть свойства (опционально)\n  [property: string]: unknown;\n}\n\n// получаем репозиторий по названию модели\n// указывая ее тип и тип идентификатора\nconst cityRep = schema.getRepository\u003cCity, number\u003e('city');\n```\n\n## Тесты\n\n```bash\nnpm run test\n```\n\n## Лицензия\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fe22m4u%2Fjs-repository","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fe22m4u%2Fjs-repository","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fe22m4u%2Fjs-repository/lists"}