{"_id":"@devzwo/ngx-signal-schema","_rev":"8-ce605342e50071776419c2a062846a6a","name":"@devzwo/ngx-signal-schema","dist-tags":{"latest":"2.0.0"},"versions":{"1.0.0":{"name":"@devzwo/ngx-signal-schema","version":"1.0.0","license":"MIT","_id":"@devzwo/ngx-signal-schema@1.0.0","maintainers":[{"name":"constantin.mueller","email":"constantin.mueller@devzwo.com"}],"dist":{"shasum":"134ce2483de2f5f39904a30104ed7c6c0848531f","tarball":"https://registry.npmjs.org/@devzwo/ngx-signal-schema/-/ngx-signal-schema-1.0.0.tgz","fileCount":6,"integrity":"sha512-WqcQioa9L4Q9gUGejaBh6mAQuBiA0AMcQKobjiZ7RG+VGo0UdhahHCsPFOfbia1x+ybib7FF/OxFCe2lKE3l+g==","signatures":[{"sig":"MEYCIQD7XrXtO3OnxhlJ0n6jK53wZ6Tg63j6NDUDUebfSsqshgIhAMpddqsW3iCPFlr3r5q6aCsLYGqrXPPSvzDs0GTdpzyd","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":117807},"type":"module","module":"fesm2022/devzwo-ngx-signal-schema.mjs","exports":{".":{"types":"./types/devzwo-ngx-signal-schema.d.ts","default":"./fesm2022/devzwo-ngx-signal-schema.mjs"},"./package.json":{"default":"./package.json"}},"gitHead":"74a84022ce47d5e99c58c637d0f8ede570237300","typings":"types/devzwo-ngx-signal-schema.d.ts","_npmUser":{"name":"constantin.mueller","email":"constantin.mueller@devzwo.com"},"_npmVersion":"11.6.2","description":"Composable schema operators for Angular Signal Forms.","directories":{},"sideEffects":false,"_nodeVersion":"24.12.0","dependencies":{"tslib":"^2.3.0"},"_hasShrinkwrap":false,"peerDependencies":{"@angular/core":"^21.2.0","@angular/common":"^21.2.0"},"_npmOperationalInternal":{"tmp":"tmp/ngx-signal-schema_1.0.0_1780511352836_0.7590020467697303","host":"s3://npm-registry-packages-npm-production"}},"1.0.5":{"name":"@devzwo/ngx-signal-schema","version":"1.0.5","license":"MIT","_id":"@devzwo/ngx-signal-schema@1.0.5","maintainers":[{"name":"constantin.mueller","email":"constantin.mueller@devzwo.com"}],"homepage":"https://github.com/devZWO/ngx-signal-schema#readme","bugs":{"url":"https://github.com/devZWO/ngx-signal-schema/issues"},"dist":{"shasum":"020ee3f3faef1e6cb5aed85b4bf3124ef8e05ff7","tarball":"https://registry.npmjs.org/@devzwo/ngx-signal-schema/-/ngx-signal-schema-1.0.5.tgz","fileCount":5,"integrity":"sha512-sLepWbfbi39UsdrOyX3JzG7sqZXZg7jRlwDlftV1vizWpFKWdPcpy9buLZ+mZPRs0b7fEOi3Kry5wRQ8SL8Jfg==","signatures":[{"sig":"MEYCIQD1s4fOyQHc+NvrVf2jQDNEWZfFN/fyOQQntzfK9fKSGgIhAP+sCc/ICVL8qcOGG2kSgslWU/EahFXCeuMKz3ld+MZy","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@devzwo%2fngx-signal-schema@1.0.5","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":96439},"type":"module","module":"fesm2022/devzwo-ngx-signal-schema.mjs","exports":{".":{"types":"./types/devzwo-ngx-signal-schema.d.ts","default":"./fesm2022/devzwo-ngx-signal-schema.mjs"},"./package.json":{"default":"./package.json"}},"gitHead":"6e81528361afcb6f5c6585f09f0cfedc519dd80a","typings":"types/devzwo-ngx-signal-schema.d.ts","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4cd790da-fbfb-49b0-ba03-63030b42cc7a"}},"repository":{"url":"git+https://github.com/devZWO/ngx-signal-schema.git","type":"git"},"_npmVersion":"11.13.0","description":"Composable schema operators for Angular Signal Forms.","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","dependencies":{"tslib":"^2.3.0"},"_hasShrinkwrap":false,"peerDependencies":{"@angular/core":"^21.2.0","@angular/common":"^21.2.0"},"_npmOperationalInternal":{"tmp":"tmp/ngx-signal-schema_1.0.5_1780521210995_0.45229274897315275","host":"s3://npm-registry-packages-npm-production"}},"1.0.6":{"name":"@devzwo/ngx-signal-schema","version":"1.0.6","keywords":["angular","angular-library","angular-signals","signal-forms","schema","validation","type-safe"],"license":"MIT","_id":"@devzwo/ngx-signal-schema@1.0.6","maintainers":[{"name":"constantin.mueller","email":"constantin.mueller@devzwo.com"}],"homepage":"https://github.com/devZWO/ngx-signal-schema#readme","bugs":{"url":"https://github.com/devZWO/ngx-signal-schema/issues"},"dist":{"shasum":"bcde6cac6f8ecbc06672cbd764840bf353c8c913","tarball":"https://registry.npmjs.org/@devzwo/ngx-signal-schema/-/ngx-signal-schema-1.0.6.tgz","fileCount":5,"integrity":"sha512-jK945yc8XPnBuLxwqI0NnFEsRV8FS7/VjrQXiTYb630Mq6zu2jZINsu7u5zxjKfXYjPRXCMNd5eItecmtflvwg==","signatures":[{"sig":"MEYCIQCg676rog12QCFqdTAYQRntBVg1e6yGP7PHMu0OGm9KDgIhAMZw09zzhCDmDZnWhSOf5XxRqClSxHHTyBz8B7FR2xUM","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@devzwo%2fngx-signal-schema@1.0.6","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":98096},"type":"module","module":"fesm2022/devzwo-ngx-signal-schema.mjs","exports":{".":{"types":"./types/devzwo-ngx-signal-schema.d.ts","default":"./fesm2022/devzwo-ngx-signal-schema.mjs"},"./package.json":{"default":"./package.json"}},"gitHead":"b7c7fb8367350d4a6279eb76f5b549d7cb72f7e3","typings":"types/devzwo-ngx-signal-schema.d.ts","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4cd790da-fbfb-49b0-ba03-63030b42cc7a"}},"repository":{"url":"git+https://github.com/devZWO/ngx-signal-schema.git","type":"git"},"_npmVersion":"11.13.0","description":"Composable schema validation operators for Angular Signal Forms.","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","dependencies":{"tslib":"^2.3.0"},"_hasShrinkwrap":false,"peerDependencies":{"@angular/core":"^21.2.0","@angular/forms":"^21.2.0"},"_npmOperationalInternal":{"tmp":"tmp/ngx-signal-schema_1.0.6_1780663399672_0.9867457363661636","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@devzwo/ngx-signal-schema","version":"1.1.1","keywords":["angular","angular-library","angular-signals","signal-forms","schema","validation","type-safe"],"license":"MIT","_id":"@devzwo/ngx-signal-schema@1.1.1","maintainers":[{"name":"constantin.mueller","email":"constantin.mueller@devzwo.com"}],"homepage":"https://github.com/devZWO/ngx-signal-schema#readme","bugs":{"url":"https://github.com/devZWO/ngx-signal-schema/issues"},"dist":{"shasum":"716943af24f76ec88fe439c156b12051599acce9","tarball":"https://registry.npmjs.org/@devzwo/ngx-signal-schema/-/ngx-signal-schema-1.1.1.tgz","fileCount":5,"integrity":"sha512-56tlj8eAQOU+EGZZk6m5BlX/aE2vcGi8VL2J5ZvIZIUb9qkAgCnsc6ssTFLo3buqZ5m8+vWr21zpGvsRIHSGZQ==","signatures":[{"sig":"MEUCIHO9kDR40gs+ueAapqSasbFoXCMMm+nFAVOh9elYg6cUAiEA7TnaB16zJKujYDFfGAduwb5sVANw9TGhCT5ppYL5O8s=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@devzwo%2fngx-signal-schema@1.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":109486},"type":"module","module":"fesm2022/devzwo-ngx-signal-schema.mjs","exports":{".":{"types":"./types/devzwo-ngx-signal-schema.d.ts","default":"./fesm2022/devzwo-ngx-signal-schema.mjs"},"./package.json":{"default":"./package.json"}},"gitHead":"b4e3caa9155d84cc84759de08519aec950d81f2d","typings":"types/devzwo-ngx-signal-schema.d.ts","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4cd790da-fbfb-49b0-ba03-63030b42cc7a"}},"repository":{"url":"git+https://github.com/devZWO/ngx-signal-schema.git","type":"git"},"_npmVersion":"11.13.0","description":"Composable schema validation operators for Angular Signal Forms.","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","dependencies":{"tslib":"^2.3.0"},"_hasShrinkwrap":false,"peerDependencies":{"@angular/core":"^21.2.0","@angular/forms":"^21.2.0"},"_npmOperationalInternal":{"tmp":"tmp/ngx-signal-schema_1.1.1_1780940544652_0.8441912495146673","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@devzwo/ngx-signal-schema","version":"1.2.0","keywords":["angular","angular-library","angular-signals","signal-forms","schema","validation","type-safe"],"license":"MIT","_id":"@devzwo/ngx-signal-schema@1.2.0","maintainers":[{"name":"constantin.mueller","email":"constantin.mueller@devzwo.com"}],"homepage":"https://github.com/devZWO/ngx-signal-schema#readme","bugs":{"url":"https://github.com/devZWO/ngx-signal-schema/issues"},"dist":{"shasum":"714bb09bee2de33b2d95b4eac7885968e4025ec9","tarball":"https://registry.npmjs.org/@devzwo/ngx-signal-schema/-/ngx-signal-schema-1.2.0.tgz","fileCount":5,"integrity":"sha512-EfxRQy859ntQkkh4/etbcS2b7mWjJIHTw+RKUa6BUMyKwvki+h+8y4Jg+ETs+UbAzTZPu4HbATfZKngFGGymjQ==","signatures":[{"sig":"MEUCIEiB/Rb0V1Itv6K0FXEt73k2esFwWwN+ztgd9AaYegYVAiEAper6LmH6F9jTOlL/UhQAiw610YPnkE2p69dA7W7nyks=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@devzwo%2fngx-signal-schema@1.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":109506},"type":"module","module":"fesm2022/devzwo-ngx-signal-schema.mjs","exports":{".":{"types":"./types/devzwo-ngx-signal-schema.d.ts","default":"./fesm2022/devzwo-ngx-signal-schema.mjs"},"./package.json":{"default":"./package.json"}},"gitHead":"4ef7f64b23b4d247f664612e709617c9fe486b4c","typings":"types/devzwo-ngx-signal-schema.d.ts","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4cd790da-fbfb-49b0-ba03-63030b42cc7a"}},"repository":{"url":"git+https://github.com/devZWO/ngx-signal-schema.git","type":"git"},"_npmVersion":"11.13.0","description":"Composable schema validation operators for Angular Signal Forms.","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","dependencies":{"tslib":"^2.3.0"},"_hasShrinkwrap":false,"peerDependencies":{"@angular/core":">=^21.2.0 <23.0.0","@angular/forms":">=^21.2.0 <23.0.0"},"_npmOperationalInternal":{"tmp":"tmp/ngx-signal-schema_1.2.0_1781018631197_0.2984766734740023","host":"s3://npm-registry-packages-npm-production"}},"1.3.0":{"name":"@devzwo/ngx-signal-schema","version":"1.3.0","keywords":["angular","angular-library","angular-signals","signal-forms","schema","validation","type-safe"],"license":"MIT","_id":"@devzwo/ngx-signal-schema@1.3.0","maintainers":[{"name":"constantin.mueller","email":"constantin.mueller@devzwo.com"}],"homepage":"https://devzwo.github.io/ngx-signal-schema/","bugs":{"url":"https://github.com/devZWO/ngx-signal-schema/issues"},"dist":{"shasum":"534dab3b4b44d73bc6ec3f3cf0e00cd0a02ba333","tarball":"https://registry.npmjs.org/@devzwo/ngx-signal-schema/-/ngx-signal-schema-1.3.0.tgz","fileCount":5,"integrity":"sha512-Cf4psJt5kji1JPJYrOT1GixPaEhfM/vZXwD95epTUxb3QIM2KI658p8ZRLwSLWjn2+dudBiWUacjDtgnJ1AVww==","signatures":[{"sig":"MEYCIQCQ5vdtRLdHb6qlyz62nM41GyKf35osWenfSbJoMrZk9gIhANF+EbYp+y+qwJZAA8AmwN4+ZrMuCki6RvBLVOJLSf1R","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@devzwo%2fngx-signal-schema@1.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":154968},"type":"module","module":"fesm2022/devzwo-ngx-signal-schema.mjs","exports":{".":{"types":"./types/devzwo-ngx-signal-schema.d.ts","default":"./fesm2022/devzwo-ngx-signal-schema.mjs"},"./package.json":{"default":"./package.json"}},"gitHead":"2728401f25ffed2b14276be59b684c989e9148d1","typings":"types/devzwo-ngx-signal-schema.d.ts","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4cd790da-fbfb-49b0-ba03-63030b42cc7a"}},"repository":{"url":"git+https://github.com/devZWO/ngx-signal-schema.git","type":"git"},"_npmVersion":"11.13.0","description":"Composable schema validation operators for Angular Signal Forms.","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","dependencies":{"tslib":"^2.3.0"},"_hasShrinkwrap":false,"peerDependencies":{"@angular/core":">=^21.2.0 <23.0.0","@angular/forms":">=^21.2.0 <23.0.0"},"_npmOperationalInternal":{"tmp":"tmp/ngx-signal-schema_1.3.0_1781464940638_0.33227057991164144","host":"s3://npm-registry-packages-npm-production"}},"1.4.0":{"name":"@devzwo/ngx-signal-schema","version":"1.4.0","keywords":["angular","angular-library","angular-signals","signal-forms","schema","validation","type-safe"],"license":"MIT","_id":"@devzwo/ngx-signal-schema@1.4.0","maintainers":[{"name":"constantin.mueller","email":"constantin.mueller@devzwo.com"}],"homepage":"https://devzwo.github.io/ngx-signal-schema/","bugs":{"url":"https://github.com/devZWO/ngx-signal-schema/issues"},"dist":{"shasum":"d6699ae5fa37414271beb42a0c4ed18374ed4898","tarball":"https://registry.npmjs.org/@devzwo/ngx-signal-schema/-/ngx-signal-schema-1.4.0.tgz","fileCount":5,"integrity":"sha512-pEdsfJNRn//wYpwpI52ylxO18guWxJtYLWE6oVNnuVtFs5pZkKeEE3A1HPgzrttGNIuO7a5F8S8JUg6VuQZTCQ==","signatures":[{"sig":"MEUCIQCBsnC+f/vrT2IiL6gSbttu6KIRVodllQ5UkBIZWtgviwIge9D2De0pR+uAhpfrg3LtWFaimEaMOQTk6OSDNSsuYL0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@devzwo%2fngx-signal-schema@1.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":195034},"type":"module","module":"fesm2022/devzwo-ngx-signal-schema.mjs","exports":{".":{"types":"./types/devzwo-ngx-signal-schema.d.ts","default":"./fesm2022/devzwo-ngx-signal-schema.mjs"},"./package.json":{"default":"./package.json"}},"gitHead":"120387bd995b5a43ef85676783eacfa61610fc65","typings":"types/devzwo-ngx-signal-schema.d.ts","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4cd790da-fbfb-49b0-ba03-63030b42cc7a"}},"repository":{"url":"git+https://github.com/devZWO/ngx-signal-schema.git","type":"git"},"_npmVersion":"11.13.0","description":"Composable schema validation operators for Angular Signal Forms.","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","dependencies":{"tslib":"^2.3.0"},"_hasShrinkwrap":false,"peerDependencies":{"@angular/core":">=^21.2.0 <23.0.0","@angular/forms":">=^21.2.0 <23.0.0"},"_npmOperationalInternal":{"tmp":"tmp/ngx-signal-schema_1.4.0_1782164773738_0.7892550136429763","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"_id":"@devzwo/ngx-signal-schema@2.0.0","bugs":{"url":"https://github.com/devZWO/ngx-signal-schema/issues"},"dist":{"shasum":"b2e531a4388c37e38f53041fa1d0a41989e86927","tarball":"https://registry.npmjs.org/@devzwo/ngx-signal-schema/-/ngx-signal-schema-2.0.0.tgz","fileCount":5,"integrity":"sha512-8x+iiAf6e1aIcF3698ZcvaocMBbRfAkTiViqxKwYkT0gJmUjw430EyYjcigN1u2hu3Tzk5Rafe2n50JMUbwIaw==","signatures":[{"sig":"MEYCIQCUo+xcwO9BfvXYY7UIvH+EmWtqWzYKO2Q3uSdKckWtRwIhAICgOU/KLKas+mWirqOKW1/ef9tpkEV/odfxgqSbK13/","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCyyOtqi460yUglkfzWscAJBejgHu/DnKM1nfopq9lbvQIhAOjRlDwnWI6zVDjEcOH9rouAbKpylMtV3UvDuGFIwk0Y"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@devzwo%2fngx-signal-schema@2.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":166532},"name":"@devzwo/ngx-signal-schema","type":"module","module":"fesm2022/devzwo-ngx-signal-schema.mjs","exports":{".":{"types":"./types/devzwo-ngx-signal-schema.d.ts","default":"./fesm2022/devzwo-ngx-signal-schema.mjs"},"./package.json":{"default":"./package.json"}},"gitHead":"1f73c04456482a3254091b51248e8966143d10d1","license":"MIT","typings":"types/devzwo-ngx-signal-schema.d.ts","version":"2.0.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4cd790da-fbfb-49b0-ba03-63030b42cc7a"}},"homepage":"https://devzwo.github.io/ngx-signal-schema/","keywords":["angular","angular-library","angular-signals","signal-forms","schema","validation","type-safe"],"repository":{"url":"git+https://github.com/devZWO/ngx-signal-schema.git","type":"git"},"_npmVersion":"11.19.0","description":"Composable schema validation operators for Angular Signal Forms.","directories":{},"maintainers":[{"name":"constantin.mueller","email":"constantin.mueller@devzwo.com"}],"sideEffects":false,"_nodeVersion":"24.20.0","dependencies":{"tslib":"^2.3.0"},"_hasShrinkwrap":false,"peerDependencies":{"@angular/core":">=22.0.0 <23.0.0","@angular/forms":">=22.0.0 <23.0.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ngx-signal-schema_2.0.0_1789675148700_0.17311762212212156"}}},"time":{"created":"2026-06-03T18:29:12.673Z","modified":"2026-09-17T19:59:09.104Z","1.0.0":"2026-06-03T18:29:13.016Z","1.0.5":"2026-06-03T21:13:31.133Z","1.0.6":"2026-06-05T12:43:19.822Z","1.1.1":"2026-06-08T17:42:24.793Z","1.2.0":"2026-06-09T15:23:51.350Z","1.3.0":"2026-06-14T19:22:20.761Z","1.4.0":"2026-06-22T21:46:13.867Z","2.0.0":"2026-09-17T19:59:08.782Z"},"bugs":{"url":"https://github.com/devZWO/ngx-signal-schema/issues"},"license":"MIT","homepage":"https://devzwo.github.io/ngx-signal-schema/","keywords":["angular","angular-library","angular-signals","signal-forms","schema","validation","type-safe"],"repository":{"url":"git+https://github.com/devZWO/ngx-signal-schema.git","type":"git"},"description":"Composable schema validation operators for Angular Signal Forms.","maintainers":[{"name":"constantin.mueller","email":"constantin.mueller@devzwo.com"}],"readme":"# @devzwo/ngx-signal-schema\n\n[![Angular](https://img.shields.io/badge/Angular-22+-DD0031?style=flat-square&logo=angular)](https://angular.dev)\n[![CI](https://img.shields.io/github/actions/workflow/status/devZWO/ngx-signal-schema/main.yml?style=flat-square)](https://github.com/devZWO/ngx-signal-schema/actions/workflows/main.yml)\n[![codecov](https://img.shields.io/codecov/c/github/devzwo/ngx-signal-schema?style=flat-square)](https://codecov.io/github/devZWO/ngx-signal-schema)\n[![NPM Version](https://img.shields.io/npm/v/@devzwo/ngx-signal-schema?style=flat-square)](https://npmjs.org/package/@devzwo/ngx-signal-schema)\n[![License](https://img.shields.io/npm/l/%40devzwo%2Fngx-signal-schema?style=flat-square)](https://github.com/devZWO/ngx-signal-schema/blob/main/LICENSE)\n\n\n\n> Composable schema validation operators for\n[Angular Signal Forms](https://angular.dev/essentials/signal-forms).\n>\n> Built for Angular v22+.\n\n---\n\n## Motivation\n\nAngular Signal Forms introduce a powerful, signal-based schema system. Defining schemas with complex dependencies (e.g., \"Field A is only required if Field B has a certain value\") often leads to cluttered boilerplate code. Existing validators are often challenging to combine or extend without redefining the entire schema.\n\n> As form complexity grows, so does the need for reusable and modular validation logic. `@devzwo/ngx-signal-schema` provides a collection of helper functions to design complex schemas declaratively and maintainably.\n\n---\n\n## ✨ Features\n\n- **Composable**: Schemata can be easily combined and extended.\n- **Declarative**: Clear structure through `composition`, `conditions`, `rules`, and `validators`.\n- **Type-safe**: Full support for TypeScript typing of Angular Signal Forms.\n- **Tree-shakeable**: Functional API ensures minimal bundle size impact.\n- **Future-proof**: Built directly on top of `@angular/forms/signals`.\n\n---\n\n## 📦 Installation\n\n```bash\nnpm install @devzwo/ngx-signal-schema\n```\n\nor\n\n```bash\npnpm install @devzwo/ngx-signal-schema\n```\n\n### Peer Dependencies\n\nEnsure that the following packages are installed in your project:\n\n- `@angular/core`: ^21.2.0\n- `@angular/forms`: ^21.2.0\n\n---\n\n## 🚀 Usage & Examples\n\n### Simple Composition with `compose`\n\nWith `compose`, existing schemas can be easily extended.\n\n```typescript\nimport {compose, requiredTrimmed} from '@devzwo/ngx-signal-schema';\n\nconst MySchema = compose(BaseSchema, required(path.name));\n```\n\n**Standard Signal Forms equivalent:**\n\n```typescript\nimport {schema, apply} from '@angular/forms/signals';\n\nconst MySchema = schema((path) => {\n    apply(path, BaseSchema)\n    required(path.name)\n});\n```\n\n### Conditional Validation with `applyIf`\n\nConditional logic can be mapped without deeply nested structures.\n\n```typescript\nimport {applyIf, valueEquals} from '@devzwo/ngx-signal-schema';\n\napplyIf(\n    path.billingAddress,\n    valueEquals(path.useDifferentBillingAddress, true),\n    AddressSchema,\n    EmptySchema\n);\n```\n\n**Standard Signal Forms equivalent:**\n\n```typescript\nimport {applyWhen} from '@angular/forms/signals';\n\n// \"Then\" case\napplyWhen(\n    path.billingAddress,\n    (ctx) => ctx.valueOf(path.useDifferentBillingAddress),\n    AddressSchema\n);\n\n// \"Else\" case (requires manual negation)\napplyWhen(\n    path.billingAddress,\n    (ctx) => !ctx.valueOf(path.useDifferentBillingAddress),\n    EmptySchema\n);\n```\n\n### Combined Rules with `inactive`\n\nSimplify UI logic by combining state rules with declarative conditions. This is particularly useful because **disabled fields are automatically excluded from validation and submission**, and hiding them ensures the UI stays clean and relevant.\n\n```typescript\nimport {inactive, valueIn, not} from '@devzwo/ngx-signal-schema';\n\n// Field is only relevant for DACH region\ninactive(\n    path.taxNumber,\n    not(valueIn(path.country, ['DE', 'AT', 'CH']))\n);\n```\n\n**Standard Signal Forms equivalent:**\n\n```typescript\nimport {disabled, hidden} from '@angular/forms/signals';\n\nconst isForeign = (ctx) => !['DE', 'AT', 'CH'].includes(ctx.valueOf(path.country));\n\ndisabled(path.taxNumber, isForeign);\nhidden(path.taxNumber, isForeign);\n```\n\n### Numeric Validation with `decimal` and `integer`\n\nThese validators check the **structural shape** of numeric values (integer digits and fractional digits) rather than just their range. They also support localized string parsing (defaulting to `de-DE`).\n\n```typescript\nimport {decimal, integer} from '@devzwo/ngx-signal-schema';\n\n// Allows up to 5 digits before and 2 digits after the decimal separator\ndecimal(path.price, {maxIntegerDigits: 5, maxFractionDigits: 2});\n\n// Allows up to 3 digits, strictly as an integer\ninteger(path.count, {maxDigits: 3});\n```\n\n### File Type Validation with `mimeType`\n\nEnsure that uploaded files match specific formats. Supports exact types, wildcards (e.g., `image/*`), and reactive updates via signal-based functions.\n\n```typescript\nimport {mimeType} from '@devzwo/ngx-signal-schema';\n\n// Static array of allowed types with wildcard support\nmimeType(path.attachment, ['image/*', 'application/pdf']);\n\n// Dynamic/Reactive allowed types (e.g., from a config signal)\nmimeType(path.avatar, () => this.allowedAvatarTypes());\n```\n\n### Flexible Schema Definitions\n\nThe composition operators are highly flexible. Wherever a `Schema` is expected, you can also provide a **Schema Function** — a function that receives the current `path` and applies rules or validators directly. This allows you to mix reusable schemas with custom inline logic.\n\n```typescript\nimport {compose, inactive, valueEquals} from '@devzwo/ngx-signal-schema';\n\nconst MySchema = compose(\n    //full schema\n    BaseSchema,\n    // Reusable schema\n    inactive(path.status, isForeign),\n    // Inline rule instead of a full schema object\n    (path) => inactive(path.subfield, valueEquals(path.status, 'inactive'))\n);\n```\n\n### Explicit Presence with `requiredDefined`\n\nThe standard `required` validator treat `false` as an invalid value because it is falsy. `requiredDefined` is necessary when `false` is a valid input (e.g., in checkboxes or toggles) but a selection is strictly mandatory.\n\n```typescript\nimport {requiredDefined} from '@devzwo/ngx-signal-schema';\n\n// Rejects null/undefined, but accepts both true and false\nrequiredDefined(path.agreedToTerms);\n```\n\n**Standard Signal Forms equivalent:**\n\n```typescript\nimport {validate} from '@angular/forms/signals';\n\nvalidate(path.agreedToTerms, (ctx) => {\n    return ctx.value() !== null && ctx.value() !== undefined\n        ? null\n        : {kind: 'required'};\n});\n```\n\n### Stable Form Structures with `OptionalBlock`\n\nAngular Signal Forms require a **stable object structure** to correctly track signals. You cannot use subtrees that are `null` or `undefined` if you want to apply rules or validation to their nested fields.\n\n`OptionalBlock` provides stable containers for optional data and collections, ensuring the signal tree remains intact even when parts of the form are logically absent or empty.\n\n\n```typescript\nimport {OptionalBlock, mapToOptionalBlock} from '@devzwo/ngx-signal-schema';\n\ninterface UserProfile {\n    // ❌ BAD: Signal Form cannot track fields inside null/undefined\n    // address?: { street: string }; \n\n    // ✅ GOOD: Stable structure even if \"disabled\"\n    address: OptionalBlock<{ street: string; city: string }>;\n}\n\n// Initializing the form\nconst initialValue: UserProfile = {\n    address: mapToOptionalBlock({street: '', city: ''}, false), // disabled by default\n};\n```\n\nIn your schema, use `applyOptional` to apply rules to the inner data of an `OptionalBlock`:\n\n```typescript\nimport {compose, applyOptional, requiredTrimmed} from '@devzwo/ngx-signal-schema';\n\nconst UserSchema = compose(\n    (path) => {\n        // Validation is only active when address.meta.enabled is true\n        applyOptional(path.address, (data) => {\n            requiredTrimmed(data.street);\n            requiredTrimmed(data.city);\n        });\n    }\n);\n```\n\n### Array Validation with `unique`\n\nStandard Signal Forms does not have any validation arrays. You can use the `unique` validator to ensure all elements in a collection are distinct. By default, it marks the individual duplicate items as invalid.\n\n```typescript\nimport {unique} from '@devzwo/ngx-signal-schema';\n\n// Basic usage: checks for duplicates in the array\n// For strings: case-insensitive and trimmed by default\nunique(path.tags);\n\n// Advanced usage: custom equality and error placement\nunique(path.employees, {\n    equalFn: (a, b) => a.email === b.email,\n    destination: 'both', // Mark both the Array itself and the duplicate items in the array\n    error: { message: 'Duplicate email addresses found' }\n});\n```\n\n---\n\n## 📚 API\n\nThe package is divided into four logical areas:\n\n### 1. Composition\n\nUtilities for structuring and combining schemas.\n\n- `compose(base, ...extensions)`: Extends a base schema with additional rules or schemas. Supports both `Schema` objects and inline functions (`(path) => void`).\n- `applyIf(path, condition, thenSchema, elseSchema)`: Conditionally applies one of two schemas or inline functions (branching logic).\n\n### 2. Conditions\n\nPredicates that can be used in rules or conditional schemas.\n\n- `valueEquals(path, expected)`: Checks for exact equality of a field value.\n- `valueIn(path, values)`: Checks if a field value is contained in a list.\n- `includes(path, value)`: Checks if an array field contains a specific value.\n- `not(rule)`: Negates an existing `SchemaRule`.\n- `and(...rules)`: Combines multiple rules with logical AND.\n- `or(...rules)`: Combines multiple rules with logical OR.\n\n### 3. Rules\n\nStructural field configurations.\n\n- `inactive(path, options)`: Applies both `disabled` and `hidden` to a field simultaneously. Prevents both user interaction and validation for irrelevant fields.\n\n### 4. Validators\n\nSpecialized validators for Signal Forms.\n\n- `requiredTrimmed(path)`: **Standard text required.** Checks for content while removing leading/trailing whitespace. Recommended for all string inputs. **Error key:** `required`\n- `requiredDefined(path)`: **Mandatory selection.** Ensures that a value is neither `null` nor `undefined`. Essential for booleans (toggles, checkboxes) where `false` is a valid selection but a choice is required. **Not recommended for strings.** **Error key:** `required`\n- `requiredAtLeastOne(path, fields)`: Validates that at least one of the specified fields is filled. **Error key:** `group.requiredAtLeastOne`\n- `requiredIfOtherFilled(path, otherPath)`: Makes a field required as soon as another field contains a value. **Error key:** `required` (default, configurable)\n- `decimal(path, options)`: Validates that a value matches a decimal format. **Error keys:** `decimal.isNumber`, `decimal.intCount`, `decimal.fractCount`\n- `integer(path, options)`: Validates that a value is a whole number. **Error keys:** `integer.isInteger`, `integer.digitCount`\n- `mimeType(path, allowedTypes)`: Validation of file types. Supports wildcards (`image/*`) and reactive arrays. **Error key:** `mimeType`\n- `unique(path, options)`: Ensures all items in an array are unique. Supports custom equality logic and can attach errors to the container, the duplicate items, or both. **Error key:** `unique`\n- `oneOfPattern(values, options)`: Helper that generates a `RegExp` matching any of the provided values. Useful with the built-in `pattern` validator.\n- `year(path)`: Specialized validator for years (`YYYY`) in text form. **Error key:** `year`\n\n### 5. Structures\n\nUtilities for modeling complex data structures managing optional state.\n\n- `optionalBlock(options)`: Creates a reusable schema for `OptionalBlock<T>`, allowing rules to be applied conditionally based on the block's enabled state.\n- `applyOptional(path, options)`: Applies rules or schemas directly to the `data` node of an `OptionalBlock` field.\n- `mapToOptionalBlock(data, enabled)`: Utility to wrap domain data into an `OptionalBlock` structure.\n- `mapFromOptionalBlock(block)`: Utility to extract data from an `OptionalBlock` if it is enabled.\n- `isOptionalBlock(obj)`: Type guard for `OptionalBlock`.\n\n## Maintainer\n\nThis project is developed and maintained by the **devZWO GmbH**.\n\nhttps://devzwo.com\n","readmeFilename":"README.md"}