Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add output object type inference based upon Schema definition (#697)
This PR enhances the capabilities of MVOM for type inference based on the schema definition. These changes allow, given a schema, to infer the output types of either a `Document` or a `Model` instance. The following is a summary of the changes: ### `Schema` class The `Schema` class has been changed so that there is now a generic `TSchemaDefinition` on the class which will be inferred based on the provided `definition` parameter to the `Schema` constructor. This generic is the basis for all other changes included in this PR. Several new utility types have been added to the `schema.ts` module to allow for output object type inference: The exported `InferDocumentObject` type accepts a `Schema` as a generic and will output a type which aligns with the structure of a `Document` that was instantiated based upon that schema's definition. The exported `InferModelObject` type accepts a `Schema` as a generic and will output a type which aligns with the structure of a `Model` that was instantiated based upon that schema's definition. The primary difference between this type and the `InferDocumentObject` type is that a `Model` instance will include the `_id` and `__v` properties, so this is effectively an extension of the `InferDocumentObject` type. An internal `InferSchemaType` type provides most of the work regarding the inference. It will accept a schema's type definition (that which is assigned to a schema's property value) and recursively process it to determine the output type of a property in the schema's definition. It will handle each scalar type as well as embedded definitions, embedded schemas, scalar arrays, nested scalar arrays, and document arrays. An internal `InferStringType` type provides additional utility for string schema type definitions. Since those type definitions may have an enumeration constraint, this type will check if there is a defined enumeration in the definition. If there is, and it is a readonly array, then the output of the string type will be a union of the enumerated strings. An internal `InferRequiredType` type will detect whether a scalar type is required or not. If it is not required then the resulting output will be unioned with `null`. #### Usage Consumers of MVOM can use the `InferModelObject` and `InferDocumentObject` types to generate a type which will provide the shape of the output for a `Model` or `Document` as follows: ```ts import { Schema } from 'mvom'; import type { InferDocumentObject, InferModelObject } from 'mvom'; const schema = new Schema({ stringProp: { type: 'string', path: '1' } }); type DocumentOutput = InferDocumentObject<typeof schema>; // { stringProp: string | null } type ModelOutput = InferModelObject<typeof schema>; // { _id: string; __v: string; stringProp: string | null } ``` #### Testing of the utility types The `Schema.test.ts` suite was updated to have several new tests to confirm that the utility types emit the expected output type. A new utility type named `Assert` was created which will compare two types and return `true` if they match and an error output if they do not. This `Assert` type was used to compare various schemas to their expected output. This test suite can also provide a lot of insight into the expected output types for various schema definitions. Note: A failed type assertion would not fail any unit tests, but it would trigger typechecking errors. They have the same end result which is that the CI suite would fail. ### compileModel.ts & dynamic `Model` class The compileModel function has been augmented to take advantage of the `InferModelObject` inference. The `schema` provided to the function will have its output inferred. This output is used to form a new `ModelCompositeValue` type which is the intersection of the `Model` instance and the inferred output. The various static and instance methods on the `Model` class which return instances of the `Model` will now be of this composite object. Effectively, those methods will now return a type which has all of the properties strongly typed as defined by the schema. Additionally, when instantiating a new `Model`, the `data` property supplied to the constructor must now comply with the inferred object shape. That is, there is type safety applied to the data that would construct a `Model` instance. ### `Document` class Similar to the changes made to the `Model` class, the `Document` class now outputs a `DocumentCompositeValue` from the static methods that can instantiate a new `Document` instance. This composite type will be the intersection of the `Document` instance and the inferred output object. ### Schema type validator changes Prior to this PR, the validators for a schema type would accept not only the value being validated, but also the `Document` instance they were being validated for. The passing of the `Document` instance was never used by any validators. Because the `Document` is now generic, this complicated the validation as all schema types would have needed to be provided the type of the `Schema` which they were a member of. Since this validation was never used anywhere, it was far simpler to simply remove the document parameter from the validators. ### Query class changes The `Query` class accept a `Model` constructor previous to this PR. However, TypeScript was complaining about this constructor due to the new composite output type of the `Model` methods. Instead, the `Query` class was modified to individually accept as parameters the things it was using from the `Model` constructor -- the connection, schema, and file. The `Model` method were adjusted to provide this information directly instead of providing its own constructor. This should have no impact to anything with the `Query` class as the end result is identical.
- Loading branch information