Просмотр исходного кода

feat: projector should pass container to factory

e22m4u 1 день назад
Родитель
Сommit
9c8b97247b
5 измененных файлов с 109 добавлено и 17 удалено
  1. 12 5
      dist/cjs/index.cjs
  2. 4 4
      src/data-projector.js
  3. 41 0
      src/data-projector.spec.js
  4. 13 1
      src/project-data.js
  5. 39 7
      src/project-data.spec.js

+ 12 - 5
dist/cjs/index.cjs

@@ -150,9 +150,16 @@ function projectData(schema, data, options) {
         options.resolver
       );
     }
+    if (options.factoryArgs !== void 0 && !Array.isArray(options.factoryArgs)) {
+      throw new import_js_format2.InvalidArgumentError(
+        'Projection option "factoryArgs" must be an Array, but %v was given.',
+        options.factoryArgs
+      );
+    }
   }
   if (typeof schema === "function") {
-    schema = schema();
+    const factoryArgs = options && options.factoryArgs || [];
+    schema = schema(...factoryArgs);
     if (!schema || typeof schema !== "object" && typeof schema !== "string" || Array.isArray(schema)) {
       throw new import_js_format2.InvalidArgumentError(
         "Schema factory must return an Object or a non-empty String, but %v was given.",
@@ -345,10 +352,10 @@ var _DataProjector = class _DataProjector extends import_js_service2.Service {
    */
   project(schema, data, options) {
     const registry = this.getService(ProjectionSchemaRegistry);
-    const resolver = /* @__PURE__ */ __name((name) => {
-      return registry.getSchema(name);
-    }, "resolver");
-    return projectData(schema, data, { ...options, resolver });
+    const defaultResolver = /* @__PURE__ */ __name((name) => registry.getSchema(name), "defaultResolver");
+    const resolver = options && options.resolver || defaultResolver;
+    const factoryArgs = options && options.factoryArgs || [this.container];
+    return projectData(schema, data, { ...options, resolver, factoryArgs });
   }
 };
 __name(_DataProjector, "DataProjector");

+ 4 - 4
src/data-projector.js

@@ -27,9 +27,9 @@ export class DataProjector extends Service {
    */
   project(schema, data, options) {
     const registry = this.getService(ProjectionSchemaRegistry);
-    const resolver = name => {
-      return registry.getSchema(name);
-    };
-    return projectData(schema, data, {...options, resolver});
+    const defaultResolver = name => registry.getSchema(name);
+    const resolver = (options && options.resolver) || defaultResolver;
+    const factoryArgs = (options && options.factoryArgs) || [this.container];
+    return projectData(schema, data, {...options, resolver, factoryArgs});
   }
 }

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

@@ -63,5 +63,46 @@ describe('DataProjector', function () {
       );
       expect(res).to.be.eql({foo: 10});
     });
+
+    it('should allow override the "resolver" option', function () {
+      const S = new DataProjector();
+      const schemaName = 'mySchema';
+      let invoked = 0;
+      const resolver = name => {
+        invoked++;
+        expect(name).to.be.eq(schemaName);
+        return {foo: true, bar: false};
+      };
+      const res = S.project('mySchema', {foo: 10, bar: 20}, {resolver});
+      expect(res).to.be.eql({foo: 10});
+      expect(invoked).to.be.eq(1);
+    });
+
+    it('should pass the service container to the schema factory', function () {
+      const S = new DataProjector();
+      let invoked = 0;
+      const factory = container => {
+        invoked++;
+        expect(container).to.be.eq(S.container);
+        return {foo: true, bar: false};
+      };
+      const res = S.project(factory, {foo: 10, bar: 20});
+      expect(res).to.be.eql({foo: 10});
+      expect(invoked).to.be.eq(1);
+    });
+
+    it('should allow override the "factoryArgs" option', function () {
+      const S = new DataProjector();
+      let invoked = 0;
+      const factoryArgs = [1, 2, 3];
+      const factory = (...args) => {
+        invoked++;
+        expect(args).to.be.eql(factoryArgs);
+        return {foo: true, bar: false};
+      };
+      const res = S.project(factory, {foo: 10, bar: 20}, {factoryArgs});
+      expect(res).to.be.eql({foo: 10});
+      expect(invoked).to.be.eq(1);
+    });
   });
 });

