hybridsjs / hybrids

Extraordinary JavaScript UI framework with unique declarative and functional architecture
https://hybrids.js.org
MIT License
3.05k stars 85 forks source link

Using "store([Model])" causes a TypeScript error #236

Closed Qsppl closed 7 months ago

Qsppl commented 7 months ago
import { Model, define, store } from "https://esm.sh/hybrids@8.2.11"

export interface IModel {
    prop1: number
}

export const ModelStore: Model<IModel> = {
    id: true,
    prop1: 1
}

export interface IComponent extends HTMLElement {
    models: IModel[]
}

export default define<IComponent>({
    tag: "a-component",
    models: store([ModelStore]),
})

image

message:

Argument of type 'Model<IModel>[]' is not assignable to parameter of type 'Model<IModel[]>'.
  Type 'Model<IModel>[]' is not assignable to type '{ [x: number]: Model<IModel> | ((model: IModel[]) => IModel); [Symbol.iterator]: Model<() => IterableIterator<IModel>> | ((model: IModel[]) => () => IterableIterator<...>); ... 32 more ...; flat: Model<...> | ((model: IModel[]) => <A, D extends number = 1>(this: A, depth?: D | undefined) => FlatArray<...>[]); }'.
    Types of property '[Symbol.unscopables]' are incompatible.
      Type '{ [x: number]: boolean | undefined; length?: boolean | undefined; toString?: boolean | undefined; toLocaleString?: boolean | undefined; pop?: boolean | undefined; push?: boolean | undefined; ... 28 more ...; readonly [Symbol.unscopables]?: boolean | undefined; }' is not assignable to type 'Model<{ [x: number]: boolean | undefined; length?: boolean | undefined; toString?: boolean | undefined; toLocaleString?: boolean | undefined; pop?: boolean | undefined; push?: boolean | undefined; ... 28 more ...; readonly [Symbol.unscopables]?: boolean | undefined; }> | ((model: IModel[]) => { ...; })'.
        Type '{ [x: number]: boolean | undefined; length?: boolean | undefined; toString?: boolean | undefined; toLocaleString?: boolean | undefined; pop?: boolean | undefined; push?: boolean | undefined; ... 28 more ...; readonly [Symbol.unscopables]?: boolean | undefined; }' is not assignable to type 'Model<{ [x: number]: boolean | undefined; length?: boolean | undefined; toString?: boolean | undefined; toLocaleString?: boolean | undefined; pop?: boolean | undefined; push?: boolean | undefined; ... 28 more ...; readonly [Symbol.unscopables]?: boolean | undefined; }>'.
          Types of property 'toString' are incompatible.
            Type 'boolean | undefined' is not assignable to type '(boolean | ((model: { [x: number]: boolean | undefined; length?: boolean | undefined; toString?: boolean | undefined; toLocaleString?: boolean | undefined; pop?: boolean | undefined; ... 29 more ...; readonly [Symbol.unscopables]?: boolean | undefined; }) => boolean | undefined) | undefined) & (() => string)'.
              Type 'undefined' is not assignable to type '(boolean | ((model: { [x: number]: boolean | undefined; length?: boolean | undefined; toString?: boolean | undefined; toLocaleString?: boolean | undefined; pop?: boolean | undefined; ... 29 more ...; readonly [Symbol.unscopables]?: boolean | undefined; }) => boolean | undefined) | undefined) & (() => string)'. ts(2345)

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "strict": true,
    "baseUrl": "./web",
    "paths": {
      // "/" is not "C:/", but is "./web"
      "/*": [ "*" ],
      // from module to their declaration
      "/modules/fingerprintjsv3.js": ["../node_modules/@fingerprintjs/fingerprintjs/dist/fp"],
      "https://esm.sh/mitt@3.0.1": ["../node_modules/mitt/index"],
      "https://esm.sh/just-intersect@4.3.0": [ "../node_modules/just-intersect/index" ],
      "https://esm.sh/bootstrap@5.3.2": ["../node_modules/@types/bootstrap/index"],
      "https://esm.sh/hybrids@8.2.11": ["@types/hybrids"],
      "https://esm.sh/colorjs.io@0.4.5": ["../node_modules/colorjs.io/types/src/index"],
      "https://esm.sh/ts-debounce@4.0.0": ["../node_modules/ts-debounce/dist/src/index"],
      "https://esm.sh/mezr@1.1.0": ["../node_modules/mezr/dist/esm/index"]
    },
    "skipLibCheck": true
  },
  "include": [ "./web" ]
}

