@sohanemon/utils 6.2.7 → 6.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/index.d.mts +665 -0
  2. package/dist/index.mjs +664 -0
  3. package/package.json +42 -18
  4. package/dist/components/html-injector.d.ts +0 -50
  5. package/dist/components/html-injector.js +0 -108
  6. package/dist/components/index.d.ts +0 -5
  7. package/dist/components/index.js +0 -7
  8. package/dist/components/media-wrapper.d.ts +0 -10
  9. package/dist/components/media-wrapper.js +0 -14
  10. package/dist/components/responsive-indicator.d.ts +0 -2
  11. package/dist/components/responsive-indicator.js +0 -68
  12. package/dist/components/scrollable-marker.d.ts +0 -1
  13. package/dist/components/scrollable-marker.js +0 -56
  14. package/dist/functions/cookie.d.ts +0 -6
  15. package/dist/functions/cookie.js +0 -22
  16. package/dist/functions/deepmerge.d.ts +0 -59
  17. package/dist/functions/deepmerge.js +0 -116
  18. package/dist/functions/hydrate.d.ts +0 -15
  19. package/dist/functions/hydrate.js +0 -31
  20. package/dist/functions/index.d.ts +0 -8
  21. package/dist/functions/index.js +0 -8
  22. package/dist/functions/object.d.ts +0 -93
  23. package/dist/functions/object.js +0 -67
  24. package/dist/functions/poll.d.ts +0 -38
  25. package/dist/functions/poll.js +0 -69
  26. package/dist/functions/schedule.d.ts +0 -12
  27. package/dist/functions/schedule.js +0 -29
  28. package/dist/functions/shield.d.ts +0 -18
  29. package/dist/functions/shield.js +0 -15
  30. package/dist/functions/utils.d.ts +0 -243
  31. package/dist/functions/utils.js +0 -439
  32. package/dist/hooks/action.d.ts +0 -20
  33. package/dist/hooks/action.js +0 -84
  34. package/dist/hooks/async.d.ts +0 -30
  35. package/dist/hooks/async.js +0 -82
  36. package/dist/hooks/index.d.ts +0 -192
  37. package/dist/hooks/index.js +0 -533
  38. package/dist/hooks/schedule.d.ts +0 -36
  39. package/dist/hooks/schedule.js +0 -68
  40. package/dist/index.d.ts +0 -2
  41. package/dist/index.js +0 -2
  42. package/dist/types/gates.d.ts +0 -133
  43. package/dist/types/gates.js +0 -1
  44. package/dist/types/guards.d.ts +0 -6
  45. package/dist/types/guards.js +0 -29
  46. package/dist/types/index.d.ts +0 -3
  47. package/dist/types/index.js +0 -3
  48. package/dist/types/utilities.d.ts +0 -62
  49. package/dist/types/utilities.js +0 -1
package/package.json CHANGED
@@ -1,23 +1,24 @@
1
1
  {
2
2
  "name": "@sohanemon/utils",
3
- "version": "6.2.7",
3
+ "version": "6.2.10",
4
4
  "author": "Sohan Emon <sohanemon@outlook.com>",
5
5
  "description": "",
6
6
  "type": "module",
7
+ "files": [
8
+ "dist"
9
+ ],
7
10
  "source": "./src/index.ts",
8
- "main": "./dist/index.js",
9
11
  "types": "./dist/index.d.ts",
12
+ "main": "./dist/index.js",
13
+ "module": "./dist/index.js",
10
14
  "exports": {
11
15
  ".": "./dist/index.js",
12
16
  "./core": "./dist/index.js",
13
17
  "./functions": "./dist/functions/index.js",
14
- "./functions/*": "./dist/functions/*.js",
15
18
  "./hooks": "./dist/hooks/index.js",
16
- "./hooks/*": "./dist/hooks/*.js",
17
19
  "./components": "./dist/components/index.js",
18
- "./components/*": "./dist/components/*.js",
19
20
  "./types": "./dist/types/index.js",
20
- "./types/*": "./dist/types/*.js"
21
+ "./package.json": "./package.json"
21
22
  },
22
23
  "typesVersions": {
23
24
  "*": {
@@ -38,19 +39,21 @@
38
39
  ]
39
40
  }
40
41
  },
