Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
Qsppl opened this issue Mar 19, 2024 · 18 comments
Closed

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

Qsppl opened this issue Mar 19, 2024 · 18 comments
Labels
typescript Applies to TypeScript support

Comments

@Qsppl
Copy link
Contributor

Qsppl commented Mar 19, 2024

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 Qsppl changed the title Using "store([Model])" causes a type error Using "store([Model])" causes a TypeScript error Mar 19, 2024
@Qsppl
Copy link
Contributor Author

Qsppl commented Mar 20, 2024

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
Copy link
Contributor

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
Copy link
Contributor Author

Qsppl commented Mar 20, 2024

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
Copy link
Contributor Author

Qsppl commented Mar 20, 2024

on typescript version: 4.9.5

  • first problem is solved
  • the second problem is not solved

@smalluban
Copy link
Contributor

smalluban commented Mar 20, 2024

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

@Qsppl
Copy link
Contributor Author

Qsppl commented Mar 20, 2024

thanks, it works

@Qsppl Qsppl closed this as completed Mar 20, 2024
@Qsppl
Copy link
Contributor Author

Qsppl commented Mar 20, 2024

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' }),
})

@Qsppl Qsppl reopened this Mar 20, 2024
@smalluban
Copy link
Contributor

Ok, I added this case: c15da30

@Qsppl
Copy link
Contributor Author

Qsppl commented Mar 20, 2024

thanks, it works

@Qsppl Qsppl closed this as completed Mar 20, 2024
@Qsppl
Copy link
Contributor Author

Qsppl commented Mar 20, 2024

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 Qsppl reopened this Mar 20, 2024
@Qsppl
Copy link
Contributor Author

Qsppl commented Mar 20, 2024

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
Copy link
Contributor

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
Copy link
Contributor Author

Qsppl commented Mar 21, 2024

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
Copy link
Contributor Author

Qsppl commented Mar 21, 2024

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

@smalluban
Copy link
Contributor

smalluban commented Mar 21, 2024

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
Copy link
Contributor Author

Qsppl commented Mar 21, 2024

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
Copy link
Contributor

smalluban commented Mar 21, 2024

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
Copy link
Contributor Author

Qsppl commented Mar 26, 2024

Problem solved.

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

commit: 64777f5

@Qsppl Qsppl closed this as completed Mar 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
typescript Applies to TypeScript support
Development

No branches or pull requests

2 participants