From dbf0a6541f921cc63c88f807ceda6ed428f896e0 Mon Sep 17 00:00:00 2001 From: Vitaliy Makeev Date: Sun, 4 Jul 2021 19:38:55 +0500 Subject: [PATCH] feat(utils): Patch add type utils folder --- src/model/MetaType.ts | 6 +- src/model/index.ts | 5 +- src/model/{ => utils}/Expand.ts | 4 +- src/model/utils/Patch.ts | 37 +++++++++ src/model/utils/index.ts | 35 ++++++++ test/model/{ => utils}/Expand.test.ts | 2 +- test/model/utils/Patch.test.ts | 111 ++++++++++++++++++++++++++ 7 files changed, 194 insertions(+), 6 deletions(-) rename src/model/{ => utils}/Expand.ts (85%) create mode 100644 src/model/utils/Patch.ts create mode 100644 src/model/utils/index.ts rename test/model/{ => utils}/Expand.test.ts (86%) create mode 100644 test/model/utils/Patch.test.ts diff --git a/src/model/MetaType.ts b/src/model/MetaType.ts index 7fd6b43..6f15413 100644 --- a/src/model/MetaType.ts +++ b/src/model/MetaType.ts @@ -4,6 +4,7 @@ import type { ContactPerson, Counterparty, CustomerOrder, + CustomerOrderPosition, Demand, Employee, Entity, @@ -12,6 +13,7 @@ import type { RetailDemand, State } from '.' +import type { Attribute } from './Attribute' import type { InvoiceIn } from './InvoiceIn' import type { InvoiceOut } from './InvoiceOut' @@ -110,7 +112,7 @@ export enum MetaType { export type EntityByMetaType = { [MetaType.Account]: Account [MetaType.AccumulationDiscount]: Entity - [MetaType.AttributeMetadata]: Entity + [MetaType.AttributeMetadata]: Attribute [MetaType.BonusProgram]: Entity [MetaType.Bundle]: Entity [MetaType.BundleComponent]: Entity @@ -131,7 +133,7 @@ export type EntityByMetaType = { [MetaType.CustomEntity]: Entity [MetaType.CustomEntityMetadata]: Entity [MetaType.CustomerOrder]: CustomerOrder - [MetaType.CustomerOrderPosition]: Entity + [MetaType.CustomerOrderPosition]: CustomerOrderPosition [MetaType.CustomTemplate]: Entity [MetaType.Demand]: Demand [MetaType.DemandPosition]: Entity diff --git a/src/model/index.ts b/src/model/index.ts index 2649b6f..c6aef04 100644 --- a/src/model/index.ts +++ b/src/model/index.ts @@ -20,10 +20,10 @@ export * from './DocumentWithPositions' export * from './Employee' export * from './Entity' export * from './EntityRef' -export * from './Expand' export * from './Group' export * from './HasAttributes' export * from './HasCreated' +export * from './HasDeleted' export * from './HasFiles' export * from './HasProject' export * from './HasRate' @@ -46,5 +46,8 @@ export * from './RetailDemand' export * from './State' export * from './TaxSystem' +// Utility types +export * from './utils' + // Reports export * from './reports' diff --git a/src/model/Expand.ts b/src/model/utils/Expand.ts similarity index 85% rename from src/model/Expand.ts rename to src/model/utils/Expand.ts index 0ca84e6..0e9be80 100644 --- a/src/model/Expand.ts +++ b/src/model/utils/Expand.ts @@ -1,5 +1,5 @@ -import type { EntityRef } from './EntityRef' -import type { EntityByMetaType } from './MetaType' +import type { EntityRef } from '../EntityRef' +import type { EntityByMetaType } from '../MetaType' type ExpandEntityRef = { [P in keyof T]: K extends P diff --git a/src/model/utils/Patch.ts b/src/model/utils/Patch.ts new file mode 100644 index 0000000..3097f8f --- /dev/null +++ b/src/model/utils/Patch.ts @@ -0,0 +1,37 @@ +import type { WritableKeys } from '.' +import type { CollectionRef, EntityByMetaType, MetaType } from '..' +import type { Entity } from '../Entity' +import type { EntityRef } from '../EntityRef' + +// prettier-ignore + +export type Patch< + T extends Entity | Entity[], + U extends MetaType = MetaType +> = + // Patch + T extends Entity + ? Partial< + { + [P in WritableKeys]: + // Коллекция МойСклад (positions) + T[P] extends CollectionRef | undefined + ? (Patch & EntityRef)[] | undefined + + // Массив сущностей (attributes) + : T[P] extends Array> | undefined + ? (Patch & EntityRef)[] | undefined + + // Default + : T[P] + } + > + + // Patch -> + : T extends Array + // -> Entity + ? M extends Entity + ? Array & EntityRef> + : never + + : never diff --git a/src/model/utils/index.ts b/src/model/utils/index.ts new file mode 100644 index 0000000..c6b1f2e --- /dev/null +++ b/src/model/utils/index.ts @@ -0,0 +1,35 @@ +export * from './Expand' +export * from './Patch' + +// https://stackoverflow.com/questions/49579094/typescript-conditional-types-filter-out-readonly-properties-pick-only-requir + +export type IfEquals = (() => T extends X + ? 1 + : 2) extends () => T extends Y ? 1 : 2 + ? A + : B + +export type ReadonlyKeys = { + [P in keyof T]-?: IfEquals< + { [Q in P]: T[P] }, + { -readonly [Q in P]: T[P] }, + never, + P + > +}[keyof T] + +export type WritableKeys = { + [P in keyof T]-?: IfEquals< + { [Q in P]: T[P] }, + { -readonly [Q in P]: T[P] }, + P + > +}[keyof T] + +// https://medium.com/@KevinBGreene/typescript-modeling-required-fields-with-mapped-types-f7bf17688786 +export type RequireKeys = { + [X in Exclude]?: T[X] +} & + { + [P in K]-?: T[P] + } diff --git a/test/model/Expand.test.ts b/test/model/utils/Expand.test.ts similarity index 86% rename from test/model/Expand.test.ts rename to test/model/utils/Expand.test.ts index 051546d..8b39d08 100644 --- a/test/model/Expand.test.ts +++ b/test/model/utils/Expand.test.ts @@ -1,4 +1,4 @@ -import type { CustomerOrder, Expand } from '../../src' +import type { CustomerOrder, Expand } from '../../../src' // Expand EntityRef const test1 = {} as Expand, 'state'> diff --git a/test/model/utils/Patch.test.ts b/test/model/utils/Patch.test.ts new file mode 100644 index 0000000..be8a876 --- /dev/null +++ b/test/model/utils/Patch.test.ts @@ -0,0 +1,111 @@ +import { Attribute, CustomerOrder, MetaType, Patch } from '../../../src' + +//#region Патч объекта +const t1 = {} as Patch + +// @ts-expect-error +t1.id // should omit readonly fields + +// @ts-expect-error +t1.name.toString() // should make fields optional + +t1.name?.toString() + +t1.positions = [ + { + meta: { type: MetaType.CustomerOrderPosition, href: '' }, + price: 12300, + discount: 10 + } +] + +t1.positions = [ + // @ts-expect-error + { + // У элемента коллекции должен быть обязательно указан meta + // meta? + price: 12300, + discount: 10 + } +] + +t1.attributes = [ + { + meta: { + type: MetaType.AttributeMetadata, + href: '' + }, + value: 123 + } +] + +t1.attributes = [ + { + meta: { + type: MetaType.AttributeMetadata, + href: '' + }, + value: '' + } +] + +t1.attributes = [ + // @ts-expect-error + { + value: '' + } +] + +t1.attributes = [ + { + meta: { + type: MetaType.AttributeMetadata, + href: '' + }, + // @ts-expect-error + name: '', + value: '' + } +] +//#endregion + +//#region Патч коллекции +const t4: Patch = [ + { + meta: { + type: MetaType.CustomerOrder, + href: '' + }, + name: 'foo', + attributes: [ + { + meta: { + type: MetaType.AttributeMetadata, + href: '' + } + } + ], + positions: [ + { + meta: { type: MetaType.CustomerOrderPosition, href: '' }, + price: 45, + discount: 10 + } + ] + } +] +//#endregion + +//#region Патч вложенной коллекции МойСклад +const attribute1: Patch = { value: 42 } + +const attribute2: Patch = [ + { + meta: { + href: '', + type: MetaType.AttributeMetadata + }, + value: 42 + } +] +//#endregion