@carecard/validate 3.1.26 → 3.1.27

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/.husky/pre-commit CHANGED
@@ -1,3 +1,5 @@
1
+ npm run lint:fix
2
+ npm run format
3
+
1
4
  # Run tests
2
- npm run test
3
- npm run test:types
5
+ npm run test:All
@@ -16,6 +16,8 @@ const MAX_NESTING_DEPTH = 5;
16
16
  * adversarial inputs.
17
17
  */
18
18
  const MAX_KEYS_PER_CALL = 5000;
19
+ const DEFAULT_FLATTEN_KEY_STYLE = 'path';
20
+ const VALID_FLATTEN_KEY_STYLES = new Set(['path', 'leaf']);
19
21
 
20
22
  /**
21
23
  * Returns true if the segment contains a mix of snake_case (underscore) and
@@ -161,6 +163,35 @@ function flattenObject(obj, prefix = '', out = {}) {
161
163
  return out;
162
164
  }
163
165
 
166
+ /**
167
+ * Recursively flattens a nested plain object using only each leaf property
168
+ * name as the output key.
169
+ *
170
+ * Example: `{ a: { b: { c: 1, d: 2 } } }` => `{ c: 1, d: 2 }`.
171
+ * If duplicate leaf keys exist at different nesting levels, the higher-level
172
+ * leaf wins. If duplicate leaf keys exist at the same depth, the first
173
+ * traversal wins.
174
+ *
175
+ * @param {Object} obj
176
+ * @param {Object} [out]
177
+ * @param {Object} [depthByKey]
178
+ * @param {number} [depth]
179
+ * @returns {Object}
180
+ */
181
+ function flattenObjectByLeafKey(obj, out = {}, depthByKey = {}, depth = 1) {
182
+ for (const [key, value] of Object.entries(obj)) {
183
+ if (value !== null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) {
184
+ flattenObjectByLeafKey(value, out, depthByKey, depth + 1);
185
+ } else {
186
+ if (!Object.prototype.hasOwnProperty.call(out, key) || depth < depthByKey[key]) {
187
+ out[key] = value;
188
+ depthByKey[key] = depth;
189
+ }
190
+ }
191
+ }
192
+ return out;
193
+ }
194
+
164
195
  /**
165
196
  * Validates and transforms whitelisted properties from an input object.
166
197
  *
@@ -181,8 +212,11 @@ function flattenObject(obj, prefix = '', out = {}) {
181
212
  * element passes validation, and the returned value is an array of the
182
213
  * validated elements (in the same order).
183
214
  * 5. Optionally converts all keys (including nested) to snake_case.
184
- * 6. Optionally flattens the result so every leaf is a top-level key,
185
- * joined by `.` (`flattenOutput`). Applied after snake_case conversion.
215
+ * 6. Optionally flattens the result (`flattenOutput`). Flattened keys use
216
+ * full dot paths by default (`flattenKeyStyle: 'path'`) or direct leaf
217
+ * names when requested (`flattenKeyStyle: 'leaf'`). For duplicate leaf
218
+ * keys in leaf mode, the shallower value wins; ties keep the first value
219
+ * encountered. Applied after snake_case conversion.
186
220
  *
187
221
  * @param {Object} inputObject - The input object (e.g., req.body / req.params).
188
222
  * @param {Array<string>} [requiredProperties=[]] - Leaf paths that MUST be present and valid.
@@ -190,17 +224,26 @@ function flattenObject(obj, prefix = '', out = {}) {
190
224
  * @param {Array<string>} [options.optionalProperties=[]] - Leaf paths allowed but not required.
191
225
  * @param {boolean} [options.convertToSnakeCase=false] - Whether to convert keys to snake_case.
192
226
  * @param {boolean} [options.flattenOutput=false] - Whether to flatten the result so that
193
- * every leaf is a top-level key (joined by `.`), with no nested objects in the output.
227
+ * every leaf is a top-level key, with no nested objects in the output.
228
+ * @param {'path'|'leaf'} [options.flattenKeyStyle='path'] - Flattened key naming strategy
229
+ * when `flattenOutput` is true. `path` uses dot-joined paths; `leaf` uses leaf names.
194
230
  * @returns {Promise<Object>} Resolves with the validated (and possibly transformed) object.
195
231
  */
196
232
  function validateWhitelistProperties(
197
233
  inputObject,
198
234
  requiredProperties = [],
199
- options = { optionalProperties: [], convertToSnakeCase: false, flattenOutput: false },
235
+ options = { optionalProperties: [], convertToSnakeCase: false, flattenOutput: false, flattenKeyStyle: DEFAULT_FLATTEN_KEY_STYLE },
200
236
  ) {
201
237
  const optionalProperties = (options && options.optionalProperties) || [];
202
238
  const convertToSnakeCase = !!(options && options.convertToSnakeCase);
203
239
  const flattenOutput = !!(options && options.flattenOutput);
240
+ const flattenKeyStyle = options && options.flattenKeyStyle !== undefined ? options.flattenKeyStyle : DEFAULT_FLATTEN_KEY_STYLE;
241
+
242
+ if (!VALID_FLATTEN_KEY_STYLES.has(flattenKeyStyle)) {
243
+ throwBadInputError({
244
+ userMessage: `Invalid flattenKeyStyle: ${String(flattenKeyStyle)}. Expected "path" or "leaf"`,
245
+ });
246
+ }
204
247
 
205
248
  // Cap the total number of paths to validate per call.
206
249
  const totalKeys = (requiredProperties ? requiredProperties.length : 0) + optionalProperties.length;
@@ -271,9 +314,9 @@ function validateWhitelistProperties(
271
314
  validatedObject = keysToSnakeCase(validatedObject);
272
315
  }
273
316
 
274
- // 6. Optional flattening: produce a flat object with dot-joined keys.
317
+ // 6. Optional flattening.
275
318
  if (flattenOutput) {
276
- validatedObject = flattenObject(validatedObject);
319
+ validatedObject = flattenKeyStyle === 'leaf' ? flattenObjectByLeafKey(validatedObject) : flattenObject(validatedObject);
277
320
  }
278
321
 
279
322
  return Promise.resolve(validatedObject);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carecard/validate",
3
- "version": "3.1.26",
3
+ "version": "3.1.27",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/CareCard-ca/pkg-validate.git"