typescript version: "5.1.6"

Qsppl commented 7 months ago

In this case, an error also occurs. In reality, a handle can return undefined, but the types say that a handle always returns a model.

import { Model, define, store } from "https://esm.sh/hybrids@8.2.11"

export interface IModel {
    prop1: number
}

export const ModelStore: Model<IModel> = {
    id: true,
    prop1: 1
}

export interface IComponent extends HTMLElement {
    model: undefined | IModel
}

export default define<IComponent>({
    tag: "a-component",
    model: store(ModelStore),
})

image

message:

Type 'Descriptor<IComponent, IModel>' is not assignable to type 'Property<IComponent, IModel | undefined>'.
  Type 'Descriptor<IComponent, IModel>' is not assignable to type 'Descriptor<IComponent, IModel | undefined>'.
    Types of property 'set' are incompatible.
      Type '((host: IComponent & HTMLElement, value: any, lastValue: IModel) => any) | undefined' is not assignable to type '((host: IComponent & HTMLElement, value: any, lastValue: IModel | undefined) => any) | undefined'.
        Type '(host: IComponent & HTMLElement, value: any, lastValue: IModel) => any' is not assignable to type '(host: IComponent & HTMLElement, value: any, lastValue: IModel | undefined) => any'.
          Types of parameters 'lastValue' and 'lastValue' are incompatible.
            Type 'IModel | undefined' is not assignable to type 'IModel'.
              Type 'undefined' is not assignable to type 'IModel'. ts(2322)
smalluban commented 7 months ago

Sadly, it's a breaking change in TypeScript v5.0.0

For now, please use the latest v4 version of TS. I suppose that changing the definition for the v5 will break it for earlier versions, so I am confused about what to do with that. Do you know if there is a way to specify the TS version, which is supported by the project?

Qsppl commented 7 months ago

I looked in other node modules:

They all have the following content in package.json:

{
    ...
    "main": "",
    "types": "index.d.ts",
    "repository": {
        "type": "git",
        "url": "https://github.com/DefinitelyTyped/DefinitelyTyped.git",
        "directory": "types/bootstrap"
    },
    "typesPublisherContentHash": "c92ed4379251cc173e28be43e85f18041aa3e6ec2cd0d7b66e800b0f8a0b36a8",
    "typeScriptVersion": "4.5"
}

I don’t know how it works, but most likely something similar is described in DefinitelyTyped documentation.

Qsppl commented 7 months ago

on typescript version: 4.9.5

smalluban commented 7 months ago

Add id: string to your IModel definition. Currently, the store() type is used for the singleton model definition.

Qsppl commented 7 months ago

thanks, it works

Qsppl commented 7 months ago

But if you use store(ModelStore, { id: 'modelId' }), then the problem appears again.

import { Model, define, store } from "https://esm.sh/hybrids@8.2.11"

export interface IModel {
    id: number
    prop1: number
}

export const ModelStore: Model<IModel> = {
    id: true,
    prop1: 1
}

export interface IComponent extends HTMLElement {
    modelId: undefined | number
    model: undefined | IModel
}

export default define<IComponent>({
    tag: "a-component",
    modelId: undefined,
    model: store(ModelStore, { id: 'modelId' }),
})
smalluban commented 7 months ago

Ok, I added this case: https://github.com/hybridsjs/hybrids/commit/c15da309ae227a596f9f85e3ba759ee814e25b04

Qsppl commented 7 months ago

thanks, it works

Qsppl commented 7 months ago

