e22m4u 22 часов назад
Родитель
Сommit
19cb2f1288

+ 45 - 79
README.md

@@ -1,9 +1,9 @@
-## @e22m4u/js-data-projector
+## @e22m4u/js-data-projection
 
 JavaScript модуль для работы с проекцией данных.
 
 Модуль использует декларативные схемы для определения правил видимости полей
-данных. Поддерживается вложенность, именованные схемы, области проекции
+данных. Поддерживается вложенность, функции-фабрики, области проекции
 и строгий режим.
 
 ## Содержание
@@ -16,24 +16,19 @@ JavaScript модуль для работы с проекцией данных.
     - [Строгий режим](#строгий-режим)
     - [Вложенные схемы](#вложенные-схемы)
     - [Область проекции](#область-проекции)
-  - [Класс `DataProjector`](#класс-dataprojector)
-    - [Именованные схемы](#именованные-схемы)
-    - [Комбинирование именованных схем](#комбинирование-именованных-схем)
+    - [Использование фабрики](#использование-фабрики)
 - [Тесты](#тесты)
 - [Лицензия](#лицензия)
 
 ## Установка
 
 ```bash
-npm install @e22m4u/js-data-projector
+npm install @e22m4u/js-data-projection
 ```
 
 ## Использование
 
-Модуль экспортирует функцию `projectData` и класс `DataProjector`.
-Оба инструмента реализуют одинаковый функционал создания проекций,
-за исключением возможности регистрации именованных схем, которая
-доступна только экземпляру класса.
+Модуль экспортирует функцию создания проекции `projectData`.
 
 ### Функция `projectData`
 
@@ -43,13 +38,12 @@ npm install @e22m4u/js-data-projector
 
 Сигнатура:
 
-- `projectData(schemaOrName, data, [options])` - возвращает проекцию;
-  - `schemaOrName: string | object` - схема проекции или имя;
+- `projectData(schemaOrFactory, data, [options])` - возвращает проекцию;
+  - `schemaOrFactory: object | Function` - схема проекции или фабрика;
   - `data: object | object[]` - проектируемые данные;
   - `options?: object` - объект настроек;
     - `strict?: boolean` - строгий режим;
     - `scope?: string` - область проекции;
-    - `resolver?: Function` - функция для разрешения имени;
 
 #### Создание проекции
 
@@ -58,7 +52,7 @@ npm install @e22m4u/js-data-projector
 в результате по умолчанию.
 
 ```js
-import {projectData} from '@e22m4u/js-data-projector';
+import {projectData} from '@e22m4u/js-data-projection';
 
 const schema = {
   name: true,
@@ -86,7 +80,7 @@ console.log(result);
 массиву.
 
 ```js
-import {projectData} from '@e22m4u/js-data-projector';
+import {projectData} from '@e22m4u/js-data-projection';
 
 const schema = {
   id: true,
@@ -113,7 +107,7 @@ console.log(result);
 отсутствие лишних данных в результате.
 
 ```js
-import {projectData} from '@e22m4u/js-data-projector';
+import {projectData} from '@e22m4u/js-data-projection';
 
 const schema = {
   name: true,
@@ -142,7 +136,7 @@ console.log(result);
 структур данных.
 
 ```js
-import {projectData} from '@e22m4u/js-data-projector';
+import {projectData} from '@e22m4u/js-data-projection';
 
 const schema = {
   id: false,
@@ -182,7 +176,7 @@ console.log(result);
 опцию `scope`.
 
 ```js
-import {projectData} from '@e22m4u/js-data-projector';
+import {projectData} from '@e22m4u/js-data-projection';
 
 const schema = {
   name: true,
@@ -217,96 +211,68 @@ console.log(outputData);
 // }
 ```
 
-### Класс `DataProjector`
+#### Использование фабрики
 
-Класс управляет реестром схем и предоставляет методы для их регистрации и
-использования. Экземпляр хранит именованные схемы в памяти, что позволяет
-ссылаться на них по строковому идентификатору при создании проекций.
-
-Метод `defineSchema`:
-
-- `defineSchema(name, schema)` - возвращает `this`;
-  - `name: string` - имя схемы;
-  - `schema: object` - схема проекции;
-
-Метод `project`:
-
-- `project(schemaOrName, data, [options])` - возвращает проекцию;
-  - `schemaOrName: string | object` - схема проекции или имя;
-  - `data: object | object[]` - проектируемые данные;
-  - `options?: object` - объект настроек;
-    - `strict?: boolean` - строгий режим;
-    - `scope?: string` - область проекции;
-
-#### Именованные схемы
-
-Класс позволяет регистрировать схемы под уникальными именами. Метод
-`defineSchema` сохраняет схему в реестре, а метод `project` использует
-имя для применения правил.
+Вместо статической схемы можно передать фабричную функцию, которая вернет
+объект схемы. Это полезно, если схему необходимо генерировать динамически
+или переиспользовать логику создания схем.
 
 ```js
-import {DataProjector} from '@e22m4u/js-data-projector';
-
-const projector = new DataProjector();
-
-// регистрация именованной схемы
-projector.defineSchema('user', {
-  id: true,
-  name: true,
-  password: false,
-});
+import {projectData} from '@e22m4u/js-data-projection';
+
+// фабрика возвращает объект схемы
+const getSchema = () => {
+  return {
+    id: true,
+    hiddenField: false,
+  };
+};
 
 const data = {
-  id: 10,
-  name: 'Fedor',
-  password: 'pass123',
+  id: 1,
+  hiddenField: 'secret',
 };
 
-const result = projector.project('user', data);
+// передача функции вместо объекта
+const result = projectData(getSchema, data);
 console.log(result);
 // {
-//   id: 10,
-//   name: 'Fedor'
+//   id: 1
 // }
 ```
 
-#### Комбинирование именованных схем
-
-Именованные схемы могут быть использованы внутри других схем. Что позволяет
-комбинировать зарегистрированные схемы для комплексных структур данных.
+Фабрики также поддерживаются во вложенных структурах. Свойство `schema` может
+принимать функцию, возвращающую схему для вложенного объекта.
 
 ```js
-import {DataProjector} from '@e22m4u/js-data-projector';
-
-const projector = new DataProjector();
+import {projectData} from '@e22m4u/js-data-projection';
 
-// регистрация схемы адреса
-projector.defineSchema('address', {
+// фабрика для вложенных данных
+const getAddressSchema = () => ({
   city: true,
   zip: false,
 });
 
-// регистрация схемы пользователя
-projector.defineSchema('user', {
+const userSchema = {
   name: true,
-  location: {
-    schema: 'address', // ссылка на именованную схему
+  address: {
+    schema: getAddressSchema, // <= использование фабрики
   },
-});
+};
 
 const data = {
-  name: 'John',
-  location: {
+  name: 'Fedor',
+  address: {
     city: 'Moscow',
-    zip: '101000',
+    zip: 123456,
   },
 };
 
-const result = projector.project('user', data);
+const result = projectData(userSchema, data);
 console.log(result);
 // {
-//   name: 'John',
-//   location: {
+//   name: 'Fedor',
+//   address: {
 //     city: 'Moscow'
 //   }
 // }

+ 0 - 391
dist/cjs/index.cjs

@@ -1,391 +0,0 @@
-"use strict";
-var __defProp = Object.defineProperty;
-var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
-var __getOwnPropNames = Object.getOwnPropertyNames;
-var __hasOwnProp = Object.prototype.hasOwnProperty;
-var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
-var __export = (target, all) => {
-  for (var name in all)
-    __defProp(target, name, { get: all[name], enumerable: true });
-};
-var __copyProps = (to, from, except, desc) => {
-  if (from && typeof from === "object" || typeof from === "function") {
-    for (let key of __getOwnPropNames(from))
-      if (!__hasOwnProp.call(to, key) && key !== except)
-        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
-  }
-  return to;
-};
-var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
-
-// src/index.js
-var index_exports = {};
-__export(index_exports, {
-  DataProjector: () => DataProjector,
-  ProjectionSchemaRegistry: () => ProjectionSchemaRegistry,
-  ProjectionScope: () => ProjectionScope,
-  projectData: () => projectData,
-  validateProjectionSchema: () => validateProjectionSchema
-});
-module.exports = __toCommonJS(index_exports);
-
-// src/project-data.js
-var import_js_format2 = require("@e22m4u/js-format");
-
-// src/validate-projection-schema.js
-var import_js_format = require("@e22m4u/js-format");
-function validateProjectionSchema(schema, shallowMode = false) {
-  if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
-    throw new import_js_format.InvalidArgumentError(
-      "Projection schema must be an Object, but %v was given.",
-      schema
-    );
-  }
-  if (typeof shallowMode !== "boolean") {
-    throw new import_js_format.InvalidArgumentError(
-      'Parameter "shallowMode" must be a Boolean, but %v was given.',
-      shallowMode
-    );
-  }
-  Object.keys(schema).forEach((propName) => {
-    const options = schema[propName];
-    if (options === void 0) {
-      return;
-    }
-    if (options === null || typeof options !== "boolean" && typeof options !== "object" || Array.isArray(options)) {
-      throw new import_js_format.InvalidArgumentError(
-        "Property options must be a Boolean or an Object, but %v was given.",
-        options
-      );
-    }
-    if (typeof options === "boolean") {
-      return;
-    }
-    if (options.select !== void 0 && typeof options.select !== "boolean") {
-      throw new import_js_format.InvalidArgumentError(
-        'Property option "select" must be a Boolean, but %v was given.',
-        options.select
-      );
-    }
-    if (options.schema !== void 0) {
-      if (!options.schema || typeof options.schema !== "string" && typeof options.schema !== "object" || Array.isArray(options.schema)) {
-        throw new import_js_format.InvalidArgumentError(
-          "Embedded schema must be an Object or a non-empty String that represents a schema name, but %v was given.",
-          options.schema
-        );
-      }
-      if (!shallowMode && typeof options.schema === "object") {
-        validateProjectionSchema(options.schema, shallowMode);
-      }
-    }
-    if (options.scopes !== void 0) {
-      if (!options.scopes || typeof options.scopes !== "object" || Array.isArray(options.scopes)) {
-        throw new import_js_format.InvalidArgumentError(
-          'Property option "scopes" must be an Object, but %v was given.',
-          options.scopes
-        );
-      }
-      Object.keys(options.scopes).forEach((scopeName) => {
-        const scopeOptions = options.scopes[scopeName];
-        if (scopeOptions === void 0) {
-          return;
-        }
-        if (scopeOptions === null || typeof scopeOptions !== "boolean" && typeof scopeOptions !== "object" || Array.isArray(scopeOptions)) {
-          throw new import_js_format.InvalidArgumentError(
-            "Scope options must be a Boolean or an Object, but %v was given.",
-            scopeOptions
-          );
-        }
-        if (typeof scopeOptions === "boolean") {
-          return;
-        }
-        if (scopeOptions.select !== void 0) {
-          if (typeof scopeOptions.select !== "boolean") {
-            throw new import_js_format.InvalidArgumentError(
-              'Scope option "select" must be a Boolean, but %v was given.',
-              scopeOptions.select
-            );
-          }
-        }
-      });
-    }
-  });
-}
-__name(validateProjectionSchema, "validateProjectionSchema");
-
-// src/project-data.js
-function projectData(schemaOrName, data, options = void 0) {
-  if (!schemaOrName || typeof schemaOrName !== "string" && typeof schemaOrName !== "object" || Array.isArray(schemaOrName)) {
-    throw new import_js_format2.InvalidArgumentError(
-      "Projection schema must be an Object or a non-empty String that represents a schema name, but %v was given.",
-      schemaOrName
-    );
-  }
-  if (options !== void 0) {
-    if (!options || typeof options !== "object" || Array.isArray(options)) {
-      throw new import_js_format2.InvalidArgumentError(
-        'Parameter "options" must be an Object, but %v was given.',
-        options
-      );
-    }
-    if (options.strict !== void 0 && typeof options.strict !== "boolean") {
-      throw new import_js_format2.InvalidArgumentError(
-        'Option "strict" must be a Boolean, but %v was given.',
-        options.strict
-      );
-    }
-    if (options.scope !== void 0 && (!options.scope || typeof options.scope !== "string")) {
-      throw new import_js_format2.InvalidArgumentError(
-        'Option "scope" must be a non-empty String, but %v was given.',
-        options.scope
-      );
-    }
-    if (options.resolver !== void 0 && (!options.resolver || typeof options.resolver !== "function")) {
-      throw new import_js_format2.InvalidArgumentError(
-        'Option "resolver" must be a Function, but %v was given.',
-        options.resolver
-      );
-    }
-  }
-  const strict = Boolean(options && options.strict);
-  const scope = options && options.scope || void 0;
-  const resolver = options && options.resolver || void 0;
-  let schema = schemaOrName;
-  if (typeof schemaOrName === "string") {
-    if (!resolver) {
-      throw new import_js_format2.InvalidArgumentError(
-        "Unable to resolve the named schema %v without a specified projection schema resolver.",
-        schemaOrName
-      );
-    }
-    schema = resolver(schemaOrName);
-    if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
-      throw new import_js_format2.InvalidArgumentError(
-        "Projection schema resolver must return an Object, but %v was given.",
-        schema
-      );
-    }
-  }
-  validateProjectionSchema(schema, true);
-  if (data === null || typeof data !== "object") {
-    return data;
-  }
-  if (Array.isArray(data)) {
-    return data.map(
-      (item) => projectData(schema, item, { strict, scope, resolver })
-    );
-  }
-  const result = {};
-  const keys = Object.keys(strict ? schema : data);
-  for (const key of keys) {
-    if (!(key in data)) continue;
-    const propOptionsOrBoolean = schema[key];
-    if (_shouldSelect(propOptionsOrBoolean, strict, scope)) {
-      const value = data[key];
-      if (propOptionsOrBoolean && typeof propOptionsOrBoolean === "object" && propOptionsOrBoolean.schema) {
-        result[key] = projectData(propOptionsOrBoolean.schema, value, {
-          strict,
-          scope,
-          resolver
-        });
-      } else {
-        result[key] = value;
-      }
-    }
-  }
-  return result;
-}
-__name(projectData, "projectData");
-function _shouldSelect(propOptionsOrBoolean, strict, scope) {
-  if (typeof propOptionsOrBoolean === "boolean") {
-    return propOptionsOrBoolean;
-  }
-  if (typeof propOptionsOrBoolean === "object") {
-    const propOptions = propOptionsOrBoolean;
-    if (scope && propOptions.scopes && typeof propOptions.scopes === "object" && propOptions.scopes[scope] != null) {
-      const scopeOptionsOrBoolean = propOptions.scopes[scope];
-      if (typeof scopeOptionsOrBoolean === "boolean") {
-        return scopeOptionsOrBoolean;
-      }
-      if (scopeOptionsOrBoolean && typeof scopeOptionsOrBoolean === "object" && typeof scopeOptionsOrBoolean.select === "boolean") {
-        return scopeOptionsOrBoolean.select;
-      }
-    }
-    if (typeof propOptionsOrBoolean.select === "boolean") {
-      return propOptionsOrBoolean.select;
-    }
-  }
-  return !strict;
-}
-__name(_shouldSelect, "_shouldSelect");
-
-// src/data-projector.js
-var import_js_service2 = require("@e22m4u/js-service");
-
-// src/projection-scope.js
-var ProjectionScope = {
-  INPUT: "input",
-  OUTPUT: "output"
-};
-
-// src/data-projector.js
-var import_js_format4 = require("@e22m4u/js-format");
-
-// src/projection-schema-registry.js
-var import_js_service = require("@e22m4u/js-service");
-var import_js_format3 = require("@e22m4u/js-format");
-var _ProjectionSchemaRegistry = class _ProjectionSchemaRegistry extends import_js_service.Service {
-  /**
-   * Schema map.
-   */
-  _schemas = /* @__PURE__ */ new Map();
-  /**
-   * Define schema.
-   *
-   * @param {string} name
-   * @param {object} schema
-   * @returns {this}
-   */
-  defineSchema(name, schema) {
-    if (!name || typeof name !== "string") {
-      throw new import_js_format3.InvalidArgumentError(
-        "Schema name must be a non-empty String, but %v was given.",
-        name
-      );
-    }
-    if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
-      throw new import_js_format3.InvalidArgumentError(
-        "Projection schema must be an Object, but %v was given.",
-        schema
-      );
-    }
-    if (this._schemas.has(name)) {
-      throw new import_js_format3.InvalidArgumentError(
-        "Projection schema %v is already registered.",
-        name
-      );
-    }
-    validateProjectionSchema(schema);
-    this._schemas.set(name, schema);
-    return this;
-  }
-  /**
-   * Get schema.
-   *
-   * @param {string} name
-   * @returns {object}
-   */
-  getSchema(name) {
-    if (!name || typeof name !== "string") {
-      throw new import_js_format3.InvalidArgumentError(
-        "Schema name must be a non-empty String, but %v was given.",
-        name
-      );
-    }
-    const schema = this._schemas.get(name);
-    if (!schema) {
-      throw new import_js_format3.InvalidArgumentError(
-        "Projection schema %v is not found.",
-        name
-      );
-    }
-    return schema;
-  }
-};
-__name(_ProjectionSchemaRegistry, "ProjectionSchemaRegistry");
-var ProjectionSchemaRegistry = _ProjectionSchemaRegistry;
-
-// src/data-projector.js
-var _DataProjector = class _DataProjector extends import_js_service2.Service {
-  /**
-   * Define schema.
-   *
-   * @param {string} name
-   * @param {object} schema
-   * @returns {this}
-   */
-  defineSchema(name, schema) {
-    this.getService(ProjectionSchemaRegistry).defineSchema(name, schema);
-    return this;
-  }
-  /**
-   * Project.
-   *
-   * @param {object|string} schemaOrName
-   * @param {object} data
-   * @param {object|undefined} options
-   * @returns {*}
-   */
-  project(schemaOrName, data, options = void 0) {
-    if (!schemaOrName || typeof schemaOrName !== "string" && typeof schemaOrName !== "object" || Array.isArray(schemaOrName)) {
-      throw new import_js_format4.InvalidArgumentError(
-        "Projection schema must be an Object or a non-empty String that represents a schema name, but %v was given.",
-        schemaOrName
-      );
-    }
-    if (options !== void 0) {
-      if (!options || typeof options !== "object" || Array.isArray(options)) {
-        throw new import_js_format4.InvalidArgumentError(
-          'Parameter "options" must be an Object, but %v was given.',
-          options
-        );
-      }
-      if (options.strict !== void 0 && typeof options.strict !== "boolean") {
-        throw new import_js_format4.InvalidArgumentError(
-          'Option "strict" must be a Boolean, but %v was given.',
-          options.strict
-        );
-      }
-      if (options.scope !== void 0 && (!options.scope || typeof options.scope !== "string")) {
-        throw new import_js_format4.InvalidArgumentError(
-          'Option "scope" must be a non-empty String, but %v was given.',
-          options.scope
-        );
-      }
-      if (options.resolver !== void 0) {
-        throw new import_js_format4.InvalidArgumentError(
-          'Option "resolver" is not supported for the DataProjector.'
-        );
-      }
-    }
-    const registry = this.getService(ProjectionSchemaRegistry);
-    return projectData(schemaOrName, data, {
-      ...options,
-      resolver: /* @__PURE__ */ __name((name) => registry.getSchema(name), "resolver")
-    });
-  }
-  /**
-   * Project with "input" scope.
-   *
-   * @param {object|string} schemaOrName
-   * @param {object} data
-   * @param {object|undefined} options
-   * @returns {*}
-   */
-  projectInput(schemaOrName, data, options = void 0) {
-    options = { ...options, scope: ProjectionScope.INPUT };
-    return this.project(schemaOrName, data, options);
-  }
-  /**
-   * Project with "output" scope.
-   *
-   * @param {object|string} schemaOrName
-   * @param {object} data
-   * @param {object|undefined} options
-   * @returns {*}
-   */
-  projectOutput(schemaOrName, data, options = void 0) {
-    options = { ...options, scope: ProjectionScope.OUTPUT };
-    return this.project(schemaOrName, data, options);
-  }
-};
-__name(_DataProjector, "DataProjector");
-var DataProjector = _DataProjector;
-// Annotate the CommonJS export names for ESM import in node:
-0 && (module.exports = {
-  DataProjector,
-  ProjectionSchemaRegistry,
-  ProjectionScope,
-  projectData,
-  validateProjectionSchema
-});

+ 4 - 5
package.json

@@ -1,5 +1,5 @@
 {
-  "name": "@e22m4u/js-data-projector",
+  "name": "@e22m4u/js-data-projection",
   "version": "0.0.3",
   "description": "JavaScript модуль для работы с проекцией данных",
   "author": "Mikhail Evstropov <e22m4u@yandex.ru>",
@@ -11,10 +11,10 @@
     "projection",
     "filtering"
   ],
-  "homepage": "https://gitrepos.ru/e22m4u/js-data-projector",
+  "homepage": "https://gitrepos.ru/e22m4u/js-data-projection",
   "repository": {
     "type": "git",
-    "url": "git+https://gitrepos.ru/e22m4u/js-data-projector.git"
+    "url": "git+https://gitrepos.ru/e22m4u/js-data-projection.git"
   },
   "type": "module",
   "types": "./src/index.d.ts",
@@ -38,8 +38,7 @@
     "prepare": "husky"
   },
   "dependencies": {
-    "@e22m4u/js-format": "~0.3.1",
-    "@e22m4u/js-service": "~0.5.1"
+    "@e22m4u/js-format": "~0.3.1"
   },
   "devDependencies": {
     "@commitlint/cli": "~20.1.0",

+ 0 - 55
src/data-projector.d.ts

@@ -1,55 +0,0 @@
-import {Service} from '@e22m4u/js-service';
-import {ProjectDataOptions} from './project-data.js';
-import {ProjectionSchema} from './projection-schema.js';
-
-/**
- * Data projector.
- */
-export declare class DataProjector extends Service {
-  /**
-   * Define schema.
-   *
-   * @param name
-   * @param schema
-   */
-  defineSchema(name: string, schema: ProjectionSchema): this;
-
-  /**
-   * Project.
-   *
-   * @param schemaOrName
-   * @param data
-   * @param options
-   */
-  project<T>(
-    schemaOrName: string | ProjectionSchema,
-    data: T,
-    options?: Omit<ProjectDataOptions, 'resolver'>,
-  ): T;
-
-  /**
-   * Project with "input" scope.
-   *
-   * @param schemaOrName
-   * @param data
-   * @param options
-   */
-  projectInput<T>(
-    schemaOrName: string | ProjectionSchema,
-    data: T,
-    options?: Omit<ProjectDataOptions, 'resolver' | 'scope'>,
-  ): T;
-
-  /**
-   * Project with "output" scope.
-   *
-   * @param schemaOrName
-   * @param data
-   * @param options
-   */
-  projectOutput<T>(
-    schemaOrName: string | ProjectionSchema,
-    data: T,
-    options?: Omit<ProjectDataOptions, 'resolver' | 'scope'>,
-  ): T;
-}

+ 0 - 108
src/data-projector.js

@@ -1,108 +0,0 @@
-import {Service} from '@e22m4u/js-service';
-import {projectData} from './project-data.js';
-import {ProjectionScope} from './projection-scope.js';
-import {InvalidArgumentError} from '@e22m4u/js-format';
-import {ProjectionSchemaRegistry} from './projection-schema-registry.js';
-
-/**
- * Data projector.
- */
-export class DataProjector extends Service {
-  /**
-   * Define schema.
-   *
-   * @param {string} name
-   * @param {object} schema
-   * @returns {this}
-   */
-  defineSchema(name, schema) {
-    this.getService(ProjectionSchemaRegistry).defineSchema(name, schema);
-    return this;
-  }
-
-  /**
-   * Project.
-   *
-   * @param {object|string} schemaOrName
-   * @param {object} data
-   * @param {object|undefined} options
-   * @returns {*}
-   */
-  project(schemaOrName, data, options = undefined) {
-    // schemaOrName
-    if (
-      !schemaOrName ||
-      (typeof schemaOrName !== 'string' && typeof schemaOrName !== 'object') ||
-      Array.isArray(schemaOrName)
-    ) {
-      throw new InvalidArgumentError(
-        'Projection schema must be an Object or a non-empty String ' +
-          'that represents a schema name, but %v was given.',
-        schemaOrName,
-      );
-    }
-    // options
-    if (options !== undefined) {
-      if (!options || typeof options !== 'object' || Array.isArray(options)) {
-        throw new InvalidArgumentError(
-          'Parameter "options" must be an Object, but %v was given.',
-          options,
-        );
-      }
-      // options.strict
-      if (options.strict !== undefined && typeof options.strict !== 'boolean') {
-        throw new InvalidArgumentError(
-          'Option "strict" must be a Boolean, but %v was given.',
-          options.strict,
-        );
-      }
-      // options.scope
-      if (
-        options.scope !== undefined &&
-        (!options.scope || typeof options.scope !== 'string')
-      ) {
-        throw new InvalidArgumentError(
-          'Option "scope" must be a non-empty String, but %v was given.',
-          options.scope,
-        );
-      }
-      // options.resolver
-      if (options.resolver !== undefined) {
-        throw new InvalidArgumentError(
-          'Option "resolver" is not supported for the DataProjector.',
-        );
-      }
-    }
-    const registry = this.getService(ProjectionSchemaRegistry);
-    return projectData(schemaOrName, data, {
-      ...options,
-      resolver: name => registry.getSchema(name),
-    });
-  }
-
-  /**
-   * Project with "input" scope.
-   *
-   * @param {object|string} schemaOrName
-   * @param {object} data
-   * @param {object|undefined} options
-   * @returns {*}
-   */
-  projectInput(schemaOrName, data, options = undefined) {
-    options = {...options, scope: ProjectionScope.INPUT};
-    return this.project(schemaOrName, data, options);
-  }
-
-  /**
-   * Project with "output" scope.
-   *
-   * @param {object|string} schemaOrName
-   * @param {object} data
-   * @param {object|undefined} options
-   * @returns {*}
-   */
-  projectOutput(schemaOrName, data, options = undefined) {
-    options = {...options, scope: ProjectionScope.OUTPUT};
-    return this.project(schemaOrName, data, options);
-  }
-}

+ 0 - 382
src/data-projector.spec.js

@@ -1,382 +0,0 @@
-import {expect} from 'chai';
-import {format} from '@e22m4u/js-format';
-import {DataProjector} from './data-projector.js';
-import {ProjectionSchemaRegistry} from './projection-schema-registry.js';
-
-describe('DataProjector', function () {
-  describe('defineSchema', function () {
-    it('should require the name parameter to be a non-empty string', function () {
-      const S = new DataProjector();
-      const throwable = v => () => S.defineSchema(v, {});
-      const error = s =>
-        format('Schema name must be a non-empty String, but %s was given.', s);
-      expect(throwable('')).to.throw(error('""'));
-      expect(throwable(10)).to.throw(error('10'));
-      expect(throwable(0)).to.throw(error('0'));
-      expect(throwable(true)).to.throw(error('true'));
-      expect(throwable(false)).to.throw(error('false'));
-      expect(throwable([])).to.throw(error('Array'));
-      expect(throwable({})).to.throw(error('Object'));
-      expect(throwable(null)).to.throw(error('null'));
-      expect(throwable(undefined)).to.throw(error('undefined'));
-      throwable('mySchema')();
-    });
-
-    it('should require the schema parameter to be an object', function () {
-      const S = new DataProjector();
-      const throwable = v => () => S.defineSchema('mySchema', v);
-      const error = s =>
-        format('Projection schema must be an Object, but %s was given.', s);
-      expect(throwable('str')).to.throw(error('"str"'));
-      expect(throwable('')).to.throw(error('""'));
-      expect(throwable(10)).to.throw(error('10'));
-      expect(throwable(0)).to.throw(error('0'));
-      expect(throwable(true)).to.throw(error('true'));
-      expect(throwable(false)).to.throw(error('false'));
-      expect(throwable([])).to.throw(error('Array'));
-      expect(throwable(null)).to.throw(error('null'));
-      expect(throwable(undefined)).to.throw(error('undefined'));
-      throwable({})();
-    });
-
-    it('should throw an error if the name is already registered', function () {
-      const S = new DataProjector();
-      S.defineSchema('mySchema', {});
-      const throwable = () => S.defineSchema('mySchema', {});
-      expect(throwable).to.throw(
-        'Projection schema "mySchema" is already registered.',
-      );
-    });
-
-    it('should register the given schema', function () {
-      const S = new DataProjector();
-      const registry = S.getService(ProjectionSchemaRegistry);
-      const schema = {foo: true, bar: false};
-      S.defineSchema('mySchema', schema);
-      expect(registry.getSchema('mySchema')).to.be.eql(schema);
-    });
-
-    it('should return this', function () {
-      const S = new DataProjector();
-      const res = S.defineSchema('mySchema', {});
-      expect(res).to.be.eq(S);
-    });
-  });
-
-  describe('project', function () {
-    it('should require the parameter "schemaOrName" to be a non-empty string or an object', function () {
-      const S = new DataProjector();
-      S.defineSchema('mySchema', {});
-      const throwable = v => () => S.project(v, {});
-      const error = s =>
-        format(
-          'Projection schema must be an Object or a non-empty String ' +
-            'that represents a schema name, but %s was given.',
-          s,
-        );
-      expect(throwable('')).to.throw(error('""'));
-      expect(throwable(10)).to.throw(error('10'));
-      expect(throwable(0)).to.throw(error('0'));
-      expect(throwable(true)).to.throw(error('true'));
-      expect(throwable(false)).to.throw(error('false'));
-      expect(throwable([])).to.throw(error('Array'));
-      expect(throwable(null)).to.throw(error('null'));
-      expect(throwable(undefined)).to.throw(error('undefined'));
-      throwable('mySchema')();
-      throwable({})();
-    });
-
-    it('should require the parameter "options" to be an object', function () {
-      const S = new DataProjector();
-      const throwable = v => () => S.project({}, {}, v);
-      const error = s =>
-        format('Parameter "options" must be an Object, but %s was given.', s);
-      expect(throwable('str')).to.throw(error('"str"'));
-      expect(throwable('')).to.throw(error('""'));
-      expect(throwable(10)).to.throw(error('10'));
-      expect(throwable(0)).to.throw(error('0'));
-      expect(throwable(true)).to.throw(error('true'));
-      expect(throwable(false)).to.throw(error('false'));
-      expect(throwable([])).to.throw(error('Array'));
-      expect(throwable(null)).to.throw(error('null'));
-      throwable({})();
-      throwable(undefined)();
-    });
-
-    it('should require the option "strict" to be a boolean', function () {
-      const S = new DataProjector();
-      const throwable = v => () => S.project({}, {}, {strict: v});
-      const error = s =>
-        format('Option "strict" must be a Boolean, but %s was given.', s);
-      expect(throwable('str')).to.throw(error('"str"'));
-      expect(throwable('')).to.throw(error('""'));
-      expect(throwable(10)).to.throw(error('10'));
-      expect(throwable(0)).to.throw(error('0'));
-      expect(throwable([])).to.throw(error('Array'));
-      expect(throwable({})).to.throw(error('Object'));
-      expect(throwable(null)).to.throw(error('null'));
-      throwable(true)();
-      throwable(false)();
-      throwable(undefined)();
-    });
-
-    it('should require the option "scope" to be a non-empty string', function () {
-      const S = new DataProjector();
-      const throwable = v => () => S.project({}, {}, {scope: v});
-      const error = s =>
-        format(
-          'Option "scope" must be a non-empty String, but %s was given.',
-          s,
-        );
-      expect(throwable('')).to.throw(error('""'));
-      expect(throwable(10)).to.throw(error('10'));
-      expect(throwable(0)).to.throw(error('0'));
-      expect(throwable(true)).to.throw(error('true'));
-      expect(throwable(false)).to.throw(error('false'));
-      expect(throwable([])).to.throw(error('Array'));
-      expect(throwable({})).to.throw(error('Object'));
-      expect(throwable(null)).to.throw(error('null'));
-      throwable('str')();
-      throwable(undefined)();
-    });
-
-    it('should throw an error when the resolver option is provided', function () {
-      const S = new DataProjector();
-      // @ts-ignore
-      const throwable = v => () => S.project({}, {}, {resolver: v});
-      const error = 'Option "resolver" is not supported for the DataProjector.';
-      expect(throwable('str')).to.throw(error);
-      expect(throwable('')).to.throw(error);
-      expect(throwable(10)).to.throw(error);
-      expect(throwable(0)).to.throw(error);
-      expect(throwable(true)).to.throw(error);
-      expect(throwable(false)).to.throw(error);
-      expect(throwable([])).to.throw(error);
-      expect(throwable({})).to.throw(error);
-      expect(throwable(null)).to.throw(error);
-      throwable(undefined)();
-    });
-
-    it('should validate the given schema object', function () {
-      const S = new DataProjector();
-      // @ts-ignore
-      const throwable = () => S.project({foo: 10}, {foo: 'bar'});
-      expect(throwable).to.throw(
-        'Property options must be a Boolean or an Object, but 10 was given.',
-      );
-    });
-
-    it('should throw an error if the schema name is not registered', function () {
-      const S = new DataProjector();
-      const throwable = () => S.project('unknown', {});
-      expect(throwable).to.throw('Projection schema "unknown" is not found.');
-    });
-
-    it('should return non-object values as is', function () {
-      const S = new DataProjector();
-      const schema = {foo: {select: true}};
-      expect(S.project(schema, 'str')).to.be.eq('str');
-      expect(S.project(schema, '')).to.be.eq('');
-      expect(S.project(schema, 10)).to.be.eq(10);
-      expect(S.project(schema, 0)).to.be.eq(0);
-      expect(S.project(schema, true)).to.be.eq(true);
-      expect(S.project(schema, false)).to.be.eq(false);
-      expect(S.project(schema, undefined)).to.be.eq(undefined);
-      expect(S.project(schema, null)).to.be.eq(null);
-    });
-
-    it('should add properties without rules by default', function () {
-      const S = new DataProjector();
-      expect(S.project({}, {foo: 10, bar: 20})).to.be.eql({
-        foo: 10,
-        bar: 20,
-      });
-    });
-
-    it('should project fields by a boolean value', function () {
-      const S = new DataProjector();
-      const schema = {foo: true, bar: false};
-      const data = {foo: 10, bar: 20, baz: 30};
-      expect(S.project(schema, data)).to.be.eql({foo: 10, baz: 30});
-    });
-
-    it('should project fields by the select option', function () {
-      const S = new DataProjector();
-      const schema = {foo: {select: true}, bar: {select: false}};
-      const data = {foo: 10, bar: 20};
-      expect(S.project(schema, data)).to.be.eql({foo: 10});
-    });
-
-    it('should project fields by the schema name', function () {
-      const S = new DataProjector();
-      S.defineSchema('user', {id: true, email: false});
-      const data = {id: 1, email: 'test@example.com', name: 'John'};
-      expect(S.project('user', data)).to.be.eql({id: 1, name: 'John'});
-    });
-
-    it('should apply projection to an array of items', function () {
-      const S = new DataProjector();
-      const schema = {id: {select: true}, secret: {select: false}};
-      const data = [
-        {id: 1, secret: 'A'},
-        {id: 2, secret: 'B'},
-      ];
-      expect(S.project(schema, data)).to.be.eql([{id: 1}, {id: 2}]);
-    });
-
-    describe('strict mode', function () {
-      it('should ignore properties not present in schema when strict mode is enabled', function () {
-        const S = new DataProjector();
-        const schema = {id: {select: true}};
-        const data = {id: 1, other: 'value'};
-        expect(S.project(schema, data, {strict: true})).to.be.eql({id: 1});
-      });
-
-      it('should project fields by a boolean value', function () {
-        const S = new DataProjector();
-        const schema = {foo: true, bar: false};
-        const data = {foo: 1, bar: 2, baz: 3};
-        expect(S.project(schema, data, {strict: true})).to.be.eql({foo: 1});
-      });
-
-      it('should default to hidden in strict mode if no rules are provided', function () {
-        const S = new DataProjector();
-        const schema = {id: {}};
-        const data = {id: 1};
-        expect(S.project(schema, data, {strict: true})).to.be.eql({});
-      });
-
-      it('should skip properties present in schema but missing in data', function () {
-        const S = new DataProjector();
-        const schema = {
-          existing: {select: true},
-          missing: {select: true},
-        };
-        const data = {existing: 1};
-        expect(S.project(schema, data, {strict: true})).to.be.eql({
-          existing: 1,
-        });
-      });
-    });
-
-    describe('projection scopes', function () {
-      it('should apply scope-specific selection rules', function () {
-        const S = new DataProjector();
-        const schema = {
-          foo: {
-            select: false,
-            scopes: {
-              input: {select: true},
-            },
-          },
-          bar: {select: true},
-        };
-        const data = {foo: 10, bar: 20};
-        expect(S.project(schema, data)).to.be.eql({bar: 20});
-        expect(S.project(schema, data, {scope: 'input'})).to.be.eql({
-          foo: 10,
-          bar: 20,
-        });
-      });
-
-      it('should fallback to general rule if scope rule is missing', function () {
-        const S = new DataProjector();
-        const schema = {
-          foo: {
-            select: true,
-            scopes: {
-              output: {select: false},
-            },
-          },
-        };
-        const data = {foo: 10};
-        expect(S.project(schema, data, {scope: 'input'})).to.be.eql({
-          foo: 10,
-        });
-      });
-
-      it('should fallback to the general rule if the scope options exists but lacks the select option', function () {
-        const S = new DataProjector();
-        const schema = {
-          foo: {
-            select: true,
-            scopes: {
-              input: {},
-            },
-          },
-          bar: {
-            select: false,
-            scopes: {
-              input: {},
-            },
-          },
-        };
-        const data = {foo: 10, bar: 20};
-        const result = S.project(schema, data, {scope: 'input'});
-        expect(result).to.be.eql({foo: 10});
-      });
-    });
-
-    describe('nested schema', function () {
-      it('should apply nested schema recursively', function () {
-        const S = new DataProjector();
-        const schema = {user: {schema: {password: false}}};
-        const data = {user: {id: 1, password: '123'}};
-        expect(S.project(schema, data)).to.be.eql({user: {id: 1}});
-      });
-
-      it('should apply nested registered schema by a name', function () {
-        const S = new DataProjector();
-        S.defineSchema('address', {zip: {select: false}});
-        const schema = {location: {schema: 'address'}};
-        const data = {location: {city: 'City', zip: '12345'}};
-        expect(S.project(schema, data)).to.be.eql({location: {city: 'City'}});
-      });
-
-      it('should apply nested schema to array of objects', function () {
-        const S = new DataProjector();
-        const schema = {items: {schema: {hidden: false}}};
-        const data = {
-          items: [
-            {id: 1, hidden: 'x'},
-            {id: 2, hidden: 'y'},
-          ],
-        };
-        expect(S.project(schema, data)).to.be.eql({items: [{id: 1}, {id: 2}]});
-      });
-
-      it('should handle null or undefined in nested data', function () {
-        const S = new DataProjector();
-        const schema = {nested: {schema: {foo: true}}};
-        expect(S.project(schema, {nested: null})).to.be.eql({nested: null});
-        expect(S.project(schema, {nested: undefined})).to.be.eql({
-          nested: undefined,
-        });
-      });
-    });
-  });
-
-  describe('projectInput', function () {
-    it('should apply the given schema with the input scope', function () {
-      const S = new DataProjector();
-      const schema = {
-        foo: {scopes: {input: true, output: false}},
-        bar: {scopes: {input: false, output: true}},
-      };
-      const res = S.projectInput(schema, {foo: 10, bar: 20});
-      expect(res).to.be.eql({foo: 10});
-    });
-  });
-
-  describe('projectOutput', function () {
-    it('should apply the given schema with the output scope', function () {
-      const S = new DataProjector();
-      const schema = {
-        foo: {scopes: {input: true, output: false}},
-        bar: {scopes: {input: false, output: true}},
-      };
-      const res = S.projectOutput(schema, {foo: 10, bar: 20});
-      expect(res).to.be.eql({bar: 20});
-    });
-  });
-});

+ 0 - 2
src/index.d.ts

@@ -1,6 +1,4 @@
 export * from './project-data.js';
-export * from './data-projector.js';
 export * from './projection-scope.js';
 export * from './projection-schema.js';
-export * from './projection-schema-registry.js';
 export * from './validate-projection-schema.js';

+ 0 - 2
src/index.js

@@ -1,6 +1,4 @@
 export * from './project-data.js';
-export * from './data-projector.js';
 export * from './projection-scope.js';
 export * from './projection-schema.js';
-export * from './projection-schema-registry.js';
 export * from './validate-projection-schema.js';

+ 4 - 7
src/project-data.d.ts

@@ -1,11 +1,9 @@
 import {ProjectionSchema} from './projection-schema.js';
 
 /**
- * Projection schema resolver.
+ * Projection schema factory.
  */
-export type ProjectionSchemaResolver = (
-  schemaName: string,
-) => ProjectionSchema;
+export type ProjectionSchemaFactory = () => ProjectionSchema;
 
 /**
  * Project data options.
@@ -13,18 +11,17 @@ export type ProjectionSchemaResolver = (
 export type ProjectDataOptions = {
   strict?: boolean;
   scope?: string;
-  resolver?: ProjectionSchemaResolver;
 };
 
 /**
  * Project data.
  *
- * @param schemaOrName
+ * @param schemaOrFactory
  * @param data
  * @param options
  */
 export declare function projectData<T>(
-  schemaOrName: string | ProjectionSchema,
+  schemaOrFactory: ProjectionSchema | ProjectionSchemaFactory,
   data: T,
   options?: ProjectDataOptions,
 ): T;

+ 20 - 44
src/project-data.js

@@ -4,22 +4,23 @@ import {validateProjectionSchema} from './validate-projection-schema.js';
 /**
  * Project data.
  *
- * @param {object|string} schemaOrName
+ * @param {object|Function} schemaOrFactory
  * @param {object} data
  * @param {object|undefined} options
  * @returns {*}
  */
-export function projectData(schemaOrName, data, options = undefined) {
-  // schemaOrName
+export function projectData(schemaOrFactory, data, options = undefined) {
+  // schemaOrFactory
   if (
-    !schemaOrName ||
-    (typeof schemaOrName !== 'string' && typeof schemaOrName !== 'object') ||
-    Array.isArray(schemaOrName)
+    !schemaOrFactory ||
+    (typeof schemaOrFactory !== 'object' &&
+      typeof schemaOrFactory !== 'function') ||
+    Array.isArray(schemaOrFactory)
   ) {
     throw new InvalidArgumentError(
-      'Projection schema must be an Object or a non-empty String ' +
-        'that represents a schema name, but %v was given.',
-      schemaOrName,
+      'Projection schema must be an Object or a Function ' +
+        'that returns a schema object, but %v was given.',
+      schemaOrFactory,
     );
   }
   // options
@@ -47,38 +48,19 @@ export function projectData(schemaOrName, data, options = undefined) {
         options.scope,
       );
     }
-    // options.resolver
-    if (
-      options.resolver !== undefined &&
-      (!options.resolver || typeof options.resolver !== 'function')
-    ) {
-      throw new InvalidArgumentError(
-        'Option "resolver" must be a Function, but %v was given.',
-        options.resolver,
-      );
-    }
   }
   const strict = Boolean(options && options.strict);
   const scope = (options && options.scope) || undefined;
-  const resolver = (options && options.resolver) || undefined;
-  // если вместо схемы передана строка,
-  // то строка используется как название
-  // зарегистрированной схемы проекции
-  let schema = schemaOrName;
-  if (typeof schemaOrName === 'string') {
-    if (!resolver) {
-      throw new InvalidArgumentError(
-        'Unable to resolve the named schema %v without ' +
-          'a specified projection schema resolver.',
-        schemaOrName,
-      );
-    }
-    schema = resolver(schemaOrName);
-    // если не удалось извлечь схему проекции
-    // по имени, то выбрасывается ошибка
+  // если вместо схемы передана фабрика,
+  // то извлекается фабричное значение
+  let schema = schemaOrFactory;
+  if (typeof schemaOrFactory === 'function') {
+    schema = schemaOrFactory();
+    // если не удалось извлечь схему проекции,
+    // то выбрасывается ошибка
     if (!schema || typeof schema !== 'object' || Array.isArray(schema)) {
       throw new InvalidArgumentError(
-        'Projection schema resolver must return an Object, but %v was given.',
+        'Projection schema factory must return an Object, but %v was given.',
         schema,
       );
     }
@@ -94,9 +76,7 @@ export function projectData(schemaOrName, data, options = undefined) {
   // если данные являются массивом, то проекция
   // применяется к каждому элементу
   if (Array.isArray(data)) {
-    return data.map(item =>
-      projectData(schema, item, {strict, scope, resolver}),
-    );
+    return data.map(item => projectData(schema, item, options));
   }
   // если данные являются объектом,
   // то создается проекция согласно схеме
@@ -121,11 +101,7 @@ export function projectData(schemaOrName, data, options = undefined) {
         typeof propOptionsOrBoolean === 'object' &&
         propOptionsOrBoolean.schema
       ) {
-        result[key] = projectData(propOptionsOrBoolean.schema, value, {
-          strict,
-          scope,
-          resolver,
-        });
+        result[key] = projectData(propOptionsOrBoolean.schema, value, options);
       }
       // иначе значение присваивается
       // свойству без изменений

+ 16 - 44
src/project-data.spec.js

@@ -3,15 +3,15 @@ import {format} from '@e22m4u/js-format';
 import {projectData} from './project-data.js';
 
 describe('projectData', function () {
-  it('should require the parameter "schemaOrName" to be a non-empty string or an object', function () {
-    const resolver = () => ({});
-    const throwable = v => () => projectData(v, {}, {resolver});
+  it('should require the parameter "schemaOrFactory" to be an object or a function', function () {
+    const throwable = v => () => projectData(v, {});
     const error = s =>
       format(
-        'Projection schema must be an Object or a non-empty String ' +
-          'that represents a schema name, but %s was given.',
+        'Projection schema must be an Object or a Function ' +
+          'that returns a schema object, but %s was given.',
         s,
       );
+    expect(throwable('str')).to.throw(error('"str"'));
     expect(throwable('')).to.throw(error('""'));
     expect(throwable(10)).to.throw(error('10'));
     expect(throwable(0)).to.throw(error('0'));
@@ -20,8 +20,8 @@ describe('projectData', function () {
     expect(throwable([])).to.throw(error('Array'));
     expect(throwable(null)).to.throw(error('null'));
     expect(throwable(undefined)).to.throw(error('undefined'));
-    throwable('mySchema')();
     throwable({})();
+    throwable(() => ({}))();
   });
 
   it('should require the parameter "options" to be an object', function () {
@@ -72,36 +72,11 @@ describe('projectData', function () {
     throwable(undefined)();
   });
 
-  it('should require the option "resolver" to be a function', function () {
-    const throwable = v => () => projectData({}, {}, {resolver: v});
-    const error = s =>
-      format('Option "resolver" must be a Function, but %s was given.', s);
-    expect(throwable('str')).to.throw(error('"str"'));
-    expect(throwable('')).to.throw(error('""'));
-    expect(throwable(10)).to.throw(error('10'));
-    expect(throwable(0)).to.throw(error('0'));
-    expect(throwable(true)).to.throw(error('true'));
-    expect(throwable(false)).to.throw(error('false'));
-    expect(throwable([])).to.throw(error('Array'));
-    expect(throwable({})).to.throw(error('Object'));
-    expect(throwable(null)).to.throw(error('null'));
-    throwable(() => undefined)();
-    throwable(undefined)();
-  });
-
-  it('should throw an error if no resolver specified when a schema name is provided', function () {
-    expect(() => projectData('mySchema', {})).to.throw(
-      'Unable to resolve the named schema "mySchema" without ' +
-        'a specified projection schema resolver.',
-    );
-  });
-
-  it('should throw an error if the schema resolver returns an invalid value', function () {
-    const throwable = v => () =>
-      projectData('mySchema', {}, {resolver: () => v});
+  it('should throw an error if the schema factory returns an invalid value', function () {
+    const throwable = v => () => projectData(() => v, {});
     const error = s =>
       format(
-        'Projection schema resolver must return an Object, but %s was given.',
+        'Projection schema factory must return an Object, but %s was given.',
         s,
       );
     expect(throwable('str')).to.throw(error('"str"'));
@@ -175,30 +150,27 @@ describe('projectData', function () {
     expect(res).to.be.eql({foo: 10, qux: {abc: 30}});
   });
 
-  describe('schema name', function () {
-    it('should pass the schema name to the schema resolver', function () {
+  describe('schema factory', function () {
+    it('should resolve a schema object from the given factory', function () {
       let invoked = 0;
-      const resolver = name => {
-        expect(name).to.be.eq('mySchema');
+      const factory = () => {
         invoked++;
         return {foo: true, bar: false};
       };
-      const res = projectData('mySchema', {foo: 10, bar: 20}, {resolver});
+      const res = projectData(factory, {foo: 10, bar: 20});
       expect(res).to.be.eql({foo: 10});
       expect(invoked).to.be.eq(1);
     });
 
-    it('should use the schema resolver in the nested schema', function () {
+    it('should use the schema factory in the nested schema', function () {
       let invoked = 0;
-      const resolver = name => {
-        expect(name).to.be.eq('mySchema');
+      const factory = () => {
         invoked++;
         return {baz: true, qux: false};
       };
       const res = projectData(
-        {foo: true, bar: {schema: 'mySchema'}},
+        {foo: true, bar: {schema: factory}},
         {foo: 10, bar: {baz: 20, qux: 30}},
-        {resolver},
       );
       expect(res).to.be.eql({foo: 10, bar: {baz: 20}});
       expect(invoked).to.be.eq(1);

+ 0 - 22
src/projection-schema-registry.d.ts

@@ -1,22 +0,0 @@
-import {Service} from '@e22m4u/js-service';
-import {ProjectionSchema} from './projection-schema.js';
-
-/**
- * Projection schema registry.
- */
-export declare class ProjectionSchemaRegistry extends Service {
-  /**
-   * Define schema.
-   *
-   * @param name
-   * @param schema
-   */
-  defineSchema(name: string, schema: ProjectionSchema): this;
-
-  /**
-   * Get schema.
-   *
-   * @param name
-   */
-  getSchema(name: string): ProjectionSchema;
-}

+ 0 - 67
src/projection-schema-registry.js

@@ -1,67 +0,0 @@
-import {Service} from '@e22m4u/js-service';
-import {InvalidArgumentError} from '@e22m4u/js-format';
-import {validateProjectionSchema} from './validate-projection-schema.js';
-
-/**
- * Projection schema registry.
- */
-export class ProjectionSchemaRegistry extends Service {
-  /**
-   * Schema map.
-   */
-  _schemas = new Map();
-
-  /**
-   * Define schema.
-   *
-   * @param {string} name
-   * @param {object} schema
-   * @returns {this}
-   */
-  defineSchema(name, schema) {
-    if (!name || typeof name !== 'string') {
-      throw new InvalidArgumentError(
-        'Schema name must be a non-empty String, but %v was given.',
-        name,
-      );
-    }
-    if (!schema || typeof schema !== 'object' || Array.isArray(schema)) {
-      throw new InvalidArgumentError(
-        'Projection schema must be an Object, but %v was given.',
-        schema,
-      );
-    }
-    if (this._schemas.has(name)) {
-      throw new InvalidArgumentError(
-        'Projection schema %v is already registered.',
-        name,
-      );
-    }
-    validateProjectionSchema(schema);
-    this._schemas.set(name, schema);
-    return this;
-  }
-
-  /**
-   * Get schema.
-   *
-   * @param {string} name
-   * @returns {object}
-   */
-  getSchema(name) {
-    if (!name || typeof name !== 'string') {
-      throw new InvalidArgumentError(
-        'Schema name must be a non-empty String, but %v was given.',
-        name,
-      );
-    }
-    const schema = this._schemas.get(name);
-    if (!schema) {
-      throw new InvalidArgumentError(
-        'Projection schema %v is not found.',
-        name,
-      );
-    }
-    return schema;
-  }
-}

+ 0 - 96
src/projection-schema-registry.spec.js

@@ -1,96 +0,0 @@
-import {expect} from 'chai';
-import {format} from '@e22m4u/js-format';
-import {ProjectionSchemaRegistry} from './projection-schema-registry.js';
-
-describe('ProjectionSchemaRegistry', function () {
-  describe('defineSchema', function () {
-    it('should require the name parameter to be a non-empty string', function () {
-      const S = new ProjectionSchemaRegistry();
-      const throwable = v => () => S.defineSchema(v, {});
-      const error = s =>
-        format('Schema name must be a non-empty String, but %s was given.', s);
-      expect(throwable('')).to.throw(error('""'));
-      expect(throwable(10)).to.throw(error('10'));
-      expect(throwable(0)).to.throw(error('0'));
-      expect(throwable(true)).to.throw(error('true'));
-      expect(throwable(false)).to.throw(error('false'));
-      expect(throwable([])).to.throw(error('Array'));
-      expect(throwable({})).to.throw(error('Object'));
-      expect(throwable(null)).to.throw(error('null'));
-      expect(throwable(undefined)).to.throw(error('undefined'));
-      throwable('mySchema')();
-    });
-
-    it('should require the schema parameter to be an object', function () {
-      const S = new ProjectionSchemaRegistry();
-      const throwable = v => () => S.defineSchema('mySchema', v);
-      const error = s =>
-        format('Projection schema must be an Object, but %s was given.', s);
-      expect(throwable('str')).to.throw(error('"str"'));
-      expect(throwable('')).to.throw(error('""'));
-      expect(throwable(10)).to.throw(error('10'));
-      expect(throwable(0)).to.throw(error('0'));
-      expect(throwable(true)).to.throw(error('true'));
-      expect(throwable(false)).to.throw(error('false'));
-      expect(throwable([])).to.throw(error('Array'));
-      expect(throwable(null)).to.throw(error('null'));
-      expect(throwable(undefined)).to.throw(error('undefined'));
-      throwable({})();
-    });
-
-    it('should throw an error if the name is already registered', function () {
-      const S = new ProjectionSchemaRegistry();
-      S.defineSchema('mySchema', {});
-      const throwable = () => S.defineSchema('mySchema', {});
-      expect(throwable).to.throw(
-        'Projection schema "mySchema" is already registered.',
-      );
-    });
-
-    it('should register the given schema', function () {
-      const S = new ProjectionSchemaRegistry();
-      const schema = {foo: true, bar: false};
-      S.defineSchema('mySchema', schema);
-      expect(S['_schemas'].get('mySchema')).to.be.eql(schema);
-    });
-
-    it('should return this', function () {
-      const S = new ProjectionSchemaRegistry();
-      const res = S.defineSchema('mySchema', {});
-      expect(res).to.be.eq(S);
-    });
-  });
-
-  describe('getSchema', function () {
-    it('should require the name parameter to be a non-empty string', function () {
-      const S = new ProjectionSchemaRegistry();
-      S.defineSchema('mySchema', {});
-      const throwable = v => () => S.getSchema(v);
-      const error = s =>
-        format('Schema name must be a non-empty String, but %s was given.', s);
-      expect(throwable(10)).to.throw(error('10'));
-      expect(throwable(0)).to.throw(error('0'));
-      expect(throwable(true)).to.throw(error('true'));
-      expect(throwable(false)).to.throw(error('false'));
-      expect(throwable([])).to.throw(error('Array'));
-      expect(throwable(null)).to.throw(error('null'));
-      expect(throwable(undefined)).to.throw(error('undefined'));
-      throwable('mySchema')();
-    });
-
-    it('should throw an error if the name is not registered', function () {
-      const S = new ProjectionSchemaRegistry();
-      S.defineSchema('mySchema', {});
-      const throwable = () => S.getSchema('unknown');
-      expect(throwable).to.throw('Projection schema "unknown" is not found.');
-    });
-
-    it('should return the registered schema', function () {
-      const S = new ProjectionSchemaRegistry();
-      const schema = {foo: true, bar: false};
-      S.defineSchema('mySchema', schema);
-      const res = S.getSchema('mySchema');
-      expect(res).to.be.eql(schema);
-    });
-  });
-});

+ 1 - 1
src/projection-schema.d.ts

@@ -11,7 +11,7 @@ export type ProjectionSchema = {
 export type ProjectionSchemaPropertyOptions = {
   select?: boolean;
   scopes?: ProjectionSchemaScopes;
-  schema?: string | ProjectionSchema;
+  schema?: Function | ProjectionSchema;
 }
 
 /**

+ 4 - 4
src/validate-projection-schema.js

@@ -52,13 +52,13 @@ export function validateProjectionSchema(schema, shallowMode = false) {
     if (options.schema !== undefined) {
       if (
         !options.schema ||
-        (typeof options.schema !== 'string' &&
-          typeof options.schema !== 'object') ||
+        (typeof options.schema !== 'object' &&
+          typeof options.schema !== 'function') ||
         Array.isArray(options.schema)
       ) {
         throw new InvalidArgumentError(
-          'Embedded schema must be an Object or a non-empty String ' +
-            'that represents a schema name, but %v was given.',
+          'Embedded schema must be an Object or a Function ' +
+            'that returns a schema, but %v was given.',
           options.schema,
         );
       }

+ 5 - 11
src/validate-projection-schema.spec.js

@@ -73,14 +73,15 @@ describe('validateProjectionSchema', function () {
     throwable(undefined)();
   });
 
-  it('should require the property option "schema" to be an object or a non-empty string', function () {
+  it('should require the property option "schema" to be an object or a function', function () {
     const throwable = v => () => validateProjectionSchema({foo: {schema: v}});
     const error = s =>
       format(
-        'Embedded schema must be an Object or a non-empty String ' +
-          'that represents a schema name, but %s was given.',
+        'Embedded schema must be an Object or a Function ' +
+          'that returns a schema, but %s was given.',
         s,
       );
+    expect(throwable('str')).to.throw(error('"str"'));
     expect(throwable('')).to.throw(error('""'));
     expect(throwable(10)).to.throw(error('10'));
     expect(throwable(0)).to.throw(error('0'));
@@ -88,8 +89,8 @@ describe('validateProjectionSchema', function () {
     expect(throwable(false)).to.throw(error('false'));
     expect(throwable([])).to.throw(error('Array'));
     expect(throwable(null)).to.throw(error('null'));
-    throwable('str')();
     throwable({})();
+    throwable(() => ({}))();
     throwable(undefined)();
   });
 
@@ -170,13 +171,7 @@ describe('validateProjectionSchema', function () {
     });
   });
 
-  it('should allow a projection name in the "schema" option', function () {
-    validateProjectionSchema({foo: {schema: 'mySchema'}});
-    validateProjectionSchema({foo: {schema: {bar: {schema: 'mySchema'}}}});
-  });
-
   it('should validate root schema in shallow mode', function () {
-    // @ts-ignore
     const throwable = () => validateProjectionSchema({foo: 10}, true);
     expect(throwable).to.throw(
       'Property options must be a Boolean or an Object, but 10 was given.',
@@ -184,7 +179,6 @@ describe('validateProjectionSchema', function () {
   });
 
   it('should skip nested schema checking in shallow mode', function () {
-    // @ts-ignore
     validateProjectionSchema({foo: {schema: {prop: 10}}}, true);
   });
 });