Skip to content

Response proxy clone #273

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

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ Changelog

_Note: Gaps between patch versions are faulty, broken or test releases._

## v3.??.?? (2022-??-??)

#### :boom: Breaking Change

* Now to clone and freeze server responses is used Proxy API if it supported `core/request/reponse`

#### :rocket: New Feature

* Added a common function to clone `core/object/proxy-clone`
* Added a common function to create a read-only view `core/object/proxy-readonly`
* Added a new bunch of constants `support` to check a runtime for features support `core/const`

## v3.74.7 (2022-01-31)

#### :bug: Bug Fix
Expand Down
16 changes: 16 additions & 0 deletions src/core/const/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Changelog
=========

> **Tags:**
> - :boom: [Breaking Change]
> - :rocket: [New Feature]
> - :bug: [Bug Fix]
> - :memo: [Documentation]
> - :house: [Internal]
> - :nail_care: [Polish]

## v3.??.?? (2022-??-??)

#### :rocket: New Feature

* Added a new bunch of constants `support` to check a runtime for features support
1 change: 1 addition & 0 deletions src/core/const/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ export const defProp = Object.freeze({
export const defReadonlyProp = Object.freeze({
configurable: true,
enumerable: true,
writable: false,
value: undefined
});
28 changes: 28 additions & 0 deletions src/core/const/support.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*!
* V4Fire Core
* https://github.com/V4Fire/Core
*
* Released under the MIT license
* https://github.com/V4Fire/Core/blob/master/LICENSE
*/

/**
* True if the runtime supports Proxy objects
*/
export const proxy = (() => {
try {
const obj = new Proxy({a: 1}, {
defineProperty(target: typeof obj, key: string, desc: PropertyDescriptor): boolean {
return Reflect.defineProperty(target, key, desc);
}
});

obj.a = 2;

return Object.keys(obj).toString() === 'a';

} catch {
return false;
}
})();

6 changes: 5 additions & 1 deletion src/core/data/modules/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import { EventEmitter2 as EventEmitter } from 'eventemitter2';

import symbolGenerator from 'core/symbol';
import { readonly } from 'core/object/proxy-readonly';

import { deprecate } from 'core/functools';
import { concatURLs } from 'core/url';
Expand Down Expand Up @@ -370,7 +371,7 @@ export default abstract class Provider extends ParamsProvider implements IProvid
}
});

res.data = Object.freeze(composition);
res.data = readonly(composition);
return res;
}),

Expand Down Expand Up @@ -487,6 +488,9 @@ export default abstract class Provider extends ParamsProvider implements IProvid

