Thanks to visit codestin.com
Credit goes to github.com

Skip to content
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
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export { unsafe } from './unsafe';
export { rawReturn } from './rawReturn';
export { isDraft } from './utils/draft';
export { isDraftable } from './utils/draft';
export { markSimpleObject } from './utils/marker';

export { castDraft, castImmutable } from './utils/cast';
export type { Immutable, Draft, Patches, Patch, Options } from './interface';
20 changes: 20 additions & 0 deletions src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,26 @@ export interface Options<O extends PatchesOptions, F extends boolean> {
mark?: Mark<O, F>;
}

export interface ExternalOptions<O extends PatchesOptions, F extends boolean> {
/**
* In strict mode, Forbid accessing non-draftable values and forbid returning a non-draft value.
*/
strict?: boolean;
/**
* Enable patch, and return the patches and inversePatches.
*/
enablePatches?: O;
/**
* Enable autoFreeze, and return frozen state.
*/
enableAutoFreeze?: F;
/**
* Set a mark to determine if the object is mutable or if an instance is an immutable.
* And it can also return a shallow copy function(AutoFreeze and Patches should both be disabled).
*/
mark?: Mark<O, F>[] | Mark<O, F>;
}

// Exclude `symbol`
type Primitive = string | number | bigint | boolean | null | undefined;

Expand Down
33 changes: 25 additions & 8 deletions src/makeCreator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {
CreateResult,
Draft,
Mark,
Options,
ExternalOptions,
PatchesOptions,
Result,
} from './interface';
Expand All @@ -14,13 +16,13 @@ import {
revokeProxy,
} from './utils';
import { current, handleReturnValue } from './current';
import { RAW_RETURN_SYMBOL } from './constant';
import { RAW_RETURN_SYMBOL, dataTypes } from './constant';

type MakeCreator = <
_F extends boolean = false,
_O extends PatchesOptions = false
>(
options?: Options<_O, _F>
options?: ExternalOptions<_O, _F>
) => {
<
T extends any,
Expand All @@ -30,7 +32,7 @@ type MakeCreator = <
>(
base: T,
mutate: (draft: Draft<T>) => R,
options?: Options<O, F>
options?: ExternalOptions<O, F>
): CreateResult<T, O, F, R>;
<
T extends any,
Expand All @@ -40,7 +42,7 @@ type MakeCreator = <
>(
base: T,
mutate: (draft: T) => R,
options?: Options<O, F>
options?: ExternalOptions<O, F>
): CreateResult<T, O, F, R>;
<
T extends any,
Expand All @@ -50,11 +52,11 @@ type MakeCreator = <
R extends void | Promise<void> = void
>(
mutate: (draft: Draft<T>, ...args: P) => R,
options?: Options<O, F>
options?: ExternalOptions<O, F>
): (base: T, ...args: P) => CreateResult<T, O, F, R>;
<T extends any, O extends PatchesOptions = _O, F extends boolean = _F>(
base: T,
options?: Options<O, F>
options?: ExternalOptions<O, F>
): [T, () => Result<T, O, F>];
};

Expand Down Expand Up @@ -104,7 +106,7 @@ export const makeCreator: MakeCreator = (arg) => {
}
const base = arg0;
const mutate = arg1 as (...args: any[]) => any;
let options = arg2 as Options<any, any>;
let options = arg2;
if (typeof arg1 !== 'function') {
options = arg1;
}
Expand All @@ -122,7 +124,22 @@ export const makeCreator: MakeCreator = (arg) => {
...options,
};
const state = isDraft(base) ? current(base) : base;
const mark = options.mark;
const mark = Array.isArray(options.mark)
? (((value: unknown, types: typeof dataTypes) => {
for (const mark of options.mark as Mark<any, any>[]) {
if (__DEV__ && typeof mark !== 'function') {
throw new Error(
`Invalid mark: ${mark}, 'mark' should be a function.`
);
}
const result = mark(value, types);
if (result) {
return result;
}
}
return;
}) as Mark<any, any>)
: options.mark;
const enablePatches = options.enablePatches ?? false;
const strict = options.strict ?? false;
const enableAutoFreeze = options.enableAutoFreeze ?? false;
Expand Down
27 changes: 27 additions & 0 deletions src/utils/marker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { dataTypes } from '../constant';

const constructorString = Object.prototype.constructor.toString();
/**
* Check if the value is a simple object(No prototype chain object or iframe same-origin object),
* support case: https://github.com/unadlib/mutative/issues/17
*/
const isSimpleObject = (value: unknown) => {
const prototype = Object.getPrototypeOf(value);
if (prototype === null) {
return true;
}
const constructor =
Object.hasOwnProperty.call(prototype, 'constructor') &&
prototype.constructor;
return (
typeof constructor === 'function' &&
Function.toString.call(constructor) === constructorString
);
};

export const markSimpleObject = (value: unknown) => {
if (isSimpleObject(value)) {
return dataTypes.immutable;
}
return;
};
92 changes: 92 additions & 0 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
isDraftable,
isDraft,
Draft,
markSimpleObject,
} from '../src';
import { PROXY_DRAFT } from '../src/constant';

Expand Down Expand Up @@ -3683,3 +3684,94 @@ test('preserves non-enumerable properties - useStrictShallowCopy:false', () => {
if (useStrictShallowCopy) expect(isEnumerable(nextState, 'foo')).toBeFalsy();
if (useStrictShallowCopy) expect(nextState.baz).toBeTruthy();
});

test('base - Multiple mark config', () => {
class Base {
a = 1;
}

class Foo {
a = 1;
}

const baseState = {
base: new Base(),
foo: new Foo(),
simpleObject: Object.create(null),
};

const state = create(
baseState,
(draft) => {
draft.base.a = 2;
draft.foo.a = 2;
draft.simpleObject.a = 2;
},
{
mark: [
markSimpleObject,
(target, types) => {
if (target instanceof Base) return types.immutable;
},
],
}
);

expect(state).not.toBe(baseState);
expect(state.base).toEqual({ a: 2 });
expect(state.base).not.toBe(baseState.base);
expect(state.foo).toEqual({ a: 2 });
expect(state.foo).toBe(baseState.foo);
expect(state.simpleObject).toEqual({ a: 2 });
expect(state.simpleObject).not.toBe(baseState.simpleObject);
});

test('base check mark function - Multiple mark config', () => {
class Base {
a = 1;
}

class Foo {
a = 1;
}

const baseState = {
base: new Base(),
foo: new Foo(),
simpleObject: Object.create(null),
};

[
null,
false,
true,
0,
1,
'test',
'',
undefined,
{},
new Map(),
new Set(),
].forEach((item) => {
expect(() => {
create(
baseState,
(draft) => {
draft.base.a = 2;
draft.foo.a = 2;
draft.simpleObject.a = 2;
},
{
mark: [
// @ts-expect-error
item,
(target) => {
if (target instanceof Base) return 'immutable';
},
],
}
);
}).toThrowError();
});
});