+ 13 - 1
src/project-data.js

@@ -47,11 +47,23 @@ export function projectData(schema, data, options) {
         options.resolver,
       );
     }
+    // options.factoryArgs
+    if (
+      options.factoryArgs !== undefined &&
+      !Array.isArray(options.factoryArgs)
+    ) {
+      throw new InvalidArgumentError(
+        'Projection option "factoryArgs" must be an Array, ' +
+          'but %v was given.',
+        options.factoryArgs,
+      );
+    }
   }
   // если схема является фабрикой,
   // то извлекается фабричное значение
   if (typeof schema === 'function') {
-    schema = schema();
+    const factoryArgs = (options && options.factoryArgs) || [];
+    schema = schema(...factoryArgs);
     if (
       !schema ||
       (typeof schema !== 'object' && typeof schema !== 'string') ||

+ 39 - 7
src/project-data.spec.js

@@ -3,7 +3,7 @@ import {format} from '@e22m4u/js-format';
 import {projectData} from './project-data.js';
 
 describe('projectData', function () {
-  it('should require the options argument to be an object', function () {
+  it('should require the "options" argument to be an object', function () {
     const throwable = v => () => projectData({}, 10, v);
     const error = s =>
       format('Projection options must be an Object, but %s was given.', s);
@@ -19,7 +19,7 @@ describe('projectData', function () {
     throwable(undefined)();
   });
 
-  it('should require the strict option to be a boolean', function () {
+  it('should require the "strict" option to be a boolean', function () {
     const throwable = v => () => projectData({}, 10, {strict: v});
     const error = s =>
       format(
@@ -38,7 +38,7 @@ describe('projectData', function () {
     throwable(undefined)();
   });
 
-  it('should require the scope option to be a non-empty string', function () {
+  it('should require the "scope" option to be a non-empty string', function () {
     const throwable = v => () => projectData({}, 10, {scope: v});
     const error = s =>
       format(
@@ -58,7 +58,7 @@ describe('projectData', function () {
     throwable(undefined)();
   });
 
-  it('should require the resolver option to be a function', function () {
+  it('should require the "resolver" option to be a function', function () {
     const throwable = v => () => projectData({}, 10, {resolver: v});
     const error = s =>
       format(
@@ -78,7 +78,26 @@ describe('projectData', function () {
     throwable(undefined)();
   });
 
-  it('should project with the schema factory', function () {
+  it('should require the "factoryArgs" option to be an Array', function () {
+    const throwable = v => () => projectData({}, 10, {factoryArgs: v});
+    const error = s =>
+      format(
+        'Projection option "factoryArgs" must be an Array, 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('Object'));
+    expect(throwable(null)).to.throw(error('null'));
+    throwable([1])();
+    throwable([])();
+  });
+
+  it('should resolve a factory value as the schema object', function () {
     let invoked = 0;
     const factory = () => {
       invoked++;
@@ -89,6 +108,19 @@ describe('projectData', function () {
     expect(invoked).to.be.eq(1);
   });
 
+  it('should pass the "factoryArgs" option to the schema factory', function () {
+    let invoked = 0;
+    const factoryArgs = [1, 2, 3];
+    const factory = (...args) => {
+      invoked++;
+      expect(args).to.be.eql(factoryArgs);
+      return {foo: true, bar: false};
+    };
+    const res = projectData(factory, {foo: 10, bar: 20}, {factoryArgs});
+    expect(res).to.be.eql({foo: 10});
+    expect(invoked).to.be.eq(1);
+  });
+
   it('should require a factory value to be an object or a non-empty string', function () {
     const throwable = v => () => projectData(() => v, {});
     const error = s =>
@@ -122,7 +154,7 @@ describe('projectData', function () {
     expect(invoked).to.be.eq(1);
   });
 
-  it('should require the resolver option when the schema name is given', function () {
+  it('should require the "resolver" option when the schema name is provided', function () {
     const throwable = () => projectData('mySchema', {});
     expect(throwable).to.throw(
       'Projection option "resolver" is required to resolve "mySchema" schema.',
@@ -147,7 +179,7 @@ describe('projectData', function () {
     throwable({})();
   });
 
-  it('should resolver the schema name from the factory function', function () {
+  it('should resolve the schema name from the factory function', function () {
     let invoked = 0;
     const resolver = name => {
       invoked++;