This change causes an error when using store(ModelStore, { draft: true }).

import { Model, define, store } from "https://esm.sh/hybrids@8.2.11"

export interface IModel {
    id: number
    prop1: number
}

export const ModelStore: Model<IModel> = {
    id: true,
    prop1: 1
}

export interface IComponent extends HTMLElement {
    modelId: undefined | number
    model: undefined | IModel
    draft: IModel
}

export default define<IComponent>({
    tag: "a-component",
    modelId: undefined,
    model: store(ModelStore, { id: 'modelId' }),
    draft: store(ModelStore, { draft: true }),
})
Qsppl commented 7 months ago

Updated 20.03.2024 21:25

I seem to have solved this problem using overload:


// DELETED
// function store<E, M>(
//     Model: Model<M>,
// ): Descriptor<E, M extends { id: any } ? M | undefined : M>

// function store<E, M>(
//     Model: Model<M>,
//     options: {
//         id?: keyof E | ((host: E) => ModelIdentifier)
//         draft?: boolean
//     },
// ): Descriptor<E, M extends { id: any } ? M | undefined : M>

/** Enumerables and Listings */
export function store<E, M extends { id: any }>(
    Model: [Model<M>],
    options?: { id?: keyof E | ((host: E) => ModelIdentifier) },
): Descriptor<E, M[]>

/** Draft by Model */
export function store<E, M>(
    Model: Model<M>,
    options: { draft: true, id?: keyof E | ((host: E) => ModelIdentifier) },
): Descriptor<E, M>

/** Model */
export function store<E, M>(
    Model: Model<M>,
    options?: { draft?: false, id?: keyof E | ((host: E) => ModelIdentifier) },
): Descriptor<E, M extends { id: any } ? M | undefined : M>

Here is an example to run the test:

import { Model, define, store } from "https://esm.sh/hybrids@8.2.11"

export interface ISingleton {
    prop1: number
}

export const SingletonStore: Model<ISingleton> = {
    prop1: 0
}

export interface IModel {
    id: number
    prop1: number
}

export const ModelStore: Model<IModel> = {
    id: true,
    prop1: 1
}

export interface IComponent extends HTMLElement {
    modelId: undefined | number

    singleton: ISingleton
    singletonDraft: ISingleton
    model: undefined | IModel
    modelDraft: IModel

    singletonById: ISingleton
    singletonDraftById: ISingleton
    modelById: undefined | IModel
    modelDraftById: IModel

    singletonList: ISingleton[]
    singletonDraftList: ISingleton[]
    modelList: IModel[]
    modelDraftList: IModel[]

    singletonListById: ISingleton[]
    singletonDraftListById: ISingleton[]
    modelListById: IModel[]
    modelDraftListById: IModel[]
}

export default define<IComponent>({
    tag: "a-component",
    modelId: 0,

    singleton: store(SingletonStore),
    singletonDraft: store(SingletonStore, { draft: true }),
    model: store(ModelStore),
    modelDraft: store(ModelStore, { draft: true }),

    singletonById: store(SingletonStore, { id: 'modelId' }),
    singletonDraftById: store(SingletonStore, { id: 'modelId', draft: true }),
    modelById: store(ModelStore, { id: 'modelId' }),
    modelDraftById: store(ModelStore, { draft: true, id: 'modelId' }),

    /// @ts-expect-error
    singletonList: store([SingletonStore]),
    /// @ts-expect-error
    singletonDraftList: store([SingletonStore], { draft: true }),
    modelList: store([ModelStore]),
    modelDraftList: store([ModelStore], { draft: true }),

    /// @ts-expect-error
    singletonListById: store([SingletonStore], { id: 'modelId' }),
    /// @ts-expect-error
    singletonDraftListById: store([SingletonStore], { id: 'modelId', draft: true }),
    modelListById: store([ModelStore], { id: 'modelId' }),
    modelDraftListById: store([ModelStore], { id: 'modelId', draft: true }),
})

But I couldn't define the Model argument as an object, so the list of singletons goes into the corresponding overloads, but I expect that such an argument should return an error: image

