kanun 1.0.7 → 1.0.9

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/dist/index.cjs CHANGED
@@ -2301,7 +2301,6 @@ var BaseValidator = class BaseValidator {
2301
2301
  */
2302
2302
  withContext(context = {}) {
2303
2303
  this.context = context;
2304
- this.addRules(this.initalRules);
2305
2304
  return this;
2306
2305
  }
2307
2306
  /**
@@ -2552,8 +2551,7 @@ var BaseValidator = class BaseValidator {
2552
2551
  * Parse the given rules add assign them to the current rules
2553
2552
  */
2554
2553
  addRules(rules) {
2555
- const availableData = mergeDeep(this.getContext().requestFiles ?? {}, this.data);
2556
- const response = validationRuleParser.explodeRules(dotify(rules, true), availableData);
2554
+ const response = validationRuleParser.explodeRules(dotify(rules, true), this.data);
2557
2555
  this.rules = response.rules;
2558
2556
  this.implicitAttributes = response.implicitAttributes;
2559
2557
  }
@@ -2660,17 +2658,11 @@ var BaseValidator = class BaseValidator {
2660
2658
  }
2661
2659
  /**
2662
2660
  * Resolve an attribute value from validator data first, then request-scoped file context.
2663
- * When the data value is empty (null or empty string), prefer a file from requestFiles
2664
- * because framework body-parsers commonly set file input fields to '' or null.
2665
- *
2666
- * @param attribute
2667
- * @returns
2668
2661
  */
2669
2662
  getAttributeValue(attribute) {
2670
2663
  const dataValue = deepFind(this.data, attribute);
2671
- const fileValue = deepFind(this.getContext().requestFiles ?? {}, attribute);
2672
- if (typeof fileValue !== "undefined" && (typeof dataValue === "undefined" || dataValue === null || dataValue === "")) return fileValue;
2673
- return dataValue;
2664
+ if (typeof dataValue !== "undefined") return dataValue;
2665
+ return deepFind(this.getContext().requestFiles ?? {}, attribute);
2674
2666
  }
2675
2667
  /**
2676
2668
  * Get the primary attribute name
@@ -3338,6 +3330,19 @@ var Validator = class Validator {
3338
3330
  };
3339
3331
  }
3340
3332
  /**
3333
+ * Prefer request-scoped uploaded files over scalar placeholder body values.
3334
+ *
3335
+ * @param attribute
3336
+ * @returns
3337
+ */
3338
+ getValidatedAttributeValue(attribute) {
3339
+ const dataValue = deepFind(this.data, attribute);
3340
+ const requestFileValue = deepFind(this.getContext().requestFiles ?? {}, attribute);
3341
+ if (typeof requestFileValue === "undefined") return dataValue;
3342
+ if (typeof dataValue === "undefined" || dataValue === null || typeof dataValue === "string" || typeof dataValue === "number" || typeof dataValue === "boolean") return requestFileValue;
3343
+ return dataValue;
3344
+ }
3345
+ /**
3341
3346
  * Get the data that passed validation.
3342
3347
  */
3343
3348
  validatedData() {
@@ -3346,8 +3351,7 @@ var Validator = class Validator {
3346
3351
  const clean = {};
3347
3352
  for (const key of validKeys) {
3348
3353
  if (excluded.has(key)) continue;
3349
- const value = deepFind(this.data, key);
3350
- const resolvedValue = typeof value !== "undefined" ? value : deepFind(this.getContext().requestFiles ?? {}, key);
3354
+ const resolvedValue = this.getValidatedAttributeValue(key);
3351
3355
  if (typeof resolvedValue !== "undefined") deepSet(clean, key, resolvedValue);
3352
3356
  }
3353
3357
  return clean;
package/dist/index.d.ts CHANGED
@@ -610,11 +610,6 @@ declare class BaseValidator<D extends GenericObject = GenericObject> {
610
610
  private isNotNullIfMarkedAsNullable;
611
611
  /**
612
612
  * Resolve an attribute value from validator data first, then request-scoped file context.
613
- * When the data value is empty (null or empty string), prefer a file from requestFiles
614
- * because framework body-parsers commonly set file input fields to '' or null.
615
- *
616
- * @param attribute
617
- * @returns
618
613
  */
619
614
  private getAttributeValue;
620
615
  /**
@@ -777,7 +772,10 @@ declare abstract class IValidationRule {
777
772
  * Parse rule names from rule string or string[] definitions
778
773
  */
779
774
  type ExtractRules<R> = R extends string ? R extends `${infer Head}|${infer Tail}` ? Head extends `${infer Rule}:${string}` ? Rule | ExtractRules<Tail> : Head | ExtractRules<Tail> : R extends `${infer Rule}:${string}` ? Rule : R : R extends string[] ? ExtractRules<R[number]> : never;
780
- type ValidatedByRules<D extends Record<string, any>, R extends RulesForData<D>> = { [K in Extract<keyof R, keyof D>]: D[K] };
775
+ type ValidatedData<T> = { [K in keyof T]: T[K] };
776
+ type RuleOutputKey<T extends string> = T extends `${infer Root}.*${string}` ? Root : T extends `${infer Root}.${string}` ? Root : T;
777
+ type RuleOutputKeys<R extends Record<string, any>> = RuleOutputKey<Extract<keyof R, string>>;
778
+ type ValidatedByRules<D extends Record<string, any>, R extends RulesForData<D>> = ValidatedData<{ [K in Extract<RuleOutputKeys<R>, keyof D>]: D[K] } & { [K in Exclude<RuleOutputKeys<R>, keyof D>]: any }>;
781
779
  /**
782
780
  * Flatten data structure into dot-notation keys
783
781
  * including wildcards (*) for arrays.
@@ -1202,7 +1200,7 @@ declare class Validator<D extends Record<string, any> = any, R extends RulesForD
1202
1200
  * @param bagName
1203
1201
  * @returns
1204
1202
  */
1205
- validateWithBag(bagName: string): Promise<ValidatedByRules<D, R>>;
1203
+ validateWithBag(bagName: string): Promise<{ [K_1 in Extract<Extract<keyof R, string> extends infer T_1 ? T_1 extends Extract<keyof R, string> ? T_1 extends `${infer Root}.*${string}` ? Root : T_1 extends `${infer Root_1}.${string}` ? Root_1 : T_1 : never : never, keyof D>]: D[K_1] } & { [K_2 in Exclude<Extract<keyof R, string> extends infer T_2 ? T_2 extends Extract<keyof R, string> ? T_2 extends `${infer Root}.*${string}` ? Root : T_2 extends `${infer Root_1}.${string}` ? Root_1 : T_2 : never : never, keyof D>]: any } extends infer T ? { [K in keyof T]: T[K] } : never>;
1206
1204
  /**
1207
1205
  * Stop validation on first failure.
1208
1206
  */
@@ -1222,6 +1220,13 @@ declare class Validator<D extends Record<string, any> = any, R extends RulesForD
1222
1220
  * @returns The current context object
1223
1221
  */
1224
1222
  getContext(): Record<string, any>;
1223
+ /**
1224
+ * Prefer request-scoped uploaded files over scalar placeholder body values.
1225
+ *
1226
+ * @param attribute
1227
+ * @returns
1228
+ */
1229
+ private getValidatedAttributeValue;
1225
1230
  /**
1226
1231
  * Get the data that passed validation.
1227
1232
  */
package/dist/index.js CHANGED
@@ -2271,7 +2271,6 @@ var BaseValidator = class BaseValidator {
2271
2271
  */
2272
2272
  withContext(context = {}) {
2273
2273
  this.context = context;
2274
- this.addRules(this.initalRules);
2275
2274
  return this;
2276
2275
  }
2277
2276
  /**
@@ -2522,8 +2521,7 @@ var BaseValidator = class BaseValidator {
2522
2521
  * Parse the given rules add assign them to the current rules
2523
2522
  */
2524
2523
  addRules(rules) {
2525
- const availableData = mergeDeep(this.getContext().requestFiles ?? {}, this.data);
2526
- const response = validationRuleParser.explodeRules(dotify(rules, true), availableData);
2524
+ const response = validationRuleParser.explodeRules(dotify(rules, true), this.data);
2527
2525
  this.rules = response.rules;
2528
2526
  this.implicitAttributes = response.implicitAttributes;
2529
2527
  }
@@ -2630,17 +2628,11 @@ var BaseValidator = class BaseValidator {
2630
2628
  }
2631
2629
  /**
2632
2630
  * Resolve an attribute value from validator data first, then request-scoped file context.
2633
- * When the data value is empty (null or empty string), prefer a file from requestFiles
2634
- * because framework body-parsers commonly set file input fields to '' or null.
2635
- *
2636
- * @param attribute
2637
- * @returns
2638
2631
  */
2639
2632
  getAttributeValue(attribute) {
2640
2633
  const dataValue = deepFind(this.data, attribute);
2641
- const fileValue = deepFind(this.getContext().requestFiles ?? {}, attribute);
2642
- if (typeof fileValue !== "undefined" && (typeof dataValue === "undefined" || dataValue === null || dataValue === "")) return fileValue;
2643
- return dataValue;
2634
+ if (typeof dataValue !== "undefined") return dataValue;
2635
+ return deepFind(this.getContext().requestFiles ?? {}, attribute);
2644
2636
  }
2645
2637
  /**
2646
2638
  * Get the primary attribute name
@@ -3308,6 +3300,19 @@ var Validator = class Validator {
3308
3300
  };
3309
3301
  }
3310
3302
  /**
3303
+ * Prefer request-scoped uploaded files over scalar placeholder body values.
3304
+ *
3305
+ * @param attribute
3306
+ * @returns
3307
+ */
3308
+ getValidatedAttributeValue(attribute) {
3309
+ const dataValue = deepFind(this.data, attribute);
3310
+ const requestFileValue = deepFind(this.getContext().requestFiles ?? {}, attribute);
3311
+ if (typeof requestFileValue === "undefined") return dataValue;
3312
+ if (typeof dataValue === "undefined" || dataValue === null || typeof dataValue === "string" || typeof dataValue === "number" || typeof dataValue === "boolean") return requestFileValue;
3313
+ return dataValue;
3314
+ }
3315
+ /**
3311
3316
  * Get the data that passed validation.
3312
3317
  */
3313
3318
  validatedData() {
@@ -3316,8 +3321,7 @@ var Validator = class Validator {
3316
3321
  const clean = {};
3317
3322
  for (const key of validKeys) {
3318
3323
  if (excluded.has(key)) continue;
3319
- const value = deepFind(this.data, key);
3320
- const resolvedValue = typeof value !== "undefined" ? value : deepFind(this.getContext().requestFiles ?? {}, key);
3324
+ const resolvedValue = this.getValidatedAttributeValue(key);
3321
3325
  if (typeof resolvedValue !== "undefined") deepSet(clean, key, resolvedValue);
3322
3326
  }
3323
3327
  return clean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kanun",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "Framework-agnostic TypeScript-first validation library.",
5
5
  "type": "module",
6
6
  "files": [
@@ -44,11 +44,13 @@
44
44
  "@types/multer": "^2.1.0",
45
45
  "@types/node": "^20.11.0",
46
46
  "@vitest/coverage-v8": "4.0.18",
47
+ "axios": "^1.13.6",
47
48
  "barrelize": "^1.7.4",
48
49
  "eslint": "^10.0.2",
49
50
  "express": "^5.2.1",
50
51
  "fastify": "^5.8.2",
51
52
  "h3": "2.0.1-rc.16",
53
+ "happy-dom": "^20.8.4",
52
54
  "hono": "^4.12.8",
53
55
  "multer": "^2.1.1",
54
56
  "tsdown": "0.21.0-beta.2",
@@ -64,6 +66,7 @@
64
66
  "scripts": {
65
67
  "lint": "eslint",
66
68
  "test": "vitest",
69
+ "test:browser": "vitest --project browser",
67
70
  "test:file-plugin": "vitest --watch=false tests/file-plugin-validator.test.ts",
68
71
  "test:coverage": "vitest --coverage --watch=false",
69
72
  "build": "tsdown",
@@ -73,6 +76,7 @@
73
76
  "barrel": "barrelize",
74
77
  "docs:dev": "vitepress dev docs",
75
78
  "docs:build": "vitepress build docs",
76
- "docs:preview": "vitepress preview docs"
79
+ "docs:preview": "vitepress preview docs",
80
+ "publish:packages": "pnpm publish --filter './packages/*' --access public"
77
81
  }
78
82
  }