├── .yarnrc.yml
├── .gitignore
├── dev
├── index.html
├── index.ts
└── App.ts
├── eslint.config.js
├── config
├── rollup.config.js
├── vite.config.ts
├── vitest.config.ts
├── release-it
│ ├── stable.json
│ ├── rc.json
│ ├── beta.json
│ └── alpha.json
├── eslint.config.js
└── types
│ ├── cjs.json
│ ├── es.json
│ └── umd.json
├── .prettierrc
├── tsconfig.json
├── LICENSE
├── src
├── utils.ts
├── index.ts
├── options.ts
└── copier.ts
├── index.d.ts
├── package.json
├── __tests__
├── utils.test.ts
├── copier.test.ts
└── index.test.ts
├── benchmark
└── index.js
├── CHANGELOG.md
└── README.md
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .yarn
2 | coverage
3 | dist
4 | node_modules
--------------------------------------------------------------------------------
/dev/index.html:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import config from './config/eslint.config.js';
2 |
3 | export default config;
4 |
--------------------------------------------------------------------------------
/config/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { createRollupConfig } from '@planttheidea/build-tools';
2 |
3 | export default createRollupConfig();
4 |
--------------------------------------------------------------------------------
/config/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { createViteConfig } from '@planttheidea/build-tools';
2 |
3 | export default createViteConfig({
4 | development: 'dev',
5 | });
6 |
--------------------------------------------------------------------------------
/config/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { createVitestConfig } from '@planttheidea/build-tools';
2 |
3 | export default createVitestConfig({
4 | react: false,
5 | source: 'src',
6 | });
7 |
--------------------------------------------------------------------------------
/config/release-it/stable.json:
--------------------------------------------------------------------------------
1 | {
2 | "github": {
3 | "release": true,
4 | "tagName": "v${version}"
5 | },
6 | "hooks": {
7 | "before:init": ["npm run format:check", "npm run typecheck", "npm run lint", "npm run test", "npm run build"]
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/config/release-it/rc.json:
--------------------------------------------------------------------------------
1 | {
2 | "github": {
3 | "release": true,
4 | "tagName": "v${version}"
5 | },
6 | "hooks": {
7 | "before:init": ["npm run format:check", "npm run typecheck", "npm run lint", "npm run test", "npm run build"]
8 | },
9 | "npm": {
10 | "tag": "next"
11 | },
12 | "preReleaseId": "rc"
13 | }
14 |
--------------------------------------------------------------------------------
/config/release-it/beta.json:
--------------------------------------------------------------------------------
1 | {
2 | "github": {
3 | "release": true,
4 | "tagName": "v${version}"
5 | },
6 | "hooks": {
7 | "before:init": ["npm run format:check", "npm run typecheck", "npm run lint", "npm run test", "npm run build"]
8 | },
9 | "npm": {
10 | "tag": "next"
11 | },
12 | "preReleaseId": "beta"
13 | }
14 |
--------------------------------------------------------------------------------
/config/release-it/alpha.json:
--------------------------------------------------------------------------------
1 | {
2 | "github": {
3 | "release": true,
4 | "tagName": "v${version}"
5 | },
6 | "hooks": {
7 | "before:init": ["npm run format:check", "npm run typecheck", "npm run lint", "npm run test", "npm run build"]
8 | },
9 | "npm": {
10 | "tag": "next"
11 | },
12 | "preReleaseId": "alpha"
13 | }
14 |
--------------------------------------------------------------------------------
/dev/index.ts:
--------------------------------------------------------------------------------
1 | import './App.ts';
2 |
3 | document.body.style.backgroundColor = '#1d1d1d';
4 | document.body.style.color = '#d5d5d5';
5 | document.body.style.margin = '0px';
6 | document.body.style.padding = '0px';
7 |
8 | const div = document.createElement('div');
9 |
10 | div.textContent = 'Check the console for details.';
11 |
12 | document.body.appendChild(div);
13 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "always",
3 | "bracketSameLine": false,
4 | "bracketSpacing": true,
5 | "checkIgnorePragma": true,
6 | "embeddedLanguageFormatting": "auto",
7 | "endOfLine": "lf",
8 | "experimentalTernaries": false,
9 | "experimentalOperatorPosition": "start",
10 | "jsxSingleQuote": false,
11 | "insertPragma": false,
12 | "objectWrap": "preserve",
13 | "printWidth": 120,
14 | "proseWrap": "always",
15 | "requirePragma": false,
16 | "quoteProps": "as-needed",
17 | "semi": true,
18 | "singleAttributePerLine": true,
19 | "singleQuote": true,
20 | "tabWidth": 2,
21 | "trailingComma": "all",
22 | "useTabs": false
23 | }
24 |
--------------------------------------------------------------------------------
/config/eslint.config.js:
--------------------------------------------------------------------------------
1 | import { createEslintConfig } from '@planttheidea/build-tools';
2 |
3 | export default createEslintConfig({
4 | config: 'config',
5 | configs: [
6 | {
7 | rules: {
8 | '@typescript-eslint/no-non-null-assertion': 'off',
9 | '@typescript-eslint/no-unsafe-assignment': 'off',
10 | '@typescript-eslint/no-unsafe-call': 'off',
11 | '@typescript-eslint/no-unsafe-member-access': 'off',
12 | '@typescript-eslint/no-unsafe-return': 'off',
13 | '@typescript-eslint/prefer-for-of': 'off',
14 | '@typescript-eslint/prefer-nullish-coalescing': 'off',
15 | },
16 | },
17 | ],
18 | development: 'dev',
19 | react: false,
20 | source: 'src',
21 | });
22 |
--------------------------------------------------------------------------------
/config/types/cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "declaration": true,
5 | "emitDeclarationOnly": false,
6 | "esModuleInterop": true,
7 | "isolatedModules": true,
8 | "lib": ["ESNext", "DOM"],
9 | "module": "Node16",
10 | "moduleDetection": "force",
11 | "moduleResolution": "Node16",
12 | "noFallthroughCasesInSwitch": true,
13 | "noImplicitAny": true,
14 | "noImplicitOverride": true,
15 | "noUncheckedIndexedAccess": true,
16 | "resolveJsonModule": true,
17 | "skipLibCheck": true,
18 | "sourceMap": true,
19 | "strict": true,
20 | "strictNullChecks": true,
21 | "inlineSources": true,
22 | "target": "ES2015",
23 | "verbatimModuleSyntax": true,
24 | "types": ["node"],
25 | "outDir": "../../dist/cjs"
26 | },
27 | "exclude": ["**/node_modules/**", "**/__tests__/**"],
28 | "include": ["../../src/**/*.ts"]
29 | }
30 |
--------------------------------------------------------------------------------
/config/types/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "declaration": true,
5 | "emitDeclarationOnly": false,
6 | "esModuleInterop": true,
7 | "isolatedModules": true,
8 | "lib": ["ESNext", "DOM"],
9 | "module": "NodeNext",
10 | "moduleDetection": "force",
11 | "moduleResolution": "NodeNext",
12 | "noFallthroughCasesInSwitch": true,
13 | "noImplicitAny": true,
14 | "noImplicitOverride": true,
15 | "noUncheckedIndexedAccess": true,
16 | "resolveJsonModule": true,
17 | "skipLibCheck": true,
18 | "sourceMap": true,
19 | "strict": true,
20 | "strictNullChecks": true,
21 | "inlineSources": true,
22 | "target": "ES2015",
23 | "verbatimModuleSyntax": true,
24 | "types": ["node"],
25 | "outDir": "../../dist/es"
26 | },
27 | "exclude": ["**/node_modules/**", "**/__tests__/**"],
28 | "include": ["../../src/**/*.ts"]
29 | }
30 |
--------------------------------------------------------------------------------
/config/types/umd.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "declaration": true,
5 | "emitDeclarationOnly": false,
6 | "esModuleInterop": true,
7 | "isolatedModules": true,
8 | "lib": ["ESNext", "DOM"],
9 | "module": "ESNext",
10 | "moduleDetection": "force",
11 | "moduleResolution": "Bundler",
12 | "noFallthroughCasesInSwitch": true,
13 | "noImplicitAny": true,
14 | "noImplicitOverride": true,
15 | "noUncheckedIndexedAccess": true,
16 | "resolveJsonModule": true,
17 | "skipLibCheck": true,
18 | "sourceMap": true,
19 | "strict": true,
20 | "strictNullChecks": true,
21 | "inlineSources": true,
22 | "target": "ES2015",
23 | "verbatimModuleSyntax": true,
24 | "types": ["node"],
25 | "outDir": "../../dist/umd"
26 | },
27 | "exclude": ["**/node_modules/**", "**/__tests__/**"],
28 | "include": ["../../src/**/*.ts"]
29 | }
30 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "declaration": false,
5 | "emitDeclarationOnly": false,
6 | "esModuleInterop": true,
7 | "isolatedModules": true,
8 | "lib": ["ESNext", "DOM"],
9 | "module": "NodeNext",
10 | "moduleDetection": "force",
11 | "moduleResolution": "NodeNext",
12 | "noFallthroughCasesInSwitch": true,
13 | "noImplicitAny": true,
14 | "noImplicitOverride": true,
15 | "noUncheckedIndexedAccess": true,
16 | "resolveJsonModule": true,
17 | "skipLibCheck": true,
18 | "sourceMap": true,
19 | "strict": true,
20 | "strictNullChecks": true,
21 | "inlineSources": true,
22 | "target": "ES2015",
23 | "verbatimModuleSyntax": true,
24 | "types": ["node"],
25 | "baseUrl": "src",
26 | "outDir": "dist",
27 | "rootDir": "./"
28 | },
29 | "exclude": ["**/node_modules/**", "dist/**/*"],
30 | "include": ["config/**/*.ts", "dev/**/*.ts", "src/**/*.ts", "__tests__/**/*.ts"]
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Tony Quetano
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | export interface Cache {
2 | has: (value: any) => boolean;
3 | set: (key: any, value: any) => void;
4 | get: (key: any) => any;
5 | }
6 |
7 | // eslint-disable-next-line @typescript-eslint/unbound-method
8 | const toStringFunction = Function.prototype.toString;
9 | // eslint-disable-next-line @typescript-eslint/unbound-method
10 | const toStringObject = Object.prototype.toString;
11 |
12 | /**
13 | * Get an empty version of the object with the same prototype it has.
14 | */
15 | export function getCleanClone(prototype: any): any {
16 | if (!prototype) {
17 | return Object.create(null);
18 | }
19 |
20 | const Constructor = prototype.constructor;
21 |
22 | if (Constructor === Object) {
23 | return prototype === Object.prototype ? {} : Object.create(prototype as object | null);
24 | }
25 |
26 | if (Constructor && ~toStringFunction.call(Constructor).indexOf('[native code]')) {
27 | try {
28 | return new Constructor();
29 | } catch {
30 | // Ignore
31 | }
32 | }
33 |
34 | return Object.create(prototype as object | null);
35 | }
36 |
37 | /**
38 | * Get the tag of the value passed, so that the correct copier can be used.
39 | */
40 | export function getTag(value: any): string {
41 | const stringTag = value[Symbol.toStringTag];
42 |
43 | if (stringTag) {
44 | return stringTag;
45 | }
46 |
47 | const type = toStringObject.call(value);
48 |
49 | return type.substring(8, type.length - 1);
50 | }
51 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | interface Cache {
2 | has: (value: any) => boolean;
3 | set: (key: any, value: any) => void;
4 | get: (key: any) => any;
5 | }
6 |
7 | type InternalCopier = (value: Value, state: State) => Value;
8 | interface State {
9 | Constructor: any;
10 | cache: Cache;
11 | copier: InternalCopier;
12 | prototype: any;
13 | }
14 |
15 | interface CopierMethods {
16 | array?: InternalCopier;
17 | arrayBuffer?: InternalCopier;
18 | asyncGenerator?: InternalCopier;
19 | blob?: InternalCopier;
20 | dataView?: InternalCopier;
21 | date?: InternalCopier;
22 | error?: InternalCopier;
23 | generator?: InternalCopier;
24 | map?: InternalCopier