Please note that if you leave JsDoc comments, they will be displayed to users in the code. image

smalluban commented 7 months ago

Good catch!

I should add some tests for those types, it must be possible (I'll add this to my todo list).

I updated the lastest commit with the overload version. You are always welcome to make a PR with a change, so you will have a chance to be a contributor ;)

Qsppl commented 7 months ago

I fixed the problem with the store([SingletonStore], ...) overload

/** Model */
function store<E, M extends { id: any } & object>(model: Model<M>, options?: { draft?: false, id?: keyof E | ((host: E) => number) }): Descriptor<E, M | undefined>
/** Model Draft */
function store<E, M extends { id: any } & object>(model: Model<M>, options: { draft: true, id?: keyof E | ((host: E) => number) }): Descriptor<E, M>

/** Model Listing */
function store<E, M extends { id: any } & object>(model: [Model<M>], options?: { draft?: false, id?: keyof E | ((host: E) => number) }): Descriptor<E, M[]>
/** Model Listing Draft */
function store<E, M extends { id: any } & object>(model: [Model<M>], options: { draft: true, id?: keyof E | ((host: E) => number) }): Descriptor<E, M[]>

/** Singleton */
function store<E, M extends { id?: never } & object>(model: Model<M> extends Array<any> ? never : Model<M>, options?: { draft?: false, id?: keyof E | ((host: E) => number) }): Descriptor<E, M>
/** Singleton Draft */
function store<E, M extends { id?: never } & object>(model: Model<M> extends Array<any> ? never : Model<M>, options: { draft: true, id?: keyof E | ((host: E) => number) }): Descriptor<E, M>

// Singleton Listing cannot exist!

Here is a new typing test for all invariants of this function:

import { Model, define, store } from "https://esm.sh/hybrids@8.2.11"

export interface ISingleton {
    prop: number
    length: number
}

export const SingletonStore: Model<ISingleton> = {
    prop: 0,
    length: 0,
}

export interface IModel {
    id: number
    prop: number
    length: number
}

export const ModelStore: Model<IModel> = {
    id: true,
    prop: 0,
    length: 0,
}

export interface IComponent extends HTMLElement {
    modelId: undefined | number

    singleton: ISingleton
    singletonDraft: ISingleton
    model: undefined | IModel
    modelDraft: IModel

    singletonById: ISingleton
    singletonDraftById: ISingleton
    modelById: undefined | IModel
    modelDraftById: IModel

    singletonList: ISingleton[]
    singletonDraftList: ISingleton[]
    modelList: IModel[]
    modelDraftList: IModel[]

    singletonListById: ISingleton[]
    singletonDraftListById: ISingleton[]
    modelListById: IModel[]
    modelDraftListById: IModel[]
}

export default define<IComponent>({
    tag: "a-component",
    modelId: 0,

    singleton: store(SingletonStore),
    singletonDraft: store(SingletonStore, { draft: true }),
    model: store(ModelStore),
    modelDraft: store(ModelStore, { draft: true }),

    singletonById: store(SingletonStore, { id: 'modelId' }),
    singletonDraftById: store(SingletonStore, { id: 'modelId', draft: true }),
    modelById: store(ModelStore, { id: 'modelId' }),
    modelDraftById: store(ModelStore, { draft: true, id: 'modelId' }),

    /// @ts-expect-error singleton cannot be list
    singletonList: store([SingletonStore]),
    /// @ts-expect-error singleton cannot be list
    singletonDraftList: store([SingletonStore], { draft: true }),
    modelList: store([ModelStore]),
    modelDraftList: store([ModelStore], { draft: true }),

    /// @ts-expect-error singleton cannot be list
    singletonListById: store([SingletonStore], { id: 'modelId' }),
    /// @ts-expect-error singleton cannot be list
    singletonDraftListById: store([SingletonStore], { id: 'modelId', draft: true }),
    modelListById: store([ModelStore], { id: 'modelId' }),
    modelDraftListById: store([ModelStore], { id: 'modelId', draft: true }),
})
Qsppl commented 7 months ago