41
- "files": [
42
- "dist",
43
- "README.md"
44
- ],
45
42
  "scripts": {
46
- "dev": "tsc --watch",
47
- "build": "tsc",
43
+ "build": "tsdown",
44
+ "dev": "tsdown --watch",
48
45
  "test": "vitest",
49
46
  "test:run": "vitest run",
50
47
  "test:list": "vitest list",
51
48
  "test:log": "vitest run --reporter verbose",
52
49
  "test:ui": "vitest --ui",
53
- "prepublish": "tsc",
50
+ "check": "biome check .",
51
+ "fix": "biome check --diagnostic-level=error --write .",
52
+ "lint": "biome lint .",
53
+ "lint:fix": "biome lint --write --unsafe .",
54
+ "format": "biome format .",
55
+ "format:write": "biome format . --write",
56
+ "typecheck": "tsgo --noEmit",
54
57
  "release": "tsgo --noEmit && npm run build && npm publish"
55
58
  },
56
59
  "keywords": [
@@ -58,19 +61,40 @@
58
61
  "cn"
59
62
  ],
60
63
  "license": "ISC",
61
- "homepage": "https://github.com/sohanemon/utils",
62
- "website": "https://sohanjs.web.app/utils",
64
+ "homepage": "https://sohanjs.web.app/utils",
65
+ "repository": {
66
+ "type": "git",
67
+ "url": "git+https://github.com/sohanemon/utils.git"
68
+ },
69
+ "bugs": {
70
+ "url": "https://github.com/sohanemon/utils/issues"
71
+ },
63
72
  "devDependencies": {
64
- "@types/node": "^24.10.0",
65
- "@types/react": "^19.2.2",
73
+ "@biomejs/biome": "2.3.8",
74
+ "@testing-library/jest-dom": "^6.9.1",
75
+ "@testing-library/react": "^16.3.0",
76
+ "@tsconfig/strictest": "^2.0.8",
77
+ "@types/node": "^24.10.1",
78
+ "@types/react": "^19.2.5",
79
+ "@types/react-dom": "^19.2.3",
80
+ "@vitejs/plugin-react": "^5.1.1",
66
81
  "@vitest/ui": "^4.0.14",
82
+ "tsdown": "^0.16.4",
67
83
  "typescript": "^5.9.3",
84
+ "vite": "npm:rolldown-vite@^7.2.5",
68
85
  "vitest": "^4.0.14"
69
86
  },
70
87
  "dependencies": {
71
88
  "@iconify/react": "^6.0.2",
72
89
  "clsx": "^2.1.1",
73
- "react": "^19.2.0",
90
+ "jsdom": "^27.2.0",
74
91
  "tailwind-merge": "^3.3.1"
92
+ },
93
+ "peerDependencies": {
94
+ "react": "^19.2.0",
95
+ "react-dom": "^19.2.0"
96
+ },
97
+ "publishConfig": {
98
+ "access": "public"
75
99
  }
76
100
  }
