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

improved generics #242

Merged
merged 1 commit into from
May 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Change Log

- Added a simpler pattern for generic models when using `prop`.

## 0.57.1

- Fixed an issue when importing the package with expo.
Expand Down
81 changes: 68 additions & 13 deletions packages/lib/src/dataModel/DataModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,42 @@ import type { AbstractModelClass, ModelClass } from "../modelShared/BaseModelSha
import { sharedInternalModel } from "../modelShared/Model"
import type { ModelProps, ModelPropsToData, ModelPropsToSetter } from "../modelShared/prop"
import type { AnyDataModel, BaseDataModel, BaseDataModelKeys } from "./BaseDataModel"
import { assertIsDataModelClass } from "./utils"
import { assertIsDataModelClass, isDataModelClass } from "./utils"

declare const dataSymbol: unique symbol

declare const composedDataSymbol: unique symbol
export type _ComposedData<SuperModel, TProps extends ModelProps> = SuperModel extends BaseDataModel<
infer D
>
? ModelPropsToData<TProps> & D
: ModelPropsToData<TProps>

export interface _DataModel<SuperModel, TProps extends ModelProps> {
[dataSymbol]: ModelPropsToData<TProps>

[composedDataSymbol]: SuperModel extends BaseDataModel<infer D>
? this[typeof dataSymbol] & D
: this[typeof dataSymbol]

new (data: this[typeof composedDataSymbol]): SuperModel &
BaseDataModel<this[typeof dataSymbol]> &
Omit<this[typeof dataSymbol], BaseDataModelKeys> &
new (data: _ComposedData<SuperModel, TProps>): SuperModel &
BaseDataModel<ModelPropsToData<TProps>> &
Omit<ModelPropsToData<TProps>, BaseDataModelKeys> &
ModelPropsToSetter<TProps>
}

/**
* Base abstract class for data models that extends another model.
*
* @typeparam TProps New model properties type.
* @typeparam TModel Model type.
* @param genFn Function that returns the base model and model properties.
* @returns
*/
export function ExtendedDataModel<
TProps extends ModelProps,
TModel extends AnyDataModel,
A extends []
>(
genFn: (
...args: A
) => {
baseModel: AbstractModelClass<TModel>
props: TProps
}
): _DataModel<TModel, TProps>

/**
* Base abstract class for data models that extends another model.
*
Expand All @@ -33,12 +50,41 @@ export interface _DataModel<SuperModel, TProps extends ModelProps> {
export function ExtendedDataModel<TProps extends ModelProps, TModel extends AnyDataModel>(
baseModel: AbstractModelClass<TModel>,
modelProps: TProps
): _DataModel<TModel, TProps>

// base
export function ExtendedDataModel<TProps extends ModelProps, TModel extends AnyDataModel>(
...args: any[]
): _DataModel<TModel, TProps> {
let baseModel
let modelProps
if (isDataModelClass(args[0])) {
baseModel = args[0]
modelProps = args[1]
} else {
const gen = args[0]()

baseModel = gen.baseModel
modelProps = gen.props
}

assertIsDataModelClass(baseModel, "baseModel")

return internalDataModel(modelProps, baseModel as any)
}

/**
* Base abstract class for data models.
*
* Never override the constructor, use `onLazyInit` or `onLazyAttachedToRootStore` instead.
*
* @typeparam TProps Model properties type.
* @param fnModelProps Function that generates model properties.
*/
export function DataModel<TProps extends ModelProps, A extends []>(
fnModelProps: (...args: A) => TProps
): _DataModel<unknown, TProps>

/**
* Base abstract class for data models.
*
Expand All @@ -49,7 +95,16 @@ export function ExtendedDataModel<TProps extends ModelProps, TModel extends AnyD
*/
export function DataModel<TProps extends ModelProps>(
modelProps: TProps
): _DataModel<unknown, TProps>

// base
export function DataModel<TProps extends ModelProps>(
fnModelPropsOrModelProps: (() => TProps) | TProps
): _DataModel<unknown, TProps> {
const modelProps =
typeof fnModelPropsOrModelProps === "function"
? fnModelPropsOrModelProps()
: fnModelPropsOrModelProps
return internalDataModel(modelProps, undefined)
}

