payload-sanitizer 0.0.2 → 0.2.0

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.
package/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
  [![npm downloads](https://img.shields.io/npm/dm/payload-sanitizer.svg)](https://www.npmjs.com/package/payload-sanitizer)
5
5
  [![license](https://img.shields.io/npm/l/payload-sanitizer.svg)](./LICENSE)
6
6
 
7
+ [![CI](https://github.com/mohit838/payload-sanitizer/actions/workflows/ci.yml/badge.svg)](https://github.com/mohit838/payload-sanitizer/actions/workflows/ci.yml)
8
+
7
9
  Tiny zero‑dependency sanitizer for JS/TS payloads that removes common junk (empty strings, whitespace-only strings, `null`, `undefined`, optional dash marker, `NaN`) without mutating the original value. Works in both frontend and backend code.
8
10
 
9
11
  ## Install
@@ -45,6 +47,8 @@ console.log(clean);
45
47
  // }
46
48
  ```
47
49
 
50
+ See `examples/` for frontend + backend usage.
51
+
48
52
  ## Why this instead of validation libraries?
49
53
 
50
54
  Validation libs (e.g., Zod) parse **against a schema**. `payload-sanitizer` just **cleans/normalizes** data with simple rules—no schema required. They pair well:
@@ -66,8 +70,12 @@ Returns a cleaned clone of `payload` (objects and arrays) without mutating the i
66
70
  - `trimStrings` (default `true`): `value.trim()` on strings before checks.
67
71
  - `cleanArrays` (default `true`): sanitize array items and drop ones that should be removed.
68
72
  - `drop` (defaults to `["undefined","null","emptyString","whitespaceString"]`): presets to remove. Presets: `"undefined" | "null" | "emptyString" | "whitespaceString" | "dash" | "nan"`.
73
+ - `dropEmptyObjects` (default `false`): remove objects that become empty after sanitizing.
74
+ - `dropEmptyArrays` (default `false`): remove arrays that become empty after sanitizing.
69
75
  - `keepKeys`: key names to always keep even if value looks droppable.
76
+ - `keepPaths`: exact paths to always keep (e.g., `"filters.status"`).
70
77
  - `dropKeys`: key names to always remove.
78
+ - `dropPaths`: exact paths to always drop (e.g., `"meta.debug"`).
71
79
  - `dropValues`: explicit values to remove (uses `Object.is`).
72
80
  - `shouldDrop(value, keyPath)`: custom predicate; return `true` to drop. `keyPath` is an array of keys/indexes from root.
73
81
 
@@ -76,6 +84,25 @@ Notes:
76
84
  - `0`, `false`, and `""` inside `keepKeys` are preserved by design.
77
85
  - If everything is dropped, arrays become `[]`, objects become `{}`; primitives are returned as-is.
78
86
 
87
+ Example with empty-object/array dropping:
88
+
89
+ ```ts
90
+ sanitize(payload, {
91
+ drop: ["undefined", "null", "emptyString", "whitespaceString", "dash"],
92
+ dropEmptyObjects: true,
93
+ dropEmptyArrays: true,
94
+ });
95
+ ```
96
+
97
+ Example with path-based rules:
98
+
99
+ ```ts
100
+ sanitize(payload, {
101
+ keepPaths: ["filters.status"],
102
+ dropPaths: ["meta.debug"],
103
+ });
104
+ ```
105
+
79
106
  ### `sanitize.with(baseOptions)`
80
107
 
81
108
  Creates a preconfigured sanitizer.
@@ -132,6 +159,15 @@ pnpm test
132
159
  pnpm build
133
160
  ```
134
161
 
162
+ ## Contributing
163
+
164
+ Contributions are welcome!
165
+ If you have ideas, edge cases, or want to improve performance/docs, please open an issue or PR.
166
+
167
+ - Read: `CONTRIBUTING.md`
168
+ - Report bugs: GitHub Issues
169
+ - Feature requests: GitHub Issues
170
+
135
171
  ## License
136
172
 
137
173
  MIT
package/dist/index.cjs CHANGED
@@ -11,6 +11,8 @@ var DEFAULT_OPTIONS = {
11
11
  deep: true,
12
12
  trimStrings: true,
13
13
  cleanArrays: true,
14
+ dropEmptyObjects: false,
15
+ dropEmptyArrays: false,
14
16
  drop: DEFAULT_DROP
15
17
  };
16
18
  function isPlainObject(value) {
@@ -59,11 +61,37 @@ function shouldDropExact(value, exactValues) {
59
61
  function hasKey(list, key) {
60
62
  return !!list && list.includes(key);
61
63
  }
64
+ function toKeyPath(path) {
65
+ if (Array.isArray(path)) return path;
66
+ if (path.trim() === "") return [];
67
+ return path.split(".").map((seg) => {
68
+ const n = Number(seg);
69
+ return Number.isInteger(n) && String(n) === seg ? n : seg;
70
+ });
71
+ }
72
+ function pathEquals(a, b) {
73
+ if (a.length !== b.length) return false;
74
+ for (let i = 0; i < a.length; i++) {
75
+ if (a[i] !== b[i]) return false;
76
+ }
77
+ return true;
78
+ }
79
+ function hasPath(list, path) {
80
+ if (!list || list.length === 0) return false;
81
+ for (const item of list) {
82
+ if (pathEquals(toKeyPath(item), path)) return true;
83
+ }
84
+ return false;
85
+ }
62
86
  function sanitizeAny(input, options, path) {
63
87
  const normalized = normalizeValue(input, options);
64
- if (shouldDropPreset(normalized, options.drop)) return void 0;
65
- if (shouldDropExact(normalized, options.dropValues)) return void 0;
66
- if (options.shouldDrop?.(normalized, path)) return void 0;
88
+ if (hasPath(options.dropPaths, path)) return void 0;
89
+ const isKeptPath = hasPath(options.keepPaths, path);
90
+ if (!isKeptPath) {
91
+ if (shouldDropPreset(normalized, options.drop)) return void 0;
92
+ if (shouldDropExact(normalized, options.dropValues)) return void 0;
93
+ if (options.shouldDrop?.(normalized, path)) return void 0;
94
+ }
67
95
  if (Array.isArray(normalized)) {
68
96
  if (!options.cleanArrays) return normalized.slice();
69
97
  const out = [];
@@ -71,6 +99,7 @@ function sanitizeAny(input, options, path) {
71
99
  const next = sanitizeAny(normalized[i], options, path.concat(i));
72
100
  if (next !== void 0) out.push(next);
73
101
  }
102
+ if (options.dropEmptyArrays && out.length === 0) return void 0;
74
103
  return out;
75
104
  }
76
105
  if (isPlainObject(normalized)) {
@@ -85,6 +114,8 @@ function sanitizeAny(input, options, path) {
85
114
  const next = sanitizeAny(v, options, path.concat(k));
86
115
  if (next !== void 0) out2[k] = next;
87
116
  }
117
+ if (options.dropEmptyObjects && Object.keys(out2).length === 0)
118
+ return void 0;
88
119
  return out2;
89
120
  }
90
121
  const out = {};
@@ -97,6 +128,8 @@ function sanitizeAny(input, options, path) {
97
128
  const next = sanitizeAny(v, options, path.concat(k));
98
129
  if (next !== void 0) out[k] = next;
99
130
  }
131
+ if (options.dropEmptyObjects && Object.keys(out).length === 0)
132
+ return void 0;
100
133
  return out;
101
134
  }
102
135
  return normalized;
@@ -107,7 +140,11 @@ function sanitize(payload, options = {}) {
107
140
  ...options,
108
141
  drop: options.drop ?? DEFAULT_OPTIONS.drop
109
142
  };
110
- const result = sanitizeAny(payload, merged, []);
143
+ const result = sanitizeAny(
144
+ payload,
145
+ merged,
146
+ []
147
+ );
111
148
  if (result === void 0) {
112
149
  if (isPlainObject(payload)) return {};
113
150
  if (Array.isArray(payload)) return [];
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["out"],"mappings":"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","file":"index.cjs","sourcesContent":["export type DropPreset =\n | \"null\"\n | \"undefined\"\n | \"emptyString\"\n | \"whitespaceString\"\n | \"dash\"\n | \"nan\";\n\nexport type KeyPath = Array<string | number>;\n\nexport type SanitizeOptions = {\n deep?: boolean;\n trimStrings?: boolean;\n cleanArrays?: boolean;\n drop?: DropPreset[];\n keepKeys?: string[];\n dropKeys?: string[];\n dropValues?: unknown[];\n shouldDrop?: (value: unknown, keyPath: KeyPath) => boolean;\n};\n\nconst DEFAULT_DROP: DropPreset[] = [\n \"undefined\",\n \"null\",\n \"emptyString\",\n \"whitespaceString\",\n];\n\nconst DEFAULT_OPTIONS: Required<\n Pick<SanitizeOptions, \"deep\" | \"trimStrings\" | \"cleanArrays\">\n> & { drop: DropPreset[] } = {\n deep: true,\n trimStrings: true,\n cleanArrays: true,\n drop: DEFAULT_DROP,\n};\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== \"object\") return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\nfunction normalizeValue(\n value: unknown,\n opts: Required<typeof DEFAULT_OPTIONS>,\n) {\n if (typeof value === \"string\" && opts.trimStrings) {\n return value.trim();\n }\n return value;\n}\n\nfunction shouldDropPreset(value: unknown, drop: DropPreset[]): boolean {\n for (const d of drop) {\n switch (d) {\n case \"undefined\":\n if (value === undefined) return true;\n break;\n case \"null\":\n if (value === null) return true;\n break;\n case \"emptyString\":\n if (value === \"\") return true;\n break;\n case \"whitespaceString\":\n // only matters if trimStrings is false; but still safe:\n if (typeof value === \"string\" && value.trim() === \"\") return true;\n break;\n case \"dash\":\n if (value === \"-\") return true;\n break;\n case \"nan\":\n if (typeof value === \"number\" && Number.isNaN(value)) return true;\n break;\n }\n }\n return false;\n}\n\nfunction shouldDropExact(value: unknown, exactValues?: unknown[]): boolean {\n if (!exactValues || exactValues.length === 0) return false;\n for (const v of exactValues) {\n if (Object.is(value, v)) return true;\n }\n return false;\n}\n\nfunction hasKey(list: string[] | undefined, key: string): boolean {\n return !!list && list.includes(key);\n}\n\nfunction sanitizeAny(\n input: unknown,\n options: Required<typeof DEFAULT_OPTIONS> & SanitizeOptions,\n path: KeyPath,\n): unknown {\n const normalized = normalizeValue(input, options);\n\n if (shouldDropPreset(normalized, options.drop)) return undefined;\n if (shouldDropExact(normalized, options.dropValues)) return undefined;\n if (options.shouldDrop?.(normalized, path)) return undefined;\n\n if (Array.isArray(normalized)) {\n if (!options.cleanArrays) return normalized.slice();\n const out: unknown[] = [];\n for (let i = 0; i < normalized.length; i++) {\n const next = sanitizeAny(normalized[i], options, path.concat(i));\n if (next !== undefined) out.push(next);\n }\n return out;\n }\n\n if (isPlainObject(normalized)) {\n if (!options.deep) {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(normalized)) {\n if (hasKey(options.dropKeys, k)) continue;\n if (hasKey(options.keepKeys, k)) {\n out[k] = v;\n continue;\n }\n const next = sanitizeAny(v, options, path.concat(k));\n if (next !== undefined) out[k] = next;\n }\n return out;\n }\n\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(normalized)) {\n if (hasKey(options.dropKeys, k)) continue;\n\n if (hasKey(options.keepKeys, k)) {\n out[k] = v;\n continue;\n }\n\n const next = sanitizeAny(v, options, path.concat(k));\n if (next !== undefined) out[k] = next;\n }\n return out;\n }\n\n return normalized;\n}\n\nexport function sanitize<T>(payload: T, options: SanitizeOptions = {}): T {\n const merged = {\n ...DEFAULT_OPTIONS,\n ...options,\n drop: options.drop ?? DEFAULT_OPTIONS.drop,\n };\n\n const result = sanitizeAny(payload, merged, []);\n\n if (result === undefined) {\n if (isPlainObject(payload)) return {} as T;\n if (Array.isArray(payload)) return [] as T;\n return payload;\n }\n\n return result as T;\n}\n\ntype SanitizerFn = {\n <T>(payload: T, options?: SanitizeOptions): T;\n with: (baseOptions?: SanitizeOptions) => ReturnType<typeof createSanitizer>;\n};\n\nexport const createSanitizer = (baseOptions: SanitizeOptions = {}) => {\n return <T>(payload: T, options: SanitizeOptions = {}) =>\n sanitize(payload, { ...baseOptions, ...options });\n};\n\n(sanitize as SanitizerFn).with = (baseOptions: SanitizeOptions = {}) =>\n createSanitizer(baseOptions);\n\nexport const sanitizeWith = (sanitize as SanitizerFn).with;\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["out"],"mappings":"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","file":"index.cjs","sourcesContent":["export type DropPreset =\n | \"null\"\n | \"undefined\"\n | \"emptyString\"\n | \"whitespaceString\"\n | \"dash\"\n | \"nan\";\n\nexport type KeyPath = Array<string | number>;\n\nexport type SanitizeOptions = {\n deep?: boolean;\n trimStrings?: boolean;\n cleanArrays?: boolean;\n drop?: DropPreset[];\n keepKeys?: string[];\n dropKeys?: string[];\n dropValues?: unknown[];\n /**\n * Always keep these paths (even if value is droppable).\n * Supports \"a.b.c\" or [\"a\",\"b\",\"c\"].\n */\n keepPaths?: Array<string | KeyPath>;\n /**\n * Always drop these paths (even if value is meaningful).\n * Supports \"a.b.c\" or [\"a\",\"b\",\"c\"].\n */\n dropPaths?: Array<string | KeyPath>;\n shouldDrop?: (value: unknown, keyPath: KeyPath) => boolean;\n /**\n * Drop objects that become empty after sanitizing.\n * Default: false\n */\n dropEmptyObjects?: boolean;\n\n /**\n * Drop arrays that become empty after sanitizing.\n * Default: false\n */\n dropEmptyArrays?: boolean;\n};\n\nconst DEFAULT_DROP: DropPreset[] = [\n \"undefined\",\n \"null\",\n \"emptyString\",\n \"whitespaceString\",\n];\n\nconst DEFAULT_OPTIONS = {\n deep: true,\n trimStrings: true,\n cleanArrays: true,\n dropEmptyObjects: false,\n dropEmptyArrays: false,\n drop: DEFAULT_DROP,\n} satisfies Required<\n Pick<\n SanitizeOptions,\n | \"deep\"\n | \"trimStrings\"\n | \"cleanArrays\"\n | \"dropEmptyObjects\"\n | \"dropEmptyArrays\"\n >\n> & { drop: DropPreset[] };\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== \"object\") return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\nfunction normalizeValue(value: unknown, opts: typeof DEFAULT_OPTIONS) {\n if (typeof value === \"string\" && opts.trimStrings) {\n return value.trim();\n }\n return value;\n}\n\nfunction shouldDropPreset(value: unknown, drop: DropPreset[]): boolean {\n for (const d of drop) {\n switch (d) {\n case \"undefined\":\n if (value === undefined) return true;\n break;\n case \"null\":\n if (value === null) return true;\n break;\n case \"emptyString\":\n if (value === \"\") return true;\n break;\n case \"whitespaceString\":\n // only matters if trimStrings is false; but still safe:\n if (typeof value === \"string\" && value.trim() === \"\") return true;\n break;\n case \"dash\":\n if (value === \"-\") return true;\n break;\n case \"nan\":\n if (typeof value === \"number\" && Number.isNaN(value)) return true;\n break;\n }\n }\n return false;\n}\n\nfunction shouldDropExact(value: unknown, exactValues?: unknown[]): boolean {\n if (!exactValues || exactValues.length === 0) return false;\n for (const v of exactValues) {\n if (Object.is(value, v)) return true;\n }\n return false;\n}\n\nfunction hasKey(list: string[] | undefined, key: string): boolean {\n return !!list && list.includes(key);\n}\n\nfunction toKeyPath(path: string | KeyPath): KeyPath {\n if (Array.isArray(path)) return path;\n if (path.trim() === \"\") return [];\n return path.split(\".\").map((seg) => {\n const n = Number(seg);\n return Number.isInteger(n) && String(n) === seg ? n : seg;\n });\n}\n\nfunction pathEquals(a: KeyPath, b: KeyPath): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\nfunction hasPath(list: Array<string | KeyPath> | undefined, path: KeyPath): boolean {\n if (!list || list.length === 0) return false;\n for (const item of list) {\n if (pathEquals(toKeyPath(item), path)) return true;\n }\n return false;\n}\n\nfunction sanitizeAny(\n input: unknown,\n options: typeof DEFAULT_OPTIONS & SanitizeOptions,\n path: KeyPath,\n): unknown {\n const normalized = normalizeValue(input, options);\n\n // Path-based overrides (highest priority)\n if (hasPath(options.dropPaths, path)) return undefined;\n\n const isKeptPath = hasPath(options.keepPaths, path);\n\n if (!isKeptPath) {\n if (shouldDropPreset(normalized, options.drop)) return undefined;\n if (shouldDropExact(normalized, options.dropValues)) return undefined;\n if (options.shouldDrop?.(normalized, path)) return undefined;\n }\n\n if (Array.isArray(normalized)) {\n if (!options.cleanArrays) return normalized.slice();\n const out: unknown[] = [];\n for (let i = 0; i < normalized.length; i++) {\n const next = sanitizeAny(normalized[i], options, path.concat(i));\n if (next !== undefined) out.push(next);\n }\n if (options.dropEmptyArrays && out.length === 0) return undefined;\n return out;\n }\n\n if (isPlainObject(normalized)) {\n if (!options.deep) {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(normalized)) {\n if (hasKey(options.dropKeys, k)) continue;\n if (hasKey(options.keepKeys, k)) {\n out[k] = v;\n continue;\n }\n const next = sanitizeAny(v, options, path.concat(k));\n if (next !== undefined) out[k] = next;\n }\n if (options.dropEmptyObjects && Object.keys(out).length === 0)\n return undefined;\n return out;\n }\n\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(normalized)) {\n if (hasKey(options.dropKeys, k)) continue;\n\n if (hasKey(options.keepKeys, k)) {\n out[k] = v;\n continue;\n }\n\n const next = sanitizeAny(v, options, path.concat(k));\n if (next !== undefined) out[k] = next;\n }\n if (options.dropEmptyObjects && Object.keys(out).length === 0)\n return undefined;\n return out;\n }\n\n return normalized;\n}\n\nexport function sanitize<T>(payload: T, options: SanitizeOptions = {}): T {\n const merged = {\n ...DEFAULT_OPTIONS,\n ...options,\n drop: options.drop ?? DEFAULT_OPTIONS.drop,\n };\n\n const result = sanitizeAny(\n payload,\n merged as typeof DEFAULT_OPTIONS & SanitizeOptions,\n [],\n );\n\n if (result === undefined) {\n if (isPlainObject(payload)) return {} as T;\n if (Array.isArray(payload)) return [] as T;\n return payload;\n }\n\n return result as T;\n}\n\ntype SanitizerFn = {\n <T>(payload: T, options?: SanitizeOptions): T;\n with: (baseOptions?: SanitizeOptions) => ReturnType<typeof createSanitizer>;\n};\n\nexport const createSanitizer = (baseOptions: SanitizeOptions = {}) => {\n return <T>(payload: T, options: SanitizeOptions = {}) =>\n sanitize(payload, { ...baseOptions, ...options });\n};\n\n(sanitize as SanitizerFn).with = (baseOptions: SanitizeOptions = {}) =>\n createSanitizer(baseOptions);\n\nexport const sanitizeWith = (sanitize as SanitizerFn).with;\n"]}
package/dist/index.d.cts CHANGED
@@ -8,7 +8,27 @@ type SanitizeOptions = {
8
8
  keepKeys?: string[];
9
9
  dropKeys?: string[];
10
10
  dropValues?: unknown[];
11
+ /**
12
+ * Always keep these paths (even if value is droppable).
13
+ * Supports "a.b.c" or ["a","b","c"].
14
+ */
15
+ keepPaths?: Array<string | KeyPath>;
16
+ /**
17
+ * Always drop these paths (even if value is meaningful).
18
+ * Supports "a.b.c" or ["a","b","c"].
19
+ */
20
+ dropPaths?: Array<string | KeyPath>;
11
21
  shouldDrop?: (value: unknown, keyPath: KeyPath) => boolean;
22
+ /**
23
+ * Drop objects that become empty after sanitizing.
24
+ * Default: false
25
+ */
26
+ dropEmptyObjects?: boolean;
27
+ /**
28
+ * Drop arrays that become empty after sanitizing.
29
+ * Default: false
30
+ */
31
+ dropEmptyArrays?: boolean;
12
32
  };
13
33
  declare function sanitize<T>(payload: T, options?: SanitizeOptions): T;
14
34
  declare const createSanitizer: (baseOptions?: SanitizeOptions) => <T>(payload: T, options?: SanitizeOptions) => T;
package/dist/index.d.ts CHANGED
@@ -8,7 +8,27 @@ type SanitizeOptions = {
8
8
  keepKeys?: string[];
9
9
  dropKeys?: string[];
10
10
  dropValues?: unknown[];
11
+ /**
12
+ * Always keep these paths (even if value is droppable).
13
+ * Supports "a.b.c" or ["a","b","c"].
14
+ */
15
+ keepPaths?: Array<string | KeyPath>;
16
+ /**
17
+ * Always drop these paths (even if value is meaningful).
18
+ * Supports "a.b.c" or ["a","b","c"].
19
+ */
20
+ dropPaths?: Array<string | KeyPath>;
11
21
  shouldDrop?: (value: unknown, keyPath: KeyPath) => boolean;
22
+ /**
23
+ * Drop objects that become empty after sanitizing.
24
+ * Default: false
25
+ */
26
+ dropEmptyObjects?: boolean;
27
+ /**
28
+ * Drop arrays that become empty after sanitizing.
29
+ * Default: false
30
+ */
31
+ dropEmptyArrays?: boolean;
12
32
  };
13
33
  declare function sanitize<T>(payload: T, options?: SanitizeOptions): T;
14
34
  declare const createSanitizer: (baseOptions?: SanitizeOptions) => <T>(payload: T, options?: SanitizeOptions) => T;
package/dist/index.js CHANGED
@@ -9,6 +9,8 @@ var DEFAULT_OPTIONS = {
9
9
  deep: true,
10
10
  trimStrings: true,
11
11
  cleanArrays: true,
12
+ dropEmptyObjects: false,
13
+ dropEmptyArrays: false,
12
14
  drop: DEFAULT_DROP
13
15
  };
14
16
  function isPlainObject(value) {
@@ -57,11 +59,37 @@ function shouldDropExact(value, exactValues) {
57
59
  function hasKey(list, key) {
58
60
  return !!list && list.includes(key);
59
61
  }
62
+ function toKeyPath(path) {
63
+ if (Array.isArray(path)) return path;
64
+ if (path.trim() === "") return [];
65
+ return path.split(".").map((seg) => {
66
+ const n = Number(seg);
67
+ return Number.isInteger(n) && String(n) === seg ? n : seg;
68
+ });
69
+ }
70
+ function pathEquals(a, b) {
71
+ if (a.length !== b.length) return false;
72
+ for (let i = 0; i < a.length; i++) {
73
+ if (a[i] !== b[i]) return false;
74
+ }
75
+ return true;
76
+ }
77
+ function hasPath(list, path) {
78
+ if (!list || list.length === 0) return false;
79
+ for (const item of list) {
80
+ if (pathEquals(toKeyPath(item), path)) return true;
81
+ }
82
+ return false;
83
+ }
60
84
  function sanitizeAny(input, options, path) {
61
85
  const normalized = normalizeValue(input, options);
62
- if (shouldDropPreset(normalized, options.drop)) return void 0;
63
- if (shouldDropExact(normalized, options.dropValues)) return void 0;
64
- if (options.shouldDrop?.(normalized, path)) return void 0;
86
+ if (hasPath(options.dropPaths, path)) return void 0;
87
+ const isKeptPath = hasPath(options.keepPaths, path);
88
+ if (!isKeptPath) {
89
+ if (shouldDropPreset(normalized, options.drop)) return void 0;
90
+ if (shouldDropExact(normalized, options.dropValues)) return void 0;
91
+ if (options.shouldDrop?.(normalized, path)) return void 0;
92
+ }
65
93
  if (Array.isArray(normalized)) {
66
94
  if (!options.cleanArrays) return normalized.slice();
67
95
  const out = [];
@@ -69,6 +97,7 @@ function sanitizeAny(input, options, path) {
69
97
  const next = sanitizeAny(normalized[i], options, path.concat(i));
70
98
  if (next !== void 0) out.push(next);
71
99
  }
100
+ if (options.dropEmptyArrays && out.length === 0) return void 0;
72
101
  return out;
73
102
  }
74
103
  if (isPlainObject(normalized)) {
@@ -83,6 +112,8 @@ function sanitizeAny(input, options, path) {
83
112
  const next = sanitizeAny(v, options, path.concat(k));
84
113
  if (next !== void 0) out2[k] = next;
85
114
  }
115
+ if (options.dropEmptyObjects && Object.keys(out2).length === 0)
116
+ return void 0;
86
117
  return out2;
87
118
  }
88
119
  const out = {};
@@ -95,6 +126,8 @@ function sanitizeAny(input, options, path) {
95
126
  const next = sanitizeAny(v, options, path.concat(k));
96
127
  if (next !== void 0) out[k] = next;
97
128
  }
129
+ if (options.dropEmptyObjects && Object.keys(out).length === 0)
130
+ return void 0;
98
131
  return out;
99
132
  }
100
133
  return normalized;
@@ -105,7 +138,11 @@ function sanitize(payload, options = {}) {
105
138
  ...options,
106
139
  drop: options.drop ?? DEFAULT_OPTIONS.drop
107
140
  };
108
- const result = sanitizeAny(payload, merged, []);
141
+ const result = sanitizeAny(
142
+ payload,
143
+ merged,
144
+ []
145
+ );
109
146
  if (result === void 0) {
110
147
  if (isPlainObject(payload)) return {};
111
148
  if (Array.isArray(payload)) return [];
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["out"],"mappings":"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","file":"index.js","sourcesContent":["export type DropPreset =\n | \"null\"\n | \"undefined\"\n | \"emptyString\"\n | \"whitespaceString\"\n | \"dash\"\n | \"nan\";\n\nexport type KeyPath = Array<string | number>;\n\nexport type SanitizeOptions = {\n deep?: boolean;\n trimStrings?: boolean;\n cleanArrays?: boolean;\n drop?: DropPreset[];\n keepKeys?: string[];\n dropKeys?: string[];\n dropValues?: unknown[];\n shouldDrop?: (value: unknown, keyPath: KeyPath) => boolean;\n};\n\nconst DEFAULT_DROP: DropPreset[] = [\n \"undefined\",\n \"null\",\n \"emptyString\",\n \"whitespaceString\",\n];\n\nconst DEFAULT_OPTIONS: Required<\n Pick<SanitizeOptions, \"deep\" | \"trimStrings\" | \"cleanArrays\">\n> & { drop: DropPreset[] } = {\n deep: true,\n trimStrings: true,\n cleanArrays: true,\n drop: DEFAULT_DROP,\n};\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== \"object\") return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\nfunction normalizeValue(\n value: unknown,\n opts: Required<typeof DEFAULT_OPTIONS>,\n) {\n if (typeof value === \"string\" && opts.trimStrings) {\n return value.trim();\n }\n return value;\n}\n\nfunction shouldDropPreset(value: unknown, drop: DropPreset[]): boolean {\n for (const d of drop) {\n switch (d) {\n case \"undefined\":\n if (value === undefined) return true;\n break;\n case \"null\":\n if (value === null) return true;\n break;\n case \"emptyString\":\n if (value === \"\") return true;\n break;\n case \"whitespaceString\":\n // only matters if trimStrings is false; but still safe:\n if (typeof value === \"string\" && value.trim() === \"\") return true;\n break;\n case \"dash\":\n if (value === \"-\") return true;\n break;\n case \"nan\":\n if (typeof value === \"number\" && Number.isNaN(value)) return true;\n break;\n }\n }\n return false;\n}\n\nfunction shouldDropExact(value: unknown, exactValues?: unknown[]): boolean {\n if (!exactValues || exactValues.length === 0) return false;\n for (const v of exactValues) {\n if (Object.is(value, v)) return true;\n }\n return false;\n}\n\nfunction hasKey(list: string[] | undefined, key: string): boolean {\n return !!list && list.includes(key);\n}\n\nfunction sanitizeAny(\n input: unknown,\n options: Required<typeof DEFAULT_OPTIONS> & SanitizeOptions,\n path: KeyPath,\n): unknown {\n const normalized = normalizeValue(input, options);\n\n if (shouldDropPreset(normalized, options.drop)) return undefined;\n if (shouldDropExact(normalized, options.dropValues)) return undefined;\n if (options.shouldDrop?.(normalized, path)) return undefined;\n\n if (Array.isArray(normalized)) {\n if (!options.cleanArrays) return normalized.slice();\n const out: unknown[] = [];\n for (let i = 0; i < normalized.length; i++) {\n const next = sanitizeAny(normalized[i], options, path.concat(i));\n if (next !== undefined) out.push(next);\n }\n return out;\n }\n\n if (isPlainObject(normalized)) {\n if (!options.deep) {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(normalized)) {\n if (hasKey(options.dropKeys, k)) continue;\n if (hasKey(options.keepKeys, k)) {\n out[k] = v;\n continue;\n }\n const next = sanitizeAny(v, options, path.concat(k));\n if (next !== undefined) out[k] = next;\n }\n return out;\n }\n\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(normalized)) {\n if (hasKey(options.dropKeys, k)) continue;\n\n if (hasKey(options.keepKeys, k)) {\n out[k] = v;\n continue;\n }\n\n const next = sanitizeAny(v, options, path.concat(k));\n if (next !== undefined) out[k] = next;\n }\n return out;\n }\n\n return normalized;\n}\n\nexport function sanitize<T>(payload: T, options: SanitizeOptions = {}): T {\n const merged = {\n ...DEFAULT_OPTIONS,\n ...options,\n drop: options.drop ?? DEFAULT_OPTIONS.drop,\n };\n\n const result = sanitizeAny(payload, merged, []);\n\n if (result === undefined) {\n if (isPlainObject(payload)) return {} as T;\n if (Array.isArray(payload)) return [] as T;\n return payload;\n }\n\n return result as T;\n}\n\ntype SanitizerFn = {\n <T>(payload: T, options?: SanitizeOptions): T;\n with: (baseOptions?: SanitizeOptions) => ReturnType<typeof createSanitizer>;\n};\n\nexport const createSanitizer = (baseOptions: SanitizeOptions = {}) => {\n return <T>(payload: T, options: SanitizeOptions = {}) =>\n sanitize(payload, { ...baseOptions, ...options });\n};\n\n(sanitize as SanitizerFn).with = (baseOptions: SanitizeOptions = {}) =>\n createSanitizer(baseOptions);\n\nexport const sanitizeWith = (sanitize as SanitizerFn).with;\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["out"],"mappings":"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","file":"index.js","sourcesContent":["export type DropPreset =\n | \"null\"\n | \"undefined\"\n | \"emptyString\"\n | \"whitespaceString\"\n | \"dash\"\n | \"nan\";\n\nexport type KeyPath = Array<string | number>;\n\nexport type SanitizeOptions = {\n deep?: boolean;\n trimStrings?: boolean;\n cleanArrays?: boolean;\n drop?: DropPreset[];\n keepKeys?: string[];\n dropKeys?: string[];\n dropValues?: unknown[];\n /**\n * Always keep these paths (even if value is droppable).\n * Supports \"a.b.c\" or [\"a\",\"b\",\"c\"].\n */\n keepPaths?: Array<string | KeyPath>;\n /**\n * Always drop these paths (even if value is meaningful).\n * Supports \"a.b.c\" or [\"a\",\"b\",\"c\"].\n */\n dropPaths?: Array<string | KeyPath>;\n shouldDrop?: (value: unknown, keyPath: KeyPath) => boolean;\n /**\n * Drop objects that become empty after sanitizing.\n * Default: false\n */\n dropEmptyObjects?: boolean;\n\n /**\n * Drop arrays that become empty after sanitizing.\n * Default: false\n */\n dropEmptyArrays?: boolean;\n};\n\nconst DEFAULT_DROP: DropPreset[] = [\n \"undefined\",\n \"null\",\n \"emptyString\",\n \"whitespaceString\",\n];\n\nconst DEFAULT_OPTIONS = {\n deep: true,\n trimStrings: true,\n cleanArrays: true,\n dropEmptyObjects: false,\n dropEmptyArrays: false,\n drop: DEFAULT_DROP,\n} satisfies Required<\n Pick<\n SanitizeOptions,\n | \"deep\"\n | \"trimStrings\"\n | \"cleanArrays\"\n | \"dropEmptyObjects\"\n | \"dropEmptyArrays\"\n >\n> & { drop: DropPreset[] };\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== \"object\") return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\nfunction normalizeValue(value: unknown, opts: typeof DEFAULT_OPTIONS) {\n if (typeof value === \"string\" && opts.trimStrings) {\n return value.trim();\n }\n return value;\n}\n\nfunction shouldDropPreset(value: unknown, drop: DropPreset[]): boolean {\n for (const d of drop) {\n switch (d) {\n case \"undefined\":\n if (value === undefined) return true;\n break;\n case \"null\":\n if (value === null) return true;\n break;\n case \"emptyString\":\n if (value === \"\") return true;\n break;\n case \"whitespaceString\":\n // only matters if trimStrings is false; but still safe:\n if (typeof value === \"string\" && value.trim() === \"\") return true;\n break;\n case \"dash\":\n if (value === \"-\") return true;\n break;\n case \"nan\":\n if (typeof value === \"number\" && Number.isNaN(value)) return true;\n break;\n }\n }\n return false;\n}\n\nfunction shouldDropExact(value: unknown, exactValues?: unknown[]): boolean {\n if (!exactValues || exactValues.length === 0) return false;\n for (const v of exactValues) {\n if (Object.is(value, v)) return true;\n }\n return false;\n}\n\nfunction hasKey(list: string[] | undefined, key: string): boolean {\n return !!list && list.includes(key);\n}\n\nfunction toKeyPath(path: string | KeyPath): KeyPath {\n if (Array.isArray(path)) return path;\n if (path.trim() === \"\") return [];\n return path.split(\".\").map((seg) => {\n const n = Number(seg);\n return Number.isInteger(n) && String(n) === seg ? n : seg;\n });\n}\n\nfunction pathEquals(a: KeyPath, b: KeyPath): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\nfunction hasPath(list: Array<string | KeyPath> | undefined, path: KeyPath): boolean {\n if (!list || list.length === 0) return false;\n for (const item of list) {\n if (pathEquals(toKeyPath(item), path)) return true;\n }\n return false;\n}\n\nfunction sanitizeAny(\n input: unknown,\n options: typeof DEFAULT_OPTIONS & SanitizeOptions,\n path: KeyPath,\n): unknown {\n const normalized = normalizeValue(input, options);\n\n // Path-based overrides (highest priority)\n if (hasPath(options.dropPaths, path)) return undefined;\n\n const isKeptPath = hasPath(options.keepPaths, path);\n\n if (!isKeptPath) {\n if (shouldDropPreset(normalized, options.drop)) return undefined;\n if (shouldDropExact(normalized, options.dropValues)) return undefined;\n if (options.shouldDrop?.(normalized, path)) return undefined;\n }\n\n if (Array.isArray(normalized)) {\n if (!options.cleanArrays) return normalized.slice();\n const out: unknown[] = [];\n for (let i = 0; i < normalized.length; i++) {\n const next = sanitizeAny(normalized[i], options, path.concat(i));\n if (next !== undefined) out.push(next);\n }\n if (options.dropEmptyArrays && out.length === 0) return undefined;\n return out;\n }\n\n if (isPlainObject(normalized)) {\n if (!options.deep) {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(normalized)) {\n if (hasKey(options.dropKeys, k)) continue;\n if (hasKey(options.keepKeys, k)) {\n out[k] = v;\n continue;\n }\n const next = sanitizeAny(v, options, path.concat(k));\n if (next !== undefined) out[k] = next;\n }\n if (options.dropEmptyObjects && Object.keys(out).length === 0)\n return undefined;\n return out;\n }\n\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(normalized)) {\n if (hasKey(options.dropKeys, k)) continue;\n\n if (hasKey(options.keepKeys, k)) {\n out[k] = v;\n continue;\n }\n\n const next = sanitizeAny(v, options, path.concat(k));\n if (next !== undefined) out[k] = next;\n }\n if (options.dropEmptyObjects && Object.keys(out).length === 0)\n return undefined;\n return out;\n }\n\n return normalized;\n}\n\nexport function sanitize<T>(payload: T, options: SanitizeOptions = {}): T {\n const merged = {\n ...DEFAULT_OPTIONS,\n ...options,\n drop: options.drop ?? DEFAULT_OPTIONS.drop,\n };\n\n const result = sanitizeAny(\n payload,\n merged as typeof DEFAULT_OPTIONS & SanitizeOptions,\n [],\n );\n\n if (result === undefined) {\n if (isPlainObject(payload)) return {} as T;\n if (Array.isArray(payload)) return [] as T;\n return payload;\n }\n\n return result as T;\n}\n\ntype SanitizerFn = {\n <T>(payload: T, options?: SanitizeOptions): T;\n with: (baseOptions?: SanitizeOptions) => ReturnType<typeof createSanitizer>;\n};\n\nexport const createSanitizer = (baseOptions: SanitizeOptions = {}) => {\n return <T>(payload: T, options: SanitizeOptions = {}) =>\n sanitize(payload, { ...baseOptions, ...options });\n};\n\n(sanitize as SanitizerFn).with = (baseOptions: SanitizeOptions = {}) =>\n createSanitizer(baseOptions);\n\nexport const sanitizeWith = (sanitize as SanitizerFn).with;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payload-sanitizer",
3
- "version": "0.0.2",
3
+ "version": "0.2.0",
4
4
  "description": "Tiny zero-dependency payload sanitizer for JS/TS (frontend + backend).",
5
5
  "keywords": [
6
6
  "sanitize",