Thank you, in the future I will send PRs for bugs that I can fix

smalluban commented 7 months ago

Your solution is almost perfect - listing does not work with draft, so there are 5 cases, not 6:

// Enumerable
function store<E, M extends { id: string } & object>(
  model: Model<M>,
  options?: { draft?: false; id?: keyof E | ((host: E) => number) },
): Descriptor<E, M | undefined>;

// Enumerable Draft
function store<E, M extends { id: string } & object>(
  model: Model<M>,
  options: { draft: true; id?: keyof E | ((host: E) => number) },
): Descriptor<E, M>;

// Enumerable Listing
function store<E, M extends { id: string } & object>(
  model: [Model<M>],
  options?: { draft?: false; id?: keyof E | ((host: E) => number) },
): Descriptor<E, M[]>;

// Singleton
function store<E, M extends { id?: never } & object>(
  model: Model<M> extends Array<any> ? never : Model<M>,
  options?: { draft?: false; id?: keyof E | ((host: E) => number) },
): Descriptor<E, M>;

// Singleton Draft
function store<E, M extends { id?: never } & object>(
  model: Model<M> extends Array<any> ? never : Model<M>,
  options: { draft: true; id?: keyof E | ((host: E) => number) },
): Descriptor<E, M>;

I was trying to protect from passing a model definition without id: true for a listening case, but this is a value, not a type - It would require adding an additional type like EnumerableModel, which then forces to set id: true. I gave up .. :P

However, with this change and another fixed issue, it should be possible to use TS v5.0+ again. Let me know if I am wrong.

Qsppl commented 7 months ago

I was trying to protect from passing a model definition without id: true for a listening case, but this is a value, not a type - It would require adding an additional type like EnumerableModel, which then forces to set id: true. I gave up .. :P

Here's proof that it works:

import { Model, ModelIdentifier } from "https://esm.sh/hybrids@8.2.11"

interface IEnumerableModel { id: any, prop: number }
interface ISingletonModel { prop: number }

// ################################### STORE BY `{ ... }` ###################################
const EnumerableInstance: IEnumerableModel = { id: true, prop: 0 }
const SingletonInstance: ISingletonModel = { prop: 0 }

/** Model */
function store<E, M extends { id: any } & object>(model: M, options?: { draft?: false, id?: keyof E | ((host: E) => ModelIdentifier) }): M | undefined
/** Model Draft */
function store<E, M extends { id: any } & object>(model: M, options: { draft: true, id?: keyof E | ((host: E) => ModelIdentifier) }): M

/** Model Listing */
function store<E, M extends { id: any } & object>(model: [M], options?: { id?: keyof E | ((host: E) => ModelIdentifier), loose?: boolean }): [M]

// Model Listing Draft cannot exist!

/** Singleton */
function store<E, M extends { id?: never } & object>(model: M extends Array<any> ? never : M, options?: { draft?: false, id?: keyof E | ((host: E) => ModelIdentifier) }): M
/** Singleton Draft */
function store<E, M extends { id?: never } & object>(model: M extends Array<any> ? never : M, options: { draft: true, id?: keyof E | ((host: E) => ModelIdentifier) }): M

// Singleton listing cannot exist!

function store(model: any, options?: any): any { }

const modelId: undefined | number = 0

const singleton: ISingletonModel = store(SingletonInstance)
const singletonDraft: ISingletonModel = store(SingletonInstance, { draft: true })
const model: undefined | IEnumerableModel = store(EnumerableInstance)
const modelDraft: IEnumerableModel = store(EnumerableInstance, { draft: true })

const singletonById: ISingletonModel = store(SingletonInstance, { id: 'modelId' })
const singletonDraftById: ISingletonModel = store(SingletonInstance, { id: 'modelId', draft: true })
const modelById: undefined | IEnumerableModel = store(EnumerableInstance, { id: 'modelId' })
const modelDraftById: IEnumerableModel = store(EnumerableInstance, { draft: true, id: 'modelId' })

