diff --git a/test/types/store/.internals/deep-nested-enumerable/deep-nested-enumerable.entity.ts b/test/types/store/.internals/deep-nested-enumerable/deep-nested-enumerable.entity.ts new file mode 100644 index 00000000..ae29805c --- /dev/null +++ b/test/types/store/.internals/deep-nested-enumerable/deep-nested-enumerable.entity.ts @@ -0,0 +1,3 @@ +export interface IDeepNestedEnumerable { + id: string; +} diff --git a/test/types/store/.internals/deep-nested-enumerable/deep-nested-enumerable.store.ts b/test/types/store/.internals/deep-nested-enumerable/deep-nested-enumerable.store.ts new file mode 100644 index 00000000..c9c01c6b --- /dev/null +++ b/test/types/store/.internals/deep-nested-enumerable/deep-nested-enumerable.store.ts @@ -0,0 +1,6 @@ +import { IDeepNestedEnumerable } from "./deep-nested-enumerable.entity"; +import { Model } from "/types"; + +export const DeepNestedEnumerable: Model = { + id: true, +}; diff --git a/test/types/store/.internals/enumerable/enumerable.entity.ts b/test/types/store/.internals/enumerable/enumerable.entity.ts new file mode 100644 index 00000000..25ab3f3a --- /dev/null +++ b/test/types/store/.internals/enumerable/enumerable.entity.ts @@ -0,0 +1,14 @@ +import { INestedEnumerable } from "../nested-enumerable/nested-enumerable.entity"; +import { ISingleton } from "../singleton/singleton.entity"; + +export interface IEnumerable { + id: string; + stringProperty: string; + numberProperty: number; + self: IEnumerable; + nestedSingleton: ISingleton; + optionalNestedSingleton?: ISingleton; + nestedEnumerable: INestedEnumerable; + optionalNestedEnumerable?: INestedEnumerable; + nestedEnumerables: INestedEnumerable[]; +} diff --git a/test/types/store/.internals/enumerable/enumerable.store.ts b/test/types/store/.internals/enumerable/enumerable.store.ts new file mode 100644 index 00000000..4e2f5220 --- /dev/null +++ b/test/types/store/.internals/enumerable/enumerable.store.ts @@ -0,0 +1,16 @@ +import { NestedEnumerable } from "../nested-enumerable/nested-enumerable.store"; +import { Singleton } from "../singleton/singleton.store"; +import { IEnumerable } from "./enumerable.entity"; +import { ModelDefinition, store } from "/types"; + +export const Enumerable: ModelDefinition = { + id: true, + stringProperty: "", + numberProperty: 0, + self: store.ref(() => Enumerable), + nestedSingleton: store.ref(() => Singleton), + optionalNestedSingleton: store.ref(() => Singleton), + nestedEnumerable: store.ref(() => NestedEnumerable), + optionalNestedEnumerable: store.ref(() => NestedEnumerable), + nestedEnumerables: store.ref(() => [NestedEnumerable]), +}; diff --git a/test/types/store/.internals/nested-enumerable/nested-enumerable.entity.ts b/test/types/store/.internals/nested-enumerable/nested-enumerable.entity.ts new file mode 100644 index 00000000..8720b1e6 --- /dev/null +++ b/test/types/store/.internals/nested-enumerable/nested-enumerable.entity.ts @@ -0,0 +1,13 @@ +import { IDeepNestedEnumerable } from "../deep-nested-enumerable/deep-nested-enumerable.entity"; +import { ISingleton } from "../singleton/singleton.entity"; + +export interface INestedEnumerable { + id: string; + stringProperty: string; + numberProperty: number; + nestedSingleton: ISingleton; + optionalNestedSingleton?: ISingleton; + nestedEnumerable: IDeepNestedEnumerable; + optionalNestedEnumerable?: IDeepNestedEnumerable; + nestedEnumerables: IDeepNestedEnumerable[]; +} diff --git a/test/types/store/.internals/nested-enumerable/nested-enumerable.store.ts b/test/types/store/.internals/nested-enumerable/nested-enumerable.store.ts new file mode 100644 index 00000000..4105528b --- /dev/null +++ b/test/types/store/.internals/nested-enumerable/nested-enumerable.store.ts @@ -0,0 +1,15 @@ +import { DeepNestedEnumerable } from "../deep-nested-enumerable/deep-nested-enumerable.store"; +import { Singleton } from "../singleton/singleton.store"; +import { INestedEnumerable } from "./nested-enumerable.entity"; +import { Model, store } from "/types"; + +export const NestedEnumerable: Model = { + id: true, + stringProperty: "", + numberProperty: 0, + nestedSingleton: store.ref(() => Singleton), + optionalNestedSingleton: store.ref(() => Singleton), + nestedEnumerable: store.ref(() => DeepNestedEnumerable), + optionalNestedEnumerable: store.ref(() => DeepNestedEnumerable), + nestedEnumerables: store.ref(() => [DeepNestedEnumerable]), +}; diff --git a/test/types/store/.internals/singleton/singleton.entity.ts b/test/types/store/.internals/singleton/singleton.entity.ts new file mode 100644 index 00000000..6eb9b323 --- /dev/null +++ b/test/types/store/.internals/singleton/singleton.entity.ts @@ -0,0 +1,12 @@ +import { INestedEnumerable } from "../nested-enumerable/nested-enumerable.entity"; + +export interface ISingleton { + stringProperty: string; + numberProperty: number; + self: ISingleton; + nestedSingleton: ISingleton; + optionalNestedSingleton?: ISingleton; + nestedEnumerable: INestedEnumerable; + optionalNestedEnumerable?: INestedEnumerable; + nestedEnumerables: INestedEnumerable[]; +} diff --git a/test/types/store/.internals/singleton/singleton.store.ts b/test/types/store/.internals/singleton/singleton.store.ts new file mode 100644 index 00000000..e515db2f --- /dev/null +++ b/test/types/store/.internals/singleton/singleton.store.ts @@ -0,0 +1,27 @@ +import { NestedEnumerable } from "../nested-enumerable/nested-enumerable.store"; +import { ISingleton } from "./singleton.entity"; +import { ModelDefinition, store } from "/types"; + +export const Singleton: ModelDefinition = { + stringProperty: "", + numberProperty: 0, + self: store.ref(() => Singleton), + nestedSingleton: store.ref(() => Singleton), + optionalNestedSingleton: store.ref(() => Singleton), + nestedEnumerable: store.ref(() => NestedEnumerable), + optionalNestedEnumerable: store.ref(() => NestedEnumerable), + nestedEnumerables: store.ref(() => [NestedEnumerable]), +}; + +const BrokenSingleton: ModelDefinition = { + /// @ts-expect-error + id: true, + stringProperty: "", + numberProperty: 0, + self: store.ref(() => Singleton), + nestedSingleton: store.ref(() => Singleton), + optionalNestedSingleton: store.ref(() => Singleton), + nestedEnumerable: store.ref(() => NestedEnumerable), + optionalNestedEnumerable: store.ref(() => NestedEnumerable), + nestedEnumerables: store.ref(() => [NestedEnumerable]), +}; diff --git a/test/types/store/descriptor.ts b/test/types/store/descriptor.ts deleted file mode 100644 index cec7356d..00000000 --- a/test/types/store/descriptor.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { Model, define, store } from "/types/index"; - -interface IEnumerableModel { - id: string; - prop: number; -} -interface ISingletonModel { - prop: number; -} - -const EnumerableDefinition: Model = { id: true, prop: 0 }; -const SingletonDefinition: Model = { prop: 0 }; - -interface IAComponent extends HTMLElement { - modelId: undefined | string; - - singleton: ISingletonModel; - singletonDraft: ISingletonModel; - model: undefined | IEnumerableModel; - modelDraft: IEnumerableModel; - - singletonById: ISingletonModel; - singletonDraftById: ISingletonModel; - modelById: undefined | IEnumerableModel; - modelDraftById: IEnumerableModel; - - singletonList: ISingletonModel[]; - singletonDraftList: ISingletonModel[]; - modelList: IEnumerableModel[]; - modelDraftList: IEnumerableModel[]; - - singletonListById: ISingletonModel[]; - singletonDraftListById: ISingletonModel[]; - modelListById: IEnumerableModel[]; - modelDraftListById: IEnumerableModel[]; - - looseSingletonList: ISingletonModel[]; - looseSingletonDraftList: ISingletonModel[]; - looseModelList: IEnumerableModel[]; - looseModelDraftList: IEnumerableModel[]; - - looseSingletonListById: ISingletonModel[]; - looseSingletonDraftListById: ISingletonModel[]; - looseModelListById: IEnumerableModel[]; - looseModelDraftListById: IEnumerableModel[]; -} - -const Component = define({ - tag: "a-component", - modelId: undefined, - - singleton: store(SingletonDefinition), - singletonDraft: store(SingletonDefinition, { draft: true }), - model: store(EnumerableDefinition), - modelDraft: store(EnumerableDefinition, { draft: true }), - - singletonById: store(SingletonDefinition, { id: "modelId" }), - singletonDraftById: store(SingletonDefinition, { - id: "modelId", - draft: true, - }), - modelById: store(EnumerableDefinition, { id: "modelId" }), - modelDraftById: store(EnumerableDefinition, { draft: true, id: "modelId" }), - - /// @ts-expect-error - singletonList: store([SingletonDefinition]), - /// @ts-expect-error - singletonDraftList: store([SingletonDefinition], { draft: true }), - modelList: store([EnumerableDefinition]), - /// @ts-expect-error - modelDraftList: store([EnumerableDefinition], { draft: true }), - - /// @ts-expect-error - singletonListById: store([SingletonDefinition], { id: "modelId" }), - /// @ts-expect-error - singletonDraftListById: store([SingletonDefinition], { - id: "modelId", - draft: true, - }), - modelListById: store([EnumerableDefinition], { id: "modelId" }), - /// @ts-expect-error - modelDraftListById: store([EnumerableDefinition], { - id: "modelId", - draft: true, - }), - - /// @ts-expect-error - looseSingletonList: store([SingletonDefinition], { loose: true }), - /// @ts-expect-error - looseSingletonDraftList: store([SingletonDefinition], { - draft: true, - loose: true, - }), - looseModelList: store([EnumerableDefinition], { loose: true }), - /// @ts-expect-error - looseModelDraftList: store([EnumerableDefinition], { - draft: true, - loose: true, - }), - - /// @ts-expect-error - looseSingletonListById: store([SingletonDefinition], { - id: "modelId", - loose: true, - }), - /// @ts-expect-error - looseSingletonDraftListById: store([SingletonDefinition], { - id: "modelId", - draft: true, - loose: true, - }), - looseModelListById: store([EnumerableDefinition], { - id: "modelId", - loose: true, - }), - /// @ts-expect-error - looseModelDraftListById: store([EnumerableDefinition], { - id: "modelId", - draft: true, - loose: true, - }), -}); diff --git a/test/types/store/direct-methods.ts b/test/types/store/direct-methods.ts deleted file mode 100644 index 5ee13944..00000000 --- a/test/types/store/direct-methods.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { store } from "/types/index"; -import SingletonStore, { ISingleton } from "./singleton-definition.store"; -import EnumerableStore, { IEnumerable } from "./enumerable-definition.store"; - -const SingletonModelPromise: Promise = - store.resolve(SingletonStore); -const SingletonModel = await SingletonModelPromise; - -const EnumerableModelPromise: Promise = - store.resolve(EnumerableStore); -const EnumerableModel = await EnumerableModelPromise; - -const EnumerableModelListPromise: Promise = store.resolve([ - EnumerableStore, -]); -const EnumerableModelList = await EnumerableModelListPromise; - -store.get(SingletonStore); -store.get([SingletonStore]); - -store.get(EnumerableStore); -store.get([EnumerableStore]); - -// ############################################### Create Singleton ############################################### - -store.set(SingletonStore, { - prop: "qweqwe", -}); - -store.set(SingletonStore, { - relatedEnumerable: {}, - relatedSingleton: {}, - relatedEnumerables: [{}, {}], -}); - -store.set(SingletonStore, { - // an error is expected, but I couldn't type it( - relatedEnumerable: [{}, {}], - // an error is expected, but I couldn't type it( - relatedSingleton: [{}, {}], - /// @ts-expect-error - relatedEnumerables: {}, -}); - -// ############################################### Update Singleton ############################################### - -store.set(SingletonModel, { - prop: "qweqwe", -}); - -store.set(SingletonModel, { - relatedEnumerable: {}, - relatedSingleton: {}, - relatedEnumerables: [{}, {}], -}); - -store.set(SingletonModel, { - // an error is expected, but I couldn't type it( - relatedEnumerable: [{}, {}], - // an error is expected, but I couldn't type it( - relatedSingleton: [{}, {}], - /// @ts-expect-error - relatedEnumerables: {}, -}); - -// ############################################### Delete Singleton ############################################### - -// an error is expected, but when determining overload, the display of errors for incorrect values ​​in Model Update breaks -store.set(SingletonModel, null); -store.set(EnumerableModel, null); - -// ############################################### Create Enumerable ############################################### - -store.set(EnumerableStore, { - prop: "qweqwe", -}); - -store.set(EnumerableStore, { - relatedEnumerable: {}, - relatedSingleton: {}, - relatedEnumerables: [{}, {}], -}); - -store.set(EnumerableStore, { - // an error is expected, but I couldn't type it( - relatedEnumerable: [{}, {}], - // an error is expected, but I couldn't type it( - relatedSingleton: [{}, {}], - /// @ts-expect-error - relatedEnumerables: {}, -}); - -// ############################################### Update Enumerable ############################################### - -store.set(EnumerableModel, { - prop: "qweqwe", -}); - -store.set(EnumerableModel, { - relatedEnumerable: {}, - relatedSingleton: {}, - relatedEnumerables: [{}, {}], -}); - -store.set(EnumerableModel, { - // an error is expected, but I couldn't type it( - relatedEnumerable: [{}, {}], - // an error is expected, but I couldn't type it( - relatedSingleton: [{}, {}], - /// @ts-expect-error - relatedEnumerables: {}, -}); - -// ############################################### Delete Enumerable ############################################### -// an error is expected, but when determining overload, the display of errors for incorrect values ​​in Model Update breaks -store.set(EnumerableStore, null); -store.set(EnumerableModel, null); diff --git a/test/types/store/direct-methods/clear.ts b/test/types/store/direct-methods/clear.ts new file mode 100644 index 00000000..923757d4 --- /dev/null +++ b/test/types/store/direct-methods/clear.ts @@ -0,0 +1,22 @@ +import { Enumerable } from "../.internals/enumerable/enumerable.store"; +import { Singleton } from "../.internals/singleton/singleton.store"; +import { store } from "/types"; + +const singleton = store.get(Singleton); +const enumerable = store.sync(Enumerable, { id: "1" }); + +// Overload via Definition +{ + store.clear(Singleton); + store.clear(Enumerable); + store.clear(Singleton, true); + store.clear(Enumerable, true); +} + +// Overload via instance +{ + store.clear(singleton); + store.clear(enumerable); + store.clear(singleton, true); + store.clear(enumerable, true); +} diff --git a/test/types/store/direct-methods/get.ts b/test/types/store/direct-methods/get.ts new file mode 100644 index 00000000..7988479f --- /dev/null +++ b/test/types/store/direct-methods/get.ts @@ -0,0 +1,41 @@ +import { IEnumerable } from "../.internals/enumerable/enumerable.entity"; +import { Enumerable } from "../.internals/enumerable/enumerable.store"; +import { ISingleton } from "../.internals/singleton/singleton.entity"; +import { Singleton } from "../.internals/singleton/singleton.store"; +import { store } from "/types"; + +let singletonInput: ISingleton; +let singletonListInput: ISingleton[]; +let enumerableInput: IEnumerable; +let enumerableListInput: IEnumerable[]; + +// Overload via Definition +{ + // ...read singleton + singletonInput = store.get(Singleton); + // ...read unidentified model + enumerableInput = store.get(Enumerable); // looks strange + + // read identified model + singletonInput = store.get(Singleton, "1"); // looks strange + enumerableInput = store.get(Enumerable, "1"); + + // read queried model + singletonInput = store.get(Singleton, { a: 1, b: 2 }); // looks strange + enumerableInput = store.get(Enumerable, { a: 1, b: 2 }); +} + +// Overload via [Definition] +{ + // ...read unidentified list + singletonListInput = store.get([Singleton]); // looks strange + enumerableListInput = store.get([Enumerable]); + + // ...read identified list + singletonListInput = store.get([Singleton], "1"); // looks strange + enumerableListInput = store.get([Enumerable], "1"); + + // ...read queried list + singletonListInput = store.get([Singleton], { a: 1, b: 2 }); // looks strange + enumerableListInput = store.get([Enumerable], { a: 1, b: 2 }); +} diff --git a/test/types/store/direct-methods/resolve.ts b/test/types/store/direct-methods/resolve.ts new file mode 100644 index 00000000..93107a26 --- /dev/null +++ b/test/types/store/direct-methods/resolve.ts @@ -0,0 +1,50 @@ +import { Enumerable } from "../.internals/enumerable/enumerable.store"; +import { IEnumerable } from "../.internals/enumerable/enumerable.entity"; +import { Singleton } from "../.internals/singleton/singleton.store"; +import { ISingleton } from "../.internals/singleton/singleton.entity"; +import { store } from "/types"; + +const singleton = store.get(Singleton); +const enumerable = store.sync(Enumerable, { id: "1" }); + +let singletonPromiseInput: Promise; +let enumerablePromiseInput: Promise; +let singletonListPromiseInput: Promise; +let enumerableListPromiseInput: Promise; + +// Overload via Definition +{ + // ...read singleton + singletonPromiseInput = store.resolve(Singleton); + // ...read unidentified model + enumerablePromiseInput = store.resolve(Enumerable); // looks strange + + // read identified model + singletonPromiseInput = store.resolve(Singleton, "1"); // looks strange + enumerablePromiseInput = store.resolve(Enumerable, "1"); + + // read queried model + singletonPromiseInput = store.resolve(Singleton, { a: 1, b: 2 }); // looks strange + enumerablePromiseInput = store.resolve(Enumerable, { a: 1, b: 2 }); +} + +// Overload via [Definition] +{ + // ...read unidentified list + singletonListPromiseInput = store.resolve([Singleton]); // looks strange + enumerableListPromiseInput = store.resolve([Enumerable]); + + // ...read identified list + singletonListPromiseInput = store.resolve([Singleton], "1"); // looks strange + enumerableListPromiseInput = store.resolve([Enumerable], "1"); + + // ...read queried list + singletonListPromiseInput = store.resolve([Singleton], { a: 1, b: 2 }); // looks strange + enumerableListPromiseInput = store.resolve([Enumerable], { a: 1, b: 2 }); +} + +// Overload via instance +{ + singletonPromiseInput = store.resolve(singleton); + enumerablePromiseInput = store.resolve(enumerable); +} diff --git a/test/types/store/direct-methods/set-and-sync.ts b/test/types/store/direct-methods/set-and-sync.ts new file mode 100644 index 00000000..0453e712 --- /dev/null +++ b/test/types/store/direct-methods/set-and-sync.ts @@ -0,0 +1,756 @@ +import { NestedEnumerable } from "../.internals/nested-enumerable/nested-enumerable.store"; +import { Enumerable } from "../.internals/enumerable/enumerable.store"; +import { Singleton } from "../.internals/singleton/singleton.store"; +import { store } from "/types"; +import { IEnumerable } from "../.internals/enumerable/enumerable.entity"; +import { ISingleton } from "../.internals/singleton/singleton.entity"; + +const nestedEnumerable = store.sync(NestedEnumerable, { id: "1" }); +const enumerable = store.sync(Enumerable, { id: "1" }); +const singleton = store.get(Singleton); + +let enumerablePromiseInput: Promise; +let singletonPromiseInput: Promise; +let enumerableInput: IEnumerable; +let singletonInput: ISingleton; + +const primitives = { + stringProperty: "qweqwe", + numberProperty: 123456789, +}; +const primitivesInCreateNestedModels = { + nestedEnumerable: primitives, + optionalNestedEnumerable: primitives, + nestedSingleton: primitives, + optionalNestedSingleton: primitives, + nestedEnumerables: [primitives, primitives], +}; +const primitivesInUpdateNestedModels = { + nestedEnumerable: { id: "1", ...primitives }, + optionalNestedEnumerable: { id: "1", ...primitives }, + nestedSingleton: { ...primitives }, + optionalNestedSingleton: { ...primitives }, + nestedEnumerables: [ + { id: "1", ...primitives }, + { id: "1", ...primitives }, + ], +}; + +const empty = {}; +const emptyInCreateNestedModels = { + nestedEnumerable: empty, + optionalNestedEnumerable: empty, + nestedSingleton: empty, + optionalNestedSingleton: empty, + nestedEnumerables: [empty, empty], +}; +const emptyInUpdateNestedModels = { + nestedEnumerable: { id: "1", ...empty }, + optionalNestedEnumerable: { id: "1", ...empty }, + nestedSingleton: { ...empty }, + optionalNestedSingleton: { ...empty }, + nestedEnumerables: [ + { id: "1", ...empty }, + { id: "1", ...empty }, + ], +}; + +const refs = { + nestedSingleton: singleton, + optionalNestedSingleton: singleton, + nestedEnumerable: nestedEnumerable, + optionalNestedEnumerable: nestedEnumerable, + nestedEnumerables: [nestedEnumerable, nestedEnumerable], +}; +const refsInCreateNestedModels = { + nestedEnumerable: refs, + optionalNestedEnumerable: refs, + nestedSingleton: refs, + optionalNestedSingleton: refs, + nestedEnumerables: [refs, refs], +}; +const refsInUpdateNestedModels = { + nestedEnumerable: { id: "1", ...refs }, + optionalNestedEnumerable: { id: "1", ...refs }, + nestedSingleton: { ...refs }, + optionalNestedSingleton: { ...refs }, + nestedEnumerables: [ + { id: "1", ...refs }, + { id: "1", ...refs }, + ], +}; + +const identifiers = { + nestedSingleton: "1", + optionalNestedSingleton: "1", + nestedEnumerable: "1", + optionalNestedEnumerable: "1", + nestedEnumerables: ["1", "2"], +}; +const identifiersInCreateNestedModels = { + nestedEnumerable: identifiers, + optionalNestedEnumerable: identifiers, + nestedSingleton: identifiers, + optionalNestedSingleton: identifiers, + nestedEnumerables: [identifiers, identifiers], +}; +const identifiersInUpdateNestedModels = { + nestedEnumerable: { id: "1", ...identifiers }, + optionalNestedEnumerable: { id: "1", ...identifiers }, + nestedSingleton: { ...identifiers }, + optionalNestedSingleton: { ...identifiers }, + nestedEnumerables: [ + { id: "1", ...identifiers }, + { id: "1", ...identifiers }, + ], +}; + +const unrefs = { + nestedSingleton: undefined, + optionalNestedSingleton: undefined, + nestedEnumerable: undefined, + optionalNestedEnumerable: undefined, + nestedEnumerables: [], +}; +const unrefsInCreateNestedModels = { + nestedEnumerable: unrefs, + optionalNestedEnumerable: unrefs, + nestedSingleton: unrefs, + optionalNestedSingleton: unrefs, + nestedEnumerables: [unrefs, unrefs], +}; +const unrefsInUpdateNestedModels = { + nestedEnumerable: { id: "1", ...unrefs }, + optionalNestedEnumerable: { id: "1", ...unrefs }, + nestedSingleton: { ...unrefs }, + optionalNestedSingleton: { ...unrefs }, + nestedEnumerables: [ + { id: "1", ...unrefs }, + { id: "1", ...unrefs }, + ], +}; + +const collisions = { + nestedSingleton: [{}], + optionalNestedSingleton: [{}], + nestedEnumerable: [{}], + optionalNestedEnumerable: [{}], + nestedEnumerables: {}, +}; +const collisionsInCreateNestedModels = { + nestedEnumerable: collisions, + optionalNestedEnumerable: collisions, + nestedSingleton: collisions, + optionalNestedSingleton: collisions, + nestedEnumerables: [collisions, collisions], +}; +const collisionsInUpdateNestedModels = { + nestedEnumerable: { id: "1", ...collisions }, + optionalNestedEnumerable: { id: "1", ...collisions }, + nestedSingleton: { ...collisions }, + optionalNestedSingleton: { ...collisions }, + nestedEnumerables: [ + { id: "1", ...collisions }, + { id: "1", ...collisions }, + ], +}; + +// Overload via Definition +{ + // Create... + { + // Create model via Definition + { + // ...set primitive propertiers + enumerablePromiseInput = store.set(Enumerable, primitives); + enumerableInput = store.sync(Enumerable, primitives); + + // ...use default values + enumerablePromiseInput = store.set(Enumerable, empty); + enumerableInput = store.sync(Enumerable, empty); + + // ...binding through refs + enumerablePromiseInput = store.set(Enumerable, refs); + enumerableInput = store.sync(Enumerable, refs); + + // ...binding through identifiers + enumerablePromiseInput = store.set(Enumerable, identifiers); + enumerableInput = store.sync(Enumerable, identifiers); + + // ...unbinding + enumerablePromiseInput = store.set(Enumerable, unrefs); + enumerableInput = store.sync(Enumerable, unrefs); + + // ...check collisions + /// @ts-expect-error + store.set(Enumerable, collisions); + /// @ts-expect-error + store.sync(Enumerable, collisions); + } + + // Create model & nested via Definition + { + // ...set primitive propertiers + enumerablePromiseInput = store.set( + Enumerable, + primitivesInCreateNestedModels, + ); + enumerableInput = store.sync(Enumerable, primitivesInCreateNestedModels); + + // ...use default values + enumerablePromiseInput = store.set(Enumerable, emptyInCreateNestedModels); + enumerableInput = store.sync(Enumerable, emptyInCreateNestedModels); + + // ...binding through refs + enumerablePromiseInput = store.set(Enumerable, refsInCreateNestedModels); + enumerableInput = store.sync(Enumerable, refsInCreateNestedModels); + + // ...binding through identifiers + enumerablePromiseInput = store.set( + Enumerable, + identifiersInCreateNestedModels, + ); + enumerableInput = store.sync(Enumerable, identifiersInCreateNestedModels); + + // ...unbinding + enumerablePromiseInput = store.set( + Enumerable, + unrefsInCreateNestedModels, + ); + enumerableInput = store.sync(Enumerable, unrefsInCreateNestedModels); + + // ...check collisions + /// @ts-expect-error + enumerablePromiseInput = store.set( + Enumerable, + collisionsInCreateNestedModels, + ); + /// @ts-expect-error + enumerableInput = store.sync(Enumerable, collisionsInCreateNestedModels); + } + } + + // Update... + { + // Update model via Definition + { + // ...set primitive propertiers + singletonPromiseInput = store.set(Singleton, primitives); + singletonInput = store.sync(Singleton, primitives); + enumerablePromiseInput = store.set(Enumerable, { + id: "1", + ...primitives, + }); + enumerableInput = store.sync(Enumerable, { id: "1", ...primitives }); + + // ...use default values + singletonPromiseInput = store.set(Singleton, empty); + singletonInput = store.sync(Singleton, empty); + enumerablePromiseInput = store.set(Enumerable, { id: "1", ...empty }); + enumerableInput = store.sync(Enumerable, { id: "1", ...empty }); + + // ...binding through refs + singletonPromiseInput = store.set(Singleton, refs); + singletonInput = store.sync(Singleton, refs); + enumerablePromiseInput = store.set(Enumerable, { id: "1", ...refs }); + enumerableInput = store.sync(Enumerable, { id: "1", ...refs }); + + // ...binding through identifiers + singletonPromiseInput = store.set(Singleton, identifiers); + singletonInput = store.sync(Singleton, identifiers); + enumerablePromiseInput = store.set(Enumerable, { + id: "1", + ...identifiers, + }); + enumerableInput = store.sync(Enumerable, { id: "1", ...identifiers }); + + // ...unbinding + singletonPromiseInput = store.set(Singleton, unrefs); + singletonInput = store.sync(Singleton, unrefs); + enumerablePromiseInput = store.set(Enumerable, { id: "1", ...unrefs }); + enumerableInput = store.sync(Enumerable, { id: "1", ...unrefs }); + + // ...check collisions + /// @ts-expect-error + singletonPromiseInput = store.set(Singleton, collisions); + /// @ts-expect-error + singletonInput = store.sync(Singleton, collisions); + /// @ts-expect-error + enumerablePromiseInput = store.set(Enumerable, { + id: "1", + ...collisions, + }); + /// @ts-expect-error + enumerableInput = store.sync(Enumerable, { id: "1", ...collisions }); + } + + // Update model & nested via Definition + { + // ...set primitive propertiers + singletonPromiseInput = store.set( + Singleton, + primitivesInUpdateNestedModels, + ); + singletonInput = store.sync(Singleton, primitivesInUpdateNestedModels); + enumerablePromiseInput = store.set(Enumerable, { + id: "1", + ...primitivesInUpdateNestedModels, + }); + enumerableInput = store.sync(Enumerable, { + id: "1", + ...primitivesInUpdateNestedModels, + }); + + // ...use default values + singletonPromiseInput = store.set(Singleton, emptyInUpdateNestedModels); + singletonInput = store.sync(Singleton, emptyInUpdateNestedModels); + enumerablePromiseInput = store.set(Enumerable, { + id: "1", + ...emptyInUpdateNestedModels, + }); + enumerableInput = store.sync(Enumerable, { + id: "1", + ...emptyInUpdateNestedModels, + }); + + // ...binding through refs + singletonPromiseInput = store.set(Singleton, refsInUpdateNestedModels); + singletonInput = store.sync(Singleton, refsInUpdateNestedModels); + enumerablePromiseInput = store.set(Enumerable, { + id: "1", + ...refsInUpdateNestedModels, + }); + enumerableInput = store.sync(Enumerable, { + id: "1", + ...refsInUpdateNestedModels, + }); + + // ...binding through identifiers + singletonPromiseInput = store.set( + Singleton, + identifiersInUpdateNestedModels, + ); + singletonInput = store.sync(Singleton, identifiersInUpdateNestedModels); + enumerablePromiseInput = store.set(Enumerable, { + id: "1", + ...identifiersInUpdateNestedModels, + }); + enumerableInput = store.sync(Enumerable, { + id: "1", + ...identifiersInUpdateNestedModels, + }); + + // ...unbinding + singletonPromiseInput = store.set(Singleton, unrefsInUpdateNestedModels); + singletonInput = store.sync(Singleton, unrefsInUpdateNestedModels); + enumerablePromiseInput = store.set(Enumerable, { + id: "1", + ...unrefsInUpdateNestedModels, + }); + enumerableInput = store.sync(Enumerable, { + id: "1", + ...unrefsInUpdateNestedModels, + }); + + // ...check collisions + /// @ts-expect-error + singletonPromiseInput = store.set( + Singleton, + collisionsInUpdateNestedModels, + ); + /// @ts-expect-error + singletonInput = store.sync(Singleton, collisionsInUpdateNestedModels); + /// @ts-expect-error + enumerablePromiseInput = store.set(Enumerable, { + id: "1", + ...collisionsInUpdateNestedModels, + }); + /// @ts-expect-error + enumerableInput = store.sync(Enumerable, { + id: "1", + ...collisionsInUpdateNestedModels, + }); + } + } + + // Delete... + { + // Delete model via Definition + + /// @ts-expect-error + enumerablePromiseInput = store.set(Enumerable, null); + /// @ts-expect-error + enumerableInput = store.sync(Enumerable, null); + + singletonPromiseInput = store.set(Singleton, null); + singletonInput = store.sync(Singleton, null); + } + + // Mixed... + { + // Create model and Update nested via Definition + { + // ...set primitive propertiers + enumerablePromiseInput = store.set( + Enumerable, + primitivesInUpdateNestedModels, + ); + enumerableInput = store.sync(Enumerable, primitivesInUpdateNestedModels); + + // ...use default values + enumerablePromiseInput = store.set(Enumerable, emptyInUpdateNestedModels); + enumerableInput = store.sync(Enumerable, emptyInUpdateNestedModels); + + // ...binding through refs + enumerablePromiseInput = store.set(Enumerable, refsInUpdateNestedModels); + enumerableInput = store.sync(Enumerable, refsInUpdateNestedModels); + + // ...binding through identifiers + enumerablePromiseInput = store.set( + Enumerable, + identifiersInUpdateNestedModels, + ); + enumerableInput = store.sync(Enumerable, identifiersInUpdateNestedModels); + + // ...unbinding + enumerablePromiseInput = store.set( + Enumerable, + unrefsInUpdateNestedModels, + ); + enumerableInput = store.sync(Enumerable, unrefsInUpdateNestedModels); + + // ...check collisions + /// @ts-expect-error + enumerablePromiseInput = store.set( + Enumerable, + collisionsInUpdateNestedModels, + ); + /// @ts-expect-error + enumerableInput = store.sync(Enumerable, collisionsInUpdateNestedModels); + } + + // Update model and Create nested via Definition + { + // ...set primitive propertiers + singletonPromiseInput = store.set( + Singleton, + primitivesInCreateNestedModels, + ); + singletonInput = store.sync(Singleton, primitivesInCreateNestedModels); + enumerablePromiseInput = store.set(Enumerable, { + id: "1", + ...primitivesInCreateNestedModels, + }); + enumerableInput = store.sync(Enumerable, { + id: "1", + ...primitivesInCreateNestedModels, + }); + + // ...use default values + singletonPromiseInput = store.set(Singleton, emptyInCreateNestedModels); + singletonInput = store.sync(Singleton, emptyInCreateNestedModels); + enumerablePromiseInput = store.set(Enumerable, { + id: "1", + ...emptyInCreateNestedModels, + }); + enumerableInput = store.sync(Enumerable, { + id: "1", + ...emptyInCreateNestedModels, + }); + + // ...binding through refs + singletonPromiseInput = store.set(Singleton, refsInCreateNestedModels); + singletonInput = store.sync(Singleton, refsInCreateNestedModels); + enumerablePromiseInput = store.set(Enumerable, { + id: "1", + ...refsInCreateNestedModels, + }); + enumerableInput = store.sync(Enumerable, { + id: "1", + ...refsInCreateNestedModels, + }); + + // ...binding through identifiers + singletonPromiseInput = store.set( + Singleton, + identifiersInCreateNestedModels, + ); + singletonInput = store.sync(Singleton, identifiersInCreateNestedModels); + enumerablePromiseInput = store.set(Enumerable, { + id: "1", + ...identifiersInCreateNestedModels, + }); + enumerableInput = store.sync(Enumerable, { + id: "1", + ...identifiersInCreateNestedModels, + }); + + // ...unbinding + singletonPromiseInput = store.set(Singleton, unrefsInCreateNestedModels); + singletonInput = store.sync(Singleton, unrefsInCreateNestedModels); + enumerablePromiseInput = store.set(Enumerable, { + id: "1", + ...unrefsInCreateNestedModels, + }); + enumerableInput = store.sync(Enumerable, { + id: "1", + ...unrefsInCreateNestedModels, + }); + + // ...check collisions + /// @ts-expect-error + singletonPromiseInput = store.set( + Singleton, + collisionsInCreateNestedModels, + ); + /// @ts-expect-error + singletonInput = store.sync(Singleton, collisionsInCreateNestedModels); + /// @ts-expect-error + enumerablePromiseInput = store.set(Enumerable, { + id: "1", + ...collisionsInCreateNestedModels, + }); + /// @ts-expect-error + enumerableInput = store.sync(Enumerable, { + id: "1", + ...collisionsInCreateNestedModels, + }); + } + } +} + +// Overload via instance +{ + // Update model via instance + { + // ...set primitive propertiers + singletonPromiseInput = store.set(singleton, primitives); + singletonInput = store.sync(singleton, primitives); + enumerablePromiseInput = store.set(enumerable, { id: "1", ...primitives }); + enumerableInput = store.sync(enumerable, { id: "1", ...primitives }); + + // ...use default values + singletonPromiseInput = store.set(singleton, empty); + singletonInput = store.sync(singleton, empty); + enumerablePromiseInput = store.set(enumerable, { id: "1", ...empty }); + enumerableInput = store.sync(enumerable, { id: "1", ...empty }); + + // ...binding through refs + singletonPromiseInput = store.set(singleton, refs); + singletonInput = store.sync(singleton, refs); + enumerablePromiseInput = store.set(enumerable, { id: "1", ...refs }); + enumerableInput = store.sync(enumerable, { id: "1", ...refs }); + + // ...binding through identifiers + singletonPromiseInput = store.set(singleton, identifiers); + singletonInput = store.sync(singleton, identifiers); + enumerablePromiseInput = store.set(enumerable, { id: "1", ...identifiers }); + enumerableInput = store.sync(enumerable, { id: "1", ...identifiers }); + + // ...unbinding + singletonPromiseInput = store.set(singleton, unrefs); + singletonInput = store.sync(singleton, unrefs); + enumerablePromiseInput = store.set(enumerable, { id: "1", ...unrefs }); + enumerableInput = store.sync(enumerable, { id: "1", ...unrefs }); + + // ...check collisions + /// @ts-expect-error + singletonPromiseInput = store.set(singleton, collisions); + /// @ts-expect-error + singletonInput = store.sync(singleton, collisions); + /// @ts-expect-error + enumerablePromiseInput = store.set(enumerable, { id: "1", ...collisions }); + /// @ts-expect-error + enumerableInput = store.sync(enumerable, { id: "1", ...collisions }); + } + + // Update model & nested via instance + { + // ...set primitive propertiers + singletonPromiseInput = store.set( + singleton, + primitivesInUpdateNestedModels, + ); + singletonInput = store.sync(singleton, primitivesInUpdateNestedModels); + enumerablePromiseInput = store.set(enumerable, { + id: "1", + ...primitivesInUpdateNestedModels, + }); + enumerableInput = store.sync(enumerable, { + id: "1", + ...primitivesInUpdateNestedModels, + }); + + // ...use default values + singletonPromiseInput = store.set(singleton, emptyInUpdateNestedModels); + singletonInput = store.sync(singleton, emptyInUpdateNestedModels); + enumerablePromiseInput = store.set(enumerable, { + id: "1", + ...emptyInUpdateNestedModels, + }); + enumerableInput = store.sync(enumerable, { + id: "1", + ...emptyInUpdateNestedModels, + }); + + // ...binding through refs + singletonPromiseInput = store.set(singleton, refsInUpdateNestedModels); + singletonInput = store.sync(singleton, refsInUpdateNestedModels); + enumerablePromiseInput = store.set(enumerable, { + id: "1", + ...refsInUpdateNestedModels, + }); + enumerableInput = store.sync(enumerable, { + id: "1", + ...refsInUpdateNestedModels, + }); + + // ...binding through identifiers + singletonPromiseInput = store.set( + singleton, + identifiersInUpdateNestedModels, + ); + singletonInput = store.sync(singleton, identifiersInUpdateNestedModels); + enumerablePromiseInput = store.set(enumerable, { + id: "1", + ...identifiersInUpdateNestedModels, + }); + enumerableInput = store.sync(enumerable, { + id: "1", + ...identifiersInUpdateNestedModels, + }); + + // ...unbinding + singletonPromiseInput = store.set(singleton, unrefsInUpdateNestedModels); + singletonInput = store.sync(singleton, unrefsInUpdateNestedModels); + enumerablePromiseInput = store.set(enumerable, { + id: "1", + ...unrefsInUpdateNestedModels, + }); + enumerableInput = store.sync(enumerable, { + id: "1", + ...unrefsInUpdateNestedModels, + }); + + // ...check collisions + /// @ts-expect-error + singletonPromiseInput = store.set( + singleton, + collisionsInUpdateNestedModels, + ); + /// @ts-expect-error + singletonInput = store.sync(singleton, collisionsInUpdateNestedModels); + /// @ts-expect-error + enumerablePromiseInput = store.set(enumerable, { + id: "1", + ...collisionsInUpdateNestedModels, + }); + /// @ts-expect-error + enumerableInput = store.sync(enumerable, { + id: "1", + ...collisionsInUpdateNestedModels, + }); + } + + // Mixed... + { + // Update model and Create nested via Definition + { + // ...set primitive propertiers + singletonPromiseInput = store.set( + singleton, + primitivesInCreateNestedModels, + ); + singletonInput = store.sync(singleton, primitivesInCreateNestedModels); + enumerablePromiseInput = store.set(enumerable, { + id: "1", + ...primitivesInCreateNestedModels, + }); + enumerableInput = store.sync(enumerable, { + id: "1", + ...primitivesInCreateNestedModels, + }); + + // ...use default values + singletonPromiseInput = store.set(singleton, emptyInCreateNestedModels); + singletonInput = store.sync(singleton, emptyInCreateNestedModels); + enumerablePromiseInput = store.set(enumerable, { + id: "1", + ...emptyInCreateNestedModels, + }); + enumerableInput = store.sync(enumerable, { + id: "1", + ...emptyInCreateNestedModels, + }); + + // ...binding through refs + singletonPromiseInput = store.set(singleton, refsInCreateNestedModels); + singletonInput = store.sync(singleton, refsInCreateNestedModels); + enumerablePromiseInput = store.set(enumerable, { + id: "1", + ...refsInCreateNestedModels, + }); + enumerableInput = store.sync(enumerable, { + id: "1", + ...refsInCreateNestedModels, + }); + + // ...binding through identifiers + singletonPromiseInput = store.set( + singleton, + identifiersInCreateNestedModels, + ); + singletonInput = store.sync(singleton, identifiersInCreateNestedModels); + enumerablePromiseInput = store.set(enumerable, { + id: "1", + ...identifiersInCreateNestedModels, + }); + enumerableInput = store.sync(enumerable, { + id: "1", + ...identifiersInCreateNestedModels, + }); + + // ...unbinding + singletonPromiseInput = store.set(singleton, unrefsInCreateNestedModels); + singletonInput = store.sync(singleton, unrefsInCreateNestedModels); + enumerablePromiseInput = store.set(enumerable, { + id: "1", + ...unrefsInCreateNestedModels, + }); + enumerableInput = store.sync(enumerable, { + id: "1", + ...unrefsInCreateNestedModels, + }); + + // ...check collisions + /// @ts-expect-error + singletonPromiseInput = store.set( + singleton, + collisionsInCreateNestedModels, + ); + /// @ts-expect-error + singletonInput = store.sync(singleton, collisionsInCreateNestedModels); + /// @ts-expect-error + enumerablePromiseInput = store.set(enumerable, { + id: "1", + ...collisionsInCreateNestedModels, + }); + /// @ts-expect-error + enumerableInput = store.sync(enumerable, { + id: "1", + ...collisionsInCreateNestedModels, + }); + } + } +} + +// Delete... +{ + // delete model via instance + { + enumerablePromiseInput = store.set(enumerable, null); + enumerableInput = store.sync(enumerable, null); + singletonPromiseInput = store.set(singleton, null); // is may be cleaning of the model values or draft values + singletonInput = store.sync(singleton, null); // is may be cleaning of the model values or draft values + } +} diff --git a/test/types/store/enumerable-definition.store.ts b/test/types/store/enumerable-definition.store.ts deleted file mode 100644 index 68aecfab..00000000 --- a/test/types/store/enumerable-definition.store.ts +++ /dev/null @@ -1,31 +0,0 @@ -import SingletonStore, { ISingleton } from "./singleton-definition.store"; -import { Model, store } from "/types/index"; - -export interface IEnumerable { - id: string; - prop: string; - length: number; - relatedSingleton?: ISingleton; - relatedEnumerable?: IEnumerable; - relatedEnumerables: IEnumerable[]; -} - -const EnumerableStore: Model = { - id: true, - prop: "", - length: 0, - relatedSingleton: store.ref(() => SingletonStore), - relatedEnumerable: store.ref(() => EnumerableStore), - relatedEnumerables: store.ref(() => [EnumerableStore]), -}; - -export default EnumerableStore; - -/// @ts-expect-error -const BrokenEnumerableStore: Model = { - prop: "", - length: 0, - relatedSingleton: store.ref(() => SingletonStore), - relatedEnumerable: store.ref(() => EnumerableStore), - relatedEnumerables: store.ref(() => [EnumerableStore]), -}; diff --git a/test/types/store/factory/factory.ts b/test/types/store/factory/factory.ts new file mode 100644 index 00000000..34b32b57 --- /dev/null +++ b/test/types/store/factory/factory.ts @@ -0,0 +1,115 @@ +import { Enumerable } from "../.internals/enumerable/enumerable.store"; +import { IEnumerable } from "../.internals/enumerable/enumerable.entity"; +import { Singleton } from "../.internals/singleton/singleton.store"; +import { ISingleton } from "../.internals/singleton/singleton.entity"; +import { define, store } from "/types/index"; + +interface IComponent extends HTMLElement { + modelId: undefined | string; + + singleton: ISingleton; + singletonDraft: ISingleton; + model: undefined | IEnumerable; + modelDraft: IEnumerable; + + singletonById: ISingleton; + singletonDraftById: ISingleton; + modelById: undefined | IEnumerable; + modelDraftById: IEnumerable; + + singletonList: ISingleton[]; + singletonDraftList: ISingleton[]; + modelList: IEnumerable[]; + modelDraftList: IEnumerable[]; + + singletonListById: ISingleton[]; + singletonDraftListById: ISingleton[]; + modelListById: IEnumerable[]; + modelDraftListById: IEnumerable[]; + + looseSingletonList: ISingleton[]; + looseSingletonDraftList: ISingleton[]; + looseModelList: IEnumerable[]; + looseModelDraftList: IEnumerable[]; + + looseSingletonListById: ISingleton[]; + looseSingletonDraftListById: ISingleton[]; + looseModelListById: IEnumerable[]; + looseModelDraftListById: IEnumerable[]; +} + +const Component = define({ + tag: "a-component", + modelId: undefined, + + singleton: store(Singleton), + singletonDraft: store(Singleton, { draft: true }), + model: store(Enumerable), + modelDraft: store(Enumerable, { draft: true }), + + singletonById: store(Singleton, { id: "modelId" }), + singletonDraftById: store(Singleton, { + id: "modelId", + draft: true, + }), + modelById: store(Enumerable, { id: "modelId" }), + modelDraftById: store(Enumerable, { draft: true, id: "modelId" }), + + /// @ts-expect-error + singletonList: store([Singleton]), + /// @ts-expect-error + singletonDraftList: store([Singleton], { draft: true }), + modelList: store([Enumerable]), + /// @ts-expect-error + modelDraftList: store([Enumerable], { draft: true }), + + /// @ts-expect-error + singletonListById: store([Singleton], { id: "modelId" }), + /// @ts-expect-error + singletonDraftListById: store([Singleton], { + id: "modelId", + draft: true, + }), + modelListById: store([Enumerable], { id: "modelId" }), + /// @ts-expect-error + modelDraftListById: store([Enumerable], { + id: "modelId", + draft: true, + }), + + /// @ts-expect-error + looseSingletonList: store([Singleton], { loose: true }), + /// @ts-expect-error + looseSingletonDraftList: store([Singleton], { + draft: true, + loose: true, + }), + looseModelList: store([Enumerable], { loose: true }), + /// @ts-expect-error + looseModelDraftList: store([Enumerable], { + draft: true, + loose: true, + }), + + /// @ts-expect-error + looseSingletonListById: store([Singleton], { + id: "modelId", + loose: true, + }), + /// @ts-expect-error + looseSingletonDraftListById: store([Singleton], { + id: "modelId", + draft: true, + loose: true, + }), + looseModelListById: store([Enumerable], { + id: "modelId", + loose: true, + }), + /// @ts-expect-error + looseModelDraftListById: store([Enumerable], { + id: "modelId", + draft: true, + loose: true, + }), +}); diff --git a/test/types/store/model-definition.ts b/test/types/store/model-definition.ts index 5e6e1498..56622de5 100644 --- a/test/types/store/model-definition.ts +++ b/test/types/store/model-definition.ts @@ -1,402 +1,479 @@ -import { Model } from "/types/index"; -import SingletonStore, { ISingleton } from "./singleton-definition.store"; -import EnumerableStore, { IEnumerable } from "./enumerable-definition.store"; +import { IEnumerable } from "./.internals/enumerable/enumerable.entity"; +import { Enumerable } from "./.internals/enumerable/enumerable.store"; +import { ISingleton } from "./.internals/singleton/singleton.entity"; +import { Singleton } from "./.internals/singleton/singleton.store"; +import { + ModelDefinition, + store, +} from "/types/index"; + +interface ICasesOfFieldsWithValues { + // value = value + fieldWithDefaultValue: T; + fieldCalculatedAsValue: T; + optionalFieldWithDefaultValue?: T; + optionalFieldCalculatedAsValue?: T; +} -interface ISomeObject { - prop: string; - length: number; +interface ICasesOfListedFieldsWithValues { + // value[] = value[] + listedFieldWithDefaultValues: T[]; + listedFieldCalculatedAsValues: T[]; + listedOptionalFieldWithDefaultValues?: T[]; + listedOptionalFieldCalculatedAsValues?: T[]; } -let condition = false; - -interface IModel { - id: string; - - // boolean - - emptyBoolean: boolean; - boolean: boolean; - looseBoolean: boolean; - undefinedOptionalBoolean?: boolean; - optionalBoolean?: boolean; - - emptyCalculatedBoolean: boolean; - calculatedBoolean: boolean; - undefinedCalculatedBoolean: boolean; - looseCalculatedBoolean: boolean; - calculatedOptionalBoolean?: boolean; - undefinedCalculatedOptionalBoolean?: boolean; - - // boolean List - - emptyBooleanList: boolean[]; - booleanList: boolean[]; - looseBooleanList: boolean[]; - undefinedOptionalBooleanList?: boolean[]; - optionalBooleanList?: boolean[]; - - emptyCalculatedBooleanList: boolean[]; - calculatedBooleanList: boolean[]; - undefinedCalculatedBooleanList: boolean[]; - looseCalculatedBooleanList: boolean[]; - calculatedOptionalBooleanList?: boolean[]; - undefinedCalculatedOptionalBooleanList?: boolean[]; - - // number - - emptyNumber: number; - number: number; - looseNumber: number; - undefinedOptionalNumber?: number; - optionalNumber?: number; - - emptyCalculatedNumber: number; - calculatedNumber: number; - undefinedCalculatedNumber: number; - looseCalculatedNumber: number; - calculatedOptionalNumber?: number; - undefinedCalculatedOptionalNumber?: number; - - // number List - - emptyNumberList: number[]; - numberList: number[]; - looseNumberList: number[]; - undefinedOptionalNumberList?: number[]; - optionalNumberList?: number[]; - - emptyCalculatedNumberList: number[]; - calculatedNumberList: number[]; - undefinedCalculatedNumberList: number[]; - looseCalculatedNumberList: number[]; - calculatedOptionalNumberList?: number[]; - undefinedCalculatedOptionalNumberList?: number[]; - - // string - - emptyString: string; - string: string; - looseString: string; - undefinedOptionalString?: string; - optionalString?: string; - - emptyCalculatedString: string; - calculatedString: string; - undefinedCalculatedString: string; - looseCalculatedString: string; - calculatedOptionalString?: string; - undefinedCalculatedOptionalString?: string; - - // string List - - emptyStringList: string[]; - stringList: string[]; - looseStringList: string[]; - undefinedOptionalStringList?: string[]; - optionalStringList?: string[]; - - emptyCalculatedStringList: string[]; - calculatedStringList: string[]; - undefinedCalculatedStringList: string[]; - looseCalculatedStringList: string[]; - calculatedOptionalStringList?: string[]; - undefinedCalculatedOptionalStringList?: string[]; - - // Object - - object: ISomeObject; - looseObject: ISomeObject; - undefinedOptionalObject?: ISomeObject; - optionalObject?: ISomeObject; - - calculatedObject: ISomeObject; - undefinedCalculatedObject: ISomeObject; - looseCalculatedObject: ISomeObject; - calculatedOptionalObject?: ISomeObject; - undefinedCalculatedOptionalObject?: ISomeObject; - - // Object List - - objectList: ISomeObject[]; - looseObjectList: ISomeObject[]; - undefinedOptionalObjectList?: ISomeObject[]; - optionalObjectList?: ISomeObject[]; - - calculatedObjectList: ISomeObject[]; - undefinedCalculatedObjectList: ISomeObject[]; - looseCalculatedObjectList: ISomeObject[]; - calculatedOptionalObjectList?: ISomeObject[]; - undefinedCalculatedOptionalObjectList?: ISomeObject[]; - - // Enumerable Model - - enumerableModel: IEnumerable; - looseEnumerableModel: IEnumerable; - undefinedOptionalEnumerableModel?: IEnumerable; - optionalEnumerableModel?: IEnumerable; - - calculatedEnumerableModel: IEnumerable; - undefinedCalculatedEnumerableModel: IEnumerable; - looseCalculatedEnumerableModel: IEnumerable; - calculatedOptionalEnumerableModel?: IEnumerable; - undefinedCalculatedOptionalEnumerableModel?: IEnumerable; - - // Enumerable Model List - - enumerableModelList: IEnumerable[]; - looseEnumerableModelList: IEnumerable[]; - undefinedOptionalEnumerableModelList?: IEnumerable[]; - optionalEnumerableModelList?: IEnumerable[]; - - calculatedEnumerableModelList: IEnumerable[]; - undefinedCalculatedEnumerableModelList: IEnumerable[]; - looseCalculatedEnumerableModelList: IEnumerable[]; - calculatedOptionalEnumerableModelList?: IEnumerable[]; - undefinedCalculatedOptionalEnumerableModelList?: IEnumerable[]; - - // Singleton Model - - singletonModel: ISingleton; - looseSingletonModel: ISingleton; - undefinedOptionalSingletonModel?: ISingleton; - optionalSingletonModel?: ISingleton; - - calculatedSingletonModel: ISingleton; - undefinedCalculatedSingletonModel: ISingleton; - looseCalculatedSingletonModel: ISingleton; - calculatedOptionalSingletonModel?: ISingleton; - undefinedCalculatedOptionalSingletonModel?: ISingleton; - - // Singleton Model List - - singletonModelList: ISingleton[]; - looseSingletonModelList: ISingleton[]; - undefinedOptionalSingletonModelList?: ISingleton[]; - optionalSingletonModelList?: ISingleton[]; - - calculatedSingletonModelList: ISingleton[]; - undefinedCalculatedSingletonModelList: ISingleton[]; - looseCalculatedSingletonModelList: ISingleton[]; - calculatedOptionalSingletonModelList?: ISingleton[]; - undefinedCalculatedOptionalSingletonModelList?: ISingleton[]; +interface ICasesOfFieldsWithEmptyValues { + // value = Empty + fieldWithDefaultEmptyValue: T; + fieldCalculatedAsEmptyValue: T; + optionalFieldWithDefaultEmptyValue?: T; + optionalFieldCalculatedAsEmptyValue?: T; } -const ModelStore: Model = { - id: true, +interface ICasesOfListedFieldsWithEmptyValues { + // value[] = [Empty] + listedFieldWithDefaultEmptyValues: T[]; + listedFieldCalculatedAsEmptyValues: T[]; + listedOptionalFieldWithDefaultEmptyValues?: T[]; + listedOptionalFieldCalculatedAsEmptyValues?: T[]; +} + +interface ICasesOfListedFieldsWithLoosedValues { + // value[] = [value, { loose }] + loosedListedFieldWithDefaultEmptyValue: T[]; + loosedListedFieldCalculatedAsEmptyValue?: T[]; + loosedListedOptionalFieldWithDefaultEmptyValue: T[]; + loosedListedOptionalFieldCalculatedAsEmptyValue?: T[]; +} + +interface ICasesOfFieldsWithUndefinedValues { + // value = undefined + fieldWithDefaultUndefinedValue: T; + fieldCalculatedAsUndefinedValue: T; + optionalFieldWithDefaultUndefinedValue?: T; + optionalFieldCalculatedAsUndefinedValue?: T; +} + +interface ICasesOfListedFieldsWithUndefinedValues { + // value[] = undefined[] + listedFieldWithDefaultUndefinedValues: T[]; + listedFieldCalculatedAsUndefinedValues: T[]; + listedOptionalFieldWithDefaultUndefinedValues?: T[]; + listedOptionalFieldCalculatedAsUndefinedValues?: T[]; +} - // boolean +interface IPrimitivePositiveCases + extends ICasesOfFieldsWithValues, + ICasesOfListedFieldsWithValues, + ICasesOfListedFieldsWithEmptyValues { } +interface IPrimitiveNegativeCases + extends ICasesOfFieldsWithEmptyValues, + ICasesOfListedFieldsWithLoosedValues, + ICasesOfFieldsWithUndefinedValues, + ICasesOfListedFieldsWithUndefinedValues { } + +const PrimitivePositiveCases: ModelDefinition = { + // boolean = boolean + fieldWithDefaultValue: false, + fieldCalculatedAsValue: ({ fieldWithDefaultValue }) => + fieldWithDefaultValue, + optionalFieldWithDefaultValue: false, + optionalFieldCalculatedAsValue: ({ optionalFieldWithDefaultValue }) => + optionalFieldWithDefaultValue || false, + + // boolean[] = boolean[] + listedFieldWithDefaultValues: [false], + listedFieldCalculatedAsValues: ({ listedFieldWithDefaultValues }) => + listedFieldWithDefaultValues, + listedOptionalFieldWithDefaultValues: [false], + listedOptionalFieldCalculatedAsValues: ({ + listedOptionalFieldWithDefaultValues, + }) => listedOptionalFieldWithDefaultValues, + + // boolean[] = [Boolean] + listedFieldWithDefaultEmptyValues: [Boolean], + listedFieldCalculatedAsEmptyValues: ({ + listedFieldWithDefaultEmptyValues, + }) => listedFieldWithDefaultEmptyValues, + listedOptionalFieldWithDefaultEmptyValues: [Boolean], + listedOptionalFieldCalculatedAsEmptyValues: ({ + listedOptionalFieldWithDefaultEmptyValues, + }) => listedOptionalFieldWithDefaultEmptyValues, +}; - boolean: false, +const PrimitiveNegativeCases: ModelDefinition = { + // boolean = Boolean /// @ts-expect-error - emptyBoolean: Boolean, - optionalBoolean: false, + fieldWithDefaultEmptyValue: Boolean, /// @ts-expect-error - undefinedOptionalBoolean: undefined, - - calculatedBoolean: () => false, + fieldCalculatedAsEmptyValue: ({ fieldWithDefaultEmptyValue }) => + fieldWithDefaultEmptyValue && Boolean, /// @ts-expect-error - undefinedCalculatedBoolean: () => undefined, + optionalFieldWithDefaultEmptyValue: Boolean, /// @ts-expect-error - emptyCalculatedBoolean: () => Boolean, - calculatedOptionalBoolean: () => false, - undefinedCalculatedOptionalBoolean: () => (condition ? undefined : false), - - // boolean List + optionalFieldCalculatedAsEmptyValue: ({ + optionalFieldWithDefaultEmptyValue, + }) => optionalFieldWithDefaultEmptyValue && Boolean, - booleanList: [false], - emptyBooleanList: [Boolean], + // boolean[] = [Boolean, { loose }] /// @ts-expect-error - looseBooleanList: [false, { loose: true }], - optionalBooleanList: [false], + loosedListedFieldWithDefaultEmptyValue: [Boolean, { loose: true }], /// @ts-expect-error - undefinedOptionalBooleanList: undefined, - - calculatedBooleanList: () => [false], + loosedListedFieldCalculatedAsEmptyValue: ({ + loosedListedFieldWithDefaultEmptyValue: + loosedListedFieldWithDefaultUndefinedValues, + }) => + loosedListedFieldWithDefaultUndefinedValues && [Boolean, { loose: true }], /// @ts-expect-error - undefinedCalculatedBooleanList: () => undefined, - emptyCalculatedBooleanList: () => [Boolean], + loosedListedOptionalFieldWithDefaultEmptyValue: [Boolean, { loose: true }], /// @ts-expect-error - looseCalculatedBooleanList: () => [false, { loose: true }], - calculatedOptionalBooleanList: () => [false], - undefinedCalculatedOptionalBooleanList: () => - condition ? undefined : [false], - - // number + loosedListedOptionalFieldCalculatedAsEmptyValue: ({ + loosedListedOptionalFieldWithDefaultEmptyValue: + loosedListedOptionalFieldWithDefaultUndefinedValues, + }) => + loosedListedOptionalFieldWithDefaultUndefinedValues && [ + Boolean, + { loose: true }, + ], - number: 0, + // boolean = undefined /// @ts-expect-error - emptyNumber: Number, - optionalNumber: 0, + fieldWithDefaultUndefinedValue: undefined, /// @ts-expect-error - undefinedOptionalNumber: undefined, - - calculatedNumber: () => 0, + fieldCalculatedAsUndefinedValue: ({ fieldWithDefaultUndefinedValue }) => + fieldWithDefaultUndefinedValue && undefined, /// @ts-expect-error - undefinedCalculatedNumber: () => undefined, + optionalFieldWithDefaultUndefinedValue: undefined, /// @ts-expect-error - emptyCalculatedNumber: () => Number, - calculatedOptionalNumber: () => 0, - undefinedCalculatedOptionalNumber: () => (condition ? undefined : 0), - - // number List + optionalFieldCalculatedAsUndefinedValue: ({ + optionalFieldWithDefaultUndefinedValue, + }) => optionalFieldWithDefaultUndefinedValue && undefined, - numberList: [0], - emptyNumberList: [Number], + // boolean[] = undefined[] /// @ts-expect-error - looseNumberList: [0, { loose: true }], - optionalNumberList: [0], + listedFieldWithDefaultUndefinedValues: [undefined], /// @ts-expect-error - undefinedOptionalNumberList: undefined, - - calculatedNumberList: () => [0], + listedFieldCalculatedAsUndefinedValues: ({ + listedFieldWithDefaultUndefinedValues, + }) => listedFieldWithDefaultUndefinedValues && [undefined], /// @ts-expect-error - undefinedCalculatedNumberList: () => undefined, - emptyCalculatedNumberList: () => [Number], + listedOptionalFieldWithDefaultUndefinedValues: [undefined], /// @ts-expect-error - looseCalculatedNumberList: () => [0, { loose: true }], - calculatedOptionalNumberList: () => [0], - undefinedCalculatedOptionalNumberList: () => (condition ? undefined : [0]), + listedOptionalFieldCalculatedAsUndefinedValues: ({ + listedOptionalFieldWithDefaultUndefinedValues, + }) => listedOptionalFieldWithDefaultUndefinedValues && [undefined], +}; - // string +interface ISomeObject { + stringProperty: string; + numberProperty: number; +} +interface IObjectPositiveCases + extends ICasesOfFieldsWithValues, + ICasesOfListedFieldsWithValues { } +interface IObjectNegativeCases + extends ICasesOfFieldsWithEmptyValues, + ICasesOfListedFieldsWithEmptyValues, + ICasesOfListedFieldsWithLoosedValues, + ICasesOfFieldsWithUndefinedValues, + ICasesOfListedFieldsWithUndefinedValues { } + +const someObject: ISomeObject = { + stringProperty: "", + numberProperty: 1, +}; + +const ObjectPositiveCases: ModelDefinition = { + // ISomeObject = someObject + fieldWithDefaultValue: someObject, + fieldCalculatedAsValue: ({ fieldWithDefaultValue }) => fieldWithDefaultValue, + optionalFieldWithDefaultValue: someObject, + optionalFieldCalculatedAsValue: ({ optionalFieldWithDefaultValue }) => + optionalFieldWithDefaultValue, + + // ISomeObject[] = someObject[] + listedFieldWithDefaultValues: [someObject, someObject], + listedFieldCalculatedAsValues: ({ listedFieldWithDefaultValues }) => + listedFieldWithDefaultValues, + listedOptionalFieldWithDefaultValues: [someObject, someObject], + listedOptionalFieldCalculatedAsValues: ({ + listedOptionalFieldWithDefaultValues, + }) => listedOptionalFieldWithDefaultValues, +}; - string: "", +const ObjectNegativeCases: ModelDefinition = { + // ISomeObject = Object /// @ts-expect-error - emptyString: String, - optionalString: "", + fieldWithDefaultEmptyValue: Object, /// @ts-expect-error - undefinedOptionalString: undefined, - - calculatedString: () => "", + fieldCalculatedAsEmptyValue: ({ fieldWithDefaultEmptyValue }) => + fieldWithDefaultEmptyValue && Object, /// @ts-expect-error - undefinedCalculatedString: () => undefined, + optionalFieldWithDefaultEmptyValue: Object, /// @ts-expect-error - emptyCalculatedString: () => String, - calculatedOptionalString: () => "", - undefinedCalculatedOptionalString: () => (condition ? undefined : ""), - - // string List + optionalFieldCalculatedAsEmptyValue: ({ + optionalFieldWithDefaultEmptyValue, + }) => optionalFieldWithDefaultEmptyValue && Object, - stringList: [""], - emptyStringList: [String], + // ISomeObject[] = [Object] /// @ts-expect-error - looseStringList: ["", { loose: true }], - optionalStringList: [""], + listedFieldWithDefaultEmptyValues: [Object], /// @ts-expect-error - undefinedOptionalStringList: undefined, - - calculatedStringList: () => [""], + listedFieldCalculatedAsEmptyValues: ({ listedFieldWithDefaultEmptyValues }) => + listedFieldWithDefaultEmptyValues && [Object], /// @ts-expect-error - undefinedCalculatedStringList: () => undefined, - emptyCalculatedStringList: () => [String], + listedOptionalFieldWithDefaultEmptyValues: [Object], /// @ts-expect-error - looseCalculatedStringList: () => ["", { loose: true }], - calculatedOptionalStringList: () => [""], - undefinedCalculatedOptionalStringList: () => (condition ? undefined : [""]), - - // Object + listedOptionalFieldCalculatedAsEmptyValues: ({ + listedOptionalFieldWithDefaultEmptyValues, + }) => listedOptionalFieldWithDefaultEmptyValues && [Object], - object: { prop: "", length: 0 }, - optionalObject: { prop: "", length: 0 }, + // ISomeObject[] = [Object, { loose }] /// @ts-expect-error - undefinedOptionalObject: undefined, - - calculatedObject: () => ({ prop: "", length: 0 }), + loosedListedFieldWithDefaultEmptyValue: [Object, { loose: true }], /// @ts-expect-error - undefinedCalculatedObject: () => undefined, - calculatedOptionalObject: () => ({ prop: "", length: 0 }), - undefinedCalculatedOptionalObject: () => - condition ? undefined : { prop: "", length: 0 }, - - // Object List + loosedListedFieldCalculatedAsEmptyValue: ({ + loosedListedFieldWithDefaultEmptyValue, + }) => loosedListedFieldWithDefaultEmptyValue && [Object, { loose: true }], + /// @ts-expect-error + loosedListedOptionalFieldWithDefaultEmptyValue: [Object, { loose: true }], + /// @ts-expect-error + loosedListedOptionalFieldCalculatedAsEmptyValue: ({ + loosedListedOptionalFieldWithDefaultEmptyValue, + }) => + loosedListedOptionalFieldWithDefaultEmptyValue && [Object, { loose: true }], - objectList: [{ prop: "", length: 0 }], + // ISomeObject = undefined /// @ts-expect-error - looseObjectList: [{ prop: "", length: 0 }, { loose: true }], - optionalObjectList: [{ prop: "", length: 0 }], + fieldWithDefaultUndefinedValue: undefined, /// @ts-expect-error - undefinedOptionalObjectList: undefined, + fieldCalculatedAsUndefinedValue: ({ fieldWithDefaultUndefinedValue }) => + fieldWithDefaultUndefinedValue && undefined, + /// @ts-expect-error + optionalFieldWithDefaultUndefinedValue: undefined, + /// @ts-expect-error + optionalFieldCalculatedAsUndefinedValue: ({ + optionalFieldWithDefaultUndefinedValue, + }) => optionalFieldWithDefaultUndefinedValue && undefined, - calculatedObjectList: () => [{ prop: "", length: 0 }], + // ISomeObject[] = undefined[] + /// @ts-expect-error + listedFieldWithDefaultUndefinedValues: [undefined], + /// @ts-expect-error + listedFieldCalculatedAsUndefinedValues: ({ + listedFieldWithDefaultUndefinedValues, + }) => listedFieldWithDefaultUndefinedValues && [undefined], /// @ts-expect-error - undefinedCalculatedObjectList: () => undefined, + listedOptionalFieldWithDefaultUndefinedValues: [undefined], /// @ts-expect-error - looseCalculatedObjectList: () => [{ prop: "", length: 0 }, { loose: true }], - calculatedOptionalObjectList: () => [{ prop: "", length: 0 }], - undefinedCalculatedOptionalObjectList: () => - condition ? undefined : [{ prop: "", length: 0 }], + listedOptionalFieldCalculatedAsUndefinedValues: ({ + listedOptionalFieldWithDefaultUndefinedValues, + }) => listedOptionalFieldWithDefaultUndefinedValues && [undefined], +}; - // Enumerable Model +interface ISingletonPositiveCases + extends ICasesOfFieldsWithEmptyValues { } +interface ISingletonNegativeCases + extends ICasesOfFieldsWithValues, + ICasesOfListedFieldsWithValues, + ICasesOfListedFieldsWithEmptyValues, + ICasesOfListedFieldsWithLoosedValues, + ICasesOfFieldsWithUndefinedValues, + ICasesOfListedFieldsWithUndefinedValues { } + +const singleton = store.sync(Singleton, {}); + +const SingletonPositiveCases: ModelDefinition = { + // ISingleton = Singleton + fieldWithDefaultEmptyValue: Singleton, + fieldCalculatedAsEmptyValue: ({ fieldWithDefaultEmptyValue }) => + fieldWithDefaultEmptyValue && Singleton, + optionalFieldWithDefaultEmptyValue: Singleton, + optionalFieldCalculatedAsEmptyValue: ({ + optionalFieldWithDefaultEmptyValue, + }) => optionalFieldWithDefaultEmptyValue && Singleton, +}; - enumerableModel: EnumerableStore, - optionalEnumerableModel: EnumerableStore, +const SingletonNegativeCases: ModelDefinition = { + // ISingleton = singleton /// @ts-expect-error - undefinedOptionalEnumerableModel: undefined, - - calculatedEnumerableModel: () => EnumerableStore, + fieldWithDefaultValue: singleton, /// @ts-expect-error - undefinedCalculatedEnumerableModel: () => undefined, - calculatedOptionalEnumerableModel: () => EnumerableStore, - undefinedCalculatedOptionalEnumerableModel: () => - condition ? undefined : EnumerableStore, - - // Enumerable Model List + fieldCalculatedAsValue: ({ fieldWithDefaultValue }) => fieldWithDefaultValue, + /// @ts-expect-error + optionalFieldWithDefaultValue: singleton, + /// @ts-expect-error + optionalFieldCalculatedAsValue: ({ optionalFieldWithDefaultValue }) => + optionalFieldWithDefaultValue, - enumerableModelList: [EnumerableStore], - looseEnumerableModelList: [EnumerableStore, { loose: true }], - optionalEnumerableModelList: [EnumerableStore], + // ISingleton[] = singleton[] + /// @ts-expect-error + listedFieldWithDefaultValues: [singleton, singleton], + /// @ts-expect-error + listedFieldCalculatedAsValues: ({ listedFieldWithDefaultValues }) => + listedFieldWithDefaultValues, + /// @ts-expect-error + listedOptionalFieldWithDefaultValues: [singleton, singleton], /// @ts-expect-error - undefinedOptionalEnumerableModelList: undefined, + listedOptionalFieldCalculatedAsValues: ({ + listedOptionalFieldWithDefaultValues, + }) => listedOptionalFieldWithDefaultValues, - calculatedEnumerableModelList: () => [EnumerableStore], + // ISingleton[] = [Singleton] /// @ts-expect-error - undefinedCalculatedEnumerableModelList: () => undefined, - looseCalculatedEnumerableModelList: () => [EnumerableStore, { loose: true }], - calculatedOptionalEnumerableModelList: () => [EnumerableStore], - undefinedCalculatedOptionalEnumerableModelList: () => - condition ? undefined : [EnumerableStore], + listedFieldWithDefaultEmptyValues: [Singleton], + /// @ts-expect-error + listedFieldCalculatedAsEmptyValues: ({ listedFieldWithDefaultEmptyValues }) => + listedFieldWithDefaultEmptyValues && [Singleton], + /// @ts-expect-error + listedOptionalFieldWithDefaultEmptyValues: [Singleton], + /// @ts-expect-error + listedOptionalFieldCalculatedAsEmptyValues: ({ + listedOptionalFieldWithDefaultEmptyValues, + }) => listedOptionalFieldWithDefaultEmptyValues && [Singleton], - // Singleton Model + // ISingleton[] = [Singleton, { loose }] + /// @ts-expect-error + loosedListedFieldWithDefaultEmptyValue: [Singleton, { loose: true }], + /// @ts-expect-error + loosedListedFieldCalculatedAsEmptyValue: ({ + loosedListedFieldWithDefaultEmptyValue, + }) => loosedListedFieldWithDefaultEmptyValue && [Singleton, { loose: true }], + /// @ts-expect-error + loosedListedOptionalFieldWithDefaultEmptyValue: [Singleton, { loose: true }], + /// @ts-expect-error + loosedListedOptionalFieldCalculatedAsEmptyValue: ({ + loosedListedOptionalFieldWithDefaultEmptyValue, + }) => + loosedListedOptionalFieldWithDefaultEmptyValue && [ + Singleton, + { loose: true }, + ], - singletonModel: SingletonStore, - optionalSingletonModel: SingletonStore, + // ISingleton = undefined + /// @ts-expect-error + fieldWithDefaultUndefinedValue: undefined, /// @ts-expect-error - undefinedOptionalSingletonModel: undefined, + fieldCalculatedAsUndefinedValue: ({ fieldWithDefaultUndefinedValue }) => + fieldWithDefaultUndefinedValue && undefined, + /// @ts-expect-error + optionalFieldWithDefaultUndefinedValue: undefined, + /// @ts-expect-error + optionalFieldCalculatedAsUndefinedValue: ({ + optionalFieldWithDefaultUndefinedValue, + }) => optionalFieldWithDefaultUndefinedValue && undefined, - calculatedSingletonModel: () => SingletonStore, + // ISingleton[] = undefined[] + /// @ts-expect-error + listedFieldWithDefaultUndefinedValues: [undefined], + /// @ts-expect-error + listedFieldCalculatedAsUndefinedValues: ({ + listedFieldWithDefaultUndefinedValues, + }) => listedFieldWithDefaultUndefinedValues && [undefined], + /// @ts-expect-error + listedOptionalFieldWithDefaultUndefinedValues: [undefined], /// @ts-expect-error - undefinedCalculatedSingletonModel: () => undefined, - calculatedOptionalSingletonModel: () => SingletonStore, - undefinedCalculatedOptionalSingletonModel: () => - condition ? undefined : SingletonStore, + listedOptionalFieldCalculatedAsUndefinedValues: ({ + listedOptionalFieldWithDefaultUndefinedValues, + }) => listedOptionalFieldWithDefaultUndefinedValues && [undefined], +}; - // Singleton Model List +interface IEnumerablePositiveCases + extends ICasesOfFieldsWithEmptyValues, + ICasesOfListedFieldsWithEmptyValues, + ICasesOfListedFieldsWithLoosedValues { } +interface IEnumerableNegativeCases + extends ICasesOfFieldsWithValues, + ICasesOfListedFieldsWithValues, + ICasesOfFieldsWithUndefinedValues, + ICasesOfListedFieldsWithUndefinedValues { } + +const enumerable = store.sync(Enumerable, {}); + +const EnumerablePositiveCases: ModelDefinition = { + // IEnumerable = Enumerable + fieldWithDefaultEmptyValue: Enumerable, + fieldCalculatedAsEmptyValue: ({ fieldWithDefaultEmptyValue }) => + fieldWithDefaultEmptyValue && Enumerable, + optionalFieldWithDefaultEmptyValue: Enumerable, + optionalFieldCalculatedAsEmptyValue: ({ + optionalFieldWithDefaultEmptyValue, + }) => optionalFieldWithDefaultEmptyValue && Enumerable, + + // IEnumerable[] = [Enumerable] + listedFieldWithDefaultEmptyValues: [Enumerable], + listedFieldCalculatedAsEmptyValues: ({ listedFieldWithDefaultEmptyValues }) => + listedFieldWithDefaultEmptyValues && [Enumerable], + listedOptionalFieldWithDefaultEmptyValues: [Enumerable], + listedOptionalFieldCalculatedAsEmptyValues: ({ + listedOptionalFieldWithDefaultEmptyValues, + }) => listedOptionalFieldWithDefaultEmptyValues && [Enumerable], + + // IEnumerable[] = [Enumerable, { loose }] + loosedListedFieldWithDefaultEmptyValue: [Enumerable, { loose: true }], + loosedListedFieldCalculatedAsEmptyValue: ({ + loosedListedFieldWithDefaultEmptyValue, + }) => loosedListedFieldWithDefaultEmptyValue && [Enumerable, { loose: true }], + loosedListedOptionalFieldWithDefaultEmptyValue: [Enumerable, { loose: true }], + loosedListedOptionalFieldCalculatedAsEmptyValue: ({ + loosedListedOptionalFieldWithDefaultEmptyValue, + }) => + loosedListedOptionalFieldWithDefaultEmptyValue && [ + Enumerable, + { loose: true }, + ], +}; +const EnumerableNegativeCases: ModelDefinition = { + // IEnumerable = enumerable /// @ts-expect-error - singletonModelList: [SingletonStore], + fieldWithDefaultValue: enumerable, /// @ts-expect-error - looseSingletonModelList: [SingletonStore, { loose: true }], + fieldCalculatedAsValue: ({ fieldWithDefaultValue }) => fieldWithDefaultValue, /// @ts-expect-error - optionalSingletonModelList: [SingletonStore], + optionalFieldWithDefaultValue: enumerable, /// @ts-expect-error - undefinedOptionalSingletonModelList: undefined, + optionalFieldCalculatedAsValue: ({ optionalFieldWithDefaultValue }) => + optionalFieldWithDefaultValue, + // IEnumerable[] = enumerable[] /// @ts-expect-error - calculatedSingletonModelList: () => [SingletonStore], + listedFieldWithDefaultValues: [enumerable, enumerable], /// @ts-expect-error - undefinedCalculatedSingletonModelList: () => undefined, + listedFieldCalculatedAsValues: ({ listedFieldWithDefaultValues }) => + listedFieldWithDefaultValues, /// @ts-expect-error - looseCalculatedSingletonModelList: () => [SingletonStore, { loose: true }], + listedOptionalFieldWithDefaultValues: [enumerable, enumerable], /// @ts-expect-error - calculatedOptionalSingletonModelList: () => [SingletonStore], - /// @ts-expect-error - undefinedCalculatedOptionalSingletonModelList: () => - condition ? undefined : [SingletonStore], -}; + listedOptionalFieldCalculatedAsValues: ({ + listedOptionalFieldWithDefaultValues, + }) => listedOptionalFieldWithDefaultValues, -export default ModelStore; + // IEnumerable = undefined + /// @ts-expect-error + fieldWithDefaultUndefinedValue: undefined, + /// @ts-expect-error + fieldCalculatedAsUndefinedValue: ({ fieldWithDefaultUndefinedValue }) => + fieldWithDefaultUndefinedValue && undefined, + /// @ts-expect-error + optionalFieldWithDefaultUndefinedValue: undefined, + /// @ts-expect-error + optionalFieldCalculatedAsUndefinedValue: ({ + optionalFieldWithDefaultUndefinedValue, + }) => optionalFieldWithDefaultUndefinedValue && undefined, -/// @ts-expect-error -const EmptyStore: Model = { - id: true, - boolean: false, + // IEnumerable[] = undefined[] + /// @ts-expect-error + listedFieldWithDefaultUndefinedValues: [undefined], + /// @ts-expect-error + listedFieldCalculatedAsUndefinedValues: ({ + listedFieldWithDefaultUndefinedValues, + }) => listedFieldWithDefaultUndefinedValues && [undefined], + /// @ts-expect-error + listedOptionalFieldWithDefaultUndefinedValues: [undefined], + /// @ts-expect-error + listedOptionalFieldCalculatedAsUndefinedValues: ({ + listedOptionalFieldWithDefaultUndefinedValues, + }) => listedOptionalFieldWithDefaultUndefinedValues && [undefined], }; diff --git a/test/types/store/record.ts b/test/types/store/record.ts index e2382b4c..fb59b2bc 100644 --- a/test/types/store/record.ts +++ b/test/types/store/record.ts @@ -1,18 +1,25 @@ -import { Model, define, store } from "/types/index"; +import { PickIndexes, FieldDefinition, GuardedModel, ModelDefinition, define, store, StoreRecord } from "/types/index"; + +type A = { a: string } interface IEnumerableModel { id: string; - values: Record; + property: string + values: Record; } -const IEnumerableModel: Model = { +type B = FieldDefinition + +const IEnumerableModel: ModelDefinition = { id: true, + property: "", values: store.record({ a: "test" }), }; -const IOtherModel: Model = { +const IOtherModel: ModelDefinition = { id: true, - values: store.record(store.ref(() => ({ a: "test" }))), + property: "", + values: store.ref(() => store.record(({ a: "test" }))), }; interface IAComponent extends HTMLElement { diff --git a/test/types/store/ref.ts b/test/types/store/ref.ts index a81912c5..5fb7131a 100644 --- a/test/types/store/ref.ts +++ b/test/types/store/ref.ts @@ -1,4 +1,4 @@ -import { Model, store } from "/types/index"; +import { ModelDefinition, store } from "/types/index"; interface IOtherModel { a: string; @@ -9,11 +9,11 @@ interface IEnumerableModel { other: IOtherModel; } -const IOtherModel: Model = { +const IOtherModel: ModelDefinition = { a: "", }; -const IEnumerableModel: Model = { +const IEnumerableModel: ModelDefinition = { id: true, other: store.ref(() => IOtherModel), }; diff --git a/test/types/store/singleton-definition.store.ts b/test/types/store/singleton-definition.store.ts deleted file mode 100644 index d9c6283f..00000000 --- a/test/types/store/singleton-definition.store.ts +++ /dev/null @@ -1,30 +0,0 @@ -import EnumerableStore, { IEnumerable } from "./enumerable-definition.store"; -import { Model, store } from "/types/index"; - -export interface ISingleton { - prop: string; - length: number; - relatedSingleton?: ISingleton; - relatedEnumerable?: IEnumerable; - relatedEnumerables: IEnumerable[]; -} - -const SingletonStore: Model = { - prop: "", - length: 0, - relatedSingleton: store.ref(() => SingletonStore), - relatedEnumerable: store.ref(() => EnumerableStore), - relatedEnumerables: store.ref(() => [EnumerableStore]), -}; - -export default SingletonStore; - -const BrokenSingletonStore: Model = { - /// @ts-expect-error - id: true, - prop: "", - length: 0, - relatedSingleton: store.ref(() => SingletonStore), - relatedEnumerable: store.ref(() => EnumerableStore), - relatedEnumerables: store.ref(() => [EnumerableStore]), -}; diff --git a/test/types/store/storage-external.ts b/test/types/store/storage-external.ts index f295c3dd..b9dcd2ad 100644 --- a/test/types/store/storage-external.ts +++ b/test/types/store/storage-external.ts @@ -1,17 +1,29 @@ +import { IEnumerable } from "./.internals/enumerable/enumerable.entity"; +import { Enumerable } from "./.internals/enumerable/enumerable.store"; import { Model, ModelIdentifier, StorageValues, store } from "/types/index"; -import EnumerableStore, { IEnumerable } from "./enumerable-definition.store"; const EnumerableModelsSource = new Map< ModelIdentifier, StorageValues >([ - ["0", { id: "0", prop: "qweqwe", length: 5, relatedEnumerable: "2" }], - ["1", { id: "1", length: 1711014651455, relatedEnumerables: ["1", "2"] }], - ["2", { id: "2", prop: "swswswsws" }], + [ + "0", + { + id: "0", + stringProperty: "qweqwe", + numberProperty: 5, + nestedEnumerable: "2", + }, + ], + [ + "1", + { id: "1", numberProperty: 1711014651455, nestedEnumerables: ["1", "2"] }, + ], + ["2", { id: "2", stringProperty: "swswswsws" }], ]); const EnumerableModelStore: Model = { - ...EnumerableStore, + ...Enumerable, [store.connect]: { list: (id) => [...Object.values(EnumerableModelsSource)], get: (id) => EnumerableModelsSource.get(id) ?? null, diff --git a/types/index.d.ts b/types/index.d.ts index 7b2c43ea..cd691025 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -6,9 +6,9 @@ export type Property = export interface Descriptor { value: - | V - | ((host: E & HTMLElement) => V) - | ((host: E & HTMLElement, value: any) => V); + | V + | ((host: E & HTMLElement) => V) + | ((host: E & HTMLElement, value: any) => V); connect?( host: E & HTMLElement & { __property_key__: V }, @@ -43,14 +43,14 @@ export type Component = ComponentBase & { keyof Omit, string >]: property extends "render" - ? RenderFunction | RenderDescriptor - : Property; + ? RenderFunction | RenderDescriptor + : Property; } & { render?: RenderFunction | RenderDescriptor; }; export interface HybridElement { - new (): E & HTMLElement; + new(): E & HTMLElement; prototype: E & HTMLElement; } @@ -86,99 +86,453 @@ export function children( /* Store */ -export type ModelInstance = { id?: ModelIdentifier } & object & - NonArrayObject & - NonModelDefinition; -export type EnumerableInstance = { id: ModelIdentifier } & ModelInstance; -export type SingletonInstance = { id?: never } & ModelInstance; - +/** + * Extracts the element type from an array type. + * @deprecated Use built-in array type utilities instead. This type will be removed in future versions. + * @template T - Target type + * @example + * type Item = Unarray; // string + * type Value = Unarray; // number + */ export type Unarray = T extends Array ? U : T; -export type NonConstructor = { readonly prototype?: never }; -export type NonArrayObject = { [Symbol.iterator]?: never } & object; -export type NonModelDefinition = { __store__connect__?: never } & object; - -export type Model = NonArrayObject & { - [property in keyof Omit]-?: NonNullable< - M[property] - > extends Array - ? - | NestedArrayModel> - | (NonConstructor & - (( - model: M, - ) => undefined extends M[property] - ? undefined | NestedArrayModel - : NestedArrayModel)) - : NonNullable extends string | String - ? string | (NonConstructor & ((model: M) => M[property])) - : NonNullable extends number | Number - ? number | (NonConstructor & ((model: M) => M[property])) - : NonNullable extends boolean | Boolean - ? boolean | (NonConstructor & ((model: M) => M[property])) - : NonNullable extends ModelInstance - ? - | Model> - | (NonConstructor & - (( - model: M, - ) => undefined extends M[property] - ? undefined | Model> - : Model>)) - : NonNullable extends NonArrayObject - ? - | NonNullable - | (NonConstructor & ((model: M) => M[property])) - : never; -} & (M extends EnumerableInstance - ? { - id: true; - } - : {}) & { - __store__connect__?: Storage | Storage["get"]; - }; -export type NestedArrayModel = - NonNullable> extends string | String - ? T | string[] | [String | StringConstructor] - : NonNullable> extends number | Number - ? T | number[] | [Number | NumberConstructor] - : NonNullable> extends boolean | Boolean - ? T | boolean[] | [Boolean | BooleanConstructor] - : NonNullable> extends EnumerableInstance - ? - | [Model>>] - | [Model>>, { loose?: boolean }] - : NonNullable> extends NonArrayObject - ? T - : never; +/** + * Represents non-array object types. + * @deprecated Use `StrictObject` instead. This type will be removed in future versions. + */ +export type NonArrayObject = NonArray & object; + +/** + * Excludes constructor types (classes) by requiring absence of `new` and `prototype` properties. + * @example + * declare function create(obj: NonConstructor & T): void; + * create({ foo: 1 }); // OK + * create(class {}); // Error - has constructor properties + */ +export type NonConstructor = { new?: never, prototype?: never }; + +/** + * Excludes iterable types (like arrays) by requiring absence of `[Symbol.iterator]`. + * @example + * declare function acceptNonArray(obj: NonArray & T): void; + * acceptNonArray({}); // OK + * acceptNonArray([]); // Error - array is iterable + */ +export type NonArray = { [Symbol.iterator]?: never }; + +/** + * Excludes function types by making the function signature return `never`. + * @example + * declare function acceptNonFunction(obj: NonFunction & T): void; + * acceptNonFunction(42); // OK + * acceptNonFunction(() => {}); // Error - function detected + */ +export type NonFunction = { (...args: any[]): never }; + +/** + * Represents a strict non-array, non-constructor object type. + * Acts as a type guard against unintended matches in complex type operations. + * + * @template T - Base object type to extend + * @example + * // Prevents array/class/function from matching object type: + * type Test1 = [1,2,3] extends StrictObject<{}> ? true : false; // false + * type Test2 = class {} extends StrictObject<{}> ? true : false; // false + */ +export type StrictObject = T & object & NonArray & NonConstructor + +/** + * Represents a function that cannot be used as a constructor. + * @template T - Function type to enforce + * @example + * declare function wrapFunction(fn: StrictFunction): void; + * wrapFunction(() => console.log("ok")); // OK + * wrapFunction(class {}); // Error - constructor prohibited + */ +export type StrictFunction any> = T & NonConstructor; + +/** + * Represents a class constructor that cannot be called as a plain function. + * @template T - Instance type of the class + * @example + * declare function registerClass(cls: StrictClass): void; + * registerClass(class Foo {}); // OK + * registerFunction(function() {}); // Error - not a class + */ +export type StrictClass = { new(...args: any[]): T; prototype: T; } & NonFunction; + +/** + * Marker type to exclude model definition objects. + * Contains hidden system property that prevents accidental type matching. + * + * @internal + * @example + * // Without NonModelDefinition, this would incorrectly return true: + * type Test = ModelDefinition extends Model ? true : false; // false + */ +export type NonModelDefinition = { __store__connect__?: never }; + + +/** + * Base model type requiring plain objects with optional `id` property. + * Combines multiple protective markers to prevent type misclassification: + * 1. StrictObject - blocks arrays/classes + * 2. NonModelDefinition - blocks model definitions + * + * @example + * // Critical for type discrimination in functions like: + * function resolve(input: M | ModelDefinition | [ModelDefinition]) { + * // Without protective markers: + * // ModelDefinition would match Model + * // ModelDefinition[] would match Model + * } + */ +export type Model = StrictObject<{ id?: ModelIdentifier }> & NonModelDefinition; + +/** + * Model type requiring explicit identifier (`id` property required). + * Inherits same protective markers as base Model. + * + * @example + * // Without StrictObject/NonModelDefinition: + * type Problem1 = ModelDefinition extends EnumerableModel ? true : false; + * // Would be true (incorrect) + */ +export type EnumerableModel = StrictObject<{ id: ModelIdentifier }> & NonModelDefinition; + +/** + * Model type prohibiting identifier (`id` property forbidden). + * Protective markers ensure only true singletons match. + * + * @example + * // Prevents array of definitions from matching: + * type Test = [ModelDefinition] extends SingletonModel ? true : false; // false + */ +export type SingletonModel = StrictObject<{ id?: never }> & NonModelDefinition; + +/** + * Removes index signatures (string, number, and symbol) from object types. + * + * Used to eliminate phantom keys that TypeScript automatically includes + * when index signatures like `[key: string]: T` or `[key: number]: T` are present. + * + * #### The Problem + * When an interface contains an index signature, TypeScript automatically + * includes all possible keys in `keyof` (including `string` and `number`). + * This interferes with key-specific mappings and causes unwanted iteration behavior. + * + * #### The Solution + * `StripIndex` filters out generic `string`, `number`, and `symbol` keys + * from `keyof T`, preserving only explicitly defined properties. + * + * #### How It Works + * Uses key remapping with conditional exclusion: + * 1. For string keys: `key extends string ? (string extends key ? never : key)` + * 2. For number keys: `key extends number ? (number extends key ? never : key)` + * 3. For symbol keys: `key extends symbol ? (symbol extends key ? never : key)` + * + * #### Example: + * ```ts + * type Raw = { + * id: string; + * name: string; + * [key: string]: unknown; // Index signature + * } + * + * type Clean = StripIndex; + * // Result: { + * // id: string; + * // name: string + * // } // No indexes! + * ``` + * + * @template T Object type to remove index signatures from + */ +export type OmitIndexes = { + [key in keyof T as + key extends string ? (string extends key ? never : key) : + key extends number ? (number extends key ? never : key) : + key extends symbol ? (symbol extends key ? never : key) : + key + ]: T[key]; +} +export type PickIndexes = { + [key in keyof T as + key extends string ? (string extends key ? key : never) : + key extends number ? (number extends key ? key : never) : + key extends symbol ? (symbol extends key ? key : never) : + key + ]: T[key] +} extends infer IndexPart ? [keyof IndexPart] extends [never] ? void : IndexPart : never; + +type A = PickIndexes<{}> + +/** + * Defines the structure and behavior of a model in the system. + * Maps each model property to either: + * - Identity marker (for 'id' properties) + * - Field definition + * - Value resolver function + * + * @template TModel - Model type being defined (must extend `Model`) + * + * ### Structure + * 1. **Property Mappings**: + * - Identity properties: Marked with `true` + * - Other properties: Field definitions or resolver functions + * 2. **Storage Connector** (optional): + * - Connection to storage system (`store.connect`) + * + * ### Key Features + * - Removes index signatures via `StripIndex` + * - Makes all properties required (`-?` modifier) + * - Distinguishes identity properties via `IsIdentityProperty` + * - Allows computed properties via `ValueResolver` + * + * @example + * ```tsx + * const User: ModelDefinition = { + * id: true, // Identity property + * name: "", // Default value (string field) + * email: { // Field definition + * type: String, + * validate: emailValidator + * }, + * fullName: (user) => `${user.first} ${user.last}`, // Computed property + * [store.connect]: (id) => api.fetchUser(id) // Storage connector + * }; + * ``` + */ +export type ModelDefinition = { + /** + * Maps each model property to its definition: + * - Identity properties → `true` + * - Other properties → Field definition or resolver function + * + * Uses `StripIndex` to remove index signatures and ensure only + * explicit properties are considered + */ + [key in keyof Omit, "id">]-?: + | (TModel[key] extends object ? + PickIndexes extends { [key: string]: infer ItemType } ? Referable> : + PickIndexes extends { [key: number]: infer ItemType } ? Referable> : + PickIndexes extends { [key: symbol]: infer ItemType } ? Referable> : + Referable> : + Referable>) + | ValueResolver +} & EnumerableIndicator & StorageConnector + + +type EnumerableIndicator = TModel extends EnumerableModel ? { id: true } : {} + +/** + * Optional storage system connector + * - Used with `store.connect` symbol in implementations + * - Can be either: + * a) Full storage interface + * b) Storage's `get` method for fetching data + */ +type StorageConnector = { + __store__connect__?: Storage | Storage["get"]; +} + +type StoreRecord = { + __store__record__: TValue +} + +/** + * Determines if a property is the model's identity property. + * + * - Must be named 'id' + * - Must have a valid ModelIdentifier type + * + * @template TKey - Property name + * @template TValue - Property value type + * + * @example + * ```ts + * type Test1 = IsIdentityProperty<'id', string>; // true + * type Test2 = IsIdentityProperty<'name', string>; // false + * type Test3 = IsIdentityProperty<'id', boolean>; // false + * ``` + */ +export type IsIdentityProperty = TKey extends "id" ? TValue extends ModelIdentifier ? true : false : false + +/** + * Represents possible types for model identifiers. + * + * - Single string value + * - Composite key (record of primitive values) + * - Undefined (for singleton models) + * + * @example + * ```ts + * type UserID = string; // Simple identifier + * type OrderID = { orderNo: string, region: string }; // Composite key + * ``` + */ export type ModelIdentifier = | string | Record | undefined; -export type ModelValues = { - [property in keyof M]?: NonNullable extends Array - ? Array>>> - : NonNullable extends ModelInstance - ? ModelValues> - : M[property]; -}; - -export type StorageValues = { - [property in keyof M]?: NonNullable extends EnumerableInstance - ? NonNullable | M["id"] - : NonNullable extends EnumerableInstance[] - ? (NonNullable> | M["id"])[] - : M[property]; +/** + * Special flag marking identity properties in model definitions. + * Always set to `true` for 'id' fields. + * + * @example + * ```ts + * const ProductModel = { + * id: true, // IdentityFlag + * // ... + * } + * ``` + */ +export type IdentityFlag = true + +/** + * Strict function type for computed property resolvers. + * + * - Receives the model instance as input + * - Returns computed value + * - Prevents constructor usage + * + * @template TModel - Model type + * @template Value - Computed value type + * + * @example + * ```ts + * const resolver: ValueResolver = + * (user) => `${user.firstName} ${user.lastName}`; + * ``` + */ +export type ValueResolver = StrictFunction<(model: TModel) => Value> + +/** + * Defines type mappings for model fields based on their data type. + * Uses distributive conditional types to handle union types correctly. + * + * #### Distributivity Importance + * Without distributivity: + * - Union types would fail (`string | number` wouldn't match either branch) + * - Complex types wouldn't resolve correctly + * + * @template FieldType - Type of the model field being defined + * + * @example Array Handling + * ```ts + * // Array of strings: + * type StringArrayDef = FieldDefinition; // GuardedStringArray + * + * // Array of models: + * type ModelArrayDef = FieldDefinition; + * // GuardedModelArray | Ref> + * ``` + * + * @example Primitive Handling + * ```ts + * type StringDef = FieldDefinition; // GuardedString + * type NumberDef = FieldDefinition; // GuardedNumber + * type BooleanDef = FieldDefinition; // GuardedBoolean + * ``` + */ +export type FieldDefinition = + // ARRAY (distributive check) + FieldType extends Array ? + ItemType extends string | String ? GuardedStringArray : + ItemType extends number | Number ? GuardedNumberArray : + ItemType extends boolean | Boolean ? GuardedBooleanArray : + ItemType extends Model ? GuardedModelArray : + ItemType extends EnumerableModel ? GuardedEnumerableModelArray : + never : + // REGULAR (distributive check) + FieldType extends string | String ? GuardedString : + FieldType extends number | Number ? GuardedNumber : + FieldType extends boolean | Boolean ? GuardedBoolean : + FieldType extends Model ? GuardedModel : + never; + +/** + * Reference wrapper type for lazy evaluation. + * Allows defining values as functions: `() => T` + */ +export type Reference = () => T + +export type Referable = T | Reference + +/** Primitive string type for field definitions */ +export type GuardedString = string + +/** Primitive number type for field definitions */ +export type GuardedNumber = number + +/** Primitive boolean type for field definitions */ +export type GuardedBoolean = boolean + +/** + * Model definition type for single model references. + * Represents a complete model definition. + */ +export type GuardedModel = ModelDefinition + +/** + * Definition for string arrays. + * Allows either: + * - Actual string array: `string[]` + * - TypeScript constructor hint: `[String]` or `[StringConstructor]` + */ +export type GuardedStringArray = string[] | [String | StringConstructor] + +/** + * Definition for number arrays. + * Allows either: + * - Actual number array: `number[]` + * - TypeScript constructor hint: `[Number]` or `[NumberConstructor]` + */ +export type GuardedNumberArray = number[] | [Number | NumberConstructor] + +/** + * Definition for boolean arrays. + * Allows either: + * - Actual boolean array: `boolean[]` + * - TypeScript constructor hint: `[Boolean]` or `[BooleanConstructor]` + */ +export type GuardedBooleanArray = boolean[] | [Boolean | BooleanConstructor] + +/** + * Definition for model arrays. + * Uses tuple form to capture model definition: `[ModelDefinition]` + */ +export type GuardedModelArray = [ModelDefinition] + +/** + * Special definition for enumerable model arrays. + * Adds loose mode option for non-strict relationships. + * + * ### Options + * - `[ModelDefinition]`: Strict relationship + * - `[ModelDefinition, { loose?: boolean }]`: Optional loose mode + */ +export type GuardedEnumerableModelArray = + | [ModelDefinition] + | [ModelDefinition, { loose?: boolean }] + +export type ModelValues = { + [property in keyof M]?: + NonNullable extends Array + ? Array>> | string | undefined> + : NonNullable extends Model + ? ModelValues> | string | undefined + : M[property]; }; -export type StorageResult = - | StorageValues +export type StorageResult = + | ModelValues | null | undefined; -export type Storage = { +export type Storage = { get?: (id: ModelIdentifier) => StorageResult | Promise>; set?: ( @@ -199,20 +553,20 @@ export type Storage = { }; // Enumerable - This overload must be the first one, then its signature and documentation will be displayed in intelephence by default. -export function store( - model: Model, +export function store( + model: ModelDefinition, options?: { draft?: false; id?: keyof E | ((host: E) => ModelIdentifier) }, ): Descriptor; // Enumerable Draft -export function store( - model: Model, +export function store( + model: ModelDefinition, options: { draft: true; id?: keyof E | ((host: E) => ModelIdentifier) }, ): Descriptor; // Enumerable Listing -export function store( - model: [Model], +export function store( + model: [ModelDefinition], options?: { draft?: false; id?: keyof E | ((host: E) => ModelIdentifier); @@ -221,90 +575,107 @@ export function store( ): Descriptor; // Singleton -export function store( - model: M extends Array ? never : Model, +export function store( + model: M extends Array ? never : ModelDefinition, options?: { draft?: false; id?: keyof E | ((host: E) => ModelIdentifier) }, ): Descriptor; // Singleton Draft -export function store( - model: M extends Array ? never : Model, +export function store( + model: M extends Array ? never : ModelDefinition, options: { draft: true; id?: keyof E | ((host: E) => ModelIdentifier) }, ): Descriptor; export namespace store { const connect = "__store__connect__"; - function get( - Model: Model, + function get( + Model: ModelDefinition, id?: ModelIdentifier, ): M; - function get( - Model: [Model], + function get( + Model: [ModelDefinition], id?: ModelIdentifier, ): M[]; - function set( - model: Model | M, - values: ModelValues | null, + function set( + model: ModelDefinition, + values: NoInfer>, + ): Promise; + function set( + model: ModelDefinition, + values: null, ): Promise; - function sync( - model: Model | M, - values: ModelValues | null, + function set( + model: M, + values: NoInfer | null>, + ): Promise; + + function sync( + model: ModelDefinition, + values: NoInfer>, ): M; - function clear( - model: Model | [Model] | M, + function sync( + model: ModelDefinition, + values: null, + ): M; + function sync( + model: M, + values: NoInfer | null>, + ): M; + + function clear( + model: ModelDefinition | [ModelDefinition] | M, clearValue?: boolean, ): void; - function pending(model: M): false | Promise; - function pending( + function pending(model: M): false | Promise; + function pending( ...models: Array ): false | Promise; - function error( + function error( model: M, propertyName?: keyof M | null, ): false | Error | any; - function ready(model: M): boolean; - function ready(...models: Array): boolean; + function ready(model: M): boolean; + function ready(...models: Array): boolean; - function submit( + function submit( draft: M, values?: ModelValues, ): Promise; - function resolve(model: M): Promise; - function resolve( - model: Model, + function resolve(model: M): Promise; + function resolve( + model: ModelDefinition, id?: ModelIdentifier, ): Promise; - function resolve( - model: [Model], + function resolve( + model: [ModelDefinition], id?: ModelIdentifier, ): Promise; - function ref(fn: () => T): () => T; + function ref(fn: Reference): Reference; - function record(value: V): Record; - function record {}>(value: V): Record>; + function record(value: FieldDefinition>): StoreRecord; - interface ValidateFunction { + interface ValidateFunction { (value: T, key: string, model: M): string | boolean | void; } - function value( + function value( defaultValue: string, validate?: ValidateFunction | RegExp, errorMessage?: string, ): string; - function value( + function value( defaultValue: number, validate?: ValidateFunction | RegExp, errorMessage?: string, ): number; - function value( + function value( defaultValue: boolean, validate?: ValidateFunction | RegExp, errorMessage?: string, @@ -368,15 +739,15 @@ export namespace router { export type Messages = { [key: string]: { message: - | string - | { - zero?: string; - one?: string; - two?: string; - few?: string; - many?: string; - other?: string; - }; + | string + | { + zero?: string; + one?: string; + two?: string; + few?: string; + many?: string; + other?: string; + }; description?: string; }; };