Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

mobx-keystone

Package Overview
Dependencies
Maintainers
1
Versions
195
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mobx-keystone - npm Package Compare versions

Comparing version 0.60.7 to 0.61.0

dist/transforms/asMap.d.ts

8

CHANGELOG.md
# Change Log
## 0.61.0
- Added `withTransform()`, a new way to do property transforms, as well as the pre-made transforms `timestampToDateTransform`, `isoStringToDateTransform`, `objectToMapTransform`, `arrayToMapTransform` and `arrayToSetTransform`. See the docs `Maps, Sets, Dates` section for more info.
- Data models can now use default property initializers.
- Added `apply` and `applyComputed` to contexts so that they can be used to share information with models on creation time (kind of like `envs` in Mobx-state-tree). See the context docs for more details.
- Fixed an issue with `asMap` where the map would not get updated when the backing array/object changed.
- Fixed an issue with `asSet` where the set would not get updated when the backing array changed.
## 0.60.7

@@ -4,0 +12,0 @@

2

dist/actionMiddlewares/undoMiddleware.d.ts

@@ -81,3 +81,3 @@ import type { ActionMiddlewareDisposer } from "../action/middleware";

} & {
$modelId: import("..").ModelProp<string, string, string, true, never>;
$modelId: import("..").ModelProp<string, string, string, string, string, true, never>;
}>;

@@ -84,0 +84,0 @@ /**

@@ -49,2 +49,22 @@ /**

unset(node: object): void;
/**
* Applies a value override while the given function is running and, if a node is returned,
* sets the node as a provider of the value.
*
* @typeparam R
* @param fn Function to run.
* @param value Value to apply.
* @returns The value returned from the function.
*/
apply<R>(fn: () => R, value: T): R;
/**
* Applies a computed value override while the given function is running and, if a node is returned,
* sets the node as a provider of the comptued value.
*
* @typeparam R
* @param fn Function to run.
* @param value Value to apply.
* @returns The value returned from the function.
*/
applyComputed<R>(fn: () => R, valueFn: () => T): R;
}