const singletonList: ISingletonModel[] = store([SingletonInstance])
const singletonDraftList: ISingletonModel[] = store([SingletonInstance], { draft: true })
const modelList: IEnumerableModel[] = store([EnumerableInstance])
const modelDraftList: IEnumerableModel[] = store([EnumerableInstance], { draft: true })

const singletonListById: ISingletonModel[] = store([SingletonInstance], { id: 'modelId' })
const singletonDraftListById: ISingletonModel[] = store([SingletonInstance], { id: 'modelId', draft: true })
const modelListById: IEnumerableModel[] = store([EnumerableInstance], { id: 'modelId' })
const modelDraftListById: IEnumerableModel[] = store([EnumerableInstance], { id: 'modelId', draft: true })

image

You can't do this because of another bug:

// This does not cause an error! 
// This code should return the error "Model cannot be an array"
// Due to the fact that Model can be an array, typing breaks down in other places!
const Model: Model<any> = []

image

Here is proof that the problem is with the Model type:

import { Model, ModelIdentifier } from "https://esm.sh/hybrids@8.2.11"

interface IEnumerableModel { id: any, prop: number }
interface ISingletonModel { prop: number }

// ################################### STORE BY `Model<{ ... }>` ###################################

const EnumerableStore: Model<IEnumerableModel> = { id: true, prop: 0 }
const SingletonStore: Model<ISingletonModel> = { prop: 0 }

// This does not cause an error! 
// This code should return the error "Model cannot be an array"
// Due to the fact that Model can be an array, typing breaks down in other places!
const Model: Model<any> = []

/** Model */
function store<E, M extends { id: any } & object = never>(model: Model<M>, options?: { draft?: false, id?: keyof E | ((host: E) => ModelIdentifier) }): M | undefined
/** Model Draft */
function store<E, M extends { id: any } & object = never>(model: Model<M>, options: { draft: true, id?: keyof E | ((host: E) => ModelIdentifier) }): M

/** Model Listing */
function store<E, M extends { id: any } & object>(model: [Model<M>], options?: { draft?: false, id?: keyof E | ((host: E) => ModelIdentifier), loose?: boolean }): [M]
// Model Listing Draft cannot exist!

/** Singleton */
function store<E, M extends { id?: never } & object>(model: M extends Array<any> ? never : Model<M>, options?: { draft?: false, id?: keyof E | ((host: E) => ModelIdentifier) }): M
/** Singleton Draft */
function store<E, M extends { id?: never } & object>(model: M extends Array<any> ? never : Model<M>, options: { draft: true, id?: keyof E | ((host: E) => ModelIdentifier) }): M

// Singleton listing cannot exist!

function store(model: any, options?: any): any { }

const modelId: undefined | number = 0

const singleton: ISingletonModel = store(SingletonStore)
const singletonDraft: ISingletonModel = store(SingletonStore, { draft: true })
const model: undefined | IEnumerableModel = store(EnumerableStore)
const modelDraft: IEnumerableModel = store(EnumerableStore, { draft: true })

const singletonById: ISingletonModel = store(SingletonStore, { id: 'modelId' })
const singletonDraftById: ISingletonModel = store(SingletonStore, { id: 'modelId', draft: true })
const modelById: undefined | IEnumerableModel = store(EnumerableStore, { id: 'modelId' })
const modelDraftById: IEnumerableModel = store(EnumerableStore, { draft: true, id: 'modelId' })

const singletonList: ISingletonModel[] = store([SingletonStore])
const singletonDraftList: ISingletonModel[] = store([SingletonStore], { draft: true })
const modelList: IEnumerableModel[] = store([EnumerableStore])
const modelDraftList: IEnumerableModel[] = store([EnumerableStore], { draft: true })

const singletonListById: ISingletonModel[] = store([SingletonStore], { id: 'modelId' })
const singletonDraftListById: ISingletonModel[] = store([SingletonStore], { id: 'modelId', draft: true })
const modelListById: IEnumerableModel[] = store([EnumerableStore], { id: 'modelId' })
const modelDraftListById: IEnumerableModel[] = store([EnumerableStore], { id: 'modelId', draft: true })

