@ciwergrp/nuxid 1.5.2 → 1.5.4

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
@@ -160,6 +160,35 @@ export default defineNuxtConfig({
160
160
 
161
161
  Disable any feature by setting it to `false` (e.g. `nuxid: { lodash: false }`).
162
162
 
163
+ ## Structure Directory
164
+
165
+ This module is designed to work best in a Nuxt + pnpm monorepo, where each product or domain is its own Nuxt app under `apps/`, and shared building blocks live in `layers/` as Nuxt layers. The root of the repo acts as the single workspace: dependency versions, lint config, TypeScript config, and tooling live at the top level, while apps and layers are composed via Nuxt’s layer system. This keeps per-domain apps small and focused, encourages reuse without copy/paste, and makes it easy to scale multiple teams or products inside one repo.
166
+
167
+ Recommended top-level structure:
168
+
169
+ ```bash
170
+ % ls -l
171
+ total 1008
172
+ -rw-r--r--@ 1 bagusvnt staff 14838 Jan 12 13:24 AGENTS.md
173
+ -rw-r--r-- 1 bagusvnt staff 371 Jan 7 10:45 README.md
174
+ drwxr-xr-x 5 bagusvnt staff 160 Jan 7 11:46 apps
175
+ -rw-r--r--@ 1 bagusvnt staff 134 Jan 7 12:26 docker-compose.yml
176
+ -rw-r--r-- 1 bagusvnt staff 1103 Jan 7 10:45 eslint.config.mjs
177
+ drwxr-xr-x 4 bagusvnt staff 128 Jan 7 10:45 layers
178
+ drwxr-xr-x@ 781 bagusvnt staff 24992 Jan 15 10:06 node_modules
179
+ -rw-r--r--@ 1 bagusvnt staff 1158 Jan 15 10:06 package.json
180
+ -rw-r--r--@ 1 bagusvnt staff 474254 Jan 15 10:06 pnpm-lock.yaml
181
+ -rw-r--r-- 1 bagusvnt staff 38 Jan 7 10:45 pnpm-workspace.yaml
182
+ -rw-r--r-- 1 bagusvnt staff 566 Jan 7 10:45 tsconfig.json
183
+ ```
184
+
185
+ Inside `apps/`, each app represents a domain or product boundary (for example `apps/admin/`) and owns its `nuxt.config.ts`, routing, and app-specific modules. Inside `layers/`, you keep shared pieces such as UI components, composables, config, and utilities that can be layered into any app. Each layer is also a Nuxt project with its own `nuxt.config.ts`, so you can expose components, auto-imports, and module options from that layer while keeping a clean separation of concerns.
186
+
187
+ Examples:
188
+
189
+ - `apps/admin/nuxt.config.ts`
190
+ - `layers/ui/nuxt.config.ts`
191
+
163
192
  ## Development
164
193
 
165
194
  ```bash
@@ -18,6 +18,21 @@ const availableRules = [
18
18
  'string',
19
19
  'nullable',
20
20
  'email',
21
+ 'accepted',
22
+ 'accepted_if',
23
+ 'boolean',
24
+ 'filled',
25
+ 'present',
26
+ 'required_if',
27
+ 'required_unless',
28
+ 'required_with',
29
+ 'required_with_all',
30
+ 'required_without',
31
+ 'required_without_all',
32
+ 'prohibited',
33
+ 'prohibited_if',
34
+ 'prohibited_unless',
35
+ 'prohibits',
21
36
  'alpha',
22
37
  'alpha_num',
23
38
  'alpha_dash',
@@ -25,16 +40,33 @@ const availableRules = [
25
40
  'alpha_space',
26
41
  'numeric',
27
42
  'integer',
43
+ 'confirmed',
44
+ 'digits',
45
+ 'digits_between',
28
46
  'bail',
47
+ 'date_format',
29
48
  'date',
30
49
  'before',
31
50
  'after',
51
+ 'before_or_equal',
52
+ 'after_or_equal',
32
53
  'array',
54
+ 'distinct',
55
+ 'in_array',
56
+ 'required_array_keys',
57
+ 'list',
33
58
  'url',
34
59
  'ip',
35
60
  'hex_color',
61
+ 'lowercase',
62
+ 'uppercase',
63
+ 'uuid',
64
+ 'ulid',
65
+ 'timezone',
36
66
  'min',
37
67
  'max',
68
+ 'between',
69
+ 'size',
38
70
  'same',
39
71
  'different',
40
72
  'gt',
@@ -42,26 +74,50 @@ const availableRules = [
42
74
  'lt',
43
75
  'lte',
44
76
  'in',
77
+ 'multiple_of',
78
+ 'decimal',
45
79
  'starts_with',
46
80
  'ends_with',
47
81
  'regex',
48
82
  ];
49
83
 
50
84
  const parameterizedRules = new Set([
85
+ 'accepted_if',
86
+ 'required_if',
87
+ 'required_unless',
88
+ 'required_with',
89
+ 'required_with_all',
90
+ 'required_without',
91
+ 'required_without_all',
92
+ 'prohibited_if',
93
+ 'prohibited_unless',
94
+ 'prohibits',
51
95
  'min',
52
96
  'max',
97
+ 'between',
98
+ 'size',
53
99
  'same',
54
100
  'different',
55
101
  'gt',
56
102
  'gte',
57
103
  'lt',
58
104
  'lte',
105
+ 'digits',
106
+ 'digits_between',
59
107
  'starts_with',
60
108
  'ends_with',
61
109
  'regex',
62
110
  'before',
63
111
  'after',
112
+ 'before_or_equal',
113
+ 'after_or_equal',
114
+ 'date_format',
64
115
  'in',
116
+ 'in_array',
117
+ 'required_array_keys',
118
+ 'multiple_of',
119
+ 'decimal',
120
+ 'timezone',
65
121
  ]);
66
122
 
67
123
  function toCamelCase(str) {
package/dist/module.d.mts CHANGED
@@ -11,6 +11,11 @@ interface LodashOptions {
11
11
  * @default true
12
12
  */
13
13
  enabled: boolean;
14
+ /**
15
+ * Auto-import mode
16
+ * @default 'factory'
17
+ */
18
+ mode: 'factory' | 'prefix';
14
19
  /**
15
20
  * Prefix applied to each import (set `false` to disable)
16
21
  * @default 'use'
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ciwergrp/nuxid",
3
3
  "configKey": "nuxid",
4
- "version": "1.5.2",
4
+ "version": "1.5.4",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -34,6 +34,7 @@ const lodashExcludes = [
34
34
 
35
35
  const lodashDefaults = {
36
36
  enabled: false,
37
+ mode: "factory",
37
38
  // prefix: false,
38
39
  prefix: "ki",
39
40
  prefixSkip: "is",
@@ -63,6 +64,10 @@ function registerLodashFeature(options, resolve) {
63
64
  if (!options.enabled) {
64
65
  return;
65
66
  }
67
+ if (options.mode === "factory") {
68
+ addImports({ name: "lodash", from: resolve("./runtime/lodash-factory") });
69
+ return;
70
+ }
66
71
  const aliasMap = new Map(options.alias);
67
72
  const excludes = new Set(options.exclude);
68
73
  const skipPrefixes = toArray(options.prefixSkip);
@@ -0,0 +1,2 @@
1
+ import * as lodashLib from 'lodash-es';
2
+ export declare function lodash(): typeof lodashLib;
@@ -0,0 +1,4 @@
1
+ import * as lodashLib from "lodash-es";
2
+ export function lodash() {
3
+ return lodashLib;
4
+ }
@@ -1,5 +1,5 @@
1
1
  import type { FormRules } from 'element-plus';
2
- export type ValidationRule = 'required' | 'string' | 'nullable' | 'email' | `min:${number}` | `max:${number}` | `same:${string}` | 'alpha' | 'alpha_num' | 'alpha_dash' | 'alphanumeric_space' | 'alpha_space' | 'numeric' | 'integer' | 'bail' | `different:${string}` | `gt:${string}` | `gte:${string}` | `lt:${string}` | `lte:${string}` | `starts_with:${string}` | `ends_with:${string}` | `regex:${string}` | 'date' | `before:${string}` | `after:${string}` | 'array' | 'url' | 'ip' | 'hex_color' | `in:${string}`;
2
+ export type ValidationRule = 'required' | 'string' | 'nullable' | 'email' | 'accepted' | `accepted_if:${string}` | 'boolean' | 'filled' | 'present' | `required_if:${string}` | `required_unless:${string}` | `required_with:${string}` | `required_with_all:${string}` | `required_without:${string}` | `required_without_all:${string}` | 'prohibited' | `prohibited_if:${string}` | `prohibited_unless:${string}` | `prohibits:${string}` | `min:${number}` | `max:${number}` | `between:${number},${number}` | `size:${number}` | `same:${string}` | 'alpha' | 'alpha_num' | 'alpha_dash' | 'alphanumeric_space' | 'alpha_space' | 'numeric' | 'integer' | 'confirmed' | `digits:${number}` | `digits_between:${number},${number}` | 'bail' | `different:${string}` | `gt:${string}` | `gte:${string}` | `lt:${string}` | `lte:${string}` | `starts_with:${string}` | `ends_with:${string}` | `regex:${string}` | `date_format:${string}` | 'date' | `before:${string}` | `after:${string}` | `before_or_equal:${string}` | `after_or_equal:${string}` | 'array' | 'distinct' | `in_array:${string}` | `required_array_keys:${string}` | 'list' | 'url' | 'ip' | 'hex_color' | `in:${string}` | `multiple_of:${number}` | `decimal:${number}` | `decimal:${number},${number}` | 'lowercase' | 'uppercase' | 'uuid' | 'ulid' | `timezone:${string}` | 'timezone';
3
3
  export interface ValidationOptions {
4
4
  messages?: Record<string, string>;
5
5
  attributes?: Record<string, string>;
@@ -1,8 +1,5 @@
1
1
  const createSameAsValidator = (otherField, formState, message) => {
2
2
  return (_rule, value, callback) => {
3
- const getNestedValue = (obj, path) => {
4
- return path.split(".").reduce((current, key) => current?.[key], obj);
5
- };
6
3
  const otherValue = getNestedValue(formState, otherField);
7
4
  if (value !== otherValue) {
8
5
  callback(new Error(message));
@@ -11,6 +8,117 @@ const createSameAsValidator = (otherField, formState, message) => {
11
8
  }
12
9
  };
13
10
  };
11
+ const getNestedValue = (obj, path) => {
12
+ return path.split(".").reduce((current, key) => current?.[key], obj);
13
+ };
14
+ const isEmptyValue = (value) => value === null || value === void 0 || value === "";
15
+ const isFilledValue = (value) => {
16
+ if (isEmptyValue(value)) {
17
+ return false;
18
+ }
19
+ if (Array.isArray(value)) {
20
+ return value.length > 0;
21
+ }
22
+ if (typeof value === "object") {
23
+ return Object.keys(value).length > 0;
24
+ }
25
+ return true;
26
+ };
27
+ const hasNestedKey = (obj, path) => {
28
+ return path.split(".").every((key) => {
29
+ if (obj === null || obj === void 0) {
30
+ return false;
31
+ }
32
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
33
+ obj = obj[key];
34
+ return true;
35
+ }
36
+ return false;
37
+ });
38
+ };
39
+ const normalizeComparisonValue = (value, expected) => {
40
+ if (expected === "null") {
41
+ return value === null || value === void 0;
42
+ }
43
+ if (expected === "true") {
44
+ return value === true || value === "true" || value === 1 || value === "1";
45
+ }
46
+ if (expected === "false") {
47
+ return value === false || value === "false" || value === 0 || value === "0";
48
+ }
49
+ return String(value) === expected;
50
+ };
51
+ const hasAnyFilled = (formState, fields) => {
52
+ return fields.some((field) => isFilledValue(getNestedValue(formState, field)));
53
+ };
54
+ const hasAllFilled = (formState, fields) => {
55
+ return fields.every((field) => isFilledValue(getNestedValue(formState, field)));
56
+ };
57
+ const getConfirmationField = (fieldName) => {
58
+ const segments = fieldName.split(".");
59
+ const last = segments.pop() || fieldName;
60
+ segments.push(`${last}_confirmation`);
61
+ return segments.join(".");
62
+ };
63
+ const getSizeValue = (value) => {
64
+ if (typeof value === "number" && Number.isFinite(value)) {
65
+ return value;
66
+ }
67
+ if (typeof value === "string") {
68
+ return value.length;
69
+ }
70
+ if (Array.isArray(value)) {
71
+ return value.length;
72
+ }
73
+ if (value && typeof value === "object") {
74
+ return Object.keys(value).length;
75
+ }
76
+ return null;
77
+ };
78
+ const isAcceptedValue = (value) => {
79
+ if (typeof value === "string") {
80
+ return ["yes", "on", "1", "true"].includes(value.toLowerCase());
81
+ }
82
+ return value === true || value === 1;
83
+ };
84
+ const isBooleanValue = (value) => {
85
+ return value === true || value === false || value === 0 || value === 1 || value === "0" || value === "1";
86
+ };
87
+ const createDateFormatValidator = (format, message) => {
88
+ const tokenMap = {
89
+ Y: "\\d{4}",
90
+ m: "(0[1-9]|1[0-2])",
91
+ d: "(0[1-9]|[12]\\d|3[01])",
92
+ H: "([01]\\d|2[0-3])",
93
+ i: "([0-5]\\d)",
94
+ s: "([0-5]\\d)"
95
+ };
96
+ let pattern = "^";
97
+ for (let i = 0; i < format.length; i += 1) {
98
+ const char = format[i];
99
+ if (char === "\\" && i + 1 < format.length) {
100
+ pattern += format[i + 1].replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
101
+ i += 1;
102
+ continue;
103
+ }
104
+ if (tokenMap[char]) {
105
+ pattern += tokenMap[char];
106
+ } else {
107
+ pattern += char.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
108
+ }
109
+ }
110
+ pattern += "$";
111
+ const regex = new RegExp(pattern);
112
+ return (_rule, value, callback) => {
113
+ if (isEmptyValue(value)) {
114
+ return callback();
115
+ }
116
+ if (typeof value !== "string" || !regex.test(value)) {
117
+ return callback(new Error(message));
118
+ }
119
+ callback();
120
+ };
121
+ };
14
122
  const createPatternValidator = (pattern, message) => {
15
123
  return (_rule, value, callback) => {
16
124
  if (value && !pattern.test(value)) {
@@ -186,6 +294,16 @@ export const createValidationRules = (definitions, formState, options = {}) => {
186
294
  } else {
187
295
  laravelRules = fieldDefinition;
188
296
  }
297
+ const hasNullable = laravelRules.includes("nullable");
298
+ const hasRequired = laravelRules.includes("required");
299
+ if (hasNullable && hasRequired) {
300
+ throw new Error(`Invalid validation rules for "${fieldName}": "nullable" cannot be combined with "required".`);
301
+ }
302
+ const fieldValue = getNestedValue(dataToValidate, fieldName);
303
+ if (hasNullable && !hasRequired && isEmptyValue(fieldValue)) {
304
+ rules[fieldName] = [];
305
+ continue;
306
+ }
189
307
  const fieldRules = [];
190
308
  const attributeName = attributes[fieldName] || fieldName;
191
309
  laravelRules.forEach((ruleString) => {
@@ -200,6 +318,289 @@ export const createValidationRules = (definitions, formState, options = {}) => {
200
318
  trigger: pickTrigger(fieldName, "blur")
201
319
  });
202
320
  break;
321
+ case "accepted":
322
+ fieldRules.push({
323
+ validator: (_rule, value, callback) => {
324
+ if (isAcceptedValue(value)) {
325
+ return callback();
326
+ }
327
+ callback(new Error(getMessage(fieldName, "accepted", `${attributeName} must be accepted`)));
328
+ },
329
+ trigger: pickTrigger(fieldName, "change")
330
+ });
331
+ break;
332
+ case "accepted_if": {
333
+ if (!paramValues.length) {
334
+ return;
335
+ }
336
+ const otherField = firstParam;
337
+ const expectedValues = paramValues.slice(1);
338
+ fieldRules.push({
339
+ validator: (_rule, value, callback) => {
340
+ const otherValue = getNestedValue(dataToValidate, otherField);
341
+ const shouldRequire = expectedValues.some((expected) => normalizeComparisonValue(otherValue, expected));
342
+ if (!shouldRequire) {
343
+ return callback();
344
+ }
345
+ if (!isAcceptedValue(value)) {
346
+ return callback(new Error(getMessage(fieldName, "accepted_if", `${attributeName} must be accepted`)));
347
+ }
348
+ callback();
349
+ },
350
+ trigger: pickTrigger(fieldName, "change")
351
+ });
352
+ break;
353
+ }
354
+ case "boolean":
355
+ fieldRules.push({
356
+ validator: (_rule, value, callback) => {
357
+ if (isEmptyValue(value)) {
358
+ return callback();
359
+ }
360
+ if (!isBooleanValue(value)) {
361
+ return callback(new Error(getMessage(fieldName, "boolean", `${attributeName} must be true or false`)));
362
+ }
363
+ callback();
364
+ },
365
+ trigger: pickTrigger(fieldName, "change")
366
+ });
367
+ break;
368
+ case "filled":
369
+ fieldRules.push({
370
+ validator: (_rule, value, callback) => {
371
+ if (!hasNestedKey(dataToValidate, fieldName)) {
372
+ return callback();
373
+ }
374
+ if (!isFilledValue(value)) {
375
+ return callback(new Error(getMessage(fieldName, "filled", `${attributeName} must not be empty`)));
376
+ }
377
+ callback();
378
+ },
379
+ trigger: pickTrigger(fieldName, "blur")
380
+ });
381
+ break;
382
+ case "present":
383
+ fieldRules.push({
384
+ validator: (_rule, _value, callback) => {
385
+ if (!hasNestedKey(dataToValidate, fieldName)) {
386
+ return callback(new Error(getMessage(fieldName, "present", `${attributeName} must be present`)));
387
+ }
388
+ callback();
389
+ },
390
+ trigger: pickTrigger(fieldName, "blur")
391
+ });
392
+ break;
393
+ case "required_if": {
394
+ if (!paramValues.length) {
395
+ return;
396
+ }
397
+ const otherField = firstParam;
398
+ const expectedValues = paramValues.slice(1);
399
+ fieldRules.push({
400
+ validator: (_rule, value, callback) => {
401
+ const otherValue = getNestedValue(dataToValidate, otherField);
402
+ const shouldRequire = expectedValues.some((expected) => normalizeComparisonValue(otherValue, expected));
403
+ if (!shouldRequire) {
404
+ return callback();
405
+ }
406
+ if (!isFilledValue(value)) {
407
+ return callback(new Error(getMessage(fieldName, "required_if", `${attributeName} is required`)));
408
+ }
409
+ callback();
410
+ },
411
+ trigger: pickTrigger(fieldName, "blur")
412
+ });
413
+ break;
414
+ }
415
+ case "required_unless": {
416
+ if (!paramValues.length) {
417
+ return;
418
+ }
419
+ const otherField = firstParam;
420
+ const expectedValues = paramValues.slice(1);
421
+ fieldRules.push({
422
+ validator: (_rule, value, callback) => {
423
+ const otherValue = getNestedValue(dataToValidate, otherField);
424
+ const isExcluded = expectedValues.some((expected) => normalizeComparisonValue(otherValue, expected));
425
+ if (isExcluded) {
426
+ return callback();
427
+ }
428
+ if (!isFilledValue(value)) {
429
+ return callback(new Error(getMessage(fieldName, "required_unless", `${attributeName} is required`)));
430
+ }
431
+ callback();
432
+ },
433
+ trigger: pickTrigger(fieldName, "blur")
434
+ });
435
+ break;
436
+ }
437
+ case "required_with": {
438
+ if (!paramValues.length) {
439
+ return;
440
+ }
441
+ const otherFields = paramValues;
442
+ fieldRules.push({
443
+ validator: (_rule, value, callback) => {
444
+ if (!hasAnyFilled(dataToValidate, otherFields)) {
445
+ return callback();
446
+ }
447
+ if (!isFilledValue(value)) {
448
+ return callback(new Error(getMessage(fieldName, "required_with", `${attributeName} is required`)));
449
+ }
450
+ callback();
451
+ },
452
+ trigger: pickTrigger(fieldName, "blur")
453
+ });
454
+ break;
455
+ }
456
+ case "required_with_all": {
457
+ if (!paramValues.length) {
458
+ return;
459
+ }
460
+ const otherFields = paramValues;
461
+ fieldRules.push({
462
+ validator: (_rule, value, callback) => {
463
+ if (!hasAllFilled(dataToValidate, otherFields)) {
464
+ return callback();
465
+ }
466
+ if (!isFilledValue(value)) {
467
+ return callback(new Error(getMessage(fieldName, "required_with_all", `${attributeName} is required`)));
468
+ }
469
+ callback();
470
+ },
471
+ trigger: pickTrigger(fieldName, "blur")
472
+ });
473
+ break;
474
+ }
475
+ case "required_without": {
476
+ if (!paramValues.length) {
477
+ return;
478
+ }
479
+ const otherFields = paramValues;
480
+ fieldRules.push({
481
+ validator: (_rule, value, callback) => {
482
+ if (hasAnyFilled(dataToValidate, otherFields)) {
483
+ return callback();
484
+ }
485
+ if (!isFilledValue(value)) {
486
+ return callback(new Error(getMessage(fieldName, "required_without", `${attributeName} is required`)));
487
+ }
488
+ callback();
489
+ },
490
+ trigger: pickTrigger(fieldName, "blur")
491
+ });
492
+ break;
493
+ }
494
+ case "required_without_all": {
495
+ if (!paramValues.length) {
496
+ return;
497
+ }
498
+ const otherFields = paramValues;
499
+ fieldRules.push({
500
+ validator: (_rule, value, callback) => {
501
+ if (hasAllFilled(dataToValidate, otherFields)) {
502
+ return callback();
503
+ }
504
+ if (!isFilledValue(value)) {
505
+ return callback(new Error(getMessage(fieldName, "required_without_all", `${attributeName} is required`)));
506
+ }
507
+ callback();
508
+ },
509
+ trigger: pickTrigger(fieldName, "blur")
510
+ });
511
+ break;
512
+ }
513
+ case "prohibited":
514
+ fieldRules.push({
515
+ validator: (_rule, value, callback) => {
516
+ if (isFilledValue(value)) {
517
+ return callback(new Error(getMessage(fieldName, "prohibited", `${attributeName} must be empty`)));
518
+ }
519
+ callback();
520
+ },
521
+ trigger: pickTrigger(fieldName, "blur")
522
+ });
523
+ break;
524
+ case "prohibited_if": {
525
+ if (!paramValues.length) {
526
+ return;
527
+ }
528
+ const otherField = firstParam;
529
+ const expectedValues = paramValues.slice(1);
530
+ fieldRules.push({
531
+ validator: (_rule, value, callback) => {
532
+ const otherValue = getNestedValue(dataToValidate, otherField);
533
+ const shouldProhibit = expectedValues.some((expected) => normalizeComparisonValue(otherValue, expected));
534
+ if (!shouldProhibit) {
535
+ return callback();
536
+ }
537
+ if (isFilledValue(value)) {
538
+ return callback(new Error(getMessage(fieldName, "prohibited_if", `${attributeName} must be empty`)));
539
+ }
540
+ callback();
541
+ },
542
+ trigger: pickTrigger(fieldName, "blur")
543
+ });
544
+ break;
545
+ }
546
+ case "prohibited_unless": {
547
+ if (!paramValues.length) {
548
+ return;
549
+ }
550
+ const otherField = firstParam;
551
+ const expectedValues = paramValues.slice(1);
552
+ fieldRules.push({
553
+ validator: (_rule, value, callback) => {
554
+ const otherValue = getNestedValue(dataToValidate, otherField);
555
+ const isAllowed = expectedValues.some((expected) => normalizeComparisonValue(otherValue, expected));
556
+ if (isAllowed) {
557
+ return callback();
558
+ }
559
+ if (isFilledValue(value)) {
560
+ return callback(new Error(getMessage(fieldName, "prohibited_unless", `${attributeName} must be empty`)));
561
+ }
562
+ callback();
563
+ },
564
+ trigger: pickTrigger(fieldName, "blur")
565
+ });
566
+ break;
567
+ }
568
+ case "prohibits": {
569
+ if (!paramValues.length) {
570
+ return;
571
+ }
572
+ const otherFields = paramValues;
573
+ fieldRules.push({
574
+ validator: (_rule, value, callback) => {
575
+ if (!isFilledValue(value)) {
576
+ return callback();
577
+ }
578
+ const hasAny = otherFields.some((field) => isFilledValue(getNestedValue(dataToValidate, field)));
579
+ if (hasAny) {
580
+ return callback(new Error(getMessage(fieldName, "prohibits", `${attributeName} prohibits ${otherFields.join(", ")}`)));
581
+ }
582
+ callback();
583
+ },
584
+ trigger: pickTrigger(fieldName, "blur")
585
+ });
586
+ break;
587
+ }
588
+ case "string":
589
+ fieldRules.push({
590
+ validator: (_rule, value, callback) => {
591
+ if (value === null || value === void 0) {
592
+ return callback();
593
+ }
594
+ if (typeof value !== "string") {
595
+ return callback(new Error(getMessage(fieldName, "string", `${attributeName} must be a string`)));
596
+ }
597
+ callback();
598
+ },
599
+ trigger: pickTrigger(fieldName, "blur")
600
+ });
601
+ break;
602
+ case "nullable":
603
+ break;
203
604
  case "email":
204
605
  fieldRules.push({
205
606
  type: "email",
@@ -229,6 +630,53 @@ export const createValidationRules = (definitions, formState, options = {}) => {
229
630
  transform: (value) => value?.trim()
230
631
  });
231
632
  break;
633
+ case "between": {
634
+ if (paramValues.length < 2) {
635
+ return;
636
+ }
637
+ const minValue = Number(paramValues[0]);
638
+ const maxValue = Number(paramValues[1]);
639
+ fieldRules.push({
640
+ validator: (_rule, value, callback) => {
641
+ if (isEmptyValue(value)) {
642
+ return callback();
643
+ }
644
+ const size = getSizeValue(value);
645
+ if (size === null || Number.isNaN(minValue) || Number.isNaN(maxValue)) {
646
+ return callback(new Error(getMessage(fieldName, "between", `${attributeName} is invalid`)));
647
+ }
648
+ if (size < minValue || size > maxValue) {
649
+ return callback(new Error(getMessage(fieldName, "between", `${attributeName} must be between ${minValue} and ${maxValue}`)));
650
+ }
651
+ callback();
652
+ },
653
+ trigger: pickTrigger(fieldName, "blur")
654
+ });
655
+ break;
656
+ }
657
+ case "size": {
658
+ if (!firstParam) {
659
+ return;
660
+ }
661
+ const expected = Number(firstParam);
662
+ fieldRules.push({
663
+ validator: (_rule, value, callback) => {
664
+ if (isEmptyValue(value)) {
665
+ return callback();
666
+ }
667
+ const size = getSizeValue(value);
668
+ if (size === null || Number.isNaN(expected)) {
669
+ return callback(new Error(getMessage(fieldName, "size", `${attributeName} is invalid`)));
670
+ }
671
+ if (size !== expected) {
672
+ return callback(new Error(getMessage(fieldName, "size", `${attributeName} must be ${expected}`)));
673
+ }
674
+ callback();
675
+ },
676
+ trigger: pickTrigger(fieldName, "blur")
677
+ });
678
+ break;
679
+ }
232
680
  case "same": {
233
681
  if (!firstParam) {
234
682
  return;
@@ -251,6 +699,15 @@ export const createValidationRules = (definitions, formState, options = {}) => {
251
699
  });
252
700
  break;
253
701
  }
702
+ case "date_format":
703
+ if (!params) {
704
+ return;
705
+ }
706
+ fieldRules.push({
707
+ validator: createDateFormatValidator(params, getMessage(fieldName, "date_format", `${attributeName} does not match the format ${params}`)),
708
+ trigger: pickTrigger(fieldName, "blur")
709
+ });
710
+ break;
254
711
  case "date":
255
712
  fieldRules.push({
256
713
  validator: createDateValidator(getMessage(fieldName, "date", `${attributeName} must be a valid date in YYYY-MM-DD format`)),
@@ -283,6 +740,32 @@ export const createValidationRules = (definitions, formState, options = {}) => {
283
740
  trigger: pickTrigger(fieldName, "blur")
284
741
  });
285
742
  break;
743
+ case "before_or_equal":
744
+ if (!firstParam) {
745
+ return;
746
+ }
747
+ fieldRules.push({
748
+ validator: createDateComparisonValidator(
749
+ firstParam,
750
+ getMessage(fieldName, "before_or_equal", `${attributeName} must be a date before or equal to ${firstParam}`),
751
+ (valueDate, compareDate) => valueDate <= compareDate
752
+ ),
753
+ trigger: pickTrigger(fieldName, "blur")
754
+ });
755
+ break;
756
+ case "after_or_equal":
757
+ if (!firstParam) {
758
+ return;
759
+ }
760
+ fieldRules.push({
761
+ validator: createDateComparisonValidator(
762
+ firstParam,
763
+ getMessage(fieldName, "after_or_equal", `${attributeName} must be a date after or equal to ${firstParam}`),
764
+ (valueDate, compareDate) => valueDate >= compareDate
765
+ ),
766
+ trigger: pickTrigger(fieldName, "blur")
767
+ });
768
+ break;
286
769
  case "alpha":
287
770
  fieldRules.push({
288
771
  validator: createPatternValidator(/^[a-z]+$/i, getMessage(fieldName, "alpha", `${attributeName} may only contain letters`)),
@@ -319,6 +802,88 @@ export const createValidationRules = (definitions, formState, options = {}) => {
319
802
  trigger: pickTrigger(fieldName, "change")
320
803
  });
321
804
  break;
805
+ case "distinct":
806
+ fieldRules.push({
807
+ validator: (_rule, value, callback) => {
808
+ if (isEmptyValue(value)) {
809
+ return callback();
810
+ }
811
+ if (!Array.isArray(value)) {
812
+ return callback(new Error(getMessage(fieldName, "distinct", `${attributeName} must be an array`)));
813
+ }
814
+ const set = new Set(value.map((item) => JSON.stringify(item)));
815
+ if (set.size !== value.length) {
816
+ return callback(new Error(getMessage(fieldName, "distinct", `${attributeName} must not contain duplicates`)));
817
+ }
818
+ callback();
819
+ },
820
+ trigger: pickTrigger(fieldName, "change")
821
+ });
822
+ break;
823
+ case "in_array": {
824
+ if (!firstParam) {
825
+ return;
826
+ }
827
+ const otherField = firstParam;
828
+ fieldRules.push({
829
+ validator: (_rule, value, callback) => {
830
+ if (isEmptyValue(value)) {
831
+ return callback();
832
+ }
833
+ const haystack = getNestedValue(dataToValidate, otherField);
834
+ if (!Array.isArray(haystack)) {
835
+ return callback(new Error(getMessage(fieldName, "in_array", `${attributeName} must be one of the values in ${otherField}`)));
836
+ }
837
+ if (!haystack.includes(value)) {
838
+ return callback(new Error(getMessage(fieldName, "in_array", `${attributeName} must be one of the values in ${otherField}`)));
839
+ }
840
+ callback();
841
+ },
842
+ trigger: pickTrigger(fieldName, "change")
843
+ });
844
+ break;
845
+ }
846
+ case "required_array_keys": {
847
+ if (!paramValues.length) {
848
+ return;
849
+ }
850
+ const requiredKeys = paramValues;
851
+ fieldRules.push({
852
+ validator: (_rule, value, callback) => {
853
+ if (isEmptyValue(value)) {
854
+ return callback(new Error(getMessage(fieldName, "required_array_keys", `${attributeName} must include all required keys`)));
855
+ }
856
+ if (typeof value !== "object") {
857
+ return callback(new Error(getMessage(fieldName, "required_array_keys", `${attributeName} must be an array or object`)));
858
+ }
859
+ const missing = requiredKeys.filter((key) => !Object.prototype.hasOwnProperty.call(value, key));
860
+ if (missing.length) {
861
+ return callback(new Error(getMessage(fieldName, "required_array_keys", `${attributeName} must include keys: ${missing.join(", ")}`)));
862
+ }
863
+ callback();
864
+ },
865
+ trigger: pickTrigger(fieldName, "change")
866
+ });
867
+ break;
868
+ }
869
+ case "list":
870
+ fieldRules.push({
871
+ validator: (_rule, value, callback) => {
872
+ if (isEmptyValue(value)) {
873
+ return callback();
874
+ }
875
+ if (!Array.isArray(value)) {
876
+ return callback(new Error(getMessage(fieldName, "list", `${attributeName} must be a list`)));
877
+ }
878
+ const isSequential = value.every((_, index) => Object.prototype.hasOwnProperty.call(value, index));
879
+ if (!isSequential) {
880
+ return callback(new Error(getMessage(fieldName, "list", `${attributeName} must be a list with sequential indices`)));
881
+ }
882
+ callback();
883
+ },
884
+ trigger: pickTrigger(fieldName, "change")
885
+ });
886
+ break;
322
887
  case "starts_with": {
323
888
  if (!paramValues.length) {
324
889
  return;
@@ -368,6 +933,85 @@ export const createValidationRules = (definitions, formState, options = {}) => {
368
933
  trigger: pickTrigger(fieldName, "blur")
369
934
  });
370
935
  break;
936
+ case "lowercase":
937
+ fieldRules.push({
938
+ validator: (_rule, value, callback) => {
939
+ if (isEmptyValue(value)) {
940
+ return callback();
941
+ }
942
+ if (typeof value !== "string" || value !== value.toLowerCase()) {
943
+ return callback(new Error(getMessage(fieldName, "lowercase", `${attributeName} must be lowercase`)));
944
+ }
945
+ callback();
946
+ },
947
+ trigger: pickTrigger(fieldName, "blur")
948
+ });
949
+ break;
950
+ case "uppercase":
951
+ fieldRules.push({
952
+ validator: (_rule, value, callback) => {
953
+ if (isEmptyValue(value)) {
954
+ return callback();
955
+ }
956
+ if (typeof value !== "string" || value !== value.toUpperCase()) {
957
+ return callback(new Error(getMessage(fieldName, "uppercase", `${attributeName} must be uppercase`)));
958
+ }
959
+ callback();
960
+ },
961
+ trigger: pickTrigger(fieldName, "blur")
962
+ });
963
+ break;
964
+ case "uuid":
965
+ fieldRules.push({
966
+ validator: (_rule, value, callback) => {
967
+ if (isEmptyValue(value)) {
968
+ return callback();
969
+ }
970
+ const text = String(value);
971
+ const pattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
972
+ if (!pattern.test(text)) {
973
+ return callback(new Error(getMessage(fieldName, "uuid", `${attributeName} must be a valid UUID`)));
974
+ }
975
+ callback();
976
+ },
977
+ trigger: pickTrigger(fieldName, "blur")
978
+ });
979
+ break;
980
+ case "ulid":
981
+ fieldRules.push({
982
+ validator: (_rule, value, callback) => {
983
+ if (isEmptyValue(value)) {
984
+ return callback();
985
+ }
986
+ const text = String(value);
987
+ const pattern = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
988
+ if (!pattern.test(text)) {
989
+ return callback(new Error(getMessage(fieldName, "ulid", `${attributeName} must be a valid ULID`)));
990
+ }
991
+ callback();
992
+ },
993
+ trigger: pickTrigger(fieldName, "blur")
994
+ });
995
+ break;
996
+ case "timezone":
997
+ fieldRules.push({
998
+ validator: (_rule, value, callback) => {
999
+ if (isEmptyValue(value)) {
1000
+ return callback();
1001
+ }
1002
+ if (typeof value !== "string") {
1003
+ return callback(new Error(getMessage(fieldName, "timezone", `${attributeName} must be a valid timezone`)));
1004
+ }
1005
+ try {
1006
+ Intl.DateTimeFormat("en-US", { timeZone: value }).format();
1007
+ } catch {
1008
+ return callback(new Error(getMessage(fieldName, "timezone", `${attributeName} must be a valid timezone`)));
1009
+ }
1010
+ callback();
1011
+ },
1012
+ trigger: pickTrigger(fieldName, "change")
1013
+ });
1014
+ break;
371
1015
  case "in": {
372
1016
  if (!paramValues.length) {
373
1017
  return;
@@ -379,6 +1023,51 @@ export const createValidationRules = (definitions, formState, options = {}) => {
379
1023
  });
380
1024
  break;
381
1025
  }
1026
+ case "multiple_of": {
1027
+ if (!firstParam) {
1028
+ return;
1029
+ }
1030
+ const factor = Number(firstParam);
1031
+ fieldRules.push({
1032
+ validator: (_rule, value, callback) => {
1033
+ if (isEmptyValue(value)) {
1034
+ return callback();
1035
+ }
1036
+ const numeric = Number(value);
1037
+ if (Number.isNaN(numeric) || Number.isNaN(factor) || factor === 0 || numeric % factor !== 0) {
1038
+ return callback(new Error(getMessage(fieldName, "multiple_of", `${attributeName} must be a multiple of ${factor}`)));
1039
+ }
1040
+ callback();
1041
+ },
1042
+ trigger: pickTrigger(fieldName, "blur")
1043
+ });
1044
+ break;
1045
+ }
1046
+ case "decimal": {
1047
+ if (!paramValues.length) {
1048
+ return;
1049
+ }
1050
+ const minDecimals = Number(paramValues[0]);
1051
+ const maxDecimals = paramValues.length > 1 ? Number(paramValues[1]) : minDecimals;
1052
+ fieldRules.push({
1053
+ validator: (_rule, value, callback) => {
1054
+ if (isEmptyValue(value)) {
1055
+ return callback();
1056
+ }
1057
+ const text = String(value);
1058
+ if (!/^-?\d+(?:\.\d+)?$/.test(text)) {
1059
+ return callback(new Error(getMessage(fieldName, "decimal", `${attributeName} must be a decimal`)));
1060
+ }
1061
+ const decimals = text.includes(".") ? text.split(".")[1].length : 0;
1062
+ if (decimals < minDecimals || decimals > maxDecimals) {
1063
+ return callback(new Error(getMessage(fieldName, "decimal", `${attributeName} must have between ${minDecimals} and ${maxDecimals} decimal places`)));
1064
+ }
1065
+ callback();
1066
+ },
1067
+ trigger: pickTrigger(fieldName, "blur")
1068
+ });
1069
+ break;
1070
+ }
382
1071
  case "numeric":
383
1072
  fieldRules.push({
384
1073
  validator: createNumericValidator(getMessage(fieldName, "numeric", `${attributeName} must be a number`)),
@@ -391,6 +1080,64 @@ export const createValidationRules = (definitions, formState, options = {}) => {
391
1080
  trigger: pickTrigger(fieldName, "blur")
392
1081
  });
393
1082
  break;
1083
+ case "confirmed": {
1084
+ const confirmationField = getConfirmationField(fieldName);
1085
+ fieldRules.push({
1086
+ validator: (_rule, value, callback) => {
1087
+ if (isEmptyValue(value)) {
1088
+ return callback();
1089
+ }
1090
+ const confirmationValue = getNestedValue(dataToValidate, confirmationField);
1091
+ if (value !== confirmationValue) {
1092
+ return callback(new Error(getMessage(fieldName, "confirmed", `${attributeName} confirmation does not match`)));
1093
+ }
1094
+ callback();
1095
+ },
1096
+ trigger: pickTrigger(fieldName, "blur")
1097
+ });
1098
+ break;
1099
+ }
1100
+ case "digits": {
1101
+ if (!firstParam) {
1102
+ return;
1103
+ }
1104
+ const expectedLength = Number(firstParam);
1105
+ fieldRules.push({
1106
+ validator: (_rule, value, callback) => {
1107
+ if (isEmptyValue(value)) {
1108
+ return callback();
1109
+ }
1110
+ const text = String(value);
1111
+ if (!/^\d+$/.test(text) || text.length !== expectedLength) {
1112
+ return callback(new Error(getMessage(fieldName, "digits", `${attributeName} must be ${expectedLength} digits`)));
1113
+ }
1114
+ callback();
1115
+ },
1116
+ trigger: pickTrigger(fieldName, "blur")
1117
+ });
1118
+ break;
1119
+ }
1120
+ case "digits_between": {
1121
+ if (paramValues.length < 2) {
1122
+ return;
1123
+ }
1124
+ const minLength = Number(paramValues[0]);
1125
+ const maxLength = Number(paramValues[1]);
1126
+ fieldRules.push({
1127
+ validator: (_rule, value, callback) => {
1128
+ if (isEmptyValue(value)) {
1129
+ return callback();
1130
+ }
1131
+ const text = String(value);
1132
+ if (!/^\d+$/.test(text) || text.length < minLength || text.length > maxLength) {
1133
+ return callback(new Error(getMessage(fieldName, "digits_between", `${attributeName} must be between ${minLength} and ${maxLength} digits`)));
1134
+ }
1135
+ callback();
1136
+ },
1137
+ trigger: pickTrigger(fieldName, "blur")
1138
+ });
1139
+ break;
1140
+ }
394
1141
  case "gt": {
395
1142
  if (!firstParam) {
396
1143
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ciwergrp/nuxid",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "description": "All-in-one essential modules for Nuxt",
5
5
  "repository": {
6
6
  "type": "git",