@@ -1,50 +0,0 @@
1
- import * as React from 'react';
2
- /**
3
- * Props for the HtmlInjector component
4
- */
5
- type HtmlInjectorProps = Omit<React.ComponentProps<'div'>, 'dangerouslySetInnerHTML'> & {
6
- /** The HTML content to inject and render */
7
- html: string;
8
- /**
9
- * Whether to sanitize the HTML content by removing potentially dangerous elements and attributes
10
- * @default false
11
- */
12
- sanitize?: boolean;
13
- /**
14
- * Whether to execute script tags found in the HTML content
15
- * @default true
16
- */
17
- executeScripts?: boolean;
18
- };
19
- /**
20
- * A robust component for safely injecting and rendering HTML content with optional script execution.
21
- *
22
- * This component provides a safe way to render dynamic HTML content with the following features:
23
- * - Optional HTML sanitization to remove dangerous elements and attributes
24
- * - Controlled script execution with proper cleanup
25
- * - Memory leak prevention by tracking and removing injected scripts
26
- * - Error handling for malformed HTML and script execution failures
27
- *
28
- * @example
29
- * ```tsx
30
- * // Basic HTML injection
31
- * <HtmlInjector html="<p>Hello <strong>World</strong></p>" />
32
- *
33
- * // With sanitization enabled
34
- * <HtmlInjector
35
- * html="<p>Safe content</p><script>alert('removed')</script>"
36
- * sanitize={true}
37
- * />
38
- *
39
- * // Disable script execution
40
- * <HtmlInjector
41
- * html="<p>Content</p><script>console.log('not executed')</script>"
42
- * executeScripts={false}
43
- * />
44
- * ```
45
- *
46
- * @param props - The component props
47
- * @returns A div element containing the injected HTML content, or null if no HTML is provided
48
- */
49
- export declare function HtmlInjector({ className, html, sanitize, executeScripts, ...props }: HtmlInjectorProps): import("react/jsx-runtime").JSX.Element;
50
- export {};
@@ -1,108 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx } from "react/jsx-runtime";
3
- import * as React from 'react';
4
- import { cn } from '../functions';
5
- /**
6
- * A robust component for safely injecting and rendering HTML content with optional script execution.
7
- *
8
- * This component provides a safe way to render dynamic HTML content with the following features:
9
- * - Optional HTML sanitization to remove dangerous elements and attributes
10
- * - Controlled script execution with proper cleanup
11
- * - Memory leak prevention by tracking and removing injected scripts
12
- * - Error handling for malformed HTML and script execution failures
13
- *
14
- * @example
15
- * ```tsx
16
- * // Basic HTML injection
17
- * <HtmlInjector html="<p>Hello <strong>World</strong></p>" />
18
- *
19
- * // With sanitization enabled
20
- * <HtmlInjector
21
- * html="<p>Safe content</p><script>alert('removed')</script>"
22
- * sanitize={true}
23
- * />
24
- *
25
- * // Disable script execution
26
- * <HtmlInjector
27
- * html="<p>Content</p><script>console.log('not executed')</script>"
28
- * executeScripts={false}
29
- * />
30
- * ```
31
- *
32
- * @param props - The component props
33
- * @returns A div element containing the injected HTML content, or null if no HTML is provided
34
- */
35
- export function HtmlInjector({ className, html, sanitize = false, executeScripts = true, ...props }) {
36
- const injectedScriptsRef = React.useRef([]);
37
- const containerRef = React.useRef(null);
38
- React.useEffect(() => {
39
- // NOTE: Cleanup previously injected scripts
40
- injectedScriptsRef.current.forEach((script) => {
41
- if (script.parentNode) {
42
- script.parentNode.removeChild(script);
43
- }
44
- });
45
- injectedScriptsRef.current = [];
46
- if (!executeScripts || !html)
47
- return;
48
- try {
49
- const tempContainer = document.createElement('div');
50
- tempContainer.innerHTML = html;
51
- const scripts = tempContainer.querySelectorAll('script');
52
- scripts.forEach((oldScript) => {
53
- const newScript = document.createElement('script');
54
- // HACK: Copy text content
55
- if (oldScript.textContent) {
56
- newScript.textContent = oldScript.textContent;
57
- }
58
- // HACK: Copy all attributes (src, type, async, defer, etc.)
59
- Array.from(oldScript.attributes).forEach((attr) => {
60
- newScript.setAttribute(attr.name, attr.value);
61
- });
62
- newScript.onerror = (error) => {
63
- console.error('Script injection error:', error);
64
- };
65
- document.body.appendChild(newScript);
66
- injectedScriptsRef.current.push(newScript);
67
- });
68
- }
69
- catch (error) {
70
- console.error('HTML injection error:', error);
71
- }
72
- }, [html, executeScripts]);
73
- React.useEffect(() => {
74
- return () => {
75
- injectedScriptsRef.current.forEach((script) => {
76
- if (script.parentNode) {
77
- script.parentNode.removeChild(script);
78
- }
79
- });
80
- };
81
- }, []);
82
- const processedHtml = React.useMemo(() => {
83
- if (!html)
84
- return '';
85
- if (sanitize) {
86
- // Basic sanitization - remove potentially dangerous elements
87
- const container = document.createElement('div');
88
- container.innerHTML = html;
89
- // Remove script tags if sanitize is enabled
90
- container.querySelectorAll('script').forEach((script) => script.remove());
91
- // Remove potentially dangerous attributes
92
- const dangerousAttrs = ['onclick', 'onload', 'onerror', 'onmouseover'];
93
- container.querySelectorAll('*').forEach((el) => {
94
- dangerousAttrs.forEach((attr) => {
95
- if (el.hasAttribute(attr)) {
96
- el.removeAttribute(attr);
97
- }
98
- });
99
- });
100
- return container.innerHTML;
101
- }
102
- return html;
103
- }, [html, sanitize]);
104
- if (!html) {
105
- return null;
106
- }
107
- return (_jsx("div", { ref: containerRef, className: cn(className), dangerouslySetInnerHTML: { __html: processedHtml }, ...props }));
108
- }
@@ -1,5 +0,0 @@
1
- export { Icon as Iconify } from '@iconify/react';
2
- export { HtmlInjector } from './html-injector';
3
- export { MediaWrapper } from './media-wrapper';
4
- export { ResponsiveIndicator, ResponsiveIndicator as TailwindIndicator, } from './responsive-indicator';
5
- export { ScrollableMarker } from './scrollable-marker';
@@ -1,7 +0,0 @@
1
- 'use client';
2
- //NOTE: It's currently unsupported to use "export *" in a client boundary
3
- export { Icon as Iconify } from '@iconify/react';
4
- export { HtmlInjector } from './html-injector';
5
- export { MediaWrapper } from './media-wrapper';
6
- export { ResponsiveIndicator, ResponsiveIndicator as TailwindIndicator, } from './responsive-indicator';
7
- export { ScrollableMarker } from './scrollable-marker';
@@ -1,10 +0,0 @@
1
- import * as React from 'react';
2
- type BreakPoints = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'max-sm' | 'max-md' | 'max-lg' | 'max-xl' | 'max-2xl';
3
- type MediaWrapperProps = React.ComponentProps<'div'> & {
4
- breakpoint: BreakPoints;
5
- as?: React.ElementType;
6
- fallback?: React.ElementType;
7
- classNameFallback?: string;
8
- };
9
- export declare function MediaWrapper({ breakpoint, as, fallback, classNameFallback, className: classNameOriginal, ...props }: MediaWrapperProps): import("react/jsx-runtime").JSX.Element;
10
- export {};
@@ -1,14 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx } from "react/jsx-runtime";
3
- import * as React from 'react';
4
- import { useMediaQuery } from '../hooks';
5
- export function MediaWrapper({ breakpoint, as = 'div', fallback = React.Fragment, classNameFallback, className: classNameOriginal, ...props }) {
6
- const overMedia = useMediaQuery(breakpoint.split('-').pop());
7
- const isMax = breakpoint.startsWith('max');
8
- const useFallback = overMedia === isMax;
9
- // Conditionally determining which component to render,
10
- // and what className should be passed to it.
11
- const Wrapper = useFallback ? fallback : as;
12
- const className = useFallback ? classNameFallback : classNameOriginal;
13
- return _jsx(Wrapper, { className: className, ...props });
14
- }
@@ -1,2 +0,0 @@
1
- import * as React from 'react';
2
- export declare const ResponsiveIndicator: React.FC;
@@ -1,68 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx } from "react/jsx-runtime";
3
- import * as React from 'react';
4
- import { isSSR } from '../functions';
5
- export const ResponsiveIndicator = () => {
6
- const [viewportWidth, setViewportWidth] = React.useState(isSSR ? 0 : window.innerWidth);
7
- const [position, setPosition] = React.useState(0); // State to manage button position
8
- React.useEffect(() => {
9
- const handleResize = () => {
10
- setViewportWidth(window.innerWidth);
11
- };
12
- window.addEventListener('resize', handleResize);
13
- return () => {
14
- window.removeEventListener('resize', handleResize);
15
- };
16
- }, []);
17
- // Function to handle button click
18
- const handleClick = () => {
19
- setPosition((prevPosition) => (prevPosition + 1) % 4); // Cycle through positions
20
- };
21
- let text = '';
22
- if (viewportWidth < 640) {
23
- text = 'xs';
24
- }
25
- else if (viewportWidth >= 640 && viewportWidth < 768) {
26
- text = 'sm';
27
- }
28
- else if (viewportWidth >= 768 && viewportWidth < 1024) {
29
- text = 'md';
30
- }
31
- else if (viewportWidth >= 1024 && viewportWidth < 1280) {
32
- text = 'lg';
33
- }
34
- else if (viewportWidth >= 1280 && viewportWidth < 1536) {
35
- text = 'xl';
36
- }
37
- else {
38
- text = '2xl';
39
- }
40
- // Define positions
41
- const positions = [
42
- { bottom: '2rem', left: '2rem' }, // Bottom left
43
- { bottom: '2rem', right: '2rem' }, // Bottom right
44
- { top: '2rem', right: '2rem' }, // Top right
45
- { top: '2rem', left: '2rem' }, // Top left
46
- ];
47
- const buttonStyle = {
48
- position: 'fixed',
49
- zIndex: 50,
50
- display: 'grid',
51
- height: '2.5rem',
52
- width: '2.5rem',
53
- borderRadius: '50%',
54
- placeContent: 'center',
55
- backgroundColor: '#2d3748',
56
- fontFamily: 'Courier New, Courier, monospace',
57
- fontSize: '1rem',
58
- color: '#ffffff',
59
- border: '2px solid #4a5568',
60
- boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
61
- padding: '0.5rem',
62
- transition: 'all 0.2s ease-in-out',
63
- ...positions[position], // Apply the current position
64
- };
65
- if (process.env.NODE_ENV === 'production')
66
- return null;
67
- return (_jsx("button", { type: "button", style: buttonStyle, onClick: handleClick, children: text }));
68
- };
@@ -1 +0,0 @@
1
- export declare function ScrollableMarker(): any;
@@ -1,56 +0,0 @@
1
- 'use client';
2
- import { useScheduledEffect } from '../hooks/schedule';
3
- export function ScrollableMarker() {
4
- useScheduledEffect(() => {
5
- const root = document.body;
6
- if (!root)
7
- return;
8
- const isScrollable = (el) => {
9
- const style = getComputedStyle(el);
10
- if (style.overflow === 'hidden' &&
11
- style.overflowY === 'hidden' &&
12
- style.overflowX === 'hidden')
13
- return false;
14
- const canScrollY = (style.overflowY === 'auto' || style.overflowY === 'scroll') &&
15
- el.scrollHeight > el.clientHeight;
16
- const canScrollX = (style.overflowX === 'auto' || style.overflowX === 'scroll') &&
17
- el.scrollWidth > el.clientWidth;
18
- return canScrollY || canScrollX;
19
- };
20
- const markScrollable = (el) => {
21
- if (isScrollable(el))
22
- el.dataset.scrollable = 'true';
23
- else
24
- delete el.dataset.scrollable;
25
- };
26
- const scanTree = (node) => {
27
- markScrollable(node);
28
- for (let i = 0; i < node.children.length; i++) {
29
- const child = node.children[i];
30
- scanTree(child);
31
- }
32
- };
33
- requestIdleCallback(() => scanTree(root));
34
- const observer = new MutationObserver((mutations) => {
35
- for (const m of mutations) {
36
- if (m.type === 'childList') {
37
- m.addedNodes.forEach((n) => {
38
- if (n instanceof HTMLElement)
39
- scanTree(n);
40
- });
41
- }
42
- else if (m.type === 'attributes' && m.target instanceof HTMLElement) {
43
- markScrollable(m.target);
44
- }
45
- }
46
- });
47
- observer.observe(root, {
48
- subtree: true,
49
- childList: true,
50
- attributes: true,
51
- attributeFilter: ['style', 'class'],
52
- });
53
- return () => observer.disconnect();
54
- }, []);
55
- return null;
56
- }
@@ -1,6 +0,0 @@
1
- export declare const setClientSideCookie: (name: string, value: string, days?: number, path?: string) => void;
2
- export declare const deleteClientSideCookie: (name: string, path?: string) => void;
3
- export declare const hasClientSideCookie: (name: string) => boolean;
4
- export declare const getClientSideCookie: (name: string) => {
5
- value: string;
6
- };
@@ -1,22 +0,0 @@
1
- export const setClientSideCookie = (name, value, days, path = '/') => {
2
- let expires = '';
3
- if (days) {
4
- const date = new Date();
5
- date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
6
- expires = `; expires=${date.toUTCString()}`;
7
- }
8
- document.cookie = `${name}=${value || ''}${expires}; path=${path}`;
9
- };
10
- export const deleteClientSideCookie = (name, path = '/') => {
11
- document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${path}`;
12
- };
13
- export const hasClientSideCookie = (name) => {
14
- return document.cookie.split('; ').some((row) => row.startsWith(`${name}=`));
15
- };
16
- export const getClientSideCookie = (name) => {
17
- const cookieValue = document.cookie
18
- .split('; ')
19
- .find((row) => row.startsWith(`${name}=`))
20
- ?.split('=')[1];
21
- return { value: cookieValue };
22
- };
@@ -1,59 +0,0 @@
1
- type TAllKeys<T> = T extends any ? keyof T : never;
2
- type TIndexValue<T, K extends PropertyKey, D = never> = T extends any ? K extends keyof T ? T[K] : D : never;
3
- type TPartialKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>> extends infer O ? {
4
- [P in keyof O]: O[P];
5
- } : never;
6
- type TFunction = (...a: any[]) => any;
7
- type TPrimitives = string | number | boolean | bigint | symbol | Date | TFunction;
8
- type TMerged<T> = [T] extends [Array<any>] ? {
9
- [K in keyof T]: TMerged<T[K]>;
10
- } : [T] extends [TPrimitives] ? T : [T] extends [object] ? TPartialKeys<{
11
- [K in TAllKeys<T>]: TMerged<TIndexValue<T, K>>;
12
- }, never> : T;
13
- /**
14
- * Deeply merges multiple objects, with later sources taking precedence.
15
- * Handles nested objects, arrays, and special object types with circular reference detection.
16
- *
17
- * Features:
18
- * - Deep merging of nested objects
19
- * - Configurable array merging strategies
20
- * - Circular reference detection and handling
21
- * - Support for symbols and special objects (Date, RegExp, etc.)
22
- * - Type-safe with improved generics
23
- * - Optional cloning to avoid mutation
24
- * - Custom merge functions for specific keys
25
- *
26
- * @template T - The target object type
27
- * @param target - The target object to merge into
28
- * @param sources - Source objects to merge from (can have additional properties)
29
- * @param options - Configuration options
30
- * @param options.arrayMerge - How to merge arrays: 'replace' (default), 'concat', or 'merge'
31
- * @param options.clone - Whether to clone the target (default: true)
32
- * @param options.customMerge - Custom merge function for specific keys
33
- * @param options.maxDepth - Maximum recursion depth (default: 100)
34
- * @returns The merged object with proper typing
35
- *
36
- * @example
37
- * // Basic merge
38
- * deepmerge({ a: 1 }, { b: 2 }) // { a: 1, b: 2 }
39
- *
40
- * @example
41
- * // Nested merge
42
- * deepmerge({ a: { x: 1 } }, { a: { y: 2 } }) // { a: { x: 1, y: 2 } }
43
- *
44
- * @example
45
- * // Array concat
46
- * deepmerge({ arr: [1] }, { arr: [2] }, { arrayMerge: 'concat' }) // { arr: [1, 2] }
47
- *
48
- * @example
49
- * // Sources with extra properties
50
- * deepmerge({ a: 1 }, { b: 2, c: 3 }) // { a: 1, b: 2, c: 3 }
51
- */
52
- export declare function deepmerge<T extends Record<string, any>, S extends Record<string, any>[]>(target: T, ...sources: S): TMerged<T | S[number]>;
53
- export declare function deepmerge<T extends Record<string, any>, S extends Record<string, any>[]>(target: T, sources: S, options?: {
54
- arrayMerge?: 'replace' | 'concat' | 'merge' | ((target: any[], source: any[]) => any[]);
55
- clone?: boolean;
56
- customMerge?: (key: string | symbol, targetValue: any, sourceValue: any) => any;
57
- maxDepth?: number;
58
- }): TMerged<T | S[number]>;
59
- export {};
@@ -1,116 +0,0 @@
1
- import { isPlainObject } from '../types';
2
- export function deepmerge(target, ...args) {
3
- let sources;
4
- let options = {};
5
- // Check if last arg is options object
6
- const lastArg = args[args.length - 1];
7
- if (lastArg &&
8
- typeof lastArg === 'object' &&
9
- !Array.isArray(lastArg) &&
10
- (lastArg.arrayMerge !== undefined ||
11
- lastArg.clone !== undefined ||
12
- lastArg.customMerge !== undefined ||
13
- lastArg.maxDepth !== undefined)) {
14
- options = { ...options, ...lastArg };
15
- sources = args.slice(0, -1);
16
- }
17
- else {
18
- sources = args;
19
- }
20
- const { arrayMerge = 'replace', clone = true, customMerge, maxDepth = 100, } = options;
21
- const visited = new WeakMap();
22
- return mergeObjects(target, sources, 0);
23
- function mergeObjects(target, sources, depth) {
24
- if (depth >= maxDepth) {
25
- console.warn(`[deepmerge] Maximum depth ${maxDepth} exceeded. Returning target as-is.`);
26
- return target;
27
- }
28
- if (!isPlainObject(target) && !Array.isArray(target)) {
29
- // For primitives or special objects, return the last source or target
30
- for (const source of sources) {
31
- if (source !== undefined)
32
- return source;
33
- }
34
- return target;
35
- }
36
- let result = clone
37
- ? Array.isArray(target)
38
- ? [...target]
39
- : { ...target }
40
- : target;
41
- for (const source of sources) {
42
- if (source == null)
43
- continue;
44
- if (visited.has(source)) {
45
- // Circular reference, skip
46
- continue;
47
- }
48
- visited.set(source, result);
49
- if (Array.isArray(result) && Array.isArray(source)) {
50
- result = mergeArrays(result, source, arrayMerge);
51
- }
52
- else if (isPlainObject(result) && isPlainObject(source)) {
53
- const keys = new Set([
54
- ...Object.keys(result),
55
- ...Object.keys(source),
56
- ...Object.getOwnPropertySymbols(result),
57
- ...Object.getOwnPropertySymbols(source),
58
- ]);
59
- for (const key of keys) {
60
- const targetValue = result[key];
61
- const sourceValue = source[key];
62
- if (customMerge &&
63
- customMerge(key, targetValue, sourceValue) !== undefined) {
64
- result[key] = customMerge(key, targetValue, sourceValue);
65
- }
66
- else if (isPlainObject(targetValue) && isPlainObject(sourceValue)) {
67
- result[key] = mergeObjects(targetValue, [sourceValue], depth + 1);
68
- }
69
- else if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
70
- result[key] = mergeArrays(targetValue, sourceValue, arrayMerge);
71
- }
72
- else if (sourceValue !== undefined) {
73
- result[key] = sourceValue;
74
- }
75
- }
76
- }
77
- else {
78
- // If types don't match, source takes precedence
79
- result = source;
80
- }
81
- }
82
- return result;
83
- }
84
- function mergeArrays(target, source, strategy) {
85
- if (typeof strategy === 'function') {
86
- return strategy(target, source);
87
- }
88
- switch (strategy) {
89
- case 'concat':
90
- return [...target, ...source];
91
- case 'merge':
92
- const maxLength = Math.max(target.length, source.length);
93
- const merged = [];
94
- for (let i = 0; i < maxLength; i++) {
95
- if (i < target.length && i < source.length) {
96
- if (isPlainObject(target[i]) && isPlainObject(source[i])) {
97
- merged[i] = mergeObjects(target[i], [source[i]], 0);
98
- }
99
- else {
100
- merged[i] = source[i];
101
- }
102
- }
103
- else if (i < target.length) {
104
- merged[i] = target[i];
105
- }
106
- else {
107
- merged[i] = source[i];
108
- }
109
- }
110
- return merged;
111
- case 'replace':
112
- default:
113
- return [...source];
114
- }
115
- }
116
- }
@@ -1,15 +0,0 @@
1
- type Hydrate<T> = T extends null ? undefined : T extends (infer U)[] ? Hydrate<U>[] : T extends object ? {
2
- [K in keyof T]: Hydrate<T[K]>;
3
- } : T;
4
- /**
5
- * Converts all `null` values to `undefined` in the data structure recursively.
6
- *
7
- * @param data - Any input data (object, array, primitive)
8
- * @returns Same type as input, but with all nulls replaced by undefined
9
- *
10
- * @example
11
- * hydrate({ a: null, b: 'test' }) // { a: undefined, b: 'test' }
12
- * hydrate([null, 1, { c: null }]) // [undefined, 1, { c: undefined }]
13
- */
14
- export declare function hydrate<T>(data: T): Hydrate<T>;
15
- export {};