{"id":27304211,"url":"https://github.com/e22m4u/ts-projection","last_synced_at":"2025-04-12T03:23:08.091Z","repository":{"id":287244135,"uuid":"964095942","full_name":"e22m4u/ts-projection","owner":"e22m4u","description":"Модуль для работы с проекцией данных для TypeScript","archived":false,"fork":false,"pushed_at":"2025-04-10T17:32:30.000Z","size":24,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-10T18:53:04.144Z","etag":null,"topics":["data","projection","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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.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,"zenodo":null}},"created_at":"2025-04-10T17:22:27.000Z","updated_at":"2025-04-10T17:32:15.000Z","dependencies_parsed_at":"2025-04-10T18:53:07.223Z","dependency_job_id":"15276dee-df89-4d76-871e-7392f5690248","html_url":"https://github.com/e22m4u/ts-projection","commit_stats":null,"previous_names":["e22m4u/ts-projection"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/e22m4u%2Fts-projection","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/e22m4u%2Fts-projection/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/e22m4u%2Fts-projection/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/e22m4u%2Fts-projection/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/e22m4u","download_url":"https://codeload.github.com/e22m4u/ts-projection/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248510769,"owners_count":21116267,"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":["data","projection","typescript"],"created_at":"2025-04-12T03:23:07.436Z","updated_at":"2025-04-12T03:23:08.052Z","avatar_url":"https://github.com/e22m4u.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @e22m4u/ts-projection\n\nМодуль для управления проекцией данных в TypeScript, позволяющий легко\nскрывать или показывать поля объектов на основе предопределенных правил.\nИдеально подходит для формирования данных API ответов, скрывая внутренние\nили чувствительные поля. Использует декораторы TypeScript для определения\nправил видимости.\n\n      \n# Содержание\n\n* [Установка](#установка)\n  * [Поддержка декораторов](#поддержка-декораторов)\n* [Базовое использование](#базовое-использование)\n* [Область проекции INPUT](#область-проекции-input)\n* [Белый список](#белый-список)\n* [Вложенные модели](#вложенные-модели)\n* [API Справка](#api-справка)\n  * [Перечисления](#перечисления)\n  * [Декораторы](#декораторы)\n  * [Функции](#функции)\n* [Тесты](#тесты)\n* [Лицензия](#лицензия)\n\n## Установка\n\n```bash\nnpm install @e22m4u/ts-projection\n```\n\n#### Поддержка декораторов\n\nДля включения поддержки декораторов, добавьте указанные\nниже опции в файл `tsconfig.json` вашего проекта.\n\n```json\n{\n  \"emitDecoratorMetadata\": true,\n  \"experimentalDecorators\": true\n}\n```\n\n## Базовое использование\n\nСкрытие пароля пользователя перед отправкой данных клиенту.\n\n**1. Определение модели пользователя с декоратором:**\n\nИспользуйте декоратор `@hiddenProperty()` на свойстве `password`.\n\n```typescript\nimport {hiddenProperty} from '@e22m4u/ts-projection';\nimport {applyProjection} from '@e22m4u/ts-projection';\n\nclass User {\n  name: string;\n  surname: string;\n\n  // свойство содержащее пароль\n  // будет скрыто на проекции\n  @hiddenProperty()\n  password: string;\n\n  constructor(name: string, surname: string, password: string) {\n    this.name = name;\n    this.surname = surname;\n    this.password = password;\n  }\n}\n```\n\n**2. Применение проекции:**\n\nВызов функции `applyProjection` с двумя аргументами создаст новый объект\nисключающий свойства помеченные декоратором `@hiddenProperty()`.\n\n```typescript\nconst user = new User('Alice', 'Smith', 'myPass');\n\n// применение проекции (скроет password)\nconst userForApi = applyProjection(User, user);\nconsole.log(userForApi);\n// {\n//   name: 'Alice'\n//   surname: 'Smith',\n// }\n\n// исходный объект пользователя не изменился\nconsole.log(user);\n// {\n//   name: 'Alice',\n//   surname: 'Smith',\n//   password: 'myPass'\n// }\n```\n\nПоле `password`, помеченное `@hiddenProperty()`, было удалено\nиз результирующего объекта, что делает отправку данных клиенту\nболее безопасной.\n\n## Область проекции `INPUT`\n\nВ базовом примере мы неявно работали в области проекции `OUTPUT` (вывод данных),\nкоторая используется по умолчанию функцией `applyProjection` при вызове с двумя\nаргументами. Эта область хорошо подходит для фильтрации данных *перед отправкой*\nпользователю.\n\nОднако часто возникает необходимость отфильтровать данные, *получаемые*\nот пользователя, например, чтобы запретить ему изменять определенные поля\n(статус блокировки, роль и т.д.). Для этого существует область\nпроекции `INPUT`.\n\n**Пример с `INPUT` областью:**\n\nДополним наш класс `User`, добавив поле `isBlocked`, которое пользователь\nне должен устанавливать через API. Мы также оставим скрытие пароля для вывода.\n\n**1. Модификация модели `User`:**\n\nИспользуем `@lockedProperty()` для `isBlocked`.\n\n```typescript\nimport {hiddenProperty} from '@e22m4u/ts-projection';\nimport {lockedProperty} from '@e22m4u/ts-projection';\nimport {applyProjection} from '@e22m4u/ts-projection';\nimport {ProjectionScope} from '@e22m4u/ts-projection';\n\nclass User {\n  name: string;\n  surname: string;\n\n  @hiddenProperty() // запрет для ВЫВОДА (нельзя получить через API)\n  password: string;\n\n  @lockedProperty() // запрет для ВВОДА (нельзя установить через API)\n  isBlocked: boolean;\n\n  constructor(\n    name: string,\n    surname: string,\n    password: string,\n    isBlocked: boolean = false,\n  ) {\n    this.name = name;\n    this.surname = surname;\n    this.password = password;\n    this.isBlocked = isBlocked;\n  }\n}\n```\n\n**2. Фильтрация входных данных:**\n\nПредставим, что мы получили данные от пользователя, и он попытался\nустановить `isBlocked`. Мы используем `applyProjection` с тремя аргументами,\nявно указав `ProjectionScope.INPUT`.\n\n```typescript\n// гипотетические данные, полученные из API запроса\nconst incomingData = {\n  name: 'Bob',\n  surname: 'Smith',\n  password: 'myPass',\n  isBlocked: true, // пользователь пытается заблокировать себя или другого\n};\n\n// применение проекции для INPUT области\nconst safeDataToProcess = applyProjection(\n  ProjectionScope.INPUT,\n  User,\n  incomingData,\n);\n\nconsole.log(safeDataToProcess);\n// {\n//   name: 'Bob',\n//   surname: 'Smith',\n//   password: 'myPass'\n// }\n// поле isBlocked было удалено благодаря @lockedProperty()\n// password остался, т.к. @hiddenProperty() скрывает только для OUTPUT\n\n// Обратите внимание: password не был удален, т.к. @hiddenProperty()\n// действует только для OUTPUT scope. Если вы не хотите принимать и его,\n// добавьте к password также декоратор @lockedProperty().\n```\n\nТаким образом, области проекции позволяют гранулярно управлять тем,\nкакие поля доступны для чтения (`OUTPUT`) и для записи (`INPUT`).\n\n## Белый список\n\nИногда удобнее не скрывать отдельные поля, а наоборот, скрыть *все* поля\nпо умолчанию и явно указать только те, которые должны быть видны при выводе\nданных. Это можно сделать с помощью декоратора класса `@hiddenProperties()`\nи декоратора свойства `@visibleProperty()`.\n\n**1. Модификация модели `User`:**\n\nПрименяем `@hiddenProperties()` к классу и `@visibleProperty()`\nк полям `name` и `surname`, которые мы хотим видеть в ответе.\n\n```typescript\nimport {\n  hiddenProperties, // скрыть всё по умолчанию для OUTPUT\n  visibleProperty,  // явно показать это свойство для OUTPUT\n  lockedProperty,   // правила для INPUT остаются\n  applyProjection,\n  ProjectionScope\n} from '@e22m4u/ts-projection';\n\n@hiddenProperties() // все поля скрыты для OUTPUT по умолчанию\nclass User {\n  @visibleProperty() // показываем name в OUTPUT\n  name: string;\n  \n  @visibleProperty() // показываем surname в OUTPUT\n  surname: string;\n\n  // password не будет виден в OUTPUT (т.к. нет @visibleProperty),\n  // и он также скрыт для INPUT\n  @lockedProperty()\n  password: string;\n\n  // isBlocked не будет виден в OUTPUT (т.к. нет @visibleProperty),\n  // и он также скрыт для INPUT\n  @lockedProperty()\n  isBlocked: boolean;\n\n  constructor(\n    name: string,\n    surname: string,\n    password: string,\n    isBlocked: boolean = false,\n  ) {\n    this.name = name;\n    this.surname = surname;\n    this.password = password;\n    this.isBlocked = isBlocked;\n  }\n}\n```\n\nПравила, установленные для конкретного свойства, имеют приоритет над общими\nправилами, установленными для класса, в рамках одной и той же области\nпроекции (`INPUT` или `OUTPUT`).\n\n**2. Применение проекции (для вывода):**\n\nИспользуем вызов с двумя аргументами, так как `@hiddenProperties`\nи `@visibleProperty` работают в области `OUTPUT`, которая используется\nпо умолчанию.\n\n```typescript\nconst user = new User('Alice', 'Smith', 'myPass', false);\n\n// применение проекции (OUTPUT по умолчанию)\nconst userForApi = applyProjection(User, user);\n\nconsole.log(userForApi);\n// {\n//   name: 'Alice',\n//   surname: 'Smith'\n// }\n// поля password и isBlocked отсутствуют, как и ожидалось\n\n// проверка INPUT области для сравнения\nconst userInputData = {\n  name: 'Bob',\n  surname: 'Jones',\n  password: 'myPass',\n  isBlocked: true,\n};\n\nconst safeUserInput = applyProjection(\n  ProjectionScope.INPUT,\n  User,\n  userInputData,\n);\nconsole.log(safeUserInput);\n// {\n//   name: 'Bob',\n//   surname: 'Jones'\n// }\n// password и isBlocked скрыты для INPUT декораторами @lockedProperty\n```\n\nЭтот подход \"белого списка\" может быть очень удобен для\nDTO (Data Transfer Objects), где вы хотите явно контролировать,\nкакие именно поля будут доступны во внешнем API, минимизируя\nриск случайной утечки данных.\n\n## Вложенные модели\n\nЧасто свойства модели содержат вложенные объекты (например, пользователь имеет\nпрофиль или адрес). Данный модуль позволяет применять правила проекции\nрекурсивно к таким вложенным объектам с помощью декоратора `@isEmbedded`.\n\n**1. Определение модели:**\n\nСоздадим модель `Profile` с собственными правилами проекции и модель `User`,\nкоторая содержит `Profile`.\n\n```typescript\nimport {\n  isEmbedded,      // определение вложенной модели\n  hiddenProperty,  // исключение свойства для вывода\n  lockedProperty,  // исключение свойства для ввода\n  applyProjection,\n  ProjectionScope\n} from '@e22m4u/ts-projection';\n\n// модель профиля\nclass Profile {\n  city: string;\n  address: string;\n\n  @lockedProperty() // запрет для INPUT\n  reputation: number;\n\n  @hiddenProperty() // запрет для OUTPUT\n  inviter: string;\n\n  constructor(\n    city: string,\n    address: string,\n    inviter: string,\n    reputation: number,\n  ) {\n    this.city = city;\n    this.address = address;\n    this.reputation = reputation;\n    this.inviter = inviter;\n  }\n}\n\n// модель пользователя\nclass User {\n  name: string;\n  surname: string;\n  \n  @lockedProperty() // запрет для INPUT\n  status: string;\n\n  @hiddenProperty() // запрет для OUTPUT\n  password: string;\n\n  @isEmbedded(() =\u003e Profile) // определение вложенной модели\n  profile: Profile;\n\n  constructor(\n    name: string,\n    surname: string,\n    status: string,\n    password: string,\n    profile: Profile,\n  ) {\n    this.name = name;\n    this.surname = surname;\n    this.status = status;\n    this.password = password;\n    this.profile = profile;\n  }\n}\n```\n\n**Важно:** В `@isEmbedded(() =\u003e Profile)` используется функция-фабрика\n`() =\u003e Profile`. Это необходимо для корректной работы, если между `User`\nи `Profile` могут возникать циклические зависимости при импорте модулей\nв TypeScript.\n\n**2. Применение проекции:**\n\nСоздадим экземпляры и применим `applyProjection` для разных областей проекции.\n\n```typescript\nconst userProfile = new Profile('Москва', 'ул. Тверская, 1', 'Admin', 100);\nconst user = new User('Tommy', 'Smith', 'active', 'myPass', userProfile);\n\n// фильтрация отдаваемых данных (проекция для OUTPUT)\nconst userOutput = applyProjection(User, user);\nconsole.log(userOutput);\n// {\n//   name: 'Tommy',\n//   surname: 'Smith',\n//   status: 'active',             // виден в OUTPUT\n//   profile: {\n//     city: 'Москва',\n//     address: 'ул. Тверская, 1',\n//     reputation: 100             // виден в OUTPUT\n//   }\n// }\n//\n// password скрыт декоратором @hiddenProperty в User\n// profile.inviter скрыт декоратором @hiddenProperty в Profile\n\n// принимаемые данные\nconst incomingUserData = {\n  name: 'John',\n  surname: 'Doe',\n  status: 'vip',                // попытка установить статус\n  password: 'newPassword',      // установка нового пароля\n  profile: {\n    city: 'Санкт-Петербург',\n    address: 'Невский пр., 10',\n    reputation: 999,            // попытка установить репутацию\n    inviter: 'Hacker',          // установка инвайтера\n  }\n};\n\n// фильтрация принимаемых данных (проекция для INPUT)\nconst safeUserInput = applyProjection(\n  ProjectionScope.INPUT,\n  User,\n  incomingUserData,\n);\nconsole.log(safeUserInput);\n// {\n//   name: 'John',\n//   surname: 'Doe',\n//   password: 'newPassword',      // виден в INPUT\n//   profile: {\n//     city: 'Санкт-Петербург',\n//     address: 'Невский пр., 10',\n//     inviter: 'Hacker'           // виден в INPUT\n//   }\n// }\n//\n// status скрыт декоратором @lockedProperty в User\n// profile.reputation скрыт декоратором @lockedProperty в Profile\n```\n\nКак видно из примеров, `applyProjection` автоматически \"погружается\" в объекты,\nпомеченные `@isEmbedded`, и применяет к ним правила проекции, определенные\nв их собственном классе (`Profile`), учитывая текущую область проекции\n(`scope`). Это позволяет легко управлять сложными структурами данных.\n\n## API Справка\n\n### Перечисления\n\n* `ProjectionScope`\n  * `INPUT`\n  * `OUTPUT`\n* `ProjectionRule`\n  * `HIDE`\n  * `SHOW`\n\n### Декораторы\n\n* **Класса:**\n  * `@lockedProperties()`: применяет `HIDE` для `INPUT`;\n  * `@hiddenProperties()`: применяет `HIDE` для `OUTPUT`;\n* **Свойства (для INPUT):**\n  * `@lockedProperty()`: применяет `HIDE` для `INPUT`;\n  * `@writableProperty()`: применяет `SHOW` для `INPUT`;\n* **Свойства (для OUTPUT):**\n  * `@hiddenProperty()`: применяет `HIDE` для `OUTPUT`;\n  * `@visibleProperty()`: применяет `SHOW` для `OUTPUT`;\n* **Свойства (структурный):**\n  * `@isEmbedded(modelFactory: () =\u003e Constructor)`:  \n  *\\- помечает вложенную модель для рекурсивной обработки;*\n\n### Функции\n\n* `applyProjection\u003cT\u003e(model: Constructor, data: T): T`  \n  *\\- Применяет проекцию `OUTPUT` (по умолчанию);*\n* `applyProjection\u003cT\u003e(scope: ProjectionScope, model: Constructor, data: T): T`  \n  *\\- Применяет проекцию для указанного `scope`;*\n\nФункция `applyProjection` всегда возвращает новый объект или массив,\nне модифицируя исходные данные. Обрабатывает примитивы, `null`, `undefined`,\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%2Fts-projection","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fe22m4u%2Fts-projection","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fe22m4u%2Fts-projection/lists"}