@@ -51,0 +71,0 @@ /**

@@ -1,2 +0,2 @@

import { dataTypeSymbol } from "../modelShared/BaseModelShared";
import { creationDataTypeSymbol, dataTypeSymbol, transformedCreationDataTypeSymbol, transformedDataTypeSymbol } from "../modelShared/BaseModelShared";
import type { TypeCheckError } from "../typeChecking/TypeCheckError";

@@ -12,4 +12,13 @@ /**

[k: string]: any;
}, CreationData extends {
[k: string]: any;
}, TransformedData extends {
[k: string]: any;
}, TransformedCreationData extends {
[k: string]: any;
}> {
[dataTypeSymbol]: Data;
[creationDataTypeSymbol]: Data;
[transformedDataTypeSymbol]: TransformedData;
[transformedCreationDataTypeSymbol]: Data;
/**

@@ -35,3 +44,3 @@ * Called after the instance is created when there's the first call to `fn(M, data)`.

*/
constructor(data: Data);
constructor(data: CreationData | TransformedCreationData);
toString(options?: {

@@ -48,3 +57,3 @@ withData?: boolean;

*/
export interface AnyDataModel extends BaseDataModel<any> {
export interface AnyDataModel extends BaseDataModel<any, any, any, any> {
}

@@ -51,0 +60,0 @@ /**

import type { AbstractModelClass } from "../modelShared/BaseModelShared";
import type { ModelProps, ModelPropsToData, ModelPropsToSetter } from "../modelShared/prop";
import type { ModelProps, ModelPropsToCreationData, ModelPropsToData, ModelPropsToSetter, ModelPropsToTransformedCreationData, ModelPropsToTransformedData } from "../modelShared/prop";
import type { AnyDataModel, BaseDataModel, BaseDataModelKeys } from "./BaseDataModel";
export declare type _ComposedData<SuperModel, TProps extends ModelProps> = SuperModel extends BaseDataModel<infer D> ? ModelPropsToData<TProps> & D : ModelPropsToData<TProps>;
export declare type _ComposedData<SuperModel, TProps extends ModelProps> = SuperModel extends BaseDataModel<any, infer CD, any, infer CTD> ? (ModelPropsToCreationData<TProps> & CD) | (ModelPropsToTransformedCreationData<TProps> & CTD) : ModelPropsToCreationData<TProps> | ModelPropsToTransformedCreationData<TProps>;
export interface _DataModel<SuperModel, TProps extends ModelProps> {
new (data: _ComposedData<SuperModel, TProps>): SuperModel & BaseDataModel<ModelPropsToData<TProps>> & Omit<ModelPropsToData<TProps>, BaseDataModelKeys> & ModelPropsToSetter<TProps>;
new (data: _ComposedData<SuperModel, TProps>): SuperModel & BaseDataModel<ModelPropsToData<TProps>, ModelPropsToCreationData<TProps>, ModelPropsToTransformedData<TProps>, ModelPropsToTransformedCreationData<TProps>> & Omit<ModelPropsToTransformedData<TProps>, BaseDataModelKeys> & ModelPropsToSetter<TProps>;
}

@@ -8,0 +8,0 @@ /**

@@ -16,2 +16,3 @@ export * from "./action";

export * from "./standardActions";
export * from "./transforms";
export * from "./treeUtils";

@@ -18,0 +19,0 @@ export * from "./tweaker";

import type { O } from "ts-toolbelt";
import { creationDataTypeSymbol, dataTypeSymbol, ModelClass } from "../modelShared/BaseModelShared";
import { creationDataTypeSymbol, dataTypeSymbol, ModelClass, transformedCreationDataTypeSymbol, transformedDataTypeSymbol } from "../modelShared/BaseModelShared";
import type { SnapshotInOfModel, SnapshotInOfObject, SnapshotOutOfModel } from "../snapshot/SnapshotOf";

@@ -23,5 +23,11 @@ import type { TypeCheckError } from "../typeChecking/TypeCheckError";

[k: string]: any;
}, TransformedData extends {
[k: string]: any;
}, TransformedCreationData extends {
[k: string]: any;
}, ModelIdPropertyName extends string = typeof modelIdKey> {
[dataTypeSymbol]: Data;
[creationDataTypeSymbol]: CreationData;
[transformedDataTypeSymbol]: TransformedData;
[transformedCreationDataTypeSymbol]: TransformedCreationData;
[modelIdPropertyNameSymbol]: ModelIdPropertyName;

@@ -86,3 +92,3 @@ /**

*/
constructor(data: CreationData);
constructor(data: TransformedCreationData);
toString(options?: {

@@ -99,3 +105,3 @@ withData?: boolean;

*/
export interface AnyModel extends BaseModel<any, any, any> {
export interface AnyModel extends BaseModel<any, any, any, any, any> {
}

@@ -102,0 +108,0 @@ /**

import type { AbstractModelClass } from "../modelShared/BaseModelShared";
import { idProp, ModelProps, ModelPropsToCreationData, ModelPropsToData, ModelPropsToSetter } from "../modelShared/prop";
import { idProp, ModelProps, ModelPropsToCreationData, ModelPropsToData, ModelPropsToSetter, ModelPropsToTransformedCreationData, ModelPropsToTransformedData } from "../modelShared/prop";
import type { AnyModel, BaseModel, BaseModelKeys, ModelIdPropertyName } from "./BaseModel";
export declare type _ComposedCreationData<SuperModel, TProps extends ModelProps> = SuperModel extends BaseModel<any, infer CD, any> ? ModelPropsToCreationData<TProps> & CD : ModelPropsToCreationData<TProps>;
export declare type _ComposedCreationData<SuperModel, TProps extends ModelProps> = SuperModel extends BaseModel<any, any, any, infer TCD> ? ModelPropsToTransformedCreationData<TProps> & TCD : ModelPropsToTransformedCreationData<TProps>;
export declare type _ModelId<SuperModel, TProps extends ModelProps> = SuperModel extends AnyModel ? ModelIdPropertyName<SuperModel> : ExtractModelIdProp<TProps> & string;
export interface _Model<SuperModel, TProps extends ModelProps> {
new (data: _ComposedCreationData<SuperModel, TProps>): SuperModel & BaseModel<ModelPropsToData<TProps>, ModelPropsToCreationData<TProps>, _ModelId<SuperModel, TProps>> & Omit<ModelPropsToData<TProps>, BaseModelKeys> & ModelPropsToSetter<TProps>;
new (data: _ComposedCreationData<SuperModel, TProps>): SuperModel & BaseModel<ModelPropsToData<TProps>, ModelPropsToCreationData<TProps>, ModelPropsToTransformedData<TProps>, ModelPropsToTransformedCreationData<TProps>, _ModelId<SuperModel, TProps>> & Omit<ModelPropsToTransformedData<TProps>, BaseModelKeys> & ModelPropsToSetter<TProps>;
}

@@ -9,0 +9,0 @@ /**

@@ -12,2 +12,10 @@ import type { AnyDataModel } from "../dataModel/BaseDataModel";

/**
* @ignore
*/
export declare const transformedDataTypeSymbol: unique symbol;
/**
* @ignore
*/
export declare const transformedCreationDataTypeSymbol: unique symbol;
/**
* Extracts the instance type of a model class.

@@ -29,4 +37,12 @@ */

*/
export declare type ModelCreationData<M extends AnyModel> = M[typeof creationDataTypeSymbol];
export declare type ModelCreationData<M extends AnyModel | AnyDataModel> = M[typeof creationDataTypeSymbol];
/**
* The transformed data type of a model.
*/
export declare type ModelTransformedData<M extends AnyModel | AnyDataModel> = M[typeof transformedDataTypeSymbol];
/**
* The transformed creation data type of a model.
*/
export declare type ModelTransformedCreationData<M extends AnyModel | AnyDataModel> = M[typeof transformedCreationDataTypeSymbol];
/**
* Tricks Typescript into accepting a particular kind of generic class as a parameter for `ExtendedModel`.

@@ -33,0 +49,0 @@ * Does nothing in runtime.

import { AnyDataModel } from "../dataModel/BaseDataModel";
import { AnyModel } from "../model/BaseModel";
import { ModelClass, ModelData } from "./BaseModelShared";
import { ModelClass } from "./BaseModelShared";
import { AnyModelProp, ModelProps } from "./prop";
export declare function createModelPropDescriptor(modelPropName: string, modelProp: AnyModelProp | undefined, enumerable: boolean): PropertyDescriptor;
export declare function getModelInstanceDataField<M extends AnyModel | AnyDataModel>(model: M, _modelProp: AnyModelProp | undefined, modelPropName: keyof ModelData<M>): ModelData<M>[typeof modelPropName];
export declare function setModelInstanceDataField<M extends AnyModel | AnyDataModel>(model: M, modelProp: AnyModelProp | undefined, modelPropName: keyof ModelData<M>, value: ModelData<M>[typeof modelPropName]): void;
export declare function sharedInternalModel<TProps extends ModelProps, TBaseModel extends AnyModel | AnyDataModel>({ modelProps, baseModel, type, valueType, }: {

@@ -9,0 +7,0 @@ modelProps: TProps;

@@ -11,5 +11,7 @@ import type { O } from "ts-toolbelt";

*/
export interface ModelProp<TPropValue, TPropCreationValue, TIsOptional, TIsId extends boolean = false, THasSetter = never> {
export interface ModelProp<TPropValue, TPropCreationValue, TTransformedValue, TTransformedCreationValue, TIsOptional, TIsId extends boolean = false, THasSetter = never> {
$valueType: TPropValue;
$creationValueType: TPropCreationValue;
$transformedValueType: TTransformedValue;
$transformedCreationValueType: TTransformedCreationValue;
$isOptional: TIsOptional;

@@ -23,12 +25,38 @@ $isId: TIsId;

isId: boolean;
withSetter(): ModelPropWithSetter<this>;
transform: {
transform(original: unknown, model: object, propName: PropertyKey, setOriginalValue: (newOriginalValue: unknown) => void): unknown;
untransform(transformed: unknown, model: object, propName: PropertyKey): unknown;
} | undefined;
withSetter(): ModelProp<TPropValue, TPropCreationValue, TTransformedValue, TTransformedCreationValue, TIsOptional, TIsId, string>;
/**
* @deprecated Setter methods are preferred.
*/
withSetter(mode: "assign"): ModelPropWithSetter<this>;
withSetter(mode: "assign"): ModelProp<TPropValue, TPropCreationValue, TTransformedValue, TTransformedCreationValue, TIsOptional, TIsId, string>;
/**
* Sets a transform for the property.
*
* @typeparam TTV Transformed value type.
* @param transform Transform to be used.
* @returns
*/
withTransform<TTV>(transform: ModelPropTransform<NonNullable<TPropValue>, TTV>): ModelProp<TPropValue, TPropCreationValue, TTV | Extract<TPropValue, null | undefined>, TTV | Extract<TPropCreationValue, null | undefined>, TIsOptional, TIsId, THasSetter>;
}
/**
* A model prop transform.
*/
export interface ModelPropTransform<TOriginal, TTransformed> {
transform(params: {
originalValue: TOriginal;
cachedTransformedValue: TTransformed | undefined;
setOriginalValue(value: TOriginal): void;
}): TTransformed;
untransform(params: {
transformedValue: TTransformed;
cacheTransformedValue(): void;
}): TOriginal;
}
/**
* Any model property.
*/
export declare type AnyModelProp = ModelProp<any, any, any, any, any>;
export declare type AnyModelProp = ModelProp<any, any, any, any, any, any, any>;
/**

@@ -51,4 +79,12 @@ * Model properties.

}, OptionalModelProps<MP>>;
export declare type ModelPropsToTransformedData<MP extends ModelProps> = {
[k in keyof MP]: MP[k]["$transformedValueType"];
};
export declare type ModelPropsToTransformedCreationData<MP extends ModelProps> = {
[k in keyof MP]?: MP[k]["$transformedCreationValueType"];
} & O.Omit<{
[k in keyof MP]: MP[k]["$transformedCreationValueType"];
}, OptionalModelProps<MP>>;
export declare type ModelPropsToSetter<MP extends ModelProps> = {
[k in keyof MP as MP[k]["$hasSetter"] & `set${Capitalize<k & string>}`]: (value: MP[k]["$valueType"]) => void;
[k in keyof MP as MP[k]["$hasSetter"] & `set${Capitalize<k & string>}`]: (value: MP[k]["$transformedValueType"]) => void;
};

@@ -59,3 +95,3 @@ /**

*/
export declare const idProp: ModelProp<string, string, string, true, never>;
export declare const idProp: ModelProp<string, string, string, string, string, true, never>;
/**

@@ -68,14 +104,8 @@ * @ignore

*/
export declare type MaybeOptionalModelProp<TPropValue> = ModelProp<TPropValue, TPropValue, IsOptionalValue<TPropValue, string, never>>;
export declare type MaybeOptionalModelProp<TPropValue> = ModelProp<TPropValue, TPropValue, TPropValue, TPropValue, IsOptionalValue<TPropValue, string, never>>;
/**
* A model prop that is definitely optional.
*/
export declare type OptionalModelProp<TPropValue> = ModelProp<TPropValue, TPropValue | null | undefined, string>;
export declare type OptionalModelProp<TPropValue> = ModelProp<TPropValue, TPropValue | null | undefined, TPropValue, TPropValue | null | undefined, string>;
/**
* A model prop with a generated setter.
*/
export declare type ModelPropWithSetter<MP extends AnyModelProp> = Omit<MP, "$hasSetter"> & {
$hasSetter: string;
};
/**
* Defines a model property, with an optional function to generate a default value

@@ -82,0 +112,0 @@ * if the input snapshot / model creation data is `null` or `undefined`.

@@ -8,3 +8,3 @@ import type { ModelClass } from "../modelShared/BaseModelShared";

} & {
$modelId: import("..").ModelProp<string, string, string, true, never>;
$modelId: import("..").ModelProp<string, string, string, string, string, true, never>;
}>;

@@ -11,0 +11,0 @@ /**

@@ -1,2 +0,2 @@

export declare function getOrCreate<K, V>(map: Map<K, V>, key: K, create: () => V): V;
export declare function getOrCreate<K extends object, V>(map: WeakMap<K, V>, key: K, create: () => V): V;
export declare function getOrCreate<K, V, C = V>(map: Map<K, V>, key: K, create: () => C): V;
export declare function getOrCreate<K extends object, V, C = V>(map: WeakMap<K, V>, key: K, create: () => C): V;
declare const ArraySet_base: import("../model/Model")._Model<unknown, {
items: import("..").OptionalModelProp<any[]>;
} & {
$modelId: import("..").ModelProp<string, string, string, true, never>;
$modelId: import("..").ModelProp<string, string, string, string, string, true, never>;
}>;

@@ -6,0 +6,0 @@ /**

@@ -7,4 +7,4 @@ import { ObservableMap } from "mobx";

*/
export declare function asMap<T>(array: Array<[string, T]>): ObservableMap<string, T> & {
dataObject: Array<[string, T]>;
export declare function asMap<K, V>(array: Array<[K, V]>): ObservableMap<K, V> & {
dataObject: Array<[K, V]>;
};

@@ -30,2 +30,2 @@ /**

*/
export declare function mapToArray<T>(map: Map<string, T>): Array<[string, T]>;
export declare function mapToArray<K, V>(map: Map<K, V>): Array<[K, V]>;

@@ -6,3 +6,3 @@ declare const ObjectMap_base: import("../model/Model")._Model<unknown, {

} & {
$modelId: import("..").ModelProp<string, string, string, true, never>;
$modelId: import("..").ModelProp<string, string, string, string, string, true, never>;
}>;

@@ -9,0 +9,0 @@ /**

{
"name": "mobx-keystone",
"version": "0.60.7",
"version": "0.61.0",
"description": "A MobX powered state management solution based on data trees with first class support for Typescript, snapshots, patches and much more",

@@ -70,12 +70,12 @@ "keywords": [

"@types/jest": "^27.0.1",
"@types/node": "^16.6.1",
"@types/node": "^16.7.1",
"jest": "^27.0.6",
"mobx": "^6.3.2",
"netlify-cli": "^6.5.1",
"netlify-cli": "^6.7.3",
"shx": "^0.3.3",
"spec.ts": "^1.1.3",
"ts-jest": "^27.0.5",
"ts-node": "^10.2.0",
"typedoc": "^0.21.5",
"typescript": "^4.2.4",
"ts-node": "^10.2.1",
"typedoc": "^0.21.6",
"typescript": "4.2.4",
"vite": "^2.5.0"

@@ -82,0 +82,0 @@ },

@@ -15,3 +15,3 @@ import { remove } from "mobx"

export function applyDelete<O extends object, K extends keyof O>(node: O, fieldName: K): void {
assertTweakedObject(node, "node")
assertTweakedObject(node, "node", true)

@@ -18,0 +18,0 @@ wrappedInternalApplyDelete().call(node, fieldName as string | number)

@@ -21,3 +21,3 @@ import { isObservable, set } from "mobx"

): void {
assertTweakedObject(node, "node")
assertTweakedObject(node, "node", true)

@@ -24,0 +24,0 @@ wrappedInternalApplySet().call(node, fieldName as string | number, value)

import { action, computed, createAtom, IAtom, IComputedValue, observable } from "mobx"
import { fastGetParent } from "../parent/path"
import { assertTweakedObject } from "../tweaker/core"
import { assertTweakedObject, isTweakedObject } from "../tweaker/core"
import { getMobxVersion, mobx6 } from "../utils"

@@ -62,2 +62,24 @@ import { getOrCreate } from "../utils/mapUtils"

unset(node: object): void
/**
* Applies a value override while the given function is running and, if a node is returned,
* sets the node as a provider of the value.
*
* @typeparam R
* @param fn Function to run.
* @param value Value to apply.
* @returns The value returned from the function.
*/
apply<R>(fn: () => R, value: T): R
/**
* Applies a computed value override while the given function is running and, if a node is returned,
* sets the node as a provider of the comptued value.
*
* @typeparam R
* @param fn Function to run.
* @param value Value to apply.
* @returns The value returned from the function.
*/
applyComputed<R>(fn: () => R, valueFn: () => T): R
}

@@ -75,3 +97,3 @@

function getContextValue<T>(contextValue: ContextValue<T>): T {
function resolveContextValue<T>(contextValue: ContextValue<T>): T {
if (contextValue.type === "value") {

@@ -88,2 +110,5 @@ return contextValue.value

@observable.ref
private overrideContextValue: ContextValue<T> | undefined
private readonly nodeContextValue = new WeakMap<object, ContextValue<T>>()

@@ -101,3 +126,3 @@ private readonly nodeAtom = new WeakMap<object, IAtom>()

if (obsForNode) {
return getContextValue(obsForNode)
return resolveContextValue(obsForNode)
}

@@ -107,2 +132,5 @@

if (!parent) {
if (this.overrideContextValue) {
return resolveContextValue(this.overrideContextValue)
}
return this.getDefault()

@@ -143,3 +171,3 @@ }

getDefault(): T {
return getContextValue(this.defaultContextValue)
return resolveContextValue(this.defaultContextValue)
}

@@ -174,7 +202,6 @@

@action
setComputed(node: object, valueFn: () => T) {
private _setComputed(node: object, computedValueFn: IComputedValue<T>) {
assertTweakedObject(node, "node")
this.nodeContextValue.set(node, { type: "computed", value: computed(valueFn) })
this.nodeContextValue.set(node, { type: "computed", value: computedValueFn })
this.getNodeAtom(node).reportChanged()

@@ -184,2 +211,7 @@ }

@action
setComputed(node: object, valueFn: () => T) {
this._setComputed(node, computed(valueFn))
}
@action
unset(node: object) {

@@ -192,2 +224,42 @@ assertTweakedObject(node, "node")

@action
apply<R>(fn: () => R, value: T): R {
const old = this.overrideContextValue
this.overrideContextValue = {
type: "value",
value,
}
try {
const ret = fn()
if (isTweakedObject(ret, true)) {
this.set(ret, value)
}
return ret
} finally {
this.overrideContextValue = old
}
}
@action
applyComputed<R>(fn: () => R, valueFn: () => T): R {
const computedValueFn = computed(valueFn)
const old = this.overrideContextValue
this.overrideContextValue = {
type: "computed",
value: computedValueFn,
}
try {
const ret = fn()
if (isTweakedObject(ret, true)) {
this._setComputed(ret, computedValueFn)
}
return ret
} finally {
this.overrideContextValue = old
}
}
constructor(defaultValue?: T) {

@@ -194,0 +266,0 @@ if (getMobxVersion() >= 6) {

@@ -1,4 +0,13 @@

import { dataTypeSymbol, ModelClass } from "../modelShared/BaseModelShared"
import {
creationDataTypeSymbol,
dataTypeSymbol,
ModelClass,
transformedCreationDataTypeSymbol,
transformedDataTypeSymbol,
} from "../modelShared/BaseModelShared"
import { modelInfoByClass } from "../modelShared/modelInfo"
import { getInternalModelClassPropsInfo } from "../modelShared/modelPropsInfo"
import { noDefaultValue } from "../modelShared/prop"
import { getSnapshot } from "../snapshot/getSnapshot"
import { isTreeNode } from "../tweaker/core"
import { toTreeNode } from "../tweaker/tweak"

@@ -23,5 +32,13 @@ import { typesDataModelData } from "../typeChecking/dataModelData"

*/
export abstract class BaseDataModel<Data extends { [k: string]: any }> {
export abstract class BaseDataModel<
Data extends { [k: string]: any },
CreationData extends { [k: string]: any },
TransformedData extends { [k: string]: any },
TransformedCreationData extends { [k: string]: any }
> {
// just to make typing work properly
[dataTypeSymbol]: Data
[dataTypeSymbol]: Data;
[creationDataTypeSymbol]: Data;
[transformedDataTypeSymbol]: TransformedData;
[transformedCreationDataTypeSymbol]: Data

@@ -54,7 +71,6 @@ /**

*/
constructor(data: Data) {
constructor(data: CreationData | TransformedCreationData) {
if (!isObject(data)) {
throw failure("data models can only work over data objects")
}
const tweakedData = toTreeNode(data)

@@ -64,2 +80,51 @@ const { modelClass: _modelClass }: DataModelConstructorOptions = arguments[1] as any

let tweakedData: Data
if (isTreeNode(data)) {
// in theory already initialized
tweakedData = data as any as Data
} else {
const modelInfo = modelInfoByClass.get(modelClass)
if (!modelInfo) {
throw failure(
`no model info for class ${modelClass.name} could be found - did you forget to add the @model decorator?`
)
}
const modelProps = getInternalModelClassPropsInfo(modelClass)
const initialData: Record<string, any> = Object.assign({}, data)
const modelPropsKeys = Object.keys(modelProps)
for (let i = 0; i < modelPropsKeys.length; i++) {
const k = modelPropsKeys[i]
const propData = modelProps[k]
let newValue = initialData![k]
let changed = false
// apply untransform (if any)
if (propData.transform) {
changed = true
newValue = propData.transform.untransform(newValue, this, k)
}
// apply default value (if needed)
if (newValue == null) {
if (propData.defaultFn !== noDefaultValue) {
changed = true
newValue = propData.defaultFn()
} else if (propData.defaultValue !== noDefaultValue) {
changed = true
newValue = propData.defaultValue
}
}
if (changed) {
initialData[k] = newValue
}
}
tweakedData = toTreeNode(initialData as Data)
}
const instancesForModelClass = getOrCreate(

@@ -84,2 +149,5 @@ dataModelInstanceCache,

delete self[dataTypeSymbol]
delete self[creationDataTypeSymbol]
delete self[transformedDataTypeSymbol]
delete self[transformedCreationDataTypeSymbol]

@@ -123,3 +191,3 @@ internalNewDataModel(this, tweakedData as any, {

*/
export interface AnyDataModel extends BaseDataModel<any> {}
export interface AnyDataModel extends BaseDataModel<any, any, any, any> {}

@@ -126,0 +194,0 @@ /**

import type { AbstractModelClass, ModelClass } from "../modelShared/BaseModelShared"
import { sharedInternalModel } from "../modelShared/Model"
import type { ModelProps, ModelPropsToData, ModelPropsToSetter } from "../modelShared/prop"
import type {
ModelProps,
ModelPropsToCreationData,
ModelPropsToData,
ModelPropsToSetter,
ModelPropsToTransformedCreationData,
ModelPropsToTransformedData,
} from "../modelShared/prop"
import type { AnyDataModel, BaseDataModel, BaseDataModelKeys } from "./BaseDataModel"

@@ -8,11 +15,19 @@ import { assertIsDataModelClass, isDataModelClass } from "./utils"

export type _ComposedData<SuperModel, TProps extends ModelProps> = SuperModel extends BaseDataModel<
infer D
any,
infer CD,
any,
infer CTD
>
? ModelPropsToData<TProps> & D
: ModelPropsToData<TProps>
? (ModelPropsToCreationData<TProps> & CD) | (ModelPropsToTransformedCreationData<TProps> & CTD)
: ModelPropsToCreationData<TProps> | ModelPropsToTransformedCreationData<TProps>
export interface _DataModel<SuperModel, TProps extends ModelProps> {
new (data: _ComposedData<SuperModel, TProps>): SuperModel &
BaseDataModel<ModelPropsToData<TProps>> &
Omit<ModelPropsToData<TProps>, BaseDataModelKeys> &
BaseDataModel<
ModelPropsToData<TProps>,
ModelPropsToCreationData<TProps>,
ModelPropsToTransformedData<TProps>,
ModelPropsToTransformedCreationData<TProps>
> &
Omit<ModelPropsToTransformedData<TProps>, BaseDataModelKeys> &
ModelPropsToSetter<TProps>

@@ -34,5 +49,3 @@ }

>(
genFn: (
...args: A
) => {
genFn: (...args: A) => {
baseModel: AbstractModelClass<TModel>

@@ -39,0 +52,0 @@ props: TProps

@@ -16,2 +16,3 @@ export * from "./action"

export * from "./standardActions"
export * from "./transforms"
export * from "./treeUtils"

@@ -18,0 +19,0 @@ export * from "./tweaker"

import { observable } from "mobx"
import type { O } from "ts-toolbelt"
import { getGlobalConfig } from "../globalConfig"
import { creationDataTypeSymbol, dataTypeSymbol, ModelClass } from "../modelShared/BaseModelShared"
import {
creationDataTypeSymbol,
dataTypeSymbol,
ModelClass,
transformedCreationDataTypeSymbol,
transformedDataTypeSymbol,
} from "../modelShared/BaseModelShared"
import { modelInfoByClass } from "../modelShared/modelInfo"

@@ -40,2 +46,4 @@ import { getSnapshot } from "../snapshot/getSnapshot"

CreationData extends { [k: string]: any },
TransformedData extends { [k: string]: any },
TransformedCreationData extends { [k: string]: any },
ModelIdPropertyName extends string = typeof modelIdKey

@@ -46,2 +54,4 @@ > {

[creationDataTypeSymbol]: CreationData;
[transformedDataTypeSymbol]: TransformedData;
[transformedCreationDataTypeSymbol]: TransformedCreationData;
[modelIdPropertyNameSymbol]: ModelIdPropertyName;

@@ -105,5 +115,3 @@

*/
fromSnapshot?(snapshot: {
[k: string]: any
}): SnapshotInOfObject<CreationData> & {
fromSnapshot?(snapshot: { [k: string]: any }): SnapshotInOfObject<CreationData> & {
[modelTypeKey]?: string

@@ -126,9 +134,6 @@ }

*/
constructor(data: CreationData) {
constructor(data: TransformedCreationData) {
let initialData = data as any
const {
snapshotInitialData,
modelClass,
generateNewIds,
}: ModelConstructorOptions = arguments[1] as any
const { snapshotInitialData, modelClass, generateNewIds }: ModelConstructorOptions =
arguments[1] as any

@@ -142,2 +147,4 @@ Object.setPrototypeOf(this, modelClass!.prototype)

delete self[creationDataTypeSymbol]
delete self[transformedDataTypeSymbol]
delete self[transformedCreationDataTypeSymbol]
delete self[modelIdPropertyNameSymbol]

@@ -198,3 +205,3 @@

*/
export interface AnyModel extends BaseModel<any, any, any> {}
export interface AnyModel extends BaseModel<any, any, any, any, any> {}

@@ -201,0 +208,0 @@ /**

@@ -9,2 +9,4 @@ import type { AbstractModelClass, ModelClass } from "../modelShared/BaseModelShared"

ModelPropsToSetter,
ModelPropsToTransformedCreationData,
ModelPropsToTransformedData,
} from "../modelShared/prop"

@@ -17,5 +19,5 @@ import type { AnyModel, BaseModel, BaseModelKeys, ModelIdPropertyName } from "./BaseModel"

TProps extends ModelProps
> = SuperModel extends BaseModel<any, infer CD, any>
? ModelPropsToCreationData<TProps> & CD
: ModelPropsToCreationData<TProps>
> = SuperModel extends BaseModel<any, any, any, infer TCD>
? ModelPropsToTransformedCreationData<TProps> & TCD
: ModelPropsToTransformedCreationData<TProps>

@@ -31,5 +33,7 @@ export type _ModelId<SuperModel, TProps extends ModelProps> = SuperModel extends AnyModel

ModelPropsToCreationData<TProps>,
ModelPropsToTransformedData<TProps>,
ModelPropsToTransformedCreationData<TProps>,
_ModelId<SuperModel, TProps>
> &
Omit<ModelPropsToData<TProps>, BaseModelKeys> &
Omit<ModelPropsToTransformedData<TProps>, BaseModelKeys> &
ModelPropsToSetter<TProps>

@@ -42,7 +46,6 @@ }

*/
export type AddModelIdPropIfNeeded<
TProps extends ModelProps
> = ExtractModelIdProp<TProps> extends never
? TProps & { $modelId: typeof idProp } // we use the actual name here to avoid having to re-export the original
: TProps
export type AddModelIdPropIfNeeded<TProps extends ModelProps> =
ExtractModelIdProp<TProps> extends never
? TProps & { $modelId: typeof idProp } // we use the actual name here to avoid having to re-export the original
: TProps

@@ -66,5 +69,3 @@ /**

export function ExtendedModel<TProps extends ModelProps, TModel extends AnyModel, A extends []>(
genFn: (
...args: A
) => {
genFn: (...args: A) => {
baseModel: AbstractModelClass<TModel>

@@ -71,0 +72,0 @@ props: TProps

@@ -8,3 +8,2 @@ import type { ModelClass } from "../modelShared/BaseModelShared"

*/
export interface ModelConstructorOptions {

@@ -11,0 +10,0 @@ snapshotInitialData?: {

import { action, set } from "mobx"
import type { O } from "ts-toolbelt"
import { isModelAutoTypeCheckingEnabled } from "../globalConfig/globalConfig"
import type { ModelCreationData } from "../modelShared/BaseModelShared"
import type { ModelTransformedCreationData } from "../modelShared/BaseModelShared"
import { modelInfoByClass } from "../modelShared/modelInfo"

@@ -26,5 +26,6 @@ import { getInternalModelClassPropsInfo } from "../modelShared/modelPropsInfo"

origModelObj: M,
initialData: ModelCreationData<M> | undefined,
initialData: ModelTransformedCreationData<M> | undefined,
options: Pick<ModelConstructorOptions, "modelClass" | "snapshotInitialData" | "generateNewIds">
): M => {
const mode = initialData ? "new" : "fromSnapshot"
const { modelClass: _modelClass, snapshotInitialData, generateNewIds } = options

@@ -80,16 +81,33 @@ const modelClass = _modelClass!

const k = modelPropsKeys[i]
// id is already initialized above
if (k !== modelIdPropertyName) {
const v = (initialData as any)[k]
if (v === undefined || v === null) {
let newValue: any = v
const propData = modelProps[k]
if (propData.defaultFn !== noDefaultValue) {
newValue = propData.defaultFn()
} else if (propData.defaultValue !== noDefaultValue) {
newValue = propData.defaultValue
}
set(initialData as any, k, newValue)
if (k === modelIdPropertyName) {
continue
}
const propData = modelProps[k]
let newValue = initialData![k]
let changed = false
// apply untransform (if any)
if (mode === "new" && propData.transform) {
changed = true
newValue = propData.transform.untransform(newValue, modelObj, k)
}
// apply default value (if needed)
if (newValue == null) {
if (propData.defaultFn !== noDefaultValue) {
changed = true
newValue = propData.defaultFn()
} else if (propData.defaultValue !== noDefaultValue) {
changed = true
newValue = propData.defaultValue
}
}
if (changed) {
set(initialData as any, k, newValue)
}
}

@@ -96,0 +114,0 @@

@@ -16,2 +16,12 @@ import type { AnyDataModel } from "../dataModel/BaseDataModel"

* @ignore
*/
export const transformedDataTypeSymbol = Symbol()
/**
* @ignore
*/
export const transformedCreationDataTypeSymbol = Symbol()
/**
* @ignore
* @internal

@@ -24,3 +34,3 @@ */

*/
export type ModelClass<M extends AnyModel | AnyDataModel> = {
export type ModelClass<M extends AnyModel | AnyDataModel> = {
new (initialData: any): M

@@ -32,3 +42,5 @@ }

*/
export type AbstractModelClass<M extends AnyModel|AnyDataModel> = abstract new (initialData: any) => M;
export type AbstractModelClass<M extends AnyModel | AnyDataModel> = abstract new (
initialData: any
) => M

@@ -43,7 +55,17 @@ /**

*/
export type ModelCreationData<
M extends AnyModel
> = M[typeof creationDataTypeSymbol]
export type ModelCreationData<M extends AnyModel | AnyDataModel> = M[typeof creationDataTypeSymbol]
/**
* The transformed data type of a model.
*/
export type ModelTransformedData<M extends AnyModel | AnyDataModel> =
M[typeof transformedDataTypeSymbol]
/**
* The transformed creation data type of a model.
*/
export type ModelTransformedCreationData<M extends AnyModel | AnyDataModel> =
M[typeof transformedCreationDataTypeSymbol]
/**
* Tricks Typescript into accepting a particular kind of generic class as a parameter for `ExtendedModel`.

@@ -56,4 +78,6 @@ * Does nothing in runtime.

*/
export function modelClass<T extends AnyModel | AnyDataModel>(type: { prototype: T }): ModelClass<T> {
export function modelClass<T extends AnyModel | AnyDataModel>(type: {
prototype: T
}): ModelClass<T> {
return type as any
}

@@ -7,3 +7,2 @@ import { applySet } from "../action/applySet"

import type { DataModelMetadata } from "../dataModel/getDataModelMetadata"
import { isDataModel } from "../dataModel/utils"
import { getGlobalConfig } from "../globalConfig/globalConfig"

@@ -21,7 +20,7 @@ import { AnyModel, BaseModel, baseModelPropNames } from "../model/BaseModel"

import { assertIsObject, failure, propNameToSetterName } from "../utils"
import { ModelClass, ModelData, modelInitializedSymbol } from "./BaseModelShared"
import { ModelClass, modelInitializedSymbol, ModelTransformedData } from "./BaseModelShared"
import { modelInitializersSymbol } from "./modelClassInitializer"
import { getInternalModelClassPropsInfo, setInternalModelClassPropsInfo } from "./modelPropsInfo"
import { modelMetadataSymbol, modelUnwrappedClassSymbol } from "./modelSymbols"
import { AnyModelProp, ModelProps, noDefaultValue, prop } from "./prop"
import { AnyModelProp, ModelProps, prop } from "./prop"
import { assertIsClassOrDataModelClass } from "./utils"

@@ -61,29 +60,38 @@

export function getModelInstanceDataField<M extends AnyModel | AnyDataModel>(
function getModelInstanceDataField<M extends AnyModel | AnyDataModel>(
model: M,
_modelProp: AnyModelProp | undefined,
modelPropName: keyof ModelData<M>
): ModelData<M>[typeof modelPropName] {
modelProp: AnyModelProp | undefined,
modelPropName: keyof ModelTransformedData<M>
): ModelTransformedData<M>[typeof modelPropName] {
// no need to use get since these vars always get on the initial $
return model.$[modelPropName]
const value = model.$[modelPropName]
if (modelProp?.transform) {
return modelProp.transform.transform(value, model, modelPropName, (newValue) => {
// use apply set instead to wrap it in an action
// set the $ object to set the original value directly
applySet(model.$, modelPropName, newValue)
})
}
return value
}
export function setModelInstanceDataField<M extends AnyModel | AnyDataModel>(
function setModelInstanceDataField<M extends AnyModel | AnyDataModel>(
model: M,
modelProp: AnyModelProp | undefined,
modelPropName: keyof ModelData<M>,
value: ModelData<M>[typeof modelPropName]
modelPropName: keyof ModelTransformedData<M>,
value: ModelTransformedData<M>[typeof modelPropName]
): void {
if (modelProp?.setter === "assign" && !getCurrentActionContext()) {
// use apply set instead to wrap it in an action
if (isDataModel(model)) {
applySet(model.$, modelPropName as any, value)
} else {
applySet(model, modelPropName as any, value)
}
applySet(model, modelPropName, value)
return
}
const transformedValue = modelProp?.transform
? modelProp.transform.untransform(value, model, modelPropName)
: value
// no need to use set since these vars always get on the initial $
model.$[modelPropName] = value
model.$[modelPropName] = transformedValue
}

@@ -156,13 +164,2 @@

if (type === "data") {
// make sure props have no defaults
for (const [k, mp] of Object.entries(composedModelProps)) {
if (mp.defaultValue !== noDefaultValue || mp.defaultFn !== noDefaultValue) {
throw failure(
`data models do not support properties with default values, but property '${k}' has one`
)
}
}
}
const needsTypeChecker = Object.values(composedModelProps).some((mp) => !!mp.typeChecker)

@@ -169,0 +166,0 @@

@@ -32,110 +32,111 @@ import { HookAction } from "../action/hookActions"

*/
export const model = (name: string) => <MC extends ModelClass<AnyModel | AnyDataModel>>(
clazz: MC
): MC => {
return internalModel(name)(clazz)
}
const internalModel = (name: string) => <MC extends ModelClass<AnyModel | AnyDataModel>>(
clazz: MC
): MC => {
const type = isModelClass(clazz) ? "class" : isDataModelClass(clazz) ? "data" : undefined
if (!type) {
throw failure(`clazz must be a class that extends from Model/DataModel`)
export const model =
(name: string) =>
<MC extends ModelClass<AnyModel | AnyDataModel>>(clazz: MC): MC => {
return internalModel(name)(clazz)
}
if (modelInfoByName[name]) {
if (getGlobalConfig().showDuplicateModelNameWarnings) {
logWarning(
"warn",
`a model with name "${name}" already exists (if you are using hot-reloading you may safely ignore this warning)`,
`duplicateModelName - ${name}`
)
const internalModel =
(name: string) =>
<MC extends ModelClass<AnyModel | AnyDataModel>>(clazz: MC): MC => {
const type = isModelClass(clazz) ? "class" : isDataModelClass(clazz) ? "data" : undefined
if (!type) {
throw failure(`clazz must be a class that extends from Model/DataModel`)
}
}
if ((clazz as any)[modelUnwrappedClassSymbol]) {
throw failure("a class already decorated with `@model` cannot be re-decorated")
}
if (modelInfoByName[name]) {
if (getGlobalConfig().showDuplicateModelNameWarnings) {
logWarning(
"warn",
`a model with name "${name}" already exists (if you are using hot-reloading you may safely ignore this warning)`,
`duplicateModelName - ${name}`
)
}
}
// trick so plain new works
const newClazz: any = function (this: any, initialData: any, modelConstructorOptions: any) {
const instance = new (clazz as any)(initialData, modelConstructorOptions)
if ((clazz as any)[modelUnwrappedClassSymbol]) {
throw failure("a class already decorated with `@model` cannot be re-decorated")
}
// set or else it points to the undecorated class
Object.defineProperty(instance, "constructor", {
configurable: true,
writable: true,
enumerable: false,
value: newClazz,
})
// trick so plain new works
const newClazz: any = function (this: any, initialData: any, modelConstructorOptions: any) {
const instance = new (clazz as any)(initialData, modelConstructorOptions)
runLateInitializationFunctions(instance, runAfterNewSymbol)
// set or else it points to the undecorated class
Object.defineProperty(instance, "constructor", {
configurable: true,
writable: true,
enumerable: false,
value: newClazz,
})
// compatibility with mobx 6
if (getMobxVersion() >= 6) {
try {
mobx6.makeObservable(instance)
} catch (err) {
// sadly we need to use this hack since the PR to do this the proper way
// was rejected on the mobx side
if (
err.message !==
"[MobX] No annotations were passed to makeObservable, but no decorator members have been found either" &&
err.message !==
"[MobX] No annotations were passed to makeObservable, but no decorated members have been found either"
) {
throw err
runLateInitializationFunctions(instance, runAfterNewSymbol)
// compatibility with mobx 6
if (getMobxVersion() >= 6) {
try {
mobx6.makeObservable(instance)
} catch (e) {
const err = e as Error
// sadly we need to use this hack since the PR to do this the proper way
// was rejected on the mobx side
if (
err.message !==
"[MobX] No annotations were passed to makeObservable, but no decorator members have been found either" &&
err.message !==
"[MobX] No annotations were passed to makeObservable, but no decorated members have been found either"
) {
throw err
}
}
}
}
// the object is ready
addHiddenProp(instance, modelInitializedSymbol, true, false)
// the object is ready
addHiddenProp(instance, modelInitializedSymbol, true, false)
if (type === "class" && instance.onInit) {
wrapModelMethodInActionIfNeeded(instance, "onInit", HookAction.OnInit)
if (type === "class" && instance.onInit) {
wrapModelMethodInActionIfNeeded(instance, "onInit", HookAction.OnInit)
instance.onInit()
}
instance.onInit()
}
if (type === "data" && instance.onLazyInit) {
wrapModelMethodInActionIfNeeded(instance, "onLazyInit", HookAction.OnLazyInit)
if (type === "data" && instance.onLazyInit) {
wrapModelMethodInActionIfNeeded(instance, "onLazyInit", HookAction.OnLazyInit)
instance.onLazyInit()
instance.onLazyInit()
}
return instance
}
return instance
}
clazz.toString = () => `class ${clazz.name}#${name}`
if (type === "class") {
;(clazz as any)[modelTypeKey] = name
}
clazz.toString = () => `class ${clazz.name}#${name}`
if (type === "class") {
;(clazz as any)[modelTypeKey] = name
}
// this also gives access to modelInitializersSymbol, modelPropertiesSymbol, modelDataTypeCheckerSymbol
Object.setPrototypeOf(newClazz, clazz)
newClazz.prototype = clazz.prototype
// this also gives access to modelInitializersSymbol, modelPropertiesSymbol, modelDataTypeCheckerSymbol
Object.setPrototypeOf(newClazz, clazz)
newClazz.prototype = clazz.prototype
Object.defineProperty(newClazz, "name", {
...Object.getOwnPropertyDescriptor(newClazz, "name"),
value: clazz.name,
})
newClazz[modelUnwrappedClassSymbol] = clazz
Object.defineProperty(newClazz, "name", {
...Object.getOwnPropertyDescriptor(newClazz, "name"),
value: clazz.name,
})
newClazz[modelUnwrappedClassSymbol] = clazz
const modelInfo = {
name,
class: newClazz,
}
const modelInfo = {
name,
class: newClazz,
}
modelInfoByName[name] = modelInfo
modelInfoByName[name] = modelInfo
modelInfoByClass.set(newClazz, modelInfo)
modelInfoByClass.set(clazz, modelInfo)
modelInfoByClass.set(newClazz, modelInfo)
modelInfoByClass.set(clazz, modelInfo)
runLateInitializationFunctions(clazz, runAfterModelDecoratorSymbol)
runLateInitializationFunctions(clazz, runAfterModelDecoratorSymbol)
return newClazz
}
return newClazz
}
// basically taken from TS

@@ -142,0 +143,0 @@ function tsDecorate(decorators: any, target: any, key: any, desc: any) {

import type { O } from "ts-toolbelt"
import type { LateTypeChecker, TypeChecker } from "../typeChecking/TypeChecker"
import { getOrCreate } from "../utils/mapUtils"
import type { IsOptionalValue } from "../utils/types"

@@ -16,2 +17,4 @@

TPropCreationValue,
TTransformedValue,
TTransformedCreationValue,
TIsOptional,

@@ -23,2 +26,4 @@ TIsId extends boolean = false,

$creationValueType: TPropCreationValue
$transformedValueType: TTransformedValue
$transformedCreationValueType: TTransformedCreationValue
$isOptional: TIsOptional

@@ -33,14 +38,75 @@ $isId: TIsId

isId: boolean
transform:
| {
transform(
original: unknown,
model: object,
propName: PropertyKey,
setOriginalValue: (newOriginalValue: unknown) => void
): unknown
untransform(transformed: unknown, model: object, propName: PropertyKey): unknown
}
| undefined
withSetter(): ModelPropWithSetter<this>
withSetter(): ModelProp<
TPropValue,
TPropCreationValue,
TTransformedValue,
TTransformedCreationValue,
TIsOptional,
TIsId,
string
>
/**
* @deprecated Setter methods are preferred.
*/
withSetter(mode: "assign"): ModelPropWithSetter<this>
withSetter(
mode: "assign"
): ModelProp<
TPropValue,
TPropCreationValue,
TTransformedValue,
TTransformedCreationValue,
TIsOptional,
TIsId,
string
>
/**
* Sets a transform for the property.
*
* @typeparam TTV Transformed value type.
* @param transform Transform to be used.
* @returns
*/
withTransform<TTV>(
transform: ModelPropTransform<NonNullable<TPropValue>, TTV>
): ModelProp<
TPropValue,
TPropCreationValue,
TTV | Extract<TPropValue, null | undefined>,
TTV | Extract<TPropCreationValue, null | undefined>,
TIsOptional,
TIsId,
THasSetter
>
}
/**
* A model prop transform.
*/
export interface ModelPropTransform<TOriginal, TTransformed> {
transform(params: {
originalValue: TOriginal
cachedTransformedValue: TTransformed | undefined
setOriginalValue(value: TOriginal): void
}): TTransformed
untransform(params: { transformedValue: TTransformed; cacheTransformedValue(): void }): TOriginal
}
/**
* Any model property.
*/
export type AnyModelProp = ModelProp<any, any, any, any, any>
export type AnyModelProp = ModelProp<any, any, any, any, any, any, any>

@@ -75,5 +141,22 @@ /**

export type ModelPropsToTransformedData<MP extends ModelProps> = {
[k in keyof MP]: MP[k]["$transformedValueType"]
}
// we don't use O.Optional anymore since it generates unions too heavy
// also if we use pick over the optional props we will loose the ability
// to infer generics
export type ModelPropsToTransformedCreationData<MP extends ModelProps> = {
[k in keyof MP]?: MP[k]["$transformedCreationValueType"]
} &
O.Omit<
{
[k in keyof MP]: MP[k]["$transformedCreationValueType"]
},
OptionalModelProps<MP>
>
export type ModelPropsToSetter<MP extends ModelProps> = {
[k in keyof MP as MP[k]["$hasSetter"] & `set${Capitalize<k & string>}`]: (
value: MP[k]["$valueType"]
value: MP[k]["$transformedValueType"]
) => void

@@ -93,3 +176,3 @@ }

},
} as any as ModelProp<string, string, string, true>
} as any as ModelProp<string, string, string, string, string, true>

@@ -107,2 +190,4 @@ /**

TPropValue,
TPropValue,
TPropValue,
IsOptionalValue<TPropValue, string, never>

@@ -117,2 +202,4 @@ >

TPropValue | null | undefined,
TPropValue,
TPropValue | null | undefined,
string

@@ -122,9 +209,2 @@ >

/**
* A model prop with a generated setter.
*/
export type ModelPropWithSetter<MP extends AnyModelProp> = Omit<MP, "$hasSetter"> & {
$hasSetter: string
}
/**
* Defines a model property, with an optional function to generate a default value

@@ -177,3 +257,3 @@ * if the input snapshot / model creation data is `null` or `undefined`.

// base
export function prop<TValue>(def?: any): ModelProp<TValue, any, any, any, any> {
export function prop(def?: any): AnyModelProp {
let hasDefaultValue = false

@@ -188,5 +268,7 @@

const obj: ReturnType<typeof prop> = {
const obj: AnyModelProp = {
$valueType: null as any,
$creationValueType: null as any,
$transformedValueType: null as any,
$transformedCreationValueType: null as any,
$isOptional: null as any,

@@ -201,2 +283,3 @@ $isId: null as never,

isId: false,
transform: undefined,

@@ -206,4 +289,78 @@ withSetter(mode?: boolean | "assign") {

},
withTransform(transform: ModelPropTransform<unknown, unknown>) {
return { ...this, transform: toFullTransform(transform) }
},
}
return obj as any
return obj
}
let cacheTransformResult = false
const cacheTransformedValueFn = () => {
cacheTransformResult = true
}
function toFullTransform(transformObject: ModelPropTransform<unknown, unknown>) {
const cache = new WeakMap<
object,
Map<PropertyKey, { originalValue: unknown; transformedValue: unknown }>
>()
const transform = (params: {
originalValue: unknown
cachedTransformedValue: unknown
setOriginalValue(newOriginalValue: unknown): void
}) => (params.originalValue == null ? params.originalValue : transformObject.transform(params))
const untransform = (params: { transformedValue: unknown; cacheTransformedValue(): void }) =>
params.transformedValue == null ? params.transformedValue : transformObject.untransform(params)
return {
transform(
originalValue: unknown,
model: object,
propName: PropertyKey,
setOriginalValue: (newOriginalValue: unknown) => void
) {
const modelCache = getOrCreate(cache, model, () => new Map())
let propCache = modelCache.get(propName)
if (propCache?.originalValue !== originalValue) {
// original changed, invalidate cache
modelCache.delete(propName)
propCache = undefined
}
const transformedValue = transform({
originalValue,
cachedTransformedValue: propCache?.transformedValue,
setOriginalValue,
})
modelCache.set(propName, {
originalValue,
transformedValue,
})
return transformedValue
},
untransform(transformedValue: unknown, model: object, propName: PropertyKey) {
const modelCache = getOrCreate(cache, model, () => new Map())
cacheTransformResult = false
const originalValue = untransform({
transformedValue,
cacheTransformedValue: cacheTransformedValueFn,
})
if (cacheTransformResult) {
modelCache.set(propName, { originalValue, transformedValue })
} else {
modelCache.delete(propName)
}
return originalValue
},
}
}

@@ -0,0 +0,0 @@ import { assertTweakedObject } from "../tweaker/core"

type AnyMap<V = any> = Map<any, V> | WeakMap<any, V>
export function getOrCreate<K, V>(map: Map<K, V>, key: K, create: () => V): V
export function getOrCreate<K extends object, V>(map: WeakMap<K, V>, key: K, create: () => V): V
export function getOrCreate<K, V, C = V>(map: Map<K, V>, key: K, create: () => C): V
export function getOrCreate<K extends object, V, C = V>(
map: WeakMap<K, V>,
key: K,
create: () => C
): V

@@ -6,0 +10,0 @@ export function getOrCreate<V>(map: AnyMap<V>, key: any, create: () => V) {

@@ -23,74 +23,96 @@ import {

} from "../utils"
import { Lock } from "../utils/lock"
import { tag } from "../utils/tag"
const observableMapBackedByObservableObject = action(<T>(obj: object): ObservableMap<string, T> & {
dataObject: typeof obj
} => {
if (inDevMode()) {
if (!isObservableObject(obj)) {
throw failure("assertion failed: expected an observable object")
const observableMapBackedByObservableObject = action(
<T>(
obj: object
): ObservableMap<string, T> & {
dataObject: typeof obj
} => {
if (inDevMode()) {
if (!isObservableObject(obj)) {
throw failure("assertion failed: expected an observable object")
}
}
}
const map = observable.map()
;(map as any).dataObject = obj
const map = observable.map()
;(map as any).dataObject = obj
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
const k = keys[i]
map.set(k, (obj as any)[k])
}
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
const k = keys[i]
map.set(k, (obj as any)[k])
}
const mutationLock = new Lock()
let mapAlreadyChanged = false
let objectAlreadyChanged = false
// when the object changes the map changes
observe(
obj,
action(
mutationLock.unlockedFn((change: IObjectDidChange) => {
switch (change.type) {
case "add":
case "update": {
map.set(change.name, change.newValue)
break
}
// when the object changes the map changes
observe(
obj,
action((change: IObjectDidChange) => {
if (mapAlreadyChanged) {
return
}
case "remove": {
map.delete(change.name)
break
objectAlreadyChanged = true
try {
switch (change.type) {
case "add":
case "update": {
map.set(change.name, change.newValue)
break
}
case "remove": {
map.delete(change.name)
break
}
}
} finally {
objectAlreadyChanged = false
}
})
)
)
// when the map changes also change the object
intercept(
map,
action((change: IMapWillChange<string, T>) => {
if (!mutationLock.isLocked) {
return null // already changed
}
// when the map changes also change the object
intercept(
map,
action((change: IMapWillChange<string, T>) => {
if (mapAlreadyChanged) {
return null
}
switch (change.type) {
case "add":
case "update": {
set(obj, change.name, change.newValue)
break
if (objectAlreadyChanged) {
return change
}
case "delete": {
remove(obj, change.name)
break
mapAlreadyChanged = true
try {
switch (change.type) {
case "add":
case "update": {
set(obj, change.name, change.newValue)
break
}
case "delete": {
remove(obj, change.name)
break
}
}
return change
} finally {
mapAlreadyChanged = false
}
}
})
)
return change
})
)
return map as any
}
)
return map as any
})
const observableMapBackedByObservableArray = action(

@@ -113,3 +135,4 @@ <T>(

const mutationLock = new Lock()
let mapAlreadyChanged = false
let arrayAlreadyChanged = false

@@ -122,4 +145,9 @@ // for speed reasons we will just assume distinct values are only once in the array

array,
action(
mutationLock.unlockedFn((change: any /*IArrayDidChange<[string, T]>*/) => {
action((change: any /*IArrayDidChange<[string, T]>*/) => {
if (mapAlreadyChanged) {
return
}
arrayAlreadyChanged = true
try {
switch (change.type) {

@@ -150,4 +178,6 @@ case "splice": {

}
})
)
} finally {
arrayAlreadyChanged = false
}
})
)

@@ -159,29 +189,39 @@

action((change: IMapWillChange<string, T>) => {
if (!mutationLock.isLocked) {
return null // already changed
if (mapAlreadyChanged) {
return null
}
switch (change.type) {
case "update": {
// replace the whole tuple to keep tuple immutability
const i = array.findIndex((i) => i[0] === change.name)
array[i] = [change.name, change.newValue!]
break
}
if (arrayAlreadyChanged) {
return change
}
case "add": {
array.push([change.name, change.newValue!])
break
}
mapAlreadyChanged = true
case "delete": {
const i = array.findIndex((i) => i[0] === change.name)
if (i >= 0) {
array.splice(i, 1)
try {
switch (change.type) {
case "update": {
// replace the whole tuple to keep tuple immutability
const i = array.findIndex((i) => i[0] === change.name)
array[i] = [change.name, change.newValue!]
break
}
break
case "add": {
array.push([change.name, change.newValue!])
break
}
case "delete": {
const i = array.findIndex((i) => i[0] === change.name)
if (i >= 0) {
array.splice(i, 1)
}
break
}
}
return change
} finally {
mapAlreadyChanged = false
}
return change
})

@@ -209,5 +249,5 @@ )

*/
export function asMap<T>(
array: Array<[string, T]>
): ObservableMap<string, T> & { dataObject: Array<[string, T]> }
export function asMap<K, V>(
array: Array<[K, V]>
): ObservableMap<K, V> & { dataObject: Array<[K, V]> }

@@ -228,5 +268,5 @@ /**

*/
export function asMap<T>(
objOrArray: Record<string, T> | Array<[string, T]>
): ObservableMap<string, T> & { dataObject: typeof objOrArray } {
export function asMap(
objOrArray: Record<string, unknown> | Array<[unknown, unknown]>
): ObservableMap<unknown, unknown> & { dataObject: typeof objOrArray } {
return asMapTag.for(objOrArray) as any

@@ -261,3 +301,3 @@ }

*/
export function mapToArray<T>(map: Map<string, T>): Array<[string, T]> {
export function mapToArray<K, V>(map: Map<K, V>): Array<[K, V]> {
assertIsMap(map, "map")

@@ -270,5 +310,5 @@

const arr: [string, any][] = []
const arr: [K, V][] = []
for (const k of map.keys()) {
arr.push([k, map.get(k)])
arr.push([k, map.get(k)!])
}

@@ -275,0 +315,0 @@

@@ -12,3 +12,2 @@ import {

import { assertIsObservableArray, assertIsSet, failure, inDevMode } from "../utils"
import { Lock } from "../utils/lock"
import { tag } from "../utils/tag"

@@ -31,3 +30,4 @@

const mutationLock = new Lock()
let setAlreadyChanged = false
let arrayAlreadyChanged = false

@@ -39,4 +39,10 @@ // for speed reasons we will just assume distinct values are only once in the array

array,
action(
mutationLock.unlockedFn((change: any /*IArrayDidChange<T>*/) => {
action((change: any /*IArrayDidChange<T>*/) => {
if (setAlreadyChanged) {
return
}
arrayAlreadyChanged = true
try {
switch (change.type) {

@@ -67,4 +73,6 @@ case "splice": {

}
})
)
} finally {
arrayAlreadyChanged = false
}
})
)

@@ -76,22 +84,32 @@

action((change: ISetWillChange<T>) => {
if (!mutationLock.isLocked) {
return null // already changed
if (setAlreadyChanged) {
return null
}
switch (change.type) {
case "add": {
array.push(change.newValue)
break
}
if (arrayAlreadyChanged) {
return change
}
case "delete": {
const i = array.indexOf(change.oldValue)
if (i >= 0) {
array.splice(i, 1)
setAlreadyChanged = true
try {
switch (change.type) {
case "add": {
array.push(change.newValue)
break
}
break
case "delete": {
const i = array.indexOf(change.oldValue)
if (i >= 0) {
array.splice(i, 1)
}
break
}
}
return change
} finally {
setAlreadyChanged = false
}
return change
})

@@ -98,0 +116,0 @@ )

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc