20 |
21 |
22 |
45 |
--------------------------------------------------------------------------------
/packages/rules/src/rules/date.ts:
--------------------------------------------------------------------------------
1 | import type { MaybeInput, RegleRuleDefinition } from '@regle/core';
2 | import { createRule } from '@regle/core';
3 | import { isFilled } from '../helpers';
4 |
5 | /**
6 | * Requires a value to be a native `Date` constructor.
7 | *
8 | * Mainly used for typing with `InferInput`.
9 | *
10 | * @example
11 | * ```ts
12 | * import { type InferInput } from '@regle/core';
13 | * import { date } from '@regle/rules';
14 | *
15 | * const rules = {
16 | * birthday: { date },
17 | * }
18 | *
19 | * const state = ref>({});
20 | * ```
21 | *
22 | * @see {@link https://reglejs.dev/core-concepts/rules/built-in-rules#date Documentation}
23 | */
24 | export const date: RegleRuleDefinition, unknown> = createRule({
25 | type: 'date',
26 | validator: (value: MaybeInput) => {
27 | if (isFilled(value)) {
28 | return value instanceof Date;
29 | }
30 | return true;
31 | },
32 | message: 'The value must be a native Date constructor',
33 | });
34 |
--------------------------------------------------------------------------------
/packages/rules/src/rules/number.ts:
--------------------------------------------------------------------------------
1 | import type { MaybeInput, RegleRuleDefinition } from '@regle/core';
2 | import { createRule } from '@regle/core';
3 | import { isFilled, isNumber } from '../helpers';
4 |
5 | /**
6 | * Requires a value to be a native number type.
7 | *
8 | * Mainly used for typing with `InferInput`.
9 | *
10 | * @example
11 | * ```ts
12 | * import { type InferInput } from '@regle/core';
13 | * import { number } from '@regle/rules';
14 | *
15 | * const rules = {
16 | * count: { number },
17 | * }
18 | *
19 | * const state = ref>({});
20 | * ```
21 | *
22 | * @see {@link https://reglejs.dev/core-concepts/rules/built-in-rules#number Documentation}
23 | */
24 | export const number: RegleRuleDefinition, unknown> = createRule({
25 | type: 'number',
26 | validator: (value: MaybeInput) => {
27 | if (isFilled(value)) {
28 | return isNumber(value);
29 | }
30 | return true;
31 | },
32 | message: 'The value must be a native number',
33 | });
34 |
--------------------------------------------------------------------------------
/packages/rules/src/rules/boolean.ts:
--------------------------------------------------------------------------------
1 | import type { MaybeInput, RegleRuleDefinition } from '@regle/core';
2 | import { createRule } from '@regle/core';
3 | import { isFilled } from '../helpers';
4 |
5 | /**
6 | * Requires a value to be a native boolean type.
7 | *
8 | * Mainly used for typing with `InferInput`.
9 | *
10 | * @example
11 | * ```ts
12 | * import { type InferInput } from '@regle/core';
13 | * import { boolean } from '@regle/rules';
14 | *
15 | * const rules = {
16 | * checkbox: { boolean },
17 | * }
18 | *
19 | * const state = ref>({});
20 | * ```
21 | *
22 | * @see {@link https://reglejs.dev/core-concepts/rules/built-in-rules#boolean Documentation}
23 | */
24 | export const boolean: RegleRuleDefinition, unknown> = createRule({
25 | type: 'boolean',
26 | validator: (value: MaybeInput) => {
27 | if (isFilled(value)) {
28 | return typeof value === 'boolean';
29 | }
30 | return true;
31 | },
32 | message: 'The value must be a native boolean',
33 | });
34 |
--------------------------------------------------------------------------------
/tests/unit/useRegle/validate/fixtures.ts:
--------------------------------------------------------------------------------
1 | import { useRegle } from '@regle/core';
2 | import { required, requiredIf } from '@regle/rules';
3 |
4 | export function simpleNestedStateWithMixedValidation() {
5 | interface Form {
6 | email?: string;
7 | date: Date;
8 | maybeUndefined: number;
9 | user?: {
10 | firstName?: string;
11 | lastName?: string;
12 | };
13 | contacts?: [{ name: string }];
14 | collection?: [{ name: string }];
15 | file: File;
16 | }
17 |
18 | const condition: boolean = true as boolean;
19 |
20 | return useRegle({} as Form, {
21 | email: { required },
22 | date: { required: requiredIf(false) },
23 | maybeUndefined: {
24 | ...(condition && {
25 | required,
26 | }),
27 | },
28 | user: {
29 | firstName: { required },
30 | },
31 | contacts: {
32 | $each: {
33 | name: { required },
34 | },
35 | },
36 | collection: {
37 | required,
38 | },
39 | file: { required, otherValidation: (value) => value instanceof File },
40 | });
41 | }
42 |
--------------------------------------------------------------------------------
/packages/core/src/types/utils/static.types.ts:
--------------------------------------------------------------------------------
1 | import type { IsUnknown } from 'type-fest';
2 | import type { Raw } from 'vue';
3 | import type { isRecordLiteral } from './misc.types';
4 |
5 | declare const RegleStaticSymbol: unique symbol;
6 |
7 | export type RegleStatic = T extends new (...args: infer Args) => infer U
8 | ? RegleStaticImpl RegleStaticImpl>
9 | : RegleStaticImpl;
10 |
11 | export type RegleStaticImpl = Raw;
12 |
13 | export type UnwrapRegleStatic = T extends RegleStaticImpl ? U : T;
14 | export type IsRegleStatic = T extends RegleStaticImpl ? true : false;
15 |
16 | export type UnwrapStatic =
17 | IsUnknown extends true ? any : NonNullable extends RegleStaticImpl ? Raw : UnwrapStaticSimple;
18 |
19 | type UnwrapStaticSimple =
20 | NonNullable extends Array
21 | ? Array>
22 | : isRecordLiteral> extends true
23 | ? {
24 | [K in keyof T]: UnwrapStatic;
25 | }
26 | : T;
27 |
--------------------------------------------------------------------------------
/docs/src/parts/components/QuickUsage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | {{ error }}
19 |
20 |
21 |
22 |
23 |
24 |
35 |
--------------------------------------------------------------------------------
/packages/core/src/core/useRegle/root/common/common-types.ts:
--------------------------------------------------------------------------------
1 | import type { ComputedRef } from 'vue';
2 | import type {
3 | $InternalRegleShortcutDefinition,
4 | CustomRulesDeclarationTree,
5 | ResolvedRegleBehaviourOptions,
6 | } from '../../../../types';
7 | import type { RegleStorage } from '../../../useStorage';
8 |
9 | export type StateWithId = unknown & { $id?: string };
10 |
11 | export interface CommonResolverOptions {
12 | customMessages: CustomRulesDeclarationTree | undefined;
13 | path: string;
14 | cachePath: string;
15 | index?: number;
16 | storage: RegleStorage;
17 | options: ResolvedRegleBehaviourOptions;
18 | fieldName: string | undefined;
19 | shortcuts: $InternalRegleShortcutDefinition | undefined;
20 | }
21 |
22 | export interface CommonResolverScopedState {
23 | $anyDirty: ComputedRef;
24 | $invalid: ComputedRef;
25 | $correct: ComputedRef;
26 | $error: ComputedRef;
27 | $pending: ComputedRef;
28 | $name: ComputedRef;
29 | $edited: ComputedRef;
30 | $anyEdited: ComputedRef;
31 | }
32 |
--------------------------------------------------------------------------------
/playground/vue3/README.md:
--------------------------------------------------------------------------------
1 | # vue-project
2 |
3 | This template should help get you started developing with Vue 3 in Vite.
4 |
5 | ## Recommended IDE Setup
6 |
7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
8 |
9 | ## Type Support for `.vue` Imports in TS
10 |
11 | TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
12 |
13 | ## Customize configuration
14 |
15 | See [Vite Configuration Reference](https://vite.dev/config/).
16 |
17 | ## Project Setup
18 |
19 | ```sh
20 | pnpm install
21 | ```
22 |
23 | ### Compile and Hot-Reload for Development
24 |
25 | ```sh
26 | pnpm dev
27 | ```
28 |
29 | ### Type-Check, Compile and Minify for Production
30 |
31 | ```sh
32 | pnpm build
33 | ```
34 |
35 | ### Lint with [ESLint](https://eslint.org/)
36 |
37 | ```sh
38 | pnpm lint
39 | ```
40 |
--------------------------------------------------------------------------------
/playground/nuxt/components/validations.ts:
--------------------------------------------------------------------------------
1 | import type { Maybe } from '@regle/core';
2 | import { createRule, defineRegleConfig } from '@regle/core';
3 | import { isEmpty, withMessage, maxLength } from '@regle/rules';
4 |
5 | export function timeout(count: number) {
6 | return new Promise((resolve) => setTimeout(resolve, count));
7 | }
8 |
9 | export const asyncEmail = createRule({
10 | type: 'asyncEmail',
11 | async validator(value: Maybe, limit: number) {
12 | if (isEmpty(value)) {
13 | return { $valid: true };
14 | }
15 | await timeout(1000);
16 | return {
17 | $valid: limit === 2,
18 | foo: 'bar',
19 | };
20 | },
21 | message: 'Value is not an email',
22 | });
23 |
24 | export const { useRegle, inferRules } = defineRegleConfig({
25 | rules: () => ({
26 | maxLength: withMessage(maxLength, ({ $params: [count] }) => {
27 | return `ehooo ${count} is max`;
28 | }),
29 | asyncEmail: withMessage(asyncEmail, () => ''),
30 | }),
31 | modifiers: {
32 | autoDirty: false,
33 | lazy: true,
34 | rewardEarly: true,
35 | },
36 | });
37 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "docs",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vitepress dev --port 6001",
7 | "build": "vitepress build",
8 | "preview": "vitepress preview"
9 | },
10 | "dependencies": {
11 | "@regle/core": "workspace:*",
12 | "@regle/rules": "workspace:*",
13 | "@regle/schemas": "workspace:*",
14 | "@resvg/resvg-js": "2.6.2",
15 | "@shikijs/vitepress-twoslash": "3.15.0",
16 | "@tailwindcss/forms": "0.5.10",
17 | "@types/lodash-es": "4.17.12",
18 | "arktype": "catalog:",
19 | "autoprefixer": "10.4.22",
20 | "check-password-strength": "catalog:",
21 | "lodash-es": "4.17.21",
22 | "pinia": "catalog:",
23 | "postcss": "8.5.6",
24 | "sass": "1.94.2",
25 | "satori": "0.18.3",
26 | "satori-html": "0.3.2",
27 | "tailwindcss": "catalog:",
28 | "valibot": "catalog:",
29 | "vite": "catalog:",
30 | "vitepress": "1.6.4",
31 | "vitepress-plugin-group-icons": "1.6.5",
32 | "vitepress-plugin-llms": "1.9.3",
33 | "vue": "catalog:",
34 | "zod": "catalog:"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/playground/vue3/src/components/validations.ts:
--------------------------------------------------------------------------------
1 | import type { Maybe } from '@regle/core';
2 | import { createRule, defineRegleConfig } from '@regle/core';
3 | import { isEmpty, withMessage, maxLength } from '@regle/rules';
4 |
5 | export function timeout(count: number) {
6 | return new Promise((resolve) => setTimeout(resolve, count));
7 | }
8 |
9 | export const asyncEmail = createRule({
10 | type: 'asyncEmail',
11 | async validator(value: Maybe, limit: number) {
12 | if (isEmpty(value)) {
13 | return { $valid: true };
14 | }
15 | await timeout(1000);
16 | return {
17 | $valid: limit === 2,
18 | foo: 'bar',
19 | };
20 | },
21 | message: 'Value is not an email',
22 | });
23 |
24 | export const { useRegle, inferRules } = defineRegleConfig({
25 | rules: () => ({
26 | maxLength: withMessage(maxLength, ({ $params: [count] }) => {
27 | return `ehooo ${count} is max`;
28 | }),
29 | asyncEmail: withMessage(asyncEmail, () => ''),
30 | }),
31 | modifiers: {
32 | autoDirty: false,
33 | lazy: true,
34 | rewardEarly: true,
35 | },
36 | });
37 |
--------------------------------------------------------------------------------
/docs/src/examples/required-indicators.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Required Indicators
3 | aside: false
4 | ---
5 |
6 | # Required Indicators Demo
7 |
8 | It is often required to add an indicator to form fields to show that they are required. This is often done by adding an asterisk (*) to the field label. This example demonstrates how to add required indicators to form fields, taking advantage of property extensions.
9 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/docs/src/public/regle-playground-button.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: 'New feature proposal 🚀'
2 | description: Suggest an idea for Regle
3 | labels: ['feature request']
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | Thanks for your interest in the project and taking the time to fill out this feature report!
9 | - type: textarea
10 | id: feature-description
11 | attributes:
12 | label: What problem is this solving
13 | description: 'A clear and concise description of what the problem is. Ex. when using the function X we cannot do Y.'
14 | validations:
15 | required: true
16 | - type: textarea
17 | id: proposed-solution
18 | attributes:
19 | label: Proposed solution
20 | description: 'A clear and concise description of what you want to happen with an API proposal when applicable'
21 | validations:
22 | required: true
23 | - type: textarea
24 | id: alternative
25 | attributes:
26 | label: Describe alternatives you've considered
27 | description: A clear and concise description of any alternative solutions or features you've considered.
28 |
--------------------------------------------------------------------------------
/docs/src/examples/custom-rules.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Custom Rules
3 | aside: false
4 | ---
5 |
6 | # Custom Rules Demo
7 |
8 | You can create your own custom rules either by using an inline function or the `createRule` utility. It is recommended to create custom rules using the `createRule` function rather than an inline function because it automatically tracks reactive dependencies and enables you to add custom `active` behavior to the rule.
9 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/tests/unit/schemas/issues/fixtures/valibot.fixture.ts:
--------------------------------------------------------------------------------
1 | import { useRegleSchema } from '@regle/schemas';
2 | import * as v from 'valibot';
3 | import { reactive } from 'vue';
4 |
5 | export function valibotFixture() {
6 | const form = reactive({
7 | level0: 0,
8 | level1: {
9 | child: 1,
10 | level2: {
11 | child: 2,
12 | },
13 | collection: [{ name: 0 as number | undefined }],
14 | },
15 | });
16 |
17 | const valibotIsEven = v.pipe(
18 | v.number('This field is required'),
19 | v.check((value) => {
20 | return value % 2 === 0;
21 | }, 'Custom error')
22 | );
23 |
24 | return useRegleSchema(
25 | form,
26 | v.object({
27 | level0: v.optional(valibotIsEven),
28 | level1: v.object({
29 | child: v.optional(valibotIsEven),
30 | level2: v.object({
31 | child: v.optional(valibotIsEven),
32 | }),
33 | collection: v.pipe(
34 | v.array(v.object({ name: valibotIsEven })),
35 | v.minLength(3, 'Array must contain at least 3 element(s)')
36 | ),
37 | }),
38 | })
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/docs/src/parts/components/operators/OperatorOr.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {{ error }}
13 |
14 |
15 |
16 |
17 |
18 |
34 |
--------------------------------------------------------------------------------
/ui-tests/fixtures/ui-vue3/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ui-vue3",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite --port=9130",
7 | "build": "run-p type-check \"build-only {@}\" --",
8 | "preview": "vite preview",
9 | "build-only": "vite build",
10 | "typecheck": "vue-tsc --noEmit --project ./tsconfig.app.json"
11 | },
12 | "dependencies": {
13 | "@regle/core": "workspace:*",
14 | "@regle/rules": "workspace:*",
15 | "@regle/schemas": "workspace:*",
16 | "check-password-strength": "catalog:",
17 | "pinia": "catalog:",
18 | "sass": "1.86.0",
19 | "vue": "catalog:",
20 | "vue-router": "catalog:"
21 | },
22 | "devDependencies": {
23 | "@tsconfig/node22": "22.0.2",
24 | "@types/node": "catalog:",
25 | "@vitejs/plugin-vue": "catalog:",
26 | "@vue/tsconfig": "0.8.1",
27 | "autoprefixer": "10.4.21",
28 | "npm-run-all2": "8.0.4",
29 | "postcss": "8.5.3",
30 | "prettier": "catalog:",
31 | "tailwindcss": "catalog:",
32 | "typescript": "catalog:",
33 | "vite": "catalog:",
34 | "vue-tsc": "catalog:"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Victor Garcia
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/docs/src/parts/components/DisplayingErrors.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {{ error }}
23 |
24 |
25 |
26 |
27 |
28 |
39 |
--------------------------------------------------------------------------------
/playground/vue3/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
31 |
--------------------------------------------------------------------------------
/.github/images/regle-playground-button.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/mcp-server/README.md:
--------------------------------------------------------------------------------
1 | # @regle/mcp-server
2 |
3 | MCP (Model Context Protocol) Server for [Regle](https://reglejs.dev) - providing AI-powered assistance for Vue form validation.
4 |
5 |
6 | ## Usage with AI Assistants
7 |
8 | ### Claude Desktop
9 |
10 | Add to your `claude_desktop_config.json`:
11 |
12 | ```json
13 | {
14 | "mcpServers": {
15 | "regle": {
16 | "command": "npx",
17 | "args": ["@regle/mcp-server"]
18 | }
19 | }
20 | }
21 | ```
22 |
23 | ### Cursor
24 |
25 | [](https://cursor.com/en-US/install-mcp?name=regle&config=eyJjb21tYW5kIjoibnB4IEByZWdsZS9tY3Atc2VydmVyIn0%3D)
26 |
27 | Or add to your MCP settings:
28 |
29 | ```json
30 | {
31 | "mcpServers": {
32 | "regle": {
33 | "command": "npx",
34 | "args": ["@regle/mcp-server"]
35 | }
36 | }
37 | }
38 | ```
39 |
40 |
41 | ## Development
42 |
43 | ```bash
44 | # Install dependencies
45 | pnpm install
46 |
47 | # Run in development mode
48 | pnpm dev
49 |
50 | # Build
51 | pnpm build
52 |
53 | # Type check
54 | pnpm typecheck
55 | ```
56 |
57 | ## License
58 |
59 | MIT
60 |
61 |
--------------------------------------------------------------------------------
/packages/rules/src/helpers/ruleHelpers/isNumber.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Type guard that checks if the passed value is a real `Number`.
3 | * Returns `false` for `NaN`, making it safer than `typeof value === "number"`.
4 | *
5 | * @param value - The value to check
6 | * @returns `true` if value is a valid number (not `NaN`), `false` otherwise
7 | *
8 | * @example
9 | * ```ts
10 | * import { createRule, type Maybe } from '@regle/core';
11 | * import { isFilled, isNumber } from '@regle/rules';
12 | *
13 | * const rule = createRule({
14 | * validator(value: Maybe) {
15 | * if (isFilled(value) && isNumber(value)) {
16 | * return checkNumber(value);
17 | * }
18 | * return true;
19 | * },
20 | * message: 'Error'
21 | * })
22 | * ```
23 | *
24 | * @see {@link https://reglejs.dev/core-concepts/rules/validations-helpers#isnumber Documentation}
25 | */
26 | export function isNumber(value: unknown): value is number {
27 | if (value == null) return false;
28 | else if (typeof value !== 'number') {
29 | return false;
30 | } else if (isNaN(value)) {
31 | return false;
32 | } else {
33 | return true;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/.github/workflows/sync-next-branch.yml:
--------------------------------------------------------------------------------
1 | name: Sync Next Branch
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | permissions:
9 | contents: write
10 |
11 | jobs:
12 | sync-next:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout repository
16 | uses: actions/checkout@v4
17 | with:
18 | fetch-depth: 0
19 | token: ${{ secrets.GITHUB_TOKEN }}
20 |
21 | - name: Configure Git
22 | run: |
23 | git config user.name "github-actions[bot]"
24 | git config user.email "github-actions[bot]@users.noreply.github.com"
25 |
26 | - name: Fetch all branches
27 | run: git fetch origin
28 |
29 | - name: Checkout next branch
30 | run: git checkout next
31 |
32 | - name: Rebase next onto main
33 | run: |
34 | if git rebase origin/main; then
35 | echo "Rebase successful"
36 | else
37 | echo "Rebase failed - conflicts detected"
38 | git rebase --abort
39 | exit 1
40 | fi
41 |
42 | - name: Push changes to next
43 | run: git push origin next --force-with-lease
44 |
--------------------------------------------------------------------------------
/packages/rules/src/helpers/ruleHelpers/toNumber.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Converts any string (or number) into a number using the `Number` constructor.
3 | *
4 | * @param argument - The value to convert
5 | * @returns The converted number (⚠️ Warning: returned value can be `NaN`)
6 | *
7 | * @example
8 | * ```ts
9 | * import { toNumber, isNumber } from '@regle/rules';
10 | *
11 | * const num = toNumber('42'); // 42
12 | * const invalid = toNumber('abc'); // NaN
13 | *
14 | * // Always check for NaN when using toNumber
15 | * if (!isNaN(toNumber(value))) {
16 | * // Safe to use as number
17 | * }
18 | * ```
19 | *
20 | * @see {@link https://reglejs.dev/core-concepts/rules/validations-helpers#tonumber Documentation}
21 | */
22 | export function toNumber(argument: T): number {
23 | if (typeof argument === 'number') {
24 | return argument;
25 | } else if (argument != null) {
26 | if (typeof argument === 'string') {
27 | const isPadded = argument.trim() !== argument;
28 | if (isPadded) {
29 | return NaN;
30 | }
31 | return +argument;
32 | }
33 | return NaN;
34 | }
35 | return NaN;
36 | }
37 |
--------------------------------------------------------------------------------
/packages/rules/src/rules/tests/sameAs.spec.ts:
--------------------------------------------------------------------------------
1 | import { sameAs } from '../sameAs';
2 | import type { RegleRuleDefinition, MaybeInput } from '@regle/core';
3 |
4 | describe('sameAs validator', () => {
5 | it('should not validate different values', () => {
6 | expect(sameAs('empty').exec('any' as any)).toBe(false);
7 | });
8 |
9 | it('should not validate undefined values', () => {
10 | expect(sameAs(undefined).exec(undefined)).toBe(true);
11 | });
12 |
13 | it('should not validate undefined param', () => {
14 | expect(sameAs(undefined).exec('any' as any)).toBe(false);
15 | });
16 |
17 | it('should validate undefined value', () => {
18 | expect(sameAs('any').exec(undefined)).toBe(true);
19 | });
20 |
21 | it('should validate identical values', () => {
22 | expect(sameAs('first').exec('first')).toBe(true);
23 | });
24 |
25 | const sameAsRule = sameAs(() => 'foo');
26 |
27 | expectTypeOf(sameAsRule).toEqualTypeOf<
28 | RegleRuleDefinition<
29 | string,
30 | [target: string, otherName?: string | undefined],
31 | false,
32 | boolean,
33 | MaybeInput,
34 | string
35 | >
36 | >();
37 | });
38 |
--------------------------------------------------------------------------------
/docs/src/public/nuxt.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/packages/rules/src/rules/endsWith.ts:
--------------------------------------------------------------------------------
1 | import type { RegleRuleWithParamsDefinition, MaybeInput } from '@regle/core';
2 | import { createRule } from '@regle/core';
3 | import { isFilled } from '../helpers';
4 |
5 | /**
6 | * Checks if the string ends with the specified substring.
7 | *
8 | * @param part - The substring the value must end with
9 | *
10 | * @example
11 | * ```ts
12 | * import { endsWith } from '@regle/rules';
13 | *
14 | * const { r$ } = useRegle({ firstName: '' }, {
15 | * firstName: { endsWith: endsWith('foo') },
16 | * })
17 | * ```
18 | *
19 | * @see {@link https://reglejs.dev/core-concepts/rules/built-in-rules#endswith Documentation}
20 | */
21 | export const endsWith: RegleRuleWithParamsDefinition<
22 | string,
23 | [part: MaybeInput],
24 | false,
25 | boolean,
26 | MaybeInput
27 | > = createRule({
28 | type: 'endsWith',
29 | validator(value: MaybeInput, part: MaybeInput) {
30 | if (isFilled(value) && isFilled(part)) {
31 | return value.endsWith(part);
32 | }
33 | return true;
34 | },
35 | message({ $params: [part] }) {
36 | return `The value must end with ${part}`;
37 | },
38 | });
39 |
--------------------------------------------------------------------------------
/.github/images/icons/nuxt.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------