diff --git a/src/model/model.ts b/src/model/model.ts index b3a7910..590bb62 100644 --- a/src/model/model.ts +++ b/src/model/model.ts @@ -1,5 +1,5 @@ import {Definition, UnknownDefinition} from "./property-definition"; -import {ConstructorOf} from "../utils"; +import {ConstructorOf, Modify} from "../utils"; /** * A model shape. @@ -524,6 +524,32 @@ export function defineModel, Ident return new ModelManager(definition); } +/** + * Define a new model, extending an existing one. + * @param extendedModel The extended model manager instance. + * @param definition The extension of the model definition object. + */ +export function extend< + ExtT extends object, ExtShape extends ModelShape, ExtIdentifier extends IdentifierDefinition, + T extends ExtT, Shape extends ModelShape, Identifier extends IdentifierDefinition, + ResIdentifier extends IdentifierDefinition, ResShape extends ModelShape = Modify +>( + extendedModel: ModelManager, + definition: ModelDefinition, +) +{ + const { properties: extendedProperties, ...overridableDefinition } = extendedModel.definition; + const { properties: propertiesExtension, ...definitionExtension } = definition; + return new ModelManager({ + ...overridableDefinition, + ...definitionExtension, + properties: { + ...extendedProperties, + ...propertiesExtension, + }, + }) as unknown as ModelManager; +} + /** * A generic model manager for a provided model type, to use in circular dependencies. */ diff --git a/src/utils.ts b/src/utils.ts index 5f748b7..6120e99 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,3 +2,8 @@ * Type definition of a class constructor. */ export type ConstructorOf = { new(): T; }; + +/** + * Type definition of an original object overridden by another. + */ +export type Modify = Omit & Override; diff --git a/tests/model.test.ts b/tests/model.test.ts index 35eb505..730fc38 100644 --- a/tests/model.test.ts +++ b/tests/model.test.ts @@ -106,6 +106,32 @@ function getTestArticle(): Article } describe("model", () => { + it("defines a new model, extending an existing one", () => { + class ExtendedAccount extends Account + { + static extendedModel = s.extend(Account.model, { + Class: ExtendedAccount, + properties: { + extendedProperty: s.property.string(), + }, + }); + + extendedProperty: string; + } + + expect(ExtendedAccount.extendedModel.definition).toEqual({ + Class: ExtendedAccount, + identifier: "id", + properties: { + id: s.property.numeric(), + createdAt: s.property.date(), + name: s.property.string(), + email: s.property.string(), + active: s.property.boolean(), + extendedProperty: s.property.string(), + }, + }); + }); it("initializes a new model", () => { const article = getTestArticle(); const newModel = Article.model.model(article);