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

Add two new primitives for MST #2052

Merged
merged 6 commits into from
Aug 10, 2023
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
129 changes: 89 additions & 40 deletions docs/API/index.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion docs/overview/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Note that since MST v3 `types.array` and `types.map` are wrapped in `types.optio
- [`types.string`](/API/#const-string)
- [`types.number`](/API/#const-number)
- [`types.integer`](/API/#const-integer)
- [`types.float`](/API/#const-float)
- [`types.finite`](/API/#const-finite)
- [`types.boolean`](/API/#const-boolean)
- [`types.Date`](/API/#const-dateprimitive)
- [`types.custom`](/API/#custom) creates a custom primitive type. This is useful to define your own types that map a serialized form one-to-one to an immutable object like a Decimal or Date.
Expand Down Expand Up @@ -92,4 +94,3 @@ Property types can only be used as a direct member of a `types.model` type and n

- [`types.identifier`](/API/#const-identifier) Only one such member can exist in a `types.model` and should uniquely identify the object. See [identifiers](/concepts/references#identifiers) for more details.
- [`types.identifierNumber`](/API/#const-identifiernumber) Similar to `types.identifier`. However, during serialization, the identifier value will be parsed from / serialized to a number.

2 changes: 2 additions & 0 deletions packages/mobx-state-tree/__tests__/core/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ const TYPES = stringToArray(`
boolean,
number,
integer,
float,
finite,
Date,
map,
array,
Expand Down
28 changes: 26 additions & 2 deletions packages/mobx-state-tree/__tests__/core/primitives.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isInteger } from "../../src/utils"
import { isFinite, isFloat, isInteger } from "../../src/utils"
import { types, applySnapshot, getSnapshot } from "../../src"