Expand Down
98 changes: 74 additions & 24 deletions packages/lib/src/model/Model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,23 @@ import {
ModelPropsToSetter,
} from "../modelShared/prop"
import type { AnyModel, BaseModel, BaseModelKeys } from "./BaseModel"
import { modelTypeKey } from "./metadata"
import { assertIsModelClass } from "./utils"
import { assertIsModelClass, isModelClass } from "./utils"

declare const dataSymbol: unique symbol

declare const creationDataSymbol: unique symbol

declare const composedCreationDataSymbol: unique symbol
export type _ComposedCreationData<
SuperModel,
TProps extends ModelProps
> = SuperModel extends BaseModel<any, infer CD, any>
? ModelPropsToCreationData<TProps> & CD
: ModelPropsToCreationData<TProps>

export interface _Model<SuperModel, TProps extends ModelProps> {
/**
* Model type name assigned to this class, or undefined if none.
*/
readonly [modelTypeKey]: string | undefined

[dataSymbol]: ModelPropsToData<TProps>

[creationDataSymbol]: ModelPropsToCreationData<TProps>

[composedCreationDataSymbol]: SuperModel extends BaseModel<any, infer CD, any>
? this[typeof creationDataSymbol] & CD
: this[typeof creationDataSymbol]

new (data: this[typeof composedCreationDataSymbol]): SuperModel &
new (data: _ComposedCreationData<SuperModel, TProps>): SuperModel &
BaseModel<
this[typeof dataSymbol],
this[typeof creationDataSymbol],
ModelPropsToData<TProps>,
ModelPropsToCreationData<TProps>,
ExtractModelIdProp<TProps> & string
> &
Omit<this[typeof dataSymbol], BaseModelKeys> &
Omit<ModelPropsToData<TProps>, BaseModelKeys> &
ModelPropsToSetter<TProps>
}

Expand All @@ -58,6 +45,25 @@ export type ExtractModelIdProp<TProps extends ModelProps> = {
[K in keyof TProps]: TProps[K]["$isId"] extends true ? K : never
}[keyof TProps]

/**
* Base abstract class for models that extends another model.
*
* @typeparam TProps New model properties type.
* @typeparam TModel Model type.
* @param genFn Function that returns the base model and model properties.
* @param modelOptions Model options.
* @returns
*/
export function ExtendedModel<TProps extends ModelProps, TModel extends AnyModel, A extends []>(
genFn: (
...args: A
) => {
baseModel: AbstractModelClass<TModel>
props: TProps
},
modelOptions?: ModelOptions
): _Model<TModel, AddModelIdPropIfNeeded<TProps>>

/**
* Base abstract class for models that extends another model.
*
Expand All @@ -72,12 +78,46 @@ export function ExtendedModel<TProps extends ModelProps, TModel extends AnyModel
baseModel: AbstractModelClass<TModel>,
modelProps: TProps,
modelOptions?: ModelOptions
): _Model<TModel, AddModelIdPropIfNeeded<TProps>>

// base
export function ExtendedModel<TProps extends ModelProps, TModel extends AnyModel>(
...args: any[]
): _Model<TModel, AddModelIdPropIfNeeded<TProps>> {
let baseModel
let modelProps
let modelOptions
if (isModelClass(args[0])) {
baseModel = args[0]
modelProps = args[1]
modelOptions = args[2]
} else {
const gen = args[0]()

baseModel = gen.baseModel
modelProps = gen.props
modelOptions = args[1]
}

assertIsModelClass(baseModel, "baseModel")

return internalModel(modelProps, baseModel as any, modelOptions)
}

/**
* Base abstract class for models.
*
* Never override the constructor, use `onInit` or `onAttachedToRootStore` instead.
*
* @typeparam TProps Model properties type.
* @param fnModelProps Function that generates model properties.
* @param modelOptions Model options.
*/
export function Model<TProps extends ModelProps, A extends []>(
fnModelProps: (...args: A) => TProps,
modelOptions?: ModelOptions
): _Model<unknown, AddModelIdPropIfNeeded<TProps>>