image

If in the same code you use any other type instead of Model, then everything works.

import { Model, ModelIdentifier } from "https://esm.sh/hybrids@8.2.11"

interface IEnumerableModel { id: any, prop: number }
interface ISingletonModel { prop: number }

// ################################### STORE BY `Type<{ ... }>` ###################################
type Type<M> = { a: M }
const EnumerableInstance: Type<IEnumerableModel> = { a: { id: true, prop: 0 } }
const SingletonInstance: Type<ISingletonModel> = { a: { prop: 0 } }

/** Model */
function store<E, M extends { id: any } & object>(model: Type<M>, options?: { draft?: false, id?: keyof E | ((host: E) => ModelIdentifier) }): M | undefined
/** Model Draft */
function store<E, M extends { id: any } & object>(model: Type<M>, options: { draft: true, id?: keyof E | ((host: E) => ModelIdentifier) }): M

/** Model Listing */
function store<E, M extends { id: any } & object>(model: [Type<M>], options?: { draft?: false, id?: keyof E | ((host: E) => ModelIdentifier), loose?: boolean }): [M]
// Model Listing Draft cannot exist!

/** Singleton */
function store<E, M extends { id?: never } & object>(model: M extends Array<any> ? never : Type<M>, options?: { draft?: false, id?: keyof E | ((host: E) => ModelIdentifier) }): M
/** Singleton Draft */
function store<E, M extends { id?: never } & object>(model: M extends Array<any> ? never : Type<M>, options: { draft: true, id?: keyof E | ((host: E) => ModelIdentifier) }): M

// Singleton listing cannot exist!

function store(model: any, options?: any): any { }

const modelId: undefined | number = 0

const singleton: ISingletonModel = store(SingletonInstance)
const singletonDraft: ISingletonModel = store(SingletonInstance, { draft: true })
const model: undefined | IEnumerableModel = store(EnumerableInstance)
const modelDraft: IEnumerableModel = store(EnumerableInstance, { draft: true })

const singletonById: ISingletonModel = store(SingletonInstance, { id: 'modelId' })
const singletonDraftById: ISingletonModel = store(SingletonInstance, { id: 'modelId', draft: true })
const modelById: undefined | IEnumerableModel = store(EnumerableInstance, { id: 'modelId' })
const modelDraftById: IEnumerableModel = store(EnumerableInstance, { draft: true, id: 'modelId' })

const singletonList: ISingletonModel[] = store([SingletonInstance])
const singletonDraftList: ISingletonModel[] = store([SingletonInstance], { draft: true })
const modelList: IEnumerableModel[] = store([EnumerableInstance])
const modelDraftList: IEnumerableModel[] = store([EnumerableInstance], { draft: true })

const singletonListById: ISingletonModel[] = store([SingletonInstance], { id: 'modelId' })
const singletonDraftListById: ISingletonModel[] = store([SingletonInstance], { id: 'modelId', draft: true })
const modelListById: IEnumerableModel[] = store([EnumerableInstance], { id: 'modelId' })
const modelDraftListById: IEnumerableModel[] = store([EnumerableInstance], { id: 'modelId', draft: true })

image

smalluban commented 7 months ago

In short, for me, the most important is to ensure that the correct case works in TypeScript - this is essentially required to make it work with TS.

Preventing from passing wrong arguments is great, but I would say "nice to have". The library protects from it and throws errors in runtime.

BTW, Your examples are so detailed, that I see them hard to reason about and focus on a problem that you want to address.

There were some bugs in the latest changes, so other stuff missing, please try out v8.2.14.

Qsppl commented 7 months ago

Problem solved.

I also sent to PR a version of the store() function, protected from passing arguments like store([SingletonStore]).

commit: https://github.com/hybridsjs/hybrids/commit/64777f5e1afbdd58d28767a0b6ef7dc227c121dc