test("Date instance can be reused", () => {
Expand Down Expand Up @@ -48,7 +48,7 @@ test("Date can be rehydrated using unix timestamp", () => {
expect(getSnapshot(store).date).toBe(newTime)
})

test("isInteger polyfill", () => {
test("check isInteger", () => {
expect(isInteger(5)).toBe(true)
expect(isInteger(-5)).toBe(true)
expect(isInteger(5.2)).toBe(false)
Expand All @@ -65,6 +65,30 @@ test("Default inference for integers is 'number'", () => {
).toBe(true)
})

test("check isFloat", () => {
expect(isFloat(3.14)).toBe(true)
expect(isFloat(-2.5)).toBe(true)
expect(isFloat(Infinity)).toBe(true)
expect(isFloat(10)).toBe(false)
expect(isFloat(0)).toBe(false)
expect(isFloat("3.14")).toBe(false)
expect(isFloat(null)).toBe(false)
expect(isFloat(undefined)).toBe(false)
expect(isFloat(NaN)).toBe(false)
})

test("check isFinite", () => {
expect(isFinite(3.14)).toBe(true)
expect(isFinite(-2.5)).toBe(true)
expect(isFinite(10)).toBe(true)
expect(isFinite(0)).toBe(true)
expect(isFinite("3.14")).toBe(false)
expect(isFinite(null)).toBe(false)
expect(isFinite(undefined)).toBe(false)
expect(isFinite(NaN)).toBe(false)
expect(isFinite(Infinity)).toBe(false)
})

if (process.env.NODE_ENV !== "production") {
test("Passing non integer to types.integer", () => {
const Size = types.model({
Expand Down
4 changes: 3 additions & 1 deletion packages/mobx-state-tree/src/core/type/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ export enum TypeFlags {
Integer = 1 << 17,
Custom = 1 << 18,
SnapshotProcessor = 1 << 19,
Lazy = 1 << 20
Lazy = 1 << 20,
Finite = 1 << 21,
Float = 1 << 22
}

/**
Expand Down
38 changes: 22 additions & 16 deletions packages/mobx-state-tree/src/types/complex-types/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,10 @@ export enum MapIdentifierMode {
}

class MSTMap<IT extends IAnyType> extends ObservableMap<string, any> {
constructor(initialData?: [string, any][] | IKeyValueMap<any> | Map<string, any> | undefined, name?: string) {
constructor(
initialData?: [string, any][] | IKeyValueMap<any> | Map<string, any> | undefined,
name?: string
) {
super(initialData, (observable.ref as any).enhancer, name)
}

Expand Down Expand Up @@ -248,15 +251,18 @@ export class MapType<IT extends IAnyType> extends ComplexType<

const modelTypes: IAnyModelType[] = []
if (tryCollectModelTypes(this._subType, modelTypes)) {
const identifierAttribute: string | undefined = modelTypes.reduce((current: IAnyModelType["identifierAttribute"], type) => {
if (!type.identifierAttribute) return current
if (current && current !== type.identifierAttribute) {
throw fail(
`The objects in a map should all have the same identifier attribute, expected '${current}', but child of type '${type.name}' declared attribute '${type.identifierAttribute}' as identifier`
)
}
return type.identifierAttribute
}, undefined as IAnyModelType["identifierAttribute"])
const identifierAttribute: string | undefined = modelTypes.reduce(
(current: IAnyModelType["identifierAttribute"], type) => {
if (!type.identifierAttribute) return current
if (current && current !== type.identifierAttribute) {
throw fail(
`The objects in a map should all have the same identifier attribute, expected '${current}', but child of type '${type.name}' declared attribute '${type.identifierAttribute}' as identifier`
)
}
return type.identifierAttribute
},
undefined as IAnyModelType["identifierAttribute"]
)

if (identifierAttribute) {
this.identifierMode = MapIdentifierMode.YES
Expand Down Expand Up @@ -286,15 +292,15 @@ export class MapType<IT extends IAnyType> extends ComplexType<

const type = node.type as this
type.hookInitializers.forEach((initializer) => {
const hooks = initializer((instance as unknown) as IMSTMap<IT>)
const hooks = initializer(instance as unknown as IMSTMap<IT>)
Object.keys(hooks).forEach((name) => {
const hook = hooks[name as keyof typeof hooks]!
const actionInvoker = createActionInvoker(instance as IAnyStateTreeNode, name, hook)
; (!devMode() ? addHiddenFinalProp : addHiddenWritableProp)(
instance,
name,
actionInvoker
)
;(!devMode() ? addHiddenFinalProp : addHiddenWritableProp)(
instance,
name,
actionInvoker
)
})
})

Expand Down
4 changes: 3 additions & 1 deletion packages/mobx-state-tree/src/types/complex-types/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -738,7 +738,9 @@ export function model<P extends ModelPropertiesDeclaration = {}>(
*/
export function model(...args: any[]): any {
if (devMode() && typeof args[0] !== "string" && args[1]) {
throw fail("Model creation failed. First argument must be a string when two arguments are provided")
throw fail(
"Model creation failed. First argument must be a string when two arguments are provided"
)
}

const name = typeof args[0] === "string" ? args.shift() : "AnonymousModel"
Expand Down
4 changes: 4 additions & 0 deletions packages/mobx-state-tree/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
boolean,
number,
integer,
float,
finite,
DatePrimitive,
map,
array,
Expand Down Expand Up @@ -46,6 +48,8 @@ export const types = {
boolean,
number,
integer,
float,
finite,
Date: DatePrimitive,
map,
array,
Expand Down
41 changes: 39 additions & 2 deletions packages/mobx-state-tree/src/types/primitives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import {
isType,
isInteger,
AnyObjectNode,
AnyNode
AnyNode,
isFloat,
isFinite
} from "../internal"

// TODO: implement CoreType using types.custom ?
Expand Down Expand Up @@ -99,7 +101,6 @@ export const number: ISimpleType<number> = new CoreType<number, number, number>(

/**
* `types.integer` - Creates a type that can only contain an integer value.
* This type is used for integer values by default
*
* Example:
* ```ts
Expand All @@ -116,6 +117,42 @@ export const integer: ISimpleType<number> = new CoreType<number, number, number>
(v) => isInteger(v)
)

/**
* `types.float` - Creates a type that can only contain an float value.
*
* Example:
* ```ts
* const Size = types.model({
* width: types.float,
* height: 10
* })
* ```
*/
// tslint:disable-next-line:variable-name
export const float: ISimpleType<number> = new CoreType<number, number, number>(
"float",
TypeFlags.Float,
(v) => isFloat(v)
)

/**
* `types.finite` - Creates a type that can only contain an finite value.
*
* Example:
* ```ts
* const Size = types.model({
* width: types.finite,
* height: 10
* })
* ```
*/
// tslint:disable-next-line:variable-name
export const finite: ISimpleType<number> = new CoreType<number, number, number>(
"finite",
TypeFlags.Finite,
(v) => isFinite(v)
)

/**
* `types.boolean` - Creates a type that can only contain a boolean value.
* This type is used for boolean values by default
Expand Down
Loading