/**
* Base abstract class for models.
*
Expand All @@ -90,7 +130,17 @@ export function ExtendedModel<TProps extends ModelProps, TModel extends AnyModel
export function Model<TProps extends ModelProps>(
modelProps: TProps,
modelOptions?: ModelOptions
): _Model<unknown, AddModelIdPropIfNeeded<TProps>>

// base
export function Model<TProps extends ModelProps>(
fnModelPropsOrModelProps: (() => TProps) | TProps,
modelOptions?: ModelOptions
): _Model<unknown, AddModelIdPropIfNeeded<TProps>> {
const modelProps =
typeof fnModelPropsOrModelProps === "function"
? fnModelPropsOrModelProps()
: fnModelPropsOrModelProps
return internalModel(modelProps, undefined, modelOptions)
}

Expand Down
13 changes: 10 additions & 3 deletions packages/lib/src/modelShared/prop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,19 @@ export type ModelPropsToData<MP extends ModelProps> = {
[k in keyof MP]: MP[k]["$valueType"]
}

export type ModelPropsToCreationData<MP extends ModelProps> = O.Optional<
// we don't use O.Optional anymore since it generates unions too heavy
export type ModelPropsToCreationData<MP extends ModelProps> = O.Pick<
{
[k in keyof MP]: MP[k]["$creationValueType"]
[k in keyof MP]?: MP[k]["$creationValueType"]
},
OptionalModelProps<MP>
>
> &
O.Omit<
{
[k in keyof MP]: MP[k]["$creationValueType"]
},
OptionalModelProps<MP>
>

export type ModelPropsToSetter<MP extends ModelProps> = {
[k in keyof MP as MP[k]["$hasSetter"] & `set${Capitalize<k & string>}`]: (
Expand Down
32 changes: 32 additions & 0 deletions packages/lib/test/dataModel/dataModel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Model,
model,
modelAction,
modelClass,
ModelData,
modelFlow,
prop,
Expand Down Expand Up @@ -957,3 +958,34 @@ test("extends works", () => {
]
`)
})

test("new pattern for generics", () => {
@model("GenericModel")
class GenericModel<T1, T2> extends DataModel(<U1, U2>() => ({
v1: prop<U1>(),
v2: prop<U2>(),
v3: prop<number>(),
}))<T1, T2> {}

assert(_ as ModelData<GenericModel<string, number>>, _ as { v1: string; v2: number; v3: number })
assert(_ as ModelData<GenericModel<number, string>>, _ as { v1: number; v2: string; v3: number })

const s = new GenericModel<string, number>({ v1: "1", v2: 2, v3: 3 })
expect(s.v1).toBe("1")
expect(s.v2).toBe(2)
expect(s.v3).toBe(3)

@model("ExtendedGenericModel")
class ExtendedGenericModel<T1, T2> extends ExtendedDataModel(<T1, T2>() => ({
baseModel: modelClass<GenericModel<T1, T2>>(GenericModel),
props: {
v4: prop<T2>(),
},
}))<T1, T2> {}

const e = new ExtendedGenericModel<string, number>({ v1: "1", v2: 2, v3: 3, v4: 4 })
expect(e.v1).toBe("1")
expect(e.v2).toBe(2)
expect(e.v3).toBe(3)
expect(e.v4).toBe(4)
})
9 changes: 5 additions & 4 deletions packages/lib/test/model/defaultProps.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,16 @@ test("default props", () => {
yy?: number | null
yyy?: number | null

a: number
aa?: number
aaa: number | null
aaaa?: number | null

b: number
bb?: number
bbb: number | null
bbbb?: number | null
} & {
a: number
aaa: number | null
b: number
bbb: number | null
}
)

Expand Down
6 changes: 3 additions & 3 deletions packages/lib/test/model/modelDecorator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,13 @@ test("model decorator sets model type static prop and toString methods", () => {
x: number = 1 // not-stored-properties not rendered
}

expect(MyModel[modelTypeKey]).toBeUndefined()
expect((MyModel as any)[modelTypeKey]).toBeUndefined()

const type = "com/myModel"
const MyModel2 = model(type)(MyModel)

expect(MyModel[modelTypeKey]).toBe(type)
expect(MyModel2[modelTypeKey]).toBe(type)
expect((MyModel as any)[modelTypeKey]).toBe(type)
expect((MyModel2 as any)[modelTypeKey]).toBe(type)

expect(`${MyModel}`).toBe(`class MyModel#${type}`)
expect(`${MyModel2}`).toBe(`class MyModel#${type}`)
Expand Down
Loading