/**
* Sets a readonly value by the specified key to the current provider
*
* @param key
* @param val
*/
protected setReadonlyParam(key: string, val: unknown): void {
Object.defineProperty(this, key, {
Expand Down
6 changes: 6 additions & 0 deletions src/core/object/proxy-clone/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ Changelog
> - :house: [Internal]
> - :nail_care: [Polish]

## v3.??.?? (2022-??-??)

#### :rocket: New Feature

* Added a common function to clone

## v3.73.2 (2021-12-28)

#### :bug: Bug Fix
Expand Down
33 changes: 32 additions & 1 deletion src/core/object/proxy-clone/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const original = {
}
};

const clone = proxyClone(original);
const
clone = proxyClone(original);

clone.user.name = 'Jack';
clone.user.skills.push('boxing');
Expand All @@ -37,3 +38,33 @@ Because the process of cloning uses native Proxy objects, there are a few limita
2. `Object.isExtensible` always returns a value from the original object.
3. If the original object prevents extensions, then a cloned object will also prevent these extensions.
4. You can't redefine a property descriptor if it contains `configurable: false` attribute in the original object.

## Common clone

The module also exports a common implementation to clone objects. If the runtime supports Proxy API, it will be used.

```js
import { clone } from 'core/object/proxy-clone';

const original = {
user: {
name: 'Bob',
age: 56,
skills: ['singing', 'dancing', 'programming']
}
};

const
clone = clone(original);

clone.user.name = 'Jack';
clone.user.skills.push('boxing');

console.log(clone.user.name !== original.user.name);

// ['singing', 'dancing', 'programming', 'boxing']
console.log(clone.user.skills);

// ['singing', 'dancing', 'programming']
console.log(original.user.skills);
```
14 changes: 13 additions & 1 deletion src/core/object/proxy-clone/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,23 @@
* @packageDocumentation
*/

import * as support from 'core/const/support';

import { unimplement } from 'core/functools/implementation';

import { toOriginalObject, NULL } from 'core/object/proxy-clone/const';
import { resolveTarget, getRawValueFromStore, Descriptor } from 'core/object/proxy-clone/helpers';

/**
* Returns a clone of the specified object.
* If the runtime supports Proxy, it will be used to clone.
*
* @param obj
*/
export function clone<T>(obj: T): T {
return support.proxy ? proxyClone(obj) : Object.fastClone(obj, {freezable: false});
}

/**
* Returns a clone of the specified object.
* The function uses a Proxy object to create a clone. The process of cloning is a lazy operation.
Expand All @@ -31,7 +43,7 @@ export default function proxyClone<T>(obj: T): T {
return obj;
}

if (typeof Proxy !== 'function') {
if (!support.proxy) {
unimplement({
name: 'proxyClone',
type: 'function',
Expand Down
6 changes: 6 additions & 0 deletions src/core/object/proxy-readonly/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ Changelog
> - :house: [Internal]
> - :nail_care: [Polish]

## v3.??.?? (2022-??-??)

#### :rocket: New Feature

* Added a common function to create a read-only view

## v3.73.0 (2021-12-14)

#### :rocket: New Feature
Expand Down
36 changes: 31 additions & 5 deletions src/core/object/proxy-readonly/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,27 @@ const original = {
}
};

const clone = proxyReadonly(original);
const
readonly = proxyReadonly(original);

try {
clone.user.name = 'Jack';
readonly.user.name = 'Jack';

} catch (err) {
console.log(err);
}

try {
clone.user.skills.push('boxing');
readonly.user.skills.push('boxing');

} catch (err) {
console.log(err);
}

console.log(clone.user.name === original.user.name);
console.log(readonly.user.name === original.user.name);

// ['singing', 'dancing', 'programming']
console.log(clone.user.skills);
console.log(readonly.user.skills);

// ['singing', 'dancing', 'programming']
console.log(original.user.skills);
Expand All @@ -45,3 +46,28 @@ console.log(original.user.skills);
Because the process of cloning uses native Proxy objects, there are a few limitations:

1. You can't use `Object.preventExtension` at a clone object because it should be applied to the original object.

## Common readonly

The module also exports a common implementation to make objects read-only. If the runtime supports Proxy API, it will be used.

```js
import { readonly } from 'core/object/proxy-readonly';

const original = {
a: 1
};

const
readonly = proxyReadonly(original);

try {
readonly.a++;

} catch (err) {
console.log(err);
}

// 1
console.log(readonly.a);
```
14 changes: 13 additions & 1 deletion src/core/object/proxy-readonly/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,21 @@
* @packageDocumentation
*/

import * as support from 'core/const/support';

import { unimplement } from 'core/functools/implementation';
import { READONLY, PROXY } from 'core/prelude/types/const';

/**
* Returns a read-only view of the specified object.
* If the runtime supports Proxy, it will be used to create a view.
*
* @param obj
*/
export function readonly<T>(obj: T): T {
return support.proxy ? proxyReadonly(obj) : Object.freeze(obj);
}

/**
* Returns a read-only view of the specified object.
* The function uses a Proxy object to create a view.
Expand All @@ -28,7 +40,7 @@ export default function proxyReadonly<T>(obj: T): Readonly<T> {
return obj;
}

if (typeof Proxy !== 'function') {
if (!support.proxy) {
unimplement({
name: 'proxyReadonly',
type: 'function',
Expand Down
4 changes: 3 additions & 1 deletion src/core/object/watch/engines/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
* https://github.com/V4Fire/Core/blob/master/LICENSE
*/

import * as support from 'core/const/support';

import * as proxyEngine from 'core/object/watch/engines/proxy';
import * as accEngine from 'core/object/watch/engines/accessors';

export default typeof Proxy === 'function' ? proxyEngine : accEngine;
export default support.proxy ? proxyEngine : accEngine;
21 changes: 13 additions & 8 deletions src/core/prelude/object/clone/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ extend(Object, 'fastClone', (obj, opts?: FastCloneOptions) => {
const
p = opts ?? {};

if (Object.isFrozen(obj) && p.freezable !== false) {
return obj;
}

let
clone;

Expand Down Expand Up @@ -181,18 +185,19 @@ export function createSerializer(

return (key, value) => {
if (init && value === base) {
return objRef;
}
value = objRef;

if (!init) {
init = true;
}
} else {
if (!init) {
init = true;
}

if (replacer) {
return replacer(key, value);
if (replacer) {
value = replacer(key, value);
}
}

return value;
return Object.unwrapProxy(value);
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/core/prelude/object/clone/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe('core/prelude/object/clone/fastClone', () => {
obj = Object.freeze({a: 1, b: 2}),
clone = Object.fastClone(obj, {freezable: true});

expect(clone).not.toBe(obj);
expect(clone).toBe(obj);
expect(clone).toEqual(obj);
expect(Object.isFrozen(clone)).toBeTrue();
});
Expand Down
Loading