├── .nvmrc ├── .npmrc ├── tests ├── .npmrc ├── utils │ ├── index.ts │ └── timeout.ts ├── shims.d.ts ├── fixtures │ └── index.ts ├── tsconfig.json ├── unit │ ├── schemas │ │ ├── single-field │ │ │ └── fixtures │ │ │ │ ├── zod.fixture.ts │ │ │ │ ├── zod4.fixture.ts │ │ │ │ ├── valibot.fixture.ts │ │ │ │ └── arktype.fixture.ts │ │ ├── scoped-regle │ │ │ └── fixtures │ │ │ │ ├── Scope1.vue │ │ │ │ ├── Scope2.vue │ │ │ │ ├── Scope0.vue │ │ │ │ ├── Scope5asRecord.vue │ │ │ │ ├── scoped-config.ts │ │ │ │ └── Scope4WithNamespace.vue │ │ ├── primitive-arrays │ │ │ └── fixtures │ │ │ │ ├── zod.fixture.ts │ │ │ │ ├── valibot.fixture.ts │ │ │ │ └── arktype.fixture.ts │ │ ├── collections │ │ │ └── fixtures │ │ │ │ ├── arktype.fixture.ts │ │ │ │ ├── zod.fixture.ts │ │ │ │ └── valibot.fixture.ts │ │ ├── syncState │ │ │ └── fixtures │ │ │ │ └── zod.fixture.ts │ │ ├── withDeps │ │ │ └── fixtures │ │ │ │ ├── zod4.fixture.ts │ │ │ │ ├── zod.fixture.ts │ │ │ │ └── valibot.fixture.ts │ │ ├── modifiers │ │ │ ├── autoDirty │ │ │ │ └── fixtures │ │ │ │ │ ├── arktype.fixture.ts │ │ │ │ │ ├── zod.fixture.ts │ │ │ │ │ └── valibot.fixture.ts │ │ │ └── rewardEarly │ │ │ │ └── fixtures │ │ │ │ ├── arktype.fixture.ts │ │ │ │ ├── zod.fixture.ts │ │ │ │ └── valibot.fixture.ts │ │ ├── global-config │ │ │ └── fixtures │ │ │ │ ├── zod.fixture.ts │ │ │ │ ├── zod4.fixture.ts │ │ │ │ └── valibot.fixture.ts │ │ └── issues │ │ │ └── fixtures │ │ │ ├── arktype.fixture.ts │ │ │ └── valibot.fixture.ts │ ├── scoped-regle │ │ └── fixtures │ │ │ ├── Scope0.vue │ │ │ ├── Scope1.vue │ │ │ ├── Scope2.vue │ │ │ ├── Scope3CustomConfig.vue │ │ │ ├── Scope5asRecord.vue │ │ │ ├── Scope4WithNamespace.vue │ │ │ └── scoped-config.ts │ ├── types-errors │ │ └── prop-types │ │ │ └── prop-types.config.ts │ ├── test.config.spec.ts │ ├── useRules │ │ └── useRules.schema.spec.ts │ └── useRegle │ │ ├── properties │ │ └── $silentValue.spec.ts │ │ └── validate │ │ └── fixtures.ts └── package.json ├── .husky ├── pre-commit └── commit-msg ├── playground ├── vue3 │ ├── src │ │ ├── assets │ │ │ ├── base.css │ │ │ ├── main.css │ │ │ └── logo.svg │ │ ├── components │ │ │ ├── tests-pinia │ │ │ │ ├── CompA.vue │ │ │ │ ├── Parent.vue │ │ │ │ └── CompoB.vue │ │ │ ├── scopes │ │ │ │ ├── scope-config.ts │ │ │ │ ├── ParentScope.vue │ │ │ │ ├── Child1.vue │ │ │ │ └── Child2.vue │ │ │ ├── Errors.vue │ │ │ ├── FieldError.vue │ │ │ ├── nested-collection │ │ │ │ ├── config.ts │ │ │ │ ├── Compo2.vue │ │ │ │ ├── Compo1.vue │ │ │ │ └── Compo3.vue │ │ │ ├── useForm.ts │ │ │ ├── JSONViewer.vue │ │ │ ├── StressTest.vue │ │ │ └── validations.ts │ │ ├── main.ts │ │ ├── stores │ │ │ └── test.store.ts │ │ └── App.vue │ ├── .npmrc │ ├── env.d.ts │ ├── public │ │ └── favicon.ico │ ├── .vscode │ │ └── extensions.json │ ├── .prettierrc.json │ ├── .editorconfig │ ├── tsconfig.json │ ├── index.html │ ├── tsconfig.node.json │ ├── .gitignore │ ├── tsconfig.app.json │ ├── vite.config.ts │ └── README.md ├── nuxt │ ├── .npmrc │ ├── public │ │ └── favicon.ico │ ├── tsconfig.json │ ├── app.vue │ ├── nuxt.config.ts │ ├── .gitignore │ ├── regle │ │ └── regle-config.ts │ ├── package.json │ └── components │ │ └── validations.ts └── advanced-example │ ├── .vscode │ └── extensions.json │ ├── src │ ├── style.css │ ├── components │ │ └── FieldError.vue │ ├── main.ts │ ├── utils │ │ └── timeout.ts │ └── validations │ │ ├── custom-rules │ │ └── check-pseudo.rule.ts │ │ └── regle.global.config.ts │ ├── postcss.config.js │ ├── vite.config.js │ ├── .prettierrc │ ├── tsconfig.json │ ├── tailwind.config.mjs │ ├── README.md │ ├── index.html │ ├── package.json │ └── public │ └── favicon.svg ├── ui-tests ├── fixtures │ └── ui-vue3 │ │ ├── README.md │ │ ├── env.d.ts │ │ ├── tsconfig.json │ │ ├── .prettierrc │ │ ├── src │ │ ├── main.ts │ │ ├── utils │ │ │ └── timeout.ts │ │ └── components │ │ │ └── MyTextArea.vue │ │ ├── vite.config.ts │ │ ├── tsconfig.app.json │ │ ├── index.html │ │ ├── tsconfig.node.json │ │ ├── .gitignore │ │ └── package.json └── utils │ └── page.utils.ts ├── packages ├── mcp-server │ ├── .gitignore │ ├── tsconfig.json │ ├── tsdown.config.ts │ └── README.md ├── shared │ ├── index.ts │ ├── utils │ │ ├── symbol.ts │ │ ├── isFile.ts │ │ └── index.ts │ ├── tsconfig.json │ ├── package.json │ └── tests │ │ ├── dotPathObjectToNestedObject.spec.ts │ │ └── isEmpty.spec.ts ├── rules │ ├── src │ │ ├── types │ │ │ ├── index.ts │ │ │ └── utils │ │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── helpers │ │ │ ├── ruleHelpers │ │ │ │ ├── index.ts │ │ │ │ ├── tests │ │ │ │ │ ├── getSize.spec.ts │ │ │ │ │ ├── matchRegex.spec.ts │ │ │ │ │ └── isFilled.spec.ts │ │ │ │ ├── isNumber.ts │ │ │ │ └── toNumber.ts │ │ │ ├── index.ts │ │ │ └── tests │ │ │ │ └── withTooltip.spec.ts │ │ ├── utils │ │ │ └── getLocale.util.ts │ │ └── rules │ │ │ ├── tests │ │ │ ├── checked.spec.ts │ │ │ ├── regex.spec.ts │ │ │ ├── date.spec.ts │ │ │ ├── boolean.spec.ts │ │ │ ├── exactValue.spec.ts │ │ │ ├── contains.spec.ts │ │ │ ├── endsWith.spec.ts │ │ │ ├── startsWith.spec.ts │ │ │ ├── string.spec.ts │ │ │ ├── literal.spec.ts │ │ │ ├── requiredUnless.spec.ts │ │ │ └── sameAs.spec.ts │ │ │ ├── type.ts │ │ │ ├── required.ts │ │ │ ├── checked.ts │ │ │ ├── hexadecimal.ts │ │ │ ├── integer.ts │ │ │ ├── decimal.ts │ │ │ ├── numeric.ts │ │ │ ├── string.ts │ │ │ ├── date.ts │ │ │ ├── number.ts │ │ │ ├── boolean.ts │ │ │ └── endsWith.ts │ ├── tsconfig.json │ ├── tsdown.dev.ts │ ├── tsdown.config.ts │ └── tsdown.sourcemap.ts ├── core │ ├── src │ │ ├── core │ │ │ ├── variants │ │ │ │ └── index.ts │ │ │ ├── useRegle │ │ │ │ ├── root │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── standard-schemas.ts │ │ │ │ │ └── common │ │ │ │ │ │ └── common-types.ts │ │ │ │ ├── guards │ │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ ├── useStorage │ │ │ │ └── index.ts │ │ │ ├── createRule │ │ │ │ └── index.ts │ │ │ ├── createScopedUseRegle │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── shims │ │ │ └── shims.d.ts │ │ ├── reset.d.ts │ │ ├── types │ │ │ ├── index.ts │ │ │ ├── core │ │ │ │ ├── index.ts │ │ │ │ └── scopedRegle.types.ts │ │ │ ├── utils │ │ │ │ ├── index.ts │ │ │ │ └── static.types.ts │ │ │ └── rules │ │ │ │ ├── index.ts │ │ │ │ └── rule.custom.types.ts │ │ ├── devtools │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── utils │ │ │ ├── index.ts │ │ │ ├── randomId.ts │ │ │ ├── state.utils.ts │ │ │ └── validationGroups.utils.ts │ │ └── constants │ │ │ └── index.ts │ ├── tsconfig.json │ ├── tsdown.sourcemap.ts │ ├── tsdown.dev.ts │ └── tsdown.config.ts ├── schemas │ ├── src │ │ ├── reset.d.ts │ │ ├── types │ │ │ ├── index.ts │ │ │ └── options.types.ts │ │ ├── core │ │ │ └── index.ts │ │ ├── overrides.ts │ │ └── index.ts │ ├── tsconfig.json │ ├── tsdown.dev.ts │ ├── tsdown.config.ts │ └── tsdown.sourcemap.ts └── nuxt │ ├── playground │ ├── tsconfig.json │ ├── nuxt.config.ts │ ├── package.json │ └── app │ │ └── regle │ │ └── regle-setup.ts │ ├── test │ └── fixtures │ │ ├── app │ │ ├── app.vue │ │ └── components │ │ │ └── MyTextArea.vue │ │ ├── tsconfig.json │ │ ├── package.json │ │ └── nuxt.config.ts │ ├── build.config.ts │ ├── src │ └── runtime │ │ ├── plugins │ │ └── regle.plugin.js │ │ └── defineRegleNuxtPlugin.d.ts │ ├── tsconfig.json │ ├── turbo.jsonc │ └── .gitignore ├── .actrc ├── docs ├── .vitepress │ ├── theme │ │ ├── tailwind.postcss │ │ └── inter.ttf │ ├── blog.data.ts │ └── components │ │ └── BlogIndex.vue ├── src │ ├── assets │ │ ├── logo.png │ │ ├── logo-reversed.png │ │ ├── arktype-logo.svg │ │ ├── logo-reglejs-favicon.svg │ │ └── logo-reglejs-favicon-reversed.svg │ ├── public │ │ ├── logo.png │ │ ├── favicon.ico │ │ ├── logo_main.png │ │ ├── robots.txt │ │ ├── favicon_white.ico │ │ ├── logo-reversed.png │ │ ├── regle-banner-og.png │ │ ├── apple-touch-icon.png │ │ ├── regle-playground.png │ │ ├── favicon-playground.ico │ │ ├── screenshots │ │ │ ├── devtools.png │ │ │ ├── vite-devtools.png │ │ │ ├── devtools-actions.png │ │ │ └── vite-devtools-regle.png │ │ ├── blog │ │ │ ├── regle-1.1-banner.png │ │ │ ├── regle-1.2-banner.png │ │ │ ├── regle-1.1-features.png │ │ │ └── regle-1.2-features.png │ │ ├── model-based.svg │ │ ├── logo-reglejs-favicon.svg │ │ ├── logo-reglejs-favicon-reversed.svg │ │ ├── favicon.svg │ │ ├── favicon-playground.svg │ │ ├── regle-playground-button.svg │ │ └── nuxt.svg │ ├── parts │ │ ├── components │ │ │ ├── scoped-validation │ │ │ │ └── basic-scope │ │ │ │ │ ├── scoped-config.ts │ │ │ │ │ ├── Child2.vue │ │ │ │ │ └── Child1.vue │ │ │ ├── pinia │ │ │ │ ├── demo.store.ts │ │ │ │ ├── ComponentB.vue │ │ │ │ └── ComponentA.vue │ │ │ ├── typing-props │ │ │ │ ├── MyInput.vue │ │ │ │ └── Parent.vue │ │ │ ├── zod │ │ │ │ └── QuickUsage.vue │ │ │ ├── valibot │ │ │ │ └── QuickUsage.vue │ │ │ ├── QuickUsage.vue │ │ │ ├── operators │ │ │ │ └── OperatorOr.vue │ │ │ └── DisplayingErrors.vue │ │ ├── QuickUsage.md │ │ └── DisplayingErrors.md │ ├── blog.md │ ├── examples │ │ ├── collections.md │ │ ├── server-validation.md │ │ ├── advanced.md │ │ ├── simple.md │ │ ├── conditional-rules.md │ │ ├── required-indicators.md │ │ └── custom-rules.md │ └── advanced-usage │ │ └── merge-regles.md ├── tsconfig.json └── package.json ├── .gitattributes ├── vercel.json ├── .github ├── FUNDING.yml ├── images │ ├── vue-sfc-play.png │ ├── logo-regle-full.png │ ├── logo-reglejs-full.png │ ├── logo-reglejs-favicon.png │ ├── regle-github-banner.png │ ├── logo-regle-full-reversed.png │ ├── logo-reglejs-full-reversed.png │ ├── logo-reglejs-favicon-reversed.png │ ├── icons │ │ ├── vue.svg │ │ └── nuxt.svg │ ├── logo-reglejs-favicon.svg │ ├── logo-reglejs-favicon-reversed.svg │ └── regle-playground-button.svg ├── workflows │ ├── changelog.yml │ ├── publish.yml │ ├── build-test.yml │ ├── coverage.yml │ ├── commit-lint.yml │ └── sync-next-branch.yml └── ISSUE_TEMPLATE │ └── feature_request.yml ├── .env-cmdrc ├── packages-private └── regle-playground │ ├── README.md │ ├── public │ ├── favicon-playground.ico │ ├── logo-vue.svg │ └── favicon-playground.svg │ ├── src │ ├── main.ts │ └── icons │ │ ├── Share.vue │ │ ├── Moon.vue │ │ ├── Download.vue │ │ └── GitHub.vue │ ├── tsconfig.json │ ├── package.json │ ├── vite.config.ts │ └── index.html ├── .cursor └── mcp.json ├── lint-staged.config.mjs ├── scripts ├── ignore-step.sh └── publish.mjs ├── tsdown.common.dev.ts ├── commitlint.config.ts ├── .vscode └── settings.json ├── prettier.config.mjs ├── taze.config.ts ├── tsconfig.json ├── playwright.config.ts ├── tsdown.common.build.ts └── LICENSE /.nvmrc: -------------------------------------------------------------------------------- 1 | 22 -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /tests/.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm lint-staged 2 | -------------------------------------------------------------------------------- /playground/vue3/src/assets/base.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /playground/vue3/src/assets/main.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui-tests/fixtures/ui-vue3/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/mcp-server/.gitignore: -------------------------------------------------------------------------------- 1 | src/generated -------------------------------------------------------------------------------- /playground/nuxt/.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true -------------------------------------------------------------------------------- /playground/vue3/.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true -------------------------------------------------------------------------------- /packages/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utils'; 2 | -------------------------------------------------------------------------------- /tests/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './timeout'; 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | corepack install && pnpm commitlint ${1} 2 | -------------------------------------------------------------------------------- /packages/rules/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utils'; 2 | -------------------------------------------------------------------------------- /tests/shims.d.ts: -------------------------------------------------------------------------------- 1 | declare var __USE_DEVTOOLS__: boolean; 2 | -------------------------------------------------------------------------------- /.actrc: -------------------------------------------------------------------------------- 1 | --container-architecture=linux/amd64 2 | --action-offline-mode -------------------------------------------------------------------------------- /docs/.vitepress/theme/tailwind.postcss: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | -------------------------------------------------------------------------------- /playground/vue3/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.html -linguist-detectable 2 | *.scss -linguist-detectable 3 | -------------------------------------------------------------------------------- /packages/core/src/core/variants/index.ts: -------------------------------------------------------------------------------- 1 | export * from './createVariant'; 2 | -------------------------------------------------------------------------------- /packages/core/src/shims/shims.d.ts: -------------------------------------------------------------------------------- 1 | declare var __USE_DEVTOOLS__: boolean; 2 | -------------------------------------------------------------------------------- /packages/rules/src/types/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './params.utils'; 2 | -------------------------------------------------------------------------------- /ui-tests/fixtures/ui-vue3/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "cleanUrls": true, 3 | "trailingSlash": true 4 | } 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: victorgarciaesgi 2 | buy_me_a_coffee: victorgarco 3 | -------------------------------------------------------------------------------- /packages/rules/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './helpers'; 2 | export * from './rules'; 3 | -------------------------------------------------------------------------------- /packages/schemas/src/reset.d.ts: -------------------------------------------------------------------------------- 1 | import '@total-typescript/ts-reset/array-includes'; 2 | -------------------------------------------------------------------------------- /packages/shared/utils/symbol.ts: -------------------------------------------------------------------------------- 1 | export const RegleRuleSymbol = Symbol('regle-rule'); 2 | -------------------------------------------------------------------------------- /packages/core/src/reset.d.ts: -------------------------------------------------------------------------------- 1 | import '@total-typescript/ts-reset/dist/array-includes'; 2 | -------------------------------------------------------------------------------- /packages/nuxt/playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/nuxt/test/fixtures/app/app.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/core/src/core/useRegle/root/index.ts: -------------------------------------------------------------------------------- 1 | export { useRootStorage } from './useRootStorage'; 2 | -------------------------------------------------------------------------------- /playground/advanced-example/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /docs/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/src/assets/logo.png -------------------------------------------------------------------------------- /docs/src/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/src/public/logo.png -------------------------------------------------------------------------------- /packages/core/src/core/useStorage/index.ts: -------------------------------------------------------------------------------- 1 | export { useStorage, type RegleStorage } from './useStorage'; 2 | -------------------------------------------------------------------------------- /packages/schemas/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './core.types'; 2 | export * from './options.types'; 3 | -------------------------------------------------------------------------------- /tests/fixtures/index.ts: -------------------------------------------------------------------------------- 1 | export * from './validations.fixtures'; 2 | export * from './rules.fixtures'; 3 | -------------------------------------------------------------------------------- /docs/src/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/src/public/favicon.ico -------------------------------------------------------------------------------- /playground/advanced-example/src/style.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /docs/src/public/logo_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/src/public/logo_main.png -------------------------------------------------------------------------------- /docs/src/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | Disallow: /*.md$ 4 | Sitemap: https://reglejs.dev/sitemap.xml -------------------------------------------------------------------------------- /packages/core/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './rules'; 2 | export * from './core'; 3 | export * from './utils'; 4 | -------------------------------------------------------------------------------- /packages/rules/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["./src/**/*.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /.github/images/vue-sfc-play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/.github/images/vue-sfc-play.png -------------------------------------------------------------------------------- /docs/.vitepress/theme/inter.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/.vitepress/theme/inter.ttf -------------------------------------------------------------------------------- /.github/images/logo-regle-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/.github/images/logo-regle-full.png -------------------------------------------------------------------------------- /docs/src/assets/logo-reversed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/src/assets/logo-reversed.png -------------------------------------------------------------------------------- /docs/src/public/favicon_white.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/src/public/favicon_white.ico -------------------------------------------------------------------------------- /docs/src/public/logo-reversed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/src/public/logo-reversed.png -------------------------------------------------------------------------------- /docs/src/public/regle-banner-og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/src/public/regle-banner-og.png -------------------------------------------------------------------------------- /packages/core/src/core/useRegle/guards/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ruleDef.guards'; 2 | export * from './rule.status.guards'; 3 | -------------------------------------------------------------------------------- /playground/nuxt/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/playground/nuxt/public/favicon.ico -------------------------------------------------------------------------------- /playground/vue3/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/playground/vue3/public/favicon.ico -------------------------------------------------------------------------------- /.env-cmdrc: -------------------------------------------------------------------------------- 1 | { 2 | "vue-3.5": { 3 | "VUE_VERSION": "3.5" 4 | }, 5 | "vue-3.4": { 6 | "VUE_VERSION": "3.4" 7 | } 8 | } -------------------------------------------------------------------------------- /.github/images/logo-reglejs-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/.github/images/logo-reglejs-full.png -------------------------------------------------------------------------------- /docs/src/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/src/public/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/src/public/regle-playground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/src/public/regle-playground.png -------------------------------------------------------------------------------- /packages/nuxt/test/fixtures/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json", 3 | "exclude": ["../../src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /.github/images/logo-reglejs-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/.github/images/logo-reglejs-favicon.png -------------------------------------------------------------------------------- /.github/images/regle-github-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/.github/images/regle-github-banner.png -------------------------------------------------------------------------------- /docs/src/public/favicon-playground.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/src/public/favicon-playground.ico -------------------------------------------------------------------------------- /docs/src/public/screenshots/devtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/src/public/screenshots/devtools.png -------------------------------------------------------------------------------- /packages/schemas/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["./src/**/*.ts", "./src/reset.d.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /docs/src/public/blog/regle-1.1-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/src/public/blog/regle-1.1-banner.png -------------------------------------------------------------------------------- /docs/src/public/blog/regle-1.2-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/src/public/blog/regle-1.2-banner.png -------------------------------------------------------------------------------- /packages/core/src/devtools/index.ts: -------------------------------------------------------------------------------- 1 | export { createDevtools } from './devtools'; 2 | export { registerRegleInstance } from './registry'; 3 | -------------------------------------------------------------------------------- /playground/nuxt/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json" 4 | } 5 | -------------------------------------------------------------------------------- /playground/vue3/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /.github/images/logo-regle-full-reversed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/.github/images/logo-regle-full-reversed.png -------------------------------------------------------------------------------- /.github/images/logo-reglejs-full-reversed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/.github/images/logo-reglejs-full-reversed.png -------------------------------------------------------------------------------- /docs/src/public/blog/regle-1.1-features.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/src/public/blog/regle-1.1-features.png -------------------------------------------------------------------------------- /docs/src/public/blog/regle-1.2-features.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/src/public/blog/regle-1.2-features.png -------------------------------------------------------------------------------- /docs/src/public/screenshots/vite-devtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/src/public/screenshots/vite-devtools.png -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": {}, 4 | "include": ["./**/*", "./.vitepress/**/*.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["./utils/**/*.ts", "./tests/**/*.ts", "./index.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /playground/advanced-example/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /.github/images/logo-reglejs-favicon-reversed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/.github/images/logo-reglejs-favicon-reversed.png -------------------------------------------------------------------------------- /docs/src/public/screenshots/devtools-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/src/public/screenshots/devtools-actions.png -------------------------------------------------------------------------------- /packages/core/src/core/createRule/index.ts: -------------------------------------------------------------------------------- 1 | export { createRule } from './createRule'; 2 | export { unwrapRuleParameters } from './unwrapRuleParameters'; 3 | -------------------------------------------------------------------------------- /docs/src/public/screenshots/vite-devtools-regle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/docs/src/public/screenshots/vite-devtools-regle.png -------------------------------------------------------------------------------- /packages-private/regle-playground/README.md: -------------------------------------------------------------------------------- 1 | # Regle Playground 2 | 3 | This is continuously deployed at [https://play.reglejs.dev](https://play.reglejs.dev). 4 | -------------------------------------------------------------------------------- /packages/nuxt/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild'; 2 | 3 | export default defineBuildConfig({ 4 | externals: ['@regle/schemas'], 5 | }); 6 | -------------------------------------------------------------------------------- /playground/nuxt/app.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /playground/vue3/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "semi": false, 4 | "singleQuote": true, 5 | "arrowParens": "avoid" 6 | } 7 | -------------------------------------------------------------------------------- /packages-private/regle-playground/public/favicon-playground.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorgarciaesgi/regle/HEAD/packages-private/regle-playground/public/favicon-playground.ico -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noErrorTruncation": true, 5 | "paths": {} 6 | }, 7 | "include": ["./**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /.cursor/mcp.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "regle-mcp-server": { 4 | "command": "node", 5 | "args": ["packages/mcp-server/dist/regle-mcp-server.js"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /playground/vue3/src/components/tests-pinia/CompA.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["./src/**/*.ts", "./src/reset.d.ts", "./tests/**/*", "./src/shims/shims.d.ts"], 4 | "exclude": ["dist"] 5 | } 6 | -------------------------------------------------------------------------------- /playground/vue3/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}] 2 | charset = utf-8 3 | indent_size = 2 4 | indent_style = space 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | -------------------------------------------------------------------------------- /docs/src/parts/components/scoped-validation/basic-scope/scoped-config.ts: -------------------------------------------------------------------------------- 1 | import { createScopedUseRegle } from '@regle/core'; 2 | 3 | export const { useScopedRegle, useCollectScope } = createScopedUseRegle(); 4 | -------------------------------------------------------------------------------- /packages/core/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './object.utils'; 2 | export * from './version-compare'; 3 | export * from './randomId'; 4 | export * from './state.utils'; 5 | export * from './validationGroups.utils'; 6 | -------------------------------------------------------------------------------- /playground/vue3/src/components/scopes/scope-config.ts: -------------------------------------------------------------------------------- 1 | import { createScopedUseRegle } from '@regle/core'; 2 | 3 | export const { useCollectScope, useScopedRegle } = createScopedUseRegle({ 4 | asRecord: true, 5 | }); 6 | -------------------------------------------------------------------------------- /lint-staged.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @filename: lint-staged.config.js 3 | * @type {import('lint-staged').Configuration} 4 | */ 5 | export default { 6 | '*.{js,ts,vue}': [`pnpm format:staged`, `pnpm lint:staged`], 7 | }; 8 | -------------------------------------------------------------------------------- /playground/advanced-example/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import vue from '@vitejs/plugin-vue'; 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | }); 8 | -------------------------------------------------------------------------------- /playground/vue3/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.node.json" 6 | }, 7 | { 8 | "path": "./tsconfig.app.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const regleSymbol = Symbol('regle'); 2 | export const instanceSymbol = Symbol('regle-instance'); 3 | 4 | export const REGLE_FLAGS = { 5 | REGLE_STATIC: '__regle_static', 6 | } as const; 7 | -------------------------------------------------------------------------------- /ui-tests/fixtures/ui-vue3/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.node.json" 6 | }, 7 | { 8 | "path": "./tsconfig.app.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /playground/advanced-example/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "trailingComma": "es5", 5 | "singleQuote": true, 6 | "semi": true, 7 | "bracketSpacing": true, 8 | "htmlWhitespaceSensitivity": "strict" 9 | } -------------------------------------------------------------------------------- /packages/shared/utils/isFile.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Server side friendly way of checking for a File 3 | */ 4 | export function isFile(value: unknown) { 5 | return value?.constructor?.name == 'File' || value?.constructor?.name == 'FileList'; 6 | } 7 | -------------------------------------------------------------------------------- /packages/nuxt/test/fixtures/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "nuxt-test", 4 | "type": "module", 5 | "dependencies": { 6 | "@regle/nuxt": "workspace:*", 7 | "check-password-strength": "catalog:" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/nuxt/src/runtime/plugins/regle.plugin.js: -------------------------------------------------------------------------------- 1 | import { RegleVuePlugin } from '@regle/core'; 2 | import { defineNuxtPlugin } from 'nuxt/app'; 3 | 4 | export default defineNuxtPlugin((nuxtApp) => { 5 | nuxtApp.vueApp.use(RegleVuePlugin); 6 | }); 7 | -------------------------------------------------------------------------------- /scripts/ignore-step.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "VERCEL_ENV: $VERCEL_ENV" 4 | 5 | if [[ "$VERCEL_ENV" == "production" ]] ; then 6 | echo "✅ - Build can proceed" 7 | exit 1; 8 | 9 | else 10 | echo "🛑 - Build cancelled" 11 | exit 0; 12 | fi -------------------------------------------------------------------------------- /ui-tests/fixtures/ui-vue3/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 140, 3 | "tabWidth": 2, 4 | "trailingComma": "es5", 5 | "singleQuote": true, 6 | "semi": true, 7 | "bracketSpacing": true, 8 | "htmlWhitespaceSensitivity": "strict" 9 | } 10 | -------------------------------------------------------------------------------- /ui-tests/fixtures/ui-vue3/src/main.ts: -------------------------------------------------------------------------------- 1 | import './assets/base.scss'; 2 | import './assets/main.scss'; 3 | 4 | import { createApp } from 'vue'; 5 | 6 | import App from './App.vue'; 7 | 8 | const app = createApp(App); 9 | 10 | app.mount('#app'); 11 | -------------------------------------------------------------------------------- /packages/core/src/types/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useRegle.types'; 2 | export * from './modifiers.types'; 3 | export * from './reset.types'; 4 | export * from './scopedRegle.types'; 5 | export * from './results.types'; 6 | export * from './variants.types'; 7 | -------------------------------------------------------------------------------- /docs/src/blog.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: false 3 | editLink: false 4 | outline: false 5 | aside: false 6 | --- 7 | 8 | 11 | 12 | # Latest from Regle 13 | 14 | -------------------------------------------------------------------------------- /playground/nuxt/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({ 2 | devtools: { 3 | enabled: true, 4 | 5 | timeline: { 6 | enabled: true, 7 | }, 8 | }, 9 | compatibilityDate: '2025-02-19', 10 | modules: ['@regle/nuxt', '@pinia/nuxt'], 11 | }); 12 | -------------------------------------------------------------------------------- /packages/nuxt/playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtConfig } from 'nuxt/config'; 2 | 3 | export default defineNuxtConfig({ 4 | modules: ['../src/module'], 5 | devtools: { enabled: true }, 6 | regle: { 7 | setupFile: '~/regle/regle-setup.ts', 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /packages/core/src/core/useRegle/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useRegle'; 2 | export { inferRules, type inferRulesFn } from './inferRules'; 3 | export { useRootStorage } from './root'; 4 | export { flatErrors } from './useErrors'; 5 | export * from './useRules'; 6 | export * from './markStatic'; 7 | -------------------------------------------------------------------------------- /tsdown.common.dev.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from 'tsdown'; 2 | 3 | export const defaultOptions: UserConfig = { 4 | format: ['esm'], 5 | ignoreWatch: ['dist', '.turbo'], 6 | dts: true, 7 | clean: false, 8 | sourcemap: true, 9 | outExtensions: () => ({ js: '.js' }), 10 | }; 11 | -------------------------------------------------------------------------------- /commitlint.config.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from '@commitlint/types'; 2 | 3 | const Configuration: UserConfig = { 4 | extends: ['@commitlint/config-conventional'], 5 | rules: { 6 | 'footer-max-line-length': [2, 'always', Infinity], 7 | }, 8 | }; 9 | 10 | export default Configuration; 11 | -------------------------------------------------------------------------------- /playground/vue3/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/mcp-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "rootDir": "./src", 6 | "resolveJsonModule": true 7 | }, 8 | "include": ["./src/**/*.ts", "./src/**/*.json"], 9 | "exclude": ["dist", "node_modules"] 10 | } 11 | -------------------------------------------------------------------------------- /tests/unit/schemas/single-field/fixtures/zod.fixture.ts: -------------------------------------------------------------------------------- 1 | import { useRegleSchema } from '@regle/schemas'; 2 | import { ref } from 'vue'; 3 | import { z } from 'zod/v3'; 4 | 5 | export function zodSingleFieldFixture() { 6 | const name = ref(); 7 | return useRegleSchema(name, z.number()); 8 | } 9 | -------------------------------------------------------------------------------- /tests/unit/schemas/single-field/fixtures/zod4.fixture.ts: -------------------------------------------------------------------------------- 1 | import { useRegleSchema } from '@regle/schemas'; 2 | import { ref } from 'vue'; 3 | import { z } from 'zod/v4'; 4 | 5 | export function zod4SingleFieldFixture() { 6 | const name = ref(); 7 | return useRegleSchema(name, z.number()); 8 | } 9 | -------------------------------------------------------------------------------- /packages/nuxt/playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "regle-playground", 4 | "type": "module", 5 | "scripts": { 6 | "dev": "nuxi dev", 7 | "build": "nuxi build", 8 | "generate": "nuxi generate" 9 | }, 10 | "dependencies": { 11 | "nuxt": "catalog:" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages-private/regle-playground/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | import '@vue/repl/style.css'; 4 | 5 | // @ts-expect-error Custom window property 6 | window.VUE_DEVTOOLS_CONFIG = { 7 | defaultSelectedAppId: 'repl', 8 | }; 9 | 10 | createApp(App).mount('#app'); 11 | -------------------------------------------------------------------------------- /tests/unit/schemas/single-field/fixtures/valibot.fixture.ts: -------------------------------------------------------------------------------- 1 | import { useRegleSchema } from '@regle/schemas'; 2 | import { ref } from 'vue'; 3 | import * as v from 'valibot'; 4 | 5 | export function valibotSingleFieldFixture() { 6 | const name = ref(); 7 | return useRegleSchema(name, v.number()); 8 | } 9 | -------------------------------------------------------------------------------- /tests/unit/schemas/single-field/fixtures/arktype.fixture.ts: -------------------------------------------------------------------------------- 1 | import { useRegleSchema } from '@regle/schemas'; 2 | import { ref } from 'vue'; 3 | import { type } from 'arktype'; 4 | 5 | export function arkTypeSingleFieldFixture() { 6 | const name = ref(); 7 | return useRegleSchema(name, type('number')); 8 | } 9 | -------------------------------------------------------------------------------- /playground/nuxt/.gitignore: -------------------------------------------------------------------------------- 1 | # Nuxt dev/build outputs 2 | .output 3 | .data 4 | .nuxt 5 | .nitro 6 | .cache 7 | dist 8 | 9 | # Node dependencies 10 | node_modules 11 | 12 | # Logs 13 | logs 14 | *.log 15 | 16 | # Misc 17 | .DS_Store 18 | .fleet 19 | .idea 20 | 21 | # Local env files 22 | .env 23 | .env.* 24 | !.env.example 25 | -------------------------------------------------------------------------------- /packages/core/src/core/createScopedUseRegle/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | createScopedUseRegle, 3 | useCollectScope, 4 | useScopedRegle, 5 | type CreateScopedUseRegleOptions, 6 | } from './createScopedUseRegle'; 7 | export { type UseScopedRegleOptions } from './useScopedRegle'; 8 | export { type useCollectScopeFn } from './useCollectScope'; 9 | -------------------------------------------------------------------------------- /playground/advanced-example/src/components/FieldError.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /playground/advanced-example/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import './style.css'; 3 | import App from './App.vue'; 4 | import { createPinia } from 'pinia'; 5 | import { RegleVuePlugin } from '@regle/core'; 6 | 7 | const pinia = createPinia(); 8 | 9 | createApp(App).use(pinia).use(RegleVuePlugin).mount('#app'); 10 | -------------------------------------------------------------------------------- /packages/core/src/types/core/scopedRegle.types.ts: -------------------------------------------------------------------------------- 1 | import type { SuperCompatibleRegleRoot } from '../rules'; 2 | 3 | export type ScopedInstancesRecord = Record> & { 4 | '~~global': Record; 5 | }; 6 | export type ScopedInstancesRecordLike = Partial; 7 | -------------------------------------------------------------------------------- /playground/vue3/src/components/Errors.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /playground/vue3/src/components/FieldError.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /packages/nuxt/test/fixtures/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({ 2 | ssr: true, 3 | modules: ['@regle/nuxt'], 4 | compatibilityDate: '2024-12-08', 5 | experimental: { 6 | externalVue: false, 7 | }, 8 | imports: { 9 | autoImport: true, 10 | }, 11 | regle: { 12 | setupFile: '~/regle-config.ts', 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /packages/shared/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './isEmpty'; 2 | export * from './symbol'; 3 | export * from './cloneDeep'; 4 | export * from './object.utils'; 5 | export * from './isDate'; 6 | export * from './toDate'; 7 | export * from './debounce'; 8 | export * from './isEqual'; 9 | export * from './isFile'; 10 | export * from './abortablePromise'; 11 | -------------------------------------------------------------------------------- /packages/schemas/src/core/index.ts: -------------------------------------------------------------------------------- 1 | export { useRegleSchema } from './useRegleSchema'; 2 | export { withDeps } from './withDeps'; 3 | export { defineRegleSchemaConfig } from './defineRegleSchemaConfig'; 4 | export { inferSchema } from './inferSchema'; 5 | export { createScopedUseRegleSchema, useCollectSchemaScope, useScopedRegleSchema } from './createScopedUseRegleSchema'; 6 | -------------------------------------------------------------------------------- /ui-tests/fixtures/ui-vue3/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url'; 2 | import { defineConfig } from 'vite'; 3 | import vue from '@vitejs/plugin-vue'; 4 | 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | resolve: { 8 | alias: { 9 | '@': fileURLToPath(new URL('./src', import.meta.url)), 10 | }, 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /packages/core/src/types/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './array.types'; 2 | export * from './misc.types'; 3 | export * from './props.types'; 4 | export * from './mismatch.types'; 5 | export * from './object.types'; 6 | export * from './infer-rules.types'; 7 | export * from './infer-results.types'; 8 | export * from './static.types'; 9 | export * from './union.types'; 10 | -------------------------------------------------------------------------------- /playground/advanced-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "exclude": ["src/**/__tests__/*"], 5 | "compilerOptions": { 6 | "composite": false, 7 | "declaration": false, 8 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/unit/scoped-regle/fixtures/Scope0.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /tests/unit/scoped-regle/fixtures/Scope1.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /tests/unit/scoped-regle/fixtures/Scope2.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "references.preferredLocation": "peek", 4 | "window.title": "Regle", 5 | "editor.codeActionsOnSave": { 6 | "source.fixAll": "explicit" 7 | }, 8 | "editor.formatOnSave": true, 9 | "editor.defaultFormatter": "esbenp.prettier-vscode", 10 | "typescript.experimental.useTsgo": false 11 | } 12 | -------------------------------------------------------------------------------- /tests/unit/schemas/scoped-regle/fixtures/Scope1.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /tests/unit/schemas/scoped-regle/fixtures/Scope2.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /tests/unit/schemas/scoped-regle/fixtures/Scope0.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /packages-private/regle-playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "compilerOptions": { 4 | "target": "esnext", 5 | "module": "esnext", 6 | "moduleResolution": "Bundler", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "sourceMap": true, 10 | "esModuleInterop": true, 11 | "lib": ["esnext", "dom"], 12 | "types": ["vite/client"] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/nuxt/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json", 3 | "compilerOptions": { 4 | "isolatedDeclarations": false, 5 | "composite": false, 6 | "isolatedModules": false, 7 | "types": ["vitest/globals"] 8 | }, 9 | "include": ["test/**/*.ts", "src/**/*.d.ts", "src/**/*.ts", "src/**/*.vue"], 10 | "exclude": ["dist", "node_modules", "playground", "test"] 11 | } 12 | -------------------------------------------------------------------------------- /playground/advanced-example/tailwind.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], 4 | theme: { 5 | extend: { 6 | colors: { 7 | ...require('tailwindcss/colors'), 8 | foo: '#eeeeee', 9 | }, 10 | }, 11 | }, 12 | plugins: [require('@tailwindcss/forms')], 13 | }; 14 | -------------------------------------------------------------------------------- /packages/nuxt/turbo.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["//"], 3 | "tasks": { 4 | "build": { 5 | "dependsOn": ["^build"], 6 | "outputs": ["dist/**", ".nuxt/**"], 7 | "outputLogs": "new-only" 8 | }, 9 | "build:sourcemaps": { 10 | "dependsOn": ["^build:sourcemaps"], 11 | "outputs": ["dist/**", ".nuxt/**"], 12 | "outputLogs": "new-only" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/unit/scoped-regle/fixtures/Scope3CustomConfig.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /playground/vue3/src/main.ts: -------------------------------------------------------------------------------- 1 | import './assets/main.css'; 2 | 3 | import { createApp } from 'vue'; 4 | import App from './App.vue'; 5 | import '@andypf/json-viewer'; 6 | import { createPinia } from 'pinia'; 7 | import { RegleVuePlugin } from '@regle/core'; 8 | 9 | const pinia = createPinia(); 10 | 11 | const app = createApp(App); 12 | app.use(pinia); 13 | app.use(RegleVuePlugin); 14 | app.mount('#app'); 15 | -------------------------------------------------------------------------------- /packages/core/src/types/rules/index.ts: -------------------------------------------------------------------------------- 1 | export * from './rule.init.types'; 2 | export * from './rule.custom.types'; 3 | export * from './rule.declaration.types'; 4 | export * from './rule.definition.type'; 5 | export * from './rule.errors.types'; 6 | export * from './rule.internal.types'; 7 | export * from './rule.params.types'; 8 | export * from './rule.status.types'; 9 | export * from './compatibility.rules'; 10 | -------------------------------------------------------------------------------- /packages/rules/src/helpers/ruleHelpers/index.ts: -------------------------------------------------------------------------------- 1 | import { isFilled } from './isFilled'; 2 | import { isNumber } from './isNumber'; 3 | import { matchRegex } from './matchRegex'; 4 | import { getSize } from './getSize'; 5 | import { toNumber } from './toNumber'; 6 | import { isEmpty, isDate, toDate } from '../../../../shared'; 7 | 8 | export { isEmpty, isFilled, matchRegex, getSize, isNumber, isDate, toDate, toNumber }; 9 | -------------------------------------------------------------------------------- /playground/vue3/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Playground 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/unit/scoped-regle/fixtures/Scope5asRecord.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /playground/advanced-example/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Vite 2 | 3 | This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` 13 | -------------------------------------------------------------------------------- /packages/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@regle/shared", 3 | "private": true, 4 | "type": "module", 5 | "devDependencies": { 6 | "@regle/core": "workspace:*", 7 | "tsdown": "catalog:", 8 | "type-fest": "catalog:", 9 | "typescript": "catalog:", 10 | "vue": "catalog:" 11 | }, 12 | "scripts": { 13 | "typecheck": "tsc --noEmit", 14 | "dev": "echo 'no dev'", 15 | "test": "echo 'no tests'" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import("prettier").Config} 3 | */ 4 | export default { 5 | printWidth: 120, 6 | tabWidth: 2, 7 | singleQuote: true, 8 | semi: true, 9 | bracketSpacing: true, 10 | trailingComma: 'es5', 11 | htmlWhitespaceSensitivity: 'strict', 12 | overrides: [ 13 | { 14 | files: ['**/*.jsonc'], 15 | options: { 16 | trailingComma: 'none', 17 | }, 18 | }, 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /ui-tests/fixtures/ui-vue3/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Fixture Vue 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /playground/advanced-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Regle advanced example 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/core/src/utils/randomId.ts: -------------------------------------------------------------------------------- 1 | function uniqueIDNuxt() { 2 | return Math.floor(Math.random() * Date.now()).toString(); 3 | } 4 | 5 | /** 6 | * Generates a random SSR compatible ID. 7 | */ 8 | export function randomId(): string { 9 | if (typeof window === 'undefined') { 10 | return uniqueIDNuxt(); 11 | } else { 12 | const uint32 = window.crypto.getRandomValues(new Uint32Array(1))[0]; 13 | return uint32.toString(10); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /playground/vue3/src/stores/test.store.ts: -------------------------------------------------------------------------------- 1 | import { useRegle } from '@regle/core'; 2 | import { required } from '@regle/rules'; 3 | import { defineStore, skipHydrate } from 'pinia'; 4 | 5 | function useForm() { 6 | const { r$ } = useRegle({ name: '' }, { name: { required: required } }); 7 | return r$; 8 | } 9 | 10 | export const useTestStore = defineStore('test-store', () => { 11 | const r$ = useForm(); 12 | return { r$: skipHydrate(r$) }; 13 | }); 14 | -------------------------------------------------------------------------------- /.github/images/icons/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /playground/nuxt/regle/regle-config.ts: -------------------------------------------------------------------------------- 1 | import { defineRegleConfig } from '@regle/core'; 2 | import { defineRegleNuxtPlugin } from '@regle/nuxt/setup'; 3 | import { required, withMessage } from '@regle/rules'; 4 | 5 | export default defineRegleNuxtPlugin(() => { 6 | return defineRegleConfig({ 7 | rules: () => ({ 8 | required: withMessage(required, 'Coucou Nuxt'), 9 | customRule: withMessage(required, 'Custom rule'), 10 | }), 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/rules/src/utils/getLocale.util.ts: -------------------------------------------------------------------------------- 1 | import type { Maybe } from '@regle/core'; 2 | 3 | export function getUserLocale(): string { 4 | if (navigator.languages != undefined) return navigator.languages[0]; 5 | return navigator.language; 6 | } 7 | 8 | export function formatLocaleDate(date: Maybe) { 9 | if (date) { 10 | return new Intl.DateTimeFormat(getUserLocale(), { dateStyle: 'short' }).format(new Date(date)); 11 | } 12 | return '?'; 13 | } 14 | -------------------------------------------------------------------------------- /playground/vue3/src/components/scopes/ParentScope.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 17 | -------------------------------------------------------------------------------- /playground/vue3/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node20/tsconfig.json", 3 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "nightwatch.conf.*", "playwright.config.*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "noEmit": true, 7 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 8 | 9 | "module": "ESNext", 10 | "moduleResolution": "Bundler", 11 | "types": ["node"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /playground/vue3/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | 30 | *.tsbuildinfo 31 | -------------------------------------------------------------------------------- /playground/vue3/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "exclude": ["src/**/__tests__/*"], 5 | "compilerOptions": { 6 | "composite": false, 7 | "declaration": false, 8 | "noErrorTruncation": true, 9 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 10 | "baseUrl": ".", 11 | "paths": { 12 | "@/*": ["./src/*"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/unit/schemas/primitive-arrays/fixtures/zod.fixture.ts: -------------------------------------------------------------------------------- 1 | import { useRegleSchema } from '@regle/schemas'; 2 | import { z } from 'zod/v4'; 3 | 4 | export function zodFixture() { 5 | const tagSchema = z.array(z.enum(['wolt', 'eat_in'], 'Custom message')); 6 | const menuSchema = z.object({ 7 | tags: tagSchema, 8 | usernames: z.array(z.string()).min(1, 'Array too short'), 9 | }); 10 | 11 | return useRegleSchema({ tags: [], usernames: [] }, menuSchema); 12 | } 13 | -------------------------------------------------------------------------------- /tests/unit/types-errors/prop-types/prop-types.config.ts: -------------------------------------------------------------------------------- 1 | import { defineRegleConfig } from '@regle/core'; 2 | import { withMessage } from '@regle/rules'; 3 | 4 | export const { useRegle: useCustomRegle } = defineRegleConfig({ 5 | rules: () => ({ 6 | myCustomRule: withMessage(() => true, ''), 7 | }), 8 | shortcuts: { 9 | fields: { 10 | $test: () => true, 11 | $isRequired: (field) => field.$rules.required?.$active ?? false, 12 | }, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /ui-tests/fixtures/ui-vue3/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node22/tsconfig.json", 3 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "nightwatch.conf.*", "playwright.config.*"], 4 | "compilerOptions": { 5 | "composite": false, 6 | "noEmit": true, 7 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 8 | "module": "ESNext", 9 | "moduleResolution": "Bundler", 10 | "types": ["node"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/unit/schemas/collections/fixtures/arktype.fixture.ts: -------------------------------------------------------------------------------- 1 | import { type } from 'arktype'; 2 | 3 | export function arktypeFixture() { 4 | return type({ 5 | name: 'string >= 1', 6 | array: type({ 7 | test: 'string >= 1', 8 | nested_array: type({ 9 | rest: 'string >= 1', 10 | }).array(), 11 | }) 12 | .array() 13 | .atLeastLength(1) 14 | .configure({ message: () => 'Array must contain at least 1 element' }), 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /docs/src/parts/components/pinia/demo.store.ts: -------------------------------------------------------------------------------- 1 | import { required, minLength, email } from '@regle/rules'; 2 | import { defineStore, skipHydrate } from 'pinia'; 3 | import { useRegle } from '@regle/core'; 4 | 5 | export const useDemoStore = defineStore('demo-store', () => { 6 | const { r$ } = useRegle( 7 | { email: '' }, 8 | { 9 | email: { required, minLength: minLength(4), email }, 10 | } 11 | ); 12 | 13 | return { 14 | r$: skipHydrate(r$), 15 | }; 16 | }); 17 | -------------------------------------------------------------------------------- /packages/rules/src/helpers/ruleHelpers/tests/getSize.spec.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | import { getSize } from '../getSize'; 3 | 4 | describe('get the size of a value', () => { 5 | it.each([ 6 | [[], 0], 7 | [[1], 1], 8 | [{}, 0], 9 | [{ a: 1 }, 1], 10 | ['', 0], 11 | ['1', 1], 12 | [1, 1], 13 | [ref([]), 0], 14 | [ref([1]), 1], 15 | ])('size(%s) should be %s', (a, expected) => { 16 | expect(getSize(a)).toBe(expected); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /playground/vue3/src/components/nested-collection/config.ts: -------------------------------------------------------------------------------- 1 | import { createScopedUseRegle, defineRegleConfig } from '@regle/core'; 2 | import { required, withMessage } from '@regle/rules'; 3 | 4 | const { useRegle } = defineRegleConfig({ 5 | rules: () => ({ 6 | required: withMessage(required, 'Coucou la global config'), 7 | }), 8 | }); 9 | 10 | export const { useCollectScope, useScopedRegle } = createScopedUseRegle({ 11 | customUseRegle: useRegle, 12 | asRecord: true, 13 | }); 14 | -------------------------------------------------------------------------------- /ui-tests/fixtures/ui-vue3/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | 30 | *.tsbuildinfo 31 | -------------------------------------------------------------------------------- /packages-private/regle-playground/src/icons/Share.vue: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /docs/src/assets/arktype-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/nuxt/playground/app/regle/regle-setup.ts: -------------------------------------------------------------------------------- 1 | import { defineRegleConfig } from '@regle/core'; 2 | import { defineRegleNuxtPlugin } from '../../../src/runtime/defineRegleNuxtPlugin'; 3 | import { required, withMessage } from '@regle/rules'; 4 | 5 | export default defineRegleNuxtPlugin(() => { 6 | return defineRegleConfig({ 7 | rules: () => ({ 8 | required: withMessage(required, 'Coucou Nuxt'), 9 | customRule: withMessage(required, 'Custom rule'), 10 | }), 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages-private/regle-playground/public/logo-vue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /scripts/publish.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | 3 | import { prerelease } from 'semver'; 4 | import { $ } from 'zx'; 5 | const { version } = require('../package.json'); 6 | 7 | const tags = prerelease(version); 8 | try { 9 | if (tags?.length) { 10 | await $`pnpm -r --filter='@regle/*' publish --report-summary --no-git-checks --tag ${tags[0]}`; 11 | } else { 12 | await $`pnpm -r --filter='@regle/*' publish --report-summary --no-git-checks`; 13 | } 14 | } catch (e) { 15 | console.error(e); 16 | } 17 | -------------------------------------------------------------------------------- /tests/unit/schemas/primitive-arrays/fixtures/valibot.fixture.ts: -------------------------------------------------------------------------------- 1 | import { useRegleSchema } from '@regle/schemas'; 2 | import * as v from 'valibot'; 3 | 4 | export function valibotfixture() { 5 | const tagSchema = v.array(v.picklist(['wolt', 'eat_in'], 'Custom message')); 6 | const menuSchema = v.object({ 7 | tags: tagSchema, 8 | usernames: v.pipe(v.array(v.string()), v.minLength(1, 'Array too short')), 9 | }); 10 | 11 | return useRegleSchema({ tags: [], usernames: [] }, menuSchema); 12 | } 13 | -------------------------------------------------------------------------------- /packages/rules/src/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export { withMessage } from './withMessage'; 2 | export { withTooltip } from './withTooltip'; 3 | export { withAsync } from './withAsync'; 4 | export { withParams } from './withParams'; 5 | export { applyIf } from './applyIf'; 6 | export { matchRegex, getSize, isDate, isEmpty, isFilled, isNumber, toDate, toNumber } from './ruleHelpers'; 7 | export { and } from './and'; 8 | export { or } from './or'; 9 | export { not } from './not'; 10 | export { assignIf } from './assignIf'; 11 | -------------------------------------------------------------------------------- /tests/unit/schemas/scoped-regle/fixtures/scoped-config.ts: -------------------------------------------------------------------------------- 1 | import { createScopedUseRegleSchema } from '@regle/schemas'; 2 | 3 | export const { useCollectScope: useScope1Validations, useScopedRegle: useScoped1Regle } = createScopedUseRegleSchema(); 4 | export const { useCollectScope: useScope2Validations, useScopedRegle: useScoped2Regle } = createScopedUseRegleSchema(); 5 | 6 | export const { useCollectScope: useScope5Validations, useScopedRegle: useScope5Regle } = createScopedUseRegleSchema({ 7 | asRecord: true, 8 | }); 9 | -------------------------------------------------------------------------------- /docs/src/public/model-based.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/rules/src/rules/tests/checked.spec.ts: -------------------------------------------------------------------------------- 1 | import { checked } from '../checked'; 2 | 3 | describe('checked validator', () => { 4 | it('should not validate undefined values', () => { 5 | expect(checked.exec(null)).toBe(true); 6 | expect(checked.exec(undefined)).toBe(true); 7 | }); 8 | 9 | it('should validate true value', () => { 10 | expect(checked.exec(true)).toBe(true); 11 | }); 12 | 13 | it('should not validate false value', () => { 14 | expect(checked.exec(false)).toBe(false); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/rules/tsdown.dev.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, type UserConfig } from 'tsdown'; 2 | import { defaultOptions } from '../../tsdown.common.dev.ts'; 3 | import { defaultExternals } from '../../tsdown.common.build.ts'; 4 | 5 | const sharedOptions: UserConfig = { 6 | ...defaultOptions, 7 | entry: { 'regle-rules': 'src/index.ts' }, 8 | dts: true, 9 | clean: false, 10 | external: [...defaultExternals, '@regle/core'], 11 | watch: ['./src', '../shared/utils'], 12 | }; 13 | 14 | export default defineConfig(sharedOptions); 15 | -------------------------------------------------------------------------------- /packages/schemas/tsdown.dev.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, type UserConfig } from 'tsdown'; 2 | import { defaultOptions } from '../../tsdown.common.dev.ts'; 3 | import { defaultExternals } from '../../tsdown.common.build.ts'; 4 | 5 | const sharedOptions: UserConfig = { 6 | ...defaultOptions, 7 | entry: { 'regle-schemas': 'src/index.ts' }, 8 | dts: true, 9 | clean: false, 10 | external: [...defaultExternals, '@regle/core'], 11 | watch: ['./src', '../shared/utils'], 12 | }; 13 | 14 | export default defineConfig(sharedOptions); 15 | -------------------------------------------------------------------------------- /packages-private/regle-playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "regle-playground", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite --port=5178", 7 | "build": "vite build", 8 | "serve": "vite preview" 9 | }, 10 | "devDependencies": { 11 | "@vitejs/plugin-vue": "6.0.2", 12 | "execa": "9.6.1", 13 | "vite": "catalog:" 14 | }, 15 | "dependencies": { 16 | "@vue/repl": "4.7.0", 17 | "file-saver": "2.0.5", 18 | "jszip": "3.10.1", 19 | "vue": "catalog:" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/schemas/src/overrides.ts: -------------------------------------------------------------------------------- 1 | import type { RegleSchemaCollectionStatus, RegleSchemaFieldStatus, RegleSchemaStatus } from './types'; 2 | 3 | declare module '@regle/core' { 4 | export interface NarrowVariantExtracts { 5 | 'regle-schemas-status': RegleSchemaStatus; 6 | 'regle-schemas-collection-status': RegleSchemaCollectionStatus; 7 | } 8 | interface NarrowVariantFieldExtracts { 9 | 'regle-schemas-field-status': RegleSchemaFieldStatus; 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /tests/unit/schemas/collections/fixtures/zod.fixture.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod/v4'; 2 | 3 | export function zodFixture() { 4 | return z.object({ 5 | name: z.string().min(1), 6 | array: z 7 | .array( 8 | z.object({ 9 | test: z.string().min(1), 10 | nested_array: z.array( 11 | z.object({ 12 | rest: z.string().min(1), 13 | }) 14 | ), 15 | }) 16 | ) 17 | .min(1, { message: 'Array must contain at least 1 element' }), 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/schemas/scoped-regle/fixtures/Scope4WithNamespace.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | -------------------------------------------------------------------------------- /packages/schemas/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | useRegleSchema, 3 | withDeps, 4 | defineRegleSchemaConfig, 5 | inferSchema, 6 | createScopedUseRegleSchema, 7 | useCollectSchemaScope, 8 | useScopedRegleSchema, 9 | } from './core'; 10 | export type { 11 | InferRegleSchemaStatusType, 12 | RegleSchema, 13 | RegleSchemaCollectionStatus, 14 | RegleSchemaFieldStatus, 15 | RegleSchemaResult, 16 | RegleSchemaStatus, 17 | RegleSingleFieldSchema, 18 | RegleSchemaBehaviourOptions, 19 | } from './types'; 20 | 21 | import './overrides'; 22 | -------------------------------------------------------------------------------- /playground/vue3/src/components/scopes/Child1.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | -------------------------------------------------------------------------------- /playground/vue3/src/components/scopes/Child2.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | -------------------------------------------------------------------------------- /tests/unit/schemas/primitive-arrays/fixtures/arktype.fixture.ts: -------------------------------------------------------------------------------- 1 | import { useRegleSchema } from '@regle/schemas'; 2 | import { type } from 'arktype'; 3 | 4 | export function arktypefixture() { 5 | const tagSchema = type("'wolt' |'eat_in'").configure({ 6 | message: () => 'Custom message', 7 | }); 8 | const menuSchema = type({ 9 | tags: type(tagSchema, '[]'), 10 | usernames: type('string[] >= 1').configure({ message: () => 'Array too short' }), 11 | }); 12 | 13 | return useRegleSchema({ tags: [], usernames: [] }, menuSchema); 14 | } 15 | -------------------------------------------------------------------------------- /tests/unit/scoped-regle/fixtures/Scope4WithNamespace.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | -------------------------------------------------------------------------------- /packages/schemas/src/types/options.types.ts: -------------------------------------------------------------------------------- 1 | export type $InternalRegleResult = { valid: boolean; data: any; errors: any; issues: any }; 2 | 3 | export type RegleSchemaBehaviourOptions = { 4 | /** 5 | * Settings for applying transforms and default to the current state 6 | */ 7 | syncState?: { 8 | /** 9 | * Applies every transform on every update to the state 10 | */ 11 | onUpdate?: boolean; 12 | /** 13 | * Applies every transform only when calling `$validate` 14 | */ 15 | onValidate?: boolean; 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /tests/utils/timeout.ts: -------------------------------------------------------------------------------- 1 | export function timeout(count: number) { 2 | return new Promise((resolve) => { 3 | setTimeout(resolve, count); 4 | }); 5 | } 6 | 7 | export function createTimeout() { 8 | let timeout: ReturnType | null = null; 9 | 10 | function timeoutFn(count: number) { 11 | if (timeout) { 12 | clearTimeout(timeout); 13 | } 14 | 15 | return new Promise((resolve) => { 16 | timeout = null; 17 | timeout = setTimeout(resolve, count); 18 | }); 19 | } 20 | 21 | return timeoutFn; 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/changelog.yml: -------------------------------------------------------------------------------- 1 | name: Generate changelog 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | push: 8 | tags: 9 | - "v*" 10 | 11 | jobs: 12 | release: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | 19 | - name: Set node 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: 22 23 | 24 | - run: npx changelogithub 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | -------------------------------------------------------------------------------- /packages/rules/tsdown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, type UserConfig } from 'tsdown'; 2 | import { defaultExternals, defaultOptions, outExtensions } from '../../tsdown.common.build.ts'; 3 | 4 | const sharedOptions: UserConfig = { 5 | ...defaultOptions, 6 | entry: { 'regle-rules': 'src/index.ts' }, 7 | external: [...defaultExternals, '@regle/core'], 8 | }; 9 | 10 | export default defineConfig([ 11 | sharedOptions, 12 | { 13 | ...sharedOptions, 14 | minify: true, 15 | dts: false, 16 | outExtensions: outExtensions(true), 17 | }, 18 | ]); 19 | -------------------------------------------------------------------------------- /docs/src/parts/components/pinia/ComponentB.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | -------------------------------------------------------------------------------- /packages/schemas/tsdown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, type UserConfig } from 'tsdown'; 2 | import { defaultExternals, defaultOptions, outExtensions } from '../../tsdown.common.build.ts'; 3 | 4 | const sharedOptions: UserConfig = { 5 | ...defaultOptions, 6 | entry: { 'regle-schemas': 'src/index.ts' }, 7 | external: [...defaultExternals, '@regle/core'], 8 | }; 9 | 10 | export default defineConfig([ 11 | sharedOptions, 12 | { 13 | ...sharedOptions, 14 | minify: true, 15 | dts: false, 16 | outExtensions: outExtensions(true), 17 | }, 18 | ]); 19 | -------------------------------------------------------------------------------- /playground/vue3/src/components/useForm.ts: -------------------------------------------------------------------------------- 1 | import { type RegleRoot } from '@regle/core'; 2 | 3 | type FormComposable = { 4 | r$?: RegleRoot, any, any, any>; 5 | }; 6 | 7 | const useForm = ({ r$ }: FormComposable = {}) => { 8 | const isFieldInvalid = (fieldName: string): boolean => { 9 | const fields = r$?.$fields; 10 | const fieldValidation = fields?.[fieldName as keyof typeof fields]; 11 | 12 | return !!fieldValidation?.$invalid; 13 | }; 14 | 15 | return { 16 | isFieldInvalid, 17 | }; 18 | }; 19 | 20 | export default useForm; 21 | -------------------------------------------------------------------------------- /taze.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'taze'; 2 | 3 | export default defineConfig({ 4 | exclude: [ 5 | 'vue-3.4', 6 | '@vue/reactivity-3.4', 7 | '@vue/runtime-core-3.4', 8 | '@vue/runtime-dom-3.4', 9 | '@vue/shared-3.4', 10 | '@vue/compiler-dom-3.4', 11 | '@vue/server-renderer-3.4', 12 | 'pinia-2.2.5', 13 | ], 14 | includeLocked: true, 15 | interactive: true, 16 | recursive: true, 17 | write: true, 18 | install: true, 19 | ignorePaths: ['**/node_modules/**'], 20 | ignoreOtherWorkspaces: true, 21 | maturityPeriod: 1, 22 | }); 23 | -------------------------------------------------------------------------------- /ui-tests/fixtures/ui-vue3/src/utils/timeout.ts: -------------------------------------------------------------------------------- 1 | export function timeout(count: number) { 2 | return new Promise((resolve) => { 3 | setTimeout(resolve, count); 4 | }); 5 | } 6 | 7 | export function createTimeout() { 8 | let timeout: ReturnType | null = null; 9 | 10 | function timeoutFn(count: number) { 11 | if (timeout) { 12 | clearTimeout(timeout); 13 | } 14 | 15 | return new Promise((resolve) => { 16 | timeout = null; 17 | timeout = setTimeout(resolve, count); 18 | }); 19 | } 20 | 21 | return timeoutFn; 22 | } 23 | -------------------------------------------------------------------------------- /playground/advanced-example/src/utils/timeout.ts: -------------------------------------------------------------------------------- 1 | export function timeout(count: number) { 2 | return new Promise((resolve) => { 3 | setTimeout(resolve, count); 4 | }); 5 | } 6 | 7 | export function createTimeout() { 8 | let timeout: ReturnType | null = null; 9 | 10 | function timeoutFn(count: number) { 11 | if (timeout) { 12 | clearTimeout(timeout); 13 | } 14 | 15 | return new Promise((resolve) => { 16 | timeout = null; 17 | timeout = setTimeout(resolve, count); 18 | }); 19 | } 20 | 21 | return timeoutFn; 22 | } 23 | -------------------------------------------------------------------------------- /packages/rules/tsdown.sourcemap.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, type UserConfig } from 'tsdown'; 2 | import { defaultExternals, defaultOptions, outExtensions } from '../../tsdown.common.build.ts'; 3 | 4 | const sharedOptions: UserConfig = { 5 | ...defaultOptions, 6 | entry: { 'regle-rules': 'src/index.ts' }, 7 | external: [...defaultExternals, '@regle/core'], 8 | sourcemap: true, 9 | }; 10 | 11 | export default defineConfig([ 12 | sharedOptions, 13 | { 14 | ...sharedOptions, 15 | minify: true, 16 | dts: false, 17 | outExtensions: outExtensions(true), 18 | }, 19 | ]); 20 | -------------------------------------------------------------------------------- /packages/rules/src/rules/tests/regex.spec.ts: -------------------------------------------------------------------------------- 1 | import { regex } from '../regex'; 2 | 3 | describe('regex validator', () => { 4 | it('does not validate falsy values', () => { 5 | expect(regex(/ad/).exec('')).toBe(true); 6 | expect(regex(/ad/).exec(null)).toBe(true); 7 | }); 8 | it('validates truthy values against regex', () => { 9 | expect(regex(/ad/).exec('aaa')).toBe(false); 10 | expect(regex(/ad/).exec('ad')).toBe(true); 11 | expect(regex([/^a.*d$/, /\d{3}/]).exec('ads')).toBe(false); 12 | expect(regex([/^a.*d$/, /\d{3}/]).exec('a123d')).toBe(true); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /docs/src/parts/QuickUsage.md: -------------------------------------------------------------------------------- 1 | ``` vue twoslash [App.vue] 2 | 13 | 14 | 22 | ``` 23 | -------------------------------------------------------------------------------- /tests/unit/schemas/collections/fixtures/valibot.fixture.ts: -------------------------------------------------------------------------------- 1 | import * as v from 'valibot'; 2 | 3 | export function valibotFixture() { 4 | return v.object({ 5 | name: v.pipe(v.string(), v.minLength(1)), 6 | array: v.pipe( 7 | v.array( 8 | v.object({ 9 | test: v.pipe(v.string(), v.minLength(1)), 10 | nested_array: v.array( 11 | v.object({ 12 | rest: v.pipe(v.string(), v.minLength(1)), 13 | }) 14 | ), 15 | }) 16 | ), 17 | v.minLength(1, 'Array must contain at least 1 element') 18 | ), 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /packages-private/regle-playground/src/icons/Moon.vue: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /packages/schemas/tsdown.sourcemap.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, type UserConfig } from 'tsdown'; 2 | import { defaultExternals, defaultOptions, outExtensions } from '../../tsdown.common.build.ts'; 3 | 4 | const sharedOptions: UserConfig = { 5 | ...defaultOptions, 6 | entry: { 'regle-schemas': 'src/index.ts' }, 7 | external: [...defaultExternals, '@regle/core'], 8 | sourcemap: true, 9 | }; 10 | 11 | export default defineConfig([ 12 | { 13 | ...sharedOptions, 14 | }, 15 | { 16 | ...sharedOptions, 17 | minify: true, 18 | dts: false, 19 | outExtensions: outExtensions(true), 20 | }, 21 | ]); 22 | -------------------------------------------------------------------------------- /playground/vue3/src/components/JSONViewer.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/unit/test.config.spec.ts: -------------------------------------------------------------------------------- 1 | import { version } from 'vue'; 2 | import { version as piniaVersion } from 'pinia/package.json'; 3 | 4 | describe('ensure test is correctly configured', () => { 5 | it('should have correct vue version', () => { 6 | if (process.env.VUE_VERSION === '3.4') { 7 | // To be sure the version switching works 8 | expect(version).toBe('3.4.38'); 9 | expect(piniaVersion).toBe('2.2.5'); 10 | } else { 11 | // To be sure the version switching works 12 | expect(version).toBe('3.5.25'); 13 | expect(piniaVersion).toBe('3.0.4'); 14 | } 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /playground/vue3/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url'; 2 | 3 | import { defineConfig } from 'vite'; 4 | import vue from '@vitejs/plugin-vue'; 5 | 6 | // https://vite.dev/config/ 7 | export default defineConfig({ 8 | plugins: [ 9 | vue({ 10 | template: { 11 | compilerOptions: { 12 | // treat all tags with a dash as custom elements 13 | isCustomElement: (tag) => tag.includes('-'), 14 | }, 15 | }, 16 | }), 17 | ], 18 | resolve: { 19 | alias: { 20 | '@': fileURLToPath(new URL('./src', import.meta.url)), 21 | }, 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /packages/rules/src/helpers/ruleHelpers/tests/matchRegex.spec.ts: -------------------------------------------------------------------------------- 1 | import { matchRegex } from '../matchRegex'; 2 | 3 | describe('validates against a regex', () => { 4 | it('does not validate falsy values', () => { 5 | expect(matchRegex('', /ad/)).toBe(true); 6 | expect(matchRegex(null, /ad/)).toBe(true); 7 | }); 8 | it('validates truthy values against regex', () => { 9 | expect(matchRegex('aaa', /ad/)).toBe(false); 10 | expect(matchRegex('ad', /ad/)).toBe(true); 11 | expect(matchRegex('ads', /^a.*d$/, /\d{3}/)).toBe(false); 12 | expect(matchRegex('a123d', /^a.*d$/, /\d{3}/)).toBe(true); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/rules/src/rules/tests/date.spec.ts: -------------------------------------------------------------------------------- 1 | import { date } from '../date'; 2 | 3 | describe('date', () => { 4 | it('should not validate undefined values', () => { 5 | expect(date.exec(null)).toBe(true); 6 | expect(date.exec(undefined)).toBe(true); 7 | }); 8 | 9 | it('should validate Date value', () => { 10 | expect(date.exec(new Date())).toBe(true); 11 | }); 12 | 13 | it('should not validate other values', () => { 14 | expect(date.exec(0)).toBe(false); 15 | expect(date.exec(1)).toBe(false); 16 | expect(date.exec(true)).toBe(false); 17 | expect(date.exec(false)).toBe(false); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /playground/vue3/src/components/nested-collection/Compo2.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.github/images/logo-reglejs-favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/src/assets/logo-reglejs-favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/src/public/logo-reglejs-favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/rules/src/helpers/ruleHelpers/tests/isFilled.spec.ts: -------------------------------------------------------------------------------- 1 | import { isFilled } from '../isFilled'; 2 | 3 | describe('test the isFilled helper for "required"', () => { 4 | it.each([ 5 | [[], false], 6 | [[], true, false], 7 | [[1], true], 8 | [undefined, false], 9 | [null, false], 10 | [false, true], 11 | [new Date(), true], 12 | [{}, false], 13 | [{ a: 1 }, true], 14 | [1, true], 15 | ['asd', true], 16 | ['', false], 17 | ])('isFilled(%s) should be %s', (a, expected, considerEmptyArrayInvalid = true) => { 18 | expect(isFilled(a, considerEmptyArrayInvalid)).toBe(expected); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /.github/images/logo-reglejs-favicon-reversed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/src/assets/logo-reglejs-favicon-reversed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/src/public/logo-reglejs-favicon-reversed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /playground/vue3/src/components/tests-pinia/Parent.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /packages/core/tsdown.sourcemap.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, type UserConfig } from 'tsdown'; 2 | import { defaultExternals, defaultOptions } from '../../tsdown.common.build.ts'; 3 | import { replacePlugin } from 'rolldown/plugins'; 4 | 5 | const sharedOptions: UserConfig = { 6 | ...defaultOptions, 7 | entry: { 'regle-core': 'src/index.ts' }, 8 | external: [...defaultExternals, '@vue/devtools-api'], 9 | sourcemap: true, 10 | treeshake: { 11 | moduleSideEffects: false, 12 | annotations: true, 13 | }, 14 | plugins: [ 15 | replacePlugin({ 16 | __USE_DEVTOOLS__: 'false', 17 | }), 18 | ], 19 | }; 20 | 21 | export default defineConfig(sharedOptions); 22 | -------------------------------------------------------------------------------- /packages/core/tsdown.dev.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, type UserConfig } from 'tsdown'; 2 | import { defaultOptions } from '../../tsdown.common.dev.ts'; 3 | import { defaultExternals } from '../../tsdown.common.build.ts'; 4 | import { replacePlugin } from 'rolldown/plugins'; 5 | 6 | const sharedOptions: UserConfig = { 7 | ...defaultOptions, 8 | entry: { 'regle-core': 'src/index.ts' }, 9 | dts: true, 10 | external: [...defaultExternals, '@vue/devtools-api'], 11 | sourcemap: true, 12 | watch: ['./src', '../shared/utils'], 13 | plugins: [ 14 | replacePlugin({ 15 | __USE_DEVTOOLS__: 'true', 16 | }), 17 | ], 18 | }; 19 | 20 | export default defineConfig(sharedOptions); 21 | -------------------------------------------------------------------------------- /packages/mcp-server/tsdown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, type UserConfig } from 'tsdown'; 2 | 3 | const sharedOptions: UserConfig = { 4 | format: ['esm'], 5 | dts: true, 6 | clean: true, 7 | sourcemap: false, 8 | treeshake: true, 9 | outExtensions: () => ({ js: '.js' }), 10 | entry: { 'regle-mcp-server': 'src/index.ts' }, 11 | external: ['zod', '@modelcontextprotocol/sdk'], 12 | banner: { 13 | js: '#!/usr/bin/env node', 14 | }, 15 | inputOptions(inputOptions) { 16 | inputOptions.experimental ??= {}; 17 | inputOptions.experimental.attachDebugInfo = 'none'; 18 | return inputOptions; 19 | }, 20 | }; 21 | 22 | export default defineConfig([sharedOptions]); 23 | -------------------------------------------------------------------------------- /playground/vue3/src/components/nested-collection/Compo1.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ui-tests/utils/page.utils.ts: -------------------------------------------------------------------------------- 1 | import type { BrowserContext, Page } from 'playwright-core'; 2 | import { test as base } from '@playwright/test'; 3 | 4 | export class Base { 5 | readonly page: Page; 6 | readonly context: BrowserContext; 7 | 8 | constructor(page: Page, context: BrowserContext) { 9 | this.page = page; 10 | this.context = context; 11 | } 12 | 13 | async goto() { 14 | await this.page.goto('/'); 15 | } 16 | } 17 | 18 | export const test = base.extend<{ index: Base }>({ 19 | index: [ 20 | async ({ page, context }, use) => { 21 | const index = new Base(page, context); 22 | await use(index); 23 | }, 24 | { auto: true }, 25 | ], 26 | }); 27 | -------------------------------------------------------------------------------- /tests/unit/schemas/syncState/fixtures/zod.fixture.ts: -------------------------------------------------------------------------------- 1 | import { useRegleSchema, type RegleSchemaBehaviourOptions } from '@regle/schemas'; 2 | import { z } from 'zod/v3'; 3 | 4 | export function zodRegleTransform(options?: RegleSchemaBehaviourOptions) { 5 | const zodTransformSchema = z.object({ 6 | noChange: z.string(), 7 | withDefault: z.string().min(1).default('default value'), 8 | withCatch: z.string().catch('catch'), 9 | withTransform: z.string().transform((value) => `transform: ${value}`), 10 | }); 11 | 12 | return useRegleSchema( 13 | { noValidationValue: 'foo' } as unknown as z.infer, 14 | zodTransformSchema, 15 | options 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /packages-private/regle-playground/src/icons/Download.vue: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /tests/unit/schemas/withDeps/fixtures/zod4.fixture.ts: -------------------------------------------------------------------------------- 1 | import { useRegleSchema, withDeps } from '@regle/schemas'; 2 | import { computed, reactive } from 'vue'; 3 | import { z } from 'zod'; 4 | 5 | export function zod4WithDepsFixture() { 6 | const form = reactive({ 7 | field1: '', 8 | nested: { 9 | field2: '', 10 | }, 11 | }); 12 | 13 | const schema = computed(() => { 14 | return z.object({ 15 | field1: z.string(), 16 | nested: z.object({ 17 | field2: withDeps( 18 | z.string().refine((v) => v !== form.field1), 19 | [() => form.field1] 20 | ), 21 | }), 22 | }); 23 | }); 24 | 25 | return useRegleSchema(form, schema); 26 | } 27 | -------------------------------------------------------------------------------- /docs/src/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/core/src/types/rules/rule.custom.types.ts: -------------------------------------------------------------------------------- 1 | import type { DefaultValidators } from '../../core/defaultValidators'; 2 | import type { RegleRuleRawInput } from './rule.definition.type'; 3 | 4 | export type CustomRulesDeclarationTree = { 5 | [x: string]: RegleRuleRawInput | undefined; 6 | }; 7 | 8 | export type DefaultValidatorsTree = { 9 | [K in keyof DefaultValidators]: RegleRuleRawInput | undefined; 10 | }; 11 | 12 | export type ExtendedRulesDeclarations = CustomRulesDeclarationTree & DefaultValidatorsTree; 13 | 14 | /** @deprecated Use {@link ExtendedRulesDeclarations} instead */ 15 | export type AllRulesDeclarations = ExtendedRulesDeclarations; 16 | -------------------------------------------------------------------------------- /playground/nuxt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-app", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "build:prod": "nuxt build", 7 | "dev": "nuxt dev", 8 | "generate": "nuxt generate", 9 | "preview": "nuxt preview" 10 | }, 11 | "dependencies": { 12 | "@pinia/nuxt": "0.11.3", 13 | "@regle/core": "workspace:*", 14 | "@regle/nuxt": "workspace:*", 15 | "@regle/rules": "workspace:*", 16 | "@regle/schemas": "workspace:*", 17 | "pinia": "catalog:", 18 | "typescript": "catalog:", 19 | "zod": "catalog:" 20 | }, 21 | "devDependencies": { 22 | "nuxt": "catalog:", 23 | "vue": "catalog:", 24 | "vue-router": "catalog:" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/unit/schemas/withDeps/fixtures/zod.fixture.ts: -------------------------------------------------------------------------------- 1 | import { useRegleSchema, withDeps } from '@regle/schemas'; 2 | import { computed, reactive } from 'vue'; 3 | import { z } from 'zod/v3'; 4 | 5 | export function zodWithDepsFixture() { 6 | const form = reactive({ 7 | field1: '', 8 | nested: { 9 | field2: '', 10 | }, 11 | }); 12 | 13 | const schema = computed(() => { 14 | return z.object({ 15 | field1: z.string(), 16 | nested: z.object({ 17 | field2: withDeps( 18 | z.string().refine((v) => v !== form.field1), 19 | [() => form.field1] 20 | ), 21 | }), 22 | }); 23 | }); 24 | 25 | return useRegleSchema(form, schema); 26 | } 27 | -------------------------------------------------------------------------------- /docs/src/public/favicon-playground.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/core/src/devtools/types.ts: -------------------------------------------------------------------------------- 1 | import type { PluginSetupFunction } from '@vue/devtools-kit'; 2 | import type { $InternalRegleStatusType, SuperCompatibleRegleRoot } from '../types'; 3 | 4 | export type DevtoolsV6PluginAPI = Parameters[0]; 5 | 6 | export interface DevtoolsComponentInstance { 7 | uid: number; 8 | name: string; 9 | componentName: string; 10 | } 11 | 12 | export type FieldsDictionary = { 13 | [x: string]: $InternalRegleStatusType; 14 | }; 15 | 16 | export interface RegleInstance { 17 | id: string; 18 | name: string; 19 | r$: SuperCompatibleRegleRoot; 20 | componentName?: string; 21 | filePath?: string; 22 | } 23 | 24 | export type DevtoolsNotifyCallback = () => void; 25 | -------------------------------------------------------------------------------- /packages/rules/src/rules/tests/boolean.spec.ts: -------------------------------------------------------------------------------- 1 | import { boolean } from '../boolean'; 2 | 3 | describe('boolean', () => { 4 | it('should not validate undefined values', () => { 5 | expect(boolean.exec(null)).toBe(true); 6 | expect(boolean.exec(undefined)).toBe(true); 7 | }); 8 | 9 | it('should validate true value', () => { 10 | expect(boolean.exec(true)).toBe(true); 11 | }); 12 | 13 | it('should not validate false value', () => { 14 | expect(boolean.exec(false)).toBe(true); 15 | }); 16 | 17 | it('should not validate other values', () => { 18 | expect(boolean.exec(0)).toBe(false); 19 | expect(boolean.exec(1)).toBe(false); 20 | expect(boolean.exec(new Date())).toBe(false); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/unit/schemas/modifiers/autoDirty/fixtures/arktype.fixture.ts: -------------------------------------------------------------------------------- 1 | import { useRegleSchema } from '@regle/schemas'; 2 | import { type } from 'arktype'; 3 | import { ref } from 'vue'; 4 | 5 | export function arktypeAutoDirtyLocalFixture() { 6 | const form = ref({ 7 | email: '', 8 | user: { 9 | firstName: '', 10 | lastName: '', 11 | }, 12 | contacts: [{ name: '' }], 13 | }); 14 | 15 | const schema = type({ 16 | email: 'string.email', 17 | user: type({ 18 | firstName: 'string>0', 19 | lastName: 'string>0', 20 | }), 21 | contacts: type({ 22 | name: 'string>0', 23 | }).array(), 24 | }); 25 | 26 | return useRegleSchema(form, schema, { autoDirty: false }); 27 | } 28 | -------------------------------------------------------------------------------- /tests/unit/schemas/modifiers/rewardEarly/fixtures/arktype.fixture.ts: -------------------------------------------------------------------------------- 1 | import { useRegleSchema } from '@regle/schemas'; 2 | import { type } from 'arktype'; 3 | import { ref } from 'vue'; 4 | 5 | export function arktypeRewardEarlyFixture() { 6 | const form = ref({ 7 | email: '', 8 | user: { 9 | firstName: '', 10 | lastName: '', 11 | }, 12 | contacts: [{ name: '' }], 13 | }); 14 | 15 | const schema = type({ 16 | email: 'string.email', 17 | user: type({ 18 | firstName: 'string>0', 19 | lastName: 'string>0', 20 | }), 21 | contacts: type({ 22 | name: 'string>0', 23 | }).array(), 24 | }); 25 | 26 | return useRegleSchema(form, schema, { rewardEarly: true }); 27 | } 28 | -------------------------------------------------------------------------------- /docs/src/parts/components/typing-props/MyInput.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 27 | -------------------------------------------------------------------------------- /packages/rules/src/helpers/tests/withTooltip.spec.ts: -------------------------------------------------------------------------------- 1 | import { required } from '../../rules'; 2 | import { withTooltip } from '../withTooltip'; 3 | 4 | describe('withTooltip', () => { 5 | it('should register tooltips to an inline rule', () => { 6 | const inlineRule = withTooltip((_value) => true, 'Hello tooltip'); 7 | expect(inlineRule.tooltip?.({} as any)).toBe('Hello tooltip'); 8 | expect(inlineRule._tooltip).toBe('Hello tooltip'); 9 | }); 10 | 11 | it('should register tooltips to an rule definition', () => { 12 | const inlineRule = withTooltip(required, 'Hello tooltip'); 13 | expect(inlineRule.tooltip?.({} as any)).toBe('Hello tooltip'); 14 | expect(inlineRule._tooltip).toBe('Hello tooltip'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /playground/vue3/src/components/nested-collection/Compo3.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /packages-private/regle-playground/public/favicon-playground.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/unit/useRules/useRules.schema.spec.ts: -------------------------------------------------------------------------------- 1 | import { useRules } from '@regle/core'; 2 | import { required, string } from '@regle/rules'; 3 | import { useRegleSchema } from '@regle/schemas'; 4 | import { nextTick } from 'vue'; 5 | import { shouldBePristineField, shouldBeValidField } from '../../utils/validations.utils'; 6 | 7 | describe('useRules schema', () => { 8 | it('should be able to use itself as aschema', async () => { 9 | const schema = useRules({ username: { required, string } }); 10 | 11 | const { r$ } = useRegleSchema({ username: '' }, schema); 12 | 13 | shouldBePristineField(r$.username); 14 | 15 | r$.$value.username = 'foo'; 16 | await nextTick(); 17 | 18 | shouldBeValidField(r$.username); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages-private/regle-playground/vite.config.ts: -------------------------------------------------------------------------------- 1 | import vue from '@vitejs/plugin-vue'; 2 | import { execaSync } from 'execa'; 3 | import fs from 'node:fs'; 4 | import { defineConfig } from 'vite'; 5 | 6 | const commit = execaSync('git', ['rev-parse', '--short', 'HEAD']); 7 | 8 | export default defineConfig({ 9 | plugins: [ 10 | vue({ 11 | script: { 12 | defineModel: true, 13 | fs: { 14 | fileExists: fs.existsSync, 15 | readFile: (file) => fs.readFileSync(file, 'utf-8'), 16 | }, 17 | }, 18 | }), 19 | ], 20 | define: { 21 | __COMMIT__: JSON.stringify(commit), 22 | __VUE_PROD_DEVTOOLS__: JSON.stringify(true), 23 | }, 24 | optimizeDeps: { 25 | exclude: ['@vue/repl'], 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /packages/core/src/core/useRegle/root/standard-schemas.ts: -------------------------------------------------------------------------------- 1 | import type { StandardSchemaV1 } from '@standard-schema/spec'; 2 | import type { $InternalRegleResult } from '../../../types'; 3 | import { flatErrors } from '../useErrors'; 4 | 5 | export function createStandardSchema(validateFn: (value: any) => Promise<$InternalRegleResult>): StandardSchemaV1 { 6 | return { 7 | '~standard': { 8 | version: 1, 9 | vendor: 'regle', 10 | validate: async (value: any) => { 11 | const { valid, data, errors } = await validateFn(value); 12 | if (valid) { 13 | return { value: data, issues: [] }; 14 | } 15 | return { issues: flatErrors(errors, { includePath: true }) }; 16 | }, 17 | } as const, 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "regle-tests", 3 | "private": true, 4 | "type": "module", 5 | "devDependencies": { 6 | "@pinia/testing": "1.0.3", 7 | "@regle/core": "workspace:*", 8 | "@regle/rules": "workspace:*", 9 | "@regle/schemas": "workspace:*", 10 | "@standard-schema/spec": "1.0.0", 11 | "@vue/compiler-dom": "catalog:", 12 | "@vue/reactivity": "catalog:", 13 | "arktype": "catalog:", 14 | "date-fns": "4.1.0", 15 | "decimal.js": "10.6.0", 16 | "pinia": "catalog:", 17 | "type-fest": "catalog:", 18 | "typescript": "catalog:", 19 | "valibot": "catalog:", 20 | "vitest": "catalog:", 21 | "vue": "catalog:", 22 | "vue-component-type-helpers": "3.1.5", 23 | "zod": "catalog:" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/unit/schemas/modifiers/autoDirty/fixtures/zod.fixture.ts: -------------------------------------------------------------------------------- 1 | import { useRegleSchema } from '@regle/schemas'; 2 | import { ref } from 'vue'; 3 | import { z } from 'zod/v4'; 4 | 5 | export function zodAutoDirtyLocalFixture() { 6 | const form = ref({ 7 | email: '', 8 | user: { 9 | firstName: '', 10 | lastName: '', 11 | }, 12 | contacts: [{ name: '' }], 13 | }); 14 | 15 | const schema = z.object({ 16 | email: z.email(), 17 | user: z.object({ 18 | firstName: z.string().min(1), 19 | lastName: z.string().min(1), 20 | }), 21 | contacts: z.array( 22 | z.object({ 23 | name: z.string().min(1), 24 | }) 25 | ), 26 | }); 27 | 28 | return useRegleSchema(form, schema, { autoDirty: false }); 29 | } 30 | -------------------------------------------------------------------------------- /tests/unit/schemas/withDeps/fixtures/valibot.fixture.ts: -------------------------------------------------------------------------------- 1 | import { useRegleSchema, withDeps } from '@regle/schemas'; 2 | import * as v from 'valibot'; 3 | import { computed, reactive } from 'vue'; 4 | 5 | export function valibotWithDepsFixture() { 6 | const form = reactive({ 7 | field1: '', 8 | nested: { 9 | field2: '', 10 | }, 11 | }); 12 | 13 | const schema = computed(() => { 14 | return v.object({ 15 | field1: v.string(), 16 | nested: v.object({ 17 | field2: withDeps( 18 | v.pipe( 19 | v.string(), 20 | v.check((v) => v !== form.field1) 21 | ), 22 | [() => form.field1] 23 | ), 24 | }), 25 | }); 26 | }); 27 | 28 | return useRegleSchema(form, schema); 29 | } 30 | -------------------------------------------------------------------------------- /tests/unit/schemas/modifiers/rewardEarly/fixtures/zod.fixture.ts: -------------------------------------------------------------------------------- 1 | import { useRegleSchema } from '@regle/schemas'; 2 | import { ref } from 'vue'; 3 | import { z } from 'zod/v3'; 4 | 5 | export function zodRewardEarlyFixture() { 6 | const form = ref({ 7 | email: '', 8 | user: { 9 | firstName: '', 10 | lastName: '', 11 | }, 12 | contacts: [{ name: '' }], 13 | }); 14 | 15 | const schema = z.object({ 16 | email: z.string().email(), 17 | user: z.object({ 18 | firstName: z.string().min(1), 19 | lastName: z.string().min(1), 20 | }), 21 | contacts: z.array( 22 | z.object({ 23 | name: z.string().min(1), 24 | }) 25 | ), 26 | }); 27 | 28 | return useRegleSchema(form, schema, { rewardEarly: true }); 29 | } 30 | -------------------------------------------------------------------------------- /packages/rules/src/rules/tests/exactValue.spec.ts: -------------------------------------------------------------------------------- 1 | import { exactValue } from '../exactValue'; 2 | 3 | describe('exactValue validator', () => { 4 | it('should validate max number', () => { 5 | expect(exactValue(5).exec(5)).toBe(true); 6 | }); 7 | 8 | it('should validate the valid number', () => { 9 | expect(exactValue(5).exec(4)).toBe(false); 10 | }); 11 | 12 | it('should validate the invalid number', () => { 13 | expect(exactValue(5).exec(6)).toBe(false); 14 | }); 15 | 16 | it('should not validate the string value', () => { 17 | expect(exactValue(5).exec('not string here' as any)).toBe(true); 18 | }); 19 | 20 | it('should not validate the object value', () => { 21 | expect(exactValue(5).exec({ hello: 'world' } as any)).toBe(true); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowImportingTsExtensions": true, 4 | "allowSyntheticDefaultImports": true, 5 | "composite": false, 6 | "downlevelIteration": true, 7 | "erasableSyntaxOnly": true, 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "jsx": "preserve", 11 | "jsxImportSource": "vue", 12 | "lib": ["esnext", "DOM"], 13 | "module": "esnext", 14 | "moduleDetection": "force", 15 | "moduleResolution": "bundler", 16 | "noEmit": true, 17 | "noErrorTruncation": true, 18 | "noUncheckedIndexedAccess": false, 19 | "skipLibCheck": true, 20 | "strict": true, 21 | "target": "esnext", 22 | "types": ["vitest/globals", "node"], 23 | "verbatimModuleSyntax": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/rules/src/rules/tests/contains.spec.ts: -------------------------------------------------------------------------------- 1 | import { contains } from '../contains'; 2 | 3 | describe('contains validator', () => { 4 | it('should not validate different values', () => { 5 | expect(contains('foo').exec('bar')).toBe(false); 6 | }); 7 | 8 | it('should not validate undefined values', () => { 9 | expect(contains(undefined).exec(undefined)).toBe(true); 10 | }); 11 | 12 | it('should not validate undefined param', () => { 13 | expect(contains(undefined).exec('any')).toBe(true); 14 | }); 15 | 16 | it('should validate undefined value', () => { 17 | expect(contains('any').exec(undefined)).toBe(true); 18 | }); 19 | 20 | it('should validate a value containing parameter', () => { 21 | expect(contains('ir').exec('first')).toBe(true); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/rules/src/rules/tests/endsWith.spec.ts: -------------------------------------------------------------------------------- 1 | import { endsWith } from '../endsWith'; 2 | 3 | describe('endsWith validator', () => { 4 | it('should not validate different values', () => { 5 | expect(endsWith('ba').exec('bar')).toBe(false); 6 | }); 7 | 8 | it('should not validate undefined values', () => { 9 | expect(endsWith(undefined).exec(undefined)).toBe(true); 10 | }); 11 | 12 | it('should not validate undefined param', () => { 13 | expect(endsWith(undefined).exec('any')).toBe(true); 14 | }); 15 | 16 | it('should validate undefined value', () => { 17 | expect(endsWith('any').exec(undefined)).toBe(true); 18 | }); 19 | 20 | it('should validate a value containing parameter', () => { 21 | expect(endsWith('st').exec('first')).toBe(true); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /docs/src/parts/components/scoped-validation/basic-scope/Child2.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 28 | -------------------------------------------------------------------------------- /packages-private/regle-playground/src/icons/GitHub.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: npm-publish 2 | 3 | permissions: 4 | contents: read 5 | id-token: write 6 | 7 | on: 8 | push: 9 | tags: 10 | - "v*" 11 | workflow_dispatch: 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-node@v4 18 | with: 19 | node-version: "22.x" 20 | registry-url: "https://registry.npmjs.org" 21 | 22 | - uses: pnpm/action-setup@v4 23 | - name: Update npm 24 | run: npm install -g npm@latest 25 | - name: Install dependencies 26 | run: pnpm i 27 | - name: Build 28 | run: pnpm run build 29 | - name: Publish 30 | run: pnpm run npm:publish 31 | env: 32 | NPM_CONFIG_PROVENANCE: true 33 | -------------------------------------------------------------------------------- /packages/nuxt/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Logs 5 | *.log* 6 | 7 | # Temp directories 8 | .temp 9 | .tmp 10 | .cache 11 | 12 | # Yarn 13 | **/.yarn/cache 14 | **/.yarn/*state* 15 | 16 | # Generated dirs 17 | dist 18 | 19 | # Nuxt 20 | .nuxt 21 | .output 22 | .data 23 | .vercel_build_output 24 | .build-* 25 | .netlify 26 | 27 | # Env 28 | .env 29 | 30 | # Testing 31 | reports 32 | coverage 33 | *.lcov 34 | .nyc_output 35 | 36 | # VSCode 37 | .vscode/* 38 | !.vscode/settings.json 39 | !.vscode/tasks.json 40 | !.vscode/launch.json 41 | !.vscode/extensions.json 42 | !.vscode/*.code-snippets 43 | 44 | # Intellij idea 45 | *.iml 46 | .idea 47 | 48 | # OSX 49 | .DS_Store 50 | .AppleDouble 51 | .LSOverride 52 | .AppleDB 53 | .AppleDesktop 54 | Network Trash Folder 55 | Temporary Items 56 | .apdisk 57 | -------------------------------------------------------------------------------- /packages/rules/src/rules/tests/startsWith.spec.ts: -------------------------------------------------------------------------------- 1 | import { startsWith } from '../startsWith'; 2 | 3 | describe('startsWith validator', () => { 4 | it('should not validate different values', () => { 5 | expect(startsWith('ar').exec('bar')).toBe(false); 6 | }); 7 | 8 | it('should not validate undefined values', () => { 9 | expect(startsWith(undefined).exec(undefined)).toBe(true); 10 | }); 11 | 12 | it('should not validate undefined param', () => { 13 | expect(startsWith(undefined).exec('any')).toBe(true); 14 | }); 15 | 16 | it('should validate undefined value', () => { 17 | expect(startsWith('any').exec(undefined)).toBe(true); 18 | }); 19 | 20 | it('should validate a value containing parameter', () => { 21 | expect(startsWith('fir').exec('first')).toBe(true); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /docs/src/parts/components/scoped-validation/basic-scope/Child1.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 28 | -------------------------------------------------------------------------------- /packages/nuxt/src/runtime/defineRegleNuxtPlugin.d.ts: -------------------------------------------------------------------------------- 1 | import type { useCollectScope as defaultUseCollectScope } from '@regle/core'; 2 | import { 3 | type useRegleFn, 4 | type inferRulesFn, 5 | type AllRulesDeclarations, 6 | type RegleShortcutDefinition, 7 | } from '@regle/core'; 8 | 9 | export declare function defineRegleNuxtPlugin< 10 | TCustomRules extends Partial, 11 | TShortcuts extends RegleShortcutDefinition, 12 | >( 13 | setup: () => { 14 | useRegle: useRegleFn; 15 | inferRules: inferRulesFn; 16 | } 17 | ): { 18 | useRegle: useRegleFn; 19 | inferRules: inferRulesFn; 20 | useScopedRegle: useRegleFn; 21 | useCollectScope: typeof defaultUseCollectScope; 22 | }; 23 | -------------------------------------------------------------------------------- /packages/rules/src/rules/tests/string.spec.ts: -------------------------------------------------------------------------------- 1 | import { string } from '../string'; 2 | 3 | describe('string', () => { 4 | it('should validate undefined values', () => { 5 | expect(string.exec(null)).toBe(true); 6 | expect(string.exec(undefined)).toBe(true); 7 | }); 8 | 9 | it('should validate string value', () => { 10 | expect(string.exec('hello')).toBe(true); 11 | }); 12 | 13 | it('should not validate other values', () => { 14 | expect(string.exec(0)).toBe(false); 15 | expect(string.exec(1)).toBe(false); 16 | }); 17 | 18 | it('should not validate other values', () => { 19 | expect(string.exec(new Date())).toBe(false); 20 | expect(string.exec(new Error())).toBe(true); 21 | expect(string.exec(true)).toBe(false); 22 | expect(string.exec(false)).toBe(false); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/rules/src/rules/type.ts: -------------------------------------------------------------------------------- 1 | import type { RegleRuleDefinition, MaybeInput } from '@regle/core'; 2 | 3 | /** 4 | * Define the input type of a rule. No runtime validation. 5 | * 6 | * Override any input type set by other rules. 7 | * 8 | * @example 9 | * ```ts 10 | * import { type InferInput } from '@regle/core'; 11 | * import { type } from '@regle/rules'; 12 | * 13 | * const rules = { 14 | * firstName: { type: type() }, 15 | * status: { type: type<'active' | 'inactive'>() }, 16 | * } 17 | * 18 | * const state = ref>({}); 19 | * ``` 20 | * 21 | * @see {@link https://reglejs.dev/core-concepts/rules/built-in-rules#type Documentation} 22 | */ 23 | export function type(): RegleRuleDefinition> { 24 | return (() => true) as any; 25 | } 26 | -------------------------------------------------------------------------------- /packages/shared/tests/dotPathObjectToNestedObject.spec.ts: -------------------------------------------------------------------------------- 1 | import { dotPathObjectToNested } from '../utils/object.utils'; 2 | 3 | describe('dotPathObjectToNested', () => { 4 | it('should convert a dot path object to a nested object', () => { 5 | expect(dotPathObjectToNested({ 'a.b.c': ['d'] })).toStrictEqual({ a: { b: { c: ['d'] } } }); 6 | 7 | expect(dotPathObjectToNested({ collection: ['a'] })).toStrictEqual({ collection: ['a'] }); 8 | 9 | expect(dotPathObjectToNested({ collection: ['a'], 'collection.0.name': ['b'] })).toStrictEqual({ 10 | collection: { $self: ['a'], $each: [{ name: ['b'] }] }, 11 | }); 12 | 13 | expect(dotPathObjectToNested({ 'collection.0.name': ['b'], collection: ['a'] })).toStrictEqual({ 14 | collection: { $self: ['a'], $each: [{ name: ['b'] }] }, 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /playground/advanced-example/src/validations/custom-rules/check-pseudo.rule.ts: -------------------------------------------------------------------------------- 1 | import type { Maybe } from '@regle/core'; 2 | import { createRule } from '@regle/core'; 3 | import { isFilled } from '@regle/rules'; 4 | import { createTimeout } from '../../utils/timeout'; 5 | 6 | const timeout = createTimeout(); 7 | 8 | export const checkPseudo = createRule({ 9 | async validator(value: Maybe) { 10 | if (isFilled(value)) { 11 | // Check the timeout function to see how cancellation can be handled 12 | await timeout(2000); 13 | return value !== 'regle'; 14 | } 15 | return true; 16 | }, 17 | message: 'The pseudo is already taken', 18 | tooltip({ $error, $pending }) { 19 | if ($error && !$pending) { 20 | return `Your pseudo can't be "regle"`; 21 | } 22 | return ''; 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /packages/rules/src/rules/required.ts: -------------------------------------------------------------------------------- 1 | import type { RegleRuleDefinition } from '@regle/core'; 2 | import { createRule } from '@regle/core'; 3 | import { isFilled } from '../helpers'; 4 | 5 | /** 6 | * Requires non-empty data. Checks for empty arrays and strings containing only whitespaces. 7 | * 8 | * @example 9 | * ```ts 10 | * import { required } from '@regle/rules'; 11 | * 12 | * const { r$ } = useRegle({ name: '' }, { 13 | * name: { required }, 14 | * }) 15 | * ``` 16 | * 17 | * @see {@link https://reglejs.dev/core-concepts/rules/built-in-rules#required Documentation} 18 | */ 19 | export const required: RegleRuleDefinition = createRule({ 20 | type: 'required', 21 | validator: (value: unknown) => { 22 | return isFilled(value); 23 | }, 24 | message: 'This field is required', 25 | }); 26 | -------------------------------------------------------------------------------- /tests/unit/schemas/modifiers/autoDirty/fixtures/valibot.fixture.ts: -------------------------------------------------------------------------------- 1 | import { useRegleSchema } from '@regle/schemas'; 2 | import * as v from 'valibot'; 3 | import { ref } from 'vue'; 4 | 5 | export function valibotAutoDirtyLocalFixture() { 6 | const form = ref({ 7 | email: '', 8 | user: { 9 | firstName: '', 10 | lastName: '', 11 | }, 12 | contacts: [{ name: '' }], 13 | }); 14 | 15 | const schema = v.object({ 16 | email: v.pipe(v.string(), v.email()), 17 | user: v.object({ 18 | firstName: v.pipe(v.string(), v.minLength(1)), 19 | lastName: v.pipe(v.string(), v.minLength(1)), 20 | }), 21 | contacts: v.array( 22 | v.object({ 23 | name: v.pipe(v.string(), v.minLength(1)), 24 | }) 25 | ), 26 | }); 27 | 28 | return useRegleSchema(form, schema, { autoDirty: false }); 29 | } 30 | -------------------------------------------------------------------------------- /tests/unit/schemas/modifiers/rewardEarly/fixtures/valibot.fixture.ts: -------------------------------------------------------------------------------- 1 | import { useRegleSchema } from '@regle/schemas'; 2 | import * as v from 'valibot'; 3 | import { ref } from 'vue'; 4 | 5 | export function valibotRewardEarlyFixture() { 6 | const form = ref({ 7 | email: '', 8 | user: { 9 | firstName: '', 10 | lastName: '', 11 | }, 12 | contacts: [{ name: '' }], 13 | }); 14 | 15 | const schema = v.object({ 16 | email: v.pipe(v.string(), v.email()), 17 | user: v.object({ 18 | firstName: v.pipe(v.string(), v.minLength(1)), 19 | lastName: v.pipe(v.string(), v.minLength(1)), 20 | }), 21 | contacts: v.array( 22 | v.object({ 23 | name: v.pipe(v.string(), v.minLength(1)), 24 | }) 25 | ), 26 | }); 27 | 28 | return useRegleSchema(form, schema, { rewardEarly: true }); 29 | } 30 | -------------------------------------------------------------------------------- /playground/advanced-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "regle-advanced-example", 3 | "private": true, 4 | "version": "1.14.6", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "typecheck": "vue-tsc --noEmit" 11 | }, 12 | "dependencies": { 13 | "@regle/core": "workspace:*", 14 | "@regle/rules": "workspace:*", 15 | "check-password-strength": "catalog:", 16 | "pinia": "catalog:", 17 | "sass": "1.94.2", 18 | "vue": "catalog:", 19 | "vue-tsc": "catalog:" 20 | }, 21 | "devDependencies": { 22 | "@tailwindcss/forms": "0.5.10", 23 | "@vitejs/plugin-vue": "catalog:", 24 | "@vue/tsconfig": "0.8.1", 25 | "autoprefixer": "10.4.22", 26 | "postcss": "8.5.6", 27 | "tailwindcss": "3.4.18", 28 | "vite": "catalog:" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /docs/src/examples/collections.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Collections 3 | aside: false 4 | --- 5 | 6 | # Collections Demo 7 | 8 | This example demonstrates how to use collections (arrays) in your form. 9 | 10 | 11 | Open in StackBlitz 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/src/parts/DisplayingErrors.md: -------------------------------------------------------------------------------- 1 | ```vue [App.vue] 2 | 18 | 19 | 28 | 29 | 38 | ``` 39 | 40 | 41 | -------------------------------------------------------------------------------- /tests/unit/scoped-regle/fixtures/scoped-config.ts: -------------------------------------------------------------------------------- 1 | import { createScopedUseRegle, defineRegleConfig } from '@regle/core'; 2 | import { required, withMessage } from '@regle/rules'; 3 | 4 | const { useRegle } = defineRegleConfig({ 5 | rules: () => ({ 6 | myCustomRule: withMessage(required, 'Custom error'), 7 | }), 8 | }); 9 | 10 | export const { useCollectScope: useScope1Validations, useScopedRegle: useScoped1Regle } = createScopedUseRegle(); 11 | export const { useCollectScope: useScope2Validations, useScopedRegle: useScoped2Regle } = createScopedUseRegle(); 12 | export const { useCollectScope: useScope3Validations, useScopedRegle: useScoped3Regle } = createScopedUseRegle({ 13 | customUseRegle: useRegle, 14 | }); 15 | 16 | export const { useCollectScope: useScope5Validations, useScopedRegle: useScope5Regle } = createScopedUseRegle({ 17 | asRecord: true, 18 | }); 19 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: Unit & E2E tests 2 | on: 3 | push: 4 | paths-ignore: 5 | - 'docs/**' 6 | - '*.md' 7 | - '.github/**' 8 | pull_request: 9 | paths-ignore: 10 | - 'docs/**' 11 | - '*.md' 12 | - '.github/**' 13 | pull_request_review: 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | 19 | strategy: 20 | matrix: 21 | node-version: [22.x] 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | - name: Use Node.js ${{ matrix.node-version }} 26 | uses: actions/setup-node@v1 27 | with: 28 | node-version: ${{ matrix.node-version }} 29 | - uses: pnpm/action-setup@v4 30 | - run: pnpm i 31 | - run: pnpm exec playwright install --with-deps 32 | - run: pnpm build 33 | - run: pnpm run test 34 | env: 35 | CI: true 36 | -------------------------------------------------------------------------------- /packages/core/src/utils/state.utils.ts: -------------------------------------------------------------------------------- 1 | import { effectScope, getCurrentScope, onScopeDispose } from 'vue'; 2 | 3 | export function tryOnScopeDispose(fn: () => any) { 4 | if (getCurrentScope()) { 5 | onScopeDispose(fn); 6 | return true; 7 | } 8 | return false; 9 | } 10 | 11 | /** 12 | * Creates a global state that is shared for scoped validation. 13 | * 14 | * @param stateFactory - The function that creates the state 15 | * @returns The state factory function 16 | */ 17 | export function createGlobalState any>(stateFactory: Fn): Fn { 18 | let initialized = false; 19 | let state: any; 20 | const scope = effectScope(true); 21 | 22 | return ((...args: any[]) => { 23 | if (!initialized) { 24 | state = scope.run(() => stateFactory(...args))!; 25 | initialized = true; 26 | } 27 | return state; 28 | }) as Fn; 29 | } 30 | -------------------------------------------------------------------------------- /playground/vue3/src/components/StressTest.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 38 | -------------------------------------------------------------------------------- /docs/src/advanced-usage/merge-regles.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Merge multiple Regles 3 | --- 4 | 5 | # Merge multiple Regles 6 | 7 | If you need to combine multiple Regle instances into one, it's possible with the `mergeRegles` helper. 8 | 9 | it will return an output similar to the main `r$`, while still being able to call `$touch` or `$validate`. 10 | 11 | All types are preserved. 12 | 13 | 14 | ```ts twoslash 15 | import {required, numeric, email} from '@regle/rules'; 16 | // ---cut--- 17 | // @noErrors 18 | import { mergeRegles, useRegle } from '@regle/core'; 19 | 20 | 21 | const { r$ } = useRegle({email: ''}, { 22 | email: { required, email }, 23 | }); 24 | 25 | const { r$: otherR$ } = useRegle({firstName: ''}, { 26 | firstName: { required }, 27 | }); 28 | 29 | const r$Merged = mergeRegles({ r$, otherR$ }); 30 | 31 | r$Merged.$value.otherR$. 32 | // ^| 33 | 34 | 35 | ``` -------------------------------------------------------------------------------- /playground/advanced-example/src/validations/regle.global.config.ts: -------------------------------------------------------------------------------- 1 | import { defineRegleConfig } from '@regle/core'; 2 | import { required, withMessage } from '@regle/rules'; 3 | import { checkPseudo } from './custom-rules/check-pseudo.rule'; 4 | import { strongPassword } from './custom-rules/strong-password.rule'; 5 | 6 | export const { useRegle: useCustomRegle } = defineRegleConfig({ 7 | rules: () => ({ 8 | /** Customizing default message */ 9 | required: withMessage(required, 'You need to provide a value'), 10 | /** Registering custom rules */ 11 | checkPseudo, 12 | strongPassword, 13 | }), 14 | /** Registering custom properties */ 15 | shortcuts: { 16 | fields: { 17 | $isRequired: (field) => field.$rules.required?.$active ?? false, 18 | }, 19 | nested: { 20 | $isEmpty: (nest) => Object.keys(nest.$fields).length === 0, 21 | }, 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /tests/unit/useRegle/properties/$silentValue.spec.ts: -------------------------------------------------------------------------------- 1 | import { simpleNestedStateWithMixedValidation } from '../../../fixtures'; 2 | import { createRegleComponent } from '../../../utils/test.utils'; 3 | import { shouldBeInvalidField, shouldBePristineField } from '../../../utils/validations.utils'; 4 | 5 | it('should update the source value', async () => { 6 | const { vm } = await createRegleComponent(simpleNestedStateWithMixedValidation); 7 | 8 | vm.r$.$silentValue = { 9 | user: { 10 | firstName: 'foo', 11 | lastName: 'foo', 12 | }, 13 | email: 'foo', 14 | contacts: [{ name: 'foo' }], 15 | }; 16 | await vm.$nextTick(); 17 | 18 | shouldBeInvalidField(vm.r$); 19 | shouldBeInvalidField(vm.r$.email); 20 | shouldBePristineField(vm.r$.user.firstName); 21 | shouldBePristineField(vm.r$.user.lastName); 22 | shouldBePristineField(vm.r$.contacts.$each[0].name); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/core/src/utils/validationGroups.utils.ts: -------------------------------------------------------------------------------- 1 | import type { RegleValidationGroupEntry } from '../types'; 2 | 3 | export function mergeBooleanGroupProperties( 4 | entries: RegleValidationGroupEntry[], 5 | property?: Extract< 6 | keyof NonNullable, 7 | '$invalid' | '$error' | '$pending' | '$dirty' | '$correct' 8 | > 9 | ) { 10 | return entries.some((entry) => { 11 | if (!property) return false; 12 | return entry?.[property]; 13 | }); 14 | } 15 | export function mergeArrayGroupProperties( 16 | entries: RegleValidationGroupEntry[], 17 | property?: Extract, '$errors' | '$silentErrors'> 18 | ) { 19 | if (!property) return []; 20 | return entries.reduce((all, entry) => { 21 | const fetchedProperty = entry?.[property] || []; 22 | return all.concat(fetchedProperty); 23 | }, [] as string[]); 24 | } 25 | -------------------------------------------------------------------------------- /docs/src/parts/components/pinia/ComponentA.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 29 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test'; 2 | 3 | const appPortForTests = 9130; 4 | 5 | export default defineConfig({ 6 | testDir: './ui-tests/specs', 7 | testMatch: /.*\.uispec\.ts/, 8 | fullyParallel: false, 9 | forbidOnly: !!process.env.CI, 10 | retries: process.env.CI ? 1 : 0, 11 | workers: 1, 12 | reporter: 'html', 13 | timeout: 30000, 14 | use: { 15 | screenshot: 'only-on-failure', 16 | trace: 'on-first-retry', 17 | baseURL: `http://localhost:${appPortForTests}`, 18 | }, 19 | projects: [ 20 | { 21 | name: 'chromium', 22 | use: { 23 | ...devices['Desktop Chrome'], 24 | viewport: { width: 1280, height: 800 }, 25 | }, 26 | }, 27 | ], 28 | webServer: [ 29 | { 30 | command: 'pnpm run ui-tests:server', 31 | url: `http://localhost:${appPortForTests}`, 32 | }, 33 | ], 34 | }); 35 | -------------------------------------------------------------------------------- /docs/src/examples/server-validation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Server Validation 3 | aside: false 4 | --- 5 | 6 | # Server Validation Demo 7 | 8 | This example demonstrates how to apply server-side validation to a form. 9 | 10 | 11 | Open in StackBlitz 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Unit tests coverage 2 | 3 | on: 4 | workflow_run: 5 | workflows: [Unit & E2E tests] 6 | branches: 7 | - main 8 | types: 9 | - completed 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | node-version: [22.x] 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v1 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - uses: pnpm/action-setup@v4 26 | - run: pnpm i 27 | - run: pnpm exec playwright install --with-deps 28 | - run: pnpm run test:coverage 29 | - name: Upload coverage reports to Codecov 30 | uses: codecov/codecov-action@v5 31 | with: 32 | token: ${{ secrets.CODECOV_TOKEN }} 33 | env: 34 | CI: true 35 | -------------------------------------------------------------------------------- /docs/src/examples/advanced.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Advanced demo 3 | aside: false 4 | --- 5 | 6 | # Advanced demo 7 | 8 | You can play with the code of this example in the stackblitz sandbox. 9 | 10 | Don't forgot to install the `Vue` extension in the online IDE. 11 | 12 | 13 | Open in StackBlitz 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /playground/vue3/src/components/tests-pinia/CompoB.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/src/examples/simple.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Simple demo 3 | aside: false 4 | --- 5 | 6 | # Simple demo 7 | 8 | You can play with the code of this example in the stackblitz sandbox. 9 | 10 | Don't forgot to install the `Vue` extension in the online IDE. 11 | 12 | 13 | Open in StackBlitz 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /playground/advanced-example/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | logo-reglejs-favicon-svg (1)-svg 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/shared/tests/isEmpty.spec.ts: -------------------------------------------------------------------------------- 1 | import { isEmpty } from '../utils'; 2 | 3 | const emptyFile = new File([''], 'empty.png'); 4 | Object.defineProperty(emptyFile, 'size', { value: 0, configurable: true }); 5 | 6 | const normalFile = new File([''], 'normal.png'); 7 | Object.defineProperty(normalFile, 'size', { value: 1024 * 1024, configurable: true }); 8 | 9 | describe('test the isEmpty helper', () => { 10 | it.each([ 11 | [[], false, false], 12 | [[], true], 13 | [[1], false], 14 | [undefined, true], 15 | [null, true], 16 | [false, false], 17 | [new Date(), false], 18 | [emptyFile, true], 19 | [normalFile, false], 20 | [{}, true], 21 | [{ a: 1 }, false], 22 | [1, false], 23 | ['asd', false], 24 | ['', true], 25 | ])('isEmpty(%s) should be %s', (a, expected, considerEmptyArrayInvalid = true) => { 26 | expect(isEmpty(a, considerEmptyArrayInvalid)).toBe(expected); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/nuxt/test/fixtures/app/components/MyTextArea.vue: -------------------------------------------------